@norskvideo/moq-net 0.1.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 (274) hide show
  1. package/README.md +70 -0
  2. package/announced.d.ts +45 -0
  3. package/announced.d.ts.map +1 -0
  4. package/announced.js +70 -0
  5. package/announced.js.map +1 -0
  6. package/bandwidth.d.ts +11 -0
  7. package/bandwidth.d.ts.map +1 -0
  8. package/bandwidth.js +7 -0
  9. package/bandwidth.js.map +1 -0
  10. package/broadcast.d.ts +39 -0
  11. package/broadcast.d.ts.map +1 -0
  12. package/broadcast.js +76 -0
  13. package/broadcast.js.map +1 -0
  14. package/connection/accept.d.ts +15 -0
  15. package/connection/accept.d.ts.map +1 -0
  16. package/connection/accept.js +137 -0
  17. package/connection/accept.js.map +1 -0
  18. package/connection/cert.test.d.ts +2 -0
  19. package/connection/cert.test.d.ts.map +1 -0
  20. package/connection/cert.test.js +26 -0
  21. package/connection/cert.test.js.map +1 -0
  22. package/connection/connect.d.ts +37 -0
  23. package/connection/connect.d.ts.map +1 -0
  24. package/connection/connect.js +348 -0
  25. package/connection/connect.js.map +1 -0
  26. package/connection/established.d.ts +30 -0
  27. package/connection/established.d.ts.map +1 -0
  28. package/connection/established.js +3 -0
  29. package/connection/established.js.map +1 -0
  30. package/connection/handshake.d.ts +10 -0
  31. package/connection/handshake.d.ts.map +1 -0
  32. package/connection/handshake.js +48 -0
  33. package/connection/handshake.js.map +1 -0
  34. package/connection/index.d.ts +5 -0
  35. package/connection/index.d.ts.map +1 -0
  36. package/connection/index.js +6 -0
  37. package/connection/index.js.map +1 -0
  38. package/connection/reload.d.ts +47 -0
  39. package/connection/reload.d.ts.map +1 -0
  40. package/connection/reload.js +137 -0
  41. package/connection/reload.js.map +1 -0
  42. package/group.d.ts +66 -0
  43. package/group.d.ts.map +1 -0
  44. package/group.js +135 -0
  45. package/group.js.map +1 -0
  46. package/group.test.d.ts +2 -0
  47. package/group.test.d.ts.map +1 -0
  48. package/group.test.js +66 -0
  49. package/group.test.js.map +1 -0
  50. package/ietf/adapter.d.ts +65 -0
  51. package/ietf/adapter.d.ts.map +1 -0
  52. package/ietf/adapter.js +626 -0
  53. package/ietf/adapter.js.map +1 -0
  54. package/ietf/connection.d.ts +65 -0
  55. package/ietf/connection.d.ts.map +1 -0
  56. package/ietf/connection.js +248 -0
  57. package/ietf/connection.js.map +1 -0
  58. package/ietf/control.d.ts +135 -0
  59. package/ietf/control.d.ts.map +1 -0
  60. package/ietf/control.js +242 -0
  61. package/ietf/control.js.map +1 -0
  62. package/ietf/fetch.d.ts +64 -0
  63. package/ietf/fetch.d.ts.map +1 -0
  64. package/ietf/fetch.js +100 -0
  65. package/ietf/fetch.js.map +1 -0
  66. package/ietf/goaway.d.ts +15 -0
  67. package/ietf/goaway.d.ts.map +1 -0
  68. package/ietf/goaway.js +34 -0
  69. package/ietf/goaway.js.map +1 -0
  70. package/ietf/ietf.test.d.ts +2 -0
  71. package/ietf/ietf.test.d.ts.map +1 -0
  72. package/ietf/ietf.test.js +873 -0
  73. package/ietf/ietf.test.js.map +1 -0
  74. package/ietf/index.d.ts +18 -0
  75. package/ietf/index.d.ts.map +1 -0
  76. package/ietf/index.js +19 -0
  77. package/ietf/index.js.map +1 -0
  78. package/ietf/message.d.ts +4 -0
  79. package/ietf/message.d.ts.map +1 -0
  80. package/ietf/message.js +54 -0
  81. package/ietf/message.js.map +1 -0
  82. package/ietf/namespace.d.ts +5 -0
  83. package/ietf/namespace.d.ts.map +1 -0
  84. package/ietf/namespace.js +27 -0
  85. package/ietf/namespace.js.map +1 -0
  86. package/ietf/object.d.ts +40 -0
  87. package/ietf/object.d.ts.map +1 -0
  88. package/ietf/object.js +160 -0
  89. package/ietf/object.js.map +1 -0
  90. package/ietf/parameters.d.ts +56 -0
  91. package/ietf/parameters.d.ts.map +1 -0
  92. package/ietf/parameters.js +337 -0
  93. package/ietf/parameters.js.map +1 -0
  94. package/ietf/properties.d.ts +4 -0
  95. package/ietf/properties.d.ts.map +1 -0
  96. package/ietf/properties.js +33 -0
  97. package/ietf/properties.js.map +1 -0
  98. package/ietf/publish.d.ts +68 -0
  99. package/ietf/publish.d.ts.map +1 -0
  100. package/ietf/publish.js +192 -0
  101. package/ietf/publish.js.map +1 -0
  102. package/ietf/publish_namespace.d.ts +68 -0
  103. package/ietf/publish_namespace.d.ts.map +1 -0
  104. package/ietf/publish_namespace.js +176 -0
  105. package/ietf/publish_namespace.js.map +1 -0
  106. package/ietf/publisher.d.ts +50 -0
  107. package/ietf/publisher.d.ts.map +1 -0
  108. package/ietf/publisher.js +330 -0
  109. package/ietf/publisher.js.map +1 -0
  110. package/ietf/request.d.ts +52 -0
  111. package/ietf/request.d.ts.map +1 -0
  112. package/ietf/request.js +119 -0
  113. package/ietf/request.js.map +1 -0
  114. package/ietf/setup.d.ts +38 -0
  115. package/ietf/setup.d.ts.map +1 -0
  116. package/ietf/setup.js +129 -0
  117. package/ietf/setup.js.map +1 -0
  118. package/ietf/subscribe.d.ts +66 -0
  119. package/ietf/subscribe.d.ts.map +1 -0
  120. package/ietf/subscribe.js +276 -0
  121. package/ietf/subscribe.js.map +1 -0
  122. package/ietf/subscribe_namespace.d.ts +101 -0
  123. package/ietf/subscribe_namespace.d.ts.map +1 -0
  124. package/ietf/subscribe_namespace.js +256 -0
  125. package/ietf/subscribe_namespace.js.map +1 -0
  126. package/ietf/subscriber.d.ts +53 -0
  127. package/ietf/subscriber.d.ts.map +1 -0
  128. package/ietf/subscriber.js +404 -0
  129. package/ietf/subscriber.js.map +1 -0
  130. package/ietf/track.d.ts +40 -0
  131. package/ietf/track.d.ts.map +1 -0
  132. package/ietf/track.js +108 -0
  133. package/ietf/track.js.map +1 -0
  134. package/ietf/version.d.ts +56 -0
  135. package/ietf/version.d.ts.map +1 -0
  136. package/ietf/version.js +63 -0
  137. package/ietf/version.js.map +1 -0
  138. package/index.d.ts +22 -0
  139. package/index.d.ts.map +1 -0
  140. package/index.js +23 -0
  141. package/index.js.map +1 -0
  142. package/integration.test.d.ts +2 -0
  143. package/integration.test.d.ts.map +1 -0
  144. package/integration.test.js +90 -0
  145. package/integration.test.js.map +1 -0
  146. package/lite/announce.d.ts +35 -0
  147. package/lite/announce.d.ts.map +1 -0
  148. package/lite/announce.js +166 -0
  149. package/lite/announce.js.map +1 -0
  150. package/lite/connection.d.ts +47 -0
  151. package/lite/connection.d.ts.map +1 -0
  152. package/lite/connection.js +247 -0
  153. package/lite/connection.js.map +1 -0
  154. package/lite/fetch.d.ts +14 -0
  155. package/lite/fetch.d.ts.map +1 -0
  156. package/lite/fetch.js +47 -0
  157. package/lite/fetch.js.map +1 -0
  158. package/lite/goaway.d.ts +10 -0
  159. package/lite/goaway.d.ts.map +1 -0
  160. package/lite/goaway.js +37 -0
  161. package/lite/goaway.js.map +1 -0
  162. package/lite/group.d.ts +18 -0
  163. package/lite/group.d.ts.map +1 -0
  164. package/lite/group.js +46 -0
  165. package/lite/group.js.map +1 -0
  166. package/lite/index.d.ts +11 -0
  167. package/lite/index.d.ts.map +1 -0
  168. package/lite/index.js +12 -0
  169. package/lite/index.js.map +1 -0
  170. package/lite/message.d.ts +5 -0
  171. package/lite/message.d.ts.map +1 -0
  172. package/lite/message.js +51 -0
  173. package/lite/message.js.map +1 -0
  174. package/lite/origin.d.ts +25 -0
  175. package/lite/origin.d.ts.map +1 -0
  176. package/lite/origin.js +35 -0
  177. package/lite/origin.js.map +1 -0
  178. package/lite/probe.d.ts +12 -0
  179. package/lite/probe.d.ts.map +1 -0
  180. package/lite/probe.js +60 -0
  181. package/lite/probe.js.map +1 -0
  182. package/lite/publisher.d.ts +63 -0
  183. package/lite/publisher.d.ts.map +1 -0
  184. package/lite/publisher.js +350 -0
  185. package/lite/publisher.js.map +1 -0
  186. package/lite/session.d.ts +36 -0
  187. package/lite/session.d.ts.map +1 -0
  188. package/lite/session.js +130 -0
  189. package/lite/session.js.map +1 -0
  190. package/lite/setup.d.ts +18 -0
  191. package/lite/setup.d.ts.map +1 -0
  192. package/lite/setup.js +55 -0
  193. package/lite/setup.js.map +1 -0
  194. package/lite/stream.d.ts +23 -0
  195. package/lite/stream.d.ts.map +1 -0
  196. package/lite/stream.js +20 -0
  197. package/lite/stream.js.map +1 -0
  198. package/lite/subscribe.d.ts +111 -0
  199. package/lite/subscribe.d.ts.map +1 -0
  200. package/lite/subscribe.js +341 -0
  201. package/lite/subscribe.js.map +1 -0
  202. package/lite/subscriber.d.ts +80 -0
  203. package/lite/subscriber.d.ts.map +1 -0
  204. package/lite/subscriber.js +316 -0
  205. package/lite/subscriber.js.map +1 -0
  206. package/lite/track.d.ts +38 -0
  207. package/lite/track.d.ts.map +1 -0
  208. package/lite/track.js +81 -0
  209. package/lite/track.js.map +1 -0
  210. package/lite/track.test.d.ts +2 -0
  211. package/lite/track.test.d.ts.map +1 -0
  212. package/lite/track.test.js +53 -0
  213. package/lite/track.test.js.map +1 -0
  214. package/lite/version.d.ts +16 -0
  215. package/lite/version.d.ts.map +1 -0
  216. package/lite/version.js +41 -0
  217. package/lite/version.js.map +1 -0
  218. package/mock.d.ts +16 -0
  219. package/mock.d.ts.map +1 -0
  220. package/mock.js +154 -0
  221. package/mock.js.map +1 -0
  222. package/package.json +27 -0
  223. package/path.d.ts +111 -0
  224. package/path.d.ts.map +1 -0
  225. package/path.js +133 -0
  226. package/path.js.map +1 -0
  227. package/path.test.d.ts +2 -0
  228. package/path.test.d.ts.map +1 -0
  229. package/path.test.js +169 -0
  230. package/path.test.js.map +1 -0
  231. package/stream.d.ts +57 -0
  232. package/stream.d.ts.map +1 -0
  233. package/stream.js +326 -0
  234. package/stream.js.map +1 -0
  235. package/stream.test.d.ts +2 -0
  236. package/stream.test.d.ts.map +1 -0
  237. package/stream.test.js +210 -0
  238. package/stream.test.js.map +1 -0
  239. package/time.d.ts +109 -0
  240. package/time.d.ts.map +1 -0
  241. package/time.js +94 -0
  242. package/time.js.map +1 -0
  243. package/track.d.ts +85 -0
  244. package/track.d.ts.map +1 -0
  245. package/track.js +227 -0
  246. package/track.js.map +1 -0
  247. package/track.test.d.ts +2 -0
  248. package/track.test.d.ts.map +1 -0
  249. package/track.test.js +56 -0
  250. package/track.test.js.map +1 -0
  251. package/util/error.d.ts +3 -0
  252. package/util/error.d.ts.map +1 -0
  253. package/util/error.js +9 -0
  254. package/util/error.js.map +1 -0
  255. package/util/hex.d.ts +3 -0
  256. package/util/hex.d.ts.map +1 -0
  257. package/util/hex.js +16 -0
  258. package/util/hex.js.map +1 -0
  259. package/util/index.d.ts +2 -0
  260. package/util/index.d.ts.map +1 -0
  261. package/util/index.js +3 -0
  262. package/util/index.js.map +1 -0
  263. package/varint.d.ts +36 -0
  264. package/varint.d.ts.map +1 -0
  265. package/varint.js +288 -0
  266. package/varint.js.map +1 -0
  267. package/varint.test.d.ts +2 -0
  268. package/varint.test.d.ts.map +1 -0
  269. package/varint.test.js +106 -0
  270. package/varint.test.js.map +1 -0
  271. package/zod.d.ts +13 -0
  272. package/zod.d.ts.map +1 -0
  273. package/zod.js +19 -0
  274. package/zod.js.map +1 -0
@@ -0,0 +1,626 @@
1
+ /* @ts-self-types="./adapter.d.ts" */
2
+ import { Mutex } from "async-mutex";
3
+ import { Reader, Stream } from "../stream.js";
4
+ import * as Varint from "../varint.js";
5
+ import * as Namespace from "./namespace.js";
6
+ import { Version } from "./version.js";
7
+ /**
8
+ * v17 native session — thin wrapper around WebTransport.
9
+ * Each request gets its own real bidi stream; no control stream multiplexing.
10
+ */
11
+ export class NativeSession {
12
+ #quic;
13
+ #requestId = 0n;
14
+ version;
15
+ constructor(quic, version) {
16
+ this.#quic = quic;
17
+ this.version = version;
18
+ }
19
+ async openBi() {
20
+ return Stream.open(this.#quic, this.version);
21
+ }
22
+ async acceptBi() {
23
+ return Stream.accept(this.#quic, this.version);
24
+ }
25
+ async nextRequestId() {
26
+ const id = this.#requestId;
27
+ this.#requestId += 2n;
28
+ return id;
29
+ }
30
+ }
31
+ // Route classification for control stream messages.
32
+ const Route = {
33
+ NewRequest: 0, // Create virtual bidi stream, push initial message
34
+ Response: 1, // Push message to existing stream (keep open)
35
+ ErrorResponse: 2, // Push message to existing stream, then close
36
+ CloseStream: 3, // Close stream recv (no bytes pushed)
37
+ FollowUp: 4, // Push follow-up message to existing stream
38
+ MaxRequestId: 5, // Update flow control
39
+ Ignore: 6, // Connection-level, no routing
40
+ GoAway: 7, // Terminal
41
+ };
42
+ /**
43
+ * Converts v14-v16 control stream multiplexing into virtual bidi streams.
44
+ *
45
+ * Reads control messages, classifies them, and routes to virtual Stream objects.
46
+ * Each request/response pair gets its own virtual Stream, making all versions
47
+ * look like v17's stream-per-request model.
48
+ */
49
+ export class ControlStreamAdapter {
50
+ // WebTransport session (for opening real bidi streams in v16)
51
+ #quic;
52
+ // Control stream
53
+ #reader;
54
+ #writer;
55
+ #writeMutex = new Mutex();
56
+ version;
57
+ // Virtual streams keyed by requestId
58
+ #streams = new Map();
59
+ // Namespace → requestId reverse lookup (v14/v15 namespace-keyed messages)
60
+ #namespaces = new Map();
61
+ // requestId → namespace reverse lookup (for cleanup in #closeStream)
62
+ #namespacesByRequestId = new Map();
63
+ // SubscribeNamespace requestIds — for routing 0x08/0x0E entries that lack requestId (v14/v15)
64
+ #subscribeNamespaces = new Set();
65
+ // Incoming stream queue (for acceptBi)
66
+ #incomingQueue = [];
67
+ #incomingWaiters = [];
68
+ // Request ID flow control
69
+ #requestId = 0n;
70
+ #maxRequestId;
71
+ #maxRequestIdResolves = [];
72
+ #closed = false;
73
+ constructor(quic, controlStream, version, maxRequestId) {
74
+ this.#quic = quic;
75
+ this.#reader = controlStream.reader;
76
+ this.#reader.version = version;
77
+ this.#writer = controlStream.writer;
78
+ this.#writer.version = version;
79
+ this.version = version;
80
+ this.#maxRequestId = maxRequestId;
81
+ }
82
+ /**
83
+ * Accept the next incoming virtual bidi stream.
84
+ * Blocks until a new request arrives on the control stream.
85
+ */
86
+ async acceptBi() {
87
+ if (this.#closed)
88
+ return undefined;
89
+ const queued = this.#incomingQueue.shift();
90
+ if (queued)
91
+ return queued;
92
+ return new Promise((resolve) => {
93
+ this.#incomingWaiters.push(resolve);
94
+ });
95
+ }
96
+ /**
97
+ * Open an outgoing virtual bidi stream.
98
+ * Buffers writes until the first full message is available, parses the
99
+ * requestId (and namespace for PublishNamespace), self-registers, then
100
+ * flushes. Subsequent writes go directly to the control stream.
101
+ */
102
+ openBi() {
103
+ let controller;
104
+ let registeredRequestId;
105
+ const readable = new ReadableStream({
106
+ start(c) {
107
+ controller = c;
108
+ },
109
+ cancel: () => {
110
+ if (registeredRequestId !== undefined) {
111
+ this.#streams.delete(registeredRequestId);
112
+ }
113
+ },
114
+ });
115
+ let buffer = new Uint8Array(0);
116
+ let registered = false;
117
+ const sendWritable = new WritableStream({
118
+ write: async (chunk) => {
119
+ // Always accumulate bytes and flush only complete messages
120
+ // to prevent interleaving of partial messages from concurrent virtual streams.
121
+ const newBuf = new Uint8Array(buffer.length + chunk.length);
122
+ newBuf.set(buffer);
123
+ newBuf.set(chunk, buffer.length);
124
+ buffer = newBuf;
125
+ // Try to flush complete messages from the buffer
126
+ for (;;) {
127
+ const boundary = this.#messageSize(buffer);
128
+ if (boundary === undefined)
129
+ break;
130
+ const toFlush = buffer.subarray(0, boundary);
131
+ buffer = buffer.subarray(boundary);
132
+ if (!registered) {
133
+ // First message: extract requestId and register before flushing
134
+ const parsed = this.#tryParseOutgoing(toFlush);
135
+ if (parsed) {
136
+ registeredRequestId = parsed.requestId;
137
+ this.#streams.set(parsed.requestId, { controller });
138
+ registered = true;
139
+ }
140
+ }
141
+ await this.#writeMutex.runExclusive(() => this.#writer.write(toFlush));
142
+ }
143
+ },
144
+ });
145
+ const stream = new Stream({ readable, writable: sendWritable });
146
+ stream.reader.version = this.version;
147
+ stream.writer.version = this.version;
148
+ return stream;
149
+ }
150
+ /**
151
+ * Open a real WebTransport bidi stream (for v16 SubscribeNamespace).
152
+ */
153
+ async openNativeBi() {
154
+ return Stream.open(this.#quic, this.version);
155
+ }
156
+ /**
157
+ * Allocate the next request ID, blocking if flow control limit reached.
158
+ */
159
+ async nextRequestId() {
160
+ for (;;) {
161
+ if (this.#closed)
162
+ return undefined;
163
+ const id = this.#requestId;
164
+ if (id < this.#maxRequestId) {
165
+ this.#requestId += 2n;
166
+ return id;
167
+ }
168
+ await new Promise((resolve) => {
169
+ this.#maxRequestIdResolves.push(resolve);
170
+ });
171
+ }
172
+ }
173
+ /**
174
+ * Main run loop — reads control stream messages and routes to virtual streams.
175
+ * Must be called after construction. Runs until the control stream closes.
176
+ */
177
+ async run() {
178
+ try {
179
+ // v16: also accept real bidi streams (for SubscribeNamespace)
180
+ if (this.version === Version.DRAFT_16) {
181
+ void this.#acceptNativeBidis();
182
+ }
183
+ for (;;) {
184
+ const done = await this.#reader.done();
185
+ if (done)
186
+ break;
187
+ const typeId = await this.#reader.u53();
188
+ const size = await this.#reader.u16();
189
+ const body = await this.#reader.read(size);
190
+ const classified = await this.#classify(typeId, body);
191
+ if (classified.route === Route.GoAway) {
192
+ console.warn("received GOAWAY on control stream");
193
+ return;
194
+ }
195
+ const { route, requestId } = classified;
196
+ switch (route) {
197
+ case Route.NewRequest:
198
+ this.#newRequest(typeId, size, body, requestId);
199
+ break;
200
+ case Route.Response:
201
+ this.#pushMessage(requestId, typeId, size, body);
202
+ break;
203
+ case Route.ErrorResponse:
204
+ this.#pushMessage(requestId, typeId, size, body);
205
+ this.#closeStream(requestId);
206
+ break;
207
+ case Route.CloseStream:
208
+ this.#closeStream(requestId);
209
+ break;
210
+ case Route.FollowUp:
211
+ this.#pushMessage(requestId, typeId, size, body);
212
+ break;
213
+ case Route.MaxRequestId:
214
+ this.#maxRequestId = requestId;
215
+ for (const resolve of this.#maxRequestIdResolves)
216
+ resolve();
217
+ this.#maxRequestIdResolves = [];
218
+ break;
219
+ }
220
+ }
221
+ }
222
+ finally {
223
+ this.close();
224
+ }
225
+ }
226
+ /** Accept real WebTransport bidi streams and queue them for acceptBi (v16). */
227
+ async #acceptNativeBidis() {
228
+ try {
229
+ for (;;) {
230
+ const stream = await Stream.accept(this.#quic, this.version);
231
+ if (!stream)
232
+ break;
233
+ const waiter = this.#incomingWaiters.shift();
234
+ if (waiter) {
235
+ waiter(stream);
236
+ }
237
+ else {
238
+ this.#incomingQueue.push(stream);
239
+ }
240
+ }
241
+ }
242
+ catch {
243
+ // Session closed
244
+ }
245
+ }
246
+ #newRequest(typeId, size, body, requestId) {
247
+ let controller;
248
+ const readable = new ReadableStream({
249
+ start(c) {
250
+ controller = c;
251
+ },
252
+ cancel: () => {
253
+ this.#streams.delete(requestId);
254
+ },
255
+ });
256
+ const sendWritable = this.#createSendWritable();
257
+ const stream = new Stream({ readable, writable: sendWritable });
258
+ stream.reader.version = this.version;
259
+ stream.writer.version = this.version;
260
+ this.#streams.set(requestId, { controller });
261
+ // Push initial message bytes so the dispatcher can read typeId + decode
262
+ controller.enqueue(this.#encodeRaw(typeId, size, body));
263
+ // Queue for acceptBi
264
+ const waiter = this.#incomingWaiters.shift();
265
+ if (waiter) {
266
+ waiter(stream);
267
+ }
268
+ else {
269
+ this.#incomingQueue.push(stream);
270
+ }
271
+ }
272
+ #pushMessage(requestId, typeId, size, body) {
273
+ const entry = this.#streams.get(requestId);
274
+ if (!entry) {
275
+ console.warn(`adapter: no stream for requestId=${requestId} typeId=0x${typeId.toString(16)}`);
276
+ return;
277
+ }
278
+ try {
279
+ entry.controller.enqueue(this.#encodeRaw(typeId, size, body));
280
+ }
281
+ catch {
282
+ // Stream already closed
283
+ }
284
+ }
285
+ #closeStream(requestId) {
286
+ const entry = this.#streams.get(requestId);
287
+ if (!entry)
288
+ return;
289
+ console.debug(`adapter: closing stream requestId=${requestId}`);
290
+ this.#streams.delete(requestId);
291
+ this.#subscribeNamespaces.delete(requestId);
292
+ const namespace = this.#namespacesByRequestId.get(requestId);
293
+ if (namespace !== undefined) {
294
+ this.#namespaces.delete(namespace);
295
+ this.#namespacesByRequestId.delete(requestId);
296
+ }
297
+ try {
298
+ entry.controller.close();
299
+ }
300
+ catch {
301
+ // Already closed
302
+ }
303
+ }
304
+ /**
305
+ * Returns the total byte size of the first complete message in buffer,
306
+ * or undefined if the buffer doesn't contain a complete message yet.
307
+ * Message format: [typeId varint][size u16 BE][body of `size` bytes]
308
+ */
309
+ #messageSize(buffer) {
310
+ if (buffer.length === 0)
311
+ return undefined;
312
+ const typeSize = 1 << ((buffer[0] & 0xc0) >> 6);
313
+ if (buffer.length < typeSize)
314
+ return undefined;
315
+ const [, afterType] = Varint.decode(buffer);
316
+ if (afterType.length < 2)
317
+ return undefined;
318
+ const size = (afterType[0] << 8) | afterType[1];
319
+ const totalSize = buffer.length - afterType.length + 2 + size;
320
+ if (buffer.length < totalSize)
321
+ return undefined;
322
+ return totalSize;
323
+ }
324
+ /**
325
+ * Try to parse the first outgoing message from accumulated bytes.
326
+ * Returns the requestId if enough data is available, undefined otherwise.
327
+ */
328
+ #tryParseOutgoing(buffer) {
329
+ if (buffer.length === 0)
330
+ return undefined;
331
+ // Check typeId varint size before decoding
332
+ const typeSize = 1 << ((buffer[0] & 0xc0) >> 6);
333
+ if (buffer.length < typeSize)
334
+ return undefined;
335
+ const [typeId, afterType] = Varint.decode(buffer);
336
+ // Need 2 bytes for u16 size
337
+ if (afterType.length < 2)
338
+ return undefined;
339
+ const size = (afterType[0] << 8) | afterType[1];
340
+ const bodyStart = afterType.subarray(2);
341
+ // Need full body
342
+ if (bodyStart.length < size)
343
+ return undefined;
344
+ const body = bodyStart.subarray(0, size);
345
+ // Decode requestId from body (QUIC varint)
346
+ const [reqId] = Varint.decode(body);
347
+ const requestId = BigInt(reqId);
348
+ // PublishNamespace (0x06): also parse namespace for v14/v15 reverse lookup
349
+ if (typeId === 0x06) {
350
+ try {
351
+ const [, afterReqId] = Varint.decode(body);
352
+ this.#parseAndRegisterNamespace(afterReqId, requestId);
353
+ }
354
+ catch {
355
+ // Non-critical: only needed for v14/v15 PublishNamespaceDone/Cancel
356
+ }
357
+ }
358
+ // SubscribeNamespace (0x11): register for follow-up routing
359
+ if (typeId === 0x11) {
360
+ this.#subscribeNamespaces.add(requestId);
361
+ }
362
+ return { requestId };
363
+ }
364
+ /**
365
+ * Parse a namespace from raw bytes and register it for reverse lookup.
366
+ */
367
+ #parseAndRegisterNamespace(buf, requestId) {
368
+ const decoder = new TextDecoder();
369
+ const [partCount, afterCount] = Varint.decode(buf);
370
+ let cursor = afterCount;
371
+ const parts = [];
372
+ for (let i = 0; i < partCount; i++) {
373
+ const [len, afterLen] = Varint.decode(cursor);
374
+ parts.push(decoder.decode(afterLen.subarray(0, len)));
375
+ cursor = afterLen.subarray(len);
376
+ }
377
+ const namespace = parts.join("/");
378
+ this.#namespaces.set(namespace, requestId);
379
+ this.#namespacesByRequestId.set(requestId, namespace);
380
+ }
381
+ /** Create a WritableStream that buffers and writes complete messages to the control stream under mutex. */
382
+ #createSendWritable() {
383
+ let buffer = new Uint8Array(0);
384
+ return new WritableStream({
385
+ write: async (chunk) => {
386
+ const newBuf = new Uint8Array(buffer.length + chunk.length);
387
+ newBuf.set(buffer);
388
+ newBuf.set(chunk, buffer.length);
389
+ buffer = newBuf;
390
+ for (;;) {
391
+ const boundary = this.#messageSize(buffer);
392
+ if (boundary === undefined)
393
+ break;
394
+ const toFlush = buffer.subarray(0, boundary);
395
+ buffer = buffer.subarray(boundary);
396
+ await this.#writeMutex.runExclusive(() => this.#writer.write(toFlush));
397
+ }
398
+ },
399
+ });
400
+ }
401
+ /** Encode raw message bytes: [typeId varint][size u16 BE][body] */
402
+ #encodeRaw(typeId, size, body) {
403
+ // v14-v16 always use QUIC varint
404
+ const typeIdBytes = Varint.encodeTo(new ArrayBuffer(9), typeId);
405
+ const result = new Uint8Array(typeIdBytes.byteLength + 2 + body.byteLength);
406
+ result.set(typeIdBytes, 0);
407
+ const sizeView = new DataView(result.buffer, typeIdBytes.byteLength, 2);
408
+ sizeView.setUint16(0, size);
409
+ result.set(body, typeIdBytes.byteLength + 2);
410
+ return result;
411
+ }
412
+ /**
413
+ * Classify a control message and extract its requestId for routing.
414
+ */
415
+ async #classify(typeId, body) {
416
+ const readRequestId = async () => {
417
+ const r = new Reader(undefined, body, this.version);
418
+ return await r.u62();
419
+ };
420
+ const readNamespaceRequestId = async () => {
421
+ const r = new Reader(undefined, body, this.version);
422
+ const namespace = await Namespace.decode(r);
423
+ const requestId = this.#namespaces.get(namespace);
424
+ if (requestId === undefined)
425
+ throw new Error(`unknown namespace: ${namespace}`);
426
+ this.#namespaces.delete(namespace);
427
+ return requestId;
428
+ };
429
+ switch (typeId) {
430
+ // === FollowUp: route to existing stream ===
431
+ case 0x02: {
432
+ // SubscribeUpdate / REQUEST_UPDATE
433
+ const requestId = await readRequestId();
434
+ return { route: Route.FollowUp, requestId };
435
+ }
436
+ // === NewRequest: create virtual stream ===
437
+ case 0x03: // Subscribe
438
+ case 0x16: // Fetch
439
+ case 0x1d: // Publish
440
+ case 0x0d: {
441
+ // TrackStatusRequest
442
+ const requestId = await readRequestId();
443
+ return { route: Route.NewRequest, requestId };
444
+ }
445
+ case 0x06: {
446
+ // PublishNamespace — also store namespace for v14/v15 reverse lookup
447
+ const r = new Reader(undefined, body, this.version);
448
+ const requestId = await r.u62();
449
+ const namespace = await Namespace.decode(r);
450
+ this.#namespaces.set(namespace, requestId);
451
+ this.#namespacesByRequestId.set(requestId, namespace);
452
+ return { route: Route.NewRequest, requestId };
453
+ }
454
+ case 0x11: {
455
+ // SubscribeNamespace (v14/v15 only on control stream)
456
+ if (this.version !== Version.DRAFT_14 && this.version !== Version.DRAFT_15) {
457
+ throw new Error("unexpected SubscribeNamespace on control stream");
458
+ }
459
+ const requestId = await readRequestId();
460
+ return { route: Route.NewRequest, requestId };
461
+ }
462
+ // === Response: push bytes, keep stream open ===
463
+ case 0x04: {
464
+ // SubscribeOk
465
+ const requestId = await readRequestId();
466
+ return { route: Route.Response, requestId };
467
+ }
468
+ case 0x18: {
469
+ // FetchOk
470
+ const requestId = await readRequestId();
471
+ return { route: Route.Response, requestId };
472
+ }
473
+ case 0x1e: {
474
+ // PublishOk
475
+ const requestId = await readRequestId();
476
+ return { route: Route.Response, requestId };
477
+ }
478
+ case 0x07: {
479
+ // v14: PublishNamespaceOk, v15+: RequestOk
480
+ const requestId = await readRequestId();
481
+ return { route: Route.Response, requestId };
482
+ }
483
+ case 0x12: {
484
+ // SubscribeNamespaceOk (v14 only)
485
+ if (this.version !== Version.DRAFT_14)
486
+ throw new Error("unexpected SubscribeNamespaceOk");
487
+ const requestId = await readRequestId();
488
+ return { route: Route.Response, requestId };
489
+ }
490
+ // === ErrorResponse: push bytes + close ===
491
+ case 0x05: {
492
+ // SubscribeError (v14) / RequestError (v15+)
493
+ const requestId = await readRequestId();
494
+ return { route: Route.ErrorResponse, requestId };
495
+ }
496
+ case 0x19: {
497
+ // FetchError (v14 only)
498
+ if (this.version !== Version.DRAFT_14)
499
+ throw new Error("unexpected FetchError");
500
+ const requestId = await readRequestId();
501
+ return { route: Route.ErrorResponse, requestId };
502
+ }
503
+ case 0x1f: {
504
+ // PublishError (v14 only)
505
+ if (this.version !== Version.DRAFT_14)
506
+ throw new Error("unexpected PublishError");
507
+ const requestId = await readRequestId();
508
+ return { route: Route.ErrorResponse, requestId };
509
+ }
510
+ case 0x08: {
511
+ if (this.version === Version.DRAFT_14) {
512
+ // PublishNamespaceError
513
+ const requestId = await readRequestId();
514
+ return { route: Route.ErrorResponse, requestId };
515
+ }
516
+ // v15: Namespace entry (no requestId) — route to SubscribeNamespace stream
517
+ const subNs08 = this.#subscribeNamespaces.values().next().value;
518
+ if (subNs08 === undefined)
519
+ throw new Error("unexpected message 0x08: no SubscribeNamespace stream");
520
+ return { route: Route.FollowUp, requestId: subNs08 };
521
+ }
522
+ case 0x0e: {
523
+ // v15: NamespaceDone entry (no requestId) — route to SubscribeNamespace stream
524
+ const subNs0e = this.#subscribeNamespaces.values().next().value;
525
+ if (subNs0e === undefined)
526
+ throw new Error("unexpected message 0x0e: no SubscribeNamespace stream");
527
+ return { route: Route.FollowUp, requestId: subNs0e };
528
+ }
529
+ case 0x13: {
530
+ // SubscribeNamespaceError (v14 only)
531
+ if (this.version !== Version.DRAFT_14)
532
+ throw new Error("unexpected SubscribeNamespaceError");
533
+ const requestId = await readRequestId();
534
+ return { route: Route.ErrorResponse, requestId };
535
+ }
536
+ // === CloseStream: close recv (no bytes pushed) ===
537
+ case 0x0a: {
538
+ // Unsubscribe
539
+ const requestId = await readRequestId();
540
+ return { route: Route.CloseStream, requestId };
541
+ }
542
+ case 0x0b: {
543
+ // PublishDone
544
+ const requestId = await readRequestId();
545
+ return { route: Route.CloseStream, requestId };
546
+ }
547
+ case 0x17: {
548
+ // FetchCancel
549
+ const requestId = await readRequestId();
550
+ return { route: Route.CloseStream, requestId };
551
+ }
552
+ case 0x09: {
553
+ // PublishNamespaceDone: v16 uses requestId, v14/v15 uses namespace
554
+ if (this.version === Version.DRAFT_16) {
555
+ const requestId = await readRequestId();
556
+ return { route: Route.CloseStream, requestId };
557
+ }
558
+ const requestId = await readNamespaceRequestId();
559
+ return { route: Route.CloseStream, requestId };
560
+ }
561
+ case 0x0c: {
562
+ // PublishNamespaceCancel: v16 uses requestId, v14/v15 uses namespace
563
+ if (this.version === Version.DRAFT_16) {
564
+ const requestId = await readRequestId();
565
+ return { route: Route.CloseStream, requestId };
566
+ }
567
+ const requestId = await readNamespaceRequestId();
568
+ return { route: Route.CloseStream, requestId };
569
+ }
570
+ case 0x14: {
571
+ // UnsubscribeNamespace (v14/v15 only)
572
+ if (this.version !== Version.DRAFT_14 && this.version !== Version.DRAFT_15) {
573
+ throw new Error("unexpected UnsubscribeNamespace");
574
+ }
575
+ const requestId = await readRequestId();
576
+ return { route: Route.CloseStream, requestId };
577
+ }
578
+ // === Utility ===
579
+ case 0x15: {
580
+ // MaxRequestId
581
+ const requestId = await readRequestId();
582
+ return { route: Route.MaxRequestId, requestId };
583
+ }
584
+ case 0x1a: {
585
+ // RequestsBlocked — connection-level, consume and ignore
586
+ await readRequestId();
587
+ return { route: Route.Ignore, requestId: 0n };
588
+ }
589
+ // === Terminal ===
590
+ case 0x10: // GoAway
591
+ return { route: Route.GoAway };
592
+ default:
593
+ throw new Error(`unknown control message type: 0x${typeId.toString(16)}`);
594
+ }
595
+ }
596
+ close() {
597
+ if (this.#closed)
598
+ return;
599
+ this.#closed = true;
600
+ console.debug("adapter: close() called");
601
+ // Close all virtual streams
602
+ for (const entry of this.#streams.values()) {
603
+ try {
604
+ entry.controller.close();
605
+ }
606
+ catch {
607
+ // Already closed
608
+ }
609
+ }
610
+ this.#streams.clear();
611
+ // Resolve any waiting acceptBi callers
612
+ for (const waiter of this.#incomingWaiters) {
613
+ waiter(undefined);
614
+ }
615
+ this.#incomingWaiters = [];
616
+ // Clear namespace mappings
617
+ this.#namespaces.clear();
618
+ this.#namespacesByRequestId.clear();
619
+ this.#subscribeNamespaces.clear();
620
+ // Unblock maxRequestId waiters
621
+ for (const resolve of this.#maxRequestIdResolves)
622
+ resolve();
623
+ this.#maxRequestIdResolves = [];
624
+ }
625
+ }
626
+ //# sourceMappingURL=adapter.js.map