another.im-ios/Monal/Classes/MLSignalStore.m
2024-11-18 15:53:52 +01:00

681 lines
29 KiB
Objective-C

//
// MLSignalStore.m
// Monal
//
// Created by Anurodh Pokharel on 5/3/18.
// Copyright © 2018 Monal.im. All rights reserved.
//
#import "MLConstants.h"
#import "MLSignalStore.h"
#import "SignalProtocolObjC.h"
#import "DataLayer.h"
#import "MLSQLite.h"
#import "HelperTools.h"
@interface MLSignalStore()
{
NSString* _dbPath;
}
@property (nonatomic, strong) NSNumber* accountID;
@property (readonly, strong) MLSQLite* sqliteDatabase;
@end
@implementation MLSignalStore
+(void) initialize
{
//TODO: WE USE THE SAME DATABASE FILE AS THE DataLayer --> this should probably be migrated into the datalayer or use its own sqlite database
//make sure the datalayer has migrated the database file to the app group location first
[DataLayer initialize];
}
//this is the getter of our readonly "sqliteDatabase" property always returning the thread-local instance of the MLSQLite class
-(MLSQLite*) sqliteDatabase
{
//always return thread-local instance of sqlite class (this is important for performance!)
return [MLSQLite sharedInstanceForFile:_dbPath];
}
-(MLSignalStore*) initWithAccountID:(NSNumber*) accountID andAccountJid:(NSString* _Nonnull) accountJid
{
self = [super init];
_dbPath = [[HelperTools getContainerURLForPathComponents:@[@"sworim.sqlite"]] path];
self.accountID = accountID;
NSArray* data = [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeReader:@"SELECT identityPrivateKey, deviceid, identityPublicKey FROM signalIdentity WHERE account_id=?;" andArguments:@[accountID]];
}];
NSDictionary* row = [data firstObject];
if(row)
{
self.deviceid = [(NSNumber *)[row objectForKey:@"deviceid"] unsignedIntValue];
self.accountJid = accountJid;
NSData* idKeyPub = [row objectForKey:@"identityPublicKey"];
NSData* idKeyPrivate = [row objectForKey:@"identityPrivateKey"];
NSError* error;
self.identityKeyPair = [[SignalIdentityKeyPair alloc] initWithPublicKey:idKeyPub privateKey:idKeyPrivate error:nil];
if(error)
{
DDLogError(@"prekey error %@", error);
return self;
}
self.signedPreKey = [[SignalSignedPreKey alloc] initWithSerializedData:[self loadSignedPreKeyWithId:1] error:&error];
if(error)
{
DDLogError(@"signed prekey error %@", error);
return self;
}
// remove old keys that should no longer be available
[self cleanupKeys];
[self reloadCachedPrekeys];
}
else
self.deviceid = 0;
return self;
}
-(void) reloadCachedPrekeys
{
self.preKeys = [self readPreKeys];
}
-(void) cleanupKeys
{
[self.sqliteDatabase voidWriteTransaction:^{
// remove old keys that have been remove a long time ago from pubsub
[self.sqliteDatabase executeNonQuery:@"DELETE FROM signalPreKey WHERE account_id=? AND pubSubRemovalTimestamp IS NOT NULL AND pubSubRemovalTimestamp <= unixepoch('now', '-14 day');" andArguments:@[self.accountID]];
// mark old unused keys to be removed from pubsub
[self.sqliteDatabase executeNonQuery:@"UPDATE signalPreKey SET pubSubRemovalTimestamp=CURRENT_TIMESTAMP WHERE account_id=? AND keyUsed=0 AND pubSubRemovalTimestamp IS NULL AND creationTimestamp<= unixepoch('now','-14 day');" andArguments:@[self.accountID]];
}];
}
-(NSMutableArray<SignalPreKey*>*) readPreKeys
{
NSArray* keys = [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeReader:@"SELECT prekeyid, preKey FROM signalPreKey WHERE account_id=? AND keyUsed=0;" andArguments:@[self.accountID]];
}];
NSMutableArray<SignalPreKey*>* array = [[NSMutableArray alloc] initWithCapacity:keys.count];
for (NSDictionary* row in keys)
{
SignalPreKey* key = [[SignalPreKey alloc] initWithSerializedData:[row objectForKey:@"preKey"] error:nil];
[array addObject:key];
}
return array;
}
-(int) getHighestPreyKeyId
{
NSNumber* highestId = [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalar:@"SELECT prekeyid FROM signalPreKey WHERE account_id=? ORDER BY prekeyid DESC LIMIT 1;" andArguments:@[self.accountID]];
}];
if(highestId == nil) {
return 0; // Default value -> first preKeyId will be 1
} else {
return highestId.intValue;
}
}
-(unsigned int) getPreKeyCount
{
NSNumber* count = [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalar:@"SELECT COUNT(prekeyid) FROM signalPreKey WHERE account_id=? AND pubSubRemovalTimestamp IS NULL AND keyUsed=0;" andArguments:@[self.accountID]];
}];
return count.unsignedIntValue;
}
-(void) saveValues
{
[self storeSignedPreKey:self.signedPreKey.serializedData signedPreKeyId:1];
[self storeIdentityPublicKey:self.identityKeyPair.publicKey andPrivateKey:self.identityKeyPair.privateKey];
for (SignalPreKey *key in self.preKeys)
{
[self storePreKey:key.serializedData preKeyId:key.preKeyId];
}
[self reloadCachedPrekeys];
}
/**
* Returns a copy of the serialized session record corresponding to the
* provided recipient ID + device ID tuple.
* or nil if not found.
*/
- (NSData* _Nullable) sessionRecordForAddress:(SignalAddress*) address
{
return [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalar:@"SELECT recordData FROM signalContactSession WHERE account_id=? AND contactName=? AND contactDeviceId=?;" andArguments:@[self.accountID, address.name, [NSNumber numberWithInteger:address.deviceId]]];
}];
}
/**
* Commit to storage the session record for a given
* recipient ID + device ID tuple.
*
* Return YES on success, NO on failure.
*/
-(BOOL) storeSessionRecord:(NSData*) recordData forAddress:(SignalAddress*) address
{
return [self.sqliteDatabase boolWriteTransaction:^{
if([self sessionRecordForAddress:address])
return [self.sqliteDatabase executeNonQuery:@"UPDATE signalContactSession SET recordData=? WHERE account_id=? AND contactName=? AND contactDeviceId=?;" andArguments:@[recordData, self.accountID, address.name, [NSNumber numberWithInteger:address.deviceId]]];
else
return [self.sqliteDatabase executeNonQuery:@"INSERT INTO signalContactSession (account_id, contactName, contactDeviceId, recordData) VALUES (?, ?, ?, ?);" andArguments:@[self.accountID, address.name, [NSNumber numberWithInteger:address.deviceId], recordData]];
}];
}
/**
* Determine whether there is a committed session record for a
* recipient ID + device ID tuple.
*/
- (BOOL) sessionRecordExistsForAddress:(SignalAddress*) address;
{
NSData* preKeyData = [self sessionRecordForAddress:address];
return preKeyData ? YES : NO;
}
/**
* Remove a session record for a recipient ID + device ID tuple.
*/
- (BOOL) deleteSessionRecordForAddress:(SignalAddress*) address
{
return [self.sqliteDatabase boolWriteTransaction:^{
return [self.sqliteDatabase executeNonQuery:@"DELETE FROM signalContactSession WHERE account_id=? AND contactName=? AND contactDeviceId=?;" andArguments:@[self.accountID, address.name, [NSNumber numberWithInteger:address.deviceId]]];
}];
}
/**
* Returns all known devices with active sessions for a recipient
*/
- (NSArray<NSNumber*>*) allDeviceIdsForAddressName:(NSString*) jid
{
return [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalarReader:@"SELECT DISTINCT contactDeviceId FROM signalContactSession WHERE account_id=? AND contactName=?;" andArguments:@[self.accountID, jid]];
}];
}
-(NSArray<NSNumber*>*) knownDevicesForAddressName:(NSString*) jid
{
if(!jid)
return nil;
return [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalarReader:@"SELECT DISTINCT contactDeviceId FROM signalContactIdentity WHERE account_id=? AND contactName=? AND removedFromDeviceList IS NULL;" andArguments:@[self.accountID, jid]];
}];
}
-(NSArray<NSNumber*>*) knownDevicesWithValidSession:(NSString*) jid
{
return [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalarReader:@"\
SELECT DISTINCT \
contactDeviceId \
FROM signalContactIdentity \
WHERE \
account_id=? \
AND contactName=? \
AND removedFromDeviceList IS NULL \
AND brokenSession=false \
;" andArguments:@[self.accountID, jid]];
}];
}
-(NSArray<NSNumber*>*) knownDevicesWithPendingBrokenSessionHandling:(NSString*) jid
{
return [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalarReader:@"\
SELECT DISTINCT \
contactDeviceId \
FROM signalContactIdentity \
WHERE \
account_id=? \
AND contactName=? \
AND removedFromDeviceList IS NULL \
AND brokenSession=true \
AND (lastFailedBundleFetch IS NULL OR lastFailedBundleFetch <= unixepoch('now', '-5 day'))\
;" andArguments:@[self.accountID, jid]];
}];
}
/**
* Remove the session records corresponding to all devices of a recipient ID.
*
* @return the number of deleted sessions on success, negative on failure
*/
-(int) deleteAllSessionsForAddressName:(NSString*) addressName
{
return [[self.sqliteDatabase idWriteTransaction:^{
NSNumber* count = (NSNumber*) [self.sqliteDatabase executeScalar:@"COUNT * FROM signalContactSession WHERE account_id=? AND contactName=?;" andArguments:@[self.accountID, addressName]];
[self.sqliteDatabase executeNonQuery:@"DELETE FROM signalContactSession WHERE account_id=? AND contactName=?;" andArguments:@[self.accountID, addressName]];
return count;
}] intValue];
}
/**
* Load a local serialized PreKey record.
* return nil if not found
*/
- (nullable NSData*) loadPreKeyWithId:(uint32_t) preKeyId;
{
DDLogDebug(@"Loading prekey %lu", (unsigned long)preKeyId);
return [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalar:@"SELECT prekey FROM signalPreKey WHERE account_id=? AND prekeyid=? AND keyUsed=0;" andArguments:@[self.accountID, [NSNumber numberWithInteger:preKeyId]]];
}];
}
/**
* Store a local serialized PreKey record.
* return YES if storage successful, else NO
*/
-(BOOL) storePreKey:(NSData*) preKey preKeyId:(uint32_t) preKeyId
{
DDLogDebug(@"Storing prekey %lu", (unsigned long)preKeyId);
return [self.sqliteDatabase boolWriteTransaction:^{
// Only store new preKeys
NSNumber* preKeyCnt = [self.sqliteDatabase executeScalar:@"SELECT count(*) FROM signalPreKey WHERE account_id=? AND prekeyid=? AND preKey=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:preKeyId], preKey]];
if(preKeyCnt.intValue > 0)
return YES;
return [self.sqliteDatabase executeNonQuery:@"INSERT INTO signalPreKey (account_id, prekeyid, preKey) VALUES (?, ?, ?);" andArguments:@[self.accountID, [NSNumber numberWithInteger:preKeyId], preKey]];
}];
}
/**
* Determine whether there is a committed PreKey record matching the
* provided ID.
*/
-(BOOL) containsPreKeyWithId:(uint32_t) preKeyId;
{
NSData* prekey = [self loadPreKeyWithId:preKeyId];
return prekey ? YES : NO;
}
/**
* Delete a PreKey record from local storage.
*/
-(BOOL) deletePreKeyWithId:(uint32_t) preKeyId
{
DDLogDebug(@"Marking prekey %lu as deleted", (unsigned long)preKeyId);
// only mark the key for deletion -> key should be removed from pubSub
return [self.sqliteDatabase boolWriteTransaction:^{
BOOL ret = [self.sqliteDatabase executeNonQuery:@"UPDATE signalPreKey SET pubSubRemovalTimestamp=CURRENT_TIMESTAMP, keyUsed=1 WHERE account_id=? AND prekeyid=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:preKeyId]]];
[self reloadCachedPrekeys];
return ret;
}];
}
/**
* Load a local serialized signed PreKey record.
*/
-(nullable NSData*) loadSignedPreKeyWithId:(uint32_t) signedPreKeyId
{
DDLogDebug(@"Loading signed prekey %lu", (unsigned long)signedPreKeyId);
return [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalar:@"SELECT signedPreKey FROM signalSignedPreKey WHERE account_id=? AND signedPreKeyId=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:signedPreKeyId]]];
}];
}
/**
* Store a local serialized signed PreKey record.
*/
- (BOOL) storeSignedPreKey:(NSData*) signedPreKey signedPreKeyId:(uint32_t) signedPreKeyId
{
DDLogDebug(@"Storing signed prekey %lu", (unsigned long)signedPreKeyId);
return [self.sqliteDatabase boolWriteTransaction:^{
return [self.sqliteDatabase executeNonQuery:@"INSERT INTO signalSignedPreKey (account_id, signedPreKeyId, signedPreKey) VALUES (?, ?, ?) ON CONFLICT(account_id, signedPreKeyId) DO UPDATE SET signedPreKey=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:signedPreKeyId], signedPreKey, signedPreKey]];
}];
}
/**
* Determine whether there is a committed signed PreKey record matching
* the provided ID.
*/
- (BOOL) containsSignedPreKeyWithId:(uint32_t) signedPreKeyId
{
return [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalar:@"SELECT signedPreKey FROM signalSignedPreKey WHERE account_id=? AND signedPreKeyId=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:signedPreKeyId]]];
}] ? YES : NO;
}
/**
* Delete a SignedPreKeyRecord from local storage.
*/
- (BOOL) removeSignedPreKeyWithId:(uint32_t) signedPreKeyId
{
DDLogDebug(@"Removing signed prekey %lu", (unsigned long)signedPreKeyId);
return [self.sqliteDatabase boolWriteTransaction:^{
return [self.sqliteDatabase executeNonQuery:@"DELETE FROM signalSignedPreKey WHERE account_id=? AND signedPreKeyId=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:signedPreKeyId]]];
}];
}
/**
* Get the local client's identity key pair.
*/
-(SignalIdentityKeyPair*) getIdentityKeyPair;
{
return self.identityKeyPair;
}
-(BOOL) identityPublicKeyExists:(NSData*) publicKey andPrivateKey:(NSData *) privateKey
{
return [[self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalar:@"SELECT COUNT(*) FROM signalIdentity WHERE account_id=? AND deviceid=? AND identityPublicKey=? AND identityPrivateKey=?;" andArguments:@[self.accountID, [NSNumber numberWithUnsignedInt:self.deviceid], publicKey, privateKey]];
}] boolValue];
}
- (BOOL) storeIdentityPublicKey:(NSData*) publicKey andPrivateKey:(NSData*) privateKey
{
return [self.sqliteDatabase boolWriteTransaction:^{
if([self identityPublicKeyExists:publicKey andPrivateKey:privateKey])
return YES;
return [self.sqliteDatabase executeNonQuery:@"INSERT INTO signalIdentity (account_id, deviceid, identityPublicKey, identityPrivateKey) values (?, ?, ?, ?);" andArguments:@[self.accountID, [NSNumber numberWithInteger:self.deviceid], publicKey, privateKey]];
}];
}
/**
* Return the local client's registration ID.
*
* Clients should maintain a registration ID, a random number
* between 1 and 16380 that's generated once at install time.
*
* return negative on failure
*/
- (uint32_t) getLocalRegistrationId;
{
return self.deviceid;
}
/**
* Save a remote client's identity key
* <p>
* Store a remote client's identity key as trusted.
* The value of key_data may be null. In this case remove the key data
* from the identity store, but retain any metadata that may be kept
* alongside it.
*/
-(BOOL) saveIdentity:(SignalAddress* _Nonnull) address identityKey:(NSData* _Nullable) identityKey;
{
return [self.sqliteDatabase boolWriteTransaction:^{
NSData* dbIdentity= (NSData *)[self.sqliteDatabase executeScalar:@"SELECT IDENTITY FROM signalContactIdentity WHERE account_id=? AND contactDeviceId=? AND contactName=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name]];
if(dbIdentity)
return YES;
// Set every new identity to TOFU to increase user experience
return [self.sqliteDatabase executeNonQuery:@"INSERT INTO signalContactIdentity \
(account_id, contactName, contactDeviceId, identity, trustLevel) \
VALUES (?, ?, ?, ?, ?)" andArguments:@[self.accountID, address.name, [NSNumber numberWithInteger:address.deviceId], identityKey, [NSNumber numberWithInt:MLOmemoInternalToFU]]];
}];
}
/**
* Verify a remote client's identity key.
*
* Determine whether a remote client's identity is trusted. Convention is
* that the TextSecure protocol is 'trust on first use.' This means that
* an identity key is considered 'trusted' if there is no entry for the recipient
* in the local store, or if it matches the saved key for a recipient in the local
* store. Only if it mismatches an entry in the local store is it considered
* 'untrusted.'
*/
-(BOOL) isTrustedIdentity:(SignalAddress*) address identityKey:(NSData*) identityKey;
{
int trustLevel = [self getTrustLevel:address identityKey:identityKey].intValue;
// For better UX trust ToFU devices even if we did not receive msg in a long time
return (trustLevel == MLOmemoTrusted || trustLevel == MLOmemoToFU || trustLevel == MLOmemoToFUButNoMsgSeenInTime);
}
-(NSData*) getIdentityForAddress:(SignalAddress*) address
{
return [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalar:@"SELECT identity FROM signalContactIdentity WHERE account_id=? AND contactDeviceId=? AND contactName=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name]];
}];
}
/*
* ToFU independent trust update
* true -> trust
* false -> don't trust
* -> after calling updateTrust once ToFU will be over
*/
-(void) updateTrust:(BOOL) trust forAddress:(SignalAddress*) address
{
[self.sqliteDatabase voidWriteTransaction:^{
[self.sqliteDatabase executeNonQuery:@"UPDATE signalContactIdentity SET trustLevel=? WHERE account_id=? AND contactDeviceId=? AND contactName=?;" andArguments:@[[NSNumber numberWithInt:(trust ? MLOmemoInternalTrusted : MLOmemoInternalNotTrusted)], self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name]];
}];
}
-(void) markDeviceAsDeleted:(SignalAddress*) address
{
[self.sqliteDatabase voidWriteTransaction:^{
[self.sqliteDatabase executeNonQuery:@"UPDATE signalContactIdentity SET removedFromDeviceList=CURRENT_TIMESTAMP WHERE account_id=? AND contactDeviceId=? AND contactName=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name]];
}];
}
-(void) removeDeviceDeletedMark:(SignalAddress*) address
{
[self.sqliteDatabase voidWriteTransaction:^{
[self.sqliteDatabase executeNonQuery:@"UPDATE signalContactIdentity SET removedFromDeviceList=NULL WHERE account_id=? AND contactDeviceId=? AND contactName=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name]];
}];
}
/*
* update lastReceivedMsg to CURRENT_TIMESTAMP
* reset brokenSession to false
*/
-(void) updateLastSuccessfulDecryptTime:(SignalAddress*) address
{
[self.sqliteDatabase voidWriteTransaction:^{
[self.sqliteDatabase executeNonQuery:@"UPDATE signalContactIdentity SET lastReceivedMsg=CURRENT_TIMESTAMP, brokenSession=false WHERE account_id=? AND contactDeviceId=? AND contactName=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name]];
}];
}
-(void) markSessionAsBroken:(SignalAddress*) address
{
[self.sqliteDatabase voidWriteTransaction:^{
[self.sqliteDatabase executeNonQuery:@"UPDATE signalContactIdentity SET brokenSession=true WHERE account_id=? AND contactDeviceId=? AND contactName=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name]];
}];
}
-(void) markBundleAsFixed:(SignalAddress*) address
{
[self.sqliteDatabase voidWriteTransaction:^{
[self.sqliteDatabase executeNonQuery:@"UPDATE signalContactIdentity SET brokenSession=false, lastFailedBundleFetch=NULL WHERE account_id=? AND contactDeviceId=? AND contactName=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name]];
}];
}
-(void) markBundleAsBroken:(SignalAddress*) address
{
[self.sqliteDatabase voidWriteTransaction:^{
[self.sqliteDatabase executeNonQuery:@"UPDATE signalContactIdentity SET lastFailedBundleFetch=unixepoch('now') WHERE account_id=? AND contactDeviceId=? AND contactName=?;" andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name]];
}];
}
-(BOOL) isSessionBrokenForJid:(NSString*) jid andDeviceId:(NSNumber*) deviceId
{
return [self.sqliteDatabase boolReadTransaction:^{
return [[self.sqliteDatabase executeScalar:@"SELECT brokenSession FROM signalContactIdentity WHERE account_id=? AND contactDeviceId=? AND contactName=?;" andArguments:@[self.accountID, deviceId, jid]] boolValue];
}];
}
-(int) getInternalTrustLevel:(SignalAddress*) address identityKey:(NSData*) identityKey
{
return [[self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalar:(@"SELECT trustLevel FROM signalContactIdentity WHERE account_id=? AND contactDeviceId=? AND contactName=? AND identity=?;") andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name, identityKey]];
}] intValue];
}
-(void) untrustAllDevicesFrom:(NSString*) jid
{
if([jid isEqualToString:self.accountJid] == NO)
{
// untrust all devices
[self.sqliteDatabase voidWriteTransaction:^{
[self.sqliteDatabase executeNonQuery:@"UPDATE signalContactIdentity SET trustLevel=? WHERE account_id=? AND contactName=?;" andArguments:@[[NSNumber numberWithInt:MLOmemoInternalNotTrusted], self.accountID, jid]];
}];
}
else
{
// untrust all of our own devices except our own device id
[self.sqliteDatabase voidWriteTransaction:^{
[self.sqliteDatabase executeNonQuery:@"UPDATE signalContactIdentity SET trustLevel=? WHERE account_id=? AND contactName=? AND contactDeviceId!=?;" andArguments:@[[NSNumber numberWithInt:MLOmemoInternalNotTrusted], self.accountID, jid, [NSNumber numberWithUnsignedInt:self.deviceid]]];
}];
}
}
-(NSNumber*) getTrustLevel:(SignalAddress*) address identityKey:(NSData*) identityKey
{
return [self.sqliteDatabase idReadTransaction:^{
return [self.sqliteDatabase executeScalar:(@"SELECT \
CASE \
WHEN (trustLevel=0) THEN 0 \
WHEN (trustLevel=1 AND removedFromDeviceList IS NULL AND (lastReceivedMsg IS NULL OR lastReceivedMsg >= unixepoch('now', '-90 day'))) THEN 100 \
WHEN (trustLevel=1 AND removedFromDeviceList IS NOT NULL) THEN 101 \
WHEN (trustLevel=1 AND removedFromDeviceList IS NULL AND (lastReceivedMsg < unixepoch('now', '-90 day'))) THEN 102 \
WHEN (COUNT(*)=0) THEN 100 \
WHEN (trustLevel=2 AND removedFromDeviceList IS NULL AND (lastReceivedMsg IS NULL OR lastReceivedMsg >= unixepoch('now', '-90 day'))) THEN 200 \
WHEN (trustLevel=2 AND removedFromDeviceList IS NOT NULL) THEN 201 \
WHEN (trustLevel=2 AND removedFromDeviceList IS NULL AND (lastReceivedMsg < unixepoch('now', '-90 day'))) THEN 202 \
ELSE 0 \
END \
FROM signalContactIdentity \
WHERE account_id=? AND contactDeviceId=? AND contactName=? AND identity=?; \
") andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name, identityKey]];
}];
}
-(void) deleteDeviceforAddress:(SignalAddress*) address
{
[self.sqliteDatabase voidWriteTransaction:^{
[self.sqliteDatabase executeNonQuery:@"DELETE FROM signalContactIdentity WHERE account_id=? AND contactDeviceId=? AND contactName=?" andArguments:@[self.accountID, [NSNumber numberWithInteger:address.deviceId], address.name]];
}];
}
// MUC session management
// return true if we found at least one fingerprint for the given buddyJid
-(BOOL) sessionsExistForBuddy:(NSString*) buddyJid
{
return [self.sqliteDatabase boolWriteTransaction:^{
NSNumber* contactDevicesExist = [self.sqliteDatabase executeScalar:@"SELECT COUNT(contactDeviceId) FROM signalContactIdentity WHERE account_id=? AND contactName=?;" andArguments:@[self.accountID, buddyJid]];
return (BOOL)(contactDevicesExist.intValue > 0);
}];
}
// delete the fingerprints and session for the given buddyJid if the jid is neither a 1:1 buddy nor a group member
-(BOOL) checkIfSessionIsStillNeeded:(NSString*) buddyJid
{
return [self.sqliteDatabase boolWriteTransaction:^{
// delete fingerprints from buddyJid if the buddyJid is neither a buddy, a self chat, nor a group member
NSNumber* buddyJidCnt = [self.sqliteDatabase executeScalar:@"SELECT \
(bCnt.buddyListCnt + mucCnt.roomCnt + accountCnt) \
FROM \
( \
SELECT \
COUNT(buddy_name) AS buddyListCnt \
FROM buddylist \
WHERE \
account_id=? \
AND buddy_name=? \
AND Muc=0 \
) AS bCnt, \
( \
SELECT \
COUNT(m.room) AS roomCnt \
FROM muc_members AS m \
INNER JOIN buddylist AS b \
ON m.account_id = b.account_id \
AND b.buddy_name = m.member_jid \
WHERE \
b.account_id=? \
AND m.member_jid=? \
AND b.Muc=1 \
AND b.muc_type='group' \
) AS mucCnt, \
( \
SELECT \
COUNT(account_id) AS accountCnt \
FROM account \
WHERE \
(username || '@' || domain) = ? \
AND account_id = ? \
) AS accountCnt" andArguments:@[self.accountID, buddyJid, self.accountID, buddyJid, self.accountID, buddyJid]];
BOOL buddyStillNeeded = buddyJidCnt.intValue > 0;
if(buddyStillNeeded == NO)
{
[self.sqliteDatabase executeNonQuery:@"DELETE FROM signalContactIdentity \
WHERE \
account_id=? \
AND contactName=? \
" andArguments:@[self.accountID, buddyJid]];
[self.sqliteDatabase executeNonQuery:@"DELETE FROM signalContactSession \
WHERE \
account_id=? \
AND contactName=? \
" andArguments:@[self.accountID, buddyJid]];
}
return buddyStillNeeded;
}];
}
// delete all fingerprints and sessions from contacts that are neither a buddy nor a group member
// return jids of dangling sessions
-(NSSet<NSString*>*) removeDanglingMucSessions
{
return [self.sqliteDatabase idWriteTransaction:^{
// create a list of all sessions
NSArray<NSString*>* jidsWithSession = [self.sqliteDatabase executeScalarReader:@"SELECT DISTINCT contactName \
FROM signalContactIdentity \
WHERE \
account_id = ? \
" andArguments:@[self.accountID]];
NSMutableSet<NSString*>* danglingJids = [NSMutableSet new];
for(NSString* jid in jidsWithSession) {
// check if the session is still needed
if([self checkIfSessionIsStillNeeded:jid] == NO) {
[danglingJids addObject:jid];
// delete old session
[self.sqliteDatabase executeNonQuery:@"DELETE FROM signalContactIdentity \
WHERE \
account_id = ? \
AND contactName = ? \
" andArguments:@[self.accountID, jid]];
[self.sqliteDatabase executeNonQuery:@"DELETE FROM signalContactSession \
WHERE \
account_id = ? \
AND contactName = ? \
" andArguments:@[self.accountID, jid]];
}
}
return danglingJids;
}];
}
/**
* Store a serialized sender key record for a given
* (groupId + senderId + deviceId) tuple.
*/
-(BOOL) storeSenderKey:(nonnull NSData*) senderKey address:(nonnull SignalAddress*) address groupId:(nonnull NSString*) groupId;
{
return false;
}
/**
* Returns a copy of the sender key record corresponding to the
* (groupId + senderId + deviceId) tuple.
*/
- (nullable NSData*) loadSenderKeyForAddress:(SignalAddress*) address groupId:(NSString*) groupId;
{
return nil;
}
@end