@moqtap/codec 0.9.0 → 0.11.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/CHANGELOG.md +173 -0
- package/dist/auth-redaction-DsdLq4Qs.d.cts +102 -0
- package/dist/auth-redaction-DsdLq4Qs.d.ts +102 -0
- package/dist/{chunk-PU2KV7VP.cjs → chunk-27K5BUAG.cjs} +485 -515
- package/dist/{chunk-BV6MITLO.js → chunk-3RNQMGKA.js} +365 -390
- package/dist/{chunk-2XH6PHXR.js → chunk-5AWIXOLB.js} +442 -467
- package/dist/{chunk-4XLYCT5Q.cjs → chunk-5IMHJIAZ.cjs} +52 -2
- package/dist/{chunk-TVSFDQYE.cjs → chunk-5JGZCKJO.cjs} +513 -531
- package/dist/{chunk-IADVBKEJ.cjs → chunk-73K4OSAN.cjs} +407 -432
- package/dist/{chunk-N2V7Z37J.js → chunk-7VQKRGRD.js} +19 -3
- package/dist/{chunk-UZO5Z2XT.cjs → chunk-A3UCQOIB.cjs} +490 -515
- package/dist/{chunk-JKMYAUGY.js → chunk-A7PDMVXM.js} +35 -11
- package/dist/{chunk-VZHL2U2N.cjs → chunk-A7VQO6YC.cjs} +54 -38
- package/dist/{chunk-KCH4BQXK.cjs → chunk-ACQHZLRZ.cjs} +73 -47
- package/dist/{chunk-H6DTAIWM.js → chunk-BBICOMW6.js} +47 -19
- package/dist/{chunk-MAXEHYVB.cjs → chunk-BNIUVOCJ.cjs} +554 -612
- package/dist/{chunk-QGIHC6BA.cjs → chunk-CNC6ANS4.cjs} +70 -46
- package/dist/{chunk-NH4L6NIY.js → chunk-FDLQSWHK.js} +437 -467
- package/dist/{chunk-IJAF6PAY.js → chunk-FFNOE4UR.js} +19 -3
- package/dist/{chunk-CZNW3R5E.cjs → chunk-GMENT63T.cjs} +54 -38
- package/dist/{chunk-7ENEWAMZ.cjs → chunk-HM7BLRVP.cjs} +305 -305
- package/dist/{chunk-XHAP2YST.js → chunk-HZONHFNO.js} +19 -3
- package/dist/{chunk-EVCPBFSR.cjs → chunk-ILYABWDK.cjs} +83 -55
- package/dist/{chunk-IDV2JYJ6.cjs → chunk-KRSFTZVO.cjs} +56 -40
- package/dist/{chunk-RIIA4ALQ.cjs → chunk-KSPH6KMH.cjs} +54 -38
- package/dist/{chunk-UA5XFP4O.cjs → chunk-KTY4LAUV.cjs} +485 -494
- package/dist/{chunk-RZTLQAKF.js → chunk-OJNLJT5D.js} +0 -1
- package/dist/{chunk-YJGOD3Y4.js → chunk-PJK2ZS3I.js} +492 -550
- package/dist/{chunk-TMXRYOBS.js → chunk-PSMZKHOC.js} +444 -453
- package/dist/{chunk-7RE6KSIY.cjs → chunk-Q5JQGHWY.cjs} +0 -1
- package/dist/{chunk-23KRITJ6.js → chunk-SQTUQY5E.js} +37 -11
- package/dist/{chunk-XQGEOFGE.js → chunk-WCRLAKDW.js} +253 -253
- package/dist/{chunk-KYVTM5VI.js → chunk-WL2YUEXH.js} +19 -3
- package/dist/{chunk-Q7NSUXXB.js → chunk-YCNIVZS7.js} +464 -482
- package/dist/{chunk-KLPWJU6W.js → chunk-YRZQVNZO.js} +50 -0
- package/dist/{codec-Bym-K2q2.d.cts → codec-76xzrLTL.d.cts} +21 -2
- package/dist/{codec-BKsVu3Fr.d.ts → codec-8p40WdLg.d.ts} +21 -2
- package/dist/{codec-Dl_QIXMz.d.ts → codec-BN2Bu0KI.d.ts} +20 -1
- package/dist/{codec-D4RvFQmu.d.ts → codec-Bb6GKTrv.d.ts} +21 -2
- package/dist/{codec-DAyiF5sq.d.cts → codec-BgpfHoAd.d.cts} +21 -2
- package/dist/{codec-CA6nbbEk.d.ts → codec-BkheTXO7.d.ts} +21 -2
- package/dist/{codec-DchsPfr3.d.cts → codec-BwVDkokh.d.cts} +21 -2
- package/dist/{codec-CgZdadPt.d.cts → codec-C5s1eBJX.d.cts} +21 -2
- package/dist/codec-CM5yd6n3.d.ts +38 -0
- package/dist/{codec-DJmqoIsp.d.cts → codec-Cb8HC3XV.d.cts} +21 -2
- package/dist/codec-Clu_skx8.d.cts +38 -0
- package/dist/{codec-DzkdW4kO.d.cts → codec-CpOpemR8.d.cts} +20 -1
- package/dist/{codec-CXCjya_G.d.cts → codec-Cshdl9Tt.d.cts} +20 -1
- package/dist/{codec-CSBFdPgP.d.cts → codec-DELe00w_.d.cts} +21 -2
- package/dist/{codec-C_xxsbKy.d.cts → codec-DHzKAUCa.d.cts} +21 -2
- package/dist/{codec--CVeqGQv.d.ts → codec-DdVBtvx7.d.ts} +20 -1
- package/dist/{codec-CviAa4lp.d.ts → codec-DlxGuYMv.d.ts} +21 -2
- package/dist/{codec-BNy5syCU.d.ts → codec-DxtgPxJS.d.ts} +20 -1
- package/dist/{codec-CtvFK4aV.d.cts → codec-GAY4lybi.d.cts} +21 -2
- package/dist/{codec-Cfo_z5ET.d.cts → codec-I8u2pfS4.d.cts} +20 -1
- package/dist/{codec-vddqy0KY.d.cts → codec-XLrnreeu.d.cts} +21 -2
- package/dist/{codec-O-1Js7h4.d.ts → codec-c0YEwwWn.d.ts} +21 -2
- package/dist/{codec-Cl73EfRW.d.ts → codec-ejDu-va6.d.ts} +21 -2
- package/dist/{codec-C006F75E.d.ts → codec-kaBUzdpV.d.ts} +21 -2
- package/dist/{codec-C5evFHL-.d.ts → codec-mVYqkIUF.d.ts} +21 -2
- package/dist/{codec-BMwgZIgG.d.ts → codec-qSbO5nOa.d.ts} +21 -2
- package/dist/{codec-BQa6Qsav.d.ts → codec-yYKGOipj.d.ts} +21 -2
- package/dist/{codec-DzLvG8Wb.d.cts → codec-zfzg9uiy.d.cts} +21 -2
- package/dist/draft07.cjs +48 -4
- package/dist/draft07.d.cts +42 -2
- package/dist/draft07.d.ts +42 -2
- package/dist/draft07.js +49 -5
- package/dist/draft08.cjs +76 -3
- package/dist/draft08.d.cts +70 -2
- package/dist/draft08.d.ts +70 -2
- package/dist/draft08.js +77 -4
- package/dist/draft09.cjs +76 -3
- package/dist/draft09.d.cts +70 -2
- package/dist/draft09.d.ts +70 -2
- package/dist/draft09.js +77 -4
- package/dist/draft10.cjs +76 -3
- package/dist/draft10.d.cts +70 -2
- package/dist/draft10.d.ts +70 -2
- package/dist/draft10.js +77 -4
- package/dist/draft11-session.d.cts +1 -1
- package/dist/draft11-session.d.ts +1 -1
- package/dist/draft11.cjs +104 -3
- package/dist/draft11.d.cts +100 -4
- package/dist/draft11.d.ts +100 -4
- package/dist/draft11.js +105 -4
- package/dist/draft12-session.d.cts +1 -1
- package/dist/draft12-session.d.ts +1 -1
- package/dist/draft12.cjs +113 -3
- package/dist/draft12.d.cts +109 -4
- package/dist/draft12.d.ts +109 -4
- package/dist/draft12.js +114 -4
- package/dist/draft13-session.d.cts +1 -1
- package/dist/draft13-session.d.ts +1 -1
- package/dist/draft13.cjs +105 -3
- package/dist/draft13.d.cts +101 -4
- package/dist/draft13.d.ts +101 -4
- package/dist/draft13.js +106 -4
- package/dist/draft14-session.d.cts +1 -1
- package/dist/draft14-session.d.ts +1 -1
- package/dist/draft14.cjs +107 -3
- package/dist/draft14.d.cts +103 -30
- package/dist/draft14.d.ts +103 -30
- package/dist/draft14.js +108 -4
- package/dist/draft15-session.d.cts +1 -1
- package/dist/draft15-session.d.ts +1 -1
- package/dist/draft15.cjs +69 -3
- package/dist/draft15.d.cts +65 -22
- package/dist/draft15.d.ts +65 -22
- package/dist/draft15.js +70 -4
- package/dist/draft16-session.cjs +2 -2
- package/dist/draft16-session.d.cts +1 -1
- package/dist/draft16-session.d.ts +1 -1
- package/dist/draft16-session.js +1 -1
- package/dist/draft16.cjs +76 -8
- package/dist/draft16.d.cts +67 -13
- package/dist/draft16.d.ts +67 -13
- package/dist/draft16.js +73 -5
- package/dist/draft17-session.d.cts +1 -1
- package/dist/draft17-session.d.ts +1 -1
- package/dist/draft17.cjs +77 -3
- package/dist/draft17.d.cts +73 -4
- package/dist/draft17.d.ts +73 -4
- package/dist/draft17.js +78 -4
- package/dist/draft18-session.d.cts +1 -1
- package/dist/draft18-session.d.ts +1 -1
- package/dist/draft18.cjs +8 -3
- package/dist/draft18.d.cts +4 -3
- package/dist/draft18.d.ts +4 -3
- package/dist/draft18.js +9 -4
- package/dist/draft19-session.d.cts +1 -1
- package/dist/draft19-session.d.ts +1 -1
- package/dist/draft19.cjs +11 -4
- package/dist/draft19.d.cts +4 -3
- package/dist/draft19.d.ts +4 -3
- package/dist/draft19.js +12 -5
- package/dist/draft20-session.d.cts +1 -1
- package/dist/draft20-session.d.ts +1 -1
- package/dist/draft20.cjs +8 -3
- package/dist/draft20.d.cts +4 -3
- package/dist/draft20.d.ts +4 -3
- package/dist/draft20.js +9 -4
- package/dist/index.cjs +30 -30
- package/dist/index.d.cts +25 -24
- package/dist/index.d.ts +25 -24
- package/dist/index.js +15 -15
- package/dist/session.cjs +2 -2
- package/dist/session.d.cts +10 -10
- package/dist/session.d.ts +10 -10
- package/dist/session.js +1 -1
- package/dist/{types-zHgCS6o5.d.cts → types-Btp416jt.d.cts} +15 -7
- package/dist/{types-zHgCS6o5.d.ts → types-Btp416jt.d.ts} +15 -7
- package/dist/{types-Cm9fjeYC.d.cts → types-BxTL1NXw.d.cts} +9 -2
- package/dist/{types-Cm9fjeYC.d.ts → types-BxTL1NXw.d.ts} +9 -2
- package/dist/{types-Bio-91Rt.d.cts → types-CI_SX4Xe.d.cts} +2 -2
- package/dist/{types-Bio-91Rt.d.ts → types-CI_SX4Xe.d.ts} +2 -2
- package/dist/{types-HI_bbXka.d.cts → types-CmqT0jUf.d.cts} +1 -1
- package/dist/{types-HI_bbXka.d.ts → types-CmqT0jUf.d.ts} +1 -1
- package/dist/{types-CPaYBsMG.d.cts → types-CuibHYcf.d.cts} +9 -2
- package/dist/{types-CPaYBsMG.d.ts → types-CuibHYcf.d.ts} +9 -2
- package/dist/{types-CWCQwuvy.d.cts → types-CwN7AEoL.d.cts} +24 -12
- package/dist/{types-CWCQwuvy.d.ts → types-CwN7AEoL.d.ts} +24 -12
- package/dist/{types-Ck45Ex5S.d.cts → types-D0kTgUuL.d.cts} +1 -1
- package/dist/{types-Ck45Ex5S.d.ts → types-D0kTgUuL.d.ts} +1 -1
- package/dist/{types-D9eiB_9A.d.cts → types-DAnalt5K.d.cts} +9 -2
- package/dist/{types-D9eiB_9A.d.ts → types-DAnalt5K.d.ts} +9 -2
- package/dist/{types-CXlSn0I9.d.cts → types-DjxexghA.d.cts} +2 -2
- package/dist/{types-CXlSn0I9.d.ts → types-DjxexghA.d.ts} +2 -2
- package/dist/{types-Cqp10JtB.d.cts → types-DxXNP32l.d.cts} +10 -2
- package/dist/{types-Cqp10JtB.d.ts → types-DxXNP32l.d.ts} +10 -2
- package/package.json +3 -3
- package/src/core/auth-redaction.ts +192 -0
- package/src/drafts/draft07/codec.ts +40 -0
- package/src/drafts/draft07/error-codes.ts +44 -0
- package/src/drafts/draft07/index.ts +15 -0
- package/src/drafts/draft08/codec.ts +40 -0
- package/src/drafts/draft08/error-codes.ts +76 -0
- package/src/drafts/draft08/index.ts +21 -0
- package/src/drafts/draft09/codec.ts +40 -0
- package/src/drafts/draft09/error-codes.ts +76 -0
- package/src/drafts/draft09/index.ts +21 -0
- package/src/drafts/draft10/codec.ts +40 -0
- package/src/drafts/draft10/error-codes.ts +76 -0
- package/src/drafts/draft10/index.ts +21 -0
- package/src/drafts/draft11/codec.ts +71 -8
- package/src/drafts/draft11/error-codes.ts +106 -0
- package/src/drafts/draft11/index.ts +23 -0
- package/src/drafts/draft11/types.ts +1 -1
- package/src/drafts/draft12/codec.ts +83 -16
- package/src/drafts/draft12/error-codes.ts +116 -0
- package/src/drafts/draft12/index.ts +25 -0
- package/src/drafts/draft12/types.ts +2 -2
- package/src/drafts/draft13/codec.ts +75 -11
- package/src/drafts/draft13/error-codes.ts +107 -0
- package/src/drafts/draft13/index.ts +23 -0
- package/src/drafts/draft13/types.ts +3 -3
- package/src/drafts/draft14/codec.ts +79 -12
- package/src/drafts/draft14/data-streams.ts +331 -259
- package/src/drafts/draft14/error-codes.ts +109 -0
- package/src/drafts/draft14/index.ts +23 -0
- package/src/drafts/draft14/types.ts +1 -1
- package/src/drafts/draft15/codec.ts +71 -8
- package/src/drafts/draft15/data-streams.ts +489 -444
- package/src/drafts/draft15/error-codes.ts +66 -0
- package/src/drafts/draft15/index.ts +15 -0
- package/src/drafts/draft15/types.ts +9 -2
- package/src/drafts/draft16/codec.ts +149 -49
- package/src/drafts/draft16/data-streams.ts +513 -442
- package/src/drafts/draft16/error-codes.ts +68 -0
- package/src/drafts/draft16/index.ts +15 -0
- package/src/drafts/draft16/messages.ts +4 -4
- package/src/drafts/draft16/session-fsm.ts +0 -1
- package/src/drafts/draft16/types.ts +24 -10
- package/src/drafts/draft17/codec.ts +77 -10
- package/src/drafts/draft17/data-streams.ts +567 -488
- package/src/drafts/draft17/index.ts +15 -0
- package/src/drafts/draft17/types.ts +9 -2
- package/src/drafts/draft18/codec.ts +70 -7
- package/src/drafts/draft18/data-streams.ts +570 -482
- package/src/drafts/draft18/index.ts +3 -0
- package/src/drafts/draft18/types.ts +18 -10
- package/src/drafts/draft19/codec.ts +95 -23
- package/src/drafts/draft19/data-streams.ts +570 -482
- package/src/drafts/draft19/error-codes.ts +3 -1
- package/src/drafts/draft19/index.ts +3 -0
- package/src/drafts/draft19/messages.ts +3 -1
- package/src/drafts/draft19/types.ts +32 -18
- package/src/drafts/draft20/codec.ts +115 -36
- package/src/drafts/draft20/data-streams.ts +610 -563
- package/src/drafts/draft20/index.ts +3 -0
- package/src/drafts/draft20/types.ts +31 -13
- package/dist/codec-BJ7BHuiZ.d.ts +0 -18
- package/dist/codec-C6Mx4bwz.d.cts +0 -18
|
@@ -216,107 +216,163 @@ export function encodeFetchStream(stream: FetchStream): Uint8Array {
|
|
|
216
216
|
return w.finish()
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
/**
|
|
220
|
+
* The SUBGROUP_HEADER fields, read once for both decoders.
|
|
221
|
+
*
|
|
222
|
+
* `decodeSubgroupStream` and `createSubgroupStreamDecoder` used to parse this
|
|
223
|
+
* header separately and they drifted: the incremental one dropped the header
|
|
224
|
+
* flags, reported no Type Flags, and left the Subgroup ID at zero under the
|
|
225
|
+
* mode that derives it from the first Object. One reader means there is
|
|
226
|
+
* nothing left to keep in sync.
|
|
227
|
+
*/
|
|
228
|
+
interface SubgroupHeaderFields {
|
|
229
|
+
readonly streamType: number
|
|
230
|
+
readonly propertiesPresent: boolean
|
|
231
|
+
readonly subgroupIsFirstObjId: boolean
|
|
232
|
+
readonly endOfGroup: boolean
|
|
233
|
+
readonly trackAlias: bigint
|
|
234
|
+
readonly groupId: bigint
|
|
235
|
+
/** Zero under `subgroupIsFirstObjId` until the first Object has been read. */
|
|
236
|
+
readonly subgroupId: bigint
|
|
237
|
+
readonly publisherPriority: number
|
|
238
|
+
}
|
|
223
239
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
240
|
+
function readSubgroupHeader(r: BufferReader): SubgroupHeaderFields {
|
|
241
|
+
const streamType = Number(r.readVarInt())
|
|
242
|
+
if (
|
|
243
|
+
!((streamType >= 0x10 && streamType <= 0x1d) || (streamType >= 0x30 && streamType <= 0x3d)) ||
|
|
244
|
+
(streamType & 0x06) === 0x06 // reserved SUBGROUP_ID_MODE (bits 2:1 = 0b11)
|
|
245
|
+
) {
|
|
246
|
+
throw new DecodeError(
|
|
247
|
+
'CONSTRAINT_VIOLATION',
|
|
248
|
+
`Expected subgroup stream type 0x10-0x15/0x18-0x1D/0x30-0x35/0x38-0x3D, got 0x${streamType.toString(16)}`,
|
|
249
|
+
0,
|
|
250
|
+
)
|
|
251
|
+
}
|
|
237
252
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
253
|
+
const propertiesPresent = (streamType & 0x01) !== 0
|
|
254
|
+
const hasSubgroupField = (streamType & 0x04) !== 0
|
|
255
|
+
const subgroupIsFirstObjId = (streamType & 0x02) !== 0 && !hasSubgroupField
|
|
256
|
+
const hasPriority = streamType < 0x30
|
|
257
|
+
const endOfGroup = (streamType & 0x08) !== 0
|
|
243
258
|
|
|
244
|
-
|
|
245
|
-
|
|
259
|
+
const trackAlias = r.readVarInt()
|
|
260
|
+
const groupId = r.readVarInt()
|
|
246
261
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
262
|
+
let subgroupId = 0n
|
|
263
|
+
if (hasSubgroupField) {
|
|
264
|
+
subgroupId = r.readVarInt()
|
|
265
|
+
}
|
|
251
266
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
267
|
+
let publisherPriority = 128
|
|
268
|
+
if (hasPriority) {
|
|
269
|
+
publisherPriority = r.readUint8()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
streamType,
|
|
274
|
+
propertiesPresent,
|
|
275
|
+
subgroupIsFirstObjId,
|
|
276
|
+
endOfGroup,
|
|
277
|
+
trackAlias,
|
|
278
|
+
groupId,
|
|
279
|
+
subgroupId,
|
|
280
|
+
publisherPriority,
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* One Object off a subgroup stream, for both decoders.
|
|
286
|
+
*
|
|
287
|
+
* `base` is the absolute offset within the stream that `r.offset === 0`
|
|
288
|
+
* corresponds to. The one-shot decoder reads from a reader spanning the whole
|
|
289
|
+
* stream and passes 0; the incremental decoder reads from a window over its
|
|
290
|
+
* own buffer and passes that window's position. That is what makes
|
|
291
|
+
* `byteOffset` mean the same thing on both paths, instead of being hardcoded
|
|
292
|
+
* to zero on one of them.
|
|
293
|
+
*
|
|
294
|
+
* Throws `UNEXPECTED_END` when the Object is not yet complete, which the
|
|
295
|
+
* incremental decoder reads as "wait for more bytes" rather than as an error.
|
|
296
|
+
*/
|
|
297
|
+
function readSubgroupObject(
|
|
298
|
+
r: BufferReader,
|
|
299
|
+
propertiesPresent: boolean,
|
|
300
|
+
isFirst: boolean,
|
|
301
|
+
prevObjectId: bigint,
|
|
302
|
+
base: number,
|
|
303
|
+
): ObjectPayload {
|
|
304
|
+
const byteOffset = base + r.offset
|
|
305
|
+
const delta = r.readVarInt()
|
|
306
|
+
const objectId = isFirst ? delta : prevObjectId + 1n + delta
|
|
307
|
+
|
|
308
|
+
const extensionData = new Uint8Array(0)
|
|
309
|
+
let objectProperties: Record<string, bigint> | undefined
|
|
310
|
+
if (propertiesPresent) {
|
|
311
|
+
const propsLen = Number(r.readVarInt())
|
|
312
|
+
if (propsLen > 0) {
|
|
313
|
+
objectProperties = decodeObjectProperties(r, propsLen)
|
|
255
314
|
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const payloadLength = Number(r.readVarInt())
|
|
318
|
+
let payload: Uint8Array
|
|
319
|
+
let status: bigint | undefined
|
|
320
|
+
let payloadByteOffset: number
|
|
321
|
+
if (payloadLength === 0) {
|
|
322
|
+
// The Object Status field is sent only when the Payload Length is zero.
|
|
323
|
+
// Skipping it leaves the varint to be read as the next Object's Object ID
|
|
324
|
+
// Delta, which desynchronises the rest of the stream silently.
|
|
325
|
+
status = r.readVarInt()
|
|
326
|
+
payloadByteOffset = base + r.offset
|
|
327
|
+
payload = new Uint8Array(0)
|
|
328
|
+
} else {
|
|
329
|
+
payloadByteOffset = base + r.offset
|
|
330
|
+
payload = r.readBytesView(payloadLength)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const obj: ObjectPayload = {
|
|
334
|
+
type: 'object',
|
|
335
|
+
byteOffset,
|
|
336
|
+
payloadByteOffset,
|
|
337
|
+
objectId,
|
|
338
|
+
payloadLength,
|
|
339
|
+
payload,
|
|
340
|
+
extensionData,
|
|
341
|
+
}
|
|
342
|
+
if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status
|
|
343
|
+
if (objectProperties !== undefined)
|
|
344
|
+
(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
345
|
+
return obj
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function decodeSubgroupStream(bytes: Uint8Array): DecodeResult<SubgroupStream> {
|
|
349
|
+
try {
|
|
350
|
+
const r = new BufferReader(bytes)
|
|
351
|
+
const h = readSubgroupHeader(r)
|
|
256
352
|
|
|
257
353
|
const objects: ObjectPayload[] = []
|
|
258
354
|
let prevObjectId = -1n
|
|
259
|
-
let
|
|
355
|
+
let isFirst = true
|
|
356
|
+
let subgroupId = h.subgroupId
|
|
260
357
|
|
|
261
358
|
while (r.remaining > 0) {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (firstObject) {
|
|
266
|
-
objectId = delta
|
|
267
|
-
if (subgroupIsFirstObjId && firstObject) {
|
|
268
|
-
subgroupId = objectId
|
|
269
|
-
}
|
|
270
|
-
firstObject = false
|
|
271
|
-
} else {
|
|
272
|
-
objectId = prevObjectId + 1n + delta
|
|
273
|
-
}
|
|
274
|
-
const extensionData = new Uint8Array(0)
|
|
275
|
-
let objectProperties: Record<string, bigint> | undefined
|
|
276
|
-
if (propertiesPresent) {
|
|
277
|
-
const propsLen = Number(r.readVarInt())
|
|
278
|
-
if (propsLen > 0) {
|
|
279
|
-
objectProperties = decodeObjectProperties(r, propsLen)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
const payloadLength = Number(r.readVarInt())
|
|
283
|
-
let payload: Uint8Array
|
|
284
|
-
let status: bigint | undefined
|
|
285
|
-
let payloadByteOffset: number
|
|
286
|
-
if (payloadLength === 0) {
|
|
287
|
-
status = r.readVarInt()
|
|
288
|
-
payloadByteOffset = r.offset
|
|
289
|
-
payload = new Uint8Array(0)
|
|
290
|
-
} else {
|
|
291
|
-
payloadByteOffset = r.offset
|
|
292
|
-
payload = r.readBytesView(payloadLength)
|
|
293
|
-
}
|
|
294
|
-
const obj: ObjectPayload = {
|
|
295
|
-
type: 'object',
|
|
296
|
-
byteOffset,
|
|
297
|
-
payloadByteOffset,
|
|
298
|
-
objectId,
|
|
299
|
-
payloadLength,
|
|
300
|
-
payload,
|
|
301
|
-
extensionData,
|
|
302
|
-
}
|
|
303
|
-
if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status
|
|
304
|
-
if (objectProperties !== undefined)
|
|
305
|
-
(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
359
|
+
const obj = readSubgroupObject(r, h.propertiesPresent, isFirst, prevObjectId, 0)
|
|
360
|
+
if (isFirst && h.subgroupIsFirstObjId) subgroupId = obj.objectId
|
|
361
|
+
isFirst = false
|
|
306
362
|
objects.push(obj)
|
|
307
|
-
prevObjectId = objectId
|
|
363
|
+
prevObjectId = obj.objectId
|
|
308
364
|
}
|
|
309
365
|
|
|
310
366
|
const result: SubgroupStream = {
|
|
311
367
|
type: 'subgroup',
|
|
312
|
-
headerType: streamType,
|
|
313
|
-
trackAlias,
|
|
314
|
-
groupId,
|
|
368
|
+
headerType: h.streamType,
|
|
369
|
+
trackAlias: h.trackAlias,
|
|
370
|
+
groupId: h.groupId,
|
|
315
371
|
subgroupId,
|
|
316
|
-
publisherPriority,
|
|
372
|
+
publisherPriority: h.publisherPriority,
|
|
317
373
|
objects,
|
|
374
|
+
...(h.endOfGroup ? { endOfGroup: true } : {}),
|
|
318
375
|
}
|
|
319
|
-
if (endOfGroup) (result as unknown as Record<string, unknown>).endOfGroup = true
|
|
320
376
|
|
|
321
377
|
return {
|
|
322
378
|
ok: true,
|
|
@@ -394,212 +450,175 @@ export function decodeDatagram(bytes: Uint8Array): DecodeResult<DatagramObject>
|
|
|
394
450
|
}
|
|
395
451
|
}
|
|
396
452
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
'CONSTRAINT_VIOLATION',
|
|
406
|
-
`Expected fetch stream type 0x05, got 0x${streamType.toString(16)}`,
|
|
407
|
-
0,
|
|
408
|
-
),
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
const requestId = r.readVarInt()
|
|
412
|
-
const objects: FetchObjectPayload[] = []
|
|
453
|
+
/** The delta state a fetch stream carries from one Object to the next. */
|
|
454
|
+
interface FetchObjectState {
|
|
455
|
+
prevGroupId: bigint
|
|
456
|
+
prevSubgroupId: bigint
|
|
457
|
+
prevObjectId: bigint
|
|
458
|
+
prevPriority: number
|
|
459
|
+
first: boolean
|
|
460
|
+
}
|
|
413
461
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
let prevPriority = 128
|
|
418
|
-
let first = true
|
|
462
|
+
function newFetchObjectState(): FetchObjectState {
|
|
463
|
+
return { prevGroupId: 0n, prevSubgroupId: 0n, prevObjectId: 0n, prevPriority: 128, first: true }
|
|
464
|
+
}
|
|
419
465
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const groupIdPresent = (flags & 0x08) !== 0
|
|
504
|
-
const priorityPresent = (flags & 0x10) !== 0
|
|
505
|
-
const propsPresent = (flags & 0x20) !== 0
|
|
506
|
-
|
|
507
|
-
if (groupIdPresent) {
|
|
508
|
-
groupId = r.readVarInt()
|
|
509
|
-
} else if (first) {
|
|
510
|
-
return {
|
|
511
|
-
ok: false,
|
|
512
|
-
error: new DecodeError(
|
|
513
|
-
'CONSTRAINT_VIOLATION',
|
|
514
|
-
'First fetch object must include groupId',
|
|
515
|
-
r.offset,
|
|
516
|
-
),
|
|
517
|
-
}
|
|
518
|
-
}
|
|
466
|
+
/**
|
|
467
|
+
* One Object off a fetch stream, for both decoders.
|
|
468
|
+
*
|
|
469
|
+
* Lifted out of `decodeFetchStream` unchanged so the incremental decoder
|
|
470
|
+
* cannot report a different shape for the same bytes. It used to return a
|
|
471
|
+
* bare ObjectPayload -- no Serialization Flags, no Group or Subgroup ID, no
|
|
472
|
+
* Priority, no deltas -- with every `byteOffset` zero, and in DATAGRAM mode
|
|
473
|
+
* it resolved the Object ID against state it never carried.
|
|
474
|
+
*
|
|
475
|
+
* `base` is the absolute offset that `r.offset === 0` corresponds to: 0 for
|
|
476
|
+
* the one-shot decoder, the buffer window's position for the incremental one.
|
|
477
|
+
*/
|
|
478
|
+
function readFetchObject(r: BufferReader, base: number, st: FetchObjectState): FetchObjectPayload {
|
|
479
|
+
const byteOffset = base + r.offset
|
|
480
|
+
const flags = Number(r.readVarInt())
|
|
481
|
+
|
|
482
|
+
let groupId = st.prevGroupId
|
|
483
|
+
let subgroupId = st.prevSubgroupId
|
|
484
|
+
let objectId = st.prevObjectId + 1n
|
|
485
|
+
let payloadLength: number
|
|
486
|
+
let payload: Uint8Array
|
|
487
|
+
let payloadByteOffset: number
|
|
488
|
+
const extensionData = new Uint8Array(0)
|
|
489
|
+
|
|
490
|
+
if (flags >= 0x80) {
|
|
491
|
+
// End of Range: 0x8C = End of Non-Existent Range, 0x10C = End of Unknown Range
|
|
492
|
+
// Per spec §10.4.4.2: Group ID and Object ID are present;
|
|
493
|
+
// Subgroup ID, Priority, Properties are not present.
|
|
494
|
+
if (flags !== 0x8c && flags !== 0x10c) {
|
|
495
|
+
throw new DecodeError(
|
|
496
|
+
'CONSTRAINT_VIOLATION',
|
|
497
|
+
`Unknown serialization flags value: 0x${flags.toString(16)}`,
|
|
498
|
+
r.offset,
|
|
499
|
+
)
|
|
500
|
+
}
|
|
501
|
+
groupId = r.readVarInt()
|
|
502
|
+
objectId = r.readVarInt()
|
|
503
|
+
payloadLength = Number(r.readVarInt())
|
|
504
|
+
payloadByteOffset = base + r.offset
|
|
505
|
+
payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
506
|
+
} else if (flags & 0x40) {
|
|
507
|
+
// DATAGRAM mode: no subgroup_id field
|
|
508
|
+
const objectIdPresent = (flags & 0x04) !== 0
|
|
509
|
+
const groupIdPresent = (flags & 0x08) !== 0
|
|
510
|
+
const priorityPresent = (flags & 0x10) !== 0
|
|
511
|
+
const propsPresent = (flags & 0x20) !== 0
|
|
512
|
+
|
|
513
|
+
if (groupIdPresent) {
|
|
514
|
+
groupId = r.readVarInt()
|
|
515
|
+
}
|
|
516
|
+
// No subgroup in datagram mode
|
|
517
|
+
if (objectIdPresent) {
|
|
518
|
+
objectId = r.readVarInt()
|
|
519
|
+
}
|
|
520
|
+
if (priorityPresent) {
|
|
521
|
+
st.prevPriority = r.readUint8()
|
|
522
|
+
}
|
|
523
|
+
let objectProperties: Record<string, bigint> | undefined
|
|
524
|
+
if (propsPresent) {
|
|
525
|
+
const propsLen = Number(r.readVarInt())
|
|
526
|
+
if (propsLen > 0) {
|
|
527
|
+
objectProperties = decodeObjectProperties(r, propsLen)
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
payloadLength = Number(r.readVarInt())
|
|
531
|
+
payloadByteOffset = base + r.offset
|
|
532
|
+
payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
533
|
+
|
|
534
|
+
const obj: FetchObjectPayload = {
|
|
535
|
+
type: 'object',
|
|
536
|
+
byteOffset,
|
|
537
|
+
payloadByteOffset,
|
|
538
|
+
serializationFlags: flags,
|
|
539
|
+
groupId,
|
|
540
|
+
subgroupId: 0n,
|
|
541
|
+
objectId,
|
|
542
|
+
publisherPriority: st.prevPriority,
|
|
543
|
+
payloadLength,
|
|
544
|
+
payload,
|
|
545
|
+
extensionData,
|
|
546
|
+
}
|
|
547
|
+
if (objectProperties)
|
|
548
|
+
(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
519
549
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
'First fetch object cannot reference prior subgroupId',
|
|
541
|
-
r.offset,
|
|
542
|
-
),
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
subgroupId = prevSubgroupId + 1n
|
|
546
|
-
} else if (subgroupEncoding === 0x03) {
|
|
547
|
-
subgroupId = r.readVarInt()
|
|
548
|
-
}
|
|
550
|
+
st.prevGroupId = groupId
|
|
551
|
+
st.prevObjectId = objectId
|
|
552
|
+
st.first = false
|
|
553
|
+
return obj
|
|
554
|
+
} else {
|
|
555
|
+
const subgroupEncoding = flags & 0x03
|
|
556
|
+
const objectIdPresent = (flags & 0x04) !== 0
|
|
557
|
+
const groupIdPresent = (flags & 0x08) !== 0
|
|
558
|
+
const priorityPresent = (flags & 0x10) !== 0
|
|
559
|
+
const propsPresent = (flags & 0x20) !== 0
|
|
560
|
+
|
|
561
|
+
if (groupIdPresent) {
|
|
562
|
+
groupId = r.readVarInt()
|
|
563
|
+
} else if (st.first) {
|
|
564
|
+
throw new DecodeError(
|
|
565
|
+
'CONSTRAINT_VIOLATION',
|
|
566
|
+
'First fetch object must include groupId',
|
|
567
|
+
r.offset,
|
|
568
|
+
)
|
|
569
|
+
}
|
|
549
570
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
571
|
+
if (subgroupEncoding === 0x00) {
|
|
572
|
+
subgroupId = 0n
|
|
573
|
+
} else if (subgroupEncoding === 0x01) {
|
|
574
|
+
if (st.first) {
|
|
575
|
+
throw new DecodeError(
|
|
576
|
+
'CONSTRAINT_VIOLATION',
|
|
577
|
+
'First fetch object cannot reference prior subgroupId',
|
|
578
|
+
r.offset,
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
subgroupId = st.prevSubgroupId
|
|
582
|
+
} else if (subgroupEncoding === 0x02) {
|
|
583
|
+
if (st.first) {
|
|
584
|
+
throw new DecodeError(
|
|
585
|
+
'CONSTRAINT_VIOLATION',
|
|
586
|
+
'First fetch object cannot reference prior subgroupId',
|
|
587
|
+
r.offset,
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
subgroupId = st.prevSubgroupId + 1n
|
|
591
|
+
} else if (subgroupEncoding === 0x03) {
|
|
592
|
+
subgroupId = r.readVarInt()
|
|
593
|
+
}
|
|
562
594
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
595
|
+
if (objectIdPresent) {
|
|
596
|
+
objectId = r.readVarInt()
|
|
597
|
+
} else if (st.first) {
|
|
598
|
+
throw new DecodeError(
|
|
599
|
+
'CONSTRAINT_VIOLATION',
|
|
600
|
+
'First fetch object must include objectId',
|
|
601
|
+
r.offset,
|
|
602
|
+
)
|
|
603
|
+
}
|
|
566
604
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (propsLen > 0) {
|
|
571
|
-
objectProperties = decodeObjectProperties(r, propsLen)
|
|
572
|
-
}
|
|
573
|
-
}
|
|
605
|
+
if (priorityPresent) {
|
|
606
|
+
st.prevPriority = r.readUint8()
|
|
607
|
+
}
|
|
574
608
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const obj: FetchObjectPayload = {
|
|
581
|
-
type: 'object',
|
|
582
|
-
byteOffset,
|
|
583
|
-
payloadByteOffset,
|
|
584
|
-
serializationFlags: flags,
|
|
585
|
-
groupId,
|
|
586
|
-
subgroupId,
|
|
587
|
-
objectId,
|
|
588
|
-
publisherPriority: prevPriority,
|
|
589
|
-
payloadLength,
|
|
590
|
-
payload,
|
|
591
|
-
extensionData,
|
|
592
|
-
}
|
|
593
|
-
;(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
594
|
-
objects.push(obj)
|
|
595
|
-
prevGroupId = groupId
|
|
596
|
-
prevSubgroupId = subgroupId
|
|
597
|
-
prevObjectId = objectId
|
|
598
|
-
first = false
|
|
599
|
-
continue
|
|
600
|
-
}
|
|
609
|
+
let objectProperties: Record<string, bigint> | undefined
|
|
610
|
+
if (propsPresent) {
|
|
611
|
+
const propsLen = Number(r.readVarInt())
|
|
612
|
+
if (propsLen > 0) {
|
|
613
|
+
objectProperties = decodeObjectProperties(r, propsLen)
|
|
601
614
|
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
payloadLength = Number(r.readVarInt())
|
|
618
|
+
payloadByteOffset = base + r.offset
|
|
619
|
+
payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
602
620
|
|
|
621
|
+
if (objectProperties) {
|
|
603
622
|
const obj: FetchObjectPayload = {
|
|
604
623
|
type: 'object',
|
|
605
624
|
byteOffset,
|
|
@@ -608,17 +627,63 @@ export function decodeFetchStream(bytes: Uint8Array): DecodeResult<FetchStream>
|
|
|
608
627
|
groupId,
|
|
609
628
|
subgroupId,
|
|
610
629
|
objectId,
|
|
611
|
-
publisherPriority: prevPriority,
|
|
630
|
+
publisherPriority: st.prevPriority,
|
|
612
631
|
payloadLength,
|
|
613
632
|
payload,
|
|
614
633
|
extensionData,
|
|
615
634
|
}
|
|
616
|
-
|
|
635
|
+
;(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
636
|
+
st.prevGroupId = groupId
|
|
637
|
+
st.prevSubgroupId = subgroupId
|
|
638
|
+
st.prevObjectId = objectId
|
|
639
|
+
st.first = false
|
|
640
|
+
return obj
|
|
641
|
+
}
|
|
642
|
+
}
|
|
617
643
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
644
|
+
const obj: FetchObjectPayload = {
|
|
645
|
+
type: 'object',
|
|
646
|
+
byteOffset,
|
|
647
|
+
payloadByteOffset,
|
|
648
|
+
serializationFlags: flags,
|
|
649
|
+
groupId,
|
|
650
|
+
subgroupId,
|
|
651
|
+
objectId,
|
|
652
|
+
publisherPriority: st.prevPriority,
|
|
653
|
+
payloadLength,
|
|
654
|
+
payload,
|
|
655
|
+
extensionData,
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
st.prevGroupId = groupId
|
|
659
|
+
st.prevSubgroupId = subgroupId
|
|
660
|
+
st.prevObjectId = objectId
|
|
661
|
+
st.first = false
|
|
662
|
+
|
|
663
|
+
return obj
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export function decodeFetchStream(bytes: Uint8Array): DecodeResult<FetchStream> {
|
|
667
|
+
try {
|
|
668
|
+
const r = new BufferReader(bytes)
|
|
669
|
+
const streamType = r.readVarInt()
|
|
670
|
+
if (streamType !== FETCH_STREAM_TYPE) {
|
|
671
|
+
return {
|
|
672
|
+
ok: false,
|
|
673
|
+
error: new DecodeError(
|
|
674
|
+
'CONSTRAINT_VIOLATION',
|
|
675
|
+
`Expected fetch stream type 0x05, got 0x${streamType.toString(16)}`,
|
|
676
|
+
0,
|
|
677
|
+
),
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const requestId = r.readVarInt()
|
|
681
|
+
const objects: FetchObjectPayload[] = []
|
|
682
|
+
|
|
683
|
+
const st = newFetchObjectState()
|
|
684
|
+
|
|
685
|
+
while (r.remaining > 0) {
|
|
686
|
+
objects.push(readFetchObject(r, 0, st))
|
|
622
687
|
}
|
|
623
688
|
|
|
624
689
|
return {
|
|
@@ -652,77 +717,107 @@ export function decodeDataStream(
|
|
|
652
717
|
|
|
653
718
|
// ─── Data Stream Decoders ──────────────────────────────────────────────────────
|
|
654
719
|
|
|
720
|
+
/**
|
|
721
|
+
* A data stream's bytes, accumulated across chunks.
|
|
722
|
+
*
|
|
723
|
+
* The decoders below used to allocate an array of `unread + chunk.length` and
|
|
724
|
+
* copy both halves into it on *every* chunk. Capacity now grows geometrically,
|
|
725
|
+
* so most chunks are a single `set` into spare room.
|
|
726
|
+
*
|
|
727
|
+
* **It never compacts in place.** Object payloads are handed out as views into
|
|
728
|
+
* this buffer (`readBytesView`), so moving bytes within it would corrupt an
|
|
729
|
+
* object that has already been emitted. The consumed prefix is dropped only
|
|
730
|
+
* when a fresh array is being allocated anyway, which leaves the old array
|
|
731
|
+
* alive for exactly as long as the views into it are.
|
|
732
|
+
*/
|
|
733
|
+
class StreamBuffer {
|
|
734
|
+
private buf = new Uint8Array(0)
|
|
735
|
+
/** Bytes written into `buf`. */
|
|
736
|
+
private len = 0
|
|
737
|
+
/** Consumed prefix of `buf`; `base + offset` is the position in the stream. */
|
|
738
|
+
offset = 0
|
|
739
|
+
/** Absolute position of `buf[0]` within the stream. */
|
|
740
|
+
base = 0
|
|
741
|
+
|
|
742
|
+
get unread(): number {
|
|
743
|
+
return this.len - this.offset
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
append(chunk: Uint8Array): void {
|
|
747
|
+
if (this.len + chunk.length > this.buf.length) {
|
|
748
|
+
const live = this.len - this.offset
|
|
749
|
+
const needed = live + chunk.length
|
|
750
|
+
// Grow until reclaiming the consumed prefix leaves the buffer at least
|
|
751
|
+
// half free. Sizing to `needed` -- which is what this did first -- leaves
|
|
752
|
+
// no room at all, so the next chunk reallocates too and the buffer
|
|
753
|
+
// reallocates on every single chunk, which is the cost it exists to
|
|
754
|
+
// avoid. Capacity never shrinks, so it settles at roughly twice the
|
|
755
|
+
// largest `live + chunk` this stream has seen.
|
|
756
|
+
let cap = Math.max(4096, this.buf.length)
|
|
757
|
+
while (cap < needed * 2) cap *= 2
|
|
758
|
+
// A fresh array every time, including when the capacity is unchanged:
|
|
759
|
+
// the old one is still pinned by the payload views handed out of it, so
|
|
760
|
+
// it can be neither compacted nor reused.
|
|
761
|
+
const next = new Uint8Array(cap)
|
|
762
|
+
next.set(this.buf.subarray(this.offset, this.len), 0)
|
|
763
|
+
this.buf = next
|
|
764
|
+
this.base += this.offset
|
|
765
|
+
this.len = live
|
|
766
|
+
this.offset = 0
|
|
767
|
+
}
|
|
768
|
+
this.buf.set(chunk, this.len)
|
|
769
|
+
this.len += chunk.length
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* The written bytes, for a reader positioned at `offset`. Bounded by `len`
|
|
774
|
+
* rather than by capacity, so `BufferReader.remaining` counts real bytes and
|
|
775
|
+
* not the spare room after them.
|
|
776
|
+
*/
|
|
777
|
+
written(): Uint8Array {
|
|
778
|
+
return this.buf.subarray(0, this.len)
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
655
782
|
export function createSubgroupStreamDecoder(): TransformStream<
|
|
656
783
|
Uint8Array,
|
|
657
784
|
SubgroupStreamHeader | ObjectPayload
|
|
658
785
|
> {
|
|
659
|
-
|
|
660
|
-
let
|
|
786
|
+
const b = new StreamBuffer()
|
|
787
|
+
let header: SubgroupHeaderFields | null = null
|
|
661
788
|
let headerEmitted = false
|
|
662
789
|
let prevObjectId = -1n
|
|
663
|
-
let
|
|
664
|
-
|
|
665
|
-
|
|
790
|
+
let isFirst = true
|
|
791
|
+
|
|
792
|
+
function emitHeader(
|
|
793
|
+
controller: TransformStreamDefaultController<SubgroupStreamHeader | ObjectPayload>,
|
|
794
|
+
h: SubgroupHeaderFields,
|
|
795
|
+
subgroupId: bigint,
|
|
796
|
+
): void {
|
|
797
|
+
controller.enqueue({
|
|
798
|
+
type: 'subgroup_header',
|
|
799
|
+
headerType: h.streamType,
|
|
800
|
+
trackAlias: h.trackAlias,
|
|
801
|
+
groupId: h.groupId,
|
|
802
|
+
subgroupId,
|
|
803
|
+
publisherPriority: h.publisherPriority,
|
|
804
|
+
...(h.endOfGroup ? { endOfGroup: true } : {}),
|
|
805
|
+
})
|
|
806
|
+
headerEmitted = true
|
|
807
|
+
}
|
|
666
808
|
|
|
667
809
|
return new TransformStream<Uint8Array, SubgroupStreamHeader | ObjectPayload>({
|
|
668
810
|
transform(chunk, controller) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
const newBuffer = new Uint8Array(buffer.length + chunk.length)
|
|
674
|
-
newBuffer.set(buffer, 0)
|
|
675
|
-
newBuffer.set(chunk, buffer.length)
|
|
676
|
-
buffer = newBuffer
|
|
811
|
+
b.append(chunk)
|
|
812
|
+
// One view and one reader per chunk. Both used to be allocated per
|
|
813
|
+
// object, and an object is the thing there are a great many of.
|
|
814
|
+
const view = b.written()
|
|
677
815
|
|
|
678
|
-
if (
|
|
816
|
+
if (header === null) {
|
|
679
817
|
try {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
if (
|
|
684
|
-
!(
|
|
685
|
-
(streamType >= 0x10 && streamType <= 0x1d) ||
|
|
686
|
-
(streamType >= 0x30 && streamType <= 0x3d)
|
|
687
|
-
)
|
|
688
|
-
) {
|
|
689
|
-
controller.error(
|
|
690
|
-
new DecodeError(
|
|
691
|
-
'CONSTRAINT_VIOLATION',
|
|
692
|
-
`Expected subgroup stream type, got 0x${streamType.toString(16)}`,
|
|
693
|
-
0,
|
|
694
|
-
),
|
|
695
|
-
)
|
|
696
|
-
return
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
_extensionsPresent = (streamType & 0x01) !== 0
|
|
700
|
-
const hasSubgroupField = (streamType & 0x04) !== 0
|
|
701
|
-
_subgroupIsFirstObjId = (streamType & 0x02) !== 0 && !hasSubgroupField
|
|
702
|
-
const hasPriority = streamType < 0x30
|
|
703
|
-
|
|
704
|
-
const trackAlias = r.readVarInt()
|
|
705
|
-
const groupId = r.readVarInt()
|
|
706
|
-
|
|
707
|
-
let subgroupId = 0n
|
|
708
|
-
if (hasSubgroupField) {
|
|
709
|
-
subgroupId = r.readVarInt()
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
let publisherPriority = 128
|
|
713
|
-
if (hasPriority) {
|
|
714
|
-
publisherPriority = r.readUint8()
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
controller.enqueue({
|
|
718
|
-
type: 'subgroup_header',
|
|
719
|
-
trackAlias,
|
|
720
|
-
groupId,
|
|
721
|
-
subgroupId,
|
|
722
|
-
publisherPriority,
|
|
723
|
-
})
|
|
724
|
-
headerEmitted = true
|
|
725
|
-
offset += r.offset
|
|
818
|
+
const hr = new BufferReader(view, b.offset)
|
|
819
|
+
header = readSubgroupHeader(hr)
|
|
820
|
+
b.offset = hr.offset
|
|
726
821
|
} catch (e) {
|
|
727
822
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
728
823
|
return
|
|
@@ -730,50 +825,46 @@ export function createSubgroupStreamDecoder(): TransformStream<
|
|
|
730
825
|
controller.error(e)
|
|
731
826
|
return
|
|
732
827
|
}
|
|
828
|
+
// Where the Subgroup ID is the first Object's ID, that Object has not
|
|
829
|
+
// been read yet. Holding the header back until it has is the only way
|
|
830
|
+
// to emit the value the one-shot decoder reports; every other mode
|
|
831
|
+
// knows its Subgroup ID already.
|
|
832
|
+
if (!header.subgroupIsFirstObjId) emitHeader(controller, header, header.subgroupId)
|
|
733
833
|
}
|
|
734
834
|
|
|
735
|
-
|
|
835
|
+
const h = header
|
|
836
|
+
if (h === null) return
|
|
837
|
+
|
|
838
|
+
const r = new BufferReader(view, b.offset)
|
|
839
|
+
while (r.remaining > 0) {
|
|
840
|
+
// Where this Object starts, so a partial one is re-read from the top
|
|
841
|
+
// once the rest of it arrives.
|
|
842
|
+
const start = r.offset
|
|
843
|
+
let obj: ObjectPayload
|
|
736
844
|
try {
|
|
737
|
-
|
|
738
|
-
const delta = r.readVarInt()
|
|
739
|
-
let objectId: bigint
|
|
740
|
-
if (firstObject) {
|
|
741
|
-
objectId = delta
|
|
742
|
-
firstObject = false
|
|
743
|
-
} else {
|
|
744
|
-
objectId = prevObjectId + 1n + delta
|
|
745
|
-
}
|
|
746
|
-
let extensionData = new Uint8Array(0)
|
|
747
|
-
if (_extensionsPresent) {
|
|
748
|
-
const extLen = Number(r.readVarInt())
|
|
749
|
-
extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
|
|
750
|
-
}
|
|
751
|
-
const payloadLength = Number(r.readVarInt())
|
|
752
|
-
const payloadByteOffset = r.offset
|
|
753
|
-
const payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
754
|
-
controller.enqueue({
|
|
755
|
-
type: 'object',
|
|
756
|
-
objectId,
|
|
757
|
-
payloadLength,
|
|
758
|
-
payload,
|
|
759
|
-
extensionData,
|
|
760
|
-
byteOffset: 0,
|
|
761
|
-
payloadByteOffset,
|
|
762
|
-
})
|
|
763
|
-
offset += r.offset
|
|
764
|
-
prevObjectId = objectId
|
|
845
|
+
obj = readSubgroupObject(r, h.propertiesPresent, isFirst, prevObjectId, b.base)
|
|
765
846
|
} catch (e) {
|
|
766
847
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
767
|
-
|
|
848
|
+
b.offset = start
|
|
849
|
+
return
|
|
768
850
|
}
|
|
769
851
|
controller.error(e)
|
|
770
852
|
return
|
|
771
853
|
}
|
|
854
|
+
if (!headerEmitted) emitHeader(controller, h, obj.objectId)
|
|
855
|
+
isFirst = false
|
|
856
|
+
b.offset = r.offset
|
|
857
|
+
prevObjectId = obj.objectId
|
|
858
|
+
controller.enqueue(obj)
|
|
772
859
|
}
|
|
773
860
|
},
|
|
774
861
|
|
|
775
862
|
flush(controller) {
|
|
776
|
-
|
|
863
|
+
// A stream carrying a header and no Objects still has a header to report,
|
|
864
|
+
// and its Subgroup ID is then zero -- the same value the one-shot decoder
|
|
865
|
+
// returns when it finds no first Object to derive one from.
|
|
866
|
+
if (header !== null && !headerEmitted) emitHeader(controller, header, 0n)
|
|
867
|
+
if (b.unread > 0) {
|
|
777
868
|
controller.error(new DecodeError('UNEXPECTED_END', 'Stream ended with incomplete data', 0))
|
|
778
869
|
}
|
|
779
870
|
},
|
|
@@ -784,24 +875,23 @@ export function createFetchStreamDecoder(): TransformStream<
|
|
|
784
875
|
Uint8Array,
|
|
785
876
|
FetchStreamHeader | ObjectPayload
|
|
786
877
|
> {
|
|
787
|
-
|
|
788
|
-
let offset = 0
|
|
878
|
+
const b = new StreamBuffer()
|
|
789
879
|
let headerEmitted = false
|
|
880
|
+
/**
|
|
881
|
+
* The delta state the Objects carry between them. This decoder used to keep
|
|
882
|
+
* none, which is why DATAGRAM-mode Object IDs came out wrong: they resolve
|
|
883
|
+
* against the previous Object's ID.
|
|
884
|
+
*/
|
|
885
|
+
const st = newFetchObjectState()
|
|
790
886
|
|
|
791
887
|
return new TransformStream<Uint8Array, FetchStreamHeader | ObjectPayload>({
|
|
792
888
|
transform(chunk, controller) {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
offset = 0
|
|
796
|
-
}
|
|
797
|
-
const newBuffer = new Uint8Array(buffer.length + chunk.length)
|
|
798
|
-
newBuffer.set(buffer, 0)
|
|
799
|
-
newBuffer.set(chunk, buffer.length)
|
|
800
|
-
buffer = newBuffer
|
|
889
|
+
b.append(chunk)
|
|
890
|
+
const view = b.written()
|
|
801
891
|
|
|
802
892
|
if (!headerEmitted) {
|
|
803
893
|
try {
|
|
804
|
-
const r = new BufferReader(
|
|
894
|
+
const r = new BufferReader(view, b.offset)
|
|
805
895
|
const streamType = r.readVarInt()
|
|
806
896
|
if (streamType !== FETCH_STREAM_TYPE) {
|
|
807
897
|
controller.error(
|
|
@@ -816,7 +906,7 @@ export function createFetchStreamDecoder(): TransformStream<
|
|
|
816
906
|
const requestId = r.readVarInt()
|
|
817
907
|
controller.enqueue({ type: 'fetch_header', requestId })
|
|
818
908
|
headerEmitted = true
|
|
819
|
-
offset
|
|
909
|
+
b.offset = r.offset
|
|
820
910
|
} catch (e) {
|
|
821
911
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
822
912
|
return
|
|
@@ -826,51 +916,27 @@ export function createFetchStreamDecoder(): TransformStream<
|
|
|
826
916
|
}
|
|
827
917
|
}
|
|
828
918
|
|
|
829
|
-
|
|
919
|
+
const r = new BufferReader(view, b.offset)
|
|
920
|
+
while (r.remaining > 0) {
|
|
921
|
+
const start = r.offset
|
|
922
|
+
let obj: FetchObjectPayload
|
|
830
923
|
try {
|
|
831
|
-
|
|
832
|
-
const flags = Number(r.readVarInt())
|
|
833
|
-
const objectIdPresent = (flags & 0x04) !== 0
|
|
834
|
-
const groupIdPresent = (flags & 0x08) !== 0
|
|
835
|
-
const priorityPresent = (flags & 0x10) !== 0
|
|
836
|
-
const extensionsPresent = (flags & 0x20) !== 0
|
|
837
|
-
const subgroupEncoding = flags & 0x03
|
|
838
|
-
|
|
839
|
-
if (groupIdPresent) r.readVarInt()
|
|
840
|
-
if (subgroupEncoding === 0x03) r.readVarInt()
|
|
841
|
-
let objectId = 0n
|
|
842
|
-
if (objectIdPresent) objectId = r.readVarInt()
|
|
843
|
-
if (priorityPresent) r.readUint8()
|
|
844
|
-
let extensionData = new Uint8Array(0)
|
|
845
|
-
if (extensionsPresent) {
|
|
846
|
-
const extLen = Number(r.readVarInt())
|
|
847
|
-
extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
|
|
848
|
-
}
|
|
849
|
-
const payloadLength = Number(r.readVarInt())
|
|
850
|
-
const payloadByteOffset = r.offset
|
|
851
|
-
const payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
852
|
-
controller.enqueue({
|
|
853
|
-
type: 'object',
|
|
854
|
-
objectId,
|
|
855
|
-
payloadLength,
|
|
856
|
-
payload,
|
|
857
|
-
extensionData,
|
|
858
|
-
byteOffset: 0,
|
|
859
|
-
payloadByteOffset,
|
|
860
|
-
})
|
|
861
|
-
offset += r.offset
|
|
924
|
+
obj = readFetchObject(r, b.base, st)
|
|
862
925
|
} catch (e) {
|
|
863
926
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
864
|
-
|
|
927
|
+
b.offset = start
|
|
928
|
+
return
|
|
865
929
|
}
|
|
866
930
|
controller.error(e)
|
|
867
931
|
return
|
|
868
932
|
}
|
|
933
|
+
b.offset = r.offset
|
|
934
|
+
controller.enqueue(obj)
|
|
869
935
|
}
|
|
870
936
|
},
|
|
871
937
|
|
|
872
938
|
flush(controller) {
|
|
873
|
-
if (
|
|
939
|
+
if (b.unread > 0) {
|
|
874
940
|
controller.error(new DecodeError('UNEXPECTED_END', 'Stream ended with incomplete data', 0))
|
|
875
941
|
}
|
|
876
942
|
},
|
|
@@ -878,56 +944,82 @@ export function createFetchStreamDecoder(): TransformStream<
|
|
|
878
944
|
}
|
|
879
945
|
|
|
880
946
|
export function createDataStreamDecoder(): TransformStream<Uint8Array, DataStreamEvent> {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
947
|
+
/**
|
|
948
|
+
* Delegation that actually delegates.
|
|
949
|
+
*
|
|
950
|
+
* This function used to pick an inner decoder and then never write a byte to
|
|
951
|
+
* it: chunks accumulated in a local buffer and `flush` decoded the whole
|
|
952
|
+
* stream in one shot. Every event therefore arrived at end-of-stream, which
|
|
953
|
+
* on a subscription that stays open means no events at all. The inner
|
|
954
|
+
* decoder's readable is now pumped into this one's controller as the bytes
|
|
955
|
+
* arrive.
|
|
956
|
+
*
|
|
957
|
+
* The first byte still decides which decoder to use, on exactly the range
|
|
958
|
+
* this draft accepted before.
|
|
959
|
+
*/
|
|
960
|
+
let writer: WritableStreamDefaultWriter<Uint8Array> | null = null
|
|
961
|
+
let pump: Promise<void> = Promise.resolve()
|
|
962
|
+
let broken = false
|
|
963
|
+
|
|
964
|
+
function attach(
|
|
965
|
+
inner: TransformStream<Uint8Array, DataStreamEvent>,
|
|
966
|
+
controller: TransformStreamDefaultController<DataStreamEvent>,
|
|
967
|
+
): void {
|
|
968
|
+
writer = inner.writable.getWriter()
|
|
969
|
+
const reader = inner.readable.getReader()
|
|
970
|
+
pump = (async () => {
|
|
971
|
+
try {
|
|
972
|
+
for (;;) {
|
|
973
|
+
const { done, value } = await reader.read()
|
|
974
|
+
if (done) break
|
|
975
|
+
controller.enqueue(value)
|
|
976
|
+
}
|
|
977
|
+
} catch (e) {
|
|
978
|
+
// The inner decoder rejected the stream. It owns the diagnosis, so its
|
|
979
|
+
// error is the one that surfaces.
|
|
980
|
+
if (!broken) {
|
|
981
|
+
broken = true
|
|
982
|
+
controller.error(e)
|
|
915
983
|
}
|
|
916
984
|
}
|
|
917
|
-
}
|
|
985
|
+
})()
|
|
986
|
+
}
|
|
918
987
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
988
|
+
async function feed(bytes: Uint8Array): Promise<void> {
|
|
989
|
+
if (writer === null || broken || bytes.length === 0) return
|
|
990
|
+
try {
|
|
991
|
+
await writer.write(bytes)
|
|
992
|
+
} catch {
|
|
993
|
+
// Writing to an errored inner stream throws the same error the pump is
|
|
994
|
+
// already reporting; swallow it here so it is reported once.
|
|
995
|
+
broken = true
|
|
996
|
+
}
|
|
997
|
+
}
|
|
922
998
|
|
|
923
|
-
|
|
924
|
-
|
|
999
|
+
return new TransformStream<Uint8Array, DataStreamEvent>({
|
|
1000
|
+
async transform(chunk, controller) {
|
|
1001
|
+
if (broken) return
|
|
1002
|
+
if (writer !== null) {
|
|
1003
|
+
await feed(chunk)
|
|
1004
|
+
return
|
|
1005
|
+
}
|
|
1006
|
+
// One byte is enough to choose, and until there is one there is nothing
|
|
1007
|
+
// to choose from.
|
|
1008
|
+
if (chunk.length === 0) return
|
|
1009
|
+
const firstByte = chunk[0]!
|
|
925
1010
|
|
|
926
1011
|
if ((firstByte >= 0x10 && firstByte <= 0x1d) || (firstByte >= 0x30 && firstByte <= 0x3d)) {
|
|
927
|
-
|
|
1012
|
+
attach(
|
|
1013
|
+
createSubgroupStreamDecoder() as unknown as TransformStream<Uint8Array, DataStreamEvent>,
|
|
1014
|
+
controller,
|
|
1015
|
+
)
|
|
928
1016
|
} else if (firstByte === 0x05) {
|
|
929
|
-
|
|
1017
|
+
attach(
|
|
1018
|
+
createFetchStreamDecoder() as unknown as TransformStream<Uint8Array, DataStreamEvent>,
|
|
1019
|
+
controller,
|
|
1020
|
+
)
|
|
930
1021
|
} else {
|
|
1022
|
+
broken = true
|
|
931
1023
|
controller.error(
|
|
932
1024
|
new DecodeError(
|
|
933
1025
|
'CONSTRAINT_VIOLATION',
|
|
@@ -938,32 +1030,19 @@ export function createDataStreamDecoder(): TransformStream<Uint8Array, DataStrea
|
|
|
938
1030
|
return
|
|
939
1031
|
}
|
|
940
1032
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1033
|
+
// The type byte is part of the stream the inner decoder reads, so the
|
|
1034
|
+
// whole chunk goes in, first byte included.
|
|
1035
|
+
await feed(chunk)
|
|
1036
|
+
},
|
|
945
1037
|
|
|
946
|
-
|
|
947
|
-
if (
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
subgroupId: stream.subgroupId,
|
|
953
|
-
publisherPriority: stream.publisherPriority,
|
|
954
|
-
})
|
|
955
|
-
for (const obj of stream.objects) {
|
|
956
|
-
controller.enqueue(obj)
|
|
957
|
-
}
|
|
958
|
-
} else if (stream.type === 'fetch') {
|
|
959
|
-
controller.enqueue({
|
|
960
|
-
type: 'fetch_header',
|
|
961
|
-
requestId: stream.requestId,
|
|
962
|
-
})
|
|
963
|
-
for (const obj of stream.objects) {
|
|
964
|
-
controller.enqueue(obj)
|
|
965
|
-
}
|
|
1038
|
+
async flush() {
|
|
1039
|
+
if (writer === null) return
|
|
1040
|
+
try {
|
|
1041
|
+
await writer.close()
|
|
1042
|
+
} catch {
|
|
1043
|
+
// Reported by the pump.
|
|
966
1044
|
}
|
|
1045
|
+
await pump
|
|
967
1046
|
},
|
|
968
1047
|
})
|
|
969
1048
|
}
|