Repository navigation
Follow key provider key index on media frame cryptors - #1042
tangowhisky-dev wants to merge 1 commit into
Conversation
Ports the semantics merged in livekit/client-sdk-swift#1157 to Android, superseding the approach in livekit#1039. BaseKeyProvider now implements the new KeyIndexObservable contract: the current key index is the single source of truth for what the local participant encrypts with. Installing a shared key moves the index and setCurrentKeyIndex() exposes it directly; getLatestKeyIndex() returns it for both media senders and data packets. E2EEManager subscribes to the provider and re-stamps the frame cryptors of already-published local tracks on the RTC thread whenever the index changes, and stamps new senders at creation. Receivers are unaffected: they read the index from each incoming frame. Cryptor map iteration is synchronized, and a serialized dispatch that re-reads the latest index prevents overlapping rotations from leaving senders stale. Behavior change: in non-shared key mode setKey() no longer moves the sender index, since the key may belong to a remote participant. Use setCurrentKeyIndex() to switch the local one. Custom KeyProvider implementations opt into live re-pointing by implementing KeyIndexObservable.
🦋 Changeset detectedLatest commit: 41fd624 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Devin Review found 2 potential issues.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| */ | ||
| fun setCurrentKeyIndex(keyIndex: Int) { | ||
| val observers = synchronized(keyIndexStateLock) { | ||
| _currentKeyIndex = Math.floorMod(keyIndex, keyRingSize) |
There was a problem hiding this comment.
🔴 Key rotation crashes on older Android
On Android API 21–23, setCurrentKeyIndex calls Math.floorMod, which those devices lack. Even constructing E2EEOptions(sharedKey) can crash because it calls setSharedKey.
Learn more
The SDK supports Android API 21, but Math.floorMod is unavailable before API 24. E2EEOptions installs a shared key during construction, which reaches this call after a successful native key install. The resulting linkage error prevents E2EE setup on older devices.
Example: On an API 21 device, construct E2EEOptions(sharedKey = "secret"). Once the provider accepts that key, index normalization throws NoSuchMethodError instead of creating the options.
Recommended fix: Normalize the remainder using arithmetic supported on API 21, retaining nonnegative indices for negative inputs; verify initialization on an API 21 device or emulator.
| _currentKeyIndex = Math.floorMod(keyIndex, keyRingSize) | |
| val remainder = keyIndex % keyRingSize | |
| _currentKeyIndex = if (remainder < 0) remainder + keyRingSize else remainder |
Was this helpful? React with 👍 or 👎 to provide feedback.
| * for every data packet sent afterwards. Receivers are unaffected: they | ||
| * read the index from each frame. | ||
| */ | ||
| fun setCurrentKeyIndex(keyIndex: Int) { |
There was a problem hiding this comment.
🟡 Key rotation lacks required Room example
setCurrentKeyIndex adds a public rotation API without the required short example at the Room entry point. The same omission affects KeyIndexObservable.
Learn more
The repository's review rules require short code examples for new public APIs at the entry point, such as Room. This change exposes setCurrentKeyIndex and KeyIndexObservable without showing room users how to access the provider and rotate an existing sender.
Example: A consumer reading Room documentation has no example connecting room.e2eeManager?.keyProvider() with an index change for published media.
Recommended fix: Add a short example in the Room entry-point documentation that shows the supported key-provider rotation flow and how a custom provider can opt into KeyIndexObservable.
Was this helpful? React with 👍 or 👎 to provide feedback.
Supersedes #1039 — ports the approach merged in client-sdk-swift#1157 to Android.
Problem
addRtpSenderstamps a sender'sFrameCryptor.keyIndexonce at creation and never updates it. Apps that rotate keys into different key-ring slots (e.g. per-epoch MLS key rings in E2EE deployments) can update live data packets viasetSharedKey(key, index), but media senders keep stamping the old index — receivers hitMISSING_KEYand drop frames. There is no public API to re-point live senders.Approach
Mirrors the Swift design rather than adding a
setKeyIndex(participant)API (whose identity parameter let a remote identity redirect local encryption — the review concern on the earlier Swift PR #1156).KeyIndexObservable— new opt-in contract forKeyProviders that track the local encryption index:BaseKeyProviderimplements it —currentKeyIndexis the single source of truth:setSharedKey(key, index)moves it (parity with JS SDKsetSharedKeybehavior merged in client-sdk-js#1830/#1831)setCurrentKeyIndex(index)exposes it directly (ring-size modulo)getLatestKeyIndex()returns it — senders stamp at creation, data packets pick it up per-sendE2EEManagersubscribes internally and re-stamps the frame cryptors of already-published local tracks on the RTC thread (viaexecuteOnRTCThread, honoring the SDK's threading rule). Receivers are untouched — they read the index from each frame's trailer.Behavior change
In non-shared key mode,
setKey()no longer moves the sender index — the key may belong to a remote participant, so it must not redirect local encryption. Apps switching the local index should callsetCurrentKeyIndex(). This matches the Swift change.Custom
KeyProviderimplementations (e.g. ones that bypassBaseKeyProviderto hand raw key bytes to the RTC provider) opt into live re-pointing by implementingKeyIndexObservable.Test plan
FrameCryptorKeyIndexTest(new, 7 tests): shared-key install moves index,setCurrentKeyIndexmodulo wrap, non-sharedsetKeydoes not move index, observer add/remove, new senders stamp current index, existing senders follow changes, receivers not re-stampede2eetests pass (DataPacketCryptorMockE2ETest,DataTrackCryptorTest):livekit-android-sdk:detektRelease,spotlessCheckclean