@moqtap/codec 0.4.3 → 0.6.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.
Files changed (133) hide show
  1. package/dist/chunk-62LYLROG.js +2095 -0
  2. package/dist/chunk-A33U4WVS.cjs +1993 -0
  3. package/dist/chunk-A655PHFX.cjs +446 -0
  4. package/dist/chunk-CQ65LKPQ.cjs +446 -0
  5. package/dist/chunk-DLSGOZ6P.js +1993 -0
  6. package/dist/chunk-IWYKDCXP.js +446 -0
  7. package/dist/chunk-MSG2FXJ2.cjs +2095 -0
  8. package/dist/chunk-O5DQ44TM.js +446 -0
  9. package/dist/{codec-C7UzTYSS.d.cts → codec-B1snaM9L.d.cts} +1 -1
  10. package/dist/{codec-ZwV-Njt6.d.ts → codec-BLYAy2OP.d.ts} +1 -1
  11. package/dist/{codec-B2lYE0YA.d.ts → codec-BNS3gS77.d.ts} +1 -1
  12. package/dist/{codec-BbbLDSKA.d.cts → codec-BOtv3R5j.d.cts} +1 -1
  13. package/dist/{codec-DDUBJGn0.d.cts → codec-BOyrk_C9.d.cts} +1 -1
  14. package/dist/{codec-BWQ5Jlap.d.cts → codec-BqqoYdh_.d.cts} +1 -1
  15. package/dist/{codec-3Ppy_5Gd.d.ts → codec-CI0BjmHc.d.ts} +1 -1
  16. package/dist/{codec-DftaRuVz.d.ts → codec-CWj5iTxA.d.ts} +1 -1
  17. package/dist/{codec-B_QST4y1.d.cts → codec-CyApDKwf.d.cts} +1 -1
  18. package/dist/{codec-BFg5xnrJ.d.ts → codec-D2JbI2tp.d.ts} +1 -1
  19. package/dist/{codec-DFU-6Rax.d.ts → codec-D47r_-Rl.d.ts} +1 -1
  20. package/dist/{codec-Db-0pk38.d.ts → codec-D49OofBr.d.ts} +1 -1
  21. package/dist/codec-D89DWXbv.d.cts +30 -0
  22. package/dist/{codec-R9TJCq-E.d.cts → codec-D95gT98n.d.cts} +1 -1
  23. package/dist/codec-DAePx2NL.d.ts +30 -0
  24. package/dist/{codec-CpZrZ3ok.d.ts → codec-DJmV04i0.d.ts} +1 -1
  25. package/dist/{codec-Va_wZCF_.d.ts → codec-DL0njGkL.d.ts} +1 -1
  26. package/dist/{codec-CFWQY9Lj.d.cts → codec-DQWQSam3.d.cts} +1 -1
  27. package/dist/{codec-wtt6WF1K.d.ts → codec-DplRgIKq.d.ts} +1 -1
  28. package/dist/{codec-CewFCsEy.d.ts → codec-DujHf55Z.d.ts} +1 -1
  29. package/dist/{codec-j819z_hL.d.cts → codec-DvcJgYKq.d.cts} +1 -1
  30. package/dist/{codec-C8aSvE41.d.cts → codec-NMsTK0GX.d.cts} +1 -1
  31. package/dist/codec-WwwUipaU.d.ts +30 -0
  32. package/dist/{codec-CAbm9d_n.d.cts → codec-cou9XBMF.d.cts} +1 -1
  33. package/dist/{codec-lqDowbyT.d.cts → codec-hvkJfCF5.d.cts} +1 -1
  34. package/dist/codec-xeuGZY77.d.cts +30 -0
  35. package/dist/draft07-session.d.cts +3 -3
  36. package/dist/draft07-session.d.ts +3 -3
  37. package/dist/draft07.d.cts +3 -3
  38. package/dist/draft07.d.ts +3 -3
  39. package/dist/draft08-session.d.cts +3 -3
  40. package/dist/draft08-session.d.ts +3 -3
  41. package/dist/draft08.d.cts +4 -4
  42. package/dist/draft08.d.ts +4 -4
  43. package/dist/draft09-session.d.cts +3 -3
  44. package/dist/draft09-session.d.ts +3 -3
  45. package/dist/draft09.d.cts +4 -4
  46. package/dist/draft09.d.ts +4 -4
  47. package/dist/draft10-session.d.cts +3 -3
  48. package/dist/draft10-session.d.ts +3 -3
  49. package/dist/draft10.d.cts +4 -4
  50. package/dist/draft10.d.ts +4 -4
  51. package/dist/draft11-session.d.cts +3 -3
  52. package/dist/draft11-session.d.ts +3 -3
  53. package/dist/draft11.d.cts +4 -4
  54. package/dist/draft11.d.ts +4 -4
  55. package/dist/draft12-session.d.cts +3 -3
  56. package/dist/draft12-session.d.ts +3 -3
  57. package/dist/draft12.d.cts +4 -4
  58. package/dist/draft12.d.ts +4 -4
  59. package/dist/draft13-session.d.cts +3 -3
  60. package/dist/draft13-session.d.ts +3 -3
  61. package/dist/draft13.d.cts +4 -4
  62. package/dist/draft13.d.ts +4 -4
  63. package/dist/draft14-session.d.cts +3 -3
  64. package/dist/draft14-session.d.ts +3 -3
  65. package/dist/draft14.d.cts +4 -4
  66. package/dist/draft14.d.ts +4 -4
  67. package/dist/draft15-session.d.cts +3 -3
  68. package/dist/draft15-session.d.ts +3 -3
  69. package/dist/draft15.d.cts +4 -4
  70. package/dist/draft15.d.ts +4 -4
  71. package/dist/draft16-session.d.cts +3 -3
  72. package/dist/draft16-session.d.ts +3 -3
  73. package/dist/draft16.d.cts +4 -4
  74. package/dist/draft16.d.ts +4 -4
  75. package/dist/draft17-session.d.cts +3 -3
  76. package/dist/draft17-session.d.ts +3 -3
  77. package/dist/draft17.d.cts +3 -3
  78. package/dist/draft17.d.ts +3 -3
  79. package/dist/draft18-session.cjs +8 -0
  80. package/dist/draft18-session.d.cts +50 -0
  81. package/dist/draft18-session.d.ts +50 -0
  82. package/dist/draft18-session.js +8 -0
  83. package/dist/draft18.cjs +179 -0
  84. package/dist/draft18.d.cts +126 -0
  85. package/dist/draft18.d.ts +126 -0
  86. package/dist/draft18.js +179 -0
  87. package/dist/draft19-session.cjs +8 -0
  88. package/dist/draft19-session.d.cts +50 -0
  89. package/dist/draft19-session.d.ts +50 -0
  90. package/dist/draft19-session.js +8 -0
  91. package/dist/draft19.cjs +189 -0
  92. package/dist/draft19.d.cts +130 -0
  93. package/dist/draft19.d.ts +130 -0
  94. package/dist/draft19.js +189 -0
  95. package/dist/index.cjs +13 -1
  96. package/dist/index.d.cts +24 -14
  97. package/dist/index.d.ts +24 -14
  98. package/dist/index.js +13 -1
  99. package/dist/{session-types-X-IYEGoG.d.ts → session-types-C0l_-Ups.d.ts} +1 -1
  100. package/dist/{session-types-C7UONex5.d.cts → session-types-KsqyVp6y.d.cts} +1 -1
  101. package/dist/session.cjs +9 -1
  102. package/dist/session.d.cts +7 -3
  103. package/dist/session.d.ts +7 -3
  104. package/dist/session.js +9 -1
  105. package/dist/{types-DuIO7cWK.d.cts → types-CdCSIfvI.d.cts} +1 -1
  106. package/dist/{types-DuIO7cWK.d.ts → types-CdCSIfvI.d.ts} +1 -1
  107. package/dist/types-Cqp10JtB.d.cts +249 -0
  108. package/dist/types-Cqp10JtB.d.ts +249 -0
  109. package/dist/types-zHgCS6o5.d.cts +265 -0
  110. package/dist/types-zHgCS6o5.d.ts +265 -0
  111. package/package.json +26 -2
  112. package/src/core/types.ts +14 -1
  113. package/src/drafts/draft17/error-codes.ts +72 -0
  114. package/src/drafts/draft18/codec.ts +1310 -0
  115. package/src/drafts/draft18/data-streams.ts +996 -0
  116. package/src/drafts/draft18/error-codes.ts +75 -0
  117. package/src/drafts/draft18/index.ts +122 -0
  118. package/src/drafts/draft18/messages.ts +56 -0
  119. package/src/drafts/draft18/rules.ts +85 -0
  120. package/src/drafts/draft18/session-fsm.ts +451 -0
  121. package/src/drafts/draft18/session.ts +15 -0
  122. package/src/drafts/draft18/types.ts +347 -0
  123. package/src/drafts/draft19/codec.ts +1425 -0
  124. package/src/drafts/draft19/data-streams.ts +996 -0
  125. package/src/drafts/draft19/error-codes.ts +78 -0
  126. package/src/drafts/draft19/index.ts +126 -0
  127. package/src/drafts/draft19/messages.ts +58 -0
  128. package/src/drafts/draft19/rules.ts +85 -0
  129. package/src/drafts/draft19/session-fsm.ts +451 -0
  130. package/src/drafts/draft19/session.ts +15 -0
  131. package/src/drafts/draft19/types.ts +368 -0
  132. package/src/index.ts +17 -1
  133. package/src/session.ts +8 -0
@@ -0,0 +1,1310 @@
1
+ import { BufferReader } from '../../core/buffer-reader.js'
2
+ import { BufferWriter } from '../../core/buffer-writer.js'
3
+ import { bytesToHex, hexToBytes } from '../../core/hex.js'
4
+ import type { BaseCodec, DecodeResult } from '../../core/types.js'
5
+ import { DecodeError } from '../../core/types.js'
6
+ import {
7
+ MESSAGE_ID_MAP,
8
+ MSG_FETCH,
9
+ MSG_FETCH_OK,
10
+ MSG_GOAWAY,
11
+ MSG_NAMESPACE,
12
+ MSG_NAMESPACE_DONE,
13
+ MSG_PUBLISH,
14
+ MSG_PUBLISH_BLOCKED,
15
+ MSG_PUBLISH_DONE,
16
+ MSG_PUBLISH_NAMESPACE,
17
+ MSG_REQUEST_ERROR,
18
+ MSG_REQUEST_OK,
19
+ MSG_REQUEST_UPDATE,
20
+ MSG_SETUP,
21
+ MSG_SUBSCRIBE,
22
+ MSG_SUBSCRIBE_NAMESPACE,
23
+ MSG_SUBSCRIBE_OK,
24
+ MSG_SUBSCRIBE_TRACKS,
25
+ MSG_TRACK_STATUS,
26
+ SETUP_OPT_AUTHORITY,
27
+ SETUP_OPT_AUTHORIZATION_TOKEN,
28
+ SETUP_OPT_MAX_AUTH_TOKEN_CACHE_SIZE,
29
+ SETUP_OPT_MOQT_IMPLEMENTATION,
30
+ SETUP_OPT_PATH,
31
+ } from './messages.js'
32
+ import type {
33
+ AuthorizationToken,
34
+ DatagramObject,
35
+ DataStreamEvent,
36
+ Draft18DataStream,
37
+ Draft18Fetch,
38
+ Draft18Message,
39
+ Draft18Params,
40
+ Draft18SetupOptions,
41
+ Draft18TrackProperties,
42
+ FetchStream,
43
+ FetchStreamHeader,
44
+ JoiningFetch,
45
+ ObjectPayload,
46
+ Redirect,
47
+ StandaloneFetch,
48
+ SubgroupStream,
49
+ SubgroupStreamHeader,
50
+ UnknownParam,
51
+ } from './types.js'
52
+
53
+ const textEncoder = /* @__PURE__ */ new TextEncoder()
54
+ const textDecoder = /* @__PURE__ */ new TextDecoder()
55
+
56
+ const REQUEST_ERROR_REDIRECT = 0x34n
57
+
58
+ // ─── Setup Options Encoding/Decoding (KVP, no count prefix) ─────────────────
59
+
60
+ function encodeSetupOptions(opts: Draft18SetupOptions, writer: BufferWriter): void {
61
+ // Collect all options sorted by type ID
62
+ const entries: Array<{ type: bigint; encode: (w: BufferWriter) => void }> = []
63
+
64
+ if (opts.path !== undefined) {
65
+ entries.push({
66
+ type: SETUP_OPT_PATH,
67
+ encode: (w) => {
68
+ const encoded = textEncoder.encode(opts.path!)
69
+ w.writeVarInt(BigInt(encoded.byteLength))
70
+ w.writeBytes(encoded)
71
+ },
72
+ })
73
+ }
74
+ if (opts.authorization_token !== undefined) {
75
+ entries.push({
76
+ type: SETUP_OPT_AUTHORIZATION_TOKEN,
77
+ encode: (w) => {
78
+ const tmpW = new BufferWriter(64)
79
+ encodeAuthorizationToken(opts.authorization_token!, tmpW)
80
+ const raw = tmpW.finish()
81
+ w.writeVarInt(BigInt(raw.byteLength))
82
+ w.writeBytes(raw)
83
+ },
84
+ })
85
+ }
86
+ if (opts.max_auth_token_cache_size !== undefined) {
87
+ entries.push({
88
+ type: SETUP_OPT_MAX_AUTH_TOKEN_CACHE_SIZE,
89
+ encode: (w) => w.writeVarInt(opts.max_auth_token_cache_size!),
90
+ })
91
+ }
92
+ if (opts.authority !== undefined) {
93
+ entries.push({
94
+ type: SETUP_OPT_AUTHORITY,
95
+ encode: (w) => {
96
+ const encoded = textEncoder.encode(opts.authority!)
97
+ w.writeVarInt(BigInt(encoded.byteLength))
98
+ w.writeBytes(encoded)
99
+ },
100
+ })
101
+ }
102
+ if (opts.moqt_implementation !== undefined) {
103
+ entries.push({
104
+ type: SETUP_OPT_MOQT_IMPLEMENTATION,
105
+ encode: (w) => {
106
+ const encoded = textEncoder.encode(opts.moqt_implementation!)
107
+ w.writeVarInt(BigInt(encoded.byteLength))
108
+ w.writeBytes(encoded)
109
+ },
110
+ })
111
+ }
112
+ if (opts.unknown) {
113
+ for (const u of opts.unknown) {
114
+ const id = BigInt(u.id)
115
+ entries.push({
116
+ type: id,
117
+ encode: (w) => {
118
+ if (id % 2n === 0n) {
119
+ const raw = hexToBytes(u.raw_hex)
120
+ const tmpReader = new BufferReader(raw)
121
+ w.writeVarInt(tmpReader.readVarInt())
122
+ } else {
123
+ const raw = hexToBytes(u.raw_hex)
124
+ w.writeVarInt(BigInt(raw.byteLength))
125
+ w.writeBytes(raw)
126
+ }
127
+ },
128
+ })
129
+ }
130
+ }
131
+
132
+ // Sort by type (ascending) and write with delta encoding
133
+ entries.sort((a, b) => (a.type < b.type ? -1 : a.type > b.type ? 1 : 0))
134
+ let prevType = 0n
135
+ for (const entry of entries) {
136
+ writer.writeVarInt(entry.type - prevType)
137
+ entry.encode(writer)
138
+ prevType = entry.type
139
+ }
140
+ }
141
+
142
+ function decodeSetupOptions(reader: BufferReader, payloadEnd: number): Draft18SetupOptions {
143
+ const result: Draft18SetupOptions = {}
144
+ const unknown: UnknownParam[] = []
145
+ let prevType = 0n
146
+
147
+ while (reader.offset < payloadEnd) {
148
+ const delta = reader.readVarInt()
149
+ const optType = prevType + delta
150
+ prevType = optType
151
+
152
+ if (optType % 2n === 0n) {
153
+ // Even: single varint value
154
+ const value = reader.readVarInt()
155
+ if (optType === SETUP_OPT_MAX_AUTH_TOKEN_CACHE_SIZE) {
156
+ result.max_auth_token_cache_size = value
157
+ } else {
158
+ const tmpWriter = new BufferWriter(16)
159
+ tmpWriter.writeVarInt(value)
160
+ const raw = tmpWriter.finish()
161
+ unknown.push({
162
+ id: `0x${optType.toString(16)}`,
163
+ length: raw.byteLength,
164
+ raw_hex: bytesToHex(raw),
165
+ })
166
+ }
167
+ } else {
168
+ // Odd: length-prefixed bytes
169
+ const length = Number(reader.readVarInt())
170
+ const bytes = reader.readBytes(length)
171
+ if (optType === SETUP_OPT_PATH) {
172
+ result.path = textDecoder.decode(bytes)
173
+ } else if (optType === SETUP_OPT_AUTHORIZATION_TOKEN) {
174
+ result.authorization_token = decodeAuthorizationToken(new BufferReader(bytes))
175
+ } else if (optType === SETUP_OPT_AUTHORITY) {
176
+ result.authority = textDecoder.decode(bytes)
177
+ } else if (optType === SETUP_OPT_MOQT_IMPLEMENTATION) {
178
+ result.moqt_implementation = textDecoder.decode(bytes)
179
+ } else {
180
+ unknown.push({
181
+ id: `0x${optType.toString(16)}`,
182
+ length,
183
+ raw_hex: bytesToHex(bytes),
184
+ })
185
+ }
186
+ }
187
+ }
188
+
189
+ if (unknown.length > 0) result.unknown = unknown
190
+ return result
191
+ }
192
+
193
+ // ─── Authorization Token Encoding/Decoding ────────────────────────────────────
194
+
195
+ function encodeAuthorizationToken(token: AuthorizationToken, w: BufferWriter): void {
196
+ const aliasType = Number(token.alias_type)
197
+ w.writeVarInt(token.alias_type)
198
+ if (aliasType === 0 || aliasType === 1 || aliasType === 2) {
199
+ // DELETE, REGISTER, USE_ALIAS: token_alias present
200
+ w.writeVarInt(token.token_alias ?? 0n)
201
+ }
202
+ if (aliasType === 1 || aliasType === 3) {
203
+ // REGISTER, USE_VALUE: token_type + token_value present.
204
+ // In draft-18 the Token Value is NOT length-prefixed inside the Token
205
+ // structure; it consumes the remainder of the outer length-prefixed buffer.
206
+ w.writeVarInt(token.token_type ?? 0n)
207
+ const tv = token.token_value ?? new Uint8Array(0)
208
+ w.writeBytes(tv)
209
+ }
210
+ }
211
+
212
+ function decodeAuthorizationToken(r: BufferReader): AuthorizationToken {
213
+ const alias_type = r.readVarInt()
214
+ const aliasType = Number(alias_type)
215
+ const result: AuthorizationToken = { alias_type }
216
+
217
+ if (aliasType === 0 || aliasType === 1 || aliasType === 2) {
218
+ ;(result as { token_alias: bigint }).token_alias = r.readVarInt()
219
+ }
220
+ if (aliasType === 1 || aliasType === 3) {
221
+ ;(result as { token_type: bigint }).token_type = r.readVarInt()
222
+ // Token Value extends to the end of the outer length-prefixed buffer
223
+ const bytes = r.readBytes(r.remaining)
224
+ ;(result as { token_value: Uint8Array }).token_value = bytes
225
+ }
226
+
227
+ return result
228
+ }
229
+
230
+ // ─── Tuple (Track Namespace) encode/decode for params ────────────────────────
231
+
232
+ function encodeNamespaceTuple(ns: string[], w: BufferWriter): void {
233
+ w.writeVarInt(BigInt(ns.length))
234
+ for (const part of ns) {
235
+ const encoded = textEncoder.encode(part)
236
+ w.writeVarInt(BigInt(encoded.byteLength))
237
+ w.writeBytes(encoded)
238
+ }
239
+ }
240
+
241
+ function decodeNamespaceTuple(r: BufferReader): string[] {
242
+ const count = Number(r.readVarInt())
243
+ const parts: string[] = []
244
+ for (let i = 0; i < count; i++) {
245
+ const len = Number(r.readVarInt())
246
+ parts.push(textDecoder.decode(r.readBytes(len)))
247
+ }
248
+ return parts
249
+ }
250
+
251
+ // ─── Message Parameter Encoding/Decoding (delta types, count prefix) ─────────
252
+
253
+ const PARAM_OBJECT_DELIVERY_TIMEOUT = 0x02n
254
+ const PARAM_AUTHORIZATION_TOKEN = 0x03n
255
+ const PARAM_RENDEZVOUS_TIMEOUT = 0x04n
256
+ const PARAM_SUBGROUP_DELIVERY_TIMEOUT = 0x06n
257
+ const PARAM_EXPIRES = 0x08n
258
+ const PARAM_LARGEST_OBJECT = 0x09n
259
+ const PARAM_FILL_TIMEOUT = 0x0an
260
+ const PARAM_FORWARD = 0x10n
261
+ const PARAM_SUBSCRIBER_PRIORITY = 0x20n
262
+ const PARAM_SUBSCRIPTION_FILTER = 0x21n
263
+ const PARAM_GROUP_ORDER = 0x22n
264
+ const PARAM_NEW_GROUP_REQUEST = 0x32n
265
+ const PARAM_TRACK_NAMESPACE_PREFIX = 0x34n
266
+
267
+ function encodeParams(params: Draft18Params, writer: BufferWriter): void {
268
+ // Collect and sort params by type
269
+ const entries: Array<{ type: bigint; encode: (w: BufferWriter) => void }> = []
270
+
271
+ if (params.object_delivery_timeout !== undefined) {
272
+ entries.push({
273
+ type: PARAM_OBJECT_DELIVERY_TIMEOUT,
274
+ encode: (w) => w.writeVarInt(params.object_delivery_timeout!),
275
+ })
276
+ }
277
+ if (params.authorization_token !== undefined) {
278
+ entries.push({
279
+ type: PARAM_AUTHORIZATION_TOKEN,
280
+ encode: (w) => {
281
+ const tmpW = new BufferWriter(64)
282
+ encodeAuthorizationToken(params.authorization_token!, tmpW)
283
+ const raw = tmpW.finish()
284
+ w.writeVarInt(BigInt(raw.byteLength))
285
+ w.writeBytes(raw)
286
+ },
287
+ })
288
+ }
289
+ if (params.rendezvous_timeout !== undefined) {
290
+ entries.push({
291
+ type: PARAM_RENDEZVOUS_TIMEOUT,
292
+ encode: (w) => w.writeVarInt(params.rendezvous_timeout!),
293
+ })
294
+ }
295
+ if (params.subgroup_delivery_timeout !== undefined) {
296
+ entries.push({
297
+ type: PARAM_SUBGROUP_DELIVERY_TIMEOUT,
298
+ encode: (w) => w.writeVarInt(params.subgroup_delivery_timeout!),
299
+ })
300
+ }
301
+ if (params.expires !== undefined) {
302
+ entries.push({
303
+ type: PARAM_EXPIRES,
304
+ encode: (w) => w.writeVarInt(params.expires!),
305
+ })
306
+ }
307
+ if (params.largest_object !== undefined) {
308
+ entries.push({
309
+ type: PARAM_LARGEST_OBJECT,
310
+ encode: (w) => {
311
+ const tmpW = new BufferWriter(16)
312
+ tmpW.writeVarInt(params.largest_object!.group)
313
+ tmpW.writeVarInt(params.largest_object!.object)
314
+ const raw = tmpW.finish()
315
+ w.writeVarInt(BigInt(raw.byteLength))
316
+ w.writeBytes(raw)
317
+ },
318
+ })
319
+ }
320
+ if (params.fill_timeout !== undefined) {
321
+ entries.push({
322
+ type: PARAM_FILL_TIMEOUT,
323
+ encode: (w) => w.writeVarInt(params.fill_timeout!),
324
+ })
325
+ }
326
+ if (params.forward !== undefined) {
327
+ entries.push({
328
+ type: PARAM_FORWARD,
329
+ encode: (w) => w.writeUint8(Number(params.forward!)),
330
+ })
331
+ }
332
+ if (params.subscriber_priority !== undefined) {
333
+ entries.push({
334
+ type: PARAM_SUBSCRIBER_PRIORITY,
335
+ encode: (w) => w.writeUint8(Number(params.subscriber_priority!)),
336
+ })
337
+ }
338
+ if (params.subscription_filter !== undefined) {
339
+ entries.push({
340
+ type: PARAM_SUBSCRIPTION_FILTER,
341
+ encode: (w) => {
342
+ const f = params.subscription_filter!
343
+ const tmpW = new BufferWriter(32)
344
+ tmpW.writeVarInt(f.filter_type)
345
+ if (f.filter_type === 3n || f.filter_type === 4n) {
346
+ tmpW.writeVarInt(f.start_group!)
347
+ tmpW.writeVarInt(f.start_object!)
348
+ }
349
+ if (f.filter_type === 4n) {
350
+ tmpW.writeVarInt(f.end_group!)
351
+ }
352
+ const raw = tmpW.finish()
353
+ w.writeVarInt(BigInt(raw.byteLength))
354
+ w.writeBytes(raw)
355
+ },
356
+ })
357
+ }
358
+ if (params.group_order !== undefined) {
359
+ entries.push({
360
+ type: PARAM_GROUP_ORDER,
361
+ encode: (w) => w.writeUint8(Number(params.group_order!)),
362
+ })
363
+ }
364
+ if (params.new_group_request !== undefined) {
365
+ entries.push({
366
+ type: PARAM_NEW_GROUP_REQUEST,
367
+ encode: (w) => w.writeVarInt(params.new_group_request!),
368
+ })
369
+ }
370
+ if (params.track_namespace_prefix !== undefined) {
371
+ entries.push({
372
+ type: PARAM_TRACK_NAMESPACE_PREFIX,
373
+ encode: (w) => {
374
+ const tmpW = new BufferWriter(32)
375
+ encodeNamespaceTuple(params.track_namespace_prefix!, tmpW)
376
+ const raw = tmpW.finish()
377
+ w.writeVarInt(BigInt(raw.byteLength))
378
+ w.writeBytes(raw)
379
+ },
380
+ })
381
+ }
382
+
383
+ // Unknown params
384
+ if (params.unknown) {
385
+ for (const u of params.unknown) {
386
+ const id = BigInt(u.id)
387
+ entries.push({
388
+ type: id,
389
+ encode: (w) => {
390
+ const raw = hexToBytes(u.raw_hex)
391
+ // For unknown params, we store raw bytes and re-emit them
392
+ w.writeBytes(raw)
393
+ },
394
+ })
395
+ }
396
+ }
397
+
398
+ entries.sort((a, b) => (a.type < b.type ? -1 : a.type > b.type ? 1 : 0))
399
+
400
+ writer.writeVarInt(BigInt(entries.length))
401
+ let prevType = 0n
402
+ for (const entry of entries) {
403
+ writer.writeVarInt(entry.type - prevType)
404
+ entry.encode(writer)
405
+ prevType = entry.type
406
+ }
407
+ }
408
+
409
+ function decodeParams(reader: BufferReader): Draft18Params {
410
+ const count = Number(reader.readVarInt())
411
+ const result: Draft18Params = {}
412
+ const unknown: UnknownParam[] = []
413
+ let prevType = 0n
414
+
415
+ for (let i = 0; i < count; i++) {
416
+ const delta = reader.readVarInt()
417
+ const paramType = prevType + delta
418
+ prevType = paramType
419
+
420
+ if (paramType === PARAM_OBJECT_DELIVERY_TIMEOUT) {
421
+ result.object_delivery_timeout = reader.readVarInt()
422
+ } else if (paramType === PARAM_AUTHORIZATION_TOKEN) {
423
+ const length = Number(reader.readVarInt())
424
+ const bytes = reader.readBytes(length)
425
+ result.authorization_token = decodeAuthorizationToken(new BufferReader(bytes))
426
+ } else if (paramType === PARAM_RENDEZVOUS_TIMEOUT) {
427
+ result.rendezvous_timeout = reader.readVarInt()
428
+ } else if (paramType === PARAM_SUBGROUP_DELIVERY_TIMEOUT) {
429
+ result.subgroup_delivery_timeout = reader.readVarInt()
430
+ } else if (paramType === PARAM_EXPIRES) {
431
+ result.expires = reader.readVarInt()
432
+ } else if (paramType === PARAM_LARGEST_OBJECT) {
433
+ // Length-prefixed Location: 2 varints (Group, Object)
434
+ const length = Number(reader.readVarInt())
435
+ const startOff = reader.offset
436
+ const group = reader.readVarInt()
437
+ const object = reader.readVarInt()
438
+ const consumed = reader.offset - startOff
439
+ if (consumed < length) reader.readBytes(length - consumed)
440
+ result.largest_object = { group, object }
441
+ } else if (paramType === PARAM_FILL_TIMEOUT) {
442
+ result.fill_timeout = reader.readVarInt()
443
+ } else if (paramType === PARAM_FORWARD) {
444
+ // uint8 in draft-18 (was varint in draft-17)
445
+ result.forward = BigInt(reader.readUint8())
446
+ } else if (paramType === PARAM_SUBSCRIBER_PRIORITY) {
447
+ // uint8: single raw byte
448
+ result.subscriber_priority = BigInt(reader.readUint8())
449
+ } else if (paramType === PARAM_SUBSCRIPTION_FILTER) {
450
+ // Length-prefixed
451
+ const length = Number(reader.readVarInt())
452
+ const startOff = reader.offset
453
+ const filter_type = reader.readVarInt()
454
+ const filter: {
455
+ filter_type: bigint
456
+ start_group?: bigint
457
+ start_object?: bigint
458
+ end_group?: bigint
459
+ } = { filter_type }
460
+ if (filter_type === 3n || filter_type === 4n) {
461
+ filter.start_group = reader.readVarInt()
462
+ filter.start_object = reader.readVarInt()
463
+ }
464
+ if (filter_type === 4n) {
465
+ filter.end_group = reader.readVarInt()
466
+ }
467
+ const consumed = reader.offset - startOff
468
+ if (consumed < length) reader.readBytes(length - consumed)
469
+ result.subscription_filter = filter
470
+ } else if (paramType === PARAM_GROUP_ORDER) {
471
+ // uint8: single raw byte
472
+ result.group_order = BigInt(reader.readUint8())
473
+ } else if (paramType === PARAM_NEW_GROUP_REQUEST) {
474
+ result.new_group_request = reader.readVarInt()
475
+ } else if (paramType === PARAM_TRACK_NAMESPACE_PREFIX) {
476
+ const length = Number(reader.readVarInt())
477
+ const bytes = reader.readBytes(length)
478
+ result.track_namespace_prefix = decodeNamespaceTuple(new BufferReader(bytes))
479
+ } else {
480
+ // Unknown parameter — heuristic encoding decode
481
+ if (paramType % 2n === 0n) {
482
+ const value = reader.readVarInt()
483
+ const tmpWriter = new BufferWriter(16)
484
+ tmpWriter.writeVarInt(value)
485
+ const raw = tmpWriter.finish()
486
+ unknown.push({
487
+ id: `0x${paramType.toString(16)}`,
488
+ length: raw.byteLength,
489
+ raw_hex: bytesToHex(raw),
490
+ })
491
+ } else {
492
+ const length = Number(reader.readVarInt())
493
+ const bytes = reader.readBytes(length)
494
+ unknown.push({
495
+ id: `0x${paramType.toString(16)}`,
496
+ length,
497
+ raw_hex: bytesToHex(bytes),
498
+ })
499
+ }
500
+ }
501
+ }
502
+
503
+ if (unknown.length > 0) result.unknown = unknown
504
+ return result
505
+ }
506
+
507
+ // ─── Track Properties Encoding/Decoding (KVP, no count prefix) ──────────────
508
+
509
+ const TPROP_OBJECT_DELIVERY_TIMEOUT = 0x02n
510
+ const TPROP_MAX_CACHE_DURATION = 0x04n
511
+ const TPROP_SUBGROUP_DELIVERY_TIMEOUT = 0x06n
512
+ const TPROP_IMMUTABLE_PROPERTIES = 0x0bn
513
+ const TPROP_DEFAULT_PUBLISHER_PRIORITY = 0x0en
514
+ const TPROP_DEFAULT_PUBLISHER_GROUP_ORDER = 0x22n
515
+ const TPROP_DYNAMIC_GROUPS = 0x30n
516
+
517
+ function encodeTrackProperties(props: Draft18TrackProperties, writer: BufferWriter): void {
518
+ const entries: Array<{ type: bigint; encode: (w: BufferWriter) => void }> = []
519
+
520
+ if (props.object_delivery_timeout !== undefined) {
521
+ entries.push({
522
+ type: TPROP_OBJECT_DELIVERY_TIMEOUT,
523
+ encode: (w) => w.writeVarInt(props.object_delivery_timeout!),
524
+ })
525
+ }
526
+ if (props.max_cache_duration !== undefined) {
527
+ entries.push({
528
+ type: TPROP_MAX_CACHE_DURATION,
529
+ encode: (w) => w.writeVarInt(props.max_cache_duration!),
530
+ })
531
+ }
532
+ if (props.subgroup_delivery_timeout !== undefined) {
533
+ entries.push({
534
+ type: TPROP_SUBGROUP_DELIVERY_TIMEOUT,
535
+ encode: (w) => w.writeVarInt(props.subgroup_delivery_timeout!),
536
+ })
537
+ }
538
+ if (props.immutable_properties !== undefined) {
539
+ entries.push({
540
+ type: TPROP_IMMUTABLE_PROPERTIES,
541
+ encode: (w) => {
542
+ const raw = props.immutable_properties!
543
+ w.writeVarInt(BigInt(raw.byteLength))
544
+ w.writeBytes(raw)
545
+ },
546
+ })
547
+ }
548
+ if (props.default_publisher_priority !== undefined) {
549
+ entries.push({
550
+ type: TPROP_DEFAULT_PUBLISHER_PRIORITY,
551
+ encode: (w) => w.writeVarInt(props.default_publisher_priority!),
552
+ })
553
+ }
554
+ if (props.default_publisher_group_order !== undefined) {
555
+ entries.push({
556
+ type: TPROP_DEFAULT_PUBLISHER_GROUP_ORDER,
557
+ encode: (w) => w.writeVarInt(props.default_publisher_group_order!),
558
+ })
559
+ }
560
+ if (props.dynamic_groups !== undefined) {
561
+ entries.push({
562
+ type: TPROP_DYNAMIC_GROUPS,
563
+ encode: (w) => w.writeVarInt(props.dynamic_groups!),
564
+ })
565
+ }
566
+
567
+ if (props.unknown) {
568
+ for (const u of props.unknown) {
569
+ const id = BigInt(u.id)
570
+ entries.push({
571
+ type: id,
572
+ encode: (w) => {
573
+ if (id % 2n === 0n) {
574
+ const raw = hexToBytes(u.raw_hex)
575
+ const tmpReader = new BufferReader(raw)
576
+ w.writeVarInt(tmpReader.readVarInt())
577
+ } else {
578
+ const raw = hexToBytes(u.raw_hex)
579
+ w.writeVarInt(BigInt(raw.byteLength))
580
+ w.writeBytes(raw)
581
+ }
582
+ },
583
+ })
584
+ }
585
+ }
586
+
587
+ entries.sort((a, b) => (a.type < b.type ? -1 : a.type > b.type ? 1 : 0))
588
+
589
+ let prevType = 0n
590
+ for (const entry of entries) {
591
+ writer.writeVarInt(entry.type - prevType)
592
+ entry.encode(writer)
593
+ prevType = entry.type
594
+ }
595
+ }
596
+
597
+ function decodeTrackProperties(reader: BufferReader, payloadEnd: number): Draft18TrackProperties {
598
+ const result: Draft18TrackProperties = {}
599
+ const unknown: UnknownParam[] = []
600
+ let prevType = 0n
601
+
602
+ while (reader.offset < payloadEnd) {
603
+ const delta = reader.readVarInt()
604
+ const propType = prevType + delta
605
+ prevType = propType
606
+
607
+ if (propType % 2n === 0n) {
608
+ // Even: single varint value
609
+ const value = reader.readVarInt()
610
+ if (propType === TPROP_OBJECT_DELIVERY_TIMEOUT) {
611
+ result.object_delivery_timeout = value
612
+ } else if (propType === TPROP_MAX_CACHE_DURATION) {
613
+ result.max_cache_duration = value
614
+ } else if (propType === TPROP_SUBGROUP_DELIVERY_TIMEOUT) {
615
+ result.subgroup_delivery_timeout = value
616
+ } else if (propType === TPROP_DEFAULT_PUBLISHER_PRIORITY) {
617
+ result.default_publisher_priority = value
618
+ } else if (propType === TPROP_DEFAULT_PUBLISHER_GROUP_ORDER) {
619
+ result.default_publisher_group_order = value
620
+ } else if (propType === TPROP_DYNAMIC_GROUPS) {
621
+ result.dynamic_groups = value
622
+ } else {
623
+ const tmpWriter = new BufferWriter(16)
624
+ tmpWriter.writeVarInt(value)
625
+ const raw = tmpWriter.finish()
626
+ unknown.push({
627
+ id: `0x${propType.toString(16)}`,
628
+ length: raw.byteLength,
629
+ raw_hex: bytesToHex(raw),
630
+ })
631
+ }
632
+ } else {
633
+ // Odd: length-prefixed bytes
634
+ const length = Number(reader.readVarInt())
635
+ const bytes = reader.readBytes(length)
636
+ if (propType === TPROP_IMMUTABLE_PROPERTIES) {
637
+ result.immutable_properties = bytes
638
+ } else {
639
+ unknown.push({
640
+ id: `0x${propType.toString(16)}`,
641
+ length,
642
+ raw_hex: bytesToHex(bytes),
643
+ })
644
+ }
645
+ }
646
+ }
647
+
648
+ if (unknown.length > 0) result.unknown = unknown
649
+ return result
650
+ }
651
+
652
+ // ─── Redirect Structure Encoding/Decoding ───────────────────────────────────
653
+
654
+ function encodeRedirect(redirect: Redirect, w: BufferWriter): void {
655
+ const uri = textEncoder.encode(redirect.connect_uri)
656
+ w.writeVarInt(BigInt(uri.byteLength))
657
+ w.writeBytes(uri)
658
+ w.writeTuple(redirect.track_namespace)
659
+ w.writeString(redirect.track_name)
660
+ }
661
+
662
+ function decodeRedirect(r: BufferReader): Redirect {
663
+ const uriLen = Number(r.readVarInt())
664
+ const uriBytes = r.readBytes(uriLen)
665
+ const connect_uri = textDecoder.decode(uriBytes)
666
+ const track_namespace = r.readTuple()
667
+ const track_name = r.readString()
668
+ return { connect_uri, track_namespace, track_name }
669
+ }
670
+
671
+ // ─── Payload Encoders ──────────────────────────────────────────────────────────
672
+
673
+ function encodeSetupPayload(msg: Draft18Message & { type: 'setup' }, w: BufferWriter): void {
674
+ encodeSetupOptions(msg.options, w)
675
+ }
676
+
677
+ function encodeSubscribePayload(
678
+ msg: Draft18Message & { type: 'subscribe' },
679
+ w: BufferWriter,
680
+ ): void {
681
+ w.writeVarInt(msg.request_id)
682
+ w.writeTuple(msg.track_namespace)
683
+ w.writeString(msg.track_name)
684
+ encodeParams(msg.parameters, w)
685
+ }
686
+
687
+ function encodeSubscribeOkPayload(
688
+ msg: Draft18Message & { type: 'subscribe_ok' },
689
+ w: BufferWriter,
690
+ ): void {
691
+ w.writeVarInt(msg.track_alias)
692
+ encodeParams(msg.parameters, w)
693
+ encodeTrackProperties(msg.track_properties, w)
694
+ }
695
+
696
+ function encodeRequestUpdatePayload(
697
+ msg: Draft18Message & { type: 'request_update' },
698
+ w: BufferWriter,
699
+ ): void {
700
+ w.writeVarInt(msg.request_id)
701
+ encodeParams(msg.parameters, w)
702
+ }
703
+
704
+ function encodePublishPayload(msg: Draft18Message & { type: 'publish' }, w: BufferWriter): void {
705
+ w.writeVarInt(msg.request_id)
706
+ w.writeTuple(msg.track_namespace)
707
+ w.writeString(msg.track_name)
708
+ w.writeVarInt(msg.track_alias)
709
+ encodeParams(msg.parameters, w)
710
+ encodeTrackProperties(msg.track_properties, w)
711
+ }
712
+
713
+ function encodePublishDonePayload(
714
+ msg: Draft18Message & { type: 'publish_done' },
715
+ w: BufferWriter,
716
+ ): void {
717
+ w.writeVarInt(msg.status_code)
718
+ w.writeVarInt(msg.stream_count)
719
+ w.writeString(msg.reason_phrase)
720
+ }
721
+
722
+ function encodePublishNamespacePayload(
723
+ msg: Draft18Message & { type: 'publish_namespace' },
724
+ w: BufferWriter,
725
+ ): void {
726
+ w.writeVarInt(msg.request_id)
727
+ w.writeTuple(msg.track_namespace)
728
+ encodeParams(msg.parameters, w)
729
+ }
730
+
731
+ function encodeNamespacePayload(
732
+ msg: Draft18Message & { type: 'namespace' },
733
+ w: BufferWriter,
734
+ ): void {
735
+ w.writeTuple(msg.namespace_suffix)
736
+ }
737
+
738
+ function encodeNamespaceDonePayload(
739
+ msg: Draft18Message & { type: 'namespace_done' },
740
+ w: BufferWriter,
741
+ ): void {
742
+ w.writeTuple(msg.namespace_suffix)
743
+ }
744
+
745
+ function encodeSubscribeNamespacePayload(
746
+ msg: Draft18Message & { type: 'subscribe_namespace' },
747
+ w: BufferWriter,
748
+ ): void {
749
+ w.writeVarInt(msg.request_id)
750
+ w.writeTuple(msg.namespace_prefix)
751
+ encodeParams(msg.parameters, w)
752
+ }
753
+
754
+ function encodeSubscribeTracksPayload(
755
+ msg: Draft18Message & { type: 'subscribe_tracks' },
756
+ w: BufferWriter,
757
+ ): void {
758
+ w.writeVarInt(msg.request_id)
759
+ w.writeTuple(msg.namespace_prefix)
760
+ encodeParams(msg.parameters, w)
761
+ }
762
+
763
+ function encodePublishBlockedPayload(
764
+ msg: Draft18Message & { type: 'publish_blocked' },
765
+ w: BufferWriter,
766
+ ): void {
767
+ w.writeTuple(msg.namespace_suffix)
768
+ w.writeString(msg.track_name)
769
+ }
770
+
771
+ function encodeFetchPayload(msg: Draft18Message & { type: 'fetch' }, w: BufferWriter): void {
772
+ w.writeVarInt(msg.request_id)
773
+ w.writeVarInt(msg.fetch_type)
774
+ const ft = Number(msg.fetch_type)
775
+ if (ft === 1 && msg.standalone) {
776
+ w.writeTuple(msg.standalone.track_namespace)
777
+ w.writeString(msg.standalone.track_name)
778
+ w.writeVarInt(msg.standalone.start_group)
779
+ w.writeVarInt(msg.standalone.start_object)
780
+ w.writeVarInt(msg.standalone.end_group)
781
+ w.writeVarInt(msg.standalone.end_object)
782
+ } else if ((ft === 2 || ft === 3) && msg.joining) {
783
+ w.writeVarInt(msg.joining.joining_request_id)
784
+ w.writeVarInt(msg.joining.joining_start)
785
+ }
786
+ encodeParams(msg.parameters, w)
787
+ }
788
+
789
+ function encodeFetchOkPayload(msg: Draft18Message & { type: 'fetch_ok' }, w: BufferWriter): void {
790
+ w.writeUint8(msg.end_of_track)
791
+ w.writeVarInt(msg.end_group)
792
+ w.writeVarInt(msg.end_object)
793
+ encodeParams(msg.parameters, w)
794
+ encodeTrackProperties(msg.track_properties, w)
795
+ }
796
+
797
+ function encodeTrackStatusPayload(
798
+ msg: Draft18Message & { type: 'track_status' },
799
+ w: BufferWriter,
800
+ ): void {
801
+ w.writeVarInt(msg.request_id)
802
+ w.writeTuple(msg.track_namespace)
803
+ w.writeString(msg.track_name)
804
+ encodeParams(msg.parameters, w)
805
+ }
806
+
807
+ function encodeRequestOkPayload(
808
+ msg: Draft18Message & { type: 'request_ok' },
809
+ w: BufferWriter,
810
+ ): void {
811
+ encodeParams(msg.parameters, w)
812
+ encodeTrackProperties(msg.track_properties, w)
813
+ }
814
+
815
+ function encodeRequestErrorPayload(
816
+ msg: Draft18Message & { type: 'request_error' },
817
+ w: BufferWriter,
818
+ ): void {
819
+ w.writeVarInt(msg.error_code)
820
+ w.writeVarInt(msg.retry_interval)
821
+ w.writeString(msg.reason_phrase)
822
+ if (msg.error_code === REQUEST_ERROR_REDIRECT && msg.redirect) {
823
+ encodeRedirect(msg.redirect, w)
824
+ }
825
+ }
826
+
827
+ function encodeGoAwayPayload(msg: Draft18Message & { type: 'goaway' }, w: BufferWriter): void {
828
+ w.writeString(msg.new_session_uri)
829
+ w.writeVarInt(msg.timeout)
830
+ if (msg.request_id !== undefined) {
831
+ w.writeVarInt(msg.request_id)
832
+ }
833
+ }
834
+
835
+ // ─── Payload Decoders ──────────────────────────────────────────────────────────
836
+
837
+ function decodeSetupPayload(r: BufferReader, payloadEnd: number): Draft18Message {
838
+ const options = decodeSetupOptions(r, payloadEnd)
839
+ return { type: 'setup', options }
840
+ }
841
+
842
+ function decodeSubscribePayload(r: BufferReader): Draft18Message {
843
+ const request_id = r.readVarInt()
844
+ const track_namespace = r.readTuple()
845
+ const track_name = r.readString()
846
+ const parameters = decodeParams(r)
847
+ return {
848
+ type: 'subscribe',
849
+ request_id,
850
+ track_namespace,
851
+ track_name,
852
+ parameters,
853
+ }
854
+ }
855
+
856
+ function decodeSubscribeOkPayload(r: BufferReader, payloadEnd: number): Draft18Message {
857
+ const track_alias = r.readVarInt()
858
+ const parameters = decodeParams(r)
859
+ const track_properties = decodeTrackProperties(r, payloadEnd)
860
+ return { type: 'subscribe_ok', track_alias, parameters, track_properties }
861
+ }
862
+
863
+ function decodeRequestUpdatePayload(r: BufferReader): Draft18Message {
864
+ const request_id = r.readVarInt()
865
+ const parameters = decodeParams(r)
866
+ return {
867
+ type: 'request_update',
868
+ request_id,
869
+ parameters,
870
+ }
871
+ }
872
+
873
+ function decodePublishPayload(r: BufferReader, payloadEnd: number): Draft18Message {
874
+ const request_id = r.readVarInt()
875
+ const track_namespace = r.readTuple()
876
+ const track_name = r.readString()
877
+ const track_alias = r.readVarInt()
878
+ const parameters = decodeParams(r)
879
+ const track_properties = decodeTrackProperties(r, payloadEnd)
880
+ return {
881
+ type: 'publish',
882
+ request_id,
883
+ track_namespace,
884
+ track_name,
885
+ track_alias,
886
+ parameters,
887
+ track_properties,
888
+ }
889
+ }
890
+
891
+ function decodePublishDonePayload(r: BufferReader): Draft18Message {
892
+ const status_code = r.readVarInt()
893
+ const stream_count = r.readVarInt()
894
+ const reason_phrase = r.readString()
895
+ return { type: 'publish_done', status_code, stream_count, reason_phrase }
896
+ }
897
+
898
+ function decodePublishNamespacePayload(r: BufferReader): Draft18Message {
899
+ const request_id = r.readVarInt()
900
+ const track_namespace = r.readTuple()
901
+ const parameters = decodeParams(r)
902
+ return {
903
+ type: 'publish_namespace',
904
+ request_id,
905
+ track_namespace,
906
+ parameters,
907
+ }
908
+ }
909
+
910
+ function decodeNamespacePayload(r: BufferReader): Draft18Message {
911
+ const namespace_suffix = r.readTuple()
912
+ return { type: 'namespace', namespace_suffix }
913
+ }
914
+
915
+ function decodeNamespaceDonePayload(r: BufferReader): Draft18Message {
916
+ const namespace_suffix = r.readTuple()
917
+ return { type: 'namespace_done', namespace_suffix }
918
+ }
919
+
920
+ function decodeSubscribeNamespacePayload(r: BufferReader): Draft18Message {
921
+ const request_id = r.readVarInt()
922
+ const namespace_prefix = r.readTuple()
923
+ const parameters = decodeParams(r)
924
+ return {
925
+ type: 'subscribe_namespace',
926
+ request_id,
927
+ namespace_prefix,
928
+ parameters,
929
+ }
930
+ }
931
+
932
+ function decodeSubscribeTracksPayload(r: BufferReader): Draft18Message {
933
+ const request_id = r.readVarInt()
934
+ const namespace_prefix = r.readTuple()
935
+ const parameters = decodeParams(r)
936
+ return {
937
+ type: 'subscribe_tracks',
938
+ request_id,
939
+ namespace_prefix,
940
+ parameters,
941
+ }
942
+ }
943
+
944
+ function decodePublishBlockedPayload(r: BufferReader): Draft18Message {
945
+ const namespace_suffix = r.readTuple()
946
+ const track_name = r.readString()
947
+ return { type: 'publish_blocked', namespace_suffix, track_name }
948
+ }
949
+
950
+ function decodeFetchPayload(r: BufferReader): Draft18Message {
951
+ const request_id = r.readVarInt()
952
+ const fetch_type = r.readVarInt()
953
+ const ft = Number(fetch_type)
954
+
955
+ if (ft < 1 || ft > 3) {
956
+ throw new DecodeError('CONSTRAINT_VIOLATION', `Invalid fetch_type: ${ft}`, r.offset)
957
+ }
958
+
959
+ let standalone: StandaloneFetch | undefined
960
+ let joining: JoiningFetch | undefined
961
+
962
+ if (ft === 1) {
963
+ const track_namespace = r.readTuple()
964
+ const track_name = r.readString()
965
+ const start_group = r.readVarInt()
966
+ const start_object = r.readVarInt()
967
+ const end_group = r.readVarInt()
968
+ const end_object = r.readVarInt()
969
+ standalone = {
970
+ track_namespace,
971
+ track_name,
972
+ start_group,
973
+ start_object,
974
+ end_group,
975
+ end_object,
976
+ }
977
+ } else {
978
+ const joining_request_id = r.readVarInt()
979
+ const joining_start = r.readVarInt()
980
+ joining = { joining_request_id, joining_start }
981
+ }
982
+
983
+ const parameters = decodeParams(r)
984
+
985
+ return {
986
+ type: 'fetch',
987
+ request_id,
988
+ fetch_type,
989
+ standalone,
990
+ joining,
991
+ parameters,
992
+ } as Draft18Fetch
993
+ }
994
+
995
+ function decodeFetchOkPayload(r: BufferReader, payloadEnd: number): Draft18Message {
996
+ const end_of_track = r.readUint8()
997
+ const end_group = r.readVarInt()
998
+ const end_object = r.readVarInt()
999
+ const parameters = decodeParams(r)
1000
+ const track_properties = decodeTrackProperties(r, payloadEnd)
1001
+ return {
1002
+ type: 'fetch_ok',
1003
+ end_of_track,
1004
+ end_group,
1005
+ end_object,
1006
+ parameters,
1007
+ track_properties,
1008
+ }
1009
+ }
1010
+
1011
+ function decodeTrackStatusPayload(r: BufferReader): Draft18Message {
1012
+ const request_id = r.readVarInt()
1013
+ const track_namespace = r.readTuple()
1014
+ const track_name = r.readString()
1015
+ const parameters = decodeParams(r)
1016
+ return {
1017
+ type: 'track_status',
1018
+ request_id,
1019
+ track_namespace,
1020
+ track_name,
1021
+ parameters,
1022
+ }
1023
+ }
1024
+
1025
+ function decodeRequestOkPayload(r: BufferReader, payloadEnd: number): Draft18Message {
1026
+ const parameters = decodeParams(r)
1027
+ const track_properties = decodeTrackProperties(r, payloadEnd)
1028
+ return { type: 'request_ok', parameters, track_properties }
1029
+ }
1030
+
1031
+ function decodeRequestErrorPayload(r: BufferReader, payloadEnd: number): Draft18Message {
1032
+ const error_code = r.readVarInt()
1033
+ const retry_interval = r.readVarInt()
1034
+ const reason_phrase = r.readString()
1035
+ let redirect: Redirect | undefined
1036
+ if (error_code === REQUEST_ERROR_REDIRECT && r.offset < payloadEnd) {
1037
+ redirect = decodeRedirect(r)
1038
+ }
1039
+ if (redirect !== undefined) {
1040
+ return { type: 'request_error', error_code, retry_interval, reason_phrase, redirect }
1041
+ }
1042
+ return { type: 'request_error', error_code, retry_interval, reason_phrase }
1043
+ }
1044
+
1045
+ function decodeGoAwayPayload(r: BufferReader, payloadEnd: number): Draft18Message {
1046
+ const new_session_uri = r.readString()
1047
+ const timeout = r.readVarInt()
1048
+ let request_id: bigint | undefined
1049
+ if (r.offset < payloadEnd) {
1050
+ request_id = r.readVarInt()
1051
+ }
1052
+ if (request_id !== undefined) {
1053
+ return { type: 'goaway', new_session_uri, timeout, request_id }
1054
+ }
1055
+ return { type: 'goaway', new_session_uri, timeout }
1056
+ }
1057
+
1058
+ // ─── Public API ────────────────────────────────────────────────────────────────
1059
+
1060
+ /**
1061
+ * Encode a draft-18 control message with type(varint) + length(uint16 BE) + payload.
1062
+ */
1063
+ export function encodeMessage(message: Draft18Message): Uint8Array {
1064
+ const typeId = MESSAGE_ID_MAP.get(message.type)
1065
+ if (typeId === undefined) {
1066
+ throw new Error(`Unknown message type: ${message.type}`)
1067
+ }
1068
+
1069
+ const payloadWriter = new BufferWriter()
1070
+ encodePayload(message, payloadWriter)
1071
+ const payload = payloadWriter.finishView()
1072
+
1073
+ if (payload.byteLength > 0xffff) {
1074
+ throw new Error(`Payload too large for 16-bit length: ${payload.byteLength}`)
1075
+ }
1076
+
1077
+ const writer = new BufferWriter(payload.byteLength + 16)
1078
+ writer.writeVarInt(typeId)
1079
+ writer.writeUint8((payload.byteLength >> 8) & 0xff)
1080
+ writer.writeUint8(payload.byteLength & 0xff)
1081
+ writer.writeBytes(payload)
1082
+
1083
+ return writer.finish()
1084
+ }
1085
+
1086
+ function encodePayload(msg: Draft18Message, w: BufferWriter): void {
1087
+ switch (msg.type) {
1088
+ case 'setup':
1089
+ return encodeSetupPayload(msg, w)
1090
+ case 'subscribe':
1091
+ return encodeSubscribePayload(msg, w)
1092
+ case 'subscribe_ok':
1093
+ return encodeSubscribeOkPayload(msg, w)
1094
+ case 'request_update':
1095
+ return encodeRequestUpdatePayload(msg, w)
1096
+ case 'publish':
1097
+ return encodePublishPayload(msg, w)
1098
+ case 'publish_done':
1099
+ return encodePublishDonePayload(msg, w)
1100
+ case 'publish_namespace':
1101
+ return encodePublishNamespacePayload(msg, w)
1102
+ case 'namespace':
1103
+ return encodeNamespacePayload(msg, w)
1104
+ case 'namespace_done':
1105
+ return encodeNamespaceDonePayload(msg, w)
1106
+ case 'subscribe_namespace':
1107
+ return encodeSubscribeNamespacePayload(msg, w)
1108
+ case 'subscribe_tracks':
1109
+ return encodeSubscribeTracksPayload(msg, w)
1110
+ case 'publish_blocked':
1111
+ return encodePublishBlockedPayload(msg, w)
1112
+ case 'fetch':
1113
+ return encodeFetchPayload(msg, w)
1114
+ case 'fetch_ok':
1115
+ return encodeFetchOkPayload(msg, w)
1116
+ case 'track_status':
1117
+ return encodeTrackStatusPayload(msg, w)
1118
+ case 'request_ok':
1119
+ return encodeRequestOkPayload(msg, w)
1120
+ case 'request_error':
1121
+ return encodeRequestErrorPayload(msg, w)
1122
+ case 'goaway':
1123
+ return encodeGoAwayPayload(msg, w)
1124
+ default: {
1125
+ const _exhaustive: never = msg
1126
+ throw new Error(`Unhandled message type: ${(_exhaustive as Draft18Message).type}`)
1127
+ }
1128
+ }
1129
+ }
1130
+
1131
+ /**
1132
+ * Decode a draft-18 control message from bytes (type + uint16 length + payload).
1133
+ */
1134
+ export function decodeMessage(bytes: Uint8Array): DecodeResult<Draft18Message> {
1135
+ try {
1136
+ const reader = new BufferReader(bytes)
1137
+ const typeId = reader.readVarInt()
1138
+
1139
+ const lenHi = reader.readUint8()
1140
+ const lenLo = reader.readUint8()
1141
+ const payloadLength = (lenHi << 8) | lenLo
1142
+
1143
+ const payloadBytes = reader.readBytes(payloadLength)
1144
+ const payloadReader = new BufferReader(payloadBytes)
1145
+
1146
+ let message: Draft18Message
1147
+
1148
+ if (typeId === MSG_SETUP) {
1149
+ message = decodeSetupPayload(payloadReader, payloadLength)
1150
+ } else if (typeId === MSG_SUBSCRIBE) {
1151
+ message = decodeSubscribePayload(payloadReader)
1152
+ } else if (typeId === MSG_SUBSCRIBE_OK) {
1153
+ message = decodeSubscribeOkPayload(payloadReader, payloadLength)
1154
+ } else if (typeId === MSG_REQUEST_UPDATE) {
1155
+ message = decodeRequestUpdatePayload(payloadReader)
1156
+ } else if (typeId === MSG_PUBLISH) {
1157
+ message = decodePublishPayload(payloadReader, payloadLength)
1158
+ } else if (typeId === MSG_PUBLISH_DONE) {
1159
+ message = decodePublishDonePayload(payloadReader)
1160
+ } else if (typeId === MSG_PUBLISH_NAMESPACE) {
1161
+ message = decodePublishNamespacePayload(payloadReader)
1162
+ } else if (typeId === MSG_NAMESPACE) {
1163
+ message = decodeNamespacePayload(payloadReader)
1164
+ } else if (typeId === MSG_NAMESPACE_DONE) {
1165
+ message = decodeNamespaceDonePayload(payloadReader)
1166
+ } else if (typeId === MSG_SUBSCRIBE_NAMESPACE) {
1167
+ message = decodeSubscribeNamespacePayload(payloadReader)
1168
+ } else if (typeId === MSG_SUBSCRIBE_TRACKS) {
1169
+ message = decodeSubscribeTracksPayload(payloadReader)
1170
+ } else if (typeId === MSG_PUBLISH_BLOCKED) {
1171
+ message = decodePublishBlockedPayload(payloadReader)
1172
+ } else if (typeId === MSG_FETCH) {
1173
+ message = decodeFetchPayload(payloadReader)
1174
+ } else if (typeId === MSG_FETCH_OK) {
1175
+ message = decodeFetchOkPayload(payloadReader, payloadLength)
1176
+ } else if (typeId === MSG_TRACK_STATUS) {
1177
+ message = decodeTrackStatusPayload(payloadReader)
1178
+ } else if (typeId === MSG_REQUEST_OK) {
1179
+ message = decodeRequestOkPayload(payloadReader, payloadLength)
1180
+ } else if (typeId === MSG_REQUEST_ERROR) {
1181
+ message = decodeRequestErrorPayload(payloadReader, payloadLength)
1182
+ } else if (typeId === MSG_GOAWAY) {
1183
+ message = decodeGoAwayPayload(payloadReader, payloadLength)
1184
+ } else {
1185
+ return {
1186
+ ok: false,
1187
+ error: new DecodeError(
1188
+ 'UNKNOWN_MESSAGE_TYPE',
1189
+ `Unknown message type ID: 0x${typeId.toString(16)}`,
1190
+ 0,
1191
+ ),
1192
+ }
1193
+ }
1194
+
1195
+ return { ok: true, value: message, bytesRead: reader.offset }
1196
+ } catch (e) {
1197
+ if (e instanceof DecodeError) {
1198
+ return { ok: false, error: e }
1199
+ }
1200
+ throw e
1201
+ }
1202
+ }
1203
+
1204
+ // ─── Data Stream Encoding/Decoding (re-exported from data-streams.ts) ───────
1205
+
1206
+ import {
1207
+ createDataStreamDecoder,
1208
+ createFetchStreamDecoder,
1209
+ createSubgroupStreamDecoder,
1210
+ decodeDatagram,
1211
+ decodeDataStream,
1212
+ decodeFetchStream,
1213
+ decodeSubgroupStream,
1214
+ encodeDatagram,
1215
+ encodeFetchStream,
1216
+ encodeSubgroupStream,
1217
+ } from './data-streams.js'
1218
+
1219
+ export {
1220
+ createDataStreamDecoder,
1221
+ createFetchStreamDecoder,
1222
+ createSubgroupStreamDecoder,
1223
+ decodeDatagram,
1224
+ decodeDataStream,
1225
+ decodeFetchStream,
1226
+ decodeSubgroupStream,
1227
+ encodeDatagram,
1228
+ encodeFetchStream,
1229
+ encodeSubgroupStream,
1230
+ }
1231
+
1232
+ // ─── Stream Decoders ───────────────────────────────────────────────────────────
1233
+
1234
+ export function createStreamDecoder(): TransformStream<Uint8Array, Draft18Message> {
1235
+ let buffer = new Uint8Array(0)
1236
+ let offset = 0
1237
+
1238
+ return new TransformStream<Uint8Array, Draft18Message>({
1239
+ transform(chunk, controller) {
1240
+ if (offset > 0) {
1241
+ buffer = buffer.subarray(offset)
1242
+ offset = 0
1243
+ }
1244
+ const newBuffer = new Uint8Array(buffer.length + chunk.length)
1245
+ newBuffer.set(buffer, 0)
1246
+ newBuffer.set(chunk, buffer.length)
1247
+ buffer = newBuffer
1248
+
1249
+ while (offset < buffer.length) {
1250
+ const result = decodeMessage(buffer.subarray(offset))
1251
+ if (!result.ok) {
1252
+ if (result.error.code === 'UNEXPECTED_END') {
1253
+ break
1254
+ }
1255
+ controller.error(result.error)
1256
+ return
1257
+ }
1258
+ controller.enqueue(result.value)
1259
+ offset += result.bytesRead
1260
+ }
1261
+ },
1262
+
1263
+ flush(controller) {
1264
+ if (offset < buffer.length) {
1265
+ controller.error(
1266
+ new DecodeError('UNEXPECTED_END', 'Stream ended with incomplete message data', 0),
1267
+ )
1268
+ }
1269
+ },
1270
+ })
1271
+ }
1272
+
1273
+ // ─── Codec Factory ─────────────────────────────────────────────────────────────
1274
+
1275
+ export interface Draft18Codec extends BaseCodec<Draft18Message> {
1276
+ readonly draft: '18'
1277
+ encodeSubgroupStream(stream: SubgroupStream): Uint8Array
1278
+ encodeDatagram(dg: DatagramObject): Uint8Array
1279
+ encodeFetchStream(stream: FetchStream): Uint8Array
1280
+ decodeSubgroupStream(bytes: Uint8Array): DecodeResult<SubgroupStream>
1281
+ decodeDatagram(bytes: Uint8Array): DecodeResult<DatagramObject>
1282
+ decodeFetchStream(bytes: Uint8Array): DecodeResult<FetchStream>
1283
+ decodeDataStream(
1284
+ streamType: 'subgroup' | 'datagram' | 'fetch',
1285
+ bytes: Uint8Array,
1286
+ ): DecodeResult<Draft18DataStream>
1287
+ createStreamDecoder(): TransformStream<Uint8Array, Draft18Message>
1288
+ createSubgroupStreamDecoder(): TransformStream<Uint8Array, SubgroupStreamHeader | ObjectPayload>
1289
+ createFetchStreamDecoder(): TransformStream<Uint8Array, FetchStreamHeader | ObjectPayload>
1290
+ createDataStreamDecoder(): TransformStream<Uint8Array, DataStreamEvent>
1291
+ }
1292
+
1293
+ export function createDraft18Codec(): Draft18Codec {
1294
+ return {
1295
+ draft: '18',
1296
+ encodeMessage,
1297
+ decodeMessage,
1298
+ encodeSubgroupStream,
1299
+ encodeDatagram,
1300
+ encodeFetchStream,
1301
+ decodeSubgroupStream,
1302
+ decodeDatagram,
1303
+ decodeFetchStream,
1304
+ decodeDataStream,
1305
+ createStreamDecoder,
1306
+ createSubgroupStreamDecoder,
1307
+ createFetchStreamDecoder,
1308
+ createDataStreamDecoder,
1309
+ }
1310
+ }