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: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,
|
||||||
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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)]!;
|
||||||
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user