@@ -24,6 +24,12 @@ class LiveKitBackend extends CallBackend {
2424 /// participant:keyIndex:keyBin
2525 final Map <CallParticipant , Map <int , Uint8List >> _encryptionKeysMap = {};
2626
27+ /// Tracks pending encryption key request retries per participant
28+ final Map <CallParticipant , Timer > _requestEncryptionKeyPending = {};
29+
30+ /// Retry interval for key requests
31+ static const Duration _keyRequestRetryInterval = Duration (seconds: 2 );
32+
2733 final List <Future > _setNewKeyTimeouts = [];
2834
2935 int _indexCounter = 0 ;
@@ -79,10 +85,7 @@ class LiveKitBackend extends CallBackend {
7985 '_makeNewSenderKey using previous key because last created at ${_lastNewKeyTime .toString ()}' ,
8086 );
8187 // still a fairly new key, just send that
82- await _sendEncryptionKeysEvent (
83- groupCall,
84- _latestLocalKeyIndex,
85- );
88+ await _sendEncryptionKeysEvent (groupCall, _latestLocalKeyIndex);
8689 return ;
8790 }
8891
@@ -222,20 +225,22 @@ class LiveKitBackend extends CallBackend {
222225 );
223226 // now wait for the key to propogate and then set it, hopefully users can
224227 // 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- });
228+ final useKeyTimeout = Future .delayed (
229+ groupCall.voip.timeouts! .useKeyDelay,
230+ () async {
231+ Logs ().i (
232+ '[VOIP E2EE] delayed setting key changed event for ${participant .id } idx $encryptionKeyIndex key $encryptionKeyBin ' ,
233+ );
234+ await groupCall.voip.delegate.keyProvider? .onSetEncryptionKey (
235+ participant,
236+ encryptionKeyBin,
237+ encryptionKeyIndex,
238+ );
239+ if (participant.isLocal) {
240+ _currentLocalKeyIndex = encryptionKeyIndex;
241+ }
242+ },
243+ );
239244 _setNewKeyTimeouts.add (useKeyTimeout);
240245 } else {
241246 Logs ().i (
@@ -271,17 +276,15 @@ class LiveKitBackend extends CallBackend {
271276 '[VOIP E2EE] _sendEncryptionKeysEvent Tried to send encryption keys event but no keys found!' ,
272277 );
273278 await _makeNewSenderKey (groupCall, false );
274- await _sendEncryptionKeysEvent (
275- groupCall,
276- keyIndex,
277- sendTo: sendTo,
278- );
279+ await _sendEncryptionKeysEvent (groupCall, keyIndex, sendTo: sendTo);
279280 return ;
280281 }
281282
282283 try {
283284 final keyContent = EncryptionKeysEventContent (
284- [EncryptionKeyEntry (keyIndex, base64Encode (myLatestKey))],
285+ [
286+ EncryptionKeyEntry (keyIndex, base64Encode (myLatestKey)),
287+ ],
285288 groupCall.groupCallId,
286289 );
287290 final Map <String , Object > data = {
@@ -300,11 +303,7 @@ class LiveKitBackend extends CallBackend {
300303 );
301304 } catch (e, s) {
302305 Logs ().e ('[VOIP E2EE] Failed to send e2ee keys, retrying' , e, s);
303- await _sendEncryptionKeysEvent (
304- groupCall,
305- keyIndex,
306- sendTo: sendTo,
307- );
306+ await _sendEncryptionKeysEvent (groupCall, keyIndex, sendTo: sendTo);
308307 }
309308 }
310309
@@ -375,6 +374,10 @@ class LiveKitBackend extends CallBackend {
375374 GroupCallSession groupCall,
376375 List <CallParticipant > remoteParticipants,
377376 ) async {
377+ Logs ().v (
378+ '[VOIP E2EE] requesting stream encryption keys from ${remoteParticipants .map ((e ) => e .id )}' ,
379+ );
380+
378381 final Map <String , Object > data = {
379382 'conf_id' : groupCall.groupCallId,
380383 'device_id' : groupCall.client.deviceID! ,
@@ -387,6 +390,29 @@ class LiveKitBackend extends CallBackend {
387390 data,
388391 EventTypes .GroupCallMemberEncryptionKeysRequest ,
389392 );
393+
394+ // Set up retry timers for each participant
395+ for (final rp in remoteParticipants) {
396+ // Skip if a retry is already pending for this participant
397+ if (_requestEncryptionKeyPending.containsKey (rp)) continue ;
398+
399+ var retryCount = 0 ;
400+ _requestEncryptionKeyPending[rp] = Timer .periodic (
401+ _keyRequestRetryInterval,
402+ (timer) {
403+ retryCount++ ;
404+ if (retryCount >= 5 ) {
405+ Logs ().w (
406+ '[VOIP E2EE] Max retries (5) reached for ${rp .id }, giving up key request' ,
407+ );
408+ timer.cancel ();
409+ _requestEncryptionKeyPending.remove (rp);
410+ return ;
411+ }
412+ unawaited (requestEncrytionKey (groupCall, [rp]));
413+ },
414+ );
415+ }
390416 }
391417
392418 @override
@@ -403,8 +429,14 @@ class LiveKitBackend extends CallBackend {
403429 final keyContent = EncryptionKeysEventContent .fromJson (content);
404430
405431 final callId = keyContent.callId;
406- final p =
407- CallParticipant (groupCall.voip, userId: userId, deviceId: deviceId);
432+ final p = CallParticipant (
433+ groupCall.voip,
434+ userId: userId,
435+ deviceId: deviceId,
436+ );
437+
438+ // Cancel any pending retry for this participant since we received keys
439+ _requestEncryptionKeyPending.remove (p)? .cancel ();
408440
409441 if (keyContent.keys.isEmpty) {
410442 Logs ().w (
@@ -471,11 +503,7 @@ class LiveKitBackend extends CallBackend {
471503 groupCall,
472504 _latestLocalKeyIndex,
473505 sendTo: [
474- CallParticipant (
475- groupCall.voip,
476- userId: userId,
477- deviceId: deviceId,
478- ),
506+ CallParticipant (groupCall.voip, userId: userId, deviceId: deviceId),
479507 ],
480508 );
481509 return true ;
@@ -525,16 +553,14 @@ class LiveKitBackend extends CallBackend {
525553 if (_memberLeaveEncKeyRotateDebounceTimer != null ) {
526554 _memberLeaveEncKeyRotateDebounceTimer! .cancel ();
527555 }
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- });
556+ _memberLeaveEncKeyRotateDebounceTimer = Timer (
557+ groupCall.voip.timeouts! .makeKeyOnLeaveDelay,
558+ () async {
559+ // we skipJoinDebounce here because we want to make sure a new key is generated
560+ // and that the join debounce does not block us from making a new key
561+ await _makeNewSenderKey (groupCall, true , skipJoinDebounce: true );
562+ },
563+ );
538564 }
539565
540566 @override
@@ -545,6 +571,12 @@ class LiveKitBackend extends CallBackend {
545571 _currentLocalKeyIndex = 0 ;
546572 _latestLocalKeyIndex = 0 ;
547573 _memberLeaveEncKeyRotateDebounceTimer? .cancel ();
574+
575+ // Clean up all pending encryption key request retries
576+ for (final timer in _requestEncryptionKeyPending.values) {
577+ timer.cancel ();
578+ }
579+ _requestEncryptionKeyPending.clear ();
548580 }
549581
550582 @override
0 commit comments