Skip to content

Commit 25886b1

Browse files
feat: implement retry encryption key request mechanism for livekit calls
1 parent 9c6cbbd commit 25886b1

File tree

2 files changed

+272
-46
lines changed

2 files changed

+272
-46
lines changed

lib/src/voip/backend/livekit_backend.dart

Lines changed: 79 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:convert';
33
import 'dart:typed_data';
44

55
import 'package:matrix/matrix.dart';
6+
67
import 'package:matrix/src/utils/crypto/crypto.dart';
78

89
class 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

Comments
 (0)