@@ -3,6 +3,7 @@ import 'dart:convert';
33import 'dart:typed_data' ;
44
55import 'package:matrix/matrix.dart' ;
6+
67import 'package:matrix/src/utils/crypto/crypto.dart' ;
78
89class LiveKitBackend extends CallBackend {
@@ -24,6 +25,12 @@ 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 , Timer > _requestEncryptionKeyPending = {};
30+
31+ /// Retry interval for key requests
32+ static const Duration _keyRequestRetryInterval = Duration (seconds: 2 );
33+
2734 final List <Future > _setNewKeyTimeouts = [];
2835
2936 int _indexCounter = 0 ;
@@ -79,10 +86,7 @@ class LiveKitBackend extends CallBackend {
7986 '_makeNewSenderKey using previous key because last created at ${_lastNewKeyTime .toString ()}' ,
8087 );
8188 // still a fairly new key, just send that
82- await _sendEncryptionKeysEvent (
83- groupCall,
84- _latestLocalKeyIndex,
85- );
89+ await _sendEncryptionKeysEvent (groupCall, _latestLocalKeyIndex);
8690 return ;
8791 }
8892
@@ -222,20 +226,22 @@ class LiveKitBackend extends CallBackend {
222226 );
223227 // now wait for the key to propogate and then set it, hopefully users can
224228 // 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- });
229+ final useKeyTimeout = Future .delayed (
230+ groupCall.voip.timeouts! .useKeyDelay,
231+ () async {
232+ Logs ().i (
233+ '[VOIP E2EE] delayed setting key changed event for ${participant .id } idx $encryptionKeyIndex key $encryptionKeyBin ' ,
234+ );
235+ await groupCall.voip.delegate.keyProvider? .onSetEncryptionKey (
236+ participant,
237+ encryptionKeyBin,
238+ encryptionKeyIndex,
239+ );
240+ if (participant.isLocal) {
241+ _currentLocalKeyIndex = encryptionKeyIndex;
242+ }
243+ },
244+ );
239245 _setNewKeyTimeouts.add (useKeyTimeout);
240246 } else {
241247 Logs ().i (
@@ -271,17 +277,15 @@ class LiveKitBackend extends CallBackend {
271277 '[VOIP E2EE] _sendEncryptionKeysEvent Tried to send encryption keys event but no keys found!' ,
272278 );
273279 await _makeNewSenderKey (groupCall, false );
274- await _sendEncryptionKeysEvent (
275- groupCall,
276- keyIndex,
277- sendTo: sendTo,
278- );
280+ await _sendEncryptionKeysEvent (groupCall, keyIndex, sendTo: sendTo);
279281 return ;
280282 }
281283
282284 try {
283285 final keyContent = EncryptionKeysEventContent (
284- [EncryptionKeyEntry (keyIndex, base64Encode (myLatestKey))],
286+ [
287+ EncryptionKeyEntry (keyIndex, base64Encode (myLatestKey)),
288+ ],
285289 groupCall.groupCallId,
286290 );
287291 final Map <String , Object > data = {
@@ -300,11 +304,7 @@ class LiveKitBackend extends CallBackend {
300304 );
301305 } catch (e, s) {
302306 Logs ().e ('[VOIP E2EE] Failed to send e2ee keys, retrying' , e, s);
303- await _sendEncryptionKeysEvent (
304- groupCall,
305- keyIndex,
306- sendTo: sendTo,
307- );
307+ await _sendEncryptionKeysEvent (groupCall, keyIndex, sendTo: sendTo);
308308 }
309309 }
310310
@@ -375,6 +375,10 @@ class LiveKitBackend extends CallBackend {
375375 GroupCallSession groupCall,
376376 List <CallParticipant > remoteParticipants,
377377 ) async {
378+ Logs ().v (
379+ '[VOIP E2EE] requesting stream encryption keys from ${remoteParticipants .map ((e ) => e .id )}' ,
380+ );
381+
378382 final Map <String , Object > data = {
379383 'conf_id' : groupCall.groupCallId,
380384 'device_id' : groupCall.client.deviceID! ,
@@ -387,6 +391,29 @@ class LiveKitBackend extends CallBackend {
387391 data,
388392 EventTypes .GroupCallMemberEncryptionKeysRequest ,
389393 );
394+
395+ // Set up retry timers for each participant
396+ for (final rp in remoteParticipants) {
397+ // Skip if a retry is already pending for this participant
398+ if (_requestEncryptionKeyPending.containsKey (rp)) continue ;
399+
400+ var retryCount = 0 ;
401+ _requestEncryptionKeyPending[rp] = Timer .periodic (
402+ _keyRequestRetryInterval,
403+ (timer) {
404+ retryCount++ ;
405+ if (retryCount >= 5 ) {
406+ Logs ().w (
407+ '[VOIP E2EE] Max retries (5) reached for ${rp .id }, giving up key request' ,
408+ );
409+ timer.cancel ();
410+ _requestEncryptionKeyPending.remove (rp);
411+ return ;
412+ }
413+ unawaited (requestEncrytionKey (groupCall, [rp]));
414+ },
415+ );
416+ }
390417 }
391418
392419 @override
@@ -403,8 +430,14 @@ class LiveKitBackend extends CallBackend {
403430 final keyContent = EncryptionKeysEventContent .fromJson (content);
404431
405432 final callId = keyContent.callId;
406- final p =
407- CallParticipant (groupCall.voip, userId: userId, deviceId: deviceId);
433+ final p = CallParticipant (
434+ groupCall.voip,
435+ userId: userId,
436+ deviceId: deviceId,
437+ );
438+
439+ // Cancel any pending retry for this participant since we received keys
440+ _requestEncryptionKeyPending.remove (p)? .cancel ();
408441
409442 if (keyContent.keys.isEmpty) {
410443 Logs ().w (
@@ -471,11 +504,7 @@ class LiveKitBackend extends CallBackend {
471504 groupCall,
472505 _latestLocalKeyIndex,
473506 sendTo: [
474- CallParticipant (
475- groupCall.voip,
476- userId: userId,
477- deviceId: deviceId,
478- ),
507+ CallParticipant (groupCall.voip, userId: userId, deviceId: deviceId),
479508 ],
480509 );
481510 return true ;
@@ -525,16 +554,14 @@ class LiveKitBackend extends CallBackend {
525554 if (_memberLeaveEncKeyRotateDebounceTimer != null ) {
526555 _memberLeaveEncKeyRotateDebounceTimer! .cancel ();
527556 }
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- });
557+ _memberLeaveEncKeyRotateDebounceTimer = Timer (
558+ groupCall.voip.timeouts! .makeKeyOnLeaveDelay,
559+ () async {
560+ // we skipJoinDebounce here because we want to make sure a new key is generated
561+ // and that the join debounce does not block us from making a new key
562+ await _makeNewSenderKey (groupCall, true , skipJoinDebounce: true );
563+ },
564+ );
538565 }
539566
540567 @override
@@ -545,6 +572,12 @@ class LiveKitBackend extends CallBackend {
545572 _currentLocalKeyIndex = 0 ;
546573 _latestLocalKeyIndex = 0 ;
547574 _memberLeaveEncKeyRotateDebounceTimer? .cancel ();
575+
576+ // Clean up all pending encryption key request retries
577+ for (final timer in _requestEncryptionKeyPending.values) {
578+ timer.cancel ();
579+ }
580+ _requestEncryptionKeyPending.clear ();
548581 }
549582
550583 @override
0 commit comments