From 36b5ad2a391ac591a5c4c62df0048edd9fff2b3e Mon Sep 17 00:00:00 2001 From: td Date: Sat, 13 Jul 2024 01:43:35 +0530 Subject: [PATCH] feat: debounce ratcheting voip keys --- lib/src/voip/backend/livekit_backend.dart | 63 ++++++++++++++++------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/lib/src/voip/backend/livekit_backend.dart b/lib/src/voip/backend/livekit_backend.dart index 1a958416a..da6e26d13 100644 --- a/lib/src/voip/backend/livekit_backend.dart +++ b/lib/src/voip/backend/livekit_backend.dart @@ -37,7 +37,7 @@ class LiveKitBackend extends CallBackend { /// participant:keyIndex:keyBin final Map> _encryptionKeysMap = {}; - final List _setNewKeyTimeouts = []; + final List> _setNewKeyTimeouts = []; int _indexCounter = 0; @@ -90,6 +90,8 @@ class LiveKitBackend extends CallBackend { ); } + DateTime lastRatchetAt = DateTime(1980); + /// also does the sending for you Future _ratchetLocalParticipantKey( GroupCallSession groupCall, @@ -109,28 +111,49 @@ class LiveKitBackend extends CallBackend { return; } - Uint8List? ratchetedKey; + if (_currentLocalKeyIndex != _latestLocalKeyIndex) { + /// Leave causes rotate, new user joins after making new key but + /// before using new key, this then causes a ratchet of the latestLocalKey + /// returns null until that key is set when useKeyDelay is done. + /// + /// You will see some onRatchetKey sending empty responses here + /// therefore the below while loop. + Logs().w( + '[VOIP E2EE] Leave and join / rotate and ratchet scenario detected, expect ${useKeyDelay.inSeconds} seconds disruption. latest: $latestLocalKeyIndex, current: $currentLocalKeyIndex'); + } - while (ratchetedKey == null || ratchetedKey.isEmpty) { - Logs().i('[VOIP E2EE] Ignoring empty ratcheted key'); - ratchetedKey = await keyProvider.onRatchetKey( + if (lastRatchetAt.isBefore(DateTime.now().subtract(makeKeyDelay))) { + Uint8List? ratchedKey; + while (ratchedKey == null || ratchedKey.isEmpty) { + Logs().d( + '[VOIP E2EE] Ignoring empty ratcheted key, probably waiting for useKeyDelay to finish, expect around ${useKeyDelay.inSeconds} seconds of disruption. latest: $latestLocalKeyIndex, current: $currentLocalKeyIndex'); + ratchedKey = await keyProvider.onRatchetKey( + groupCall.localParticipant!, + latestLocalKeyIndex, + ); + } + lastRatchetAt = DateTime.now(); + Logs().i( + '[VOIP E2EE] Ratched latest key to $ratchedKey at idx $latestLocalKeyIndex'); + await _setEncryptionKey( + groupCall, groupCall.localParticipant!, latestLocalKeyIndex, + ratchedKey, + delayBeforeUsingKeyOurself: false, + send: true, + sendTo: sendTo, + ); + } else { + Logs().d( + '[VOIP E2EE] Skipped ratcheting because lastRatchet run was at ${lastRatchetAt.millisecondsSinceEpoch}'); + // send without setting because it is already set + await _sendEncryptionKeysEvent( + groupCall, + latestLocalKeyIndex, + sendTo: sendTo, ); } - - Logs().i( - '[VOIP E2EE] Ratched latest key to $ratchetedKey at idx $latestLocalKeyIndex'); - - await _setEncryptionKey( - groupCall, - groupCall.localParticipant!, - latestLocalKeyIndex, - ratchetedKey, - delayBeforeUsingKeyOurself: false, - send: true, - sendTo: sendTo, - ); } Future _changeEncryptionKey( @@ -177,7 +200,7 @@ class LiveKitBackend extends CallBackend { if (delayBeforeUsingKeyOurself) { // now wait for the key to propogate and then set it, hopefully users can // stil decrypt everything - final useKeyTimeout = Future.delayed(useKeyDelay, () async { + final useKeyTimeout = Future.delayed(useKeyDelay, () async { Logs().i( '[VOIP E2EE] setting key changed event for ${participant.id} idx $encryptionKeyIndex key $encryptionKeyBin'); await groupCall.voip.delegate.keyProvider?.onSetEncryptionKey( @@ -260,7 +283,7 @@ class LiveKitBackend extends CallBackend { String eventType, ) async { if (remoteParticipants.isEmpty) return; - Logs().v( + Logs().d( '[VOIP] _sendToDeviceEvent: sending ${data.toString()} to ${remoteParticipants.map((e) => e.id)} '); final txid = VoIP.customTxid ?? groupCall.client.generateUniqueTransactionId();