@moqtap/codec 0.6.0 → 0.8.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/README.md +28 -2
- package/dist/{chunk-CXFAQKL3.js → chunk-23KRITJ6.js} +1 -1
- package/dist/{chunk-JL3NEW3O.cjs → chunk-4XLYCT5Q.cjs} +90 -1
- package/dist/{chunk-DLSGOZ6P.js → chunk-5XZKO2U5.js} +34 -34
- package/dist/{chunk-62LYLROG.js → chunk-APTV6F72.js} +35 -35
- package/dist/{chunk-JB5VDJ2V.cjs → chunk-B56AG5HU.cjs} +37 -37
- package/dist/{chunk-JTRU76JN.cjs → chunk-BQHLUT37.cjs} +109 -111
- package/dist/{chunk-4HBKHNU2.cjs → chunk-CZNW3R5E.cjs} +37 -37
- package/dist/{chunk-OGZTQA24.cjs → chunk-EQAYMCXO.cjs} +64 -64
- package/dist/{chunk-HYI6LFMD.js → chunk-HEO2JG6C.js} +1 -1
- package/dist/{chunk-UP4XPH4J.js → chunk-I55J3KXD.js} +31 -31
- package/dist/{chunk-HZHSO7EW.cjs → chunk-IDV2JYJ6.cjs} +39 -39
- package/dist/{chunk-BAG6YWCP.cjs → chunk-IEJULBKB.cjs} +63 -63
- package/dist/{chunk-Y6KCSM3V.js → chunk-IJAF6PAY.js} +1 -1
- package/dist/{chunk-WMJVSBBE.cjs → chunk-J3ASFPCX.cjs} +57 -57
- package/dist/{chunk-Q5D6OW6Q.js → chunk-JJEN2VG5.js} +51 -53
- package/dist/{chunk-COWGGUL4.js → chunk-JKMYAUGY.js} +1 -1
- package/dist/{chunk-SIYYMFCC.cjs → chunk-KCH4BQXK.cjs} +37 -37
- package/dist/{chunk-VOU7PNHI.js → chunk-KLPWJU6W.js} +89 -0
- package/dist/{chunk-NZ7GYEMT.js → chunk-KNLPX3YA.js} +1 -1
- package/dist/{chunk-A33U4WVS.cjs → chunk-KTVN4P6J.cjs} +67 -67
- package/dist/{chunk-7KBAWOBA.js → chunk-KYVTM5VI.js} +1 -1
- package/dist/{chunk-RZ3UML47.js → chunk-MD2OA7SE.js} +1 -1
- package/dist/{chunk-MSG2FXJ2.cjs → chunk-MH7537FB.cjs} +68 -68
- package/dist/{chunk-SHMA4PBY.js → chunk-N2V7Z37J.js} +1 -1
- package/dist/{chunk-HO44OANK.cjs → chunk-QGIHC6BA.cjs} +36 -36
- package/dist/{chunk-PKEOQSSZ.cjs → chunk-RIIA4ALQ.cjs} +37 -37
- package/dist/{chunk-XZE5R57R.cjs → chunk-VZHL2U2N.cjs} +37 -37
- package/dist/{chunk-RW4UG7CN.js → chunk-XHAP2YST.js} +1 -1
- package/dist/{codec-B1snaM9L.d.cts → codec-BnamSx7L.d.cts} +1 -1
- package/dist/{codec-D49OofBr.d.ts → codec-CFhUGTu2.d.ts} +1 -1
- package/dist/draft07.cjs +4 -4
- package/dist/draft07.js +2 -2
- package/dist/draft08.cjs +3 -3
- package/dist/draft08.js +2 -2
- package/dist/draft09.cjs +3 -3
- package/dist/draft09.js +2 -2
- package/dist/draft10.cjs +3 -3
- package/dist/draft10.js +2 -2
- package/dist/draft11.cjs +3 -3
- package/dist/draft11.js +2 -2
- package/dist/draft12.cjs +3 -3
- package/dist/draft12.js +2 -2
- package/dist/draft13.cjs +3 -3
- package/dist/draft13.js +2 -2
- package/dist/draft14-session.d.cts +1 -1
- package/dist/draft14-session.d.ts +1 -1
- package/dist/draft14.cjs +3 -3
- package/dist/draft14.d.cts +3 -3
- package/dist/draft14.d.ts +3 -3
- package/dist/draft14.js +2 -2
- package/dist/draft15.cjs +3 -3
- package/dist/draft15.js +2 -2
- package/dist/draft16.cjs +3 -3
- package/dist/draft16.js +2 -2
- package/dist/draft17.cjs +3 -3
- package/dist/draft17.js +2 -2
- package/dist/draft18.cjs +3 -3
- package/dist/draft18.js +2 -2
- package/dist/draft19.cjs +3 -3
- package/dist/draft19.js +2 -2
- package/dist/index.cjs +81 -29
- package/dist/index.d.cts +73 -3
- package/dist/index.d.ts +73 -3
- package/dist/index.js +67 -15
- package/dist/session.d.cts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/{types-DjmrHp_s.d.cts → types-CVeKIk7A.d.cts} +29 -21
- package/dist/{types-DjmrHp_s.d.ts → types-CVeKIk7A.d.ts} +29 -21
- package/package.json +2 -2
- package/src/core/accessors.ts +120 -0
- package/src/core/buffer-reader.ts +74 -2
- package/src/core/buffer-writer.ts +56 -4
- package/src/drafts/draft14/codec.ts +50 -52
- package/src/drafts/draft14/types.ts +31 -23
- package/src/drafts/draft17/codec.ts +2 -2
- package/src/drafts/draft17/data-streams.ts +2 -2
- package/src/drafts/draft18/codec.ts +2 -2
- package/src/drafts/draft18/data-streams.ts +2 -2
- package/src/drafts/draft19/codec.ts +2 -2
- package/src/drafts/draft19/data-streams.ts +2 -2
- package/src/index.ts +8 -0
|
@@ -63,8 +63,8 @@ interface Draft14Subscribe extends Draft14BaseMessage {
|
|
|
63
63
|
readonly request_id: bigint;
|
|
64
64
|
readonly track_namespace: string[];
|
|
65
65
|
readonly track_name: string;
|
|
66
|
-
readonly subscriber_priority:
|
|
67
|
-
readonly group_order:
|
|
66
|
+
readonly subscriber_priority: number;
|
|
67
|
+
readonly group_order: number;
|
|
68
68
|
readonly forward: bigint;
|
|
69
69
|
readonly filter_type: bigint;
|
|
70
70
|
readonly start_group?: bigint;
|
|
@@ -77,7 +77,7 @@ interface Draft14SubscribeOk extends Draft14BaseMessage {
|
|
|
77
77
|
readonly request_id: bigint;
|
|
78
78
|
readonly track_alias: bigint;
|
|
79
79
|
readonly expires: bigint;
|
|
80
|
-
readonly group_order:
|
|
80
|
+
readonly group_order: number;
|
|
81
81
|
readonly content_exists: bigint;
|
|
82
82
|
readonly largest_location?: Location;
|
|
83
83
|
readonly parameters: Draft14Params;
|
|
@@ -89,7 +89,7 @@ interface Draft14SubscribeUpdate extends Draft14BaseMessage {
|
|
|
89
89
|
readonly start_group: bigint;
|
|
90
90
|
readonly start_object: bigint;
|
|
91
91
|
readonly end_group: bigint;
|
|
92
|
-
readonly subscriber_priority:
|
|
92
|
+
readonly subscriber_priority: number;
|
|
93
93
|
readonly forward: bigint;
|
|
94
94
|
readonly parameters: Draft14Params;
|
|
95
95
|
}
|
|
@@ -109,7 +109,7 @@ interface Draft14Publish extends Draft14BaseMessage {
|
|
|
109
109
|
readonly track_namespace: string[];
|
|
110
110
|
readonly track_name: string;
|
|
111
111
|
readonly track_alias: bigint;
|
|
112
|
-
readonly group_order:
|
|
112
|
+
readonly group_order: number;
|
|
113
113
|
readonly content_exists: bigint;
|
|
114
114
|
readonly largest_location?: Location;
|
|
115
115
|
readonly forward: bigint;
|
|
@@ -119,8 +119,8 @@ interface Draft14PublishOk extends Draft14BaseMessage {
|
|
|
119
119
|
readonly type: 'publish_ok';
|
|
120
120
|
readonly request_id: bigint;
|
|
121
121
|
readonly forward: bigint;
|
|
122
|
-
readonly subscriber_priority:
|
|
123
|
-
readonly group_order:
|
|
122
|
+
readonly subscriber_priority: number;
|
|
123
|
+
readonly group_order: number;
|
|
124
124
|
readonly filter_type: bigint;
|
|
125
125
|
readonly start_group?: bigint;
|
|
126
126
|
readonly start_object?: bigint;
|
|
@@ -188,26 +188,34 @@ interface Draft14UnsubscribeNamespace extends Draft14BaseMessage {
|
|
|
188
188
|
readonly type: 'unsubscribe_namespace';
|
|
189
189
|
readonly track_namespace_prefix: string[];
|
|
190
190
|
}
|
|
191
|
+
interface StandaloneFetch {
|
|
192
|
+
readonly track_namespace: string[];
|
|
193
|
+
readonly track_name: string;
|
|
194
|
+
readonly start_group: bigint;
|
|
195
|
+
readonly start_object: bigint;
|
|
196
|
+
readonly end_group: bigint;
|
|
197
|
+
readonly end_object: bigint;
|
|
198
|
+
}
|
|
199
|
+
interface JoiningFetch {
|
|
200
|
+
readonly joining_request_id: bigint;
|
|
201
|
+
readonly joining_start: bigint;
|
|
202
|
+
}
|
|
191
203
|
interface Draft14Fetch extends Draft14BaseMessage {
|
|
192
204
|
readonly type: 'fetch';
|
|
193
205
|
readonly request_id: bigint;
|
|
194
|
-
readonly subscriber_priority:
|
|
195
|
-
readonly group_order:
|
|
206
|
+
readonly subscriber_priority: number;
|
|
207
|
+
readonly group_order: number;
|
|
196
208
|
readonly fetch_type: bigint;
|
|
197
|
-
|
|
198
|
-
readonly
|
|
199
|
-
|
|
200
|
-
readonly
|
|
201
|
-
readonly end_group?: bigint;
|
|
202
|
-
readonly end_object?: bigint;
|
|
203
|
-
readonly joining_request_id?: bigint;
|
|
204
|
-
readonly joining_start?: bigint;
|
|
209
|
+
/** Present when fetch_type = 1 */
|
|
210
|
+
readonly standalone?: StandaloneFetch;
|
|
211
|
+
/** Present when fetch_type = 2 or 3 */
|
|
212
|
+
readonly joining?: JoiningFetch;
|
|
205
213
|
readonly parameters: Draft14Params;
|
|
206
214
|
}
|
|
207
215
|
interface Draft14FetchOk extends Draft14BaseMessage {
|
|
208
216
|
readonly type: 'fetch_ok';
|
|
209
217
|
readonly request_id: bigint;
|
|
210
|
-
readonly group_order:
|
|
218
|
+
readonly group_order: number;
|
|
211
219
|
readonly end_of_track: bigint;
|
|
212
220
|
readonly end_location: Location;
|
|
213
221
|
readonly parameters: Draft14Params;
|
|
@@ -227,8 +235,8 @@ interface Draft14TrackStatus extends Draft14BaseMessage {
|
|
|
227
235
|
readonly request_id: bigint;
|
|
228
236
|
readonly track_namespace: string[];
|
|
229
237
|
readonly track_name: string;
|
|
230
|
-
readonly subscriber_priority:
|
|
231
|
-
readonly group_order:
|
|
238
|
+
readonly subscriber_priority: number;
|
|
239
|
+
readonly group_order: number;
|
|
232
240
|
readonly forward: bigint;
|
|
233
241
|
readonly filter_type: bigint;
|
|
234
242
|
readonly start_group?: bigint;
|
|
@@ -240,7 +248,7 @@ interface Draft14TrackStatusOk extends Draft14BaseMessage {
|
|
|
240
248
|
readonly request_id: bigint;
|
|
241
249
|
readonly track_alias: bigint;
|
|
242
250
|
readonly expires: bigint;
|
|
243
|
-
readonly group_order:
|
|
251
|
+
readonly group_order: number;
|
|
244
252
|
readonly content_exists: bigint;
|
|
245
253
|
readonly largest_location?: Location;
|
|
246
254
|
readonly parameters: Draft14Params;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moqtap/codec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "MoQT wire-format codec and session state machine — multi-draft, zero dependencies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -208,7 +208,7 @@
|
|
|
208
208
|
"test": "vitest run"
|
|
209
209
|
},
|
|
210
210
|
"devDependencies": {
|
|
211
|
-
"@moqtap/test-vectors": "^0.
|
|
211
|
+
"@moqtap/test-vectors": "^0.11.0",
|
|
212
212
|
"tsup": "^8.0.0",
|
|
213
213
|
"typescript": "^5.5.0",
|
|
214
214
|
"vitest": "^3.0.0"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draft-agnostic readers for decoded control messages.
|
|
3
|
+
*
|
|
4
|
+
* The codec keeps one message model per draft, because the drafts genuinely
|
|
5
|
+
* disagree: a track alias travels in SUBSCRIBE through draft-11 and in
|
|
6
|
+
* SUBSCRIBE_OK from draft-12; a request was a `subscribe_id` through draft-10
|
|
7
|
+
* and a `request_id` after it; a joining FETCH points at a
|
|
8
|
+
* `joining_subscribe_id` through draft-13 and a `joining_request_id` after it.
|
|
9
|
+
* Faithfulness to each draft is the point of the codec.
|
|
10
|
+
*
|
|
11
|
+
* What consumers usually want is the answer, not the archaeology — and every
|
|
12
|
+
* one of them that has asked has written the same field-spelling ladder by
|
|
13
|
+
* hand. These accessors are that ladder, written once.
|
|
14
|
+
*
|
|
15
|
+
* Each returns `undefined` when the message does not carry the field, which
|
|
16
|
+
* includes the cases where the draft has not assigned it yet: `trackAliasOf`
|
|
17
|
+
* on a draft-14 SUBSCRIBE is `undefined` because the publisher has not chosen
|
|
18
|
+
* the alias until SUBSCRIBE_OK.
|
|
19
|
+
*
|
|
20
|
+
* Both snake_case (what this codec emits) and camelCase spellings are read, so
|
|
21
|
+
* these also work on messages recovered from qlog-style traces written by
|
|
22
|
+
* other tools.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Any decoded control message, from any draft. */
|
|
26
|
+
export type AnyMessage = Readonly<Record<string, unknown>>
|
|
27
|
+
|
|
28
|
+
/** A track's full identity: the namespace tuple plus the name within it. */
|
|
29
|
+
export interface TrackIdentity {
|
|
30
|
+
readonly namespace: string[]
|
|
31
|
+
readonly name: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** First defined value among several spellings of one field. */
|
|
35
|
+
function pick(msg: AnyMessage, names: readonly string[]): unknown {
|
|
36
|
+
for (const name of names) {
|
|
37
|
+
const value = msg[name]
|
|
38
|
+
if (value != null) return value
|
|
39
|
+
}
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function bigintOf(value: unknown): bigint | undefined {
|
|
44
|
+
if (value == null) return undefined
|
|
45
|
+
if (typeof value === 'bigint') return value
|
|
46
|
+
if (typeof value === 'number' || typeof value === 'string') {
|
|
47
|
+
try {
|
|
48
|
+
return BigInt(value)
|
|
49
|
+
} catch {
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The request this message belongs to.
|
|
58
|
+
*
|
|
59
|
+
* Named `subscribe_id` through draft-10 and `request_id` from draft-11, when
|
|
60
|
+
* the id space widened to cover every request type rather than subscriptions
|
|
61
|
+
* alone.
|
|
62
|
+
*
|
|
63
|
+
* Returns `undefined` for draft-17+ responses, which carry no id at all: each
|
|
64
|
+
* request there has its own bidirectional stream, and that stream — not a
|
|
65
|
+
* field — is what ties a response to its request.
|
|
66
|
+
*/
|
|
67
|
+
export function requestIdOf(msg: AnyMessage): bigint | undefined {
|
|
68
|
+
return bigintOf(pick(msg, ['request_id', 'requestId', 'subscribe_id', 'subscribeId']))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The track alias this message assigns or refers to, if any.
|
|
73
|
+
*
|
|
74
|
+
* Which message carries it moved: SUBSCRIBE through draft-11, then SUBSCRIBE_OK
|
|
75
|
+
* from draft-12, when assigning the alias became the publisher's job. PUBLISH
|
|
76
|
+
* carries it alongside the track name in every draft that has PUBLISH.
|
|
77
|
+
*/
|
|
78
|
+
export function trackAliasOf(msg: AnyMessage): bigint | undefined {
|
|
79
|
+
return bigintOf(pick(msg, ['track_alias', 'trackAlias']))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The track this message names, or `undefined` if it names none.
|
|
84
|
+
*
|
|
85
|
+
* Covers SUBSCRIBE, PUBLISH, TRACK_STATUS and a standalone FETCH — whose track
|
|
86
|
+
* sits under `standalone` in most drafts and flat in draft-07, which predates
|
|
87
|
+
* joining fetch. A joining FETCH names no track of its own; use
|
|
88
|
+
* {@link joiningRequestIdOf} to find the request whose track it continues.
|
|
89
|
+
*
|
|
90
|
+
* PUBLISH_NAMESPACE and its relatives are namespace-only and yield
|
|
91
|
+
* `undefined`: a namespace is not a track.
|
|
92
|
+
*/
|
|
93
|
+
export function trackOf(msg: AnyMessage): TrackIdentity | undefined {
|
|
94
|
+
const source = (pick(msg, ['standalone']) ?? msg) as AnyMessage
|
|
95
|
+
const name = pick(source, ['track_name', 'trackName'])
|
|
96
|
+
if (typeof name !== 'string') return undefined
|
|
97
|
+
const namespace = pick(source, ['track_namespace', 'trackNamespace'])
|
|
98
|
+
return {
|
|
99
|
+
namespace: Array.isArray(namespace) ? namespace.map(String) : [],
|
|
100
|
+
name,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* For a joining FETCH, the request whose track it continues.
|
|
106
|
+
*
|
|
107
|
+
* Spelled `joining_subscribe_id` through draft-13 and `joining_request_id`
|
|
108
|
+
* after it, nested under `joining` in every draft that has one.
|
|
109
|
+
*/
|
|
110
|
+
export function joiningRequestIdOf(msg: AnyMessage): bigint | undefined {
|
|
111
|
+
const source = (pick(msg, ['joining']) ?? msg) as AnyMessage
|
|
112
|
+
return bigintOf(
|
|
113
|
+
pick(source, [
|
|
114
|
+
'joining_request_id',
|
|
115
|
+
'joiningRequestId',
|
|
116
|
+
'joining_subscribe_id',
|
|
117
|
+
'joiningSubscribeId',
|
|
118
|
+
]),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
@@ -3,8 +3,8 @@ import { DecodeError } from './types.js'
|
|
|
3
3
|
const textDecoder = /* @__PURE__ */ new TextDecoder()
|
|
4
4
|
|
|
5
5
|
export class BufferReader {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
protected readonly view: DataView
|
|
7
|
+
protected pos: number
|
|
8
8
|
|
|
9
9
|
constructor(
|
|
10
10
|
readonly buffer: Uint8Array,
|
|
@@ -128,3 +128,75 @@ export class BufferReader {
|
|
|
128
128
|
return params
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Reader for drafts 17 and later, which dropped the RFC 9000 varint for MoQT's
|
|
134
|
+
* own (§1.4.1).
|
|
135
|
+
*
|
|
136
|
+
* The number of leading 1 bits in the first byte gives the encoded length; the
|
|
137
|
+
* bits after the terminating 0, followed by the remaining bytes, are the value
|
|
138
|
+
* in network byte order. One byte therefore carries 0-127 rather than 0-63,
|
|
139
|
+
* and nine bytes carry the full 64-bit range instead of RFC 9000's 62. A first
|
|
140
|
+
* byte of 0xff is the nine-byte form and is prefix only.
|
|
141
|
+
*
|
|
142
|
+
* Encodings need not be minimal: draft-19 §1.4.1 says any length that can
|
|
143
|
+
* represent the value is valid, so 0 may arrive as 0x00, 0x8000, 0xc00000 or
|
|
144
|
+
* longer.
|
|
145
|
+
*/
|
|
146
|
+
export class MoqtBufferReader extends BufferReader {
|
|
147
|
+
/** Draft-17 omits the 7-byte length; draft-18 restored it. */
|
|
148
|
+
protected get allowSevenByte(): boolean {
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
override readVarInt(): bigint {
|
|
153
|
+
if (this.remaining < 1) {
|
|
154
|
+
throw new DecodeError('UNEXPECTED_END', 'Not enough bytes for varint', this.pos)
|
|
155
|
+
}
|
|
156
|
+
const first = this.view.getUint8(this.pos)
|
|
157
|
+
|
|
158
|
+
if (first === 0xff) {
|
|
159
|
+
if (this.remaining < 9) {
|
|
160
|
+
throw new DecodeError('UNEXPECTED_END', 'Not enough bytes for 9-byte varint', this.pos)
|
|
161
|
+
}
|
|
162
|
+
const value = this.view.getBigUint64(this.pos + 1)
|
|
163
|
+
this.pos += 9
|
|
164
|
+
return value
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let leadingOnes = 0
|
|
168
|
+
while (leadingOnes < 8 && (first & (0x80 >> leadingOnes)) !== 0) leadingOnes++
|
|
169
|
+
const length = leadingOnes + 1
|
|
170
|
+
|
|
171
|
+
if (length === 7 && !this.allowSevenByte) {
|
|
172
|
+
// draft-17 §1.4.1: "11111100 is an invalid code point. An endpoint that
|
|
173
|
+
// receives this value MUST close the session with a PROTOCOL_VIOLATION."
|
|
174
|
+
throw new DecodeError(
|
|
175
|
+
'INVALID_VARINT',
|
|
176
|
+
'The 7-byte varint is not defined in draft-17',
|
|
177
|
+
this.pos,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
if (this.remaining < length) {
|
|
181
|
+
throw new DecodeError(
|
|
182
|
+
'UNEXPECTED_END',
|
|
183
|
+
`Not enough bytes for ${length}-byte varint`,
|
|
184
|
+
this.pos,
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let value = BigInt(first & ((1 << (8 - length)) - 1))
|
|
189
|
+
for (let i = 1; i < length; i++) {
|
|
190
|
+
value = (value << 8n) | BigInt(this.view.getUint8(this.pos + i))
|
|
191
|
+
}
|
|
192
|
+
this.pos += length
|
|
193
|
+
return value
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Draft-17's reader, which rejects the 7-byte form draft-18 restored. */
|
|
198
|
+
export class Draft17BufferReader extends MoqtBufferReader {
|
|
199
|
+
protected override get allowSevenByte(): boolean {
|
|
200
|
+
return false
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const textEncoder = /* @__PURE__ */ new TextEncoder()
|
|
2
2
|
|
|
3
3
|
export class BufferWriter {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
protected buffer: Uint8Array
|
|
5
|
+
protected view: DataView
|
|
6
|
+
protected pos: number
|
|
7
7
|
|
|
8
8
|
constructor(initialSize = 256) {
|
|
9
9
|
this.buffer = new Uint8Array(initialSize)
|
|
@@ -15,7 +15,7 @@ export class BufferWriter {
|
|
|
15
15
|
return this.pos
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
protected ensureCapacity(needed: number): void {
|
|
19
19
|
const required = this.pos + needed
|
|
20
20
|
if (required <= this.buffer.byteLength) return
|
|
21
21
|
|
|
@@ -98,3 +98,55 @@ export class BufferWriter {
|
|
|
98
98
|
return this.buffer.subarray(0, this.pos)
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Writer for drafts 17 and later, which dropped the RFC 9000 varint for MoQT's
|
|
104
|
+
* own (§1.4.1). See {@link MoqtBufferReader} for the encoding.
|
|
105
|
+
*
|
|
106
|
+
* Values are always written in the fewest bytes that hold them. Longer forms
|
|
107
|
+
* are legal on the wire and decode fine, but there is no reason to emit them.
|
|
108
|
+
*/
|
|
109
|
+
export class MoqtBufferWriter extends BufferWriter {
|
|
110
|
+
/** Draft-17 omits the 7-byte length; draft-18 restored it. */
|
|
111
|
+
protected get allowSevenByte(): boolean {
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override writeVarInt(value: number | bigint): void {
|
|
116
|
+
const v = BigInt(value)
|
|
117
|
+
if (v < 0n) throw new Error('VarInt value must be non-negative')
|
|
118
|
+
if (v > 0xffffffffffffffffn) throw new Error('VarInt value exceeds 64-bit range')
|
|
119
|
+
|
|
120
|
+
let length = 9
|
|
121
|
+
for (let n = 1; n <= 8; n++) {
|
|
122
|
+
if (n === 7 && !this.allowSevenByte) continue
|
|
123
|
+
if (v < 1n << BigInt(7 * n)) {
|
|
124
|
+
length = n
|
|
125
|
+
break
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.ensureCapacity(length)
|
|
130
|
+
if (length === 9) {
|
|
131
|
+
this.view.setUint8(this.pos, 0xff)
|
|
132
|
+
this.view.setBigUint64(this.pos + 1, v)
|
|
133
|
+
this.pos += 9
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// (length - 1) leading 1 bits, then a 0, in the top `length` bits.
|
|
138
|
+
const prefix = BigInt(((1 << (length - 1)) - 1) << (9 - length)) & 0xffn
|
|
139
|
+
const combined = (prefix << BigInt(8 * (length - 1))) | v
|
|
140
|
+
for (let i = 0; i < length; i++) {
|
|
141
|
+
this.view.setUint8(this.pos + i, Number((combined >> BigInt(8 * (length - 1 - i))) & 0xffn))
|
|
142
|
+
}
|
|
143
|
+
this.pos += length
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Draft-17's writer, which never emits the 7-byte form draft-18 restored. */
|
|
148
|
+
export class Draft17BufferWriter extends MoqtBufferWriter {
|
|
149
|
+
protected override get allowSevenByte(): boolean {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -333,8 +333,8 @@ function encodeSubscribePayload(
|
|
|
333
333
|
w.writeVarInt(msg.request_id)
|
|
334
334
|
w.writeTuple(msg.track_namespace)
|
|
335
335
|
w.writeString(msg.track_name)
|
|
336
|
-
w.writeUint8(
|
|
337
|
-
w.writeUint8(
|
|
336
|
+
w.writeUint8(msg.subscriber_priority)
|
|
337
|
+
w.writeUint8(msg.group_order)
|
|
338
338
|
w.writeVarInt(msg.forward)
|
|
339
339
|
w.writeVarInt(msg.filter_type)
|
|
340
340
|
const ft = msg.filter_type
|
|
@@ -355,7 +355,7 @@ function encodeSubscribeOkPayload(
|
|
|
355
355
|
w.writeVarInt(msg.request_id)
|
|
356
356
|
w.writeVarInt(msg.track_alias)
|
|
357
357
|
w.writeVarInt(msg.expires)
|
|
358
|
-
w.writeUint8(
|
|
358
|
+
w.writeUint8(msg.group_order)
|
|
359
359
|
w.writeVarInt(msg.content_exists)
|
|
360
360
|
if (msg.content_exists === 1n) {
|
|
361
361
|
w.writeVarInt(msg.largest_location!.group)
|
|
@@ -373,7 +373,7 @@ function encodeSubscribeUpdatePayload(
|
|
|
373
373
|
w.writeVarInt(msg.start_group)
|
|
374
374
|
w.writeVarInt(msg.start_object)
|
|
375
375
|
w.writeVarInt(msg.end_group)
|
|
376
|
-
w.writeUint8(
|
|
376
|
+
w.writeUint8(msg.subscriber_priority)
|
|
377
377
|
w.writeVarInt(msg.forward)
|
|
378
378
|
encodeMessageParams(msg.parameters, w)
|
|
379
379
|
}
|
|
@@ -399,7 +399,7 @@ function encodePublishPayload(msg: Draft14Message & { type: 'publish' }, w: Buff
|
|
|
399
399
|
w.writeTuple(msg.track_namespace)
|
|
400
400
|
w.writeString(msg.track_name)
|
|
401
401
|
w.writeVarInt(msg.track_alias)
|
|
402
|
-
w.writeUint8(
|
|
402
|
+
w.writeUint8(msg.group_order)
|
|
403
403
|
w.writeVarInt(msg.content_exists)
|
|
404
404
|
if (msg.content_exists === 1n) {
|
|
405
405
|
w.writeVarInt(msg.largest_location!.group)
|
|
@@ -415,8 +415,8 @@ function encodePublishOkPayload(
|
|
|
415
415
|
): void {
|
|
416
416
|
w.writeVarInt(msg.request_id)
|
|
417
417
|
w.writeVarInt(msg.forward)
|
|
418
|
-
w.writeUint8(
|
|
419
|
-
w.writeUint8(
|
|
418
|
+
w.writeUint8(msg.subscriber_priority)
|
|
419
|
+
w.writeUint8(msg.group_order)
|
|
420
420
|
w.writeVarInt(msg.filter_type)
|
|
421
421
|
const ft = msg.filter_type
|
|
422
422
|
if (ft >= FilterType.AbsoluteStart) {
|
|
@@ -525,27 +525,27 @@ function encodeUnsubscribeNamespacePayload(
|
|
|
525
525
|
|
|
526
526
|
function encodeFetchPayload(msg: Draft14Message & { type: 'fetch' }, w: BufferWriter): void {
|
|
527
527
|
w.writeVarInt(msg.request_id)
|
|
528
|
-
w.writeUint8(
|
|
529
|
-
w.writeUint8(
|
|
528
|
+
w.writeUint8(msg.subscriber_priority)
|
|
529
|
+
w.writeUint8(msg.group_order)
|
|
530
530
|
w.writeVarInt(msg.fetch_type)
|
|
531
531
|
const ft = msg.fetch_type
|
|
532
|
-
if (ft === FetchType.Standalone) {
|
|
533
|
-
w.writeTuple(msg.track_namespace
|
|
534
|
-
w.writeString(msg.track_name
|
|
535
|
-
w.writeVarInt(msg.start_group
|
|
536
|
-
w.writeVarInt(msg.start_object
|
|
537
|
-
w.writeVarInt(msg.end_group
|
|
538
|
-
w.writeVarInt(msg.end_object
|
|
539
|
-
} else {
|
|
540
|
-
w.writeVarInt(msg.joining_request_id
|
|
541
|
-
w.writeVarInt(msg.joining_start
|
|
532
|
+
if (ft === FetchType.Standalone && msg.standalone) {
|
|
533
|
+
w.writeTuple(msg.standalone.track_namespace)
|
|
534
|
+
w.writeString(msg.standalone.track_name)
|
|
535
|
+
w.writeVarInt(msg.standalone.start_group)
|
|
536
|
+
w.writeVarInt(msg.standalone.start_object)
|
|
537
|
+
w.writeVarInt(msg.standalone.end_group)
|
|
538
|
+
w.writeVarInt(msg.standalone.end_object)
|
|
539
|
+
} else if (msg.joining) {
|
|
540
|
+
w.writeVarInt(msg.joining.joining_request_id)
|
|
541
|
+
w.writeVarInt(msg.joining.joining_start)
|
|
542
542
|
}
|
|
543
543
|
encodeMessageParams(msg.parameters, w)
|
|
544
544
|
}
|
|
545
545
|
|
|
546
546
|
function encodeFetchOkPayload(msg: Draft14Message & { type: 'fetch_ok' }, w: BufferWriter): void {
|
|
547
547
|
w.writeVarInt(msg.request_id)
|
|
548
|
-
w.writeUint8(
|
|
548
|
+
w.writeUint8(msg.group_order)
|
|
549
549
|
w.writeVarInt(msg.end_of_track)
|
|
550
550
|
w.writeVarInt(msg.end_location.group)
|
|
551
551
|
w.writeVarInt(msg.end_location.object)
|
|
@@ -575,8 +575,8 @@ function encodeTrackStatusPayload(
|
|
|
575
575
|
w.writeVarInt(msg.request_id)
|
|
576
576
|
w.writeTuple(msg.track_namespace)
|
|
577
577
|
w.writeString(msg.track_name)
|
|
578
|
-
w.writeUint8(
|
|
579
|
-
w.writeUint8(
|
|
578
|
+
w.writeUint8(msg.subscriber_priority)
|
|
579
|
+
w.writeUint8(msg.group_order)
|
|
580
580
|
w.writeVarInt(msg.forward)
|
|
581
581
|
w.writeVarInt(msg.filter_type)
|
|
582
582
|
const ft = msg.filter_type
|
|
@@ -594,7 +594,7 @@ function encodeTrackStatusOkPayload(
|
|
|
594
594
|
w.writeVarInt(msg.request_id)
|
|
595
595
|
w.writeVarInt(msg.track_alias)
|
|
596
596
|
w.writeVarInt(msg.expires)
|
|
597
|
-
w.writeUint8(
|
|
597
|
+
w.writeUint8(msg.group_order)
|
|
598
598
|
w.writeVarInt(msg.content_exists)
|
|
599
599
|
if (msg.content_exists === 1n) {
|
|
600
600
|
w.writeVarInt(msg.largest_location!.group)
|
|
@@ -659,8 +659,8 @@ function decodeSubscribePayload(r: BufferReader): Draft14Message {
|
|
|
659
659
|
const request_id = r.readVarInt()
|
|
660
660
|
const track_namespace = r.readTuple()
|
|
661
661
|
const track_name = r.readString()
|
|
662
|
-
const subscriber_priority =
|
|
663
|
-
const group_order =
|
|
662
|
+
const subscriber_priority = r.readUint8()
|
|
663
|
+
const group_order = r.readUint8()
|
|
664
664
|
const forward = r.readVarInt()
|
|
665
665
|
const filter_type = r.readVarInt()
|
|
666
666
|
|
|
@@ -705,7 +705,7 @@ function decodeSubscribeOkPayload(r: BufferReader): Draft14Message {
|
|
|
705
705
|
const request_id = r.readVarInt()
|
|
706
706
|
const track_alias = r.readVarInt()
|
|
707
707
|
const expires = r.readVarInt()
|
|
708
|
-
const group_order =
|
|
708
|
+
const group_order = r.readUint8()
|
|
709
709
|
const content_exists = r.readVarInt()
|
|
710
710
|
|
|
711
711
|
let largest_location: Location | undefined
|
|
@@ -736,7 +736,7 @@ function decodeSubscribeUpdatePayload(r: BufferReader): Draft14Message {
|
|
|
736
736
|
const start_group = r.readVarInt()
|
|
737
737
|
const start_object = r.readVarInt()
|
|
738
738
|
const end_group = r.readVarInt()
|
|
739
|
-
const subscriber_priority =
|
|
739
|
+
const subscriber_priority = r.readUint8()
|
|
740
740
|
const forward = r.readVarInt()
|
|
741
741
|
const parameters = decodeMessageParams(r)
|
|
742
742
|
return {
|
|
@@ -769,7 +769,7 @@ function decodePublishPayload(r: BufferReader): Draft14Message {
|
|
|
769
769
|
const track_namespace = r.readTuple()
|
|
770
770
|
const track_name = r.readString()
|
|
771
771
|
const track_alias = r.readVarInt()
|
|
772
|
-
const group_order =
|
|
772
|
+
const group_order = r.readUint8()
|
|
773
773
|
const content_exists = r.readVarInt()
|
|
774
774
|
let largest_location: Location | undefined
|
|
775
775
|
if (content_exists === 1n) {
|
|
@@ -797,8 +797,8 @@ function decodePublishPayload(r: BufferReader): Draft14Message {
|
|
|
797
797
|
function decodePublishOkPayload(r: BufferReader): Draft14Message {
|
|
798
798
|
const request_id = r.readVarInt()
|
|
799
799
|
const forward = r.readVarInt()
|
|
800
|
-
const subscriber_priority =
|
|
801
|
-
const group_order =
|
|
800
|
+
const subscriber_priority = r.readUint8()
|
|
801
|
+
const group_order = r.readUint8()
|
|
802
802
|
const filter_type = r.readVarInt()
|
|
803
803
|
|
|
804
804
|
let start_group: bigint | undefined
|
|
@@ -931,8 +931,8 @@ function decodeUnsubscribeNamespacePayload(r: BufferReader): Draft14Message {
|
|
|
931
931
|
|
|
932
932
|
function decodeFetchPayload(r: BufferReader): Draft14Message {
|
|
933
933
|
const request_id = r.readVarInt()
|
|
934
|
-
const subscriber_priority =
|
|
935
|
-
const group_order =
|
|
934
|
+
const subscriber_priority = r.readUint8()
|
|
935
|
+
const group_order = r.readUint8()
|
|
936
936
|
const fetch_type = r.readVarInt()
|
|
937
937
|
|
|
938
938
|
if (fetch_type < FetchType.Standalone || fetch_type > FetchType.AbsoluteJoining) {
|
|
@@ -940,12 +940,14 @@ function decodeFetchPayload(r: BufferReader): Draft14Message {
|
|
|
940
940
|
}
|
|
941
941
|
|
|
942
942
|
if (fetch_type === FetchType.Standalone) {
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
943
|
+
const standalone = {
|
|
944
|
+
track_namespace: r.readTuple(),
|
|
945
|
+
track_name: r.readString(),
|
|
946
|
+
start_group: r.readVarInt(),
|
|
947
|
+
start_object: r.readVarInt(),
|
|
948
|
+
end_group: r.readVarInt(),
|
|
949
|
+
end_object: r.readVarInt(),
|
|
950
|
+
}
|
|
949
951
|
const parameters = decodeMessageParams(r)
|
|
950
952
|
return {
|
|
951
953
|
type: 'fetch',
|
|
@@ -953,17 +955,14 @@ function decodeFetchPayload(r: BufferReader): Draft14Message {
|
|
|
953
955
|
subscriber_priority,
|
|
954
956
|
group_order,
|
|
955
957
|
fetch_type,
|
|
956
|
-
|
|
957
|
-
track_name,
|
|
958
|
-
start_group,
|
|
959
|
-
start_object,
|
|
960
|
-
end_group,
|
|
961
|
-
end_object,
|
|
958
|
+
standalone,
|
|
962
959
|
parameters,
|
|
963
960
|
}
|
|
964
961
|
} else {
|
|
965
|
-
const
|
|
966
|
-
|
|
962
|
+
const joining = {
|
|
963
|
+
joining_request_id: r.readVarInt(),
|
|
964
|
+
joining_start: r.readVarInt(),
|
|
965
|
+
}
|
|
967
966
|
const parameters = decodeMessageParams(r)
|
|
968
967
|
return {
|
|
969
968
|
type: 'fetch',
|
|
@@ -971,8 +970,7 @@ function decodeFetchPayload(r: BufferReader): Draft14Message {
|
|
|
971
970
|
subscriber_priority,
|
|
972
971
|
group_order,
|
|
973
972
|
fetch_type,
|
|
974
|
-
|
|
975
|
-
joining_start,
|
|
973
|
+
joining,
|
|
976
974
|
parameters,
|
|
977
975
|
}
|
|
978
976
|
}
|
|
@@ -980,7 +978,7 @@ function decodeFetchPayload(r: BufferReader): Draft14Message {
|
|
|
980
978
|
|
|
981
979
|
function decodeFetchOkPayload(r: BufferReader): Draft14Message {
|
|
982
980
|
const request_id = r.readVarInt()
|
|
983
|
-
const group_order =
|
|
981
|
+
const group_order = r.readUint8()
|
|
984
982
|
const end_of_track = r.readVarInt()
|
|
985
983
|
const end_location: Location = {
|
|
986
984
|
group: r.readVarInt(),
|
|
@@ -1013,8 +1011,8 @@ function decodeTrackStatusPayload(r: BufferReader): Draft14Message {
|
|
|
1013
1011
|
const request_id = r.readVarInt()
|
|
1014
1012
|
const track_namespace = r.readTuple()
|
|
1015
1013
|
const track_name = r.readString()
|
|
1016
|
-
const subscriber_priority =
|
|
1017
|
-
const group_order =
|
|
1014
|
+
const subscriber_priority = r.readUint8()
|
|
1015
|
+
const group_order = r.readUint8()
|
|
1018
1016
|
const forward = r.readVarInt()
|
|
1019
1017
|
const filter_type = r.readVarInt()
|
|
1020
1018
|
|
|
@@ -1048,7 +1046,7 @@ function decodeTrackStatusOkPayload(r: BufferReader): Draft14Message {
|
|
|
1048
1046
|
const request_id = r.readVarInt()
|
|
1049
1047
|
const track_alias = r.readVarInt()
|
|
1050
1048
|
const expires = r.readVarInt()
|
|
1051
|
-
const group_order =
|
|
1049
|
+
const group_order = r.readUint8()
|
|
1052
1050
|
const content_exists = r.readVarInt()
|
|
1053
1051
|
|
|
1054
1052
|
let largest_location: Location | undefined
|