Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: debounce ratcheting voip keys #1877

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 43 additions & 20 deletions lib/src/voip/backend/livekit_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class LiveKitBackend extends CallBackend {
/// participant:keyIndex:keyBin
final Map<CallParticipant, Map<int, Uint8List>> _encryptionKeysMap = {};

final List<Future> _setNewKeyTimeouts = [];
final List<Future<void>> _setNewKeyTimeouts = [];

int _indexCounter = 0;

Expand Down Expand Up @@ -90,6 +90,8 @@ class LiveKitBackend extends CallBackend {
);
}

DateTime lastRatchetAt = DateTime(1980);

/// also does the sending for you
Future<void> _ratchetLocalParticipantKey(
GroupCallSession groupCall,
Expand All @@ -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<void> _changeEncryptionKey(
Expand Down Expand Up @@ -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<void>.delayed(useKeyDelay, () async {
Logs().i(
'[VOIP E2EE] setting key changed event for ${participant.id} idx $encryptionKeyIndex key $encryptionKeyBin');
await groupCall.voip.delegate.keyProvider?.onSetEncryptionKey(
Expand Down Expand Up @@ -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();
Expand Down
Loading