livekit-client 2.17.0 → 2.17.2

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.
Files changed (99) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +7 -2
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +2350 -1430
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/e2ee/KeyProvider.d.ts +4 -2
  10. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +9 -6
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/logger.d.ts +2 -1
  14. package/dist/src/logger.d.ts.map +1 -1
  15. package/dist/src/room/PCTransport.d.ts.map +1 -1
  16. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  17. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  18. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
  19. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
  20. package/dist/src/room/data-track/depacketizer.d.ts +51 -0
  21. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -0
  22. package/dist/src/room/data-track/frame.d.ts +7 -0
  23. package/dist/src/room/data-track/frame.d.ts.map +1 -0
  24. package/dist/src/room/data-track/handle.d.ts +27 -0
  25. package/dist/src/room/data-track/handle.d.ts.map +1 -0
  26. package/dist/src/room/data-track/packet/constants.d.ts +20 -0
  27. package/dist/src/room/data-track/packet/constants.d.ts.map +1 -0
  28. package/dist/src/room/data-track/packet/errors.d.ts +42 -0
  29. package/dist/src/room/data-track/packet/errors.d.ts.map +1 -0
  30. package/dist/src/room/data-track/packet/extensions.d.ts +68 -0
  31. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -0
  32. package/dist/src/room/data-track/packet/index.d.ts +91 -0
  33. package/dist/src/room/data-track/packet/index.d.ts.map +1 -0
  34. package/dist/src/room/data-track/packet/serializable.d.ts +12 -0
  35. package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -0
  36. package/dist/src/room/data-track/packetizer.d.ts +43 -0
  37. package/dist/src/room/data-track/packetizer.d.ts.map +1 -0
  38. package/dist/src/room/data-track/utils.d.ts +60 -0
  39. package/dist/src/room/data-track/utils.d.ts.map +1 -0
  40. package/dist/src/room/errors.d.ts +16 -4
  41. package/dist/src/room/errors.d.ts.map +1 -1
  42. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  43. package/dist/src/room/track/LocalTrack.d.ts +1 -0
  44. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  45. package/dist/src/room/types.d.ts +1 -0
  46. package/dist/src/room/types.d.ts.map +1 -1
  47. package/dist/src/utils/TypedPromise.d.ts +4 -0
  48. package/dist/src/utils/TypedPromise.d.ts.map +1 -1
  49. package/dist/src/utils/throws.d.ts +36 -0
  50. package/dist/src/utils/throws.d.ts.map +1 -0
  51. package/dist/ts4.2/e2ee/KeyProvider.d.ts +4 -2
  52. package/dist/ts4.2/index.d.ts +10 -3
  53. package/dist/ts4.2/logger.d.ts +2 -1
  54. package/dist/ts4.2/room/data-track/depacketizer.d.ts +51 -0
  55. package/dist/ts4.2/room/data-track/frame.d.ts +7 -0
  56. package/dist/ts4.2/room/data-track/handle.d.ts +27 -0
  57. package/dist/ts4.2/room/data-track/packet/constants.d.ts +20 -0
  58. package/dist/ts4.2/room/data-track/packet/errors.d.ts +42 -0
  59. package/dist/ts4.2/room/data-track/packet/extensions.d.ts +68 -0
  60. package/dist/ts4.2/room/data-track/packet/index.d.ts +98 -0
  61. package/dist/ts4.2/room/data-track/packet/serializable.d.ts +12 -0
  62. package/dist/ts4.2/room/data-track/packetizer.d.ts +43 -0
  63. package/dist/ts4.2/room/data-track/utils.d.ts +60 -0
  64. package/dist/ts4.2/room/errors.d.ts +16 -4
  65. package/dist/ts4.2/room/track/LocalTrack.d.ts +1 -0
  66. package/dist/ts4.2/room/types.d.ts +1 -0
  67. package/dist/ts4.2/utils/TypedPromise.d.ts +4 -0
  68. package/dist/ts4.2/utils/throws.d.ts +39 -0
  69. package/package.json +12 -10
  70. package/src/e2ee/KeyProvider.ts +4 -2
  71. package/src/index.ts +21 -5
  72. package/src/logger.ts +1 -0
  73. package/src/room/PCTransport.ts +1 -0
  74. package/src/room/PCTransportManager.ts +27 -9
  75. package/src/room/RTCEngine.ts +13 -2
  76. package/src/room/Room.ts +1 -1
  77. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +1 -0
  78. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +2 -1
  79. package/src/room/data-track/depacketizer.test.ts +442 -0
  80. package/src/room/data-track/depacketizer.ts +298 -0
  81. package/src/room/data-track/frame.ts +8 -0
  82. package/src/room/data-track/handle.test.ts +13 -0
  83. package/src/room/data-track/handle.ts +80 -0
  84. package/src/room/data-track/packet/constants.ts +27 -0
  85. package/src/room/data-track/packet/errors.ts +121 -0
  86. package/src/room/data-track/packet/extensions.ts +259 -0
  87. package/src/room/data-track/packet/index.test.ts +615 -0
  88. package/src/room/data-track/packet/index.ts +363 -0
  89. package/src/room/data-track/packet/serializable.ts +29 -0
  90. package/src/room/data-track/packetizer.test.ts +131 -0
  91. package/src/room/data-track/packetizer.ts +128 -0
  92. package/src/room/data-track/utils.test.ts +54 -0
  93. package/src/room/data-track/utils.ts +206 -0
  94. package/src/room/errors.ts +23 -5
  95. package/src/room/track/LocalAudioTrack.ts +4 -7
  96. package/src/room/track/LocalTrack.ts +4 -0
  97. package/src/room/types.ts +3 -1
  98. package/src/utils/TypedPromise.ts +7 -0
  99. package/src/utils/throws.ts +42 -0
@@ -0,0 +1,615 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { DataTrackPacket, DataTrackPacketHeader, FrameMarker } from '.';
4
+ import { DataTrackHandle } from '../handle';
5
+ import { DataTrackTimestamp, WrapAroundUnsignedInt } from '../utils';
6
+ import { EXT_FLAG_SHIFT } from './constants';
7
+ import {
8
+ DataTrackE2eeExtension,
9
+ DataTrackExtensionTag,
10
+ DataTrackExtensions,
11
+ DataTrackUserTimestampExtension,
12
+ } from './extensions';
13
+
14
+ describe('DataTrackPacket', () => {
15
+ describe('Serialization', () => {
16
+ it('should serialize a single packet', () => {
17
+ const header = new DataTrackPacketHeader({
18
+ marker: FrameMarker.Single,
19
+ trackHandle: DataTrackHandle.fromNumber(101),
20
+ sequence: WrapAroundUnsignedInt.u16(102),
21
+ frameNumber: WrapAroundUnsignedInt.u16(103),
22
+ timestamp: DataTrackTimestamp.fromRtpTicks(104),
23
+ });
24
+
25
+ const payloadBytes = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
26
+
27
+ const packet = new DataTrackPacket(header, payloadBytes);
28
+
29
+ expect(packet.toBinaryLengthBytes()).toStrictEqual(22);
30
+ expect(packet.toBinary()).toStrictEqual(
31
+ new Uint8Array([
32
+ 0x18, // Version 0, single, extension
33
+ 0, // Reserved
34
+ 0, // Track handle (big endian)
35
+ 101,
36
+ 0, // Sequence (big endian)
37
+ 102,
38
+ 0, // Frame number (big endian)
39
+ 103,
40
+ 0, // Timestamp (big endian)
41
+ 0,
42
+ 0,
43
+ 104,
44
+ /* (No extension words value) */
45
+ 0, // Payload
46
+ 1,
47
+ 2,
48
+ 3,
49
+ 4,
50
+ 5,
51
+ 6,
52
+ 7,
53
+ 8,
54
+ 9,
55
+ ]),
56
+ );
57
+ });
58
+ it('should serialize a final packet with extensions', () => {
59
+ const header = new DataTrackPacketHeader({
60
+ marker: FrameMarker.Final,
61
+ trackHandle: DataTrackHandle.fromNumber(0x8811),
62
+ sequence: WrapAroundUnsignedInt.u16(0x4422),
63
+ frameNumber: WrapAroundUnsignedInt.u16(0x4411),
64
+ timestamp: DataTrackTimestamp.fromRtpTicks(0x44221188),
65
+ extensions: new DataTrackExtensions({
66
+ userTimestamp: new DataTrackUserTimestampExtension(0x4411221111118811n),
67
+ e2ee: new DataTrackE2eeExtension(0xfa, new Uint8Array(12).fill(0x3c)),
68
+ }),
69
+ });
70
+
71
+ const payloadBytes = new Uint8Array(32).fill(0xfa);
72
+
73
+ const packet = new DataTrackPacket(header, payloadBytes);
74
+
75
+ expect(packet.toBinaryLengthBytes()).toStrictEqual(78);
76
+ expect(packet.toBinary()).toStrictEqual(
77
+ new Uint8Array([
78
+ 0xc, // Version 0, final, extension
79
+ 0, // Reserved
80
+ 136, // Track handle (big endian)
81
+ 17,
82
+ 68, // Sequence (big endian)
83
+ 34,
84
+ 68, // Frame number (big endian)
85
+ 17,
86
+ 68, // Timestamp (big endian)
87
+ 34,
88
+ 17,
89
+ 136,
90
+ 0, // Rtp oriented extension words (big endian)
91
+ 7,
92
+
93
+ // E2ee extension
94
+ 0, // ID 1 (big endian)
95
+ 1,
96
+ 0, // Length 12 (big endian)
97
+ 12,
98
+ 0xfa, // Key index
99
+ 0x3c, // Iv array
100
+ 0x3c,
101
+ 0x3c,
102
+ 0x3c,
103
+ 0x3c,
104
+ 0x3c,
105
+ 0x3c,
106
+ 0x3c,
107
+ 0x3c,
108
+ 0x3c,
109
+ 0x3c,
110
+ 0x3c,
111
+
112
+ // User timestamp extension
113
+ 0, // ID 2 (big endian)
114
+ 2,
115
+ 0, // Length 7 (big endian)
116
+ 7,
117
+ 68, // Timestamp value (big endian)
118
+ 17,
119
+ 34,
120
+ 17,
121
+ 17,
122
+ 17,
123
+ 136,
124
+ 17,
125
+
126
+ 0, // Extension padding
127
+ 0,
128
+ 0,
129
+
130
+ 0xfa, // Payload
131
+ 0xfa,
132
+ 0xfa,
133
+ 0xfa,
134
+ 0xfa,
135
+ 0xfa,
136
+ 0xfa,
137
+ 0xfa,
138
+ 0xfa,
139
+ 0xfa,
140
+ 0xfa,
141
+ 0xfa,
142
+ 0xfa,
143
+ 0xfa,
144
+ 0xfa,
145
+ 0xfa,
146
+ 0xfa,
147
+ 0xfa,
148
+ 0xfa,
149
+ 0xfa,
150
+ 0xfa,
151
+ 0xfa,
152
+ 0xfa,
153
+ 0xfa,
154
+ 0xfa,
155
+ 0xfa,
156
+ 0xfa,
157
+ 0xfa,
158
+ 0xfa,
159
+ 0xfa,
160
+ 0xfa,
161
+ 0xfa,
162
+ ]),
163
+ );
164
+ });
165
+ it('should serialize a start packet with only the e2ee extension', () => {
166
+ const header = new DataTrackPacketHeader({
167
+ marker: FrameMarker.Start,
168
+ trackHandle: DataTrackHandle.fromNumber(101),
169
+ sequence: WrapAroundUnsignedInt.u16(102),
170
+ frameNumber: WrapAroundUnsignedInt.u16(103),
171
+ timestamp: DataTrackTimestamp.fromRtpTicks(104),
172
+ extensions: new DataTrackExtensions({
173
+ e2ee: new DataTrackE2eeExtension(0xfa, new Uint8Array(12).fill(0x3c)),
174
+ }),
175
+ });
176
+
177
+ const payloadBytes = new Uint8Array(32).fill(0xfa);
178
+
179
+ const packet = new DataTrackPacket(header, payloadBytes);
180
+
181
+ expect(packet.toBinaryLengthBytes()).toStrictEqual(66);
182
+ expect(packet.toBinary()).toStrictEqual(
183
+ new Uint8Array([
184
+ 0x14, // Version 0, start, extension
185
+ 0, // Reserved
186
+ 0, // Track handle (big endian)
187
+ 101,
188
+ 0, // Sequence (big endian)
189
+ 102,
190
+ 0, // Frame number (big endian)
191
+ 103,
192
+ 0, // Timestamp (big endian)
193
+ 0,
194
+ 0,
195
+ 104,
196
+ 0, // RTP oriented extension words (big endian)
197
+ 4,
198
+
199
+ // E2ee extension
200
+ 0, // ID 1 (big endian)
201
+ 1,
202
+ 0, // Length 12 (big endian)
203
+ 12,
204
+ 0xfa, // Key index
205
+ 0x3c, // Iv array
206
+ 0x3c,
207
+ 0x3c,
208
+ 0x3c,
209
+ 0x3c,
210
+ 0x3c,
211
+ 0x3c,
212
+ 0x3c,
213
+ 0x3c,
214
+ 0x3c,
215
+ 0x3c,
216
+ 0x3c,
217
+
218
+ 0, // Extension padding
219
+ 0,
220
+ 0,
221
+
222
+ 0xfa, // Payload
223
+ 0xfa,
224
+ 0xfa,
225
+ 0xfa,
226
+ 0xfa,
227
+ 0xfa,
228
+ 0xfa,
229
+ 0xfa,
230
+ 0xfa,
231
+ 0xfa,
232
+ 0xfa,
233
+ 0xfa,
234
+ 0xfa,
235
+ 0xfa,
236
+ 0xfa,
237
+ 0xfa,
238
+ 0xfa,
239
+ 0xfa,
240
+ 0xfa,
241
+ 0xfa,
242
+ 0xfa,
243
+ 0xfa,
244
+ 0xfa,
245
+ 0xfa,
246
+ 0xfa,
247
+ 0xfa,
248
+ 0xfa,
249
+ 0xfa,
250
+ 0xfa,
251
+ 0xfa,
252
+ 0xfa,
253
+ 0xfa,
254
+ ]),
255
+ );
256
+ });
257
+
258
+ it('should be unable to serialize a packet header into a DataView which is too small', () => {
259
+ const header = new DataTrackPacketHeader({
260
+ marker: FrameMarker.Single,
261
+ trackHandle: DataTrackHandle.fromNumber(101),
262
+ sequence: WrapAroundUnsignedInt.u16(102),
263
+ frameNumber: WrapAroundUnsignedInt.u16(103),
264
+ timestamp: DataTrackTimestamp.fromRtpTicks(104),
265
+ });
266
+ const payloadBytes = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
267
+ const packet = new DataTrackPacket(header, payloadBytes);
268
+
269
+ const twoByteLongDataView = new DataView(new ArrayBuffer(2));
270
+ expect(() => packet.toBinaryInto(twoByteLongDataView)).toThrow('Buffer cannot fit header');
271
+ });
272
+ it('should be unable to serialize a packet payload into a DataView which is too small', () => {
273
+ const header = new DataTrackPacketHeader({
274
+ marker: FrameMarker.Single,
275
+ trackHandle: DataTrackHandle.fromNumber(101),
276
+ sequence: WrapAroundUnsignedInt.u16(102),
277
+ frameNumber: WrapAroundUnsignedInt.u16(103),
278
+ timestamp: DataTrackTimestamp.fromRtpTicks(104),
279
+ });
280
+ const payloadBytes = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
281
+ const packet = new DataTrackPacket(header, payloadBytes);
282
+
283
+ const fourteenByteLongDataView = new DataView(
284
+ new ArrayBuffer(14 /* 12 byte header + 2 extra bytes */),
285
+ );
286
+ expect(() => packet.toBinaryInto(fourteenByteLongDataView)).toThrow(
287
+ 'Buffer cannot fit payload',
288
+ );
289
+ });
290
+ });
291
+
292
+ describe('Deserialization', () => {
293
+ const VALID_PACKET_BYTES = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0];
294
+
295
+ it('should deserialize a single packet', () => {
296
+ const [packet, bytes] = DataTrackPacket.fromBinary(
297
+ new Uint8Array([
298
+ 0x18, // Version 0, single, extension
299
+ 0, // Reserved
300
+ 0, // Track handle (big endian)
301
+ 101,
302
+ 0, // Sequence (big endian)
303
+ 102,
304
+ 0, // Frame number (big endian)
305
+ 103,
306
+ 0, // Timestamp (big endian)
307
+ 0,
308
+ 0,
309
+ 104,
310
+ /* (No extension words value) */
311
+ 1, // Payload
312
+ 2,
313
+ 3,
314
+ 4,
315
+ 5,
316
+ 6,
317
+ 7,
318
+ 8,
319
+ 9,
320
+ ]),
321
+ );
322
+
323
+ expect(bytes).toStrictEqual(21);
324
+ expect(packet.toJSON()).toStrictEqual({
325
+ header: {
326
+ frameNumber: 103,
327
+ marker: FrameMarker.Single,
328
+ sequence: 102,
329
+ timestamp: 104,
330
+ trackHandle: 101,
331
+ extensions: {
332
+ e2ee: null,
333
+ userTimestamp: null,
334
+ },
335
+ },
336
+ payload: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]),
337
+ });
338
+ });
339
+
340
+ it('should fail to deserialize a too short buffer', () => {
341
+ const packetBytes = new Uint8Array(VALID_PACKET_BYTES);
342
+
343
+ expect(() => DataTrackPacket.fromBinary(packetBytes.slice(0, 5))).toThrow(
344
+ 'Too short to contain a valid header',
345
+ );
346
+ });
347
+
348
+ it('should fail to deserialize a packet including extensions but missing the ext words value', () => {
349
+ const packetBytes = new Uint8Array(VALID_PACKET_BYTES);
350
+ packetBytes[0] |= 1 << EXT_FLAG_SHIFT; // Extension flag - should have ext word indicator here
351
+
352
+ expect(() => DataTrackPacket.fromBinary(packetBytes)).toThrow(
353
+ 'Extension word indicator is missing',
354
+ );
355
+ });
356
+
357
+ it('should fail to deserialize a packet which overruns headers', () => {
358
+ const packetBytes = new Uint8Array([
359
+ ...VALID_PACKET_BYTES,
360
+
361
+ 0, // Extension word (big endian)
362
+ 1,
363
+ ]);
364
+ packetBytes[0] |= 1 << EXT_FLAG_SHIFT; // Extension flag - should have ext word indicator here
365
+
366
+ expect(() => DataTrackPacket.fromBinary(packetBytes)).toThrow(
367
+ 'Header exceeds total packet length',
368
+ );
369
+ });
370
+
371
+ it('should fail to deserialize a packet with an unsupported version', () => {
372
+ const packetBytes = new Uint8Array(VALID_PACKET_BYTES);
373
+ packetBytes[0] = 0x20; // Version 1 (not supported yet)
374
+
375
+ expect(() => DataTrackPacket.fromBinary(packetBytes)).toThrow('Unsupported version 1');
376
+ });
377
+
378
+ it('should deserialize base header', () => {
379
+ const [packet, bytes] = DataTrackPacket.fromBinary(
380
+ new Uint8Array([
381
+ 0x8, // Version 0, final, extension
382
+ 0x0, // Reserved
383
+ 0x88, // Track handle (big endian)
384
+ 0x11,
385
+ 0x44, // Sequence (big endian)
386
+ 0x22,
387
+ 0x44, // Frame number (big endian)
388
+ 0x11,
389
+ 0x44, // Timestamp (big endian)
390
+ 0x22,
391
+ 0x11,
392
+ 0x88,
393
+ ]),
394
+ );
395
+
396
+ expect(bytes).toStrictEqual(12);
397
+ expect(packet.toJSON()).toStrictEqual({
398
+ header: {
399
+ marker: FrameMarker.Final,
400
+ trackHandle: 0x8811,
401
+ sequence: 0x4422,
402
+ frameNumber: 0x4411,
403
+ timestamp: 0x44221188,
404
+ extensions: {
405
+ e2ee: null,
406
+ userTimestamp: null,
407
+ },
408
+ },
409
+ payload: new Uint8Array([]),
410
+ });
411
+ });
412
+
413
+ it.each([0, 1, 24])('should skip extension padding', (extensionWords) => {
414
+ const packetBytes = new Uint8Array([
415
+ ...VALID_PACKET_BYTES,
416
+
417
+ 0, // Extension words (big endian)
418
+ extensionWords,
419
+
420
+ ...new Array((extensionWords + 1) /* RTP oriented extension words */ * 4).fill(0), // Padding
421
+ ]);
422
+ packetBytes[0] |= 1 << EXT_FLAG_SHIFT; // Extension flag
423
+
424
+ const [packet] = DataTrackPacket.fromBinary(packetBytes);
425
+
426
+ expect(new Uint8Array(packet.toJSON().payload).byteLength).toStrictEqual(0);
427
+ });
428
+
429
+ it('should deserialize e2ee extension properly', () => {
430
+ const packetBytes = new Uint8Array([
431
+ ...VALID_PACKET_BYTES,
432
+
433
+ 0, // RTP oriented extension words (big endian)
434
+ 4,
435
+
436
+ // E2ee extension
437
+ 0, // ID 1 (big endian)
438
+ 1,
439
+ 0, // Length 12 (big endian)
440
+ 12,
441
+ 0xfa, // Key index
442
+ 0x3c, // Iv array
443
+ 0x3c,
444
+ 0x3c,
445
+ 0x3c,
446
+ 0x3c,
447
+ 0x3c,
448
+ 0x3c,
449
+ 0x3c,
450
+ 0x3c,
451
+ 0x3c,
452
+ 0x3c,
453
+ 0x3c,
454
+
455
+ 0, // Padding
456
+ 0,
457
+ 0,
458
+ ]);
459
+ packetBytes[0] |= 1 << EXT_FLAG_SHIFT; // Extension flag
460
+
461
+ const [packet] = DataTrackPacket.fromBinary(packetBytes);
462
+
463
+ expect(packet.toJSON().header.extensions.e2ee).toStrictEqual({
464
+ tag: DataTrackExtensionTag.E2ee,
465
+ lengthBytes: 13,
466
+ keyIndex: 0xfa,
467
+ iv: new Uint8Array(12).fill(0x3c),
468
+ });
469
+ });
470
+
471
+ it('should deserialize user timestamp extension properly', () => {
472
+ const packetBytes = new Uint8Array([
473
+ ...VALID_PACKET_BYTES,
474
+
475
+ 0, // Extension words (big endian)
476
+ 2,
477
+
478
+ // User timestamp extension
479
+ 0, // ID 2 (big endian)
480
+ 2,
481
+ 0, // Length 7 (big endian)
482
+ 7,
483
+ 0x44, // Timestamp (big endian)
484
+ 0x11,
485
+ 0x22,
486
+ 0x11,
487
+ 0x11,
488
+ 0x11,
489
+ 0x88,
490
+ 0x11,
491
+ ]);
492
+ packetBytes[0] |= 1 << EXT_FLAG_SHIFT; // Extension flag
493
+
494
+ const [packet] = DataTrackPacket.fromBinary(packetBytes);
495
+
496
+ expect(packet.toJSON().header.extensions.userTimestamp).toStrictEqual({
497
+ tag: DataTrackExtensionTag.UserTimestamp,
498
+ lengthBytes: 8,
499
+ timestamp: 0x4411221111118811n,
500
+ });
501
+ });
502
+
503
+ it('should deserialize unknown extension properly', () => {
504
+ const packetBytes = new Uint8Array([
505
+ ...VALID_PACKET_BYTES,
506
+
507
+ 0, // RTP oriented extension words (big endian)
508
+ 2,
509
+
510
+ // Unknown / potential future extension
511
+ 0, // ID 8 (big endian)
512
+ 8,
513
+ 0, // Length 12 (big endian)
514
+ 6,
515
+ 0x1, // Payload
516
+ 0x2,
517
+ 0x3,
518
+ 0x4,
519
+ 0x5,
520
+ 0x6,
521
+
522
+ 0x0, // Padding
523
+ 0x0,
524
+ ]);
525
+ packetBytes[0] |= 1 << EXT_FLAG_SHIFT; // Extension flag
526
+
527
+ const [packet] = DataTrackPacket.fromBinary(packetBytes);
528
+
529
+ expect(packet.toJSON().header.extensions).toStrictEqual({
530
+ userTimestamp: null,
531
+ e2ee: null,
532
+ });
533
+ });
534
+
535
+ it('should ensure extensions are word aligned', () => {
536
+ const packetBytes = new Uint8Array([
537
+ ...VALID_PACKET_BYTES,
538
+
539
+ 0, // RTP oriented extension words (big endian)
540
+ 0,
541
+
542
+ 0x0, // Padding, missing one byte
543
+ 0x0,
544
+ 0x0,
545
+ ]);
546
+ packetBytes[0] |= 1 << EXT_FLAG_SHIFT; // Extension flag
547
+
548
+ expect(() => DataTrackPacket.fromBinary(packetBytes)).toThrow(
549
+ 'Header exceeds total packet length',
550
+ );
551
+ });
552
+ });
553
+
554
+ describe('Round trip serialization + deserialization', () => {
555
+ it('should serialize a single packet', () => {
556
+ const header = new DataTrackPacketHeader({
557
+ marker: FrameMarker.Single,
558
+ trackHandle: DataTrackHandle.fromNumber(101),
559
+ sequence: WrapAroundUnsignedInt.u16(102),
560
+ frameNumber: WrapAroundUnsignedInt.u16(103),
561
+ timestamp: DataTrackTimestamp.fromRtpTicks(104),
562
+ });
563
+
564
+ const payloadBytes = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]);
565
+
566
+ const encodedPacket = new DataTrackPacket(header, payloadBytes);
567
+
568
+ expect(encodedPacket.toBinaryLengthBytes()).toStrictEqual(21);
569
+ expect(encodedPacket.toBinary()).toStrictEqual(
570
+ new Uint8Array([
571
+ 0x18, // Version 0, single, extension
572
+ 0, // Reserved
573
+ 0, // Track handle (big endian)
574
+ 101,
575
+ 0, // Sequence (big endian)
576
+ 102,
577
+ 0, // Frame number (big endian)
578
+ 103,
579
+ 0, // Timestamp (big endian)
580
+ 0,
581
+ 0,
582
+ 104,
583
+ /* (No extension words value) */
584
+ 1, // Payload
585
+ 2,
586
+ 3,
587
+ 4,
588
+ 5,
589
+ 6,
590
+ 7,
591
+ 8,
592
+ 9,
593
+ ]),
594
+ );
595
+
596
+ const [decodedPacket, bytes] = DataTrackPacket.fromBinary(encodedPacket.toBinary());
597
+
598
+ expect(bytes).toStrictEqual(21);
599
+ expect(decodedPacket.toJSON()).toStrictEqual({
600
+ header: {
601
+ frameNumber: 103,
602
+ marker: FrameMarker.Single,
603
+ sequence: 102,
604
+ timestamp: 104,
605
+ trackHandle: 101,
606
+ extensions: {
607
+ e2ee: null,
608
+ userTimestamp: null,
609
+ },
610
+ },
611
+ payload: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]),
612
+ });
613
+ });
614
+ });
615
+ });