@moqtap/codec 0.1.1 → 0.2.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 (252) hide show
  1. package/README.md +99 -95
  2. package/dist/chunk-4RIXXEII.js +1275 -0
  3. package/dist/chunk-4XYGE53S.cjs +698 -0
  4. package/dist/chunk-4YJANAXU.cjs +1109 -0
  5. package/dist/chunk-6AEHWULA.cjs +641 -0
  6. package/dist/chunk-7DUBLRXC.js +680 -0
  7. package/dist/{chunk-YBZD3DU5.cjs → chunk-7IVGHMKJ.cjs} +44 -42
  8. package/dist/chunk-BISI45MN.cjs +680 -0
  9. package/dist/{chunk-NLYTRGXA.cjs → chunk-BPNL5YFQ.cjs} +5 -5
  10. package/dist/chunk-CXDHOMHG.js +1097 -0
  11. package/dist/chunk-DUBCL3WF.cjs +749 -0
  12. package/dist/chunk-DWK5ZQZ4.js +642 -0
  13. package/dist/chunk-E6E3NQYU.js +680 -0
  14. package/dist/chunk-EFM5T7OM.js +698 -0
  15. package/dist/chunk-ENURAVHI.cjs +680 -0
  16. package/dist/{chunk-Z66WDWHI.js → chunk-FXZ2MYKJ.js} +127 -1
  17. package/dist/chunk-G26SJ6XS.cjs +1341 -0
  18. package/dist/chunk-G7GI7LJA.js +737 -0
  19. package/dist/chunk-GXEW4COZ.cjs +737 -0
  20. package/dist/chunk-HSVYF6XX.cjs +1361 -0
  21. package/dist/chunk-IBVM4DMJ.cjs +1097 -0
  22. package/dist/chunk-IV2H5CFI.cjs +1275 -0
  23. package/dist/chunk-IV2HRJVT.js +1198 -0
  24. package/dist/chunk-JSQM2MG3.js +680 -0
  25. package/dist/chunk-K4OLITS2.cjs +1055 -0
  26. package/dist/{chunk-UOBWHJA5.js → chunk-KFTCU2P6.js} +2 -3
  27. package/dist/chunk-LH4NTURO.js +1361 -0
  28. package/dist/{chunk-YTXLWKOR.js → chunk-MFAP7R6L.js} +1 -1
  29. package/dist/chunk-NGVE2RZT.js +1097 -0
  30. package/dist/chunk-NUX5BHWO.js +1341 -0
  31. package/dist/chunk-PJRA2TQ5.js +1055 -0
  32. package/dist/chunk-RVJAGE4S.cjs +1198 -0
  33. package/dist/{chunk-QYG6KGOV.cjs → chunk-RWQ43Z4F.cjs} +2 -3
  34. package/dist/chunk-RZHAPEXO.js +749 -0
  35. package/dist/chunk-ST24APEO.js +1109 -0
  36. package/dist/chunk-SYHW3FLI.cjs +642 -0
  37. package/dist/chunk-TLYNOOQP.cjs +432 -0
  38. package/dist/{chunk-IXHOBNXA.js → chunk-TMNGRIPL.js} +43 -41
  39. package/dist/chunk-UNS34PTA.cjs +680 -0
  40. package/dist/chunk-UR6JKS56.js +432 -0
  41. package/dist/{chunk-NPWHHWXT.cjs → chunk-UYXTY6ZQ.cjs} +127 -1
  42. package/dist/chunk-XUUCOLWU.cjs +1097 -0
  43. package/dist/chunk-YG5KJESI.js +641 -0
  44. package/dist/chunk-ZBKE2QRQ.js +1401 -0
  45. package/dist/chunk-ZSPO2GF2.cjs +1401 -0
  46. package/dist/codec-95k8CAu5.d.cts +35 -0
  47. package/dist/codec-AFuOxfsO.d.ts +60 -0
  48. package/dist/codec-B-UJ5Iow.d.cts +75 -0
  49. package/dist/codec-BC5jfvMb.d.ts +35 -0
  50. package/dist/codec-BECYPfY8.d.ts +35 -0
  51. package/dist/codec-BsPU1vNC.d.ts +39 -0
  52. package/dist/codec-BvpuF-6u.d.cts +39 -0
  53. package/dist/codec-C8jZI5Cx.d.cts +39 -0
  54. package/dist/codec-CAevkgf5.d.cts +33 -0
  55. package/dist/codec-CSUqCrRs.d.ts +39 -0
  56. package/dist/codec-C_HMXNK_.d.ts +33 -0
  57. package/dist/{codec-Bvr7rFtj.d.cts → codec-CpuvYTSV.d.cts} +1 -1
  58. package/dist/codec-D0x8-SCw.d.cts +35 -0
  59. package/dist/codec-D7ARhpG1.d.ts +75 -0
  60. package/dist/codec-DNAUGshO.d.cts +60 -0
  61. package/dist/codec-DPx_QNn0.d.ts +31 -0
  62. package/dist/{codec-B2mc2g3i.d.ts → codec-DRhCx_hw.d.ts} +1 -1
  63. package/dist/codec-Db7YPe3l.d.ts +31 -0
  64. package/dist/codec-axkJpb7D.d.cts +31 -0
  65. package/dist/codec-ujAbFaep.d.cts +31 -0
  66. package/dist/draft10-session.cjs +6 -0
  67. package/dist/draft10-session.d.cts +8 -0
  68. package/dist/draft10-session.d.ts +8 -0
  69. package/dist/draft10-session.js +6 -0
  70. package/dist/draft10.cjs +115 -0
  71. package/dist/draft10.d.cts +95 -0
  72. package/dist/draft10.d.ts +95 -0
  73. package/dist/draft10.js +115 -0
  74. package/dist/draft11-session.cjs +6 -0
  75. package/dist/draft11-session.d.cts +8 -0
  76. package/dist/draft11-session.d.ts +8 -0
  77. package/dist/draft11-session.js +6 -0
  78. package/dist/draft11.cjs +109 -0
  79. package/dist/draft11.d.cts +99 -0
  80. package/dist/draft11.d.ts +99 -0
  81. package/dist/draft11.js +109 -0
  82. package/dist/draft12-session.cjs +6 -0
  83. package/dist/draft12-session.d.cts +8 -0
  84. package/dist/draft12-session.d.ts +8 -0
  85. package/dist/draft12-session.js +6 -0
  86. package/dist/draft12.cjs +117 -0
  87. package/dist/draft12.d.cts +106 -0
  88. package/dist/draft12.d.ts +106 -0
  89. package/dist/draft12.js +117 -0
  90. package/dist/draft13-session.cjs +6 -0
  91. package/dist/draft13-session.d.cts +8 -0
  92. package/dist/draft13-session.d.ts +8 -0
  93. package/dist/draft13-session.js +6 -0
  94. package/dist/draft13.cjs +119 -0
  95. package/dist/draft13.d.cts +108 -0
  96. package/dist/draft13.d.ts +108 -0
  97. package/dist/draft13.js +119 -0
  98. package/dist/draft14-session.cjs +2 -2
  99. package/dist/draft14-session.d.cts +3 -3
  100. package/dist/draft14-session.d.ts +3 -3
  101. package/dist/draft14-session.js +1 -1
  102. package/dist/draft14.cjs +3 -3
  103. package/dist/draft14.d.cts +16 -4
  104. package/dist/draft14.d.ts +16 -4
  105. package/dist/draft14.js +2 -2
  106. package/dist/draft15-session.cjs +6 -0
  107. package/dist/draft15-session.d.cts +8 -0
  108. package/dist/draft15-session.d.ts +8 -0
  109. package/dist/draft15-session.js +6 -0
  110. package/dist/draft15.cjs +111 -0
  111. package/dist/draft15.d.cts +93 -0
  112. package/dist/draft15.d.ts +93 -0
  113. package/dist/draft15.js +111 -0
  114. package/dist/draft16-session.cjs +6 -0
  115. package/dist/draft16-session.d.cts +8 -0
  116. package/dist/draft16-session.d.ts +8 -0
  117. package/dist/draft16-session.js +6 -0
  118. package/dist/draft16.cjs +113 -0
  119. package/dist/draft16.d.cts +94 -0
  120. package/dist/draft16.d.ts +94 -0
  121. package/dist/draft16.js +113 -0
  122. package/dist/draft17-session.cjs +8 -0
  123. package/dist/draft17-session.d.cts +51 -0
  124. package/dist/draft17-session.d.ts +51 -0
  125. package/dist/draft17-session.js +8 -0
  126. package/dist/draft17.cjs +99 -0
  127. package/dist/draft17.d.cts +40 -0
  128. package/dist/draft17.d.ts +40 -0
  129. package/dist/draft17.js +99 -0
  130. package/dist/draft7-session.cjs +3 -3
  131. package/dist/draft7-session.d.cts +3 -3
  132. package/dist/draft7-session.d.ts +3 -3
  133. package/dist/draft7-session.js +2 -2
  134. package/dist/draft7.cjs +4 -4
  135. package/dist/draft7.d.cts +2 -2
  136. package/dist/draft7.d.ts +2 -2
  137. package/dist/draft7.js +2 -2
  138. package/dist/draft8-session.cjs +6 -0
  139. package/dist/draft8-session.d.cts +8 -0
  140. package/dist/draft8-session.d.ts +8 -0
  141. package/dist/draft8-session.js +6 -0
  142. package/dist/draft8.cjs +115 -0
  143. package/dist/draft8.d.cts +95 -0
  144. package/dist/draft8.d.ts +95 -0
  145. package/dist/draft8.js +115 -0
  146. package/dist/draft9-session.cjs +6 -0
  147. package/dist/draft9-session.d.cts +8 -0
  148. package/dist/draft9-session.d.ts +8 -0
  149. package/dist/draft9-session.js +6 -0
  150. package/dist/draft9.cjs +115 -0
  151. package/dist/draft9.d.cts +95 -0
  152. package/dist/draft9.d.ts +95 -0
  153. package/dist/draft9.js +115 -0
  154. package/dist/index.cjs +76 -4
  155. package/dist/index.d.cts +68 -5
  156. package/dist/index.d.ts +68 -5
  157. package/dist/index.js +76 -4
  158. package/dist/{session-types-DFjMk4HH.d.ts → session-types-CJIFbTPd.d.ts} +1 -1
  159. package/dist/{session-types-DW1RSZX_.d.cts → session-types-Cbq8IGCP.d.cts} +1 -1
  160. package/dist/session.cjs +5 -5
  161. package/dist/session.d.cts +3 -3
  162. package/dist/session.d.ts +3 -3
  163. package/dist/session.js +5 -5
  164. package/dist/types-4VxSL2Ho.d.cts +261 -0
  165. package/dist/types-4VxSL2Ho.d.ts +261 -0
  166. package/dist/types-B2afJZM-.d.cts +236 -0
  167. package/dist/types-B2afJZM-.d.ts +236 -0
  168. package/dist/types-Bg6QYNVt.d.cts +290 -0
  169. package/dist/types-Bg6QYNVt.d.ts +290 -0
  170. package/dist/types-C_1HrqBl.d.cts +306 -0
  171. package/dist/types-C_1HrqBl.d.ts +306 -0
  172. package/dist/types-Cw4WE9dh.d.cts +261 -0
  173. package/dist/types-Cw4WE9dh.d.ts +261 -0
  174. package/dist/types-D5gNQiDj.d.cts +261 -0
  175. package/dist/types-D5gNQiDj.d.ts +261 -0
  176. package/dist/types-DqCDFqgB.d.cts +230 -0
  177. package/dist/types-DqCDFqgB.d.ts +230 -0
  178. package/dist/types-ERexTpT8.d.cts +217 -0
  179. package/dist/types-ERexTpT8.d.ts +217 -0
  180. package/dist/{types-DPYE49t0.d.cts → types-QNXoxC9Y.d.cts} +2 -7
  181. package/dist/{types-DPYE49t0.d.ts → types-QNXoxC9Y.d.ts} +2 -7
  182. package/dist/types-r-CasCf1.d.cts +262 -0
  183. package/dist/types-r-CasCf1.d.ts +262 -0
  184. package/package.json +110 -2
  185. package/src/core/types.ts +24 -7
  186. package/src/drafts/draft07/codec.ts +49 -41
  187. package/src/drafts/draft07/rules.ts +1 -3
  188. package/src/drafts/draft08/codec.ts +1254 -0
  189. package/src/drafts/draft08/index.ts +125 -0
  190. package/src/drafts/draft08/messages.ts +72 -0
  191. package/src/drafts/draft08/rules.ts +91 -0
  192. package/src/drafts/draft08/session-fsm.ts +718 -0
  193. package/src/drafts/draft08/session.ts +26 -0
  194. package/src/drafts/draft08/types.ts +377 -0
  195. package/src/drafts/draft09/codec.ts +1235 -0
  196. package/src/drafts/draft09/index.ts +125 -0
  197. package/src/drafts/draft09/messages.ts +72 -0
  198. package/src/drafts/draft09/rules.ts +91 -0
  199. package/src/drafts/draft09/session-fsm.ts +718 -0
  200. package/src/drafts/draft09/session.ts +26 -0
  201. package/src/drafts/draft09/types.ts +376 -0
  202. package/src/drafts/draft10/codec.ts +1235 -0
  203. package/src/drafts/draft10/index.ts +125 -0
  204. package/src/drafts/draft10/messages.ts +72 -0
  205. package/src/drafts/draft10/rules.ts +91 -0
  206. package/src/drafts/draft10/session-fsm.ts +718 -0
  207. package/src/drafts/draft10/session.ts +26 -0
  208. package/src/drafts/draft10/types.ts +376 -0
  209. package/src/drafts/draft11/codec.ts +1198 -0
  210. package/src/drafts/draft11/index.ts +123 -0
  211. package/src/drafts/draft11/messages.ts +71 -0
  212. package/src/drafts/draft11/rules.ts +100 -0
  213. package/src/drafts/draft11/session-fsm.ts +758 -0
  214. package/src/drafts/draft11/session.ts +26 -0
  215. package/src/drafts/draft11/types.ts +375 -0
  216. package/src/drafts/draft12/codec.ts +1354 -0
  217. package/src/drafts/draft12/index.ts +130 -0
  218. package/src/drafts/draft12/messages.ts +84 -0
  219. package/src/drafts/draft12/rules.ts +106 -0
  220. package/src/drafts/draft12/session-fsm.ts +805 -0
  221. package/src/drafts/draft12/session.ts +26 -0
  222. package/src/drafts/draft12/types.ts +414 -0
  223. package/src/drafts/draft13/codec.ts +1438 -0
  224. package/src/drafts/draft13/index.ts +132 -0
  225. package/src/drafts/draft13/messages.ts +86 -0
  226. package/src/drafts/draft13/rules.ts +108 -0
  227. package/src/drafts/draft13/session-fsm.ts +819 -0
  228. package/src/drafts/draft13/session.ts +26 -0
  229. package/src/drafts/draft13/types.ts +433 -0
  230. package/src/drafts/draft14/session-fsm.ts +182 -1
  231. package/src/drafts/draft15/codec.ts +1661 -0
  232. package/src/drafts/draft15/index.ts +121 -0
  233. package/src/drafts/draft15/messages.ts +64 -0
  234. package/src/drafts/draft15/rules.ts +95 -0
  235. package/src/drafts/draft15/session-fsm.ts +687 -0
  236. package/src/drafts/draft15/session.ts +26 -0
  237. package/src/drafts/draft15/types.ts +336 -0
  238. package/src/drafts/draft16/codec.ts +1623 -0
  239. package/src/drafts/draft16/index.ts +123 -0
  240. package/src/drafts/draft16/messages.ts +67 -0
  241. package/src/drafts/draft16/rules.ts +96 -0
  242. package/src/drafts/draft16/session-fsm.ts +682 -0
  243. package/src/drafts/draft16/session.ts +26 -0
  244. package/src/drafts/draft16/types.ts +354 -0
  245. package/src/drafts/draft17/codec.ts +1621 -0
  246. package/src/drafts/draft17/index.ts +105 -0
  247. package/src/drafts/draft17/messages.ts +53 -0
  248. package/src/drafts/draft17/rules.ts +85 -0
  249. package/src/drafts/draft17/session-fsm.ts +437 -0
  250. package/src/drafts/draft17/session.ts +15 -0
  251. package/src/drafts/draft17/types.ts +310 -0
  252. package/src/index.ts +217 -2
@@ -0,0 +1,1661 @@
1
+ import { BufferReader } from "../../core/buffer-reader.js";
2
+ import { BufferWriter } from "../../core/buffer-writer.js";
3
+ import type { BaseCodec, DecodeResult } from "../../core/types.js";
4
+ import { DecodeError } from "../../core/types.js";
5
+ import {
6
+ MESSAGE_ID_MAP,
7
+ MSG_CLIENT_SETUP,
8
+ MSG_FETCH,
9
+ MSG_FETCH_CANCEL,
10
+ MSG_FETCH_OK,
11
+ MSG_GOAWAY,
12
+ MSG_MAX_REQUEST_ID,
13
+ MSG_PUBLISH,
14
+ MSG_PUBLISH_DONE,
15
+ MSG_PUBLISH_NAMESPACE,
16
+ MSG_PUBLISH_NAMESPACE_CANCEL,
17
+ MSG_PUBLISH_NAMESPACE_DONE,
18
+ MSG_PUBLISH_OK,
19
+ MSG_REQUEST_ERROR,
20
+ MSG_REQUEST_OK,
21
+ MSG_REQUESTS_BLOCKED,
22
+ MSG_SERVER_SETUP,
23
+ MSG_SUBSCRIBE,
24
+ MSG_SUBSCRIBE_NAMESPACE,
25
+ MSG_SUBSCRIBE_OK,
26
+ MSG_SUBSCRIBE_UPDATE,
27
+ MSG_TRACK_STATUS,
28
+ MSG_UNSUBSCRIBE,
29
+ MSG_UNSUBSCRIBE_NAMESPACE,
30
+ SETUP_PARAM_AUTHORITY,
31
+ SETUP_PARAM_MAX_AUTH_TOKEN_CACHE_SIZE,
32
+ SETUP_PARAM_MAX_REQUEST_ID,
33
+ SETUP_PARAM_MOQT_IMPLEMENTATION,
34
+ SETUP_PARAM_PATH,
35
+ } from "./messages.js";
36
+ import type {
37
+ DatagramObject,
38
+ DataStreamEvent,
39
+ Draft15DataStream,
40
+ Draft15Fetch,
41
+ Draft15Message,
42
+ Draft15Params,
43
+ Draft15SetupParams,
44
+ FetchObjectPayload,
45
+ FetchStream,
46
+ FetchStreamHeader,
47
+ JoiningFetch,
48
+ ObjectPayload,
49
+ StandaloneFetch,
50
+ SubgroupStream,
51
+ SubgroupStreamHeader,
52
+ UnknownParam,
53
+ } from "./types.js";
54
+
55
+ // ─── Helpers ───────────────────────────────────────────────────────────────────
56
+
57
+ function bytesToHex(bytes: Uint8Array): string {
58
+ let hex = "";
59
+ for (let i = 0; i < bytes.byteLength; i++) {
60
+ hex += (bytes[i] as number).toString(16).padStart(2, "0");
61
+ }
62
+ return hex;
63
+ }
64
+
65
+ function hexToBytes(hex: string): Uint8Array {
66
+ const bytes = new Uint8Array(hex.length / 2);
67
+ for (let i = 0; i < hex.length; i += 2) {
68
+ bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
69
+ }
70
+ return bytes;
71
+ }
72
+
73
+ // ─── Setup Parameter Encoding/Decoding ──────────────────────────────────────────
74
+
75
+ function encodeSetupParams(params: Draft15SetupParams, writer: BufferWriter): void {
76
+ let count = 0;
77
+ if (params.path !== undefined) count++;
78
+ if (params.max_request_id !== undefined) count++;
79
+ if (params.max_auth_token_cache_size !== undefined) count++;
80
+ if (params.authority !== undefined) count++;
81
+ if (params.moqt_implementation !== undefined) count++;
82
+ if (params.unknown) count += params.unknown.length;
83
+
84
+ writer.writeVarInt(count);
85
+
86
+ // PATH (0x01) - odd, length-prefixed bytes
87
+ if (params.path !== undefined) {
88
+ writer.writeVarInt(SETUP_PARAM_PATH);
89
+ const encoded = new TextEncoder().encode(params.path);
90
+ writer.writeVarInt(encoded.byteLength);
91
+ writer.writeBytes(encoded);
92
+ }
93
+
94
+ // MAX_REQUEST_ID (0x02) - even, varint value
95
+ if (params.max_request_id !== undefined) {
96
+ writer.writeVarInt(SETUP_PARAM_MAX_REQUEST_ID);
97
+ writer.writeVarInt(params.max_request_id);
98
+ }
99
+
100
+ // MAX_AUTH_TOKEN_CACHE_SIZE (0x04) - even, varint value
101
+ if (params.max_auth_token_cache_size !== undefined) {
102
+ writer.writeVarInt(SETUP_PARAM_MAX_AUTH_TOKEN_CACHE_SIZE);
103
+ writer.writeVarInt(params.max_auth_token_cache_size);
104
+ }
105
+
106
+ // AUTHORITY (0x05) - odd, length-prefixed bytes
107
+ if (params.authority !== undefined) {
108
+ writer.writeVarInt(SETUP_PARAM_AUTHORITY);
109
+ const encoded = new TextEncoder().encode(params.authority);
110
+ writer.writeVarInt(encoded.byteLength);
111
+ writer.writeBytes(encoded);
112
+ }
113
+
114
+ // MOQT_IMPLEMENTATION (0x07) - odd, length-prefixed bytes
115
+ if (params.moqt_implementation !== undefined) {
116
+ writer.writeVarInt(SETUP_PARAM_MOQT_IMPLEMENTATION);
117
+ const encoded = new TextEncoder().encode(params.moqt_implementation);
118
+ writer.writeVarInt(encoded.byteLength);
119
+ writer.writeBytes(encoded);
120
+ }
121
+
122
+ // Unknown params
123
+ if (params.unknown) {
124
+ for (const u of params.unknown) {
125
+ const id = BigInt(u.id);
126
+ writer.writeVarInt(id);
127
+ if (id % 2n === 0n) {
128
+ // Even: value is a varint — raw_hex contains the varint bytes
129
+ const raw = hexToBytes(u.raw_hex);
130
+ const tmpReader = new BufferReader(raw);
131
+ const value = tmpReader.readVarInt();
132
+ writer.writeVarInt(value);
133
+ } else {
134
+ // Odd: length-prefixed bytes
135
+ const raw = hexToBytes(u.raw_hex);
136
+ writer.writeVarInt(raw.byteLength);
137
+ writer.writeBytes(raw);
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ function decodeSetupParams(reader: BufferReader): Draft15SetupParams {
144
+ const count = Number(reader.readVarInt());
145
+ const result: Draft15SetupParams = {};
146
+ const unknown: UnknownParam[] = [];
147
+
148
+ for (let i = 0; i < count; i++) {
149
+ const paramType = reader.readVarInt();
150
+
151
+ if (paramType % 2n === 0n) {
152
+ // Even: value is a varint directly
153
+ const value = reader.readVarInt();
154
+ if (paramType === SETUP_PARAM_MAX_REQUEST_ID) {
155
+ result.max_request_id = value;
156
+ } else if (paramType === SETUP_PARAM_MAX_AUTH_TOKEN_CACHE_SIZE) {
157
+ result.max_auth_token_cache_size = value;
158
+ } else {
159
+ const tmpWriter = new BufferWriter(16);
160
+ tmpWriter.writeVarInt(value);
161
+ const raw = tmpWriter.finish();
162
+ unknown.push({
163
+ id: `0x${paramType.toString(16)}`,
164
+ length: raw.byteLength,
165
+ raw_hex: bytesToHex(raw),
166
+ });
167
+ }
168
+ } else {
169
+ // Odd: value is length-prefixed bytes
170
+ const length = Number(reader.readVarInt());
171
+ const bytes = reader.readBytes(length);
172
+ if (paramType === SETUP_PARAM_PATH) {
173
+ result.path = new TextDecoder().decode(bytes);
174
+ } else if (paramType === SETUP_PARAM_AUTHORITY) {
175
+ result.authority = new TextDecoder().decode(bytes);
176
+ } else if (paramType === SETUP_PARAM_MOQT_IMPLEMENTATION) {
177
+ result.moqt_implementation = new TextDecoder().decode(bytes);
178
+ } else {
179
+ unknown.push({
180
+ id: `0x${paramType.toString(16)}`,
181
+ length,
182
+ raw_hex: bytesToHex(bytes),
183
+ });
184
+ }
185
+ }
186
+ }
187
+
188
+ if (unknown.length > 0) {
189
+ result.unknown = unknown;
190
+ }
191
+
192
+ return result;
193
+ }
194
+
195
+ // ─── Version-Specific Parameter Encoding/Decoding ───────────────────────────────
196
+
197
+ // Well-known version-specific parameter IDs
198
+ const PARAM_EXPIRES = 0x08n; // even: varint
199
+ const PARAM_LARGEST_OBJECT = 0x09n; // odd: length-prefixed (group varint + object varint)
200
+ const PARAM_SUBSCRIBER_PRIORITY = 0x20n; // even: varint
201
+ const PARAM_SUBSCRIPTION_FILTER = 0x21n; // odd: length-prefixed
202
+ const PARAM_GROUP_ORDER = 0x22n; // even: varint
203
+
204
+ function encodeParams(params: Draft15Params, writer: BufferWriter): void {
205
+ // Count known + unknown params
206
+ let count = params.unknown ? params.unknown.length : 0;
207
+ if (params.expires !== undefined) count++;
208
+ if (params.largest_object !== undefined) count++;
209
+ if (params.subscriber_priority !== undefined) count++;
210
+ if (params.subscription_filter !== undefined) count++;
211
+ if (params.group_order !== undefined) count++;
212
+ writer.writeVarInt(count);
213
+
214
+ // Encode known params (sorted by ID for canonical encoding)
215
+ if (params.expires !== undefined) {
216
+ writer.writeVarInt(PARAM_EXPIRES);
217
+ writer.writeVarInt(params.expires);
218
+ }
219
+ if (params.largest_object !== undefined) {
220
+ writer.writeVarInt(PARAM_LARGEST_OBJECT);
221
+ const tmpW = new BufferWriter(16);
222
+ tmpW.writeVarInt(params.largest_object.group);
223
+ tmpW.writeVarInt(params.largest_object.object);
224
+ const raw = tmpW.finish();
225
+ writer.writeVarInt(raw.byteLength);
226
+ writer.writeBytes(raw);
227
+ }
228
+ if (params.subscriber_priority !== undefined) {
229
+ writer.writeVarInt(PARAM_SUBSCRIBER_PRIORITY);
230
+ writer.writeVarInt(params.subscriber_priority);
231
+ }
232
+ if (params.subscription_filter !== undefined) {
233
+ writer.writeVarInt(PARAM_SUBSCRIPTION_FILTER);
234
+ const tmpW = new BufferWriter(32);
235
+ const f = params.subscription_filter;
236
+ tmpW.writeVarInt(f.filter_type);
237
+ if (f.filter_type === 3n || f.filter_type === 4n) {
238
+ tmpW.writeVarInt(f.start_group!);
239
+ tmpW.writeVarInt(f.start_object!);
240
+ }
241
+ if (f.filter_type === 4n) {
242
+ tmpW.writeVarInt(f.end_group!);
243
+ }
244
+ const raw = tmpW.finish();
245
+ writer.writeVarInt(raw.byteLength);
246
+ writer.writeBytes(raw);
247
+ }
248
+ if (params.group_order !== undefined) {
249
+ writer.writeVarInt(PARAM_GROUP_ORDER);
250
+ writer.writeVarInt(params.group_order);
251
+ }
252
+
253
+ // Encode unknown params
254
+ if (params.unknown) {
255
+ for (const u of params.unknown) {
256
+ const id = BigInt(u.id);
257
+ writer.writeVarInt(id);
258
+ if (id % 2n === 0n) {
259
+ const raw = hexToBytes(u.raw_hex);
260
+ const tmpReader = new BufferReader(raw);
261
+ const value = tmpReader.readVarInt();
262
+ writer.writeVarInt(value);
263
+ } else {
264
+ const raw = hexToBytes(u.raw_hex);
265
+ writer.writeVarInt(raw.byteLength);
266
+ writer.writeBytes(raw);
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ function decodeParams(reader: BufferReader): Draft15Params {
273
+ const count = Number(reader.readVarInt());
274
+ const result: Draft15Params = {};
275
+ const unknown: UnknownParam[] = [];
276
+
277
+ for (let i = 0; i < count; i++) {
278
+ const paramType = reader.readVarInt();
279
+
280
+ if (paramType === PARAM_EXPIRES) {
281
+ result.expires = reader.readVarInt();
282
+ } else if (paramType === PARAM_SUBSCRIBER_PRIORITY) {
283
+ result.subscriber_priority = reader.readVarInt();
284
+ } else if (paramType === PARAM_GROUP_ORDER) {
285
+ result.group_order = reader.readVarInt();
286
+ } else if (paramType === PARAM_LARGEST_OBJECT) {
287
+ const length = Number(reader.readVarInt());
288
+ const startOff = reader.offset;
289
+ const group = reader.readVarInt();
290
+ const object = reader.readVarInt();
291
+ // Skip any remaining bytes in the length-prefixed block
292
+ const consumed = reader.offset - startOff;
293
+ if (consumed < length) reader.readBytes(length - consumed);
294
+ result.largest_object = { group, object };
295
+ } else if (paramType === PARAM_SUBSCRIPTION_FILTER) {
296
+ const length = Number(reader.readVarInt());
297
+ const startOff = reader.offset;
298
+ const filter_type = reader.readVarInt();
299
+ const filter: {
300
+ filter_type: bigint;
301
+ start_group?: bigint;
302
+ start_object?: bigint;
303
+ end_group?: bigint;
304
+ } = { filter_type };
305
+ if (filter_type === 3n || filter_type === 4n) {
306
+ filter.start_group = reader.readVarInt();
307
+ filter.start_object = reader.readVarInt();
308
+ }
309
+ if (filter_type === 4n) {
310
+ filter.end_group = reader.readVarInt();
311
+ }
312
+ // Skip any remaining bytes
313
+ const consumed = reader.offset - startOff;
314
+ if (consumed < length) reader.readBytes(length - consumed);
315
+ result.subscription_filter = filter;
316
+ } else if (paramType % 2n === 0n) {
317
+ // Unknown even: varint value
318
+ const value = reader.readVarInt();
319
+ const tmpWriter = new BufferWriter(16);
320
+ tmpWriter.writeVarInt(value);
321
+ const raw = tmpWriter.finish();
322
+ unknown.push({
323
+ id: `0x${paramType.toString(16)}`,
324
+ length: raw.byteLength,
325
+ raw_hex: bytesToHex(raw),
326
+ });
327
+ } else {
328
+ // Unknown odd: length-prefixed bytes
329
+ const length = Number(reader.readVarInt());
330
+ const bytes = reader.readBytes(length);
331
+ unknown.push({
332
+ id: `0x${paramType.toString(16)}`,
333
+ length,
334
+ raw_hex: bytesToHex(bytes),
335
+ });
336
+ }
337
+ }
338
+
339
+ if (unknown.length > 0) {
340
+ result.unknown = unknown;
341
+ }
342
+
343
+ return result;
344
+ }
345
+
346
+ // ─── Payload Encoders ──────────────────────────────────────────────────────────
347
+
348
+ function encodeClientSetupPayload(
349
+ msg: Draft15Message & { type: "client_setup" },
350
+ w: BufferWriter,
351
+ ): void {
352
+ encodeSetupParams(msg.parameters, w);
353
+ }
354
+
355
+ function encodeServerSetupPayload(
356
+ msg: Draft15Message & { type: "server_setup" },
357
+ w: BufferWriter,
358
+ ): void {
359
+ encodeSetupParams(msg.parameters, w);
360
+ }
361
+
362
+ function encodeSubscribePayload(
363
+ msg: Draft15Message & { type: "subscribe" },
364
+ w: BufferWriter,
365
+ ): void {
366
+ w.writeVarInt(msg.request_id);
367
+ w.writeTuple(msg.track_namespace);
368
+ w.writeString(msg.track_name);
369
+ encodeParams(msg.parameters, w);
370
+ }
371
+
372
+ function encodeSubscribeOkPayload(
373
+ msg: Draft15Message & { type: "subscribe_ok" },
374
+ w: BufferWriter,
375
+ ): void {
376
+ w.writeVarInt(msg.request_id);
377
+ w.writeVarInt(msg.track_alias);
378
+ encodeParams(msg.parameters, w);
379
+ }
380
+
381
+ function encodeSubscribeUpdatePayload(
382
+ msg: Draft15Message & { type: "subscribe_update" },
383
+ w: BufferWriter,
384
+ ): void {
385
+ w.writeVarInt(msg.request_id);
386
+ w.writeVarInt(msg.subscription_request_id);
387
+ encodeParams(msg.parameters, w);
388
+ }
389
+
390
+ function encodeUnsubscribePayload(
391
+ msg: Draft15Message & { type: "unsubscribe" },
392
+ w: BufferWriter,
393
+ ): void {
394
+ w.writeVarInt(msg.request_id);
395
+ }
396
+
397
+ function encodePublishPayload(msg: Draft15Message & { type: "publish" }, w: BufferWriter): void {
398
+ w.writeVarInt(msg.request_id);
399
+ w.writeTuple(msg.track_namespace);
400
+ w.writeString(msg.track_name);
401
+ w.writeVarInt(msg.track_alias);
402
+ encodeParams(msg.parameters, w);
403
+ }
404
+
405
+ function encodePublishOkPayload(
406
+ msg: Draft15Message & { type: "publish_ok" },
407
+ w: BufferWriter,
408
+ ): void {
409
+ w.writeVarInt(msg.request_id);
410
+ encodeParams(msg.parameters, w);
411
+ }
412
+
413
+ function encodePublishDonePayload(
414
+ msg: Draft15Message & { type: "publish_done" },
415
+ w: BufferWriter,
416
+ ): void {
417
+ w.writeVarInt(msg.request_id);
418
+ w.writeVarInt(msg.status_code);
419
+ w.writeVarInt(msg.stream_count);
420
+ w.writeString(msg.reason_phrase);
421
+ }
422
+
423
+ function encodePublishNamespacePayload(
424
+ msg: Draft15Message & { type: "publish_namespace" },
425
+ w: BufferWriter,
426
+ ): void {
427
+ w.writeVarInt(msg.request_id);
428
+ w.writeTuple(msg.track_namespace);
429
+ encodeParams(msg.parameters, w);
430
+ }
431
+
432
+ function encodePublishNamespaceDonePayload(
433
+ msg: Draft15Message & { type: "publish_namespace_done" },
434
+ w: BufferWriter,
435
+ ): void {
436
+ w.writeTuple(msg.track_namespace);
437
+ }
438
+
439
+ function encodePublishNamespaceCancelPayload(
440
+ msg: Draft15Message & { type: "publish_namespace_cancel" },
441
+ w: BufferWriter,
442
+ ): void {
443
+ w.writeTuple(msg.track_namespace);
444
+ w.writeVarInt(msg.error_code);
445
+ w.writeString(msg.reason_phrase);
446
+ }
447
+
448
+ function encodeSubscribeNamespacePayload(
449
+ msg: Draft15Message & { type: "subscribe_namespace" },
450
+ w: BufferWriter,
451
+ ): void {
452
+ w.writeVarInt(msg.request_id);
453
+ w.writeTuple(msg.namespace_prefix);
454
+ encodeParams(msg.parameters, w);
455
+ }
456
+
457
+ function encodeUnsubscribeNamespacePayload(
458
+ msg: Draft15Message & { type: "unsubscribe_namespace" },
459
+ w: BufferWriter,
460
+ ): void {
461
+ w.writeVarInt(msg.request_id);
462
+ }
463
+
464
+ function encodeFetchPayload(msg: Draft15Message & { type: "fetch" }, w: BufferWriter): void {
465
+ w.writeVarInt(msg.request_id);
466
+ w.writeVarInt(msg.fetch_type);
467
+ const ft = Number(msg.fetch_type);
468
+ if (ft === 1 && msg.standalone) {
469
+ // Standalone Fetch
470
+ w.writeTuple(msg.standalone.track_namespace);
471
+ w.writeString(msg.standalone.track_name);
472
+ // Start Location
473
+ w.writeVarInt(msg.standalone.start_group);
474
+ w.writeVarInt(msg.standalone.start_object);
475
+ // End Location
476
+ w.writeVarInt(msg.standalone.end_group);
477
+ w.writeVarInt(msg.standalone.end_object);
478
+ } else if ((ft === 2 || ft === 3) && msg.joining) {
479
+ // Joining Fetch (relative=2, absolute=3)
480
+ w.writeVarInt(msg.joining.joining_request_id);
481
+ w.writeVarInt(msg.joining.joining_start);
482
+ }
483
+ encodeParams(msg.parameters, w);
484
+ }
485
+
486
+ function encodeFetchOkPayload(msg: Draft15Message & { type: "fetch_ok" }, w: BufferWriter): void {
487
+ w.writeVarInt(msg.request_id);
488
+ w.writeUint8(msg.end_of_track);
489
+ // End Location
490
+ w.writeVarInt(msg.end_group);
491
+ w.writeVarInt(msg.end_object);
492
+ encodeParams(msg.parameters, w);
493
+ }
494
+
495
+ function encodeFetchCancelPayload(
496
+ msg: Draft15Message & { type: "fetch_cancel" },
497
+ w: BufferWriter,
498
+ ): void {
499
+ w.writeVarInt(msg.request_id);
500
+ }
501
+
502
+ function encodeTrackStatusPayload(
503
+ msg: Draft15Message & { type: "track_status" },
504
+ w: BufferWriter,
505
+ ): void {
506
+ w.writeVarInt(msg.request_id);
507
+ w.writeTuple(msg.track_namespace);
508
+ w.writeString(msg.track_name);
509
+ encodeParams(msg.parameters, w);
510
+ }
511
+
512
+ function encodeRequestOkPayload(
513
+ msg: Draft15Message & { type: "request_ok" },
514
+ w: BufferWriter,
515
+ ): void {
516
+ w.writeVarInt(msg.request_id);
517
+ encodeParams(msg.parameters, w);
518
+ }
519
+
520
+ function encodeRequestErrorPayload(
521
+ msg: Draft15Message & { type: "request_error" },
522
+ w: BufferWriter,
523
+ ): void {
524
+ w.writeVarInt(msg.request_id);
525
+ w.writeVarInt(msg.error_code);
526
+ w.writeString(msg.reason_phrase);
527
+ }
528
+
529
+ function encodeGoAwayPayload(msg: Draft15Message & { type: "goaway" }, w: BufferWriter): void {
530
+ w.writeString(msg.new_session_uri);
531
+ }
532
+
533
+ function encodeMaxRequestIdPayload(
534
+ msg: Draft15Message & { type: "max_request_id" },
535
+ w: BufferWriter,
536
+ ): void {
537
+ w.writeVarInt(msg.max_request_id);
538
+ }
539
+
540
+ function encodeRequestsBlockedPayload(
541
+ msg: Draft15Message & { type: "requests_blocked" },
542
+ w: BufferWriter,
543
+ ): void {
544
+ w.writeVarInt(msg.maximum_request_id);
545
+ }
546
+
547
+ // ─── Payload Decoders ──────────────────────────────────────────────────────────
548
+
549
+ function decodeClientSetupPayload(r: BufferReader): Draft15Message {
550
+ const parameters = decodeSetupParams(r);
551
+ return { type: "client_setup", parameters };
552
+ }
553
+
554
+ function decodeServerSetupPayload(r: BufferReader): Draft15Message {
555
+ const parameters = decodeSetupParams(r);
556
+ return { type: "server_setup", parameters };
557
+ }
558
+
559
+ function decodeSubscribePayload(r: BufferReader): Draft15Message {
560
+ const request_id = r.readVarInt();
561
+ const track_namespace = r.readTuple();
562
+ const track_name = r.readString();
563
+ const parameters = decodeParams(r);
564
+ return { type: "subscribe", request_id, track_namespace, track_name, parameters };
565
+ }
566
+
567
+ function decodeSubscribeOkPayload(r: BufferReader): Draft15Message {
568
+ const request_id = r.readVarInt();
569
+ const track_alias = r.readVarInt();
570
+ const parameters = decodeParams(r);
571
+ return { type: "subscribe_ok", request_id, track_alias, parameters };
572
+ }
573
+
574
+ function decodeSubscribeUpdatePayload(r: BufferReader): Draft15Message {
575
+ const request_id = r.readVarInt();
576
+ const subscription_request_id = r.readVarInt();
577
+ const parameters = decodeParams(r);
578
+ return { type: "subscribe_update", request_id, subscription_request_id, parameters };
579
+ }
580
+
581
+ function decodeUnsubscribePayload(r: BufferReader): Draft15Message {
582
+ const request_id = r.readVarInt();
583
+ return { type: "unsubscribe", request_id };
584
+ }
585
+
586
+ function decodePublishPayload(r: BufferReader): Draft15Message {
587
+ const request_id = r.readVarInt();
588
+ const track_namespace = r.readTuple();
589
+ const track_name = r.readString();
590
+ const track_alias = r.readVarInt();
591
+ const parameters = decodeParams(r);
592
+ return { type: "publish", request_id, track_namespace, track_name, track_alias, parameters };
593
+ }
594
+
595
+ function decodePublishOkPayload(r: BufferReader): Draft15Message {
596
+ const request_id = r.readVarInt();
597
+ const parameters = decodeParams(r);
598
+ return { type: "publish_ok", request_id, parameters };
599
+ }
600
+
601
+ function decodePublishDonePayload(r: BufferReader): Draft15Message {
602
+ const request_id = r.readVarInt();
603
+ const status_code = r.readVarInt();
604
+ const stream_count = r.readVarInt();
605
+ const reason_phrase = r.readString();
606
+ return { type: "publish_done", request_id, status_code, stream_count, reason_phrase };
607
+ }
608
+
609
+ function decodePublishNamespacePayload(r: BufferReader): Draft15Message {
610
+ const request_id = r.readVarInt();
611
+ const track_namespace = r.readTuple();
612
+ const parameters = decodeParams(r);
613
+ return { type: "publish_namespace", request_id, track_namespace, parameters };
614
+ }
615
+
616
+ function decodePublishNamespaceDonePayload(r: BufferReader): Draft15Message {
617
+ const track_namespace = r.readTuple();
618
+ return { type: "publish_namespace_done", track_namespace };
619
+ }
620
+
621
+ function decodePublishNamespaceCancelPayload(r: BufferReader): Draft15Message {
622
+ const track_namespace = r.readTuple();
623
+ const error_code = r.readVarInt();
624
+ const reason_phrase = r.readString();
625
+ return { type: "publish_namespace_cancel", track_namespace, error_code, reason_phrase };
626
+ }
627
+
628
+ function decodeSubscribeNamespacePayload(r: BufferReader): Draft15Message {
629
+ const request_id = r.readVarInt();
630
+ const namespace_prefix = r.readTuple();
631
+ const parameters = decodeParams(r);
632
+ return { type: "subscribe_namespace", request_id, namespace_prefix, parameters };
633
+ }
634
+
635
+ function decodeUnsubscribeNamespacePayload(r: BufferReader): Draft15Message {
636
+ const request_id = r.readVarInt();
637
+ return { type: "unsubscribe_namespace", request_id };
638
+ }
639
+
640
+ function decodeFetchPayload(r: BufferReader): Draft15Message {
641
+ const request_id = r.readVarInt();
642
+ const fetch_type = r.readVarInt();
643
+ const ft = Number(fetch_type);
644
+
645
+ if (ft < 1 || ft > 3) {
646
+ throw new DecodeError("CONSTRAINT_VIOLATION", `Invalid fetch_type: ${ft}`, r.offset);
647
+ }
648
+
649
+ let standalone: StandaloneFetch | undefined;
650
+ let joining: JoiningFetch | undefined;
651
+
652
+ if (ft === 1) {
653
+ // Standalone Fetch
654
+ const track_namespace = r.readTuple();
655
+ const track_name = r.readString();
656
+ const start_group = r.readVarInt();
657
+ const start_object = r.readVarInt();
658
+ const end_group = r.readVarInt();
659
+ const end_object = r.readVarInt();
660
+ standalone = { track_namespace, track_name, start_group, start_object, end_group, end_object };
661
+ } else {
662
+ // Joining Fetch (relative=2, absolute=3)
663
+ const joining_request_id = r.readVarInt();
664
+ const joining_start = r.readVarInt();
665
+ joining = { joining_request_id, joining_start };
666
+ }
667
+
668
+ const parameters = decodeParams(r);
669
+
670
+ return {
671
+ type: "fetch",
672
+ request_id,
673
+ fetch_type,
674
+ standalone,
675
+ joining,
676
+ parameters,
677
+ } as Draft15Fetch;
678
+ }
679
+
680
+ function decodeFetchOkPayload(r: BufferReader): Draft15Message {
681
+ const request_id = r.readVarInt();
682
+ const end_of_track = r.readUint8();
683
+ const end_group = r.readVarInt();
684
+ const end_object = r.readVarInt();
685
+ const parameters = decodeParams(r);
686
+ return { type: "fetch_ok", request_id, end_of_track, end_group, end_object, parameters };
687
+ }
688
+
689
+ function decodeFetchCancelPayload(r: BufferReader): Draft15Message {
690
+ const request_id = r.readVarInt();
691
+ return { type: "fetch_cancel", request_id };
692
+ }
693
+
694
+ function decodeTrackStatusPayload(r: BufferReader): Draft15Message {
695
+ const request_id = r.readVarInt();
696
+ const track_namespace = r.readTuple();
697
+ const track_name = r.readString();
698
+ const parameters = decodeParams(r);
699
+ return { type: "track_status", request_id, track_namespace, track_name, parameters };
700
+ }
701
+
702
+ function decodeRequestOkPayload(r: BufferReader): Draft15Message {
703
+ const request_id = r.readVarInt();
704
+ const parameters = decodeParams(r);
705
+ return { type: "request_ok", request_id, parameters };
706
+ }
707
+
708
+ function decodeRequestErrorPayload(r: BufferReader): Draft15Message {
709
+ const request_id = r.readVarInt();
710
+ const error_code = r.readVarInt();
711
+ const reason_phrase = r.readString();
712
+ return { type: "request_error", request_id, error_code, reason_phrase };
713
+ }
714
+
715
+ function decodeGoAwayPayload(r: BufferReader): Draft15Message {
716
+ const new_session_uri = r.readString();
717
+ return { type: "goaway", new_session_uri };
718
+ }
719
+
720
+ function decodeMaxRequestIdPayload(r: BufferReader): Draft15Message {
721
+ const max_request_id = r.readVarInt();
722
+ return { type: "max_request_id", max_request_id };
723
+ }
724
+
725
+ function decodeRequestsBlockedPayload(r: BufferReader): Draft15Message {
726
+ const maximum_request_id = r.readVarInt();
727
+ return { type: "requests_blocked", maximum_request_id };
728
+ }
729
+
730
+ // ─── Payload dispatch tables ───────────────────────────────────────────────────
731
+
732
+ const payloadDecoders: ReadonlyMap<bigint, (r: BufferReader) => Draft15Message> = new Map([
733
+ [MSG_CLIENT_SETUP, decodeClientSetupPayload],
734
+ [MSG_SERVER_SETUP, decodeServerSetupPayload],
735
+ [MSG_SUBSCRIBE, decodeSubscribePayload],
736
+ [MSG_SUBSCRIBE_OK, decodeSubscribeOkPayload],
737
+ [MSG_SUBSCRIBE_UPDATE, decodeSubscribeUpdatePayload],
738
+ [MSG_UNSUBSCRIBE, decodeUnsubscribePayload],
739
+ [MSG_PUBLISH, decodePublishPayload],
740
+ [MSG_PUBLISH_OK, decodePublishOkPayload],
741
+ [MSG_PUBLISH_DONE, decodePublishDonePayload],
742
+ [MSG_PUBLISH_NAMESPACE, decodePublishNamespacePayload],
743
+ [MSG_PUBLISH_NAMESPACE_DONE, decodePublishNamespaceDonePayload],
744
+ [MSG_PUBLISH_NAMESPACE_CANCEL, decodePublishNamespaceCancelPayload],
745
+ [MSG_SUBSCRIBE_NAMESPACE, decodeSubscribeNamespacePayload],
746
+ [MSG_UNSUBSCRIBE_NAMESPACE, decodeUnsubscribeNamespacePayload],
747
+ [MSG_FETCH, decodeFetchPayload],
748
+ [MSG_FETCH_OK, decodeFetchOkPayload],
749
+ [MSG_FETCH_CANCEL, decodeFetchCancelPayload],
750
+ [MSG_TRACK_STATUS, decodeTrackStatusPayload],
751
+ [MSG_REQUEST_OK, decodeRequestOkPayload],
752
+ [MSG_REQUEST_ERROR, decodeRequestErrorPayload],
753
+ [MSG_GOAWAY, decodeGoAwayPayload],
754
+ [MSG_MAX_REQUEST_ID, decodeMaxRequestIdPayload],
755
+ [MSG_REQUESTS_BLOCKED, decodeRequestsBlockedPayload],
756
+ ]);
757
+
758
+ // ─── Public API ────────────────────────────────────────────────────────────────
759
+
760
+ /**
761
+ * Encode a draft-15 control message with type(varint) + length(uint16 BE) + payload.
762
+ */
763
+ export function encodeMessage(message: Draft15Message): Uint8Array {
764
+ const typeId = MESSAGE_ID_MAP.get(message.type);
765
+ if (typeId === undefined) {
766
+ throw new Error(`Unknown message type: ${message.type}`);
767
+ }
768
+
769
+ // Encode payload into a separate buffer
770
+ const payloadWriter = new BufferWriter();
771
+ encodePayload(message, payloadWriter);
772
+ const payload = payloadWriter.finish();
773
+
774
+ if (payload.byteLength > 0xffff) {
775
+ throw new Error(`Payload too large for 16-bit length: ${payload.byteLength}`);
776
+ }
777
+
778
+ // Write framed message: type(varint) + length(uint16 BE) + payload
779
+ const writer = new BufferWriter();
780
+ writer.writeVarInt(typeId);
781
+ writer.writeUint8((payload.byteLength >> 8) & 0xff);
782
+ writer.writeUint8(payload.byteLength & 0xff);
783
+ writer.writeBytes(payload);
784
+
785
+ return writer.finish();
786
+ }
787
+
788
+ function encodePayload(msg: Draft15Message, w: BufferWriter): void {
789
+ switch (msg.type) {
790
+ case "client_setup":
791
+ return encodeClientSetupPayload(msg, w);
792
+ case "server_setup":
793
+ return encodeServerSetupPayload(msg, w);
794
+ case "subscribe":
795
+ return encodeSubscribePayload(msg, w);
796
+ case "subscribe_ok":
797
+ return encodeSubscribeOkPayload(msg, w);
798
+ case "subscribe_update":
799
+ return encodeSubscribeUpdatePayload(msg, w);
800
+ case "unsubscribe":
801
+ return encodeUnsubscribePayload(msg, w);
802
+ case "publish":
803
+ return encodePublishPayload(msg, w);
804
+ case "publish_ok":
805
+ return encodePublishOkPayload(msg, w);
806
+ case "publish_done":
807
+ return encodePublishDonePayload(msg, w);
808
+ case "publish_namespace":
809
+ return encodePublishNamespacePayload(msg, w);
810
+ case "publish_namespace_done":
811
+ return encodePublishNamespaceDonePayload(msg, w);
812
+ case "publish_namespace_cancel":
813
+ return encodePublishNamespaceCancelPayload(msg, w);
814
+ case "subscribe_namespace":
815
+ return encodeSubscribeNamespacePayload(msg, w);
816
+ case "unsubscribe_namespace":
817
+ return encodeUnsubscribeNamespacePayload(msg, w);
818
+ case "fetch":
819
+ return encodeFetchPayload(msg, w);
820
+ case "fetch_ok":
821
+ return encodeFetchOkPayload(msg, w);
822
+ case "fetch_cancel":
823
+ return encodeFetchCancelPayload(msg, w);
824
+ case "track_status":
825
+ return encodeTrackStatusPayload(msg, w);
826
+ case "request_ok":
827
+ return encodeRequestOkPayload(msg, w);
828
+ case "request_error":
829
+ return encodeRequestErrorPayload(msg, w);
830
+ case "goaway":
831
+ return encodeGoAwayPayload(msg, w);
832
+ case "max_request_id":
833
+ return encodeMaxRequestIdPayload(msg, w);
834
+ case "requests_blocked":
835
+ return encodeRequestsBlockedPayload(msg, w);
836
+ default: {
837
+ const _exhaustive: never = msg;
838
+ throw new Error(`Unhandled message type: ${(_exhaustive as Draft15Message).type}`);
839
+ }
840
+ }
841
+ }
842
+
843
+ /**
844
+ * Decode a draft-15 control message from bytes (type + uint16 length + payload).
845
+ */
846
+ export function decodeMessage(bytes: Uint8Array): DecodeResult<Draft15Message> {
847
+ try {
848
+ const reader = new BufferReader(bytes);
849
+ const typeId = reader.readVarInt();
850
+
851
+ // Read 16-bit big-endian payload length
852
+ const lenHi = reader.readUint8();
853
+ const lenLo = reader.readUint8();
854
+ const payloadLength = (lenHi << 8) | lenLo;
855
+
856
+ // Read exactly payloadLength bytes
857
+ const payloadBytes = reader.readBytes(payloadLength);
858
+ const payloadReader = new BufferReader(payloadBytes);
859
+
860
+ const decoder = payloadDecoders.get(typeId);
861
+ if (!decoder) {
862
+ return {
863
+ ok: false,
864
+ error: new DecodeError(
865
+ "UNKNOWN_MESSAGE_TYPE",
866
+ `Unknown message type ID: 0x${typeId.toString(16)}`,
867
+ 0,
868
+ ),
869
+ };
870
+ }
871
+
872
+ const message = decoder(payloadReader);
873
+ return { ok: true, value: message, bytesRead: reader.offset };
874
+ } catch (e) {
875
+ if (e instanceof DecodeError) {
876
+ return { ok: false, error: e };
877
+ }
878
+ throw e;
879
+ }
880
+ }
881
+
882
+ // ─── Data Stream Encoding/Decoding ─────────────────────────────────────────────
883
+
884
+ // Draft-15 subgroup stream types use 0x10-0x1D range.
885
+ // For MVP, we use 0x10 (subgroupId=0, no extensions, no endOfGroup, priority present).
886
+ const _SUBGROUP_STREAM_TYPE_SIMPLE = 0x10n;
887
+ // Draft-15 fetch stream type
888
+ const FETCH_STREAM_TYPE = 0x05n;
889
+
890
+ /**
891
+ * Encode a subgroup stream header + objects.
892
+ * Uses the headerType from the stream to reproduce the exact wire type byte.
893
+ */
894
+ export function encodeSubgroupStream(stream: SubgroupStream): Uint8Array {
895
+ const w = new BufferWriter();
896
+ const streamType = stream.headerType;
897
+ w.writeVarInt(BigInt(streamType));
898
+
899
+ const hasSubgroupField = (streamType & 0x04) !== 0;
900
+ const hasPriority = streamType < 0x30;
901
+
902
+ w.writeVarInt(stream.trackAlias);
903
+ w.writeVarInt(stream.groupId);
904
+ if (hasSubgroupField) {
905
+ w.writeVarInt(stream.subgroupId);
906
+ }
907
+ if (hasPriority) {
908
+ w.writeUint8(stream.publisherPriority);
909
+ }
910
+ // Objects: delta-encoded object IDs
911
+ let prevObjectId = -1n;
912
+ for (const obj of stream.objects) {
913
+ const delta = prevObjectId < 0n ? obj.objectId : obj.objectId - prevObjectId - 1n;
914
+ w.writeVarInt(delta);
915
+ w.writeVarInt(obj.payloadLength);
916
+ if (obj.payloadLength === 0) {
917
+ w.writeVarInt(obj.status ?? 0n);
918
+ } else {
919
+ w.writeBytes(obj.payload);
920
+ }
921
+ prevObjectId = obj.objectId;
922
+ }
923
+ return w.finish();
924
+ }
925
+
926
+ /**
927
+ * Encode a datagram object.
928
+ * Uses type 0x00: Object ID present, priority present, no extensions, no EOG, payload.
929
+ */
930
+ export function encodeDatagram(dg: DatagramObject): Uint8Array {
931
+ const w = new BufferWriter();
932
+ // Determine datagram type byte from fields
933
+ const dgType = dg.datagramType;
934
+ w.writeVarInt(BigInt(dgType));
935
+ w.writeVarInt(dg.trackAlias);
936
+ w.writeVarInt(dg.groupId);
937
+
938
+ // Datagram type flags:
939
+ // bit 2 (0x04): object_id ABSENT when set
940
+ // bit 1 (0x02): end-of-group
941
+ // bit 5 (0x20): status (replaces payload)
942
+ const objectIdAbsent = (dgType & 0x04) !== 0;
943
+ const isStatus = (dgType & 0x20) !== 0;
944
+
945
+ if (!objectIdAbsent) {
946
+ w.writeVarInt(dg.objectId);
947
+ }
948
+ w.writeUint8(dg.publisherPriority);
949
+
950
+ if (isStatus) {
951
+ w.writeVarInt(dg.objectStatus ?? 0n);
952
+ } else {
953
+ w.writeBytes(dg.payload);
954
+ }
955
+ return w.finish();
956
+ }
957
+
958
+ /**
959
+ * Encode a fetch stream header + objects.
960
+ */
961
+ export function encodeFetchStream(stream: FetchStream): Uint8Array {
962
+ const w = new BufferWriter();
963
+ w.writeVarInt(FETCH_STREAM_TYPE);
964
+ w.writeVarInt(stream.requestId);
965
+ for (const obj of stream.objects) {
966
+ w.writeUint8(obj.serializationFlags);
967
+ const flags = obj.serializationFlags;
968
+ if (flags & 0x08) w.writeVarInt(obj.groupId);
969
+ // subgroup encoding: bits 0-1
970
+ const subgroupEncoding = flags & 0x03;
971
+ if (subgroupEncoding === 0x03) w.writeVarInt(obj.subgroupId);
972
+ if (flags & 0x04) w.writeVarInt(obj.objectId);
973
+ if (flags & 0x10) w.writeUint8(obj.publisherPriority);
974
+ // extensions (0x20) — not supported in encoder
975
+ w.writeVarInt(obj.payloadLength);
976
+ if (obj.payloadLength === 0) {
977
+ w.writeVarInt(obj.status ?? 0n);
978
+ } else {
979
+ w.writeBytes(obj.payload);
980
+ }
981
+ }
982
+ return w.finish();
983
+ }
984
+
985
+ /**
986
+ * Decode a subgroup data stream from raw bytes.
987
+ */
988
+ export function decodeSubgroupStream(bytes: Uint8Array): DecodeResult<SubgroupStream> {
989
+ try {
990
+ const r = new BufferReader(bytes);
991
+ const streamType = Number(r.readVarInt());
992
+
993
+ // Valid subgroup types: 0x10-0x1D and 0x30-0x3D
994
+ if (
995
+ !((streamType >= 0x10 && streamType <= 0x1d) || (streamType >= 0x30 && streamType <= 0x3d))
996
+ ) {
997
+ return {
998
+ ok: false,
999
+ error: new DecodeError(
1000
+ "CONSTRAINT_VIOLATION",
1001
+ `Expected subgroup stream type 0x10-0x1D or 0x30-0x3D, got 0x${streamType.toString(16)}`,
1002
+ 0,
1003
+ ),
1004
+ };
1005
+ }
1006
+
1007
+ // Decode type flags
1008
+ const hasSubgroupField = (streamType & 0x04) !== 0;
1009
+ const subgroupIsFirstObjId = (streamType & 0x02) !== 0 && !hasSubgroupField;
1010
+ const hasPriority = streamType < 0x30;
1011
+
1012
+ const trackAlias = r.readVarInt();
1013
+ const groupId = r.readVarInt();
1014
+
1015
+ let subgroupId = 0n;
1016
+ if (hasSubgroupField) {
1017
+ subgroupId = r.readVarInt();
1018
+ }
1019
+
1020
+ let publisherPriority = 128; // default
1021
+ if (hasPriority) {
1022
+ publisherPriority = r.readUint8();
1023
+ }
1024
+
1025
+ const objects: ObjectPayload[] = [];
1026
+ let prevObjectId = -1n;
1027
+ let firstObject = true;
1028
+
1029
+ while (r.remaining > 0) {
1030
+ const delta = r.readVarInt();
1031
+ let objectId: bigint;
1032
+ if (firstObject) {
1033
+ objectId = delta;
1034
+ if (subgroupIsFirstObjId && firstObject) {
1035
+ subgroupId = objectId;
1036
+ }
1037
+ firstObject = false;
1038
+ } else {
1039
+ objectId = prevObjectId + 1n + delta;
1040
+ }
1041
+ // Extensions would be parsed here if type has extensions flag — skip for MVP
1042
+ const payloadLength = Number(r.readVarInt());
1043
+ let payload: Uint8Array;
1044
+ let status: bigint | undefined;
1045
+ if (payloadLength === 0) {
1046
+ // When payload_length is 0, an object status varint follows
1047
+ status = r.readVarInt();
1048
+ payload = new Uint8Array(0);
1049
+ } else {
1050
+ payload = r.readBytes(payloadLength);
1051
+ }
1052
+ const obj: ObjectPayload = { type: "object", objectId, payloadLength, payload };
1053
+ if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status;
1054
+ objects.push(obj);
1055
+ prevObjectId = objectId;
1056
+ }
1057
+
1058
+ return {
1059
+ ok: true,
1060
+ value: {
1061
+ type: "subgroup",
1062
+ headerType: streamType,
1063
+ trackAlias,
1064
+ groupId,
1065
+ subgroupId,
1066
+ publisherPriority,
1067
+ objects,
1068
+ },
1069
+ bytesRead: r.offset,
1070
+ };
1071
+ } catch (e) {
1072
+ if (e instanceof DecodeError) return { ok: false, error: e };
1073
+ throw e;
1074
+ }
1075
+ }
1076
+
1077
+ /**
1078
+ * Decode a datagram object from raw bytes.
1079
+ */
1080
+ export function decodeDatagram(bytes: Uint8Array): DecodeResult<DatagramObject> {
1081
+ try {
1082
+ const r = new BufferReader(bytes);
1083
+ const dgType = Number(r.readVarInt());
1084
+
1085
+ // Datagram type flags:
1086
+ // bit 2 (0x04): object_id ABSENT when set
1087
+ // bit 1 (0x02): end-of-group
1088
+ // bit 5 (0x20): status (replaces payload with status varint)
1089
+ const objectIdAbsent = (dgType & 0x04) !== 0;
1090
+ const endOfGroup = (dgType & 0x02) !== 0;
1091
+ const isStatus = (dgType & 0x20) !== 0;
1092
+
1093
+ const trackAlias = r.readVarInt();
1094
+ const groupId = r.readVarInt();
1095
+ let objectId = 0n;
1096
+ if (!objectIdAbsent) {
1097
+ objectId = r.readVarInt();
1098
+ }
1099
+ const publisherPriority = r.readUint8();
1100
+
1101
+ let objectStatus: bigint | undefined;
1102
+ let payload: Uint8Array;
1103
+ if (isStatus) {
1104
+ objectStatus = r.readVarInt();
1105
+ payload = new Uint8Array(0);
1106
+ } else {
1107
+ payload = r.readBytes(r.remaining);
1108
+ }
1109
+ const payloadLength = payload.byteLength;
1110
+
1111
+ const result: DatagramObject = {
1112
+ type: "datagram",
1113
+ datagramType: dgType,
1114
+ trackAlias,
1115
+ groupId,
1116
+ objectId,
1117
+ publisherPriority,
1118
+ payloadLength,
1119
+ payload,
1120
+ };
1121
+
1122
+ if (endOfGroup) (result as unknown as Record<string, unknown>).endOfGroup = true;
1123
+ if (objectStatus !== undefined)
1124
+ (result as unknown as Record<string, unknown>).objectStatus = objectStatus;
1125
+
1126
+ return { ok: true, value: result, bytesRead: r.offset };
1127
+ } catch (e) {
1128
+ if (e instanceof DecodeError) return { ok: false, error: e };
1129
+ throw e;
1130
+ }
1131
+ }
1132
+
1133
+ /**
1134
+ * Decode a fetch data stream from raw bytes.
1135
+ */
1136
+ export function decodeFetchStream(bytes: Uint8Array): DecodeResult<FetchStream> {
1137
+ try {
1138
+ const r = new BufferReader(bytes);
1139
+ const streamType = r.readVarInt();
1140
+ if (streamType !== FETCH_STREAM_TYPE) {
1141
+ return {
1142
+ ok: false,
1143
+ error: new DecodeError(
1144
+ "CONSTRAINT_VIOLATION",
1145
+ `Expected fetch stream type 0x05, got 0x${streamType.toString(16)}`,
1146
+ 0,
1147
+ ),
1148
+ };
1149
+ }
1150
+ const requestId = r.readVarInt();
1151
+ const objects: FetchObjectPayload[] = [];
1152
+
1153
+ let prevGroupId = 0n;
1154
+ let prevSubgroupId = 0n;
1155
+ let prevObjectId = 0n;
1156
+ let prevPriority = 128;
1157
+ let first = true;
1158
+
1159
+ while (r.remaining > 0) {
1160
+ const flags = r.readUint8();
1161
+ const subgroupEncoding = flags & 0x03;
1162
+ const objectIdPresent = (flags & 0x04) !== 0;
1163
+ const groupIdPresent = (flags & 0x08) !== 0;
1164
+ const priorityPresent = (flags & 0x10) !== 0;
1165
+ const extensionsPresent = (flags & 0x20) !== 0;
1166
+
1167
+ if (flags & 0xc0) {
1168
+ return {
1169
+ ok: false,
1170
+ error: new DecodeError(
1171
+ "CONSTRAINT_VIOLATION",
1172
+ "Reserved bits set in fetch object flags",
1173
+ r.offset,
1174
+ ),
1175
+ };
1176
+ }
1177
+
1178
+ let groupId = prevGroupId;
1179
+ if (groupIdPresent) {
1180
+ groupId = r.readVarInt();
1181
+ } else if (first) {
1182
+ return {
1183
+ ok: false,
1184
+ error: new DecodeError(
1185
+ "CONSTRAINT_VIOLATION",
1186
+ "First fetch object must include groupId",
1187
+ r.offset,
1188
+ ),
1189
+ };
1190
+ }
1191
+
1192
+ let subgroupId = prevSubgroupId;
1193
+ if (subgroupEncoding === 0x03) {
1194
+ subgroupId = r.readVarInt();
1195
+ } else if (subgroupEncoding === 0x00) {
1196
+ // Inherit from previous (or 0 if first)
1197
+ }
1198
+
1199
+ let objectId = prevObjectId + 1n;
1200
+ if (objectIdPresent) {
1201
+ objectId = r.readVarInt();
1202
+ } else if (first) {
1203
+ return {
1204
+ ok: false,
1205
+ error: new DecodeError(
1206
+ "CONSTRAINT_VIOLATION",
1207
+ "First fetch object must include objectId",
1208
+ r.offset,
1209
+ ),
1210
+ };
1211
+ }
1212
+
1213
+ if (priorityPresent) {
1214
+ prevPriority = r.readUint8();
1215
+ }
1216
+
1217
+ if (extensionsPresent) {
1218
+ const extLen = Number(r.readVarInt());
1219
+ if (extLen > 0) {
1220
+ r.readBytes(extLen);
1221
+ }
1222
+ }
1223
+
1224
+ const payloadLength = Number(r.readVarInt());
1225
+ let payload: Uint8Array;
1226
+ let status: bigint | undefined;
1227
+ if (payloadLength > 0) {
1228
+ payload = r.readBytes(payloadLength);
1229
+ } else {
1230
+ status = r.readVarInt();
1231
+ payload = new Uint8Array(0);
1232
+ }
1233
+
1234
+ const obj: FetchObjectPayload = {
1235
+ type: "object",
1236
+ serializationFlags: flags,
1237
+ groupId,
1238
+ subgroupId,
1239
+ objectId,
1240
+ publisherPriority: prevPriority,
1241
+ payloadLength,
1242
+ payload,
1243
+ };
1244
+ if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status;
1245
+ objects.push(obj);
1246
+
1247
+ prevGroupId = groupId;
1248
+ prevSubgroupId = subgroupId;
1249
+ prevObjectId = objectId;
1250
+ first = false;
1251
+ }
1252
+
1253
+ return {
1254
+ ok: true,
1255
+ value: { type: "fetch", requestId, objects },
1256
+ bytesRead: r.offset,
1257
+ };
1258
+ } catch (e) {
1259
+ if (e instanceof DecodeError) return { ok: false, error: e };
1260
+ throw e;
1261
+ }
1262
+ }
1263
+
1264
+ /**
1265
+ * Decode a data stream, dispatching by stream type.
1266
+ */
1267
+ export function decodeDataStream(
1268
+ streamType: "subgroup" | "datagram" | "fetch",
1269
+ bytes: Uint8Array,
1270
+ ): DecodeResult<Draft15DataStream> {
1271
+ switch (streamType) {
1272
+ case "subgroup":
1273
+ return decodeSubgroupStream(bytes);
1274
+ case "datagram":
1275
+ return decodeDatagram(bytes);
1276
+ case "fetch":
1277
+ return decodeFetchStream(bytes);
1278
+ default: {
1279
+ const _exhaustive: never = streamType;
1280
+ throw new Error(`Unknown stream type: ${_exhaustive}`);
1281
+ }
1282
+ }
1283
+ }
1284
+
1285
+ // ─── Stream Decoders ───────────────────────────────────────────────────────────
1286
+
1287
+ /**
1288
+ * Create a TransformStream that decodes a continuous byte stream into
1289
+ * individual Draft15Message objects.
1290
+ */
1291
+ export function createStreamDecoder(): TransformStream<Uint8Array, Draft15Message> {
1292
+ let buffer = new Uint8Array(0);
1293
+
1294
+ return new TransformStream<Uint8Array, Draft15Message>({
1295
+ transform(chunk, controller) {
1296
+ const newBuffer = new Uint8Array(buffer.length + chunk.length);
1297
+ newBuffer.set(buffer, 0);
1298
+ newBuffer.set(chunk, buffer.length);
1299
+ buffer = newBuffer;
1300
+
1301
+ while (buffer.length > 0) {
1302
+ const result = decodeMessage(buffer);
1303
+ if (!result.ok) {
1304
+ if (result.error.code === "UNEXPECTED_END") {
1305
+ break;
1306
+ }
1307
+ controller.error(result.error);
1308
+ return;
1309
+ }
1310
+ controller.enqueue(result.value);
1311
+ buffer = buffer.slice(result.bytesRead);
1312
+ }
1313
+ },
1314
+
1315
+ flush(controller) {
1316
+ if (buffer.length > 0) {
1317
+ controller.error(
1318
+ new DecodeError("UNEXPECTED_END", "Stream ended with incomplete message data", 0),
1319
+ );
1320
+ }
1321
+ },
1322
+ });
1323
+ }
1324
+
1325
+ /**
1326
+ * Create a TransformStream that decodes a subgroup data stream.
1327
+ */
1328
+ export function createSubgroupStreamDecoder(): TransformStream<
1329
+ Uint8Array,
1330
+ SubgroupStreamHeader | ObjectPayload
1331
+ > {
1332
+ let buffer = new Uint8Array(0);
1333
+ let headerEmitted = false;
1334
+ let prevObjectId = -1n;
1335
+ let firstObject = true;
1336
+ let _subgroupIsFirstObjId = false;
1337
+
1338
+ return new TransformStream<Uint8Array, SubgroupStreamHeader | ObjectPayload>({
1339
+ transform(chunk, controller) {
1340
+ const newBuffer = new Uint8Array(buffer.length + chunk.length);
1341
+ newBuffer.set(buffer, 0);
1342
+ newBuffer.set(chunk, buffer.length);
1343
+ buffer = newBuffer;
1344
+
1345
+ if (!headerEmitted) {
1346
+ try {
1347
+ const r = new BufferReader(buffer);
1348
+ const streamType = Number(r.readVarInt());
1349
+
1350
+ if (
1351
+ !(
1352
+ (streamType >= 0x10 && streamType <= 0x1d) ||
1353
+ (streamType >= 0x30 && streamType <= 0x3d)
1354
+ )
1355
+ ) {
1356
+ controller.error(
1357
+ new DecodeError(
1358
+ "CONSTRAINT_VIOLATION",
1359
+ `Expected subgroup stream type, got 0x${streamType.toString(16)}`,
1360
+ 0,
1361
+ ),
1362
+ );
1363
+ return;
1364
+ }
1365
+
1366
+ const hasSubgroupField = (streamType & 0x04) !== 0;
1367
+ _subgroupIsFirstObjId = (streamType & 0x02) !== 0 && !hasSubgroupField;
1368
+ const hasPriority = streamType < 0x30;
1369
+
1370
+ const trackAlias = r.readVarInt();
1371
+ const groupId = r.readVarInt();
1372
+
1373
+ let subgroupId = 0n;
1374
+ if (hasSubgroupField) {
1375
+ subgroupId = r.readVarInt();
1376
+ }
1377
+
1378
+ let publisherPriority = 128;
1379
+ if (hasPriority) {
1380
+ publisherPriority = r.readUint8();
1381
+ }
1382
+
1383
+ controller.enqueue({
1384
+ type: "subgroup_header",
1385
+ trackAlias,
1386
+ groupId,
1387
+ subgroupId,
1388
+ publisherPriority,
1389
+ });
1390
+ headerEmitted = true;
1391
+ buffer = buffer.slice(r.offset);
1392
+ } catch (e) {
1393
+ if (e instanceof DecodeError && e.code === "UNEXPECTED_END") {
1394
+ return;
1395
+ }
1396
+ controller.error(e);
1397
+ return;
1398
+ }
1399
+ }
1400
+
1401
+ // Parse objects with delta-encoded IDs
1402
+ while (buffer.length > 0) {
1403
+ try {
1404
+ const r = new BufferReader(buffer);
1405
+ const delta = r.readVarInt();
1406
+ let objectId: bigint;
1407
+ if (firstObject) {
1408
+ objectId = delta;
1409
+ firstObject = false;
1410
+ } else {
1411
+ objectId = prevObjectId + 1n + delta;
1412
+ }
1413
+ const payloadLength = Number(r.readVarInt());
1414
+ const payload = payloadLength > 0 ? r.readBytes(payloadLength) : new Uint8Array(0);
1415
+ controller.enqueue({ type: "object", objectId, payloadLength, payload });
1416
+ buffer = buffer.slice(r.offset);
1417
+ prevObjectId = objectId;
1418
+ } catch (e) {
1419
+ if (e instanceof DecodeError && e.code === "UNEXPECTED_END") {
1420
+ break;
1421
+ }
1422
+ controller.error(e);
1423
+ return;
1424
+ }
1425
+ }
1426
+ },
1427
+
1428
+ flush(controller) {
1429
+ if (buffer.length > 0) {
1430
+ controller.error(new DecodeError("UNEXPECTED_END", "Stream ended with incomplete data", 0));
1431
+ }
1432
+ },
1433
+ });
1434
+ }
1435
+
1436
+ /**
1437
+ * Create a TransformStream that decodes a fetch data stream.
1438
+ */
1439
+ export function createFetchStreamDecoder(): TransformStream<
1440
+ Uint8Array,
1441
+ FetchStreamHeader | ObjectPayload
1442
+ > {
1443
+ let buffer = new Uint8Array(0);
1444
+ let headerEmitted = false;
1445
+
1446
+ return new TransformStream<Uint8Array, FetchStreamHeader | ObjectPayload>({
1447
+ transform(chunk, controller) {
1448
+ const newBuffer = new Uint8Array(buffer.length + chunk.length);
1449
+ newBuffer.set(buffer, 0);
1450
+ newBuffer.set(chunk, buffer.length);
1451
+ buffer = newBuffer;
1452
+
1453
+ if (!headerEmitted) {
1454
+ try {
1455
+ const r = new BufferReader(buffer);
1456
+ const streamType = r.readVarInt();
1457
+ if (streamType !== FETCH_STREAM_TYPE) {
1458
+ controller.error(
1459
+ new DecodeError(
1460
+ "CONSTRAINT_VIOLATION",
1461
+ `Expected fetch stream type 0x05, got 0x${streamType.toString(16)}`,
1462
+ 0,
1463
+ ),
1464
+ );
1465
+ return;
1466
+ }
1467
+ const requestId = r.readVarInt();
1468
+ controller.enqueue({ type: "fetch_header", requestId });
1469
+ headerEmitted = true;
1470
+ buffer = buffer.slice(r.offset);
1471
+ } catch (e) {
1472
+ if (e instanceof DecodeError && e.code === "UNEXPECTED_END") {
1473
+ return;
1474
+ }
1475
+ controller.error(e);
1476
+ return;
1477
+ }
1478
+ }
1479
+
1480
+ // Parse fetch objects with serialization flags
1481
+ // Simplified: just read flags + explicit fields
1482
+ while (buffer.length > 0) {
1483
+ try {
1484
+ const r = new BufferReader(buffer);
1485
+ const flags = r.readUint8();
1486
+ const objectIdPresent = (flags & 0x04) !== 0;
1487
+ const groupIdPresent = (flags & 0x08) !== 0;
1488
+ const priorityPresent = (flags & 0x10) !== 0;
1489
+ const extensionsPresent = (flags & 0x20) !== 0;
1490
+ const subgroupEncoding = flags & 0x03;
1491
+
1492
+ if (groupIdPresent) r.readVarInt(); // groupId — consumed
1493
+ if (subgroupEncoding === 0x03) r.readVarInt(); // subgroupId — consumed
1494
+ let objectId = 0n;
1495
+ if (objectIdPresent) objectId = r.readVarInt();
1496
+ if (priorityPresent) r.readUint8(); // priority — consumed
1497
+ if (extensionsPresent) {
1498
+ const extLen = Number(r.readVarInt());
1499
+ if (extLen > 0) r.readBytes(extLen);
1500
+ }
1501
+ const payloadLength = Number(r.readVarInt());
1502
+ const payload = payloadLength > 0 ? r.readBytes(payloadLength) : new Uint8Array(0);
1503
+ controller.enqueue({ type: "object", objectId, payloadLength, payload });
1504
+ buffer = buffer.slice(r.offset);
1505
+ } catch (e) {
1506
+ if (e instanceof DecodeError && e.code === "UNEXPECTED_END") {
1507
+ break;
1508
+ }
1509
+ controller.error(e);
1510
+ return;
1511
+ }
1512
+ }
1513
+ },
1514
+
1515
+ flush(controller) {
1516
+ if (buffer.length > 0) {
1517
+ controller.error(new DecodeError("UNEXPECTED_END", "Stream ended with incomplete data", 0));
1518
+ }
1519
+ },
1520
+ });
1521
+ }
1522
+
1523
+ /**
1524
+ * Create a unified auto-detecting data stream decoder.
1525
+ */
1526
+ export function createDataStreamDecoder(): TransformStream<Uint8Array, DataStreamEvent> {
1527
+ let buffer = new Uint8Array(0);
1528
+ let inner: TransformStream<Uint8Array, DataStreamEvent> | null = null;
1529
+ const _innerWriter: WritableStreamDefaultWriter<Uint8Array> | null = null;
1530
+ const _innerReader: ReadableStreamDefaultReader<DataStreamEvent> | null = null;
1531
+
1532
+ return new TransformStream<Uint8Array, DataStreamEvent>({
1533
+ transform(chunk, controller) {
1534
+ const newBuffer = new Uint8Array(buffer.length + chunk.length);
1535
+ newBuffer.set(buffer, 0);
1536
+ newBuffer.set(chunk, buffer.length);
1537
+ buffer = newBuffer;
1538
+
1539
+ if (inner === null) {
1540
+ // Need at least one byte to determine type
1541
+ if (buffer.length === 0) return;
1542
+ const firstByte = buffer[0]!;
1543
+
1544
+ if ((firstByte >= 0x10 && firstByte <= 0x1d) || (firstByte >= 0x30 && firstByte <= 0x3d)) {
1545
+ // Subgroup — delegate to subgroup decoder
1546
+ // We need to feed the full buffer including the type byte
1547
+ const decoder = createSubgroupStreamDecoder();
1548
+ inner = decoder as unknown as TransformStream<Uint8Array, DataStreamEvent>;
1549
+ } else if (firstByte === 0x05) {
1550
+ // Fetch
1551
+ const decoder = createFetchStreamDecoder();
1552
+ inner = decoder as unknown as TransformStream<Uint8Array, DataStreamEvent>;
1553
+ } else {
1554
+ controller.error(
1555
+ new DecodeError(
1556
+ "CONSTRAINT_VIOLATION",
1557
+ `Unknown data stream type: 0x${firstByte.toString(16)}`,
1558
+ 0,
1559
+ ),
1560
+ );
1561
+ return;
1562
+ }
1563
+
1564
+ // For simplicity, process inline instead of piping
1565
+ // Just re-parse from buffer using the appropriate one-shot decoder
1566
+ }
1567
+
1568
+ // Since inner TransformStream piping is complex, use a simpler approach:
1569
+ // Accumulate and attempt decode when flush is called
1570
+ // For the streaming case, we just buffer everything
1571
+ },
1572
+
1573
+ flush(controller) {
1574
+ if (buffer.length === 0) return;
1575
+
1576
+ const firstByte = buffer[0]!;
1577
+ let result: DecodeResult<Draft15DataStream>;
1578
+
1579
+ if ((firstByte >= 0x10 && firstByte <= 0x1d) || (firstByte >= 0x30 && firstByte <= 0x3d)) {
1580
+ result = decodeSubgroupStream(buffer);
1581
+ } else if (firstByte === 0x05) {
1582
+ result = decodeFetchStream(buffer);
1583
+ } else {
1584
+ controller.error(
1585
+ new DecodeError(
1586
+ "CONSTRAINT_VIOLATION",
1587
+ `Unknown data stream type: 0x${firstByte.toString(16)}`,
1588
+ 0,
1589
+ ),
1590
+ );
1591
+ return;
1592
+ }
1593
+
1594
+ if (!result.ok) {
1595
+ controller.error(result.error);
1596
+ return;
1597
+ }
1598
+
1599
+ const stream = result.value;
1600
+ if (stream.type === "subgroup") {
1601
+ controller.enqueue({
1602
+ type: "subgroup_header",
1603
+ trackAlias: stream.trackAlias,
1604
+ groupId: stream.groupId,
1605
+ subgroupId: stream.subgroupId,
1606
+ publisherPriority: stream.publisherPriority,
1607
+ });
1608
+ for (const obj of stream.objects) {
1609
+ controller.enqueue(obj);
1610
+ }
1611
+ } else if (stream.type === "fetch") {
1612
+ controller.enqueue({
1613
+ type: "fetch_header",
1614
+ requestId: stream.requestId,
1615
+ });
1616
+ for (const obj of stream.objects) {
1617
+ controller.enqueue(obj);
1618
+ }
1619
+ }
1620
+ },
1621
+ });
1622
+ }
1623
+
1624
+ // ─── Codec Factory ─────────────────────────────────────────────────────────────
1625
+
1626
+ export interface Draft15Codec extends BaseCodec<Draft15Message> {
1627
+ readonly draft: "draft-ietf-moq-transport-15";
1628
+ encodeSubgroupStream(stream: SubgroupStream): Uint8Array;
1629
+ encodeDatagram(dg: DatagramObject): Uint8Array;
1630
+ encodeFetchStream(stream: FetchStream): Uint8Array;
1631
+ decodeSubgroupStream(bytes: Uint8Array): DecodeResult<SubgroupStream>;
1632
+ decodeDatagram(bytes: Uint8Array): DecodeResult<DatagramObject>;
1633
+ decodeFetchStream(bytes: Uint8Array): DecodeResult<FetchStream>;
1634
+ decodeDataStream(
1635
+ streamType: "subgroup" | "datagram" | "fetch",
1636
+ bytes: Uint8Array,
1637
+ ): DecodeResult<Draft15DataStream>;
1638
+ createStreamDecoder(): TransformStream<Uint8Array, Draft15Message>;
1639
+ createSubgroupStreamDecoder(): TransformStream<Uint8Array, SubgroupStreamHeader | ObjectPayload>;
1640
+ createFetchStreamDecoder(): TransformStream<Uint8Array, FetchStreamHeader | ObjectPayload>;
1641
+ createDataStreamDecoder(): TransformStream<Uint8Array, DataStreamEvent>;
1642
+ }
1643
+
1644
+ export function createDraft15Codec(): Draft15Codec {
1645
+ return {
1646
+ draft: "draft-ietf-moq-transport-15",
1647
+ encodeMessage,
1648
+ decodeMessage,
1649
+ encodeSubgroupStream,
1650
+ encodeDatagram,
1651
+ encodeFetchStream,
1652
+ decodeSubgroupStream,
1653
+ decodeDatagram,
1654
+ decodeFetchStream,
1655
+ decodeDataStream,
1656
+ createStreamDecoder,
1657
+ createSubgroupStreamDecoder,
1658
+ createFetchStreamDecoder,
1659
+ createDataStreamDecoder,
1660
+ };
1661
+ }