feat: Implement deferred loading of ratchet data

This commit is contained in:
PapaTutuWawa 2023-06-17 16:15:09 +02:00
parent 4fb25a3ab7
commit dad85b8467
5 changed files with 101 additions and 11 deletions

View File

@ -27,6 +27,16 @@ import 'package:omemo_dart/src/trust/base.dart';
import 'package:omemo_dart/src/x3dh/x3dh.dart'; import 'package:omemo_dart/src/x3dh/x3dh.dart';
import 'package:synchronized/synchronized.dart'; import 'package:synchronized/synchronized.dart';
class OmemoDataPackage {
const OmemoDataPackage(this.devices, this.ratchets);
/// The device list for the given JID.
final List<int> devices;
/// The ratchets for the JID.
final Map<RatchetMapKey, OmemoDoubleRatchet> ratchets;
}
/// Callback type definitions /// Callback type definitions
/// Directly "package" [result] into an OMEMO message and send it to [recipientJid]. /// Directly "package" [result] into an OMEMO message and send it to [recipientJid].
@ -84,6 +94,12 @@ typedef RemoveRatchetsFunction = Future<void> Function(
/// A stub implementation of [RemoveRatchetsFunction]. /// A stub implementation of [RemoveRatchetsFunction].
Future<void> removeRatchetsStub(List<RatchetMapKey> ratchets) async {} Future<void> removeRatchetsStub(List<RatchetMapKey> ratchets) async {}
/// Loads all the required data for the ratchets of [jid].
typedef LoadRatchetsCallback = Future<OmemoDataPackage?> Function(String jid);
/// A stub implementation of [LoadRatchetsCallback].
Future<OmemoDataPackage?> loadRatchetsStub(String _) async => null;
class OmemoManager { class OmemoManager {
OmemoManager( OmemoManager(
this._device, this._device,
@ -96,6 +112,7 @@ class OmemoManager {
this.commitDeviceList = commitDeviceListStub, this.commitDeviceList = commitDeviceListStub,
this.commitDevice = commitDeviceStub, this.commitDevice = commitDeviceStub,
this.removeRatchets = removeRatchetsStub, this.removeRatchets = removeRatchetsStub,
this.loadRatchets = loadRatchetsStub,
}); });
final Logger _log = Logger('OmemoManager'); final Logger _log = Logger('OmemoManager');
@ -128,6 +145,9 @@ class OmemoManager {
/// Callback to remove ratchets from persistent storage. /// Callback to remove ratchets from persistent storage.
final RemoveRatchetsFunction removeRatchets; final RemoveRatchetsFunction removeRatchets;
/// Callback to load ratchets from persistent storage.
final LoadRatchetsCallback loadRatchets;
/// Map bare JID to its known devices /// Map bare JID to its known devices
final Map<String, List<int>> _deviceList = {}; final Map<String, List<int>> _deviceList = {};
@ -141,6 +161,9 @@ class OmemoManager {
/// Map bare JID to whether we already tried to subscribe to the device list node. /// Map bare JID to whether we already tried to subscribe to the device list node.
final Map<String, bool> _subscriptionMap = {}; final Map<String, bool> _subscriptionMap = {};
/// List of JIDs for which we cached trust data, the device list, and the ratchets.
final List<String> _cachedJids = [];
/// For preventing a race condition in encryption/decryption /// For preventing a race condition in encryption/decryption
final RatchetAccessQueue _ratchetQueue = RatchetAccessQueue(); final RatchetAccessQueue _ratchetQueue = RatchetAccessQueue();
@ -153,6 +176,33 @@ class OmemoManager {
// ignore: prefer_final_fields // ignore: prefer_final_fields
OmemoDevice _device; OmemoDevice _device;
Future<void> _cacheJidsIfNeccessary(List<String> jids) async {
for (final jid in jids) {
await _cacheJidIfNeccessary(jid);
}
}
Future<void> _cacheJidIfNeccessary(String jid) async {
// JID is already cached. We don't have to do anything.
if (_cachedJids.contains(jid)) {
return;
}
_cachedJids.add(jid);
final result = await loadRatchets(jid);
if (result == null) {
_log.fine('Did not load ratchet data for $jid. Assuming there is none.');
return;
}
// Cache the data
_deviceList[jid] = result.devices;
_ratchetMap.addAll(result.ratchets);
// Load trust data
await trustManager.loadTrustData(jid);
}
Future<Result<OmemoError, String?>> _decryptAndVerifyHmac( Future<Result<OmemoError, String?>> _decryptAndVerifyHmac(
List<int>? ciphertext, List<int>? ciphertext,
List<int> keyAndHmac, List<int> keyAndHmac,
@ -294,6 +344,9 @@ class OmemoManager {
Future<DecryptionResult> _onIncomingStanzaImpl( Future<DecryptionResult> _onIncomingStanzaImpl(
OmemoIncomingStanza stanza, OmemoIncomingStanza stanza,
) async { ) async {
// Populate the cache
await _cacheJidIfNeccessary(stanza.bareSenderJid);
// Find the correct key for our device // Find the correct key for our device
final deviceId = await getDeviceId(); final deviceId = await getDeviceId();
final key = stanza.keys.firstWhereOrNull((key) => key.rid == deviceId); final key = stanza.keys.firstWhereOrNull((key) => key.rid == deviceId);
@ -530,6 +583,9 @@ class OmemoManager {
Future<EncryptionResult> _onOutgoingStanzaImpl( Future<EncryptionResult> _onOutgoingStanzaImpl(
OmemoOutgoingStanza stanza, OmemoOutgoingStanza stanza,
) async { ) async {
// Populate the cache
await _cacheJidsIfNeccessary(stanza.recipientJids);
// Encrypt the payload, if we have any // Encrypt the payload, if we have any
final List<int> payloadKey; final List<int> payloadKey;
final List<int> ciphertext; final List<int> ciphertext;
@ -761,6 +817,8 @@ class OmemoManager {
} }
await removeRatchets(keys.toList()); await removeRatchets(keys.toList());
// TODO: Do we have to tell the trust manager?
// Clear the device list // Clear the device list
await commitDeviceList( await commitDeviceList(
jid, jid,

View File

@ -20,4 +20,7 @@ class AlwaysTrustingTrustManager extends TrustManager {
@override @override
Future<void> removeTrustDecisionsForJid(String jid) async {} Future<void> removeTrustDecisionsForJid(String jid) async {}
@override
Future<void> loadTrustData(String jid) async {}
} }

View File

@ -1,3 +1,5 @@
import 'package:meta/meta.dart';
/// The base class for managing trust in OMEMO sessions. /// The base class for managing trust in OMEMO sessions.
// ignore: one_member_abstracts // ignore: one_member_abstracts
abstract class TrustManager { abstract class TrustManager {
@ -19,4 +21,11 @@ abstract class TrustManager {
/// Removes all trust decisions for [jid]. /// Removes all trust decisions for [jid].
Future<void> removeTrustDecisionsForJid(String jid); Future<void> removeTrustDecisionsForJid(String jid);
// ignore: comment_references
/// Called from within the [OmemoManager].
/// Loads the trust data for the JID [jid] from persistent storage
/// into the internal cache, if applicable.
@internal
Future<void> loadTrustData(String jid);
} }

View File

@ -1,4 +1,5 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:omemo_dart/src/helpers.dart';
import 'package:omemo_dart/src/omemo/ratchet_map_key.dart'; import 'package:omemo_dart/src/omemo/ratchet_map_key.dart';
import 'package:omemo_dart/src/trust/base.dart'; import 'package:omemo_dart/src/trust/base.dart';
import 'package:synchronized/synchronized.dart'; import 'package:synchronized/synchronized.dart';
@ -37,6 +38,12 @@ typedef BTBVRemoveTrustForJidCallback = Future<void> Function(String jid);
/// A stub-implementation of [BTBVRemoveTrustForJidCallback]. /// A stub-implementation of [BTBVRemoveTrustForJidCallback].
Future<void> btbvRemoveTrustStub(String _) async {} Future<void> btbvRemoveTrustStub(String _) async {}
/// A callback for when trust data should be loaded.
typedef BTBVLoadDataCallback = Future<List<BTBVTrustData>> Function(String jid);
/// A stub-implementation for [BTBVLoadDataCallback].
Future<List<BTBVTrustData>> btbvLoadDataStub(String _) async => [];
/// Every device is in either of those two trust states: /// Every device is in either of those two trust states:
/// - notTrusted: The device is absolutely not trusted /// - notTrusted: The device is absolutely not trusted
/// - blindTrust: The fingerprint is not verified using OOB means /// - blindTrust: The fingerprint is not verified using OOB means
@ -56,33 +63,31 @@ enum BTBVTrustState {
/// See https://gultsch.de/trust.html for more details. /// See https://gultsch.de/trust.html for more details.
class BlindTrustBeforeVerificationTrustManager extends TrustManager { class BlindTrustBeforeVerificationTrustManager extends TrustManager {
BlindTrustBeforeVerificationTrustManager({ BlindTrustBeforeVerificationTrustManager({
Map<RatchetMapKey, BTBVTrustState>? trustCache, this.loadData = btbvLoadDataStub,
Map<RatchetMapKey, bool>? enablementCache,
Map<String, List<int>>? devices,
this.commit = btbvCommitStub, this.commit = btbvCommitStub,
this.removeTrust = btbvRemoveTrustStub, this.removeTrust = btbvRemoveTrustStub,
}) : trustCache = trustCache ?? {}, });
enablementCache = enablementCache ?? {},
devices = devices ?? {},
_lock = Lock();
/// The cache for mapping a RatchetMapKey to its trust state /// The cache for mapping a RatchetMapKey to its trust state
@visibleForTesting @visibleForTesting
@protected @protected
final Map<RatchetMapKey, BTBVTrustState> trustCache; final Map<RatchetMapKey, BTBVTrustState> trustCache = {};
/// The cache for mapping a RatchetMapKey to whether it is enabled or not /// The cache for mapping a RatchetMapKey to whether it is enabled or not
@visibleForTesting @visibleForTesting
@protected @protected
final Map<RatchetMapKey, bool> enablementCache; final Map<RatchetMapKey, bool> enablementCache = {};
/// Mapping of Jids to their device identifiers /// Mapping of Jids to their device identifiers
@visibleForTesting @visibleForTesting
@protected @protected
final Map<String, List<int>> devices; final Map<String, List<int>> devices = {};
/// The lock for devices and trustCache /// The lock for devices and trustCache
final Lock _lock; final Lock _lock = Lock();
/// Callback for loading trust data.
final BTBVLoadDataCallback loadData;
/// Callback for commiting trust data to persistent storage. /// Callback for commiting trust data to persistent storage.
final BTBVTrustCommitCallback commit; final BTBVTrustCommitCallback commit;
@ -241,6 +246,18 @@ class BlindTrustBeforeVerificationTrustManager extends TrustManager {
}); });
} }
@override
Future<void> loadTrustData(String jid) async {
await _lock.synchronized(() async {
for (final result in await loadData(jid)) {
final key = RatchetMapKey(jid, result.device);
trustCache[key] = result.state;
enablementCache[key] = result.enabled;
devices.appendOrCreate(jid, result.device);
}
});
}
@visibleForTesting @visibleForTesting
BTBVTrustState getDeviceTrust(String jid, int deviceId) => BTBVTrustState getDeviceTrust(String jid, int deviceId) =>
trustCache[RatchetMapKey(jid, deviceId)]!; trustCache[RatchetMapKey(jid, deviceId)]!;

View File

@ -20,4 +20,7 @@ class NeverTrustingTrustManager extends TrustManager {
@override @override
Future<void> removeTrustDecisionsForJid(String jid) async {} Future<void> removeTrustDecisionsForJid(String jid) async {}
@override
Future<void> loadTrustData(String jid) async {}
} }