@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
|
@@ -416,106 +416,171 @@ function resolveGroupId(
|
|
|
416
416
|
return groupId
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
419
|
+
/**
|
|
420
|
+
* The SUBGROUP_HEADER fields, read once for both decoders.
|
|
421
|
+
*
|
|
422
|
+
* `decodeSubgroupStream` and `createSubgroupStreamDecoder` share this single
|
|
423
|
+
* reader so they cannot disagree about a header: the Type Flags, the
|
|
424
|
+
* end-of-group and first-object flags, and the Subgroup ID under the mode that
|
|
425
|
+
* derives it from the first Object all come from here. Two readers drift.
|
|
426
|
+
*/
|
|
427
|
+
interface SubgroupHeaderFields {
|
|
428
|
+
readonly streamType: number
|
|
429
|
+
readonly propertiesPresent: boolean
|
|
430
|
+
/** Subgroup Mode 0b01: the Subgroup ID is the first Object's ID. */
|
|
431
|
+
readonly subgroupIsFirstObjId: boolean
|
|
432
|
+
readonly endOfGroup: boolean
|
|
433
|
+
readonly firstObjectFlag: boolean
|
|
434
|
+
readonly trackAlias: bigint
|
|
435
|
+
readonly groupId: bigint
|
|
436
|
+
/** Zero under `subgroupIsFirstObjId` until the first Object has been read. */
|
|
437
|
+
readonly subgroupId: bigint
|
|
438
|
+
readonly publisherPriority: number
|
|
439
|
+
}
|
|
431
440
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
441
|
+
function readSubgroupHeader(r: BufferReader): SubgroupHeaderFields {
|
|
442
|
+
// DECISION (DECISIONS.md D5): permissive on receive. The Type Flags are a
|
|
443
|
+
// vi64 and Section 1.4.1 allows non-minimal encodings, so 0x10 may legally
|
|
444
|
+
// arrive as the two-byte 0x8010. Read the value, then judge the value.
|
|
445
|
+
const typeFlags = r.readVarInt()
|
|
446
|
+
const flagsError = subgroupTypeFlagsError(typeFlags)
|
|
447
|
+
if (flagsError !== null) {
|
|
448
|
+
throw new DecodeError('CONSTRAINT_VIOLATION', flagsError, 0)
|
|
449
|
+
}
|
|
450
|
+
const streamType = Number(typeFlags)
|
|
439
451
|
|
|
440
|
-
|
|
441
|
-
|
|
452
|
+
const propertiesPresent = (streamType & 0x01) !== 0
|
|
453
|
+
const subgroupMode = (streamType & 0x06) >> 1
|
|
454
|
+
const hasSubgroupField = subgroupMode === 0x02
|
|
455
|
+
const subgroupIsFirstObjId = subgroupMode === 0x01
|
|
456
|
+
const endOfGroup = (streamType & 0x08) !== 0
|
|
457
|
+
// DEFAULT_PRIORITY bit (0x20): when set, priority is absent.
|
|
458
|
+
const hasPriority = (streamType & 0x20) === 0
|
|
459
|
+
const firstObjectFlag = (streamType & 0x40) !== 0
|
|
442
460
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
subgroupId = r.readVarInt()
|
|
446
|
-
}
|
|
461
|
+
const trackAlias = r.readVarInt()
|
|
462
|
+
const groupId = r.readVarInt()
|
|
447
463
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
464
|
+
let subgroupId = 0n
|
|
465
|
+
if (hasSubgroupField) {
|
|
466
|
+
subgroupId = r.readVarInt()
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
let publisherPriority = 128
|
|
470
|
+
if (hasPriority) {
|
|
471
|
+
publisherPriority = r.readUint8()
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
streamType,
|
|
476
|
+
propertiesPresent,
|
|
477
|
+
subgroupIsFirstObjId,
|
|
478
|
+
endOfGroup,
|
|
479
|
+
firstObjectFlag,
|
|
480
|
+
trackAlias,
|
|
481
|
+
groupId,
|
|
482
|
+
subgroupId,
|
|
483
|
+
publisherPriority,
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* One Object off a subgroup stream, for both decoders.
|
|
489
|
+
*
|
|
490
|
+
* `base` is the absolute offset within the stream that `r.offset === 0`
|
|
491
|
+
* corresponds to. The one-shot decoder reads from a reader spanning the whole
|
|
492
|
+
* stream and passes 0; the incremental decoder reads from a window over its
|
|
493
|
+
* buffer and passes that window's position, which is what makes `byteOffset`
|
|
494
|
+
* mean the same thing on both paths instead of being hardcoded to zero on one
|
|
495
|
+
* of them.
|
|
496
|
+
*
|
|
497
|
+
* Throws `UNEXPECTED_END` when the Object is not yet complete, which the
|
|
498
|
+
* incremental decoder treats as "wait for more bytes" rather than as an error.
|
|
499
|
+
*/
|
|
500
|
+
function readSubgroupObject(
|
|
501
|
+
r: BufferReader,
|
|
502
|
+
propertiesPresent: boolean,
|
|
503
|
+
isFirst: boolean,
|
|
504
|
+
prevObjectId: bigint,
|
|
505
|
+
base: number,
|
|
506
|
+
): ObjectPayload {
|
|
507
|
+
const byteOffset = base + r.offset
|
|
508
|
+
const delta = r.readVarInt()
|
|
509
|
+
// Section 11.4.2: "Object ID = previous Object ID + Object ID Delta + 1", or
|
|
510
|
+
// the delta itself for the first Object on the stream.
|
|
511
|
+
const objectId = isFirst ? delta : prevObjectId + 1n + delta
|
|
512
|
+
|
|
513
|
+
const extensionData = new Uint8Array(0)
|
|
514
|
+
let objectProperties: Record<string, bigint> | undefined
|
|
515
|
+
if (propertiesPresent) {
|
|
516
|
+
const propsLen = Number(r.readVarInt())
|
|
517
|
+
if (propsLen > 0) {
|
|
518
|
+
objectProperties = decodeObjectProperties(r, propsLen)
|
|
451
519
|
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const payloadLength = Number(r.readVarInt())
|
|
523
|
+
let payload: Uint8Array
|
|
524
|
+
let status: bigint | undefined
|
|
525
|
+
let payloadByteOffset: number
|
|
526
|
+
if (payloadLength === 0) {
|
|
527
|
+
// Section 11.4.2: "The Object Status field is only sent if the Object
|
|
528
|
+
// Payload Length is zero." Skipping it leaves the varint to be read as the
|
|
529
|
+
// next Object's Object ID Delta, which desynchronises the rest of the
|
|
530
|
+
// stream silently.
|
|
531
|
+
status = r.readVarInt()
|
|
532
|
+
payloadByteOffset = base + r.offset
|
|
533
|
+
payload = new Uint8Array(0)
|
|
534
|
+
} else {
|
|
535
|
+
payloadByteOffset = base + r.offset
|
|
536
|
+
payload = r.readBytesView(payloadLength)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const obj: ObjectPayload = {
|
|
540
|
+
type: 'object',
|
|
541
|
+
byteOffset,
|
|
542
|
+
payloadByteOffset,
|
|
543
|
+
objectId,
|
|
544
|
+
objectIdDelta: delta,
|
|
545
|
+
payloadLength,
|
|
546
|
+
payload,
|
|
547
|
+
extensionData,
|
|
548
|
+
}
|
|
549
|
+
if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status
|
|
550
|
+
if (objectProperties !== undefined)
|
|
551
|
+
(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
552
|
+
return obj
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export function decodeSubgroupStream(bytes: Uint8Array): DecodeResult<SubgroupStream> {
|
|
556
|
+
try {
|
|
557
|
+
const r = new BufferReader(bytes)
|
|
558
|
+
const h = readSubgroupHeader(r)
|
|
452
559
|
|
|
453
560
|
const objects: ObjectPayload[] = []
|
|
454
561
|
let prevObjectId = -1n
|
|
455
562
|
let isFirst = true
|
|
563
|
+
let subgroupId = h.subgroupId
|
|
456
564
|
|
|
457
565
|
while (r.remaining > 0) {
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (isFirst) {
|
|
462
|
-
objectId = delta
|
|
463
|
-
if (subgroupIsFirstObjId) {
|
|
464
|
-
subgroupId = objectId
|
|
465
|
-
}
|
|
466
|
-
isFirst = false
|
|
467
|
-
} else {
|
|
468
|
-
// Section 11.4.2 delta encoding: prior + delta + 1.
|
|
469
|
-
objectId = prevObjectId + 1n + delta
|
|
470
|
-
}
|
|
471
|
-
const extensionData = new Uint8Array(0)
|
|
472
|
-
let objectProperties: Record<string, bigint> | undefined
|
|
473
|
-
if (propertiesPresent) {
|
|
474
|
-
const propsLen = Number(r.readVarInt())
|
|
475
|
-
if (propsLen > 0) {
|
|
476
|
-
objectProperties = decodeObjectProperties(r, propsLen)
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
const payloadLength = Number(r.readVarInt())
|
|
480
|
-
let payload: Uint8Array
|
|
481
|
-
let status: bigint | undefined
|
|
482
|
-
let payloadByteOffset: number
|
|
483
|
-
if (payloadLength === 0) {
|
|
484
|
-
status = r.readVarInt()
|
|
485
|
-
payloadByteOffset = r.offset
|
|
486
|
-
payload = new Uint8Array(0)
|
|
487
|
-
} else {
|
|
488
|
-
payloadByteOffset = r.offset
|
|
489
|
-
payload = r.readBytesView(payloadLength)
|
|
490
|
-
}
|
|
491
|
-
const obj: ObjectPayload = {
|
|
492
|
-
type: 'object',
|
|
493
|
-
byteOffset,
|
|
494
|
-
payloadByteOffset,
|
|
495
|
-
objectId,
|
|
496
|
-
objectIdDelta: delta,
|
|
497
|
-
payloadLength,
|
|
498
|
-
payload,
|
|
499
|
-
extensionData,
|
|
500
|
-
}
|
|
501
|
-
if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status
|
|
502
|
-
if (objectProperties !== undefined)
|
|
503
|
-
(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
566
|
+
const obj = readSubgroupObject(r, h.propertiesPresent, isFirst, prevObjectId, 0)
|
|
567
|
+
if (isFirst && h.subgroupIsFirstObjId) subgroupId = obj.objectId
|
|
568
|
+
isFirst = false
|
|
504
569
|
objects.push(obj)
|
|
505
|
-
prevObjectId = objectId
|
|
570
|
+
prevObjectId = obj.objectId
|
|
506
571
|
}
|
|
507
572
|
|
|
508
573
|
const result: SubgroupStream = {
|
|
509
574
|
type: 'subgroup',
|
|
510
|
-
headerType: streamType,
|
|
511
|
-
trackAlias,
|
|
512
|
-
groupId,
|
|
575
|
+
headerType: h.streamType,
|
|
576
|
+
trackAlias: h.trackAlias,
|
|
577
|
+
groupId: h.groupId,
|
|
513
578
|
subgroupId,
|
|
514
|
-
publisherPriority,
|
|
579
|
+
publisherPriority: h.publisherPriority,
|
|
515
580
|
objects,
|
|
581
|
+
...(h.endOfGroup ? { endOfGroup: true } : {}),
|
|
582
|
+
...(h.firstObjectFlag ? { firstObject: true } : {}),
|
|
516
583
|
}
|
|
517
|
-
if (endOfGroup) (result as unknown as Record<string, unknown>).endOfGroup = true
|
|
518
|
-
if (firstObject) (result as unknown as Record<string, unknown>).firstObject = true
|
|
519
584
|
|
|
520
585
|
return {
|
|
521
586
|
ok: true,
|
|
@@ -639,6 +704,253 @@ export function decodeDatagram(bytes: Uint8Array): DecodeResult<DatagramObject>
|
|
|
639
704
|
}
|
|
640
705
|
}
|
|
641
706
|
|
|
707
|
+
/** The delta state a fetch stream carries from one Object to the next. */
|
|
708
|
+
interface FetchObjectState {
|
|
709
|
+
prevGroupId: bigint
|
|
710
|
+
prevSubgroupId: bigint
|
|
711
|
+
prevObjectId: bigint
|
|
712
|
+
prevPriority: number
|
|
713
|
+
first: boolean
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function newFetchObjectState(): FetchObjectState {
|
|
717
|
+
return { prevGroupId: 0n, prevSubgroupId: 0n, prevObjectId: 0n, prevPriority: 128, first: true }
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* One Object off a fetch stream, for both decoders.
|
|
722
|
+
*
|
|
723
|
+
* Lifted out of `decodeFetchStream` unchanged so the incremental decoder
|
|
724
|
+
* cannot report a different shape for the same bytes: the Serialization Flags,
|
|
725
|
+
* the Group and Subgroup ID, the Priority, the resolved deltas and a true
|
|
726
|
+
* `byteOffset` all come from this one reader, and DATAGRAM-mode Object IDs
|
|
727
|
+
* resolve against the state it carries.
|
|
728
|
+
*
|
|
729
|
+
* `base` is the absolute offset that `r.offset === 0` corresponds to: 0 for
|
|
730
|
+
* the one-shot decoder, the buffer window's position for the incremental one.
|
|
731
|
+
*/
|
|
732
|
+
function readFetchObject(r: BufferReader, base: number, st: FetchObjectState): FetchObjectPayload {
|
|
733
|
+
const byteOffset = base + r.offset
|
|
734
|
+
const flags = Number(r.readVarInt())
|
|
735
|
+
|
|
736
|
+
let groupId = st.prevGroupId
|
|
737
|
+
let subgroupId = st.prevSubgroupId
|
|
738
|
+
// Section 11.4.4.1: absent an Object ID Delta, Object ID is the prior
|
|
739
|
+
// Object's ID plus one. Stream delta encoding, not a range end.
|
|
740
|
+
let objectId = st.prevObjectId + 1n
|
|
741
|
+
let payloadLength: number
|
|
742
|
+
let payload: Uint8Array
|
|
743
|
+
let payloadByteOffset: number
|
|
744
|
+
const extensionData = new Uint8Array(0)
|
|
745
|
+
|
|
746
|
+
if (flags >= 0x80) {
|
|
747
|
+
// End-of-Range marker. Section 11.4.4: "Any other value is a
|
|
748
|
+
// PROTOCOL_VIOLATION."
|
|
749
|
+
if (!FETCH_END_OF_RANGE_MARKERS.has(flags)) {
|
|
750
|
+
throw new DecodeError(
|
|
751
|
+
'CONSTRAINT_VIOLATION',
|
|
752
|
+
`0x${flags.toString(16)} is neither a flag combination below 128 nor one of the three End of Range markers`,
|
|
753
|
+
r.offset,
|
|
754
|
+
)
|
|
755
|
+
}
|
|
756
|
+
// D7 / Q16: ordinary delta arithmetic (see encodeFetchStream).
|
|
757
|
+
const groupDelta = r.readVarInt()
|
|
758
|
+
groupId = resolveGroupId(st.first, groupDelta, st.prevGroupId, r.offset)
|
|
759
|
+
// With a Group ID Delta present, Object ID is the Object ID Delta.
|
|
760
|
+
const objectDelta = r.readVarInt()
|
|
761
|
+
objectId = objectDelta
|
|
762
|
+
// D7 / Q17: Object Payload Length is present, encoded as 0.
|
|
763
|
+
payloadLength = Number(r.readVarInt())
|
|
764
|
+
payloadByteOffset = base + r.offset
|
|
765
|
+
payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
766
|
+
|
|
767
|
+
const marker: FetchObjectPayload = {
|
|
768
|
+
type: 'object',
|
|
769
|
+
byteOffset,
|
|
770
|
+
payloadByteOffset,
|
|
771
|
+
serializationFlags: flags,
|
|
772
|
+
groupId,
|
|
773
|
+
groupIdDelta: groupDelta,
|
|
774
|
+
// A marker has no Subgroup ID or Priority of its own — Section
|
|
775
|
+
// 11.4.4.2 says neither field is present. The interface requires
|
|
776
|
+
// both, so they carry the last actual Object's values, which is also
|
|
777
|
+
// what a following Object that references "the prior Object" gets.
|
|
778
|
+
subgroupId: st.prevSubgroupId,
|
|
779
|
+
objectId,
|
|
780
|
+
objectIdDelta: objectDelta,
|
|
781
|
+
publisherPriority: st.prevPriority,
|
|
782
|
+
payloadLength,
|
|
783
|
+
payload,
|
|
784
|
+
extensionData,
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Section 11.4.4.2: prior Group ID and prior Object ID come FROM the
|
|
788
|
+
// marker; prior Subgroup ID and prior Priority stay with the last
|
|
789
|
+
// actual Object before it, so they are deliberately not updated here.
|
|
790
|
+
st.prevGroupId = groupId
|
|
791
|
+
st.prevObjectId = objectId
|
|
792
|
+
st.first = false
|
|
793
|
+
return marker
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (flags & 0x40) {
|
|
797
|
+
// DATAGRAM mode: no subgroup_id field
|
|
798
|
+
const objectIdPresent = (flags & 0x04) !== 0
|
|
799
|
+
const groupIdPresent = (flags & 0x08) !== 0
|
|
800
|
+
const priorityPresent = (flags & 0x10) !== 0
|
|
801
|
+
const propsPresent = (flags & 0x20) !== 0
|
|
802
|
+
|
|
803
|
+
let groupDelta: bigint | undefined
|
|
804
|
+
if (groupIdPresent) {
|
|
805
|
+
// First object's delta IS the absolute group id; subsequent objects:
|
|
806
|
+
// groupId = st.prevGroupId + delta + 1 (ascending).
|
|
807
|
+
groupDelta = r.readVarInt()
|
|
808
|
+
groupId = resolveGroupId(st.first, groupDelta, st.prevGroupId, r.offset)
|
|
809
|
+
}
|
|
810
|
+
let objectDelta: bigint | undefined
|
|
811
|
+
if (objectIdPresent) {
|
|
812
|
+
objectDelta = r.readVarInt()
|
|
813
|
+
objectId = resolveObjectId(groupIdPresent, objectDelta, st.prevObjectId)
|
|
814
|
+
}
|
|
815
|
+
if (objectId > MAX_U64) {
|
|
816
|
+
throw new DecodeError('CONSTRAINT_VIOLATION', 'computed Object ID exceeds 2^64 - 1', r.offset)
|
|
817
|
+
}
|
|
818
|
+
if (priorityPresent) {
|
|
819
|
+
st.prevPriority = r.readUint8()
|
|
820
|
+
}
|
|
821
|
+
let objectProperties: Record<string, bigint> | undefined
|
|
822
|
+
if (propsPresent) {
|
|
823
|
+
const propsLen = Number(r.readVarInt())
|
|
824
|
+
if (propsLen > 0) {
|
|
825
|
+
objectProperties = decodeObjectProperties(r, propsLen)
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
payloadLength = Number(r.readVarInt())
|
|
829
|
+
payloadByteOffset = base + r.offset
|
|
830
|
+
payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
831
|
+
|
|
832
|
+
const obj: FetchObjectPayload = {
|
|
833
|
+
type: 'object',
|
|
834
|
+
byteOffset,
|
|
835
|
+
payloadByteOffset,
|
|
836
|
+
serializationFlags: flags,
|
|
837
|
+
groupId,
|
|
838
|
+
subgroupId: 0n,
|
|
839
|
+
objectId,
|
|
840
|
+
publisherPriority: st.prevPriority,
|
|
841
|
+
payloadLength,
|
|
842
|
+
payload,
|
|
843
|
+
extensionData,
|
|
844
|
+
}
|
|
845
|
+
if (groupDelta !== undefined)
|
|
846
|
+
(obj as unknown as Record<string, unknown>).groupIdDelta = groupDelta
|
|
847
|
+
if (objectDelta !== undefined)
|
|
848
|
+
(obj as unknown as Record<string, unknown>).objectIdDelta = objectDelta
|
|
849
|
+
if (objectProperties)
|
|
850
|
+
(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
851
|
+
|
|
852
|
+
st.prevGroupId = groupId
|
|
853
|
+
st.prevObjectId = objectId
|
|
854
|
+
st.first = false
|
|
855
|
+
return obj
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const subgroupEncoding = flags & 0x03
|
|
859
|
+
const objectIdPresent = (flags & 0x04) !== 0
|
|
860
|
+
const groupIdPresent = (flags & 0x08) !== 0
|
|
861
|
+
const priorityPresent = (flags & 0x10) !== 0
|
|
862
|
+
const propsPresent = (flags & 0x20) !== 0
|
|
863
|
+
|
|
864
|
+
let groupDelta: bigint | undefined
|
|
865
|
+
if (groupIdPresent) {
|
|
866
|
+
groupDelta = r.readVarInt()
|
|
867
|
+
groupId = resolveGroupId(st.first, groupDelta, st.prevGroupId, r.offset)
|
|
868
|
+
} else if (st.first) {
|
|
869
|
+
throw new DecodeError(
|
|
870
|
+
'CONSTRAINT_VIOLATION',
|
|
871
|
+
'First fetch object must include groupId',
|
|
872
|
+
r.offset,
|
|
873
|
+
)
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (subgroupEncoding === 0x00) {
|
|
877
|
+
subgroupId = 0n
|
|
878
|
+
} else if (subgroupEncoding === 0x01 || subgroupEncoding === 0x02) {
|
|
879
|
+
if (st.first) {
|
|
880
|
+
throw new DecodeError(
|
|
881
|
+
'CONSTRAINT_VIOLATION',
|
|
882
|
+
'First fetch object cannot reference prior subgroupId',
|
|
883
|
+
r.offset,
|
|
884
|
+
)
|
|
885
|
+
}
|
|
886
|
+
subgroupId = subgroupEncoding === 0x01 ? st.prevSubgroupId : st.prevSubgroupId + 1n
|
|
887
|
+
} else {
|
|
888
|
+
subgroupId = r.readVarInt()
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
let objectDelta: bigint | undefined
|
|
892
|
+
if (objectIdPresent) {
|
|
893
|
+
objectDelta = r.readVarInt()
|
|
894
|
+
objectId = resolveObjectId(groupIdPresent, objectDelta, st.prevObjectId)
|
|
895
|
+
} else if (st.first) {
|
|
896
|
+
throw new DecodeError(
|
|
897
|
+
'CONSTRAINT_VIOLATION',
|
|
898
|
+
'First fetch object must include objectId',
|
|
899
|
+
r.offset,
|
|
900
|
+
)
|
|
901
|
+
}
|
|
902
|
+
if (objectId > MAX_U64) {
|
|
903
|
+
throw new DecodeError('CONSTRAINT_VIOLATION', 'computed Object ID exceeds 2^64 - 1', r.offset)
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (priorityPresent) {
|
|
907
|
+
st.prevPriority = r.readUint8()
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
let objectProperties: Record<string, bigint> | undefined
|
|
911
|
+
if (propsPresent) {
|
|
912
|
+
const propsLen = Number(r.readVarInt())
|
|
913
|
+
if (propsLen > 0) {
|
|
914
|
+
objectProperties = decodeObjectProperties(r, propsLen)
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
payloadLength = Number(r.readVarInt())
|
|
919
|
+
payloadByteOffset = base + r.offset
|
|
920
|
+
payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
921
|
+
|
|
922
|
+
// A fetch stream carries no Object Status: status is only present on
|
|
923
|
+
// subscription-delivered objects (Section 11.2.1.1). That is also the
|
|
924
|
+
// one way the two copies of a doubly delivered object differ — the
|
|
925
|
+
// subscription copy can carry a status, the fill copy cannot.
|
|
926
|
+
const obj: FetchObjectPayload = {
|
|
927
|
+
type: 'object',
|
|
928
|
+
byteOffset,
|
|
929
|
+
payloadByteOffset,
|
|
930
|
+
serializationFlags: flags,
|
|
931
|
+
groupId,
|
|
932
|
+
subgroupId,
|
|
933
|
+
objectId,
|
|
934
|
+
publisherPriority: st.prevPriority,
|
|
935
|
+
payloadLength,
|
|
936
|
+
payload,
|
|
937
|
+
extensionData,
|
|
938
|
+
}
|
|
939
|
+
if (groupDelta !== undefined)
|
|
940
|
+
(obj as unknown as Record<string, unknown>).groupIdDelta = groupDelta
|
|
941
|
+
if (objectDelta !== undefined)
|
|
942
|
+
(obj as unknown as Record<string, unknown>).objectIdDelta = objectDelta
|
|
943
|
+
if (objectProperties)
|
|
944
|
+
(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
945
|
+
|
|
946
|
+
st.prevGroupId = groupId
|
|
947
|
+
st.prevSubgroupId = subgroupId
|
|
948
|
+
st.prevObjectId = objectId
|
|
949
|
+
st.first = false
|
|
950
|
+
|
|
951
|
+
return obj
|
|
952
|
+
}
|
|
953
|
+
|
|
642
954
|
export function decodeFetchStream(bytes: Uint8Array): DecodeResult<FetchStream> {
|
|
643
955
|
try {
|
|
644
956
|
const r = new BufferReader(bytes)
|
|
@@ -660,259 +972,10 @@ export function decodeFetchStream(bytes: Uint8Array): DecodeResult<FetchStream>
|
|
|
660
972
|
const requestId = r.readVarInt()
|
|
661
973
|
const objects: FetchObjectPayload[] = []
|
|
662
974
|
|
|
663
|
-
|
|
664
|
-
let prevSubgroupId = 0n
|
|
665
|
-
let prevObjectId = 0n
|
|
666
|
-
let prevPriority = 128
|
|
667
|
-
let first = true
|
|
975
|
+
const st = newFetchObjectState()
|
|
668
976
|
|
|
669
977
|
while (r.remaining > 0) {
|
|
670
|
-
|
|
671
|
-
const flags = Number(r.readVarInt())
|
|
672
|
-
|
|
673
|
-
let groupId = prevGroupId
|
|
674
|
-
let subgroupId = prevSubgroupId
|
|
675
|
-
// Section 11.4.4.1: absent an Object ID Delta, Object ID is the prior
|
|
676
|
-
// Object's ID plus one. Stream delta encoding, not a range end.
|
|
677
|
-
let objectId = prevObjectId + 1n
|
|
678
|
-
let payloadLength: number
|
|
679
|
-
let payload: Uint8Array
|
|
680
|
-
let payloadByteOffset: number
|
|
681
|
-
const extensionData = new Uint8Array(0)
|
|
682
|
-
|
|
683
|
-
if (flags >= 0x80) {
|
|
684
|
-
// End-of-Range marker. Section 11.4.4: "Any other value is a
|
|
685
|
-
// PROTOCOL_VIOLATION."
|
|
686
|
-
if (!FETCH_END_OF_RANGE_MARKERS.has(flags)) {
|
|
687
|
-
return {
|
|
688
|
-
ok: false,
|
|
689
|
-
error: new DecodeError(
|
|
690
|
-
'CONSTRAINT_VIOLATION',
|
|
691
|
-
`0x${flags.toString(16)} is neither a flag combination below 128 nor one of the three End of Range markers`,
|
|
692
|
-
r.offset,
|
|
693
|
-
),
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
// D7 / Q16: ordinary delta arithmetic (see encodeFetchStream).
|
|
697
|
-
const groupDelta = r.readVarInt()
|
|
698
|
-
groupId = resolveGroupId(first, groupDelta, prevGroupId, r.offset)
|
|
699
|
-
// With a Group ID Delta present, Object ID is the Object ID Delta.
|
|
700
|
-
const objectDelta = r.readVarInt()
|
|
701
|
-
objectId = objectDelta
|
|
702
|
-
// D7 / Q17: Object Payload Length is present, encoded as 0.
|
|
703
|
-
payloadLength = Number(r.readVarInt())
|
|
704
|
-
payloadByteOffset = r.offset
|
|
705
|
-
payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
706
|
-
|
|
707
|
-
const marker: FetchObjectPayload = {
|
|
708
|
-
type: 'object',
|
|
709
|
-
byteOffset,
|
|
710
|
-
payloadByteOffset,
|
|
711
|
-
serializationFlags: flags,
|
|
712
|
-
groupId,
|
|
713
|
-
groupIdDelta: groupDelta,
|
|
714
|
-
// A marker has no Subgroup ID or Priority of its own — Section
|
|
715
|
-
// 11.4.4.2 says neither field is present. The interface requires
|
|
716
|
-
// both, so they carry the last actual Object's values, which is also
|
|
717
|
-
// what a following Object that references "the prior Object" gets.
|
|
718
|
-
subgroupId: prevSubgroupId,
|
|
719
|
-
objectId,
|
|
720
|
-
objectIdDelta: objectDelta,
|
|
721
|
-
publisherPriority: prevPriority,
|
|
722
|
-
payloadLength,
|
|
723
|
-
payload,
|
|
724
|
-
extensionData,
|
|
725
|
-
}
|
|
726
|
-
objects.push(marker)
|
|
727
|
-
|
|
728
|
-
// Section 11.4.4.2: prior Group ID and prior Object ID come FROM the
|
|
729
|
-
// marker; prior Subgroup ID and prior Priority stay with the last
|
|
730
|
-
// actual Object before it, so they are deliberately not updated here.
|
|
731
|
-
prevGroupId = groupId
|
|
732
|
-
prevObjectId = objectId
|
|
733
|
-
first = false
|
|
734
|
-
continue
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if (flags & 0x40) {
|
|
738
|
-
// DATAGRAM mode: no subgroup_id field
|
|
739
|
-
const objectIdPresent = (flags & 0x04) !== 0
|
|
740
|
-
const groupIdPresent = (flags & 0x08) !== 0
|
|
741
|
-
const priorityPresent = (flags & 0x10) !== 0
|
|
742
|
-
const propsPresent = (flags & 0x20) !== 0
|
|
743
|
-
|
|
744
|
-
let groupDelta: bigint | undefined
|
|
745
|
-
if (groupIdPresent) {
|
|
746
|
-
// First object's delta IS the absolute group id; subsequent objects:
|
|
747
|
-
// groupId = prevGroupId + delta + 1 (ascending).
|
|
748
|
-
groupDelta = r.readVarInt()
|
|
749
|
-
groupId = resolveGroupId(first, groupDelta, prevGroupId, r.offset)
|
|
750
|
-
}
|
|
751
|
-
let objectDelta: bigint | undefined
|
|
752
|
-
if (objectIdPresent) {
|
|
753
|
-
objectDelta = r.readVarInt()
|
|
754
|
-
objectId = resolveObjectId(groupIdPresent, objectDelta, prevObjectId)
|
|
755
|
-
}
|
|
756
|
-
if (objectId > MAX_U64) {
|
|
757
|
-
return {
|
|
758
|
-
ok: false,
|
|
759
|
-
error: new DecodeError(
|
|
760
|
-
'CONSTRAINT_VIOLATION',
|
|
761
|
-
'computed Object ID exceeds 2^64 - 1',
|
|
762
|
-
r.offset,
|
|
763
|
-
),
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
if (priorityPresent) {
|
|
767
|
-
prevPriority = r.readUint8()
|
|
768
|
-
}
|
|
769
|
-
let objectProperties: Record<string, bigint> | undefined
|
|
770
|
-
if (propsPresent) {
|
|
771
|
-
const propsLen = Number(r.readVarInt())
|
|
772
|
-
if (propsLen > 0) {
|
|
773
|
-
objectProperties = decodeObjectProperties(r, propsLen)
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
payloadLength = Number(r.readVarInt())
|
|
777
|
-
payloadByteOffset = r.offset
|
|
778
|
-
payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
779
|
-
|
|
780
|
-
const obj: FetchObjectPayload = {
|
|
781
|
-
type: 'object',
|
|
782
|
-
byteOffset,
|
|
783
|
-
payloadByteOffset,
|
|
784
|
-
serializationFlags: flags,
|
|
785
|
-
groupId,
|
|
786
|
-
subgroupId: 0n,
|
|
787
|
-
objectId,
|
|
788
|
-
publisherPriority: prevPriority,
|
|
789
|
-
payloadLength,
|
|
790
|
-
payload,
|
|
791
|
-
extensionData,
|
|
792
|
-
}
|
|
793
|
-
if (groupDelta !== undefined)
|
|
794
|
-
(obj as unknown as Record<string, unknown>).groupIdDelta = groupDelta
|
|
795
|
-
if (objectDelta !== undefined)
|
|
796
|
-
(obj as unknown as Record<string, unknown>).objectIdDelta = objectDelta
|
|
797
|
-
if (objectProperties)
|
|
798
|
-
(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
799
|
-
objects.push(obj)
|
|
800
|
-
|
|
801
|
-
prevGroupId = groupId
|
|
802
|
-
prevObjectId = objectId
|
|
803
|
-
first = false
|
|
804
|
-
continue
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
const subgroupEncoding = flags & 0x03
|
|
808
|
-
const objectIdPresent = (flags & 0x04) !== 0
|
|
809
|
-
const groupIdPresent = (flags & 0x08) !== 0
|
|
810
|
-
const priorityPresent = (flags & 0x10) !== 0
|
|
811
|
-
const propsPresent = (flags & 0x20) !== 0
|
|
812
|
-
|
|
813
|
-
let groupDelta: bigint | undefined
|
|
814
|
-
if (groupIdPresent) {
|
|
815
|
-
groupDelta = r.readVarInt()
|
|
816
|
-
groupId = resolveGroupId(first, groupDelta, prevGroupId, r.offset)
|
|
817
|
-
} else if (first) {
|
|
818
|
-
return {
|
|
819
|
-
ok: false,
|
|
820
|
-
error: new DecodeError(
|
|
821
|
-
'CONSTRAINT_VIOLATION',
|
|
822
|
-
'First fetch object must include groupId',
|
|
823
|
-
r.offset,
|
|
824
|
-
),
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
if (subgroupEncoding === 0x00) {
|
|
829
|
-
subgroupId = 0n
|
|
830
|
-
} else if (subgroupEncoding === 0x01 || subgroupEncoding === 0x02) {
|
|
831
|
-
if (first) {
|
|
832
|
-
return {
|
|
833
|
-
ok: false,
|
|
834
|
-
error: new DecodeError(
|
|
835
|
-
'CONSTRAINT_VIOLATION',
|
|
836
|
-
'First fetch object cannot reference prior subgroupId',
|
|
837
|
-
r.offset,
|
|
838
|
-
),
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
subgroupId = subgroupEncoding === 0x01 ? prevSubgroupId : prevSubgroupId + 1n
|
|
842
|
-
} else {
|
|
843
|
-
subgroupId = r.readVarInt()
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
let objectDelta: bigint | undefined
|
|
847
|
-
if (objectIdPresent) {
|
|
848
|
-
objectDelta = r.readVarInt()
|
|
849
|
-
objectId = resolveObjectId(groupIdPresent, objectDelta, prevObjectId)
|
|
850
|
-
} else if (first) {
|
|
851
|
-
return {
|
|
852
|
-
ok: false,
|
|
853
|
-
error: new DecodeError(
|
|
854
|
-
'CONSTRAINT_VIOLATION',
|
|
855
|
-
'First fetch object must include objectId',
|
|
856
|
-
r.offset,
|
|
857
|
-
),
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
if (objectId > MAX_U64) {
|
|
861
|
-
return {
|
|
862
|
-
ok: false,
|
|
863
|
-
error: new DecodeError(
|
|
864
|
-
'CONSTRAINT_VIOLATION',
|
|
865
|
-
'computed Object ID exceeds 2^64 - 1',
|
|
866
|
-
r.offset,
|
|
867
|
-
),
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
if (priorityPresent) {
|
|
872
|
-
prevPriority = r.readUint8()
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
let objectProperties: Record<string, bigint> | undefined
|
|
876
|
-
if (propsPresent) {
|
|
877
|
-
const propsLen = Number(r.readVarInt())
|
|
878
|
-
if (propsLen > 0) {
|
|
879
|
-
objectProperties = decodeObjectProperties(r, propsLen)
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
payloadLength = Number(r.readVarInt())
|
|
884
|
-
payloadByteOffset = r.offset
|
|
885
|
-
payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
886
|
-
|
|
887
|
-
// A fetch stream carries no Object Status: status is only present on
|
|
888
|
-
// subscription-delivered objects (Section 11.2.1.1). That is also the
|
|
889
|
-
// one way the two copies of a doubly delivered object differ — the
|
|
890
|
-
// subscription copy can carry a status, the fill copy cannot.
|
|
891
|
-
const obj: FetchObjectPayload = {
|
|
892
|
-
type: 'object',
|
|
893
|
-
byteOffset,
|
|
894
|
-
payloadByteOffset,
|
|
895
|
-
serializationFlags: flags,
|
|
896
|
-
groupId,
|
|
897
|
-
subgroupId,
|
|
898
|
-
objectId,
|
|
899
|
-
publisherPriority: prevPriority,
|
|
900
|
-
payloadLength,
|
|
901
|
-
payload,
|
|
902
|
-
extensionData,
|
|
903
|
-
}
|
|
904
|
-
if (groupDelta !== undefined)
|
|
905
|
-
(obj as unknown as Record<string, unknown>).groupIdDelta = groupDelta
|
|
906
|
-
if (objectDelta !== undefined)
|
|
907
|
-
(obj as unknown as Record<string, unknown>).objectIdDelta = objectDelta
|
|
908
|
-
if (objectProperties)
|
|
909
|
-
(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
|
|
910
|
-
objects.push(obj)
|
|
911
|
-
|
|
912
|
-
prevGroupId = groupId
|
|
913
|
-
prevSubgroupId = subgroupId
|
|
914
|
-
prevObjectId = objectId
|
|
915
|
-
first = false
|
|
978
|
+
objects.push(readFetchObject(r, 0, st))
|
|
916
979
|
}
|
|
917
980
|
|
|
918
981
|
return {
|
|
@@ -946,66 +1009,107 @@ export function decodeDataStream(
|
|
|
946
1009
|
|
|
947
1010
|
// ─── Data Stream Decoders ──────────────────────────────────────────────────────
|
|
948
1011
|
|
|
1012
|
+
/**
|
|
1013
|
+
* A data stream's bytes, accumulated across chunks.
|
|
1014
|
+
*
|
|
1015
|
+
* Capacity grows geometrically, so most chunks are a single `set` into spare
|
|
1016
|
+
* room rather than a fresh `unread + chunk.length` array and two copies.
|
|
1017
|
+
*
|
|
1018
|
+
* **It never compacts in place.** Object payloads are handed out as views into
|
|
1019
|
+
* this buffer (`readBytesView`), so moving bytes within it would corrupt an
|
|
1020
|
+
* object that has already been emitted. The consumed prefix is dropped only
|
|
1021
|
+
* when a fresh array is being allocated anyway, which leaves the old array
|
|
1022
|
+
* alive for exactly as long as the views into it are.
|
|
1023
|
+
*/
|
|
1024
|
+
class StreamBuffer {
|
|
1025
|
+
private buf = new Uint8Array(0)
|
|
1026
|
+
/** Bytes written into `buf`. */
|
|
1027
|
+
private len = 0
|
|
1028
|
+
/** Consumed prefix of `buf`; `base + offset` is the position in the stream. */
|
|
1029
|
+
offset = 0
|
|
1030
|
+
/** Absolute position of `buf[0]` within the stream. */
|
|
1031
|
+
base = 0
|
|
1032
|
+
|
|
1033
|
+
get unread(): number {
|
|
1034
|
+
return this.len - this.offset
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
append(chunk: Uint8Array): void {
|
|
1038
|
+
if (this.len + chunk.length > this.buf.length) {
|
|
1039
|
+
const live = this.len - this.offset
|
|
1040
|
+
const needed = live + chunk.length
|
|
1041
|
+
// Grow until reclaiming the consumed prefix leaves the buffer at least
|
|
1042
|
+
// half free. Sizing to `needed` would leave no room at all, so the next
|
|
1043
|
+
// chunk would reallocate too and the buffer would reallocate on every
|
|
1044
|
+
// single chunk, which is the cost it exists to avoid. Capacity never
|
|
1045
|
+
// shrinks, so it settles at roughly twice the largest `live + chunk`
|
|
1046
|
+
// this stream has seen.
|
|
1047
|
+
let cap = Math.max(4096, this.buf.length)
|
|
1048
|
+
while (cap < needed * 2) cap *= 2
|
|
1049
|
+
// A fresh array every time, including when the capacity is unchanged:
|
|
1050
|
+
// the old one is still pinned by the payload views handed out of it, so
|
|
1051
|
+
// it can be neither compacted nor reused.
|
|
1052
|
+
const next = new Uint8Array(cap)
|
|
1053
|
+
next.set(this.buf.subarray(this.offset, this.len), 0)
|
|
1054
|
+
this.buf = next
|
|
1055
|
+
this.base += this.offset
|
|
1056
|
+
this.len = live
|
|
1057
|
+
this.offset = 0
|
|
1058
|
+
}
|
|
1059
|
+
this.buf.set(chunk, this.len)
|
|
1060
|
+
this.len += chunk.length
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* The written bytes, for a reader positioned at `offset`. Bounded by `len`
|
|
1065
|
+
* rather than by capacity, so `BufferReader.remaining` counts real bytes and
|
|
1066
|
+
* not the spare room after them.
|
|
1067
|
+
*/
|
|
1068
|
+
written(): Uint8Array {
|
|
1069
|
+
return this.buf.subarray(0, this.len)
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
949
1073
|
export function createSubgroupStreamDecoder(): TransformStream<
|
|
950
1074
|
Uint8Array,
|
|
951
1075
|
SubgroupStreamHeader | ObjectPayload
|
|
952
1076
|
> {
|
|
953
|
-
|
|
954
|
-
let
|
|
1077
|
+
const b = new StreamBuffer()
|
|
1078
|
+
let header: SubgroupHeaderFields | null = null
|
|
955
1079
|
let headerEmitted = false
|
|
956
1080
|
let prevObjectId = -1n
|
|
957
|
-
let
|
|
958
|
-
|
|
1081
|
+
let isFirst = true
|
|
1082
|
+
|
|
1083
|
+
function emitHeader(
|
|
1084
|
+
controller: TransformStreamDefaultController<SubgroupStreamHeader | ObjectPayload>,
|
|
1085
|
+
h: SubgroupHeaderFields,
|
|
1086
|
+
subgroupId: bigint,
|
|
1087
|
+
): void {
|
|
1088
|
+
controller.enqueue({
|
|
1089
|
+
type: 'subgroup_header',
|
|
1090
|
+
headerType: h.streamType,
|
|
1091
|
+
trackAlias: h.trackAlias,
|
|
1092
|
+
groupId: h.groupId,
|
|
1093
|
+
subgroupId,
|
|
1094
|
+
publisherPriority: h.publisherPriority,
|
|
1095
|
+
...(h.endOfGroup ? { endOfGroup: true } : {}),
|
|
1096
|
+
...(h.firstObjectFlag ? { firstObject: true } : {}),
|
|
1097
|
+
})
|
|
1098
|
+
headerEmitted = true
|
|
1099
|
+
}
|
|
959
1100
|
|
|
960
1101
|
return new TransformStream<Uint8Array, SubgroupStreamHeader | ObjectPayload>({
|
|
961
1102
|
transform(chunk, controller) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
const newBuffer = new Uint8Array(buffer.length + chunk.length)
|
|
967
|
-
newBuffer.set(buffer, 0)
|
|
968
|
-
newBuffer.set(chunk, buffer.length)
|
|
969
|
-
buffer = newBuffer
|
|
1103
|
+
b.append(chunk)
|
|
1104
|
+
// One view and one reader per chunk, not per object: an object is the
|
|
1105
|
+
// thing there are a great many of.
|
|
1106
|
+
const view = b.written()
|
|
970
1107
|
|
|
971
|
-
if (
|
|
1108
|
+
if (header === null) {
|
|
972
1109
|
try {
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
if (flagsError !== null) {
|
|
977
|
-
controller.error(new DecodeError('CONSTRAINT_VIOLATION', flagsError, 0))
|
|
978
|
-
return
|
|
979
|
-
}
|
|
980
|
-
const streamType = Number(typeFlags)
|
|
981
|
-
|
|
982
|
-
_propertiesPresent = (streamType & 0x01) !== 0
|
|
983
|
-
const subgroupMode = (streamType & 0x06) >> 1
|
|
984
|
-
const hasSubgroupField = subgroupMode === 0x02
|
|
985
|
-
const hasPriority = (streamType & 0x20) === 0
|
|
986
|
-
|
|
987
|
-
const trackAlias = r.readVarInt()
|
|
988
|
-
const groupId = r.readVarInt()
|
|
989
|
-
|
|
990
|
-
let subgroupId = 0n
|
|
991
|
-
if (hasSubgroupField) {
|
|
992
|
-
subgroupId = r.readVarInt()
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
let publisherPriority = 128
|
|
996
|
-
if (hasPriority) {
|
|
997
|
-
publisherPriority = r.readUint8()
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
controller.enqueue({
|
|
1001
|
-
type: 'subgroup_header',
|
|
1002
|
-
trackAlias,
|
|
1003
|
-
groupId,
|
|
1004
|
-
subgroupId,
|
|
1005
|
-
publisherPriority,
|
|
1006
|
-
})
|
|
1007
|
-
headerEmitted = true
|
|
1008
|
-
offset += r.offset
|
|
1110
|
+
const hr = new BufferReader(view, b.offset)
|
|
1111
|
+
header = readSubgroupHeader(hr)
|
|
1112
|
+
b.offset = hr.offset
|
|
1009
1113
|
} catch (e) {
|
|
1010
1114
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
1011
1115
|
return
|
|
@@ -1013,51 +1117,46 @@ export function createSubgroupStreamDecoder(): TransformStream<
|
|
|
1013
1117
|
controller.error(e)
|
|
1014
1118
|
return
|
|
1015
1119
|
}
|
|
1120
|
+
// Where the Subgroup ID is the first Object's ID, that Object has not
|
|
1121
|
+
// been read yet. Holding the header back until it has is the only way
|
|
1122
|
+
// to emit the value the one-shot decoder reports; every other mode
|
|
1123
|
+
// knows its Subgroup ID already.
|
|
1124
|
+
if (!header.subgroupIsFirstObjId) emitHeader(controller, header, header.subgroupId)
|
|
1016
1125
|
}
|
|
1017
1126
|
|
|
1018
|
-
|
|
1127
|
+
const h = header
|
|
1128
|
+
if (h === null) return
|
|
1129
|
+
|
|
1130
|
+
const r = new BufferReader(view, b.offset)
|
|
1131
|
+
while (r.remaining > 0) {
|
|
1132
|
+
// Where this Object starts, so a partial one is re-read from the top
|
|
1133
|
+
// once the rest of it arrives.
|
|
1134
|
+
const start = r.offset
|
|
1135
|
+
let obj: ObjectPayload
|
|
1019
1136
|
try {
|
|
1020
|
-
|
|
1021
|
-
const delta = r.readVarInt()
|
|
1022
|
-
let objectId: bigint
|
|
1023
|
-
if (firstObject) {
|
|
1024
|
-
objectId = delta
|
|
1025
|
-
firstObject = false
|
|
1026
|
-
} else {
|
|
1027
|
-
objectId = prevObjectId + 1n + delta
|
|
1028
|
-
}
|
|
1029
|
-
let extensionData = new Uint8Array(0)
|
|
1030
|
-
if (_propertiesPresent) {
|
|
1031
|
-
const extLen = Number(r.readVarInt())
|
|
1032
|
-
extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
|
|
1033
|
-
}
|
|
1034
|
-
const payloadLength = Number(r.readVarInt())
|
|
1035
|
-
const payloadByteOffset = r.offset
|
|
1036
|
-
const payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
1037
|
-
controller.enqueue({
|
|
1038
|
-
type: 'object',
|
|
1039
|
-
objectId,
|
|
1040
|
-
objectIdDelta: delta,
|
|
1041
|
-
payloadLength,
|
|
1042
|
-
payload,
|
|
1043
|
-
extensionData,
|
|
1044
|
-
byteOffset: 0,
|
|
1045
|
-
payloadByteOffset,
|
|
1046
|
-
})
|
|
1047
|
-
offset += r.offset
|
|
1048
|
-
prevObjectId = objectId
|
|
1137
|
+
obj = readSubgroupObject(r, h.propertiesPresent, isFirst, prevObjectId, b.base)
|
|
1049
1138
|
} catch (e) {
|
|
1050
1139
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
1051
|
-
|
|
1140
|
+
b.offset = start
|
|
1141
|
+
return
|
|
1052
1142
|
}
|
|
1053
1143
|
controller.error(e)
|
|
1054
1144
|
return
|
|
1055
1145
|
}
|
|
1146
|
+
if (!headerEmitted) emitHeader(controller, h, obj.objectId)
|
|
1147
|
+
isFirst = false
|
|
1148
|
+
b.offset = r.offset
|
|
1149
|
+
prevObjectId = obj.objectId
|
|
1150
|
+
controller.enqueue(obj)
|
|
1056
1151
|
}
|
|
1057
1152
|
},
|
|
1058
1153
|
|
|
1059
1154
|
flush(controller) {
|
|
1060
|
-
|
|
1155
|
+
// A stream carrying a header and no Objects still has a header to report,
|
|
1156
|
+
// and its Subgroup ID is then zero -- the same value the one-shot decoder
|
|
1157
|
+
// returns when it finds no first Object to derive one from.
|
|
1158
|
+
if (header !== null && !headerEmitted) emitHeader(controller, header, 0n)
|
|
1159
|
+
if (b.unread > 0) {
|
|
1061
1160
|
controller.error(new DecodeError('UNEXPECTED_END', 'Stream ended with incomplete data', 0))
|
|
1062
1161
|
}
|
|
1063
1162
|
},
|
|
@@ -1068,24 +1167,23 @@ export function createFetchStreamDecoder(): TransformStream<
|
|
|
1068
1167
|
Uint8Array,
|
|
1069
1168
|
FetchStreamHeader | ObjectPayload
|
|
1070
1169
|
> {
|
|
1071
|
-
|
|
1072
|
-
let offset = 0
|
|
1170
|
+
const b = new StreamBuffer()
|
|
1073
1171
|
let headerEmitted = false
|
|
1172
|
+
/**
|
|
1173
|
+
* The delta state the Objects carry between them. DATAGRAM-mode Object IDs
|
|
1174
|
+
* resolve against the previous Object's ID, so a decoder that keeps no state
|
|
1175
|
+
* here reports them wrong.
|
|
1176
|
+
*/
|
|
1177
|
+
const st = newFetchObjectState()
|
|
1074
1178
|
|
|
1075
1179
|
return new TransformStream<Uint8Array, FetchStreamHeader | ObjectPayload>({
|
|
1076
1180
|
transform(chunk, controller) {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
offset = 0
|
|
1080
|
-
}
|
|
1081
|
-
const newBuffer = new Uint8Array(buffer.length + chunk.length)
|
|
1082
|
-
newBuffer.set(buffer, 0)
|
|
1083
|
-
newBuffer.set(chunk, buffer.length)
|
|
1084
|
-
buffer = newBuffer
|
|
1181
|
+
b.append(chunk)
|
|
1182
|
+
const view = b.written()
|
|
1085
1183
|
|
|
1086
1184
|
if (!headerEmitted) {
|
|
1087
1185
|
try {
|
|
1088
|
-
const r = new BufferReader(
|
|
1186
|
+
const r = new BufferReader(view, b.offset)
|
|
1089
1187
|
const streamType = r.readVarInt()
|
|
1090
1188
|
if (streamType !== FETCH_STREAM_TYPE) {
|
|
1091
1189
|
controller.error(
|
|
@@ -1100,7 +1198,7 @@ export function createFetchStreamDecoder(): TransformStream<
|
|
|
1100
1198
|
const requestId = r.readVarInt()
|
|
1101
1199
|
controller.enqueue({ type: 'fetch_header', requestId })
|
|
1102
1200
|
headerEmitted = true
|
|
1103
|
-
offset
|
|
1201
|
+
b.offset = r.offset
|
|
1104
1202
|
} catch (e) {
|
|
1105
1203
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
1106
1204
|
return
|
|
@@ -1110,89 +1208,27 @@ export function createFetchStreamDecoder(): TransformStream<
|
|
|
1110
1208
|
}
|
|
1111
1209
|
}
|
|
1112
1210
|
|
|
1113
|
-
|
|
1211
|
+
const r = new BufferReader(view, b.offset)
|
|
1212
|
+
while (r.remaining > 0) {
|
|
1213
|
+
const start = r.offset
|
|
1214
|
+
let obj: FetchObjectPayload
|
|
1114
1215
|
try {
|
|
1115
|
-
|
|
1116
|
-
const flags = Number(r.readVarInt())
|
|
1117
|
-
|
|
1118
|
-
if (flags >= 0x80) {
|
|
1119
|
-
if (!FETCH_END_OF_RANGE_MARKERS.has(flags)) {
|
|
1120
|
-
controller.error(
|
|
1121
|
-
new DecodeError(
|
|
1122
|
-
'CONSTRAINT_VIOLATION',
|
|
1123
|
-
`0x${flags.toString(16)} is neither a flag combination below 128 nor one of the three End of Range markers`,
|
|
1124
|
-
0,
|
|
1125
|
-
),
|
|
1126
|
-
)
|
|
1127
|
-
return
|
|
1128
|
-
}
|
|
1129
|
-
r.readVarInt() // Group ID Delta
|
|
1130
|
-
const objectDelta = r.readVarInt()
|
|
1131
|
-
const markerPayloadLength = Number(r.readVarInt())
|
|
1132
|
-
const markerPayloadByteOffset = r.offset
|
|
1133
|
-
controller.enqueue({
|
|
1134
|
-
type: 'object',
|
|
1135
|
-
objectId: objectDelta,
|
|
1136
|
-
objectIdDelta: objectDelta,
|
|
1137
|
-
payloadLength: markerPayloadLength,
|
|
1138
|
-
payload: new Uint8Array(0),
|
|
1139
|
-
extensionData: new Uint8Array(0),
|
|
1140
|
-
byteOffset: 0,
|
|
1141
|
-
payloadByteOffset: markerPayloadByteOffset,
|
|
1142
|
-
})
|
|
1143
|
-
offset += r.offset
|
|
1144
|
-
continue
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
const objectIdPresent = (flags & 0x04) !== 0
|
|
1148
|
-
const groupIdPresent = (flags & 0x08) !== 0
|
|
1149
|
-
const priorityPresent = (flags & 0x10) !== 0
|
|
1150
|
-
const extensionsPresent = (flags & 0x20) !== 0
|
|
1151
|
-
const subgroupEncoding = flags & 0x03
|
|
1152
|
-
const datagramMode = (flags & 0x40) !== 0
|
|
1153
|
-
|
|
1154
|
-
if (groupIdPresent) r.readVarInt()
|
|
1155
|
-
if (!datagramMode && subgroupEncoding === 0x03) r.readVarInt()
|
|
1156
|
-
let objectId = 0n
|
|
1157
|
-
let objectIdDelta: bigint | undefined
|
|
1158
|
-
if (objectIdPresent) {
|
|
1159
|
-
objectIdDelta = r.readVarInt()
|
|
1160
|
-
objectId = objectIdDelta
|
|
1161
|
-
}
|
|
1162
|
-
if (priorityPresent) r.readUint8()
|
|
1163
|
-
let extensionData = new Uint8Array(0)
|
|
1164
|
-
if (extensionsPresent) {
|
|
1165
|
-
const extLen = Number(r.readVarInt())
|
|
1166
|
-
extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
|
|
1167
|
-
}
|
|
1168
|
-
const payloadLength = Number(r.readVarInt())
|
|
1169
|
-
const payloadByteOffset = r.offset
|
|
1170
|
-
const payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
1171
|
-
const event: ObjectPayload = {
|
|
1172
|
-
type: 'object',
|
|
1173
|
-
objectId,
|
|
1174
|
-
payloadLength,
|
|
1175
|
-
payload,
|
|
1176
|
-
extensionData,
|
|
1177
|
-
byteOffset: 0,
|
|
1178
|
-
payloadByteOffset,
|
|
1179
|
-
}
|
|
1180
|
-
if (objectIdDelta !== undefined)
|
|
1181
|
-
(event as unknown as Record<string, unknown>).objectIdDelta = objectIdDelta
|
|
1182
|
-
controller.enqueue(event)
|
|
1183
|
-
offset += r.offset
|
|
1216
|
+
obj = readFetchObject(r, b.base, st)
|
|
1184
1217
|
} catch (e) {
|
|
1185
1218
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
1186
|
-
|
|
1219
|
+
b.offset = start
|
|
1220
|
+
return
|
|
1187
1221
|
}
|
|
1188
1222
|
controller.error(e)
|
|
1189
1223
|
return
|
|
1190
1224
|
}
|
|
1225
|
+
b.offset = r.offset
|
|
1226
|
+
controller.enqueue(obj)
|
|
1191
1227
|
}
|
|
1192
1228
|
},
|
|
1193
1229
|
|
|
1194
1230
|
flush(controller) {
|
|
1195
|
-
if (
|
|
1231
|
+
if (b.unread > 0) {
|
|
1196
1232
|
controller.error(new DecodeError('UNEXPECTED_END', 'Stream ended with incomplete data', 0))
|
|
1197
1233
|
}
|
|
1198
1234
|
},
|
|
@@ -1200,56 +1236,80 @@ export function createFetchStreamDecoder(): TransformStream<
|
|
|
1200
1236
|
}
|
|
1201
1237
|
|
|
1202
1238
|
export function createDataStreamDecoder(): TransformStream<Uint8Array, DataStreamEvent> {
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
controller.
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1239
|
+
/**
|
|
1240
|
+
* Delegation that actually delegates.
|
|
1241
|
+
*
|
|
1242
|
+
* The inner decoder's readable is pumped into this one's controller as the
|
|
1243
|
+
* bytes arrive. Buffering chunks locally and decoding the whole stream in
|
|
1244
|
+
* `flush` instead would deliver every event at end-of-stream, which on a
|
|
1245
|
+
* subscription that stays open means no events at all.
|
|
1246
|
+
*
|
|
1247
|
+
* The first byte decides which decoder to use, on exactly the range this
|
|
1248
|
+
* draft accepts.
|
|
1249
|
+
*/
|
|
1250
|
+
let writer: WritableStreamDefaultWriter<Uint8Array> | null = null
|
|
1251
|
+
let pump: Promise<void> = Promise.resolve()
|
|
1252
|
+
let broken = false
|
|
1253
|
+
|
|
1254
|
+
function attach(
|
|
1255
|
+
inner: TransformStream<Uint8Array, DataStreamEvent>,
|
|
1256
|
+
controller: TransformStreamDefaultController<DataStreamEvent>,
|
|
1257
|
+
): void {
|
|
1258
|
+
writer = inner.writable.getWriter()
|
|
1259
|
+
const reader = inner.readable.getReader()
|
|
1260
|
+
pump = (async () => {
|
|
1261
|
+
try {
|
|
1262
|
+
for (;;) {
|
|
1263
|
+
const { done, value } = await reader.read()
|
|
1264
|
+
if (done) break
|
|
1265
|
+
controller.enqueue(value)
|
|
1266
|
+
}
|
|
1267
|
+
} catch (e) {
|
|
1268
|
+
// The inner decoder rejected the stream. It owns the diagnosis, so its
|
|
1269
|
+
// error is the one that surfaces.
|
|
1270
|
+
if (!broken) {
|
|
1271
|
+
broken = true
|
|
1272
|
+
controller.error(e)
|
|
1237
1273
|
}
|
|
1238
1274
|
}
|
|
1239
|
-
}
|
|
1275
|
+
})()
|
|
1276
|
+
}
|
|
1240
1277
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1278
|
+
async function feed(bytes: Uint8Array): Promise<void> {
|
|
1279
|
+
if (writer === null || broken || bytes.length === 0) return
|
|
1280
|
+
try {
|
|
1281
|
+
await writer.write(bytes)
|
|
1282
|
+
} catch {
|
|
1283
|
+
// Writing to an errored inner stream throws the same error the pump is
|
|
1284
|
+
// already reporting; swallow it here so it is reported once.
|
|
1285
|
+
broken = true
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1244
1288
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1289
|
+
return new TransformStream<Uint8Array, DataStreamEvent>({
|
|
1290
|
+
async transform(chunk, controller) {
|
|
1291
|
+
if (broken) return
|
|
1292
|
+
if (writer !== null) {
|
|
1293
|
+
await feed(chunk)
|
|
1294
|
+
return
|
|
1295
|
+
}
|
|
1296
|
+
// One byte is enough to choose, and until there is one there is nothing
|
|
1297
|
+
// to choose from.
|
|
1298
|
+
if (chunk.length === 0) return
|
|
1299
|
+
const firstByte = chunk[0]!
|
|
1247
1300
|
|
|
1248
1301
|
if (isValidSubgroupType(firstByte)) {
|
|
1249
|
-
|
|
1302
|
+
attach(
|
|
1303
|
+
createSubgroupStreamDecoder() as unknown as TransformStream<Uint8Array, DataStreamEvent>,
|
|
1304
|
+
controller,
|
|
1305
|
+
)
|
|
1250
1306
|
} else if (firstByte === 0x05) {
|
|
1251
|
-
|
|
1307
|
+
attach(
|
|
1308
|
+
createFetchStreamDecoder() as unknown as TransformStream<Uint8Array, DataStreamEvent>,
|
|
1309
|
+
controller,
|
|
1310
|
+
)
|
|
1252
1311
|
} else {
|
|
1312
|
+
broken = true
|
|
1253
1313
|
controller.error(
|
|
1254
1314
|
new DecodeError(
|
|
1255
1315
|
'CONSTRAINT_VIOLATION',
|
|
@@ -1260,32 +1320,19 @@ export function createDataStreamDecoder(): TransformStream<Uint8Array, DataStrea
|
|
|
1260
1320
|
return
|
|
1261
1321
|
}
|
|
1262
1322
|
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1323
|
+
// The type byte is part of the stream the inner decoder reads, so the
|
|
1324
|
+
// whole chunk goes in, first byte included.
|
|
1325
|
+
await feed(chunk)
|
|
1326
|
+
},
|
|
1267
1327
|
|
|
1268
|
-
|
|
1269
|
-
if (
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
subgroupId: stream.subgroupId,
|
|
1275
|
-
publisherPriority: stream.publisherPriority,
|
|
1276
|
-
})
|
|
1277
|
-
for (const obj of stream.objects) {
|
|
1278
|
-
controller.enqueue(obj)
|
|
1279
|
-
}
|
|
1280
|
-
} else if (stream.type === 'fetch') {
|
|
1281
|
-
controller.enqueue({
|
|
1282
|
-
type: 'fetch_header',
|
|
1283
|
-
requestId: stream.requestId,
|
|
1284
|
-
})
|
|
1285
|
-
for (const obj of stream.objects) {
|
|
1286
|
-
controller.enqueue(obj)
|
|
1287
|
-
}
|
|
1328
|
+
async flush() {
|
|
1329
|
+
if (writer === null) return
|
|
1330
|
+
try {
|
|
1331
|
+
await writer.close()
|
|
1332
|
+
} catch {
|
|
1333
|
+
// Reported by the pump.
|
|
1288
1334
|
}
|
|
1335
|
+
await pump
|
|
1289
1336
|
},
|
|
1290
1337
|
})
|
|
1291
1338
|
}
|