livekit-client 1.13.3 → 1.13.4
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/README.md +2 -2
- 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 +86 -16
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +87 -8
- 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/e2ee/utils.d.ts +3 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +7 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrack.d.ts +7 -0
- package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
- package/dist/ts4.2/src/e2ee/utils.d.ts +3 -0
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +7 -0
- package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +7 -0
- package/package.json +1 -1
- package/src/e2ee/utils.ts +52 -0
- package/src/e2ee/worker/FrameCryptor.ts +45 -27
- package/src/room/RTCEngine.ts +3 -2
- package/src/room/Room.ts +3 -3
- package/src/room/participant/LocalParticipant.ts +2 -2
- package/src/room/track/LocalTrack.ts +14 -0
- package/src/room/track/RemoteTrack.ts +14 -0
@@ -882,6 +882,54 @@ function ratchet(material, salt) {
|
|
882
882
|
return crypto.subtle.deriveBits(algorithmOptions, material, 256);
|
883
883
|
});
|
884
884
|
}
|
885
|
+
function needsRbspUnescaping(frameData) {
|
886
|
+
for (var i = 0; i < frameData.length - 3; i++) {
|
887
|
+
if (frameData[i] == 0 && frameData[i + 1] == 0 && frameData[i + 2] == 3) return true;
|
888
|
+
}
|
889
|
+
return false;
|
890
|
+
}
|
891
|
+
function parseRbsp(stream) {
|
892
|
+
const dataOut = [];
|
893
|
+
var length = stream.length;
|
894
|
+
for (var i = 0; i < stream.length;) {
|
895
|
+
// Be careful about over/underflow here. byte_length_ - 3 can underflow, and
|
896
|
+
// i + 3 can overflow, but byte_length_ - i can't, because i < byte_length_
|
897
|
+
// above, and that expression will produce the number of bytes left in
|
898
|
+
// the stream including the byte at i.
|
899
|
+
if (length - i >= 3 && !stream[i] && !stream[i + 1] && stream[i + 2] == 3) {
|
900
|
+
// Two rbsp bytes.
|
901
|
+
dataOut.push(stream[i++]);
|
902
|
+
dataOut.push(stream[i++]);
|
903
|
+
// Skip the emulation byte.
|
904
|
+
i++;
|
905
|
+
} else {
|
906
|
+
// Single rbsp byte.
|
907
|
+
dataOut.push(stream[i++]);
|
908
|
+
}
|
909
|
+
}
|
910
|
+
return new Uint8Array(dataOut);
|
911
|
+
}
|
912
|
+
const kZerosInStartSequence = 2;
|
913
|
+
const kEmulationByte = 3;
|
914
|
+
function writeRbsp(data_in) {
|
915
|
+
const dataOut = [];
|
916
|
+
var numConsecutiveZeros = 0;
|
917
|
+
for (var i = 0; i < data_in.length; ++i) {
|
918
|
+
var byte = data_in[i];
|
919
|
+
if (byte <= kEmulationByte && numConsecutiveZeros >= kZerosInStartSequence) {
|
920
|
+
// Need to escape.
|
921
|
+
dataOut.push(kEmulationByte);
|
922
|
+
numConsecutiveZeros = 0;
|
923
|
+
}
|
924
|
+
dataOut.push(byte);
|
925
|
+
if (byte == 0) {
|
926
|
+
++numConsecutiveZeros;
|
927
|
+
} else {
|
928
|
+
numConsecutiveZeros = 0;
|
929
|
+
}
|
930
|
+
}
|
931
|
+
return new Uint8Array(dataOut);
|
932
|
+
}
|
885
933
|
|
886
934
|
class SifGuard {
|
887
935
|
constructor() {
|
@@ -1045,8 +1093,9 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1045
1093
|
const keyIndex = this.keys.getCurrentKeyIndex();
|
1046
1094
|
if (encryptionKey) {
|
1047
1095
|
const iv = this.makeIV((_a = encodedFrame.getMetadata().synchronizationSource) !== null && _a !== void 0 ? _a : -1, encodedFrame.timestamp);
|
1096
|
+
let frameInfo = this.getUnencryptedBytes(encodedFrame);
|
1048
1097
|
// Thіs is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
|
1049
|
-
const frameHeader = new Uint8Array(encodedFrame.data, 0,
|
1098
|
+
const frameHeader = new Uint8Array(encodedFrame.data, 0, frameInfo.unencryptedBytes);
|
1050
1099
|
// Frame trailer contains the R|IV_LENGTH and key index
|
1051
1100
|
const frameTrailer = new Uint8Array(2);
|
1052
1101
|
frameTrailer[0] = IV_LENGTH;
|
@@ -1063,14 +1112,18 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1063
1112
|
name: ENCRYPTION_ALGORITHM,
|
1064
1113
|
iv,
|
1065
1114
|
additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
|
1066
|
-
}, encryptionKey, new Uint8Array(encodedFrame.data,
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1115
|
+
}, encryptionKey, new Uint8Array(encodedFrame.data, frameInfo.unencryptedBytes));
|
1116
|
+
let newDataWithoutHeader = new Uint8Array(cipherText.byteLength + iv.byteLength + frameTrailer.byteLength);
|
1117
|
+
newDataWithoutHeader.set(new Uint8Array(cipherText)); // add ciphertext.
|
1118
|
+
newDataWithoutHeader.set(new Uint8Array(iv), cipherText.byteLength); // append IV.
|
1119
|
+
newDataWithoutHeader.set(frameTrailer, cipherText.byteLength + iv.byteLength); // append frame trailer.
|
1120
|
+
if (frameInfo.isH264) {
|
1121
|
+
newDataWithoutHeader = writeRbsp(newDataWithoutHeader);
|
1122
|
+
}
|
1123
|
+
var newData = new Uint8Array(frameHeader.byteLength + newDataWithoutHeader.byteLength);
|
1124
|
+
newData.set(frameHeader);
|
1125
|
+
newData.set(newDataWithoutHeader, frameHeader.byteLength);
|
1126
|
+
encodedFrame.data = newData.buffer;
|
1074
1127
|
return controller.enqueue(encodedFrame);
|
1075
1128
|
} catch (e) {
|
1076
1129
|
// TODO: surface this to the app.
|
@@ -1150,6 +1203,7 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1150
1203
|
if (!ratchetOpts.encryptionKey && !keySet) {
|
1151
1204
|
throw new TypeError("no encryption key found for decryption of ".concat(this.participantIdentity));
|
1152
1205
|
}
|
1206
|
+
let frameInfo = this.getUnencryptedBytes(encodedFrame);
|
1153
1207
|
// Construct frame trailer. Similar to the frame header described in
|
1154
1208
|
// https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
|
1155
1209
|
// but we put it at the end.
|
@@ -1158,7 +1212,15 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1158
1212
|
// payload |IV...(length = IV_LENGTH)|R|IV_LENGTH|KID |
|
1159
1213
|
// ---------+-------------------------+-+---------+----
|
1160
1214
|
try {
|
1161
|
-
const frameHeader = new Uint8Array(encodedFrame.data, 0,
|
1215
|
+
const frameHeader = new Uint8Array(encodedFrame.data, 0, frameInfo.unencryptedBytes);
|
1216
|
+
var encryptedData = new Uint8Array(encodedFrame.data, frameHeader.length, encodedFrame.data.byteLength - frameHeader.length);
|
1217
|
+
if (frameInfo.isH264 && needsRbspUnescaping(encryptedData)) {
|
1218
|
+
encryptedData = parseRbsp(encryptedData);
|
1219
|
+
const newUint8 = new Uint8Array(frameHeader.byteLength + encryptedData.byteLength);
|
1220
|
+
newUint8.set(frameHeader);
|
1221
|
+
newUint8.set(encryptedData, frameHeader.byteLength);
|
1222
|
+
encodedFrame.data = newUint8.buffer;
|
1223
|
+
}
|
1162
1224
|
const frameTrailer = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - 2, 2);
|
1163
1225
|
const ivLength = frameTrailer[0];
|
1164
1226
|
const iv = new Uint8Array(encodedFrame.data, encodedFrame.data.byteLength - ivLength - frameTrailer.byteLength, ivLength);
|
@@ -1253,26 +1315,32 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1253
1315
|
}
|
1254
1316
|
getUnencryptedBytes(frame) {
|
1255
1317
|
var _a;
|
1318
|
+
var frameInfo = {
|
1319
|
+
unencryptedBytes: 0,
|
1320
|
+
isH264: false
|
1321
|
+
};
|
1256
1322
|
if (isVideoFrame(frame)) {
|
1257
1323
|
let detectedCodec = (_a = this.getVideoCodec(frame)) !== null && _a !== void 0 ? _a : this.videoCodec;
|
1258
1324
|
if (detectedCodec === 'av1' || detectedCodec === 'vp9') {
|
1259
1325
|
throw new Error("".concat(detectedCodec, " is not yet supported for end to end encryption"));
|
1260
1326
|
}
|
1261
1327
|
if (detectedCodec === 'vp8') {
|
1262
|
-
|
1328
|
+
frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
|
1329
|
+
return frameInfo;
|
1263
1330
|
}
|
1264
1331
|
const data = new Uint8Array(frame.data);
|
1265
1332
|
try {
|
1266
1333
|
const naluIndices = findNALUIndices(data);
|
1267
1334
|
// if the detected codec is undefined we test whether it _looks_ like a h264 frame as a best guess
|
1268
|
-
|
1269
|
-
if (isH264) {
|
1335
|
+
frameInfo.isH264 = detectedCodec === 'h264' || naluIndices.some(naluIndex => [NALUType.SLICE_IDR, NALUType.SLICE_NON_IDR].includes(parseNALUType(data[naluIndex])));
|
1336
|
+
if (frameInfo.isH264) {
|
1270
1337
|
for (const index of naluIndices) {
|
1271
1338
|
let type = parseNALUType(data[index]);
|
1272
1339
|
switch (type) {
|
1273
1340
|
case NALUType.SLICE_IDR:
|
1274
1341
|
case NALUType.SLICE_NON_IDR:
|
1275
|
-
|
1342
|
+
frameInfo.unencryptedBytes = index + 2;
|
1343
|
+
return frameInfo;
|
1276
1344
|
default:
|
1277
1345
|
break;
|
1278
1346
|
}
|
@@ -1282,9 +1350,11 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1282
1350
|
} catch (e) {
|
1283
1351
|
// no op, we just continue and fallback to vp8
|
1284
1352
|
}
|
1285
|
-
|
1353
|
+
frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
|
1354
|
+
return frameInfo;
|
1286
1355
|
} else {
|
1287
|
-
|
1356
|
+
frameInfo.unencryptedBytes = UNENCRYPTED_BYTES.audio;
|
1357
|
+
return frameInfo;
|
1288
1358
|
}
|
1289
1359
|
}
|
1290
1360
|
/**
|