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