livekit-client 2.5.8 → 2.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +543 -5128
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +104 -98
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +25 -5
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +1 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +0 -10
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +25 -5
- package/dist/ts4.2/src/index.d.ts +2 -1
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/utils.d.ts +0 -10
- package/package.json +3 -2
- package/src/api/SignalClient.ts +2 -1
- package/src/e2ee/worker/FrameCryptor.test.ts +311 -113
- package/src/e2ee/worker/FrameCryptor.ts +10 -5
- package/src/e2ee/worker/ParticipantKeyHandler.test.ts +169 -5
- package/src/e2ee/worker/ParticipantKeyHandler.ts +50 -20
- package/src/e2ee/worker/__snapshots__/ParticipantKeyHandler.test.ts.snap +356 -0
- package/src/index.ts +1 -1
- package/src/room/PCTransportManager.ts +2 -1
- package/src/room/RTCEngine.ts +3 -1
- package/src/room/Room.ts +1 -1
- package/src/room/participant/LocalParticipant.ts +4 -1
- package/src/room/track/LocalTrack.ts +2 -1
- package/src/room/track/LocalVideoTrack.ts +2 -1
- package/src/room/track/options.ts +5 -5
- package/src/room/utils.ts +0 -38
- package/src/utils/AsyncQueue.test.ts +2 -2
- package/src/utils/AsyncQueue.ts +1 -1
@@ -60,6 +60,21 @@ class TestUnderlyingSink<T> implements UnderlyingSink<T> {
|
|
60
60
|
function prepareParticipantTestDecoder(
|
61
61
|
participantIdentity: string,
|
62
62
|
partialKeyProviderOptions: Partial<KeyProviderOptions>,
|
63
|
+
) {
|
64
|
+
return prepareParticipantTest('decode', participantIdentity, partialKeyProviderOptions);
|
65
|
+
}
|
66
|
+
|
67
|
+
function prepareParticipantTestEncoder(
|
68
|
+
participantIdentity: string,
|
69
|
+
partialKeyProviderOptions: Partial<KeyProviderOptions>,
|
70
|
+
) {
|
71
|
+
return prepareParticipantTest('encode', participantIdentity, partialKeyProviderOptions);
|
72
|
+
}
|
73
|
+
|
74
|
+
function prepareParticipantTest(
|
75
|
+
mode: 'encode' | 'decode',
|
76
|
+
participantIdentity: string,
|
77
|
+
partialKeyProviderOptions: Partial<KeyProviderOptions>,
|
63
78
|
): {
|
64
79
|
keys: ParticipantKeyHandler;
|
65
80
|
cryptor: FrameCryptor;
|
@@ -80,12 +95,7 @@ function prepareParticipantTestDecoder(
|
|
80
95
|
|
81
96
|
const input = new TestUnderlyingSource<RTCEncodedVideoFrame>();
|
82
97
|
const output = new TestUnderlyingSink<RTCEncodedVideoFrame>();
|
83
|
-
cryptor.setupTransform(
|
84
|
-
'decode',
|
85
|
-
new ReadableStream(input),
|
86
|
-
new WritableStream(output),
|
87
|
-
'testTrack',
|
88
|
-
);
|
98
|
+
cryptor.setupTransform(mode, new ReadableStream(input), new WritableStream(output), 'testTrack');
|
89
99
|
|
90
100
|
return { keys, cryptor, input, output };
|
91
101
|
}
|
@@ -93,10 +103,6 @@ function prepareParticipantTestDecoder(
|
|
93
103
|
describe('FrameCryptor', () => {
|
94
104
|
const participantIdentity = 'testParticipant';
|
95
105
|
|
96
|
-
afterEach(() => {
|
97
|
-
encryptionEnabledMap.clear();
|
98
|
-
});
|
99
|
-
|
100
106
|
it('identifies server injected frame correctly', () => {
|
101
107
|
const frameTrailer = new TextEncoder().encode('LKROCKS');
|
102
108
|
const frameData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, ...frameTrailer]).buffer;
|
@@ -113,154 +119,346 @@ describe('FrameCryptor', () => {
|
|
113
119
|
expect(isFrameServerInjected(frameData.buffer, frameTrailer)).toBe(false);
|
114
120
|
});
|
115
121
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
122
|
+
describe('encode', () => {
|
123
|
+
afterEach(() => {
|
124
|
+
encryptionEnabledMap.clear();
|
125
|
+
});
|
120
126
|
|
121
|
-
|
122
|
-
|
127
|
+
it('passthrough if participant encryption disabled', async () => {
|
128
|
+
vitest.useFakeTimers();
|
129
|
+
try {
|
130
|
+
const { input, output } = prepareParticipantTestEncoder(participantIdentity, {});
|
123
131
|
|
124
|
-
|
132
|
+
// disable encryption for participant
|
133
|
+
encryptionEnabledMap.set(participantIdentity, false);
|
125
134
|
|
126
|
-
|
127
|
-
await vitest.advanceTimersToNextTimerAsync();
|
135
|
+
const frame = mockRTCEncodedVideoFrame(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
|
128
136
|
|
129
|
-
|
130
|
-
|
131
|
-
vitest.useRealTimers();
|
132
|
-
}
|
133
|
-
});
|
137
|
+
input.write(frame);
|
138
|
+
await vitest.advanceTimersToNextTimerAsync();
|
134
139
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
140
|
+
expect(output.chunks).toEqual([frame]);
|
141
|
+
} finally {
|
142
|
+
vitest.useRealTimers();
|
143
|
+
}
|
144
|
+
});
|
139
145
|
|
140
|
-
|
141
|
-
|
146
|
+
it('passthrough for empty frame', async () => {
|
147
|
+
vitest.useFakeTimers();
|
148
|
+
try {
|
149
|
+
const { input, output } = prepareParticipantTestEncoder(participantIdentity, {});
|
142
150
|
|
143
|
-
|
144
|
-
|
151
|
+
// empty frame
|
152
|
+
const frame = mockRTCEncodedVideoFrame(new Uint8Array(0));
|
145
153
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
154
|
+
input.write(frame);
|
155
|
+
await vitest.advanceTimersToNextTimerAsync();
|
156
|
+
|
157
|
+
expect(output.chunks).toEqual([frame]);
|
158
|
+
} finally {
|
159
|
+
vitest.useRealTimers();
|
160
|
+
}
|
161
|
+
});
|
162
|
+
|
163
|
+
it('immediately drops frame and emits error if no key set', async () => {
|
164
|
+
vitest.useFakeTimers();
|
165
|
+
try {
|
166
|
+
const { cryptor, input, output } = prepareParticipantTestEncoder(participantIdentity, {});
|
167
|
+
|
168
|
+
const errorListener = vitest.fn();
|
169
|
+
cryptor.on(CryptorEvent.Error, errorListener);
|
170
|
+
|
171
|
+
const frame = mockRTCEncodedVideoFrame(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
|
151
172
|
|
152
|
-
|
153
|
-
|
154
|
-
try {
|
155
|
-
const { keys, input, output } = prepareParticipantTestDecoder(participantIdentity, {
|
156
|
-
failureTolerance: 0,
|
157
|
-
});
|
173
|
+
input.write(frame);
|
174
|
+
await vitest.advanceTimersToNextTimerAsync();
|
158
175
|
|
159
|
-
|
176
|
+
expect(output.chunks).toEqual([]);
|
177
|
+
expect(errorListener).toHaveBeenCalled();
|
178
|
+
} finally {
|
179
|
+
vitest.useRealTimers();
|
180
|
+
}
|
181
|
+
});
|
182
|
+
|
183
|
+
it('encrypts frame', async () => {
|
184
|
+
vitest.useFakeTimers();
|
185
|
+
try {
|
186
|
+
const { keys, input, output } = prepareParticipantTestEncoder(participantIdentity, {});
|
187
|
+
|
188
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
160
189
|
|
161
|
-
|
190
|
+
const plainTextData = new Uint8Array([
|
191
|
+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
192
|
+
]);
|
193
|
+
const frame = mockRTCEncodedVideoFrame(plainTextData);
|
162
194
|
|
163
|
-
|
164
|
-
|
195
|
+
input.write(frame);
|
196
|
+
await vitest.waitFor(() => expect(output.chunks).toHaveLength(1));
|
165
197
|
|
166
|
-
|
167
|
-
|
198
|
+
expect(output.chunks).toEqual([frame]);
|
199
|
+
expect(frame.data.byteLength).toBeGreaterThan(16);
|
168
200
|
|
169
|
-
|
170
|
-
|
171
|
-
await vitest.advanceTimersToNextTimerAsync();
|
201
|
+
// first bytes are unencrypted
|
202
|
+
expect(new Uint8Array(frame.data.slice(0, 10))).toEqual(plainTextData.subarray(0, 10));
|
172
203
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
204
|
+
// remainder should not be the same
|
205
|
+
expect(new Uint8Array(frame.data.slice(10, 16))).not.toEqual(
|
206
|
+
plainTextData.subarray(10, 16),
|
207
|
+
);
|
208
|
+
|
209
|
+
const frameTrailer = new Uint8Array(frame.data.slice(frame.data.byteLength - 2));
|
210
|
+
// IV length
|
211
|
+
expect(frameTrailer[0]).toEqual(IV_LENGTH);
|
212
|
+
// key index
|
213
|
+
expect(frameTrailer[1]).toEqual(1);
|
214
|
+
} finally {
|
215
|
+
vitest.useRealTimers();
|
216
|
+
}
|
217
|
+
});
|
178
218
|
});
|
179
219
|
|
180
|
-
|
181
|
-
|
182
|
-
|
220
|
+
describe('decode', () => {
|
221
|
+
afterEach(() => {
|
222
|
+
encryptionEnabledMap.clear();
|
183
223
|
});
|
184
224
|
|
185
|
-
|
225
|
+
it('passthrough if participant encryption disabled', async () => {
|
226
|
+
vitest.useFakeTimers();
|
227
|
+
try {
|
228
|
+
const { input, output } = prepareParticipantTestDecoder(participantIdentity, {});
|
186
229
|
|
187
|
-
|
230
|
+
// disable encryption for participant
|
231
|
+
encryptionEnabledMap.set(participantIdentity, false);
|
188
232
|
|
189
|
-
|
190
|
-
vitest.spyOn(keys, 'decryptionFailure');
|
233
|
+
const frame = mockEncryptedRTCEncodedVideoFrame(1);
|
191
234
|
|
192
|
-
|
193
|
-
|
235
|
+
input.write(frame);
|
236
|
+
await vitest.advanceTimersToNextTimerAsync();
|
237
|
+
|
238
|
+
expect(output.chunks).toEqual([frame]);
|
239
|
+
} finally {
|
240
|
+
vitest.useRealTimers();
|
241
|
+
}
|
194
242
|
});
|
195
|
-
cryptor.on(CryptorEvent.Error, errorListener);
|
196
243
|
|
197
|
-
|
244
|
+
it('passthrough for empty frame', async () => {
|
245
|
+
vitest.useFakeTimers();
|
246
|
+
try {
|
247
|
+
const { input, output } = prepareParticipantTestDecoder(participantIdentity, {});
|
198
248
|
|
199
|
-
|
200
|
-
|
201
|
-
expect(keys.decryptionFailure).toHaveBeenCalledTimes(1);
|
202
|
-
expect(keys.getKeySet).toHaveBeenCalled();
|
203
|
-
expect(keys.getKeySet).toHaveBeenLastCalledWith(1);
|
204
|
-
expect(keys.hasValidKey).toBe(true);
|
249
|
+
// empty frame
|
250
|
+
const frame = mockRTCEncodedVideoFrame(new Uint8Array(0));
|
205
251
|
|
206
|
-
|
252
|
+
input.write(frame);
|
253
|
+
await vitest.advanceTimersToNextTimerAsync();
|
207
254
|
|
208
|
-
|
255
|
+
expect(output.chunks).toEqual([frame]);
|
256
|
+
} finally {
|
257
|
+
vitest.useRealTimers();
|
258
|
+
}
|
259
|
+
});
|
209
260
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
261
|
+
it('immediately drops frames when key marked invalid', async () => {
|
262
|
+
vitest.useFakeTimers();
|
263
|
+
try {
|
264
|
+
const { keys, input, output } = prepareParticipantTestDecoder(participantIdentity, {
|
265
|
+
failureTolerance: 0,
|
266
|
+
});
|
216
267
|
|
217
|
-
|
268
|
+
keys.decryptionFailure();
|
218
269
|
|
219
|
-
|
220
|
-
|
270
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(1));
|
271
|
+
await vitest.advanceTimersToNextTimerAsync();
|
221
272
|
|
222
|
-
|
223
|
-
// decryptionFailure() isn't called in this case
|
224
|
-
expect(keys.getKeySet).toHaveBeenCalled();
|
225
|
-
expect(keys.getKeySet).toHaveBeenLastCalledWith(0);
|
226
|
-
expect(keys.hasValidKey).toBe(false);
|
227
|
-
});
|
273
|
+
expect(output.chunks).toEqual([]);
|
228
274
|
|
229
|
-
|
230
|
-
const { keys, input } = prepareParticipantTestDecoder(participantIdentity, {
|
231
|
-
failureTolerance: 0,
|
232
|
-
});
|
275
|
+
keys.decryptionFailure();
|
233
276
|
|
234
|
-
|
235
|
-
|
277
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(0));
|
278
|
+
await vitest.advanceTimersToNextTimerAsync();
|
236
279
|
|
237
|
-
|
280
|
+
expect(output.chunks).toEqual([]);
|
281
|
+
} finally {
|
282
|
+
vitest.useRealTimers();
|
283
|
+
}
|
284
|
+
});
|
238
285
|
|
239
|
-
|
286
|
+
it('calls decryptionFailure on missing key and emits error', async () => {
|
287
|
+
vitest.useFakeTimers();
|
288
|
+
try {
|
289
|
+
const { cryptor, keys, input } = prepareParticipantTestDecoder(participantIdentity, {});
|
290
|
+
|
291
|
+
const errorListener = vitest.fn();
|
292
|
+
cryptor.on(CryptorEvent.Error, errorListener);
|
293
|
+
vitest.spyOn(keys, 'decryptionFailure');
|
294
|
+
|
295
|
+
// no key is set at this index
|
296
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(1));
|
297
|
+
await vitest.advanceTimersToNextTimerAsync();
|
298
|
+
|
299
|
+
expect(keys.decryptionFailure).toHaveBeenCalledTimes(1);
|
300
|
+
expect(keys.decryptionFailure).toHaveBeenCalledWith(1);
|
301
|
+
expect(errorListener).toHaveBeenCalled();
|
302
|
+
} finally {
|
303
|
+
vitest.useRealTimers();
|
304
|
+
}
|
305
|
+
});
|
240
306
|
|
241
|
-
|
307
|
+
it('immediately drops frame if no key', async () => {
|
308
|
+
vitest.useFakeTimers();
|
309
|
+
try {
|
310
|
+
const { input, output } = prepareParticipantTestDecoder(participantIdentity, {});
|
242
311
|
|
243
|
-
|
312
|
+
vitest.spyOn(crypto.subtle, 'decrypt');
|
244
313
|
|
245
|
-
|
246
|
-
|
314
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(1));
|
315
|
+
await vitest.advanceTimersToNextTimerAsync();
|
247
316
|
|
248
|
-
|
249
|
-
|
250
|
-
|
317
|
+
expect(crypto.subtle.decrypt).not.toHaveBeenCalled();
|
318
|
+
expect(output.chunks).toEqual([]);
|
319
|
+
} finally {
|
320
|
+
vitest.useRealTimers();
|
321
|
+
}
|
251
322
|
});
|
252
323
|
|
253
|
-
|
254
|
-
|
324
|
+
it('calls decryptionFailure with incorrect key and emits error', async () => {
|
325
|
+
vitest.useFakeTimers();
|
326
|
+
try {
|
327
|
+
const { cryptor, keys, input, output } = prepareParticipantTestDecoder(
|
328
|
+
participantIdentity,
|
329
|
+
{ ratchetWindowSize: 0 },
|
330
|
+
);
|
331
|
+
|
332
|
+
vitest.spyOn(crypto.subtle, 'decrypt');
|
333
|
+
vitest.spyOn(keys, 'decryptionFailure');
|
334
|
+
const errorListener = vitest.fn();
|
335
|
+
cryptor.on(CryptorEvent.Error, errorListener);
|
336
|
+
|
337
|
+
await keys.setKey(await createKeyMaterialFromString('incorrect key'), 1);
|
338
|
+
|
339
|
+
const frame = mockRTCEncodedVideoFrame(
|
340
|
+
new Uint8Array([
|
341
|
+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 254, 96, 91, 111, 187, 132, 31, 12, 207, 136, 17, 221,
|
342
|
+
233, 116, 174, 6, 50, 37, 214, 71, 119, 196, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
|
343
|
+
199, 51, 12, 1,
|
344
|
+
]),
|
345
|
+
);
|
346
|
+
// global.RTCEncodedAudioFrame = vitest.fn();
|
347
|
+
input.write(frame);
|
348
|
+
await vitest.waitFor(() => expect(keys.decryptionFailure).toHaveBeenCalled());
|
349
|
+
|
350
|
+
expect(crypto.subtle.decrypt).toHaveBeenCalled();
|
351
|
+
expect(output.chunks).toEqual([]);
|
352
|
+
expect(errorListener).toHaveBeenCalled();
|
353
|
+
expect(keys.decryptionFailure).toHaveBeenCalledTimes(1);
|
354
|
+
expect(keys.decryptionFailure).toHaveBeenCalledWith(1);
|
355
|
+
} finally {
|
356
|
+
vitest.useRealTimers();
|
357
|
+
}
|
358
|
+
});
|
359
|
+
|
360
|
+
it('decrypts frame with correct key', async () => {
|
361
|
+
vitest.useFakeTimers();
|
362
|
+
try {
|
363
|
+
const { keys, input, output } = prepareParticipantTestDecoder(participantIdentity, {});
|
364
|
+
|
365
|
+
vitest.spyOn(keys, 'decryptionSuccess');
|
255
366
|
|
256
|
-
|
367
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
257
368
|
|
258
|
-
|
369
|
+
const frame = mockRTCEncodedVideoFrame(
|
370
|
+
new Uint8Array([
|
371
|
+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 254, 96, 91, 111, 187, 132, 31, 12, 207, 136, 17, 221,
|
372
|
+
233, 116, 174, 6, 50, 37, 214, 71, 119, 196, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
|
373
|
+
199, 51, 12, 1,
|
374
|
+
]),
|
375
|
+
);
|
376
|
+
input.write(frame);
|
377
|
+
await vitest.waitFor(() => expect(output.chunks).toHaveLength(1));
|
259
378
|
|
260
|
-
|
379
|
+
expect(output.chunks).toEqual([frame]);
|
261
380
|
|
262
|
-
|
381
|
+
expect(keys.decryptionSuccess).toHaveBeenCalledTimes(1);
|
382
|
+
expect(keys.decryptionSuccess).toHaveBeenCalledWith(1);
|
263
383
|
|
264
|
-
|
384
|
+
expect(frame.data.byteLength).toBe(16);
|
385
|
+
|
386
|
+
expect(new Uint8Array(frame.data)).toEqual(
|
387
|
+
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
388
|
+
);
|
389
|
+
} finally {
|
390
|
+
vitest.useRealTimers();
|
391
|
+
}
|
392
|
+
});
|
393
|
+
|
394
|
+
it('recovers from delayed use of rotated key', async () => {
|
395
|
+
vitest.useFakeTimers();
|
396
|
+
try {
|
397
|
+
// 1. we (the local participant) have just joined a room and do not have the existing key (index 0) for the existing/remote participant
|
398
|
+
const { keys, input, output } = prepareParticipantTestDecoder(participantIdentity, {
|
399
|
+
failureTolerance: 1,
|
400
|
+
ratchetWindowSize: 0,
|
401
|
+
});
|
402
|
+
vitest.spyOn(keys, 'decryptionFailure');
|
403
|
+
|
404
|
+
// 2. we receive some frames from the existing participant encrypted with the existing key 0 that we don't have
|
405
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(0));
|
406
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(0));
|
407
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(0));
|
408
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(0));
|
409
|
+
|
410
|
+
// 3. we should have marked key at index 0 as invalid by now and dropped all the frames
|
411
|
+
await vitest.waitFor(() => expect(keys.decryptionFailure).toHaveBeenCalledTimes(2));
|
412
|
+
expect(keys.hasInvalidKeyAtIndex(0)).toBe(true);
|
413
|
+
expect(output.chunks).toEqual([]);
|
414
|
+
|
415
|
+
// 4. the existing participant then notices that we have joined the room and generates a new key (with a new key index 1)
|
416
|
+
// and distributes it out of band to us
|
417
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
418
|
+
|
419
|
+
// 5. the existing participant waits a period of time before using the new key and continues sending media using the previous key 0.
|
420
|
+
// we receive these frames and should drop them as we still don't have the key.
|
421
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(0));
|
422
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(0));
|
423
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(0));
|
424
|
+
input.write(mockEncryptedRTCEncodedVideoFrame(0));
|
425
|
+
|
426
|
+
await vitest.advanceTimersToNextTimerAsync();
|
427
|
+
expect(output.chunks).toEqual([]);
|
428
|
+
|
429
|
+
// 6. the existing participant moves over to the new key index 1 and we start to receive frames for index 1 that we
|
430
|
+
// should be able to decrypt even though we had the previous failures.
|
431
|
+
input.write(
|
432
|
+
mockRTCEncodedVideoFrame(
|
433
|
+
new Uint8Array([
|
434
|
+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 254, 96, 91, 111, 187, 132, 31, 12, 207, 136, 17, 221,
|
435
|
+
233, 116, 174, 6, 50, 37, 214, 71, 119, 196, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
|
436
|
+
199, 51, 12, 1,
|
437
|
+
]),
|
438
|
+
),
|
439
|
+
);
|
440
|
+
|
441
|
+
input.write(
|
442
|
+
mockRTCEncodedVideoFrame(
|
443
|
+
new Uint8Array([
|
444
|
+
99, 2, 3, 4, 5, 6, 7, 8, 9, 10, 154, 108, 209, 239, 253, 33, 72, 111, 13, 125, 10,
|
445
|
+
101, 28, 209, 141, 162, 0, 238, 189, 254, 66, 156, 255, 255, 255, 255, 0, 0, 0, 0,
|
446
|
+
255, 255, 96, 247, 12, 1,
|
447
|
+
]),
|
448
|
+
),
|
449
|
+
);
|
450
|
+
|
451
|
+
await vitest.waitFor(() => expect(output.chunks.length).toEqual(2));
|
452
|
+
|
453
|
+
expect(new Uint8Array(output.chunks[0].data)).toEqual(
|
454
|
+
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
455
|
+
);
|
456
|
+
expect(new Uint8Array(output.chunks[1].data)).toEqual(
|
457
|
+
new Uint8Array([99, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
458
|
+
);
|
459
|
+
} finally {
|
460
|
+
vitest.useRealTimers();
|
461
|
+
}
|
462
|
+
});
|
265
463
|
});
|
266
464
|
});
|
@@ -357,10 +357,15 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
357
357
|
const data = new Uint8Array(encodedFrame.data);
|
358
358
|
const keyIndex = data[encodedFrame.data.byteLength - 1];
|
359
359
|
|
360
|
-
if (this.keys.
|
360
|
+
if (this.keys.hasInvalidKeyAtIndex(keyIndex)) {
|
361
|
+
// drop frame
|
362
|
+
return;
|
363
|
+
}
|
364
|
+
|
365
|
+
if (this.keys.getKeySet(keyIndex)) {
|
361
366
|
try {
|
362
367
|
const decodedFrame = await this.decryptFrame(encodedFrame, keyIndex);
|
363
|
-
this.keys.decryptionSuccess();
|
368
|
+
this.keys.decryptionSuccess(keyIndex);
|
364
369
|
if (decodedFrame) {
|
365
370
|
return controller.enqueue(decodedFrame);
|
366
371
|
}
|
@@ -369,13 +374,13 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
369
374
|
// emit an error if the key handler thinks we have a valid key
|
370
375
|
if (this.keys.hasValidKey) {
|
371
376
|
this.emit(CryptorEvent.Error, error);
|
372
|
-
this.keys.decryptionFailure();
|
377
|
+
this.keys.decryptionFailure(keyIndex);
|
373
378
|
}
|
374
379
|
} else {
|
375
380
|
workerLogger.warn('decoding frame failed', { error });
|
376
381
|
}
|
377
382
|
}
|
378
|
-
} else
|
383
|
+
} else {
|
379
384
|
// emit an error if the key index is out of bounds but the key handler thinks we still have a valid key
|
380
385
|
workerLogger.warn(`skipping decryption due to missing key at index ${keyIndex}`);
|
381
386
|
this.emit(
|
@@ -386,7 +391,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
386
391
|
this.participantIdentity,
|
387
392
|
),
|
388
393
|
);
|
389
|
-
this.keys.decryptionFailure();
|
394
|
+
this.keys.decryptionFailure(keyIndex);
|
390
395
|
}
|
391
396
|
}
|
392
397
|
|