livekit-client 2.5.8 → 2.5.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|