@@ -3,6 +3,7 @@ import 'dart:convert';
33import 'dart:typed_data' ;
44
55import 'package:matrix/matrix.dart' ;
6+ import 'package:matrix/src/models/retry_event_model.dart' ;
67import 'package:matrix/src/utils/crypto/crypto.dart' ;
78
89class LiveKitBackend extends CallBackend {
@@ -24,6 +25,18 @@ class LiveKitBackend extends CallBackend {
2425 /// participant:keyIndex:keyBin
2526 final Map <CallParticipant , Map <int , Uint8List >> _encryptionKeysMap = {};
2627
28+ /// Tracks pending encryption key request retries per participant
29+ final Map <CallParticipant , RetryEventModel > _requestEncryptionKeyPending = {};
30+
31+ /// Tracks retry count per participant to prevent infinite retries
32+ final Map <CallParticipant , int > _keyRequestRetryCount = {};
33+
34+ /// Maximum number of retry attempts for key requests
35+ static const int _maxKeyRequestRetries = 5 ;
36+
37+ /// Retry interval for key requests
38+ static const Duration _keyRequestRetryInterval = Duration (seconds: 2 );
39+
2740 final List <Future > _setNewKeyTimeouts = [];
2841
2942 int _indexCounter = 0 ;
@@ -79,10 +92,7 @@ class LiveKitBackend extends CallBackend {
7992 '_makeNewSenderKey using previous key because last created at ${_lastNewKeyTime .toString ()}' ,
8093 );
8194 // still a fairly new key, just send that
82- await _sendEncryptionKeysEvent (
83- groupCall,
84- _latestLocalKeyIndex,
85- );
95+ await _sendEncryptionKeysEvent (groupCall, _latestLocalKeyIndex);
8696 return ;
8797 }
8898
@@ -222,20 +232,22 @@ class LiveKitBackend extends CallBackend {
222232 );
223233 // now wait for the key to propogate and then set it, hopefully users can
224234 // stil decrypt everything
225- final useKeyTimeout =
226- Future .delayed (groupCall.voip.timeouts! .useKeyDelay, () async {
227- Logs ().i (
228- '[VOIP E2EE] delayed setting key changed event for ${participant .id } idx $encryptionKeyIndex key $encryptionKeyBin ' ,
229- );
230- await groupCall.voip.delegate.keyProvider? .onSetEncryptionKey (
231- participant,
232- encryptionKeyBin,
233- encryptionKeyIndex,
234- );
235- if (participant.isLocal) {
236- _currentLocalKeyIndex = encryptionKeyIndex;
237- }
238- });
235+ final useKeyTimeout = Future .delayed (
236+ groupCall.voip.timeouts! .useKeyDelay,
237+ () async {
238+ Logs ().i (
239+ '[VOIP E2EE] delayed setting key changed event for ${participant .id } idx $encryptionKeyIndex key $encryptionKeyBin ' ,
240+ );
241+ await groupCall.voip.delegate.keyProvider? .onSetEncryptionKey (
242+ participant,
243+ encryptionKeyBin,
244+ encryptionKeyIndex,
245+ );
246+ if (participant.isLocal) {
247+ _currentLocalKeyIndex = encryptionKeyIndex;
248+ }
249+ },
250+ );
239251 _setNewKeyTimeouts.add (useKeyTimeout);
240252 } else {
241253 Logs ().i (
@@ -271,17 +283,15 @@ class LiveKitBackend extends CallBackend {
271283 '[VOIP E2EE] _sendEncryptionKeysEvent Tried to send encryption keys event but no keys found!' ,
272284 );
273285 await _makeNewSenderKey (groupCall, false );
274- await _sendEncryptionKeysEvent (
275- groupCall,
276- keyIndex,
277- sendTo: sendTo,
278- );
286+ await _sendEncryptionKeysEvent (groupCall, keyIndex, sendTo: sendTo);
279287 return ;
280288 }
281289
282290 try {
283291 final keyContent = EncryptionKeysEventContent (
284- [EncryptionKeyEntry (keyIndex, base64Encode (myLatestKey))],
292+ [
293+ EncryptionKeyEntry (keyIndex, base64Encode (myLatestKey)),
294+ ],
285295 groupCall.groupCallId,
286296 );
287297 final Map <String , Object > data = {
@@ -300,11 +310,7 @@ class LiveKitBackend extends CallBackend {
300310 );
301311 } catch (e, s) {
302312 Logs ().e ('[VOIP E2EE] Failed to send e2ee keys, retrying' , e, s);
303- await _sendEncryptionKeysEvent (
304- groupCall,
305- keyIndex,
306- sendTo: sendTo,
307- );
313+ await _sendEncryptionKeysEvent (groupCall, keyIndex, sendTo: sendTo);
308314 }
309315 }
310316
@@ -375,6 +381,10 @@ class LiveKitBackend extends CallBackend {
375381 GroupCallSession groupCall,
376382 List <CallParticipant > remoteParticipants,
377383 ) async {
384+ Logs ().v (
385+ '[VOIP E2EE] requesting stream encryption keys from ${remoteParticipants .map ((e ) => e .id )}' ,
386+ );
387+
378388 final Map <String , Object > data = {
379389 'conf_id' : groupCall.groupCallId,
380390 'device_id' : groupCall.client.deviceID! ,
@@ -387,6 +397,32 @@ class LiveKitBackend extends CallBackend {
387397 data,
388398 EventTypes .GroupCallMemberEncryptionKeysRequest ,
389399 );
400+
401+ // Set up retry timers for each participant
402+ for (final rp in remoteParticipants) {
403+ // Skip if a retry is already pending for this participant
404+ if (_requestEncryptionKeyPending.containsKey (rp)) continue ;
405+
406+ // Check retry count to prevent infinite retries
407+ final currentCount = _keyRequestRetryCount[rp] ?? 0 ;
408+ if (currentCount >= _maxKeyRequestRetries) {
409+ Logs ().w (
410+ '[VOIP E2EE] Max retries ($_maxKeyRequestRetries ) reached for ${rp .id }, giving up key request' ,
411+ );
412+ _keyRequestRetryCount.remove (rp);
413+ continue ;
414+ }
415+ _keyRequestRetryCount[rp] = currentCount + 1 ;
416+
417+ _requestEncryptionKeyPending[rp] = RetryEventModel (
418+ timeInterval: _keyRequestRetryInterval,
419+ retryFunction: (_) {
420+ // Remove the pending entry before retrying to allow new retry to be scheduled
421+ _requestEncryptionKeyPending.remove (rp)? .dispose ();
422+ unawaited (requestEncrytionKey (groupCall, [rp]));
423+ },
424+ );
425+ }
390426 }
391427
392428 @override
@@ -403,8 +439,15 @@ class LiveKitBackend extends CallBackend {
403439 final keyContent = EncryptionKeysEventContent .fromJson (content);
404440
405441 final callId = keyContent.callId;
406- final p =
407- CallParticipant (groupCall.voip, userId: userId, deviceId: deviceId);
442+ final p = CallParticipant (
443+ groupCall.voip,
444+ userId: userId,
445+ deviceId: deviceId,
446+ );
447+
448+ // Cancel any pending retry for this participant since we received keys
449+ _requestEncryptionKeyPending.remove (p)? .dispose ();
450+ _keyRequestRetryCount.remove (p);
408451
409452 if (keyContent.keys.isEmpty) {
410453 Logs ().w (
@@ -471,11 +514,7 @@ class LiveKitBackend extends CallBackend {
471514 groupCall,
472515 _latestLocalKeyIndex,
473516 sendTo: [
474- CallParticipant (
475- groupCall.voip,
476- userId: userId,
477- deviceId: deviceId,
478- ),
517+ CallParticipant (groupCall.voip, userId: userId, deviceId: deviceId),
479518 ],
480519 );
481520 return true ;
@@ -525,16 +564,14 @@ class LiveKitBackend extends CallBackend {
525564 if (_memberLeaveEncKeyRotateDebounceTimer != null ) {
526565 _memberLeaveEncKeyRotateDebounceTimer! .cancel ();
527566 }
528- _memberLeaveEncKeyRotateDebounceTimer =
529- Timer (groupCall.voip.timeouts! .makeKeyOnLeaveDelay, () async {
530- // we skipJoinDebounce here because we want to make sure a new key is generated
531- // and that the join debounce does not block us from making a new key
532- await _makeNewSenderKey (
533- groupCall,
534- true ,
535- skipJoinDebounce: true ,
536- );
537- });
567+ _memberLeaveEncKeyRotateDebounceTimer = Timer (
568+ groupCall.voip.timeouts! .makeKeyOnLeaveDelay,
569+ () async {
570+ // we skipJoinDebounce here because we want to make sure a new key is generated
571+ // and that the join debounce does not block us from making a new key
572+ await _makeNewSenderKey (groupCall, true , skipJoinDebounce: true );
573+ },
574+ );
538575 }
539576
540577 @override
@@ -545,6 +582,13 @@ class LiveKitBackend extends CallBackend {
545582 _currentLocalKeyIndex = 0 ;
546583 _latestLocalKeyIndex = 0 ;
547584 _memberLeaveEncKeyRotateDebounceTimer? .cancel ();
585+
586+ // Clean up all pending encryption key request retries
587+ for (final retry in _requestEncryptionKeyPending.values) {
588+ retry.dispose ();
589+ }
590+ _requestEncryptionKeyPending.clear ();
591+ _keyRequestRetryCount.clear ();
548592 }
549593
550594 @override
0 commit comments