@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
|
@@ -140,111 +140,158 @@ export function encodeFetchStream(stream: FetchStream): Uint8Array {
|
|
|
140
140
|
/**
|
|
141
141
|
* Decode a subgroup data stream from raw bytes.
|
|
142
142
|
*/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
),
|
|
164
|
-
}
|
|
165
|
-
}
|
|
143
|
+
/**
|
|
144
|
+
* The SUBGROUP_HEADER fields, read once for both decoders.
|
|
145
|
+
*
|
|
146
|
+
* `decodeSubgroupStream` and `createSubgroupStreamDecoder` used to parse this
|
|
147
|
+
* header separately and they drifted: the incremental one dropped the header
|
|
148
|
+
* flags, reported no Type Flags, and left the Subgroup ID at zero under the
|
|
149
|
+
* mode that derives it from the first Object. One reader means there is
|
|
150
|
+
* nothing left to keep in sync.
|
|
151
|
+
*/
|
|
152
|
+
interface SubgroupHeaderFields {
|
|
153
|
+
readonly streamType: number
|
|
154
|
+
readonly extensionsPresent: boolean
|
|
155
|
+
readonly subgroupIsFirstObjId: boolean
|
|
156
|
+
readonly endOfGroup: boolean
|
|
157
|
+
readonly trackAlias: bigint
|
|
158
|
+
readonly groupId: bigint
|
|
159
|
+
/** Zero under `subgroupIsFirstObjId` until the first Object has been read. */
|
|
160
|
+
readonly subgroupId: bigint
|
|
161
|
+
readonly publisherPriority: number
|
|
162
|
+
}
|
|
166
163
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
164
|
+
function readSubgroupHeader(r: BufferReader): SubgroupHeaderFields {
|
|
165
|
+
const streamType = Number(r.readVarInt())
|
|
166
|
+
if (
|
|
167
|
+
!((streamType >= 0x10 && streamType <= 0x1f) || (streamType >= 0x30 && streamType <= 0x3f)) ||
|
|
168
|
+
(streamType & 0x06) === 0x06
|
|
169
|
+
) {
|
|
170
|
+
throw new DecodeError(
|
|
171
|
+
'CONSTRAINT_VIOLATION',
|
|
172
|
+
`Expected subgroup stream type 0x10-0x1F/0x30-0x3F, got 0x${streamType.toString(16)}`,
|
|
173
|
+
0,
|
|
174
|
+
)
|
|
175
|
+
}
|
|
178
176
|
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
const extensionsPresent = (streamType & 0x01) !== 0
|
|
178
|
+
const endOfGroup = (streamType & 0x08) !== 0
|
|
179
|
+
const hasSubgroupField = (streamType & 0x04) !== 0
|
|
180
|
+
const hasPriority = streamType < 0x30
|
|
181
|
+
const subgroupIsFirstObjId = false
|
|
181
182
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
subgroupId = r.readVarInt()
|
|
185
|
-
}
|
|
183
|
+
const trackAlias = r.readVarInt()
|
|
184
|
+
const groupId = r.readVarInt()
|
|
186
185
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
186
|
+
let subgroupId = 0n
|
|
187
|
+
if (hasSubgroupField) {
|
|
188
|
+
subgroupId = r.readVarInt()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let publisherPriority = 128
|
|
192
|
+
if (hasPriority) {
|
|
193
|
+
publisherPriority = r.readUint8()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
streamType,
|
|
198
|
+
extensionsPresent,
|
|
199
|
+
subgroupIsFirstObjId,
|
|
200
|
+
endOfGroup,
|
|
201
|
+
trackAlias,
|
|
202
|
+
groupId,
|
|
203
|
+
subgroupId,
|
|
204
|
+
publisherPriority,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* One Object off a subgroup stream, for both decoders.
|
|
210
|
+
*
|
|
211
|
+
* `base` is the absolute offset within the stream that `r.offset === 0`
|
|
212
|
+
* corresponds to. The one-shot decoder reads from a reader spanning the whole
|
|
213
|
+
* stream and passes 0; the incremental decoder reads from a window over its
|
|
214
|
+
* own buffer and passes that window's position. That is what makes
|
|
215
|
+
* `byteOffset` mean the same thing on both paths, instead of being hardcoded
|
|
216
|
+
* to zero on one of them.
|
|
217
|
+
*
|
|
218
|
+
* Throws `UNEXPECTED_END` when the Object is not yet complete, which the
|
|
219
|
+
* incremental decoder reads as "wait for more bytes" rather than as an error.
|
|
220
|
+
*/
|
|
221
|
+
function readSubgroupObject(
|
|
222
|
+
r: BufferReader,
|
|
223
|
+
extensionsPresent: boolean,
|
|
224
|
+
isFirst: boolean,
|
|
225
|
+
prevObjectId: bigint,
|
|
226
|
+
base: number,
|
|
227
|
+
): ObjectPayload {
|
|
228
|
+
const byteOffset = base + r.offset
|
|
229
|
+
const delta = r.readVarInt()
|
|
230
|
+
const objectId = isFirst ? delta : prevObjectId + 1n + delta
|
|
231
|
+
|
|
232
|
+
let extensionData = new Uint8Array(0)
|
|
233
|
+
if (extensionsPresent) {
|
|
234
|
+
const extLen = Number(r.readVarInt())
|
|
235
|
+
extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const payloadLength = Number(r.readVarInt())
|
|
239
|
+
let payload: Uint8Array
|
|
240
|
+
let status: bigint | undefined
|
|
241
|
+
let payloadByteOffset: number
|
|
242
|
+
if (payloadLength === 0) {
|
|
243
|
+
// The Object Status field is sent only when the Payload Length is zero.
|
|
244
|
+
// Skipping it leaves the varint to be read as the next Object's Object ID
|
|
245
|
+
// Delta, which desynchronises the rest of the stream silently.
|
|
246
|
+
status = r.readVarInt()
|
|
247
|
+
payloadByteOffset = base + r.offset
|
|
248
|
+
payload = new Uint8Array(0)
|
|
249
|
+
} else {
|
|
250
|
+
payloadByteOffset = base + r.offset
|
|
251
|
+
payload = r.readBytesView(payloadLength)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const obj: ObjectPayload = {
|
|
255
|
+
type: 'object',
|
|
256
|
+
byteOffset,
|
|
257
|
+
payloadByteOffset,
|
|
258
|
+
objectId,
|
|
259
|
+
payloadLength,
|
|
260
|
+
payload,
|
|
261
|
+
extensionData,
|
|
262
|
+
}
|
|
263
|
+
if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status
|
|
264
|
+
return obj
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function decodeSubgroupStream(bytes: Uint8Array): DecodeResult<SubgroupStream> {
|
|
268
|
+
try {
|
|
269
|
+
const r = new BufferReader(bytes)
|
|
270
|
+
const h = readSubgroupHeader(r)
|
|
191
271
|
|
|
192
272
|
const objects: ObjectPayload[] = []
|
|
193
273
|
let prevObjectId = -1n
|
|
194
|
-
let
|
|
274
|
+
let isFirst = true
|
|
275
|
+
let subgroupId = h.subgroupId
|
|
195
276
|
|
|
196
277
|
while (r.remaining > 0) {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (firstObject) {
|
|
201
|
-
objectId = delta
|
|
202
|
-
firstObject = false
|
|
203
|
-
} else {
|
|
204
|
-
objectId = prevObjectId + 1n + delta
|
|
205
|
-
}
|
|
206
|
-
let extensionData = new Uint8Array(0)
|
|
207
|
-
if (extensionsPresent) {
|
|
208
|
-
const extLen = Number(r.readVarInt())
|
|
209
|
-
extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
|
|
210
|
-
}
|
|
211
|
-
const payloadLength = Number(r.readVarInt())
|
|
212
|
-
let payload: Uint8Array
|
|
213
|
-
let status: bigint | undefined
|
|
214
|
-
let payloadByteOffset: number
|
|
215
|
-
if (payloadLength === 0) {
|
|
216
|
-
// When payload_length is 0, an object status varint follows
|
|
217
|
-
status = r.readVarInt()
|
|
218
|
-
payloadByteOffset = r.offset
|
|
219
|
-
payload = new Uint8Array(0)
|
|
220
|
-
} else {
|
|
221
|
-
payloadByteOffset = r.offset
|
|
222
|
-
payload = r.readBytesView(payloadLength)
|
|
223
|
-
}
|
|
224
|
-
const obj: ObjectPayload = {
|
|
225
|
-
type: 'object',
|
|
226
|
-
byteOffset,
|
|
227
|
-
payloadByteOffset,
|
|
228
|
-
objectId,
|
|
229
|
-
payloadLength,
|
|
230
|
-
extensionData,
|
|
231
|
-
payload,
|
|
232
|
-
}
|
|
233
|
-
if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status
|
|
278
|
+
const obj = readSubgroupObject(r, h.extensionsPresent, isFirst, prevObjectId, 0)
|
|
279
|
+
if (isFirst && h.subgroupIsFirstObjId) subgroupId = obj.objectId
|
|
280
|
+
isFirst = false
|
|
234
281
|
objects.push(obj)
|
|
235
|
-
prevObjectId = objectId
|
|
282
|
+
prevObjectId = obj.objectId
|
|
236
283
|
}
|
|
237
284
|
|
|
238
285
|
const result: SubgroupStream = {
|
|
239
286
|
type: 'subgroup',
|
|
240
|
-
headerType: streamType,
|
|
241
|
-
trackAlias,
|
|
242
|
-
groupId,
|
|
287
|
+
headerType: h.streamType,
|
|
288
|
+
trackAlias: h.trackAlias,
|
|
289
|
+
groupId: h.groupId,
|
|
243
290
|
subgroupId,
|
|
244
|
-
publisherPriority,
|
|
291
|
+
publisherPriority: h.publisherPriority,
|
|
245
292
|
objects,
|
|
293
|
+
...(h.endOfGroup ? { endOfGroup: true } : {}),
|
|
246
294
|
}
|
|
247
|
-
if (endOfGroup) (result as unknown as Record<string, unknown>).endOfGroup = true
|
|
248
295
|
|
|
249
296
|
return {
|
|
250
297
|
ok: true,
|
|
@@ -257,9 +304,6 @@ export function decodeSubgroupStream(bytes: Uint8Array): DecodeResult<SubgroupSt
|
|
|
257
304
|
}
|
|
258
305
|
}
|
|
259
306
|
|
|
260
|
-
/**
|
|
261
|
-
* Decode a datagram object from raw bytes.
|
|
262
|
-
*/
|
|
263
307
|
export function decodeDatagram(bytes: Uint8Array): DecodeResult<DatagramObject> {
|
|
264
308
|
try {
|
|
265
309
|
const r = new BufferReader(bytes)
|
|
@@ -331,6 +375,141 @@ export function decodeDatagram(bytes: Uint8Array): DecodeResult<DatagramObject>
|
|
|
331
375
|
/**
|
|
332
376
|
* Decode a fetch data stream from raw bytes.
|
|
333
377
|
*/
|
|
378
|
+
/** The delta state a fetch stream carries from one Object to the next. */
|
|
379
|
+
interface FetchObjectState {
|
|
380
|
+
prevGroupId: bigint
|
|
381
|
+
prevSubgroupId: bigint
|
|
382
|
+
prevObjectId: bigint
|
|
383
|
+
prevPriority: number
|
|
384
|
+
first: boolean
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function newFetchObjectState(): FetchObjectState {
|
|
388
|
+
return { prevGroupId: 0n, prevSubgroupId: 0n, prevObjectId: 0n, prevPriority: 128, first: true }
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* One Object off a fetch stream, for both decoders.
|
|
393
|
+
*
|
|
394
|
+
* Lifted out of `decodeFetchStream` unchanged so the incremental decoder
|
|
395
|
+
* cannot report a different shape for the same bytes. It used to return a
|
|
396
|
+
* bare ObjectPayload -- no Serialization Flags, no Group or Subgroup ID, no
|
|
397
|
+
* Priority, no deltas -- with every `byteOffset` zero, and in DATAGRAM mode
|
|
398
|
+
* it resolved the Object ID against state it never carried.
|
|
399
|
+
*
|
|
400
|
+
* `base` is the absolute offset that `r.offset === 0` corresponds to: 0 for
|
|
401
|
+
* the one-shot decoder, the buffer window's position for the incremental one.
|
|
402
|
+
*/
|
|
403
|
+
function readFetchObject(r: BufferReader, base: number, st: FetchObjectState): FetchObjectPayload {
|
|
404
|
+
const byteOffset = base + r.offset
|
|
405
|
+
const flags = r.readUint8()
|
|
406
|
+
const subgroupEncoding = flags & 0x03
|
|
407
|
+
const objectIdPresent = (flags & 0x04) !== 0
|
|
408
|
+
const groupIdPresent = (flags & 0x08) !== 0
|
|
409
|
+
const priorityPresent = (flags & 0x10) !== 0
|
|
410
|
+
const extensionsPresent = (flags & 0x20) !== 0
|
|
411
|
+
|
|
412
|
+
if (flags & 0xc0) {
|
|
413
|
+
throw new DecodeError(
|
|
414
|
+
'CONSTRAINT_VIOLATION',
|
|
415
|
+
'Reserved bits set in fetch object flags',
|
|
416
|
+
r.offset,
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let groupId = st.prevGroupId
|
|
421
|
+
if (groupIdPresent) {
|
|
422
|
+
groupId = r.readVarInt()
|
|
423
|
+
} else if (st.first) {
|
|
424
|
+
throw new DecodeError(
|
|
425
|
+
'CONSTRAINT_VIOLATION',
|
|
426
|
+
'First fetch object must include groupId',
|
|
427
|
+
r.offset,
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
let subgroupId: bigint
|
|
432
|
+
if (subgroupEncoding === 0x00) {
|
|
433
|
+
subgroupId = 0n
|
|
434
|
+
} else if (subgroupEncoding === 0x01) {
|
|
435
|
+
if (st.first) {
|
|
436
|
+
throw new DecodeError(
|
|
437
|
+
'CONSTRAINT_VIOLATION',
|
|
438
|
+
'First fetch object cannot reference prior subgroupId',
|
|
439
|
+
r.offset,
|
|
440
|
+
)
|
|
441
|
+
}
|
|
442
|
+
subgroupId = st.prevSubgroupId
|
|
443
|
+
} else if (subgroupEncoding === 0x02) {
|
|
444
|
+
if (st.first) {
|
|
445
|
+
throw new DecodeError(
|
|
446
|
+
'CONSTRAINT_VIOLATION',
|
|
447
|
+
'First fetch object cannot reference prior subgroupId',
|
|
448
|
+
r.offset,
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
subgroupId = st.prevSubgroupId + 1n
|
|
452
|
+
} else {
|
|
453
|
+
subgroupId = r.readVarInt()
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
let objectId = st.prevObjectId + 1n
|
|
457
|
+
if (objectIdPresent) {
|
|
458
|
+
objectId = r.readVarInt()
|
|
459
|
+
} else if (st.first) {
|
|
460
|
+
throw new DecodeError(
|
|
461
|
+
'CONSTRAINT_VIOLATION',
|
|
462
|
+
'First fetch object must include objectId',
|
|
463
|
+
r.offset,
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (priorityPresent) {
|
|
468
|
+
st.prevPriority = r.readUint8()
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
let extensionData = new Uint8Array(0)
|
|
472
|
+
if (extensionsPresent) {
|
|
473
|
+
const extLen = Number(r.readVarInt())
|
|
474
|
+
extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const payloadLength = Number(r.readVarInt())
|
|
478
|
+
let payload: Uint8Array
|
|
479
|
+
let status: bigint | undefined
|
|
480
|
+
let payloadByteOffset: number
|
|
481
|
+
if (payloadLength > 0) {
|
|
482
|
+
payloadByteOffset = base + r.offset
|
|
483
|
+
payload = r.readBytesView(payloadLength)
|
|
484
|
+
} else {
|
|
485
|
+
status = r.readVarInt()
|
|
486
|
+
payloadByteOffset = base + r.offset
|
|
487
|
+
payload = new Uint8Array(0)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const obj: FetchObjectPayload = {
|
|
491
|
+
type: 'object',
|
|
492
|
+
byteOffset,
|
|
493
|
+
payloadByteOffset,
|
|
494
|
+
serializationFlags: flags,
|
|
495
|
+
groupId,
|
|
496
|
+
subgroupId,
|
|
497
|
+
objectId,
|
|
498
|
+
publisherPriority: st.prevPriority,
|
|
499
|
+
payloadLength,
|
|
500
|
+
extensionData,
|
|
501
|
+
payload,
|
|
502
|
+
}
|
|
503
|
+
if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status
|
|
504
|
+
|
|
505
|
+
st.prevGroupId = groupId
|
|
506
|
+
st.prevSubgroupId = subgroupId
|
|
507
|
+
st.prevObjectId = objectId
|
|
508
|
+
st.first = false
|
|
509
|
+
|
|
510
|
+
return obj
|
|
511
|
+
}
|
|
512
|
+
|
|
334
513
|
export function decodeFetchStream(bytes: Uint8Array): DecodeResult<FetchStream> {
|
|
335
514
|
try {
|
|
336
515
|
const r = new BufferReader(bytes)
|
|
@@ -348,134 +527,10 @@ export function decodeFetchStream(bytes: Uint8Array): DecodeResult<FetchStream>
|
|
|
348
527
|
const requestId = r.readVarInt()
|
|
349
528
|
const objects: FetchObjectPayload[] = []
|
|
350
529
|
|
|
351
|
-
|
|
352
|
-
let prevSubgroupId = 0n
|
|
353
|
-
let prevObjectId = 0n
|
|
354
|
-
let prevPriority = 128
|
|
355
|
-
let first = true
|
|
530
|
+
const st = newFetchObjectState()
|
|
356
531
|
|
|
357
532
|
while (r.remaining > 0) {
|
|
358
|
-
|
|
359
|
-
const flags = r.readUint8()
|
|
360
|
-
const subgroupEncoding = flags & 0x03
|
|
361
|
-
const objectIdPresent = (flags & 0x04) !== 0
|
|
362
|
-
const groupIdPresent = (flags & 0x08) !== 0
|
|
363
|
-
const priorityPresent = (flags & 0x10) !== 0
|
|
364
|
-
const extensionsPresent = (flags & 0x20) !== 0
|
|
365
|
-
|
|
366
|
-
if (flags & 0xc0) {
|
|
367
|
-
return {
|
|
368
|
-
ok: false,
|
|
369
|
-
error: new DecodeError(
|
|
370
|
-
'CONSTRAINT_VIOLATION',
|
|
371
|
-
'Reserved bits set in fetch object flags',
|
|
372
|
-
r.offset,
|
|
373
|
-
),
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
let groupId = prevGroupId
|
|
378
|
-
if (groupIdPresent) {
|
|
379
|
-
groupId = r.readVarInt()
|
|
380
|
-
} else if (first) {
|
|
381
|
-
return {
|
|
382
|
-
ok: false,
|
|
383
|
-
error: new DecodeError(
|
|
384
|
-
'CONSTRAINT_VIOLATION',
|
|
385
|
-
'First fetch object must include groupId',
|
|
386
|
-
r.offset,
|
|
387
|
-
),
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
let subgroupId: bigint
|
|
392
|
-
if (subgroupEncoding === 0x00) {
|
|
393
|
-
subgroupId = 0n
|
|
394
|
-
} else if (subgroupEncoding === 0x01) {
|
|
395
|
-
if (first) {
|
|
396
|
-
return {
|
|
397
|
-
ok: false,
|
|
398
|
-
error: new DecodeError(
|
|
399
|
-
'CONSTRAINT_VIOLATION',
|
|
400
|
-
'First fetch object cannot reference prior subgroupId',
|
|
401
|
-
r.offset,
|
|
402
|
-
),
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
subgroupId = prevSubgroupId
|
|
406
|
-
} else if (subgroupEncoding === 0x02) {
|
|
407
|
-
if (first) {
|
|
408
|
-
return {
|
|
409
|
-
ok: false,
|
|
410
|
-
error: new DecodeError(
|
|
411
|
-
'CONSTRAINT_VIOLATION',
|
|
412
|
-
'First fetch object cannot reference prior subgroupId',
|
|
413
|
-
r.offset,
|
|
414
|
-
),
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
subgroupId = prevSubgroupId + 1n
|
|
418
|
-
} else {
|
|
419
|
-
subgroupId = r.readVarInt()
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
let objectId = prevObjectId + 1n
|
|
423
|
-
if (objectIdPresent) {
|
|
424
|
-
objectId = r.readVarInt()
|
|
425
|
-
} else if (first) {
|
|
426
|
-
return {
|
|
427
|
-
ok: false,
|
|
428
|
-
error: new DecodeError(
|
|
429
|
-
'CONSTRAINT_VIOLATION',
|
|
430
|
-
'First fetch object must include objectId',
|
|
431
|
-
r.offset,
|
|
432
|
-
),
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (priorityPresent) {
|
|
437
|
-
prevPriority = r.readUint8()
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
let extensionData = new Uint8Array(0)
|
|
441
|
-
if (extensionsPresent) {
|
|
442
|
-
const extLen = Number(r.readVarInt())
|
|
443
|
-
extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const payloadLength = Number(r.readVarInt())
|
|
447
|
-
let payload: Uint8Array
|
|
448
|
-
let status: bigint | undefined
|
|
449
|
-
let payloadByteOffset: number
|
|
450
|
-
if (payloadLength > 0) {
|
|
451
|
-
payloadByteOffset = r.offset
|
|
452
|
-
payload = r.readBytesView(payloadLength)
|
|
453
|
-
} else {
|
|
454
|
-
status = r.readVarInt()
|
|
455
|
-
payloadByteOffset = r.offset
|
|
456
|
-
payload = new Uint8Array(0)
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const obj: FetchObjectPayload = {
|
|
460
|
-
type: 'object',
|
|
461
|
-
byteOffset,
|
|
462
|
-
payloadByteOffset,
|
|
463
|
-
serializationFlags: flags,
|
|
464
|
-
groupId,
|
|
465
|
-
subgroupId,
|
|
466
|
-
objectId,
|
|
467
|
-
publisherPriority: prevPriority,
|
|
468
|
-
payloadLength,
|
|
469
|
-
extensionData,
|
|
470
|
-
payload,
|
|
471
|
-
}
|
|
472
|
-
if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status
|
|
473
|
-
objects.push(obj)
|
|
474
|
-
|
|
475
|
-
prevGroupId = groupId
|
|
476
|
-
prevSubgroupId = subgroupId
|
|
477
|
-
prevObjectId = objectId
|
|
478
|
-
first = false
|
|
533
|
+
objects.push(readFetchObject(r, 0, st))
|
|
479
534
|
}
|
|
480
535
|
|
|
481
536
|
return {
|
|
@@ -515,78 +570,107 @@ export function decodeDataStream(
|
|
|
515
570
|
/**
|
|
516
571
|
* Create a TransformStream that decodes a subgroup data stream.
|
|
517
572
|
*/
|
|
573
|
+
/**
|
|
574
|
+
* A data stream's bytes, accumulated across chunks.
|
|
575
|
+
*
|
|
576
|
+
* The decoders below used to allocate an array of `unread + chunk.length` and
|
|
577
|
+
* copy both halves into it on *every* chunk. Capacity now grows geometrically,
|
|
578
|
+
* so most chunks are a single `set` into spare room.
|
|
579
|
+
*
|
|
580
|
+
* **It never compacts in place.** Object payloads are handed out as views into
|
|
581
|
+
* this buffer (`readBytesView`), so moving bytes within it would corrupt an
|
|
582
|
+
* object that has already been emitted. The consumed prefix is dropped only
|
|
583
|
+
* when a fresh array is being allocated anyway, which leaves the old array
|
|
584
|
+
* alive for exactly as long as the views into it are.
|
|
585
|
+
*/
|
|
586
|
+
class StreamBuffer {
|
|
587
|
+
private buf = new Uint8Array(0)
|
|
588
|
+
/** Bytes written into `buf`. */
|
|
589
|
+
private len = 0
|
|
590
|
+
/** Consumed prefix of `buf`; `base + offset` is the position in the stream. */
|
|
591
|
+
offset = 0
|
|
592
|
+
/** Absolute position of `buf[0]` within the stream. */
|
|
593
|
+
base = 0
|
|
594
|
+
|
|
595
|
+
get unread(): number {
|
|
596
|
+
return this.len - this.offset
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
append(chunk: Uint8Array): void {
|
|
600
|
+
if (this.len + chunk.length > this.buf.length) {
|
|
601
|
+
const live = this.len - this.offset
|
|
602
|
+
const needed = live + chunk.length
|
|
603
|
+
// Grow until reclaiming the consumed prefix leaves the buffer at least
|
|
604
|
+
// half free. Sizing to `needed` -- which is what this did first -- leaves
|
|
605
|
+
// no room at all, so the next chunk reallocates too and the buffer
|
|
606
|
+
// reallocates on every single chunk, which is the cost it exists to
|
|
607
|
+
// avoid. Capacity never shrinks, so it settles at roughly twice the
|
|
608
|
+
// largest `live + chunk` this stream has seen.
|
|
609
|
+
let cap = Math.max(4096, this.buf.length)
|
|
610
|
+
while (cap < needed * 2) cap *= 2
|
|
611
|
+
// A fresh array every time, including when the capacity is unchanged:
|
|
612
|
+
// the old one is still pinned by the payload views handed out of it, so
|
|
613
|
+
// it can be neither compacted nor reused.
|
|
614
|
+
const next = new Uint8Array(cap)
|
|
615
|
+
next.set(this.buf.subarray(this.offset, this.len), 0)
|
|
616
|
+
this.buf = next
|
|
617
|
+
this.base += this.offset
|
|
618
|
+
this.len = live
|
|
619
|
+
this.offset = 0
|
|
620
|
+
}
|
|
621
|
+
this.buf.set(chunk, this.len)
|
|
622
|
+
this.len += chunk.length
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* The written bytes, for a reader positioned at `offset`. Bounded by `len`
|
|
627
|
+
* rather than by capacity, so `BufferReader.remaining` counts real bytes and
|
|
628
|
+
* not the spare room after them.
|
|
629
|
+
*/
|
|
630
|
+
written(): Uint8Array {
|
|
631
|
+
return this.buf.subarray(0, this.len)
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
518
635
|
export function createSubgroupStreamDecoder(): TransformStream<
|
|
519
636
|
Uint8Array,
|
|
520
637
|
SubgroupStreamHeader | ObjectPayload
|
|
521
638
|
> {
|
|
522
|
-
|
|
523
|
-
let
|
|
639
|
+
const b = new StreamBuffer()
|
|
640
|
+
let header: SubgroupHeaderFields | null = null
|
|
524
641
|
let headerEmitted = false
|
|
525
642
|
let prevObjectId = -1n
|
|
526
|
-
let
|
|
527
|
-
|
|
528
|
-
|
|
643
|
+
let isFirst = true
|
|
644
|
+
|
|
645
|
+
function emitHeader(
|
|
646
|
+
controller: TransformStreamDefaultController<SubgroupStreamHeader | ObjectPayload>,
|
|
647
|
+
h: SubgroupHeaderFields,
|
|
648
|
+
subgroupId: bigint,
|
|
649
|
+
): void {
|
|
650
|
+
controller.enqueue({
|
|
651
|
+
type: 'subgroup_header',
|
|
652
|
+
headerType: h.streamType,
|
|
653
|
+
trackAlias: h.trackAlias,
|
|
654
|
+
groupId: h.groupId,
|
|
655
|
+
subgroupId,
|
|
656
|
+
publisherPriority: h.publisherPriority,
|
|
657
|
+
...(h.endOfGroup ? { endOfGroup: true } : {}),
|
|
658
|
+
})
|
|
659
|
+
headerEmitted = true
|
|
660
|
+
}
|
|
529
661
|
|
|
530
662
|
return new TransformStream<Uint8Array, SubgroupStreamHeader | ObjectPayload>({
|
|
531
663
|
transform(chunk, controller) {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const newBuffer = new Uint8Array(buffer.length + chunk.length)
|
|
537
|
-
newBuffer.set(buffer, 0)
|
|
538
|
-
newBuffer.set(chunk, buffer.length)
|
|
539
|
-
buffer = newBuffer
|
|
664
|
+
b.append(chunk)
|
|
665
|
+
// One view and one reader per chunk. Both used to be allocated per
|
|
666
|
+
// object, and an object is the thing there are a great many of.
|
|
667
|
+
const view = b.written()
|
|
540
668
|
|
|
541
|
-
if (
|
|
669
|
+
if (header === null) {
|
|
542
670
|
try {
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
if (
|
|
547
|
-
!(
|
|
548
|
-
(streamType >= 0x10 && streamType <= 0x1f) ||
|
|
549
|
-
(streamType >= 0x30 && streamType <= 0x3f)
|
|
550
|
-
)
|
|
551
|
-
) {
|
|
552
|
-
controller.error(
|
|
553
|
-
new DecodeError(
|
|
554
|
-
'CONSTRAINT_VIOLATION',
|
|
555
|
-
`Expected subgroup stream type, got 0x${streamType.toString(16)}`,
|
|
556
|
-
0,
|
|
557
|
-
),
|
|
558
|
-
)
|
|
559
|
-
return
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
_extensionsPresent = (streamType & 0x01) !== 0
|
|
563
|
-
const hasSubgroupField = (streamType & 0x04) !== 0
|
|
564
|
-
// Draft-15: bit 1 = end_of_group (not subgroupIsFirstObjId)
|
|
565
|
-
_subgroupIsFirstObjId = false
|
|
566
|
-
const hasPriority = streamType < 0x30
|
|
567
|
-
|
|
568
|
-
const trackAlias = r.readVarInt()
|
|
569
|
-
const groupId = r.readVarInt()
|
|
570
|
-
|
|
571
|
-
let subgroupId = 0n
|
|
572
|
-
if (hasSubgroupField) {
|
|
573
|
-
subgroupId = r.readVarInt()
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
let publisherPriority = 128
|
|
577
|
-
if (hasPriority) {
|
|
578
|
-
publisherPriority = r.readUint8()
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
controller.enqueue({
|
|
582
|
-
type: 'subgroup_header',
|
|
583
|
-
trackAlias,
|
|
584
|
-
groupId,
|
|
585
|
-
subgroupId,
|
|
586
|
-
publisherPriority,
|
|
587
|
-
})
|
|
588
|
-
headerEmitted = true
|
|
589
|
-
offset += r.offset
|
|
671
|
+
const hr = new BufferReader(view, b.offset)
|
|
672
|
+
header = readSubgroupHeader(hr)
|
|
673
|
+
b.offset = hr.offset
|
|
590
674
|
} catch (e) {
|
|
591
675
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
592
676
|
return
|
|
@@ -594,83 +678,73 @@ export function createSubgroupStreamDecoder(): TransformStream<
|
|
|
594
678
|
controller.error(e)
|
|
595
679
|
return
|
|
596
680
|
}
|
|
681
|
+
// Where the Subgroup ID is the first Object's ID, that Object has not
|
|
682
|
+
// been read yet. Holding the header back until it has is the only way
|
|
683
|
+
// to emit the value the one-shot decoder reports; every other mode
|
|
684
|
+
// knows its Subgroup ID already.
|
|
685
|
+
if (!header.subgroupIsFirstObjId) emitHeader(controller, header, header.subgroupId)
|
|
597
686
|
}
|
|
598
687
|
|
|
599
|
-
|
|
600
|
-
|
|
688
|
+
const h = header
|
|
689
|
+
if (h === null) return
|
|
690
|
+
|
|
691
|
+
const r = new BufferReader(view, b.offset)
|
|
692
|
+
while (r.remaining > 0) {
|
|
693
|
+
// Where this Object starts, so a partial one is re-read from the top
|
|
694
|
+
// once the rest of it arrives.
|
|
695
|
+
const start = r.offset
|
|
696
|
+
let obj: ObjectPayload
|
|
601
697
|
try {
|
|
602
|
-
|
|
603
|
-
const byteOffset = r.offset
|
|
604
|
-
const delta = r.readVarInt()
|
|
605
|
-
let objectId: bigint
|
|
606
|
-
if (firstObject) {
|
|
607
|
-
objectId = delta
|
|
608
|
-
firstObject = false
|
|
609
|
-
} else {
|
|
610
|
-
objectId = prevObjectId + 1n + delta
|
|
611
|
-
}
|
|
612
|
-
let extensionData = new Uint8Array(0)
|
|
613
|
-
if (_extensionsPresent) {
|
|
614
|
-
const extLen = Number(r.readVarInt())
|
|
615
|
-
extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
|
|
616
|
-
}
|
|
617
|
-
const payloadLength = Number(r.readVarInt())
|
|
618
|
-
const payloadByteOffset = r.offset
|
|
619
|
-
const payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
620
|
-
controller.enqueue({
|
|
621
|
-
type: 'object',
|
|
622
|
-
byteOffset,
|
|
623
|
-
payloadByteOffset,
|
|
624
|
-
objectId,
|
|
625
|
-
payloadLength,
|
|
626
|
-
extensionData,
|
|
627
|
-
payload,
|
|
628
|
-
})
|
|
629
|
-
offset += r.offset
|
|
630
|
-
prevObjectId = objectId
|
|
698
|
+
obj = readSubgroupObject(r, h.extensionsPresent, isFirst, prevObjectId, b.base)
|
|
631
699
|
} catch (e) {
|
|
632
700
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
633
|
-
|
|
701
|
+
b.offset = start
|
|
702
|
+
return
|
|
634
703
|
}
|
|
635
704
|
controller.error(e)
|
|
636
705
|
return
|
|
637
706
|
}
|
|
707
|
+
if (!headerEmitted) emitHeader(controller, h, obj.objectId)
|
|
708
|
+
isFirst = false
|
|
709
|
+
b.offset = r.offset
|
|
710
|
+
prevObjectId = obj.objectId
|
|
711
|
+
controller.enqueue(obj)
|
|
638
712
|
}
|
|
639
713
|
},
|
|
640
714
|
|
|
641
715
|
flush(controller) {
|
|
642
|
-
|
|
716
|
+
// A stream carrying a header and no Objects still has a header to report,
|
|
717
|
+
// and its Subgroup ID is then zero -- the same value the one-shot decoder
|
|
718
|
+
// returns when it finds no first Object to derive one from.
|
|
719
|
+
if (header !== null && !headerEmitted) emitHeader(controller, header, 0n)
|
|
720
|
+
if (b.unread > 0) {
|
|
643
721
|
controller.error(new DecodeError('UNEXPECTED_END', 'Stream ended with incomplete data', 0))
|
|
644
722
|
}
|
|
645
723
|
},
|
|
646
724
|
})
|
|
647
725
|
}
|
|
648
726
|
|
|
649
|
-
/**
|
|
650
|
-
* Create a TransformStream that decodes a fetch data stream.
|
|
651
|
-
*/
|
|
652
727
|
export function createFetchStreamDecoder(): TransformStream<
|
|
653
728
|
Uint8Array,
|
|
654
729
|
FetchStreamHeader | ObjectPayload
|
|
655
730
|
> {
|
|
656
|
-
|
|
657
|
-
let offset = 0
|
|
731
|
+
const b = new StreamBuffer()
|
|
658
732
|
let headerEmitted = false
|
|
733
|
+
/**
|
|
734
|
+
* The delta state the Objects carry between them. This decoder used to keep
|
|
735
|
+
* none, which is why DATAGRAM-mode Object IDs came out wrong: they resolve
|
|
736
|
+
* against the previous Object's ID.
|
|
737
|
+
*/
|
|
738
|
+
const st = newFetchObjectState()
|
|
659
739
|
|
|
660
740
|
return new TransformStream<Uint8Array, FetchStreamHeader | ObjectPayload>({
|
|
661
741
|
transform(chunk, controller) {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
offset = 0
|
|
665
|
-
}
|
|
666
|
-
const newBuffer = new Uint8Array(buffer.length + chunk.length)
|
|
667
|
-
newBuffer.set(buffer, 0)
|
|
668
|
-
newBuffer.set(chunk, buffer.length)
|
|
669
|
-
buffer = newBuffer
|
|
742
|
+
b.append(chunk)
|
|
743
|
+
const view = b.written()
|
|
670
744
|
|
|
671
745
|
if (!headerEmitted) {
|
|
672
746
|
try {
|
|
673
|
-
const r = new BufferReader(
|
|
747
|
+
const r = new BufferReader(view, b.offset)
|
|
674
748
|
const streamType = r.readVarInt()
|
|
675
749
|
if (streamType !== FETCH_STREAM_TYPE) {
|
|
676
750
|
controller.error(
|
|
@@ -685,7 +759,7 @@ export function createFetchStreamDecoder(): TransformStream<
|
|
|
685
759
|
const requestId = r.readVarInt()
|
|
686
760
|
controller.enqueue({ type: 'fetch_header', requestId })
|
|
687
761
|
headerEmitted = true
|
|
688
|
-
offset
|
|
762
|
+
b.offset = r.offset
|
|
689
763
|
} catch (e) {
|
|
690
764
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
691
765
|
return
|
|
@@ -695,126 +769,110 @@ export function createFetchStreamDecoder(): TransformStream<
|
|
|
695
769
|
}
|
|
696
770
|
}
|
|
697
771
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
772
|
+
const r = new BufferReader(view, b.offset)
|
|
773
|
+
while (r.remaining > 0) {
|
|
774
|
+
const start = r.offset
|
|
775
|
+
let obj: FetchObjectPayload
|
|
701
776
|
try {
|
|
702
|
-
|
|
703
|
-
const flags = r.readUint8()
|
|
704
|
-
const objectIdPresent = (flags & 0x04) !== 0
|
|
705
|
-
const groupIdPresent = (flags & 0x08) !== 0
|
|
706
|
-
const priorityPresent = (flags & 0x10) !== 0
|
|
707
|
-
const extensionsPresent = (flags & 0x20) !== 0
|
|
708
|
-
const subgroupEncoding = flags & 0x03
|
|
709
|
-
|
|
710
|
-
if (groupIdPresent) r.readVarInt() // groupId — consumed
|
|
711
|
-
if (subgroupEncoding === 0x03) r.readVarInt() // subgroupId — consumed
|
|
712
|
-
let objectId = 0n
|
|
713
|
-
if (objectIdPresent) objectId = r.readVarInt()
|
|
714
|
-
if (priorityPresent) r.readUint8() // priority — consumed
|
|
715
|
-
let extensionData = new Uint8Array(0)
|
|
716
|
-
if (extensionsPresent) {
|
|
717
|
-
const extLen = Number(r.readVarInt())
|
|
718
|
-
extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
|
|
719
|
-
}
|
|
720
|
-
const payloadLength = Number(r.readVarInt())
|
|
721
|
-
const payloadByteOffset = r.offset
|
|
722
|
-
const payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
|
|
723
|
-
controller.enqueue({
|
|
724
|
-
type: 'object',
|
|
725
|
-
objectId,
|
|
726
|
-
payloadLength,
|
|
727
|
-
extensionData,
|
|
728
|
-
payload,
|
|
729
|
-
byteOffset: 0,
|
|
730
|
-
payloadByteOffset,
|
|
731
|
-
})
|
|
732
|
-
offset += r.offset
|
|
777
|
+
obj = readFetchObject(r, b.base, st)
|
|
733
778
|
} catch (e) {
|
|
734
779
|
if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
|
|
735
|
-
|
|
780
|
+
b.offset = start
|
|
781
|
+
return
|
|
736
782
|
}
|
|
737
783
|
controller.error(e)
|
|
738
784
|
return
|
|
739
785
|
}
|
|
786
|
+
b.offset = r.offset
|
|
787
|
+
controller.enqueue(obj)
|
|
740
788
|
}
|
|
741
789
|
},
|
|
742
790
|
|
|
743
791
|
flush(controller) {
|
|
744
|
-
if (
|
|
792
|
+
if (b.unread > 0) {
|
|
745
793
|
controller.error(new DecodeError('UNEXPECTED_END', 'Stream ended with incomplete data', 0))
|
|
746
794
|
}
|
|
747
795
|
},
|
|
748
796
|
})
|
|
749
797
|
}
|
|
750
798
|
|
|
751
|
-
/**
|
|
752
|
-
* Create a unified auto-detecting data stream decoder.
|
|
753
|
-
*/
|
|
754
799
|
export function createDataStreamDecoder(): TransformStream<Uint8Array, DataStreamEvent> {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
0,
|
|
792
|
-
),
|
|
793
|
-
)
|
|
794
|
-
return
|
|
800
|
+
/**
|
|
801
|
+
* Delegation that actually delegates.
|
|
802
|
+
*
|
|
803
|
+
* This function used to pick an inner decoder and then never write a byte to
|
|
804
|
+
* it: chunks accumulated in a local buffer and `flush` decoded the whole
|
|
805
|
+
* stream in one shot. Every event therefore arrived at end-of-stream, which
|
|
806
|
+
* on a subscription that stays open means no events at all. The inner
|
|
807
|
+
* decoder's readable is now pumped into this one's controller as the bytes
|
|
808
|
+
* arrive.
|
|
809
|
+
*
|
|
810
|
+
* The first byte still decides which decoder to use, on exactly the range
|
|
811
|
+
* this draft accepted before.
|
|
812
|
+
*/
|
|
813
|
+
let writer: WritableStreamDefaultWriter<Uint8Array> | null = null
|
|
814
|
+
let pump: Promise<void> = Promise.resolve()
|
|
815
|
+
let broken = false
|
|
816
|
+
|
|
817
|
+
function attach(
|
|
818
|
+
inner: TransformStream<Uint8Array, DataStreamEvent>,
|
|
819
|
+
controller: TransformStreamDefaultController<DataStreamEvent>,
|
|
820
|
+
): void {
|
|
821
|
+
writer = inner.writable.getWriter()
|
|
822
|
+
const reader = inner.readable.getReader()
|
|
823
|
+
pump = (async () => {
|
|
824
|
+
try {
|
|
825
|
+
for (;;) {
|
|
826
|
+
const { done, value } = await reader.read()
|
|
827
|
+
if (done) break
|
|
828
|
+
controller.enqueue(value)
|
|
829
|
+
}
|
|
830
|
+
} catch (e) {
|
|
831
|
+
// The inner decoder rejected the stream. It owns the diagnosis, so its
|
|
832
|
+
// error is the one that surfaces.
|
|
833
|
+
if (!broken) {
|
|
834
|
+
broken = true
|
|
835
|
+
controller.error(e)
|
|
795
836
|
}
|
|
796
|
-
|
|
797
|
-
// For simplicity, process inline instead of piping
|
|
798
|
-
// Just re-parse from buffer using the appropriate one-shot decoder
|
|
799
837
|
}
|
|
838
|
+
})()
|
|
839
|
+
}
|
|
800
840
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
841
|
+
async function feed(bytes: Uint8Array): Promise<void> {
|
|
842
|
+
if (writer === null || broken || bytes.length === 0) return
|
|
843
|
+
try {
|
|
844
|
+
await writer.write(bytes)
|
|
845
|
+
} catch {
|
|
846
|
+
// Writing to an errored inner stream throws the same error the pump is
|
|
847
|
+
// already reporting; swallow it here so it is reported once.
|
|
848
|
+
broken = true
|
|
849
|
+
}
|
|
850
|
+
}
|
|
809
851
|
|
|
810
|
-
|
|
811
|
-
|
|
852
|
+
return new TransformStream<Uint8Array, DataStreamEvent>({
|
|
853
|
+
async transform(chunk, controller) {
|
|
854
|
+
if (broken) return
|
|
855
|
+
if (writer !== null) {
|
|
856
|
+
await feed(chunk)
|
|
857
|
+
return
|
|
858
|
+
}
|
|
859
|
+
// One byte is enough to choose, and until there is one there is nothing
|
|
860
|
+
// to choose from.
|
|
861
|
+
if (chunk.length === 0) return
|
|
862
|
+
const firstByte = chunk[0]!
|
|
812
863
|
|
|
813
864
|
if ((firstByte >= 0x10 && firstByte <= 0x1f) || (firstByte >= 0x30 && firstByte <= 0x3f)) {
|
|
814
|
-
|
|
865
|
+
attach(
|
|
866
|
+
createSubgroupStreamDecoder() as unknown as TransformStream<Uint8Array, DataStreamEvent>,
|
|
867
|
+
controller,
|
|
868
|
+
)
|
|
815
869
|
} else if (firstByte === 0x05) {
|
|
816
|
-
|
|
870
|
+
attach(
|
|
871
|
+
createFetchStreamDecoder() as unknown as TransformStream<Uint8Array, DataStreamEvent>,
|
|
872
|
+
controller,
|
|
873
|
+
)
|
|
817
874
|
} else {
|
|
875
|
+
broken = true
|
|
818
876
|
controller.error(
|
|
819
877
|
new DecodeError(
|
|
820
878
|
'CONSTRAINT_VIOLATION',
|
|
@@ -825,32 +883,19 @@ export function createDataStreamDecoder(): TransformStream<Uint8Array, DataStrea
|
|
|
825
883
|
return
|
|
826
884
|
}
|
|
827
885
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
886
|
+
// The type byte is part of the stream the inner decoder reads, so the
|
|
887
|
+
// whole chunk goes in, first byte included.
|
|
888
|
+
await feed(chunk)
|
|
889
|
+
},
|
|
832
890
|
|
|
833
|
-
|
|
834
|
-
if (
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
subgroupId: stream.subgroupId,
|
|
840
|
-
publisherPriority: stream.publisherPriority,
|
|
841
|
-
})
|
|
842
|
-
for (const obj of stream.objects) {
|
|
843
|
-
controller.enqueue(obj)
|
|
844
|
-
}
|
|
845
|
-
} else if (stream.type === 'fetch') {
|
|
846
|
-
controller.enqueue({
|
|
847
|
-
type: 'fetch_header',
|
|
848
|
-
requestId: stream.requestId,
|
|
849
|
-
})
|
|
850
|
-
for (const obj of stream.objects) {
|
|
851
|
-
controller.enqueue(obj)
|
|
852
|
-
}
|
|
891
|
+
async flush() {
|
|
892
|
+
if (writer === null) return
|
|
893
|
+
try {
|
|
894
|
+
await writer.close()
|
|
895
|
+
} catch {
|
|
896
|
+
// Reported by the pump.
|
|
853
897
|
}
|
|
898
|
+
await pump
|
|
854
899
|
},
|
|
855
900
|
})
|
|
856
901
|
}
|