feat: Implement deferred loading of ratchet data
This commit is contained in:
parent
4fb25a3ab7
commit
dad85b8467
@ -27,6 +27,16 @@ import 'package:omemo_dart/src/trust/base.dart';
|
||||
import 'package:omemo_dart/src/x3dh/x3dh.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
|
||||
|
||||
/// 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].
|
||||
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 {
|
||||
OmemoManager(
|
||||
this._device,
|
||||
@ -96,6 +112,7 @@ class OmemoManager {
|
||||
this.commitDeviceList = commitDeviceListStub,
|
||||
this.commitDevice = commitDeviceStub,
|
||||
this.removeRatchets = removeRatchetsStub,
|
||||
this.loadRatchets = loadRatchetsStub,
|
||||
});
|
||||
|
||||
final Logger _log = Logger('OmemoManager');
|
||||
@ -128,6 +145,9 @@ class OmemoManager {
|
||||
/// Callback to remove ratchets from persistent storage.
|
||||
final RemoveRatchetsFunction removeRatchets;
|
||||
|
||||
/// Callback to load ratchets from persistent storage.
|
||||
final LoadRatchetsCallback loadRatchets;
|
||||
|
||||
/// Map bare JID to its known devices
|
||||
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.
|
||||
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
|
||||
final RatchetAccessQueue _ratchetQueue = RatchetAccessQueue();
|
||||
|
||||
@ -153,6 +176,33 @@ class OmemoManager {
|
||||
// ignore: prefer_final_fields
|
||||
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(
|
||||
List<int>? ciphertext,
|
||||
List<int> keyAndHmac,
|
||||
@ -294,6 +344,9 @@ class OmemoManager {
|
||||
Future<DecryptionResult> _onIncomingStanzaImpl(
|
||||
OmemoIncomingStanza stanza,
|
||||
) async {
|
||||
// Populate the cache
|
||||
await _cacheJidIfNeccessary(stanza.bareSenderJid);
|
||||
|
||||
// Find the correct key for our device
|
||||
final deviceId = await getDeviceId();
|
||||
final key = stanza.keys.firstWhereOrNull((key) => key.rid == deviceId);
|
||||
@ -530,6 +583,9 @@ class OmemoManager {
|
||||
Future<EncryptionResult> _onOutgoingStanzaImpl(
|
||||
OmemoOutgoingStanza stanza,
|
||||
) async {
|
||||
// Populate the cache
|
||||
await _cacheJidsIfNeccessary(stanza.recipientJids);
|
||||
|
||||
// Encrypt the payload, if we have any
|
||||
final List<int> payloadKey;
|
||||
final List<int> ciphertext;
|
||||
@ -761,6 +817,8 @@ class OmemoManager {
|
||||
}
|
||||
await removeRatchets(keys.toList());
|
||||
|
||||
// TODO: Do we have to tell the trust manager?
|
||||
|
||||
// Clear the device list
|
||||
await commitDeviceList(
|
||||
jid,
|
||||
|
@ -20,4 +20,7 @@ class AlwaysTrustingTrustManager extends TrustManager {
|
||||
|
||||
@override
|
||||
Future<void> removeTrustDecisionsForJid(String jid) async {}
|
||||
|
||||
@override
|
||||
Future<void> loadTrustData(String jid) async {}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// The base class for managing trust in OMEMO sessions.
|
||||
// ignore: one_member_abstracts
|
||||
abstract class TrustManager {
|
||||
@ -19,4 +21,11 @@ abstract class TrustManager {
|
||||
|
||||
/// Removes all trust decisions for [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);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
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/trust/base.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
@ -37,6 +38,12 @@ typedef BTBVRemoveTrustForJidCallback = Future<void> Function(String jid);
|
||||
/// A stub-implementation of [BTBVRemoveTrustForJidCallback].
|
||||
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:
|
||||
/// - notTrusted: The device is absolutely not trusted
|
||||
/// - blindTrust: The fingerprint is not verified using OOB means
|
||||
@ -56,33 +63,31 @@ enum BTBVTrustState {
|
||||
/// See https://gultsch.de/trust.html for more details.
|
||||
class BlindTrustBeforeVerificationTrustManager extends TrustManager {
|
||||
BlindTrustBeforeVerificationTrustManager({
|
||||
Map<RatchetMapKey, BTBVTrustState>? trustCache,
|
||||
Map<RatchetMapKey, bool>? enablementCache,
|
||||
Map<String, List<int>>? devices,
|
||||
this.loadData = btbvLoadDataStub,
|
||||
this.commit = btbvCommitStub,
|
||||
this.removeTrust = btbvRemoveTrustStub,
|
||||
}) : trustCache = trustCache ?? {},
|
||||
enablementCache = enablementCache ?? {},
|
||||
devices = devices ?? {},
|
||||
_lock = Lock();
|
||||
});
|
||||
|
||||
/// The cache for mapping a RatchetMapKey to its trust state
|
||||
@visibleForTesting
|
||||
@protected
|
||||
final Map<RatchetMapKey, BTBVTrustState> trustCache;
|
||||
final Map<RatchetMapKey, BTBVTrustState> trustCache = {};
|
||||
|
||||
/// The cache for mapping a RatchetMapKey to whether it is enabled or not
|
||||
@visibleForTesting
|
||||
@protected
|
||||
final Map<RatchetMapKey, bool> enablementCache;
|
||||
final Map<RatchetMapKey, bool> enablementCache = {};
|
||||
|
||||
/// Mapping of Jids to their device identifiers
|
||||
@visibleForTesting
|
||||
@protected
|
||||
final Map<String, List<int>> devices;
|
||||
final Map<String, List<int>> devices = {};
|
||||
|
||||
/// 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.
|
||||
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
|
||||
BTBVTrustState getDeviceTrust(String jid, int deviceId) =>
|
||||
trustCache[RatchetMapKey(jid, deviceId)]!;
|
||||
|
@ -20,4 +20,7 @@ class NeverTrustingTrustManager extends TrustManager {
|
||||
|
||||
@override
|
||||
Future<void> removeTrustDecisionsForJid(String jid) async {}
|
||||
|
||||
@override
|
||||
Future<void> loadTrustData(String jid) async {}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user