From 1118ab50604191dc874bd8dd8f679d6302b6e784 Mon Sep 17 00:00:00 2001 From: Christian Gick Date: Tue, 10 Mar 2026 09:38:17 +0200 Subject: [PATCH] fix(e2ee): aggressive video re-keying after track subscription (MAT-144) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Video frame cryptors may not be fully initialized when set_key() is first called during on_track_subscribed. Audio works immediately but video oscillates OK↔DEC_FAILED with the same key. Add staggered re-keying at 0.3s, 0.8s, 2s, 5s after video track subscription to ensure the key is applied after the frame cryptor is fully ready. Co-Authored-By: Claude Opus 4.6 --- voice.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/voice.py b/voice.py index 85c01b3..bf14bb6 100644 --- a/voice.py +++ b/voice.py @@ -693,8 +693,25 @@ class VoiceSession: if self._caller_all_keys: for idx, base_k in sorted(self._caller_all_keys.items()): _derive_and_set_key(kp_local, caller_id, base_k, idx) - logger.info("on_ts: derived+set key[%d] for %s (%s track)", + logger.info("on_ts: set key[%d] for %s (%s track)", idx, caller_id, track_type) + # MAT-144: Video frame cryptors may not be fully initialized + # when set_key is first called. Schedule aggressive re-keying. + if int(t.kind) == 2: + async def _video_rekey(pid=caller_id): + for delay in (0.3, 0.8, 2.0, 5.0): + await asyncio.sleep(delay) + if not self.lk_room: + break + try: + kp_v = self.lk_room.e2ee_manager.key_provider + for idx, base_k in sorted(self._caller_all_keys.items()): + _derive_and_set_key(kp_v, pid, base_k, idx) + logger.info("video_rekey: re-set %d keys for %s (delay=%.1fs)", + len(self._caller_all_keys), pid, delay) + except Exception as exc: + logger.warning("video_rekey failed: %s", exc) + asyncio.ensure_future(_video_rekey()) else: logger.warning("on_ts: no caller keys yet — scheduling 0.5s retry") async def _brief_key_retry(pid=caller_id): @@ -704,7 +721,7 @@ class VoiceSession: kp_r = self.lk_room.e2ee_manager.key_provider for idx, base_k in sorted(self._caller_all_keys.items()): _derive_and_set_key(kp_r, pid, base_k, idx) - logger.info("on_ts_retry: derived+set key[%d] for %s", idx, pid) + logger.info("on_ts_retry: set key[%d] for %s", idx, pid) except Exception as exc: logger.warning("on_ts_retry: set_key failed: %s", exc) else: