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