livekit-client 2.18.9 → 2.19.0
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 +5609 -644
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +3553 -2813
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.pt.worker.js +2 -0
- package/dist/livekit-client.pt.worker.js.map +1 -0
- package/dist/livekit-client.pt.worker.mjs +5834 -0
- package/dist/livekit-client.pt.worker.mjs.map +1 -0
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts +2 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +8 -7
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +35 -8
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +5 -5
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts +5 -5
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +21 -4
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/sifPayload.d.ts +7 -7
- package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +7 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/packetTrailer/PacketTrailerManager.d.ts +49 -0
- package/dist/src/packetTrailer/PacketTrailerManager.d.ts.map +1 -0
- package/dist/src/packetTrailer/packetTrailer.d.ts +32 -0
- package/dist/src/packetTrailer/packetTrailer.d.ts.map +1 -0
- package/dist/src/packetTrailer/types.d.ts +57 -0
- package/dist/src/packetTrailer/types.d.ts.map +1 -0
- package/dist/src/packetTrailer/utils.d.ts +9 -0
- package/dist/src/packetTrailer/utils.d.ts.map +1 -0
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +1 -0
- package/dist/src/room/RTCEngine.d.ts +2 -4
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +7 -3
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/depacketizer.d.ts +12 -4
- package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
- package/dist/src/room/data-track/frame.d.ts +3 -3
- package/dist/src/room/data-track/frame.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/pipeline.d.ts +4 -1
- package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +2 -2
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
- package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/index.d.ts +5 -5
- package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
- package/dist/src/room/data-track/types.d.ts +7 -0
- package/dist/src/room/data-track/types.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +2 -2
- package/dist/src/room/participant/LocalParticipant.d.ts +8 -14
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +5 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/rpc/client/RpcClientManager.d.ts +39 -0
- package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -0
- package/dist/src/room/rpc/client/events.d.ts +8 -0
- package/dist/src/room/rpc/client/events.d.ts.map +1 -0
- package/dist/src/room/rpc/index.d.ts +6 -0
- package/dist/src/room/rpc/index.d.ts.map +1 -0
- package/dist/src/room/rpc/server/RpcServerManager.d.ts +44 -0
- package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -0
- package/dist/src/room/rpc/server/events.d.ts +8 -0
- package/dist/src/room/rpc/server/events.d.ts.map +1 -0
- package/dist/src/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
- package/dist/src/room/rpc/utils.d.ts.map +1 -0
- package/dist/src/room/track/PacketTrailerExtractor.d.ts +19 -0
- package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +1 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts +16 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +1 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +10 -0
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +4 -3
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
- package/dist/src/utils/dataPacketBuffer.d.ts +1 -1
- package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
- package/dist/src/version.d.ts +9 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/ts4.2/api/SignalClient.d.ts +2 -1
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +8 -7
- package/dist/ts4.2/e2ee/types.d.ts +35 -8
- package/dist/ts4.2/e2ee/utils.d.ts +5 -5
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +5 -5
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +21 -4
- package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -1
- package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +7 -7
- package/dist/ts4.2/index.d.ts +5 -1
- package/dist/ts4.2/options.d.ts +7 -0
- package/dist/ts4.2/packetTrailer/PacketTrailerManager.d.ts +49 -0
- package/dist/ts4.2/packetTrailer/packetTrailer.d.ts +32 -0
- package/dist/ts4.2/packetTrailer/types.d.ts +57 -0
- package/dist/ts4.2/packetTrailer/utils.d.ts +9 -0
- package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +2 -4
- package/dist/ts4.2/room/Room.d.ts +7 -3
- package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +5 -1
- package/dist/ts4.2/room/data-track/depacketizer.d.ts +12 -4
- package/dist/ts4.2/room/data-track/frame.d.ts +3 -3
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
- package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +4 -1
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +2 -2
- package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
- package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -5
- package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -1
- package/dist/ts4.2/room/data-track/types.d.ts +7 -0
- package/dist/ts4.2/room/events.d.ts +2 -2
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +8 -14
- package/dist/ts4.2/room/participant/Participant.d.ts +1 -1
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +5 -1
- package/dist/ts4.2/room/rpc/client/RpcClientManager.d.ts +43 -0
- package/dist/ts4.2/room/rpc/client/events.d.ts +8 -0
- package/dist/ts4.2/room/rpc/index.d.ts +7 -0
- package/dist/ts4.2/room/rpc/server/RpcServerManager.d.ts +44 -0
- package/dist/ts4.2/room/rpc/server/events.d.ts +8 -0
- package/dist/ts4.2/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
- package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +19 -0
- package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +16 -0
- package/dist/ts4.2/room/track/Track.d.ts +1 -1
- package/dist/ts4.2/room/track/options.d.ts +10 -0
- package/dist/ts4.2/room/utils.d.ts +4 -3
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +1 -1
- package/dist/ts4.2/version.d.ts +9 -1
- package/package.json +24 -16
- package/src/api/SignalClient.test.ts +102 -10
- package/src/api/SignalClient.ts +4 -2
- package/src/api/WebSocketStream.test.ts +0 -1
- package/src/e2ee/E2eeManager.ts +82 -30
- package/src/e2ee/types.ts +37 -8
- package/src/e2ee/utils.ts +7 -6
- package/src/e2ee/worker/DataCryptor.ts +6 -6
- package/src/e2ee/worker/FrameCryptor.test.ts +177 -4
- package/src/e2ee/worker/FrameCryptor.ts +94 -14
- package/src/e2ee/worker/ParticipantKeyHandler.test.ts +4 -4
- package/src/e2ee/worker/e2ee.worker.ts +13 -5
- package/src/e2ee/worker/naluUtils.ts +4 -4
- package/src/e2ee/worker/sifPayload.ts +10 -8
- package/src/index.ts +7 -0
- package/src/options.ts +8 -0
- package/src/packetTrailer/PacketTrailerManager.test.ts +172 -0
- package/src/packetTrailer/PacketTrailerManager.ts +250 -0
- package/src/packetTrailer/packetTrailer.test.ts +174 -0
- package/src/packetTrailer/packetTrailer.ts +276 -0
- package/src/packetTrailer/types.ts +75 -0
- package/src/packetTrailer/utils.test.ts +105 -0
- package/src/packetTrailer/utils.ts +50 -0
- package/src/packetTrailer/worker/packetTrailer.worker.ts +155 -0
- package/src/packetTrailer/worker/tsconfig.json +14 -0
- package/src/room/BackOffStrategy.test.ts +1 -1
- package/src/room/RTCEngine.test.ts +219 -0
- package/src/room/RTCEngine.ts +86 -46
- package/src/room/Room.test.ts +62 -1
- package/src/room/Room.ts +111 -86
- package/src/room/data-track/RemoteDataTrack.ts +8 -1
- package/src/room/data-track/depacketizer.test.ts +433 -1
- package/src/room/data-track/depacketizer.ts +79 -61
- package/src/room/data-track/frame.ts +2 -2
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +194 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +21 -1
- package/src/room/data-track/incoming/pipeline.ts +13 -2
- package/src/room/data-track/outgoing/types.ts +3 -2
- package/src/room/data-track/packet/extensions.ts +2 -2
- package/src/room/data-track/packet/index.ts +6 -6
- package/src/room/data-track/packet/serializable.ts +1 -1
- package/src/room/data-track/types.ts +8 -0
- package/src/room/events.ts +2 -2
- package/src/room/participant/LocalParticipant.test.ts +81 -0
- package/src/room/participant/LocalParticipant.ts +64 -187
- package/src/room/participant/Participant.ts +1 -1
- package/src/room/participant/RemoteParticipant.ts +9 -0
- package/src/room/participant/publishUtils.ts +1 -1
- package/src/room/rpc/client/RpcClientManager.test.ts +430 -0
- package/src/room/rpc/client/RpcClientManager.ts +269 -0
- package/src/room/rpc/client/events.ts +9 -0
- package/src/room/rpc/index.ts +14 -0
- package/src/room/rpc/server/RpcServerManager.test.ts +471 -0
- package/src/room/rpc/server/RpcServerManager.ts +293 -0
- package/src/room/rpc/server/events.ts +9 -0
- package/src/room/{rpc.ts → rpc/utils.ts} +49 -8
- package/src/room/track/PacketTrailerExtractor.ts +43 -0
- package/src/room/track/RemoteVideoTrack.ts +23 -2
- package/src/room/track/Track.ts +1 -1
- package/src/room/track/create.ts +0 -4
- package/src/room/track/options.ts +11 -0
- package/src/room/track/record.ts +1 -1
- package/src/room/track/utils.ts +4 -1
- package/src/room/utils.test.ts +14 -1
- package/src/room/utils.ts +19 -4
- package/src/test/MockMediaStreamTrack.ts +0 -1
- package/src/type-polyfills/non-shared-typed-arrays.d.ts +6 -0
- package/src/utils/dataPacketBuffer.ts +1 -1
- package/src/version.ts +11 -1
- package/dist/src/room/rpc.d.ts.map +0 -1
- package/src/room/rpc.test.ts +0 -301
package/src/e2ee/types.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { LogLevel } from '../logger';
|
|
2
|
+
import type { PacketTrailerFramePayload } from '../packetTrailer/packetTrailer';
|
|
3
|
+
import type { PacketTrailerPublishOptions } from '../packetTrailer/types';
|
|
2
4
|
import type { VideoCodec } from '../room/track/options';
|
|
3
5
|
import type { BaseE2EEManager } from './E2eeManager';
|
|
4
6
|
import type { BaseKeyProvider } from './KeyProvider';
|
|
@@ -38,7 +40,7 @@ export interface RTPVideoMapMessage extends BaseMessage {
|
|
|
38
40
|
export interface SifTrailerMessage extends BaseMessage {
|
|
39
41
|
kind: 'setSifTrailer';
|
|
40
42
|
data: {
|
|
41
|
-
trailer:
|
|
43
|
+
trailer: NonSharedUint8Array;
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -51,6 +53,16 @@ export interface EncodeMessage extends BaseMessage {
|
|
|
51
53
|
trackId: string;
|
|
52
54
|
codec?: VideoCodec;
|
|
53
55
|
isReuse: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Whether the published track advertises packet trailer features.
|
|
58
|
+
* When false, the cryptor skips the per-frame trailer extraction path
|
|
59
|
+
* entirely on decode.
|
|
60
|
+
*/
|
|
61
|
+
hasPacketTrailer: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Packet trailer metadata to append on published video frames.
|
|
64
|
+
*/
|
|
65
|
+
packetTrailer?: PacketTrailerPublishOptions;
|
|
54
66
|
};
|
|
55
67
|
}
|
|
56
68
|
|
|
@@ -68,6 +80,7 @@ export interface UpdateCodecMessage extends BaseMessage {
|
|
|
68
80
|
participantIdentity: string;
|
|
69
81
|
trackId: string;
|
|
70
82
|
codec: VideoCodec;
|
|
83
|
+
hasPacketTrailer: boolean;
|
|
71
84
|
};
|
|
72
85
|
}
|
|
73
86
|
|
|
@@ -116,8 +129,8 @@ export interface DecryptDataRequestMessage extends BaseMessage {
|
|
|
116
129
|
kind: 'decryptDataRequest';
|
|
117
130
|
data: {
|
|
118
131
|
uuid: string;
|
|
119
|
-
payload:
|
|
120
|
-
iv:
|
|
132
|
+
payload: NonSharedUint8Array;
|
|
133
|
+
iv: NonSharedUint8Array;
|
|
121
134
|
participantIdentity: string;
|
|
122
135
|
keyIndex: number;
|
|
123
136
|
};
|
|
@@ -127,7 +140,7 @@ export interface DecryptDataResponseMessage extends BaseMessage {
|
|
|
127
140
|
kind: 'decryptDataResponse';
|
|
128
141
|
data: {
|
|
129
142
|
uuid: string;
|
|
130
|
-
payload:
|
|
143
|
+
payload: NonSharedUint8Array;
|
|
131
144
|
};
|
|
132
145
|
}
|
|
133
146
|
|
|
@@ -135,7 +148,7 @@ export interface EncryptDataRequestMessage extends BaseMessage {
|
|
|
135
148
|
kind: 'encryptDataRequest';
|
|
136
149
|
data: {
|
|
137
150
|
uuid: string;
|
|
138
|
-
payload:
|
|
151
|
+
payload: NonSharedUint8Array;
|
|
139
152
|
participantIdentity: string;
|
|
140
153
|
};
|
|
141
154
|
}
|
|
@@ -144,12 +157,17 @@ export interface EncryptDataResponseMessage extends BaseMessage {
|
|
|
144
157
|
kind: 'encryptDataResponse';
|
|
145
158
|
data: {
|
|
146
159
|
uuid: string;
|
|
147
|
-
payload:
|
|
148
|
-
iv:
|
|
160
|
+
payload: NonSharedUint8Array;
|
|
161
|
+
iv: NonSharedUint8Array;
|
|
149
162
|
keyIndex: number;
|
|
150
163
|
};
|
|
151
164
|
}
|
|
152
165
|
|
|
166
|
+
export interface PTMetadataFromE2EEMessage extends BaseMessage {
|
|
167
|
+
kind: 'packetTrailerMetadata';
|
|
168
|
+
data: PacketTrailerFramePayload;
|
|
169
|
+
}
|
|
170
|
+
|
|
153
171
|
export type E2EEWorkerMessage =
|
|
154
172
|
| InitMessage
|
|
155
173
|
| SetKeyMessage
|
|
@@ -166,7 +184,8 @@ export type E2EEWorkerMessage =
|
|
|
166
184
|
| DecryptDataRequestMessage
|
|
167
185
|
| DecryptDataResponseMessage
|
|
168
186
|
| EncryptDataRequestMessage
|
|
169
|
-
| EncryptDataResponseMessage
|
|
187
|
+
| EncryptDataResponseMessage
|
|
188
|
+
| PTMetadataFromE2EEMessage;
|
|
170
189
|
|
|
171
190
|
export type KeySet = { material: CryptoKey; encryptionKey: CryptoKey };
|
|
172
191
|
|
|
@@ -221,4 +240,14 @@ export type ScriptTransformOptions = {
|
|
|
221
240
|
participantIdentity: string;
|
|
222
241
|
trackId: string;
|
|
223
242
|
codec?: VideoCodec;
|
|
243
|
+
/**
|
|
244
|
+
* Whether the published track advertises packet trailer features.
|
|
245
|
+
* When false, the cryptor skips the per-frame trailer extraction path
|
|
246
|
+
* entirely on decode.
|
|
247
|
+
*/
|
|
248
|
+
hasPacketTrailer: boolean;
|
|
249
|
+
/**
|
|
250
|
+
* Packet trailer metadata to append on published video frames.
|
|
251
|
+
*/
|
|
252
|
+
packetTrailer?: PacketTrailerPublishOptions;
|
|
224
253
|
};
|
package/src/e2ee/utils.ts
CHANGED
|
@@ -8,11 +8,12 @@ export function isE2EESupported() {
|
|
|
8
8
|
|
|
9
9
|
export function isScriptTransformSupported() {
|
|
10
10
|
// @ts-ignore
|
|
11
|
-
return typeof window.RTCRtpScriptTransform !== 'undefined';
|
|
11
|
+
return typeof window !== 'undefined' && typeof window.RTCRtpScriptTransform !== 'undefined';
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function isInsertableStreamSupported() {
|
|
15
15
|
return (
|
|
16
|
+
typeof window !== 'undefined' &&
|
|
16
17
|
typeof window.RTCRtpSender !== 'undefined' &&
|
|
17
18
|
// @ts-ignore
|
|
18
19
|
typeof window.RTCRtpSender.prototype.createEncodedStreams !== 'undefined'
|
|
@@ -26,7 +27,7 @@ export function isVideoFrame(
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export async function importKey(
|
|
29
|
-
keyBytes:
|
|
30
|
+
keyBytes: NonSharedUint8Array | ArrayBuffer,
|
|
30
31
|
algorithm: string | { name: string } = { name: ENCRYPTION_ALGORITHM },
|
|
31
32
|
usage: 'derive' | 'encrypt' = 'encrypt',
|
|
32
33
|
) {
|
|
@@ -112,7 +113,7 @@ export async function deriveKeys(material: CryptoKey, options: KeyProviderOption
|
|
|
112
113
|
return { material, encryptionKey };
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
export function createE2EEKey():
|
|
116
|
+
export function createE2EEKey(): NonSharedUint8Array {
|
|
116
117
|
return window.crypto.getRandomValues(new Uint8Array(32));
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -127,14 +128,14 @@ export async function ratchet(material: CryptoKey, salt: string): Promise<ArrayB
|
|
|
127
128
|
return crypto.subtle.deriveBits(algorithmOptions, material, 256);
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
export function needsRbspUnescaping(frameData:
|
|
131
|
+
export function needsRbspUnescaping(frameData: NonSharedUint8Array) {
|
|
131
132
|
for (var i = 0; i < frameData.length - 3; i++) {
|
|
132
133
|
if (frameData[i] == 0 && frameData[i + 1] == 0 && frameData[i + 2] == 3) return true;
|
|
133
134
|
}
|
|
134
135
|
return false;
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
export function parseRbsp(stream:
|
|
138
|
+
export function parseRbsp(stream: NonSharedUint8Array): NonSharedUint8Array {
|
|
138
139
|
const dataOut: number[] = [];
|
|
139
140
|
var length = stream.length;
|
|
140
141
|
for (var i = 0; i < stream.length; ) {
|
|
@@ -159,7 +160,7 @@ export function parseRbsp(stream: Uint8Array): Uint8Array {
|
|
|
159
160
|
const kZerosInStartSequence = 2;
|
|
160
161
|
const kEmulationByte = 3;
|
|
161
162
|
|
|
162
|
-
export function writeRbsp(data_in:
|
|
163
|
+
export function writeRbsp(data_in: NonSharedUint8Array): NonSharedUint8Array {
|
|
163
164
|
const dataOut: number[] = [];
|
|
164
165
|
var numConsecutiveZeros = 0;
|
|
165
166
|
for (var i = 0; i < data_in.length; ++i) {
|
|
@@ -21,11 +21,11 @@ export class DataCryptor {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
static async encrypt(
|
|
24
|
-
data:
|
|
24
|
+
data: NonSharedUint8Array,
|
|
25
25
|
keys: ParticipantKeyHandler,
|
|
26
26
|
): Promise<{
|
|
27
|
-
payload:
|
|
28
|
-
iv:
|
|
27
|
+
payload: NonSharedUint8Array;
|
|
28
|
+
iv: NonSharedUint8Array;
|
|
29
29
|
keyIndex: number;
|
|
30
30
|
}> {
|
|
31
31
|
const iv = DataCryptor.makeIV(performance.now());
|
|
@@ -51,14 +51,14 @@ export class DataCryptor {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
static async decrypt(
|
|
54
|
-
data:
|
|
55
|
-
iv:
|
|
54
|
+
data: NonSharedUint8Array,
|
|
55
|
+
iv: NonSharedUint8Array,
|
|
56
56
|
keys: ParticipantKeyHandler,
|
|
57
57
|
keyIndex: number = 0,
|
|
58
58
|
initialMaterial?: KeySet,
|
|
59
59
|
ratchetOpts: DecodeRatchetOptions = { ratchetCount: 0 },
|
|
60
60
|
): Promise<{
|
|
61
|
-
payload:
|
|
61
|
+
payload: NonSharedUint8Array;
|
|
62
62
|
}> {
|
|
63
63
|
const keySet = await keys.getKeySet(keyIndex);
|
|
64
64
|
if (!keySet) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vitest } from 'vitest';
|
|
2
|
+
import { appendPacketTrailer, extractPacketTrailer } from '../../packetTrailer/packetTrailer';
|
|
3
|
+
import type { PacketTrailerPublishOptions } from '../../packetTrailer/types';
|
|
2
4
|
import { IV_LENGTH, KEY_PROVIDER_DEFAULTS } from '../constants';
|
|
3
5
|
import { CryptorEvent } from '../events';
|
|
4
6
|
import type { KeyProviderOptions } from '../types';
|
|
@@ -13,7 +15,7 @@ function mockEncryptedRTCEncodedVideoFrame(keyIndex: number): RTCEncodedVideoFra
|
|
|
13
15
|
return mockRTCEncodedVideoFrame(data);
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
function mockRTCEncodedVideoFrame(data:
|
|
18
|
+
function mockRTCEncodedVideoFrame(data: NonSharedUint8Array): RTCEncodedVideoFrame {
|
|
17
19
|
return {
|
|
18
20
|
data: data.buffer,
|
|
19
21
|
timestamp: vitest.getMockedSystemTime()?.getTime() ?? 0,
|
|
@@ -24,7 +26,7 @@ function mockRTCEncodedVideoFrame(data: Uint8Array): RTCEncodedVideoFrame {
|
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
function mockFrameTrailer(keyIndex: number):
|
|
29
|
+
function mockFrameTrailer(keyIndex: number): NonSharedUint8Array {
|
|
28
30
|
const frameTrailer = new Uint8Array(2);
|
|
29
31
|
|
|
30
32
|
frameTrailer[0] = IV_LENGTH;
|
|
@@ -67,14 +69,21 @@ function prepareParticipantTestDecoder(
|
|
|
67
69
|
function prepareParticipantTestEncoder(
|
|
68
70
|
participantIdentity: string,
|
|
69
71
|
partialKeyProviderOptions: Partial<KeyProviderOptions>,
|
|
72
|
+
packetTrailer?: PacketTrailerPublishOptions,
|
|
70
73
|
) {
|
|
71
|
-
return prepareParticipantTest(
|
|
74
|
+
return prepareParticipantTest(
|
|
75
|
+
'encode',
|
|
76
|
+
participantIdentity,
|
|
77
|
+
partialKeyProviderOptions,
|
|
78
|
+
packetTrailer,
|
|
79
|
+
);
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
function prepareParticipantTest(
|
|
75
83
|
mode: 'encode' | 'decode',
|
|
76
84
|
participantIdentity: string,
|
|
77
85
|
partialKeyProviderOptions: Partial<KeyProviderOptions>,
|
|
86
|
+
packetTrailer?: PacketTrailerPublishOptions,
|
|
78
87
|
): {
|
|
79
88
|
keys: ParticipantKeyHandler;
|
|
80
89
|
cryptor: FrameCryptor;
|
|
@@ -95,7 +104,15 @@ function prepareParticipantTest(
|
|
|
95
104
|
|
|
96
105
|
const input = new TestUnderlyingSource<RTCEncodedVideoFrame>();
|
|
97
106
|
const output = new TestUnderlyingSink<RTCEncodedVideoFrame>();
|
|
98
|
-
cryptor.setupTransform(
|
|
107
|
+
cryptor.setupTransform(
|
|
108
|
+
mode,
|
|
109
|
+
new ReadableStream(input),
|
|
110
|
+
new WritableStream(output),
|
|
111
|
+
'testTrack',
|
|
112
|
+
false,
|
|
113
|
+
undefined,
|
|
114
|
+
packetTrailer,
|
|
115
|
+
);
|
|
99
116
|
|
|
100
117
|
return { keys, cryptor, input, output };
|
|
101
118
|
}
|
|
@@ -215,6 +232,127 @@ describe('FrameCryptor', () => {
|
|
|
215
232
|
vitest.useRealTimers();
|
|
216
233
|
}
|
|
217
234
|
});
|
|
235
|
+
|
|
236
|
+
it('appends packet trailer after encryption', async () => {
|
|
237
|
+
vitest.useFakeTimers();
|
|
238
|
+
const now = new Date('2025-04-10T12:00:00.123Z');
|
|
239
|
+
vitest.setSystemTime(now);
|
|
240
|
+
try {
|
|
241
|
+
const { keys, input, output } = prepareParticipantTestEncoder(
|
|
242
|
+
participantIdentity,
|
|
243
|
+
{},
|
|
244
|
+
{ timestamp: true, frameId: true },
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
|
248
|
+
|
|
249
|
+
const frame = mockRTCEncodedVideoFrame(
|
|
250
|
+
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
input.write(frame);
|
|
254
|
+
await vitest.waitFor(() => expect(output.chunks).toHaveLength(1));
|
|
255
|
+
|
|
256
|
+
const extracted = extractPacketTrailer(frame.data);
|
|
257
|
+
expect(extracted.metadata?.userTimestamp).toBeGreaterThanOrEqual(
|
|
258
|
+
BigInt(now.getTime()) * BigInt(1000),
|
|
259
|
+
);
|
|
260
|
+
expect(extracted.metadata?.frameId).toBe(1);
|
|
261
|
+
|
|
262
|
+
const frameTrailer = new Uint8Array(
|
|
263
|
+
extracted.data.buffer.slice(extracted.data.byteLength - 2),
|
|
264
|
+
);
|
|
265
|
+
expect(frameTrailer[0]).toEqual(IV_LENGTH);
|
|
266
|
+
expect(frameTrailer[1]).toEqual(1);
|
|
267
|
+
} finally {
|
|
268
|
+
vitest.useRealTimers();
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('does not append packet trailer after encryption when no trailer features are enabled', async () => {
|
|
273
|
+
vitest.useFakeTimers();
|
|
274
|
+
try {
|
|
275
|
+
const { keys, input, output } = prepareParticipantTestEncoder(
|
|
276
|
+
participantIdentity,
|
|
277
|
+
{},
|
|
278
|
+
{ timestamp: false, frameId: false },
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
|
282
|
+
|
|
283
|
+
const frame = mockRTCEncodedVideoFrame(
|
|
284
|
+
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
input.write(frame);
|
|
288
|
+
await vitest.waitFor(() => expect(output.chunks).toHaveLength(1));
|
|
289
|
+
|
|
290
|
+
const extracted = extractPacketTrailer(frame.data);
|
|
291
|
+
expect(extracted.metadata).toBeUndefined();
|
|
292
|
+
expect(extracted.data.byteLength).toBe(frame.data.byteLength);
|
|
293
|
+
|
|
294
|
+
const frameTrailer = new Uint8Array(frame.data.slice(frame.data.byteLength - 2));
|
|
295
|
+
expect(frameTrailer[0]).toEqual(IV_LENGTH);
|
|
296
|
+
expect(frameTrailer[1]).toEqual(1);
|
|
297
|
+
} finally {
|
|
298
|
+
vitest.useRealTimers();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('appends only timestamp packet trailer metadata after encryption', async () => {
|
|
303
|
+
vitest.useFakeTimers();
|
|
304
|
+
const now = new Date('2025-04-10T12:00:00.123Z');
|
|
305
|
+
vitest.setSystemTime(now);
|
|
306
|
+
try {
|
|
307
|
+
const { keys, input, output } = prepareParticipantTestEncoder(
|
|
308
|
+
participantIdentity,
|
|
309
|
+
{},
|
|
310
|
+
{ timestamp: true },
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
|
314
|
+
|
|
315
|
+
const frame = mockRTCEncodedVideoFrame(
|
|
316
|
+
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
input.write(frame);
|
|
320
|
+
await vitest.waitFor(() => expect(output.chunks).toHaveLength(1));
|
|
321
|
+
|
|
322
|
+
const extracted = extractPacketTrailer(frame.data);
|
|
323
|
+
expect(frame.data.byteLength - extracted.data.byteLength).toBe(15);
|
|
324
|
+
expect(extracted.metadata?.userTimestamp).toBeGreaterThanOrEqual(
|
|
325
|
+
BigInt(now.getTime()) * BigInt(1000),
|
|
326
|
+
);
|
|
327
|
+
expect(extracted.metadata?.frameId).toBe(0);
|
|
328
|
+
} finally {
|
|
329
|
+
vitest.useRealTimers();
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('appends only frame id packet trailer metadata after encryption', async () => {
|
|
334
|
+
const { keys, input, output } = prepareParticipantTestEncoder(
|
|
335
|
+
participantIdentity,
|
|
336
|
+
{},
|
|
337
|
+
{ frameId: true },
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
|
341
|
+
|
|
342
|
+
const frame = mockRTCEncodedVideoFrame(
|
|
343
|
+
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
input.write(frame);
|
|
347
|
+
await vitest.waitFor(() => expect(output.chunks).toHaveLength(1));
|
|
348
|
+
|
|
349
|
+
const extracted = extractPacketTrailer(frame.data);
|
|
350
|
+
expect(frame.data.byteLength - extracted.data.byteLength).toBe(11);
|
|
351
|
+
expect(extracted.metadata).toEqual({
|
|
352
|
+
userTimestamp: BigInt(0),
|
|
353
|
+
frameId: 1,
|
|
354
|
+
});
|
|
355
|
+
});
|
|
218
356
|
});
|
|
219
357
|
|
|
220
358
|
describe('decode', () => {
|
|
@@ -391,6 +529,41 @@ describe('FrameCryptor', () => {
|
|
|
391
529
|
}
|
|
392
530
|
});
|
|
393
531
|
|
|
532
|
+
it('posts packet trailer metadata when the advertised trailer path is active', async () => {
|
|
533
|
+
vitest.useFakeTimers();
|
|
534
|
+
try {
|
|
535
|
+
const { cryptor, input, output } = prepareParticipantTestDecoder(participantIdentity, {});
|
|
536
|
+
const postMessage = vitest.fn();
|
|
537
|
+
vitest.stubGlobal('postMessage', postMessage);
|
|
538
|
+
encryptionEnabledMap.set(participantIdentity, false);
|
|
539
|
+
cryptor.setHasPacketTrailer(true);
|
|
540
|
+
|
|
541
|
+
const payload = Uint8Array.from([1, 2, 3, 4]);
|
|
542
|
+
const trailer = appendPacketTrailer(payload, 1_744_249_600_123_456n, 0);
|
|
543
|
+
const frame = mockRTCEncodedVideoFrame(trailer);
|
|
544
|
+
|
|
545
|
+
input.write(frame);
|
|
546
|
+
await vitest.advanceTimersToNextTimerAsync();
|
|
547
|
+
|
|
548
|
+
expect(new Uint8Array(output.chunks[0].data)).toEqual(payload);
|
|
549
|
+
expect(postMessage).toHaveBeenCalledWith({
|
|
550
|
+
kind: 'packetTrailerMetadata',
|
|
551
|
+
data: {
|
|
552
|
+
trackId: 'testTrack',
|
|
553
|
+
rtpTimestamp: frame.timestamp,
|
|
554
|
+
ssrc: 0,
|
|
555
|
+
metadata: {
|
|
556
|
+
userTimestamp: 1_744_249_600_123_456n,
|
|
557
|
+
frameId: 0,
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
} finally {
|
|
562
|
+
vitest.unstubAllGlobals();
|
|
563
|
+
vitest.useRealTimers();
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
394
567
|
it('recovers from delayed use of rotated key', async () => {
|
|
395
568
|
vitest.useFakeTimers();
|
|
396
569
|
try {
|
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
1
|
// TODO code inspired by https://github.com/webrtc/samples/blob/gh-pages/src/content/insertable-streams/endtoend-encryption/js/worker.js
|
|
3
2
|
import { EventEmitter } from 'events';
|
|
4
3
|
import type TypedEventEmitter from 'typed-emitter';
|
|
5
4
|
import { workerLogger } from '../../logger';
|
|
5
|
+
import {
|
|
6
|
+
appendPacketTrailerToEncodedFrame,
|
|
7
|
+
processPacketTrailer,
|
|
8
|
+
} from '../../packetTrailer/packetTrailer';
|
|
9
|
+
import type { PacketTrailerPublishOptions } from '../../packetTrailer/types';
|
|
10
|
+
import { hasPacketTrailerPublishOptions } from '../../packetTrailer/utils';
|
|
6
11
|
import type { VideoCodec } from '../../room/track/options';
|
|
7
12
|
import { ENCRYPTION_ALGORITHM, IV_LENGTH, UNENCRYPTED_BYTES } from '../constants';
|
|
8
13
|
import { CryptorError, CryptorErrorReason } from '../errors';
|
|
9
14
|
import { type CryptorCallbacks, CryptorEvent } from '../events';
|
|
10
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
DecodeRatchetOptions,
|
|
17
|
+
KeyProviderOptions,
|
|
18
|
+
KeySet,
|
|
19
|
+
PTMetadataFromE2EEMessage,
|
|
20
|
+
RatchetResult,
|
|
21
|
+
} from '../types';
|
|
11
22
|
import { deriveKeys, isVideoFrame, needsRbspUnescaping, parseRbsp, writeRbsp } from '../utils';
|
|
12
23
|
import type { ParticipantKeyHandler } from './ParticipantKeyHandler';
|
|
13
24
|
import { processNALUsForEncryption } from './naluUtils';
|
|
@@ -65,12 +76,23 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
65
76
|
/**
|
|
66
77
|
* used for detecting server injected unencrypted frames
|
|
67
78
|
*/
|
|
68
|
-
private sifTrailer:
|
|
79
|
+
private sifTrailer: NonSharedUint8Array;
|
|
69
80
|
|
|
70
81
|
private detectedCodec?: VideoCodec;
|
|
71
82
|
|
|
72
83
|
private currentTransform?: TransformerInfo;
|
|
73
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Whether the subscribed track advertises packet trailer features.
|
|
87
|
+
* When false, we skip the per-frame trailer extraction path entirely
|
|
88
|
+
* on decode to avoid unnecessary work on tracks that don't use it.
|
|
89
|
+
*/
|
|
90
|
+
private hasPacketTrailer: boolean = false;
|
|
91
|
+
|
|
92
|
+
private packetTrailer?: PacketTrailerPublishOptions;
|
|
93
|
+
|
|
94
|
+
private packetTrailerFrameId = 0;
|
|
95
|
+
|
|
74
96
|
/**
|
|
75
97
|
* Throttling mechanism for decryption errors to prevent memory leaks
|
|
76
98
|
*/
|
|
@@ -88,7 +110,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
88
110
|
keys: ParticipantKeyHandler;
|
|
89
111
|
participantIdentity: string;
|
|
90
112
|
keyProviderOptions: KeyProviderOptions;
|
|
91
|
-
sifTrailer?:
|
|
113
|
+
sifTrailer?: NonSharedUint8Array;
|
|
92
114
|
}) {
|
|
93
115
|
super();
|
|
94
116
|
this.sendCounts = new Map();
|
|
@@ -178,6 +200,20 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
178
200
|
this.rtpMap = map;
|
|
179
201
|
}
|
|
180
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Sets whether the track associated with this cryptor carries packet
|
|
205
|
+
* trailer data. When false, {@link decodeFunction} skips the per-frame
|
|
206
|
+
* trailer extraction branch entirely.
|
|
207
|
+
*/
|
|
208
|
+
setHasPacketTrailer(hasPacketTrailer: boolean) {
|
|
209
|
+
this.hasPacketTrailer = hasPacketTrailer;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
setPacketTrailer(packetTrailer?: PacketTrailerPublishOptions) {
|
|
213
|
+
this.packetTrailer = packetTrailer;
|
|
214
|
+
this.packetTrailerFrameId = 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
181
217
|
setupTransform(
|
|
182
218
|
operation: 'encode' | 'decode',
|
|
183
219
|
readable: ReadableStream<RTCEncodedVideoFrame | RTCEncodedAudioFrame>,
|
|
@@ -185,11 +221,15 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
185
221
|
trackId: string,
|
|
186
222
|
isReuse: boolean,
|
|
187
223
|
codec?: VideoCodec,
|
|
224
|
+
packetTrailer?: PacketTrailerPublishOptions,
|
|
188
225
|
) {
|
|
189
226
|
if (codec) {
|
|
190
227
|
workerLogger.info('setting codec on cryptor to', { codec });
|
|
191
228
|
this.videoCodec = codec;
|
|
192
229
|
}
|
|
230
|
+
if (operation === 'encode') {
|
|
231
|
+
this.setPacketTrailer(packetTrailer);
|
|
232
|
+
}
|
|
193
233
|
|
|
194
234
|
workerLogger.debug('Setting up frame cryptor transform', {
|
|
195
235
|
operation,
|
|
@@ -262,7 +302,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
262
302
|
});
|
|
263
303
|
}
|
|
264
304
|
|
|
265
|
-
setSifTrailer(trailer:
|
|
305
|
+
setSifTrailer(trailer: NonSharedUint8Array) {
|
|
266
306
|
workerLogger.debug('setting SIF trailer', { ...this.logContext, trailer });
|
|
267
307
|
this.sifTrailer = trailer;
|
|
268
308
|
}
|
|
@@ -353,11 +393,13 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
353
393
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
|
354
394
|
controller: TransformStreamDefaultController,
|
|
355
395
|
) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
396
|
+
// skip for encryption and packet trailer writes for empty dtx frames
|
|
397
|
+
if (encodedFrame.data.byteLength === 0) {
|
|
398
|
+
return controller.enqueue(encodedFrame);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (!this.isEnabled()) {
|
|
402
|
+
this.appendPacketTrailer(encodedFrame);
|
|
361
403
|
return controller.enqueue(encodedFrame);
|
|
362
404
|
}
|
|
363
405
|
const keySet = this.keys.getKeySet();
|
|
@@ -410,7 +452,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
410
452
|
new Uint8Array(encodedFrame.data, frameInfo.unencryptedBytes),
|
|
411
453
|
);
|
|
412
454
|
|
|
413
|
-
let newDataWithoutHeader = new Uint8Array(
|
|
455
|
+
let newDataWithoutHeader: NonSharedUint8Array = new Uint8Array(
|
|
414
456
|
cipherText.byteLength + iv.byteLength + frameTrailer.byteLength,
|
|
415
457
|
);
|
|
416
458
|
newDataWithoutHeader.set(new Uint8Array(cipherText)); // add ciphertext.
|
|
@@ -426,6 +468,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
426
468
|
newData.set(newDataWithoutHeader, frameHeader.byteLength);
|
|
427
469
|
|
|
428
470
|
encodedFrame.data = newData.buffer;
|
|
471
|
+
this.appendPacketTrailer(encodedFrame);
|
|
429
472
|
|
|
430
473
|
return controller.enqueue(encodedFrame);
|
|
431
474
|
} catch (e: any) {
|
|
@@ -444,6 +487,18 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
444
487
|
}
|
|
445
488
|
}
|
|
446
489
|
|
|
490
|
+
private appendPacketTrailer(encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame) {
|
|
491
|
+
if (!hasPacketTrailerPublishOptions(this.packetTrailer) || !isVideoFrame(encodedFrame)) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (this.packetTrailer?.frameId) {
|
|
496
|
+
this.packetTrailerFrameId =
|
|
497
|
+
this.packetTrailerFrameId === 0xffffffff ? 1 : this.packetTrailerFrameId + 1;
|
|
498
|
+
}
|
|
499
|
+
appendPacketTrailerToEncodedFrame(encodedFrame, this.packetTrailer, this.packetTrailerFrameId);
|
|
500
|
+
}
|
|
501
|
+
|
|
447
502
|
/**
|
|
448
503
|
* Function that will be injected in a stream and will decrypt the given encoded frames.
|
|
449
504
|
*
|
|
@@ -454,6 +509,24 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
454
509
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
|
455
510
|
controller: TransformStreamDefaultController,
|
|
456
511
|
) {
|
|
512
|
+
if (this.hasPacketTrailer && isVideoFrame(encodedFrame)) {
|
|
513
|
+
try {
|
|
514
|
+
const ptResult = processPacketTrailer(encodedFrame, this.trackId);
|
|
515
|
+
if (ptResult.data) {
|
|
516
|
+
encodedFrame.data = ptResult.data;
|
|
517
|
+
}
|
|
518
|
+
if (ptResult.payload && this.participantIdentity) {
|
|
519
|
+
const msg: PTMetadataFromE2EEMessage = {
|
|
520
|
+
kind: 'packetTrailerMetadata',
|
|
521
|
+
data: ptResult.payload,
|
|
522
|
+
};
|
|
523
|
+
postMessage(msg);
|
|
524
|
+
}
|
|
525
|
+
} catch {
|
|
526
|
+
// best-effort: never break the media pipeline if trailer parsing fails
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
457
530
|
if (
|
|
458
531
|
!this.isEnabled() ||
|
|
459
532
|
// skip for decryption for empty dtx frames
|
|
@@ -540,8 +613,12 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
540
613
|
// ---------+-------------------------+-+---------+----
|
|
541
614
|
|
|
542
615
|
try {
|
|
543
|
-
const frameHeader = new Uint8Array(
|
|
544
|
-
|
|
616
|
+
const frameHeader: NonSharedUint8Array = new Uint8Array(
|
|
617
|
+
encodedFrame.data,
|
|
618
|
+
0,
|
|
619
|
+
frameInfo.unencryptedBytes,
|
|
620
|
+
);
|
|
621
|
+
var encryptedData: NonSharedUint8Array = new Uint8Array(
|
|
545
622
|
encodedFrame.data,
|
|
546
623
|
frameHeader.length,
|
|
547
624
|
encodedFrame.data.byteLength - frameHeader.length,
|
|
@@ -758,7 +835,10 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
758
835
|
* by the livekit server and thus to be treated as unencrypted
|
|
759
836
|
* @internal
|
|
760
837
|
*/
|
|
761
|
-
export function isFrameServerInjected(
|
|
838
|
+
export function isFrameServerInjected(
|
|
839
|
+
frameData: ArrayBuffer,
|
|
840
|
+
trailerBytes: NonSharedUint8Array,
|
|
841
|
+
): boolean {
|
|
762
842
|
if (trailerBytes.byteLength === 0) {
|
|
763
843
|
return false;
|
|
764
844
|
}
|
|
@@ -261,7 +261,7 @@ describe('ParticipantKeyHandler', () => {
|
|
|
261
261
|
|
|
262
262
|
await keyHandler.setKey(originalMaterial);
|
|
263
263
|
|
|
264
|
-
const ciphertexts:
|
|
264
|
+
const ciphertexts: NonSharedUint8Array[] = [];
|
|
265
265
|
|
|
266
266
|
const plaintext = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
|
|
267
267
|
|
|
@@ -354,8 +354,8 @@ describe('ParticipantKeyHandler', () => {
|
|
|
354
354
|
async function encrypt(
|
|
355
355
|
participantKeyHandler: ParticipantKeyHandler,
|
|
356
356
|
keyIndex: number,
|
|
357
|
-
iv:
|
|
358
|
-
data:
|
|
357
|
+
iv: NonSharedUint8Array,
|
|
358
|
+
data: NonSharedUint8Array,
|
|
359
359
|
): Promise<ArrayBuffer> {
|
|
360
360
|
return crypto.subtle.encrypt(
|
|
361
361
|
{
|
|
@@ -370,7 +370,7 @@ describe('ParticipantKeyHandler', () => {
|
|
|
370
370
|
async function decrypt(
|
|
371
371
|
participantKeyHandler: ParticipantKeyHandler,
|
|
372
372
|
keyIndex: number,
|
|
373
|
-
iv:
|
|
373
|
+
iv: NonSharedUint8Array,
|
|
374
374
|
cipherText: ArrayBuffer,
|
|
375
375
|
): Promise<ArrayBuffer> {
|
|
376
376
|
return crypto.subtle.decrypt(
|