fix(voice): wait for key rotation via nio sync, not HTTP fetch

io.element.call.encryption_keys events are Megolm-encrypted in this room
(appear as m.room.encrypted). The HTTP fetch cannot decrypt them — only
the nio sync client can via Olm/Megolm decryption.

Change the post-connect rotation poll to check self._caller_all_keys
directly (updated by on_encryption_key() via nio sync) instead of calling
_fetch_encryption_key_http() which always returns nothing in encrypted rooms.

Also extends wait to 10s and adds progress logging every 2s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-02-22 08:23:17 +02:00
parent cf519595d6
commit c330900a3a

View File

@@ -258,22 +258,23 @@ class VoiceSession:
len(self.lk_room.remote_participants)) len(self.lk_room.remote_participants))
# Element Call rotates its encryption key when bot joins the LiveKit room. # Element Call rotates its encryption key when bot joins the LiveKit room.
# We fetched the pre-join key above; now wait for EC to generate and publish # EC sends the new key via Matrix (Megolm-encrypted); nio sync will decrypt it
# the post-join rotated key via Matrix timeline. # and call on_encryption_key(), which updates self._caller_all_keys.
# NOTE: HTTP fetch is useless here — keys are Matrix-E2EE encrypted (m.room.encrypted).
pre_max_idx = max(self._caller_all_keys.keys()) if self._caller_all_keys else -1 pre_max_idx = max(self._caller_all_keys.keys()) if self._caller_all_keys else -1
logger.info("Polling for EC key rotation (pre-join max_idx=%d)...", pre_max_idx) logger.info("Waiting for EC key rotation via nio sync (current max_idx=%d)...", pre_max_idx)
for _attempt in range(10): # poll up to 5s (10 × 0.5s) for _attempt in range(20): # up to 10s (20 × 0.5s)
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
await self._fetch_encryption_key_http()
new_max = max(self._caller_all_keys.keys()) if self._caller_all_keys else -1 new_max = max(self._caller_all_keys.keys()) if self._caller_all_keys else -1
if new_max > pre_max_idx: if new_max > pre_max_idx:
self._caller_key = self._caller_all_keys[new_max] self._caller_key = self._caller_all_keys[new_max]
logger.info("Key rotated: index %d%d (%d bytes)", logger.info("Key rotated: index %d%d (%d bytes)",
pre_max_idx, new_max, len(self._caller_key)) pre_max_idx, new_max, len(self._caller_key))
break break
logger.debug("Key rotation poll %d: max_idx still %d", _attempt + 1, new_max) if _attempt % 4 == 3: # log every 2s
logger.info("Key rotation wait %ds: max_idx still %d", (_attempt + 1) // 2, new_max)
else: else:
logger.warning("No key rotation after 5s — using pre-join key[%d]", pre_max_idx) logger.warning("No key rotation after 10s — using pre-join key[%d]", pre_max_idx)
# Set per-participant keys via key provider # Set per-participant keys via key provider
kp = self.lk_room.e2ee_manager.key_provider kp = self.lk_room.e2ee_manager.key_provider