@norskvideo/moq-net 0.1.8 → 0.2.1

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 (286) hide show
  1. package/README.md +2 -2
  2. package/announce.d.ts +7 -0
  3. package/announce.d.ts.map +1 -0
  4. package/announce.js +8 -0
  5. package/announce.js.map +1 -0
  6. package/announced.d.ts +49 -91
  7. package/announced.d.ts.map +1 -1
  8. package/announced.js +21 -156
  9. package/announced.js.map +1 -1
  10. package/bandwidth.d.ts +163 -0
  11. package/bandwidth.d.ts.map +1 -0
  12. package/bandwidth.js +304 -0
  13. package/bandwidth.js.map +1 -0
  14. package/bandwidth_api.d.ts +7 -0
  15. package/bandwidth_api.d.ts.map +1 -0
  16. package/bandwidth_api.js +8 -0
  17. package/bandwidth_api.js.map +1 -0
  18. package/broadcast.d.ts +44 -35
  19. package/broadcast.d.ts.map +1 -1
  20. package/broadcast.js +104 -60
  21. package/broadcast.js.map +1 -1
  22. package/connection/accept.d.ts +16 -1
  23. package/connection/accept.d.ts.map +1 -1
  24. package/connection/accept.js +52 -28
  25. package/connection/accept.js.map +1 -1
  26. package/connection/browser.d.ts.map +1 -1
  27. package/connection/browser.js +9 -7
  28. package/connection/browser.js.map +1 -1
  29. package/connection/connect.d.ts +30 -6
  30. package/connection/connect.d.ts.map +1 -1
  31. package/connection/connect.js +110 -54
  32. package/connection/connect.js.map +1 -1
  33. package/connection/established.d.ts +17 -21
  34. package/connection/established.d.ts.map +1 -1
  35. package/connection/established.js.map +1 -1
  36. package/connection/forward.d.ts +2 -0
  37. package/connection/forward.d.ts.map +1 -0
  38. package/connection/forward.js +173 -0
  39. package/connection/forward.js.map +1 -0
  40. package/connection/handshake.d.ts +1 -0
  41. package/connection/handshake.d.ts.map +1 -1
  42. package/connection/handshake.js +5 -2
  43. package/connection/handshake.js.map +1 -1
  44. package/connection/index.d.ts +5 -5
  45. package/connection/index.d.ts.map +1 -1
  46. package/connection/index.js +4 -5
  47. package/connection/index.js.map +1 -1
  48. package/connection/pool.d.ts +186 -0
  49. package/connection/pool.d.ts.map +1 -0
  50. package/connection/pool.js +361 -0
  51. package/connection/pool.js.map +1 -0
  52. package/connection/reload.d.ts +14 -93
  53. package/connection/reload.d.ts.map +1 -1
  54. package/connection/reload.js +217 -81
  55. package/connection/reload.js.map +1 -1
  56. package/connection/stats.d.ts +3 -26
  57. package/connection/stats.d.ts.map +1 -1
  58. package/connection/stats.js.map +1 -1
  59. package/connection/transport.d.ts +0 -7
  60. package/connection/transport.d.ts.map +1 -1
  61. package/consume.d.ts +1 -43
  62. package/consume.d.ts.map +1 -1
  63. package/consume.js +1 -1
  64. package/consume.js.map +1 -1
  65. package/error.d.ts +180 -29
  66. package/error.d.ts.map +1 -1
  67. package/error.js +331 -16
  68. package/error.js.map +1 -1
  69. package/errors.d.ts +7 -0
  70. package/errors.d.ts.map +1 -0
  71. package/errors.js +8 -0
  72. package/errors.js.map +1 -0
  73. package/group.d.ts +9 -43
  74. package/group.d.ts.map +1 -1
  75. package/group.js +284 -69
  76. package/group.js.map +1 -1
  77. package/hop.d.ts +115 -0
  78. package/hop.d.ts.map +1 -0
  79. package/hop.js +119 -0
  80. package/hop.js.map +1 -0
  81. package/ietf/adapter.d.ts +5 -1
  82. package/ietf/adapter.d.ts.map +1 -1
  83. package/ietf/adapter.js +105 -60
  84. package/ietf/adapter.js.map +1 -1
  85. package/ietf/aliases.d.ts +1 -78
  86. package/ietf/aliases.d.ts.map +1 -1
  87. package/ietf/cluster.d.ts +9 -123
  88. package/ietf/cluster.d.ts.map +1 -1
  89. package/ietf/cluster.js +84 -44
  90. package/ietf/cluster.js.map +1 -1
  91. package/ietf/connection.d.ts +6 -72
  92. package/ietf/connection.d.ts.map +1 -1
  93. package/ietf/connection.js +62 -56
  94. package/ietf/connection.js.map +1 -1
  95. package/ietf/error.d.ts +11 -0
  96. package/ietf/error.d.ts.map +1 -0
  97. package/ietf/error.js +193 -0
  98. package/ietf/error.js.map +1 -0
  99. package/ietf/fetch.d.ts +7 -20
  100. package/ietf/fetch.d.ts.map +1 -1
  101. package/ietf/fetch.js +52 -22
  102. package/ietf/fetch.js.map +1 -1
  103. package/ietf/filter.d.ts +2 -0
  104. package/ietf/filter.d.ts.map +1 -1
  105. package/ietf/filter.js +10 -1
  106. package/ietf/filter.js.map +1 -1
  107. package/ietf/goaway.d.ts.map +1 -1
  108. package/ietf/goaway.js +22 -5
  109. package/ietf/goaway.js.map +1 -1
  110. package/ietf/hidden.d.ts +2 -0
  111. package/ietf/hidden.d.ts.map +1 -0
  112. package/ietf/hidden.js +30 -0
  113. package/ietf/hidden.js.map +1 -0
  114. package/ietf/index.d.ts +2 -0
  115. package/ietf/index.d.ts.map +1 -1
  116. package/ietf/index.js +2 -0
  117. package/ietf/index.js.map +1 -1
  118. package/ietf/object.d.ts +15 -8
  119. package/ietf/object.d.ts.map +1 -1
  120. package/ietf/object.js +51 -36
  121. package/ietf/object.js.map +1 -1
  122. package/ietf/parameters.d.ts +14 -2
  123. package/ietf/parameters.d.ts.map +1 -1
  124. package/ietf/parameters.js +97 -29
  125. package/ietf/parameters.js.map +1 -1
  126. package/ietf/properties.d.ts +1 -0
  127. package/ietf/properties.d.ts.map +1 -1
  128. package/ietf/properties.js +14 -0
  129. package/ietf/properties.js.map +1 -1
  130. package/ietf/publish.d.ts +19 -2
  131. package/ietf/publish.d.ts.map +1 -1
  132. package/ietf/publish.js +40 -6
  133. package/ietf/publish.js.map +1 -1
  134. package/ietf/publish_namespace.d.ts +25 -0
  135. package/ietf/publish_namespace.d.ts.map +1 -1
  136. package/ietf/publish_namespace.js +63 -0
  137. package/ietf/publish_namespace.js.map +1 -1
  138. package/ietf/publisher.d.ts +1 -82
  139. package/ietf/publisher.d.ts.map +1 -1
  140. package/ietf/publisher.js +483 -235
  141. package/ietf/publisher.js.map +1 -1
  142. package/ietf/solicit.d.ts +1 -40
  143. package/ietf/solicit.d.ts.map +1 -1
  144. package/ietf/subscribe.d.ts +8 -6
  145. package/ietf/subscribe.d.ts.map +1 -1
  146. package/ietf/subscribe.js +33 -27
  147. package/ietf/subscribe.js.map +1 -1
  148. package/ietf/subscribe_namespace.d.ts +8 -2
  149. package/ietf/subscribe_namespace.d.ts.map +1 -1
  150. package/ietf/subscribe_namespace.js +18 -8
  151. package/ietf/subscribe_namespace.js.map +1 -1
  152. package/ietf/subscriber.d.ts +1 -65
  153. package/ietf/subscriber.d.ts.map +1 -1
  154. package/ietf/subscriber.js +337 -123
  155. package/ietf/subscriber.js.map +1 -1
  156. package/ietf/token.d.ts +2 -0
  157. package/ietf/token.d.ts.map +1 -0
  158. package/ietf/token.js +99 -0
  159. package/ietf/token.js.map +1 -0
  160. package/ietf/track.d.ts +4 -0
  161. package/ietf/track.d.ts.map +1 -1
  162. package/ietf/track.js +6 -20
  163. package/ietf/track.js.map +1 -1
  164. package/ietf/version.d.ts +12 -1
  165. package/ietf/version.d.ts.map +1 -1
  166. package/ietf/version.js +13 -0
  167. package/ietf/version.js.map +1 -1
  168. package/index.d.ts +12 -7
  169. package/index.d.ts.map +1 -1
  170. package/index.js +10 -5
  171. package/index.js.map +1 -1
  172. package/internal.d.ts +115 -1
  173. package/internal.d.ts.map +1 -1
  174. package/internal.js +108 -0
  175. package/internal.js.map +1 -1
  176. package/lite/announce.d.ts +60 -10
  177. package/lite/announce.d.ts.map +1 -1
  178. package/lite/announce.js +176 -31
  179. package/lite/announce.js.map +1 -1
  180. package/lite/connection.d.ts +9 -59
  181. package/lite/connection.d.ts.map +1 -1
  182. package/lite/connection.js +34 -35
  183. package/lite/connection.js.map +1 -1
  184. package/lite/datagram.d.ts +3 -2
  185. package/lite/datagram.d.ts.map +1 -1
  186. package/lite/datagram.js +7 -8
  187. package/lite/datagram.js.map +1 -1
  188. package/lite/fetch.d.ts +15 -1
  189. package/lite/fetch.d.ts.map +1 -1
  190. package/lite/fetch.js +39 -7
  191. package/lite/fetch.js.map +1 -1
  192. package/lite/goaway.d.ts.map +1 -1
  193. package/lite/goaway.js +7 -1
  194. package/lite/goaway.js.map +1 -1
  195. package/lite/group.d.ts +30 -12
  196. package/lite/group.d.ts.map +1 -1
  197. package/lite/group.js +68 -26
  198. package/lite/group.js.map +1 -1
  199. package/lite/message.d.ts +2 -2
  200. package/lite/message.d.ts.map +1 -1
  201. package/lite/message.js +14 -5
  202. package/lite/message.js.map +1 -1
  203. package/lite/priority.d.ts +1 -61
  204. package/lite/priority.d.ts.map +1 -1
  205. package/lite/priority.js +4 -5
  206. package/lite/priority.js.map +1 -1
  207. package/lite/publisher.d.ts +1 -69
  208. package/lite/publisher.d.ts.map +1 -1
  209. package/lite/publisher.js +630 -262
  210. package/lite/publisher.js.map +1 -1
  211. package/lite/setup.d.ts +8 -8
  212. package/lite/setup.d.ts.map +1 -1
  213. package/lite/setup.js +33 -30
  214. package/lite/setup.js.map +1 -1
  215. package/lite/subscribe.d.ts +71 -17
  216. package/lite/subscribe.d.ts.map +1 -1
  217. package/lite/subscribe.js +205 -53
  218. package/lite/subscribe.js.map +1 -1
  219. package/lite/subscriber.d.ts +15 -56
  220. package/lite/subscriber.d.ts.map +1 -1
  221. package/lite/subscriber.js +451 -247
  222. package/lite/subscriber.js.map +1 -1
  223. package/lite/track.d.ts +4 -10
  224. package/lite/track.d.ts.map +1 -1
  225. package/lite/track.js +34 -29
  226. package/lite/track.js.map +1 -1
  227. package/lite/version.d.ts +42 -6
  228. package/lite/version.d.ts.map +1 -1
  229. package/lite/version.js +126 -10
  230. package/lite/version.js.map +1 -1
  231. package/origin.d.ts +256 -29
  232. package/origin.d.ts.map +1 -1
  233. package/origin.js +1427 -37
  234. package/origin.js.map +1 -1
  235. package/package.json +8 -3
  236. package/path.d.ts +25 -7
  237. package/path.d.ts.map +1 -1
  238. package/path.js +5 -3
  239. package/path.js.map +1 -1
  240. package/stream.d.ts +73 -14
  241. package/stream.d.ts.map +1 -1
  242. package/stream.js +372 -141
  243. package/stream.js.map +1 -1
  244. package/tail.d.ts +18 -0
  245. package/tail.d.ts.map +1 -0
  246. package/tail.js +167 -0
  247. package/tail.js.map +1 -0
  248. package/time.d.ts +15 -2
  249. package/time.d.ts.map +1 -1
  250. package/time.js +28 -9
  251. package/time.js.map +1 -1
  252. package/track.d.ts +211 -83
  253. package/track.d.ts.map +1 -1
  254. package/track.js +816 -205
  255. package/track.js.map +1 -1
  256. package/util/abort.d.ts +2 -0
  257. package/util/abort.d.ts.map +1 -0
  258. package/util/abort.js +20 -0
  259. package/util/abort.js.map +1 -0
  260. package/util/log.d.ts +5 -0
  261. package/util/log.d.ts.map +1 -0
  262. package/util/log.js +17 -0
  263. package/util/log.js.map +1 -0
  264. package/util/u64.d.ts +39 -0
  265. package/util/u64.d.ts.map +1 -0
  266. package/util/u64.js +83 -0
  267. package/util/u64.js.map +1 -0
  268. package/util/varint.d.ts +29 -0
  269. package/util/varint.d.ts.map +1 -0
  270. package/util/varint.js +198 -0
  271. package/util/varint.js.map +1 -0
  272. package/varint.d.ts +10 -6
  273. package/varint.d.ts.map +1 -1
  274. package/varint.js +40 -237
  275. package/varint.js.map +1 -1
  276. package/wire.d.ts +80 -0
  277. package/wire.d.ts.map +1 -0
  278. package/wire.js +32 -0
  279. package/wire.js.map +1 -0
  280. package/zod.d.ts +1 -1
  281. package/zod.d.ts.map +1 -1
  282. package/zod.js.map +1 -1
  283. package/mock.d.ts +0 -66
  284. package/mock.d.ts.map +0 -1
  285. package/mock.js +0 -243
  286. package/mock.js.map +0 -1
package/ietf/cluster.js CHANGED
@@ -1,22 +1,23 @@
1
1
  /* @ts-self-types="./cluster.d.ts" */
2
2
  /**
3
- * The MoQ Cluster extension (draft-lcurley-moq-cluster-00).
3
+ * The MoQ Cluster extension (draft-lcurley-moq-cluster-01).
4
4
  *
5
5
  * moq-transport carries no routing information, so a peer cannot tell whether an
6
6
  * advertisement already passed through us. This extension adds it:
7
7
  *
8
- * - each endpoint declares its own Hop ID via the RELAY_HOPS Setup Option, which is also
9
- * what negotiates the extension;
8
+ * - each endpoint declares its own Hop ID via the HOP_ID Setup Option, which is also what
9
+ * negotiates the extension;
10
10
  * - every advertisement carries the HOP_PATH it traversed and the accumulated ROUTE_COST
11
- * of that path, as Key-Value-Pair message parameters on PUBLISH_NAMESPACE and NAMESPACE.
11
+ * of that path, as Key-Value-Pair message parameters on PUBLISH_NAMESPACE and NAMESPACE,
12
+ * and a PUBLISH_NAMESPACE is repriced with the same parameters on a REQUEST_UPDATE.
12
13
  *
13
14
  * These are the semantics moq-lite carries natively on every announcement (see
14
15
  * `lite/announce.ts`); this module is the moq-transport binding, negotiated on draft-17+
15
16
  * only, where SETUP is a Key-Value-Pair block.
16
17
  *
17
- * We are a leaf, never a relay: the only path we can advertise is our own, so we stamp a
18
- * single-entry HOP_PATH and seed the cost at 0. What we get out of declaring is the other
19
- * direction. A relay that knows our Hop ID withholds the advertisements that already flowed
18
+ * A broadcast we produce carries a single-entry HOP_PATH and a cost of 0, and a route we
19
+ * forward carries its own hop chain and warm cost with our id appended. What we get out of
20
+ * declaring is also the other direction. A relay that knows our Hop ID withholds the advertisements that already flowed
20
21
  * through us, so publishing a broadcast no longer announces it back to us, which is what
21
22
  * moq-lite has always done with its own hop chain.
22
23
  *
@@ -24,7 +25,7 @@
24
25
  * @internal
25
26
  */
26
27
  import { ProtocolViolation, reason } from "../error.js";
27
- import { MAX_HOPS, OriginSchema, UNKNOWN_ORIGIN } from "../origin.js";
28
+ import { HopSchema, MAX_HOPS, UNKNOWN_HOP } from "../hop.js";
28
29
  import * as Varint from "../varint.js";
29
30
  import { Parameters, SetupOption } from "./parameters.js";
30
31
  import { Version } from "./version.js";
@@ -48,16 +49,11 @@ export function supported(version) {
48
49
  export function fromSetup(params, version) {
49
50
  if (!supported(version))
50
51
  return undefined;
51
- // RELAY_HOPS is odd, so its value is a length-prefixed byte string holding the sender's
52
- // Hop ID as a single varint. 0 is legal: the peer speaks the extension but withholds
53
- // its identity.
54
- const value = params.getBytes(SetupOption.RelayHops);
52
+ // 0 is legal: the peer speaks the extension but withholds its identity.
53
+ const value = params.getVarint(SetupOption.HopId);
55
54
  if (value === undefined)
56
55
  return undefined;
57
- const [origin, rest] = Varint.decodeLeadingOnes(value);
58
- if (rest.length !== 0)
59
- throw new Error("trailing bytes in RELAY_HOPS");
60
- return OriginSchema.parse(origin);
56
+ return HopSchema.parse(value);
61
57
  }
62
58
  /**
63
59
  * Declare our own Hop ID, which is also what negotiates the extension.
@@ -67,7 +63,7 @@ export function fromSetup(params, version) {
67
63
  export function intoSetup(params, self, version) {
68
64
  if (!supported(version))
69
65
  return;
70
- params.setBytes(SetupOption.RelayHops, Varint.encodeLeadingOnes(self));
66
+ params.setVarint(SetupOption.HopId, self);
71
67
  }
72
68
  /**
73
69
  * Whether the session negotiated the extension, which is what decides whether an
@@ -104,7 +100,7 @@ export function advertise(ids) {
104
100
  * @internal
105
101
  */
106
102
  export function loops(advert, self) {
107
- return self !== UNKNOWN_ORIGIN && advert.hops.includes(self);
103
+ return self !== UNKNOWN_HOP && advert.hops.includes(self);
108
104
  }
109
105
  /**
110
106
  * Write an advertisement's parameters, ready to encode onto the wire.
@@ -112,23 +108,40 @@ export function loops(advert, self) {
112
108
  * @internal
113
109
  */
114
110
  export function intoParams(advert) {
115
- validate(advert.hops);
116
111
  const params = new Parameters();
117
- // The entries fill the value, so they are written with no count of their own and framed
118
- // by the parameter's own length prefix.
119
- const hops = advert.hops.map((hop) => Varint.encodeLeadingOnes(hop));
120
- const value = new Uint8Array(hops.reduce((total, hop) => total + hop.length, 0));
121
- let offset = 0;
122
- for (const hop of hops) {
123
- value.set(hop, offset);
124
- offset += hop.length;
125
- }
126
- params.hopPath = value;
112
+ params.hopPath = encodeHops(advert.hops);
127
113
  // Absent means 0, so a free path sends nothing.
128
114
  if (advert.cost !== 0n)
129
115
  params.routeCost = advert.cost;
130
116
  return params;
131
117
  }
118
+ /**
119
+ * Write a REQUEST_UPDATE's parameters: only what changed, and a cost of 0 explicitly,
120
+ * since an omitted parameter keeps its value.
121
+ *
122
+ * @internal
123
+ */
124
+ export function updateIntoParams(update) {
125
+ const params = new Parameters();
126
+ if (update.hops !== undefined)
127
+ params.hopPath = encodeHops(update.hops);
128
+ if (update.cost !== undefined)
129
+ params.routeCost = update.cost;
130
+ return params;
131
+ }
132
+ /** Write a HOP_PATH value: the entries fill it with no count of their own, framed by the
133
+ * parameter's own length prefix. */
134
+ function encodeHops(hops) {
135
+ validate(hops);
136
+ const encoded = hops.map((hop) => Varint.encodeLeadingOnes(hop));
137
+ const value = new Uint8Array(encoded.reduce((total, hop) => total + hop.length, 0));
138
+ let offset = 0;
139
+ for (const hop of encoded) {
140
+ value.set(hop, offset);
141
+ offset += hop.length;
142
+ }
143
+ return value;
144
+ }
132
145
  /**
133
146
  * Read the parameters a negotiated session puts on every advertisement.
134
147
  *
@@ -163,25 +176,52 @@ export function fromParams(params) {
163
176
  const value = params.hopPath;
164
177
  if (value === undefined)
165
178
  throw new Error("advertisement is missing HOP_PATH");
166
- const hops = [];
167
- let rest = value;
168
- while (rest.length > 0) {
169
- // A short read here means the entries did not exactly fill the length.
170
- const [hop, remain] = Varint.decodeLeadingOnes(rest);
171
- hops.push(OriginSchema.parse(hop));
172
- rest = remain;
173
- // Bail before the buffer does: a hostile length would otherwise cost us one
174
- // allocation per entry all the way to 64KB of parameter value.
175
- if (hops.length > MAX_HOPS)
176
- throw new Error(`hop count exceeds maximum ${MAX_HOPS}`);
177
- }
178
- validate(hops);
179
- return { hops, cost: params.routeCost ?? 0n };
179
+ return { hops: decodeHops(value), cost: params.routeCost ?? 0n };
180
+ }
181
+ catch (err) {
182
+ throw new ProtocolViolation(reason(err), { cause: err });
183
+ }
184
+ }
185
+ /**
186
+ * Read a REQUEST_UPDATE's parameters: each is optional, and a malformed HOP_PATH is the
187
+ * same violation it is on an advertisement.
188
+ *
189
+ * @internal
190
+ */
191
+ export function updateFromParams(params) {
192
+ try {
193
+ const value = params.hopPath;
194
+ return { hops: value === undefined ? undefined : decodeHops(value), cost: params.routeCost };
180
195
  }
181
196
  catch (err) {
182
197
  throw new ProtocolViolation(reason(err), { cause: err });
183
198
  }
184
199
  }
200
+ /**
201
+ * The advertisement after `update` lands on `held`: an omitted parameter keeps its value.
202
+ *
203
+ * @internal
204
+ */
205
+ export function apply(held, update) {
206
+ return { hops: update.hops ?? held.hops, cost: update.cost ?? held.cost };
207
+ }
208
+ /** Read a HOP_PATH value: bare varints filling the parameter, with no count of their own. */
209
+ function decodeHops(value) {
210
+ const hops = [];
211
+ let rest = value;
212
+ while (rest.length > 0) {
213
+ // A short read here means the entries did not exactly fill the length.
214
+ const [hop, remain] = Varint.decodeLeadingOnes(rest);
215
+ hops.push(HopSchema.parse(hop));
216
+ rest = remain;
217
+ // Bail before the buffer does: a hostile length would otherwise cost us one
218
+ // allocation per entry all the way to 64KB of parameter value.
219
+ if (hops.length > MAX_HOPS)
220
+ throw new Error(`hop count exceeds maximum ${MAX_HOPS}`);
221
+ }
222
+ validate(hops);
223
+ return hops;
224
+ }
185
225
  /**
186
226
  * Reject a path that cannot have come from a conforming sender: an empty list, one longer
187
227
  * than we accept, or a non-zero Hop ID appearing twice (a loop). Duplicate zeros are legal,
@@ -195,7 +235,7 @@ function validate(hops) {
195
235
  // MAX_HOPS is 32, so the quadratic scan is cheaper than allocating a set.
196
236
  for (let i = 0; i < hops.length; i++) {
197
237
  const hop = hops[i];
198
- if (hop === UNKNOWN_ORIGIN)
238
+ if (hop === UNKNOWN_HOP)
199
239
  continue;
200
240
  if (hops.indexOf(hop, i + 1) !== -1)
201
241
  throw new Error(`hop ${hop} appears twice in the hop path`);
@@ -1 +1 @@
1
- {"version":3,"file":"cluster.js","sourceRoot":"","sources":["../../src/ietf/cluster.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The MoQ Cluster extension (draft-lcurley-moq-cluster-00).\n *\n * moq-transport carries no routing information, so a peer cannot tell whether an\n * advertisement already passed through us. This extension adds it:\n *\n * - each endpoint declares its own Hop ID via the RELAY_HOPS Setup Option, which is also\n * what negotiates the extension;\n * - every advertisement carries the HOP_PATH it traversed and the accumulated ROUTE_COST\n * of that path, as Key-Value-Pair message parameters on PUBLISH_NAMESPACE and NAMESPACE.\n *\n * These are the semantics moq-lite carries natively on every announcement (see\n * `lite/announce.ts`); this module is the moq-transport binding, negotiated on draft-17+\n * only, where SETUP is a Key-Value-Pair block.\n *\n * We are a leaf, never a relay: the only path we can advertise is our own, so we stamp a\n * single-entry HOP_PATH and seed the cost at 0. What we get out of declaring is the other\n * direction. A relay that knows our Hop ID withholds the advertisements that already flowed\n * through us, so publishing a broadcast no longer announces it back to us, which is what\n * moq-lite has always done with its own hop chain.\n *\n * @module\n * @internal\n */\n\nimport { ProtocolViolation, reason } from \"../error.ts\";\nimport { MAX_HOPS, type Origin, OriginSchema, UNKNOWN_ORIGIN } from \"../origin.ts\";\nimport type { Reader } from \"../stream.ts\";\nimport * as Varint from \"../varint.ts\";\nimport { Parameters, SetupOption, type SetupOptions } from \"./parameters.ts\";\nimport { type IetfVersion, Version } from \"./version.ts\";\n\n/**\n * Whether a version can negotiate the extension.\n *\n * Draft-17 is the first with the unified SETUP: a bare Key-Value-Pair block that a new\n * option slots into without touching the message shape. Earlier drafts exchange SETUP over\n * the legacy control stream, which this extension does not cover.\n *\n * @internal\n */\nexport function supported(version: IetfVersion): boolean {\n\treturn version !== Version.DRAFT_14 && version !== Version.DRAFT_15 && version !== Version.DRAFT_16;\n}\n\n/**\n * The Hop IDs this session declared, one per direction.\n *\n * @internal\n */\nexport interface Hops {\n\t/** Our own Hop ID, declared in SETUP and stamped onto every advertisement we send. */\n\tself: Origin;\n\n\t/**\n\t * The peer's Hop ID, or `undefined` when it declared no RELAY_HOPS.\n\t *\n\t * This is what decides whether advertisements carry the parameters at all, in both\n\t * directions: a peer that declared nothing has not read ours either, so sending it a\n\t * HOP_PATH would be a protocol violation and expecting one back would reject a legal\n\t * message. A peer that declared the reserved 0 negotiated the extension but withheld its\n\t * identity, so the parameters flow and there is simply nothing to exclude.\n\t */\n\tpeer?: Origin;\n}\n\n/**\n * The cluster parameters carried on one advertisement.\n *\n * Present on every PUBLISH_NAMESPACE and NAMESPACE of a session that negotiated the\n * extension, and on none of a session that did not.\n *\n * @internal\n */\nexport interface Advert {\n\t/** HOP_PATH: the path this advertisement traversed, publisher first, sender last. */\n\thops: Origin[];\n\n\t/** ROUTE_COST: the marginal cost of subscribing via this advertisement. Absent on the\n\t * wire means 0, so an endpoint that prices nothing sends nothing. */\n\tcost: bigint;\n}\n\n/**\n * Read the peer's Hop ID out of its SETUP, or `undefined` when it did not negotiate.\n *\n * @internal\n */\nexport function fromSetup(params: SetupOptions, version: IetfVersion): Origin | undefined {\n\tif (!supported(version)) return undefined;\n\n\t// RELAY_HOPS is odd, so its value is a length-prefixed byte string holding the sender's\n\t// Hop ID as a single varint. 0 is legal: the peer speaks the extension but withholds\n\t// its identity.\n\tconst value = params.getBytes(SetupOption.RelayHops);\n\tif (value === undefined) return undefined;\n\n\tconst [origin, rest] = Varint.decodeLeadingOnes(value);\n\tif (rest.length !== 0) throw new Error(\"trailing bytes in RELAY_HOPS\");\n\treturn OriginSchema.parse(origin);\n}\n\n/**\n * Declare our own Hop ID, which is also what negotiates the extension.\n *\n * @internal\n */\nexport function intoSetup(params: SetupOptions, self: Origin, version: IetfVersion) {\n\tif (!supported(version)) return;\n\tparams.setBytes(SetupOption.RelayHops, Varint.encodeLeadingOnes(self));\n}\n\n/**\n * Whether the session negotiated the extension, which is what decides whether an\n * advertisement carries the parameters in either direction.\n *\n * @internal\n */\nexport function negotiated(ids: Hops | undefined): boolean {\n\treturn ids?.peer !== undefined;\n}\n\n/**\n * The advertisement to send for one of our own broadcasts, or `undefined` on a session\n * that negotiated nothing.\n *\n * The sender's own Hop ID is always the last entry, so a receiver reconstructs the full\n * path without the sender restating it. We originate everything we advertise, so the path\n * is just us and the cost is 0: a subscription costs the peer nothing but the transfer,\n * since we are already producing the content.\n *\n * @internal\n */\nexport function advertise(ids: Hops | undefined): Advert | undefined {\n\tif (ids === undefined || ids.peer === undefined) return undefined;\n\treturn { hops: [ids.self], cost: 0n };\n}\n\n/**\n * Whether this advertisement looped back through `self`.\n *\n * A receiver discards such an advertisement: forwarding it would extend the loop, and\n * subscribing through it would route the receiver back to itself. Hop ID 0 identifies\n * nothing, so a receiver that withheld its own identity detects nothing.\n *\n * @internal\n */\nexport function loops(advert: Advert, self: Origin): boolean {\n\treturn self !== UNKNOWN_ORIGIN && advert.hops.includes(self);\n}\n\n/**\n * Write an advertisement's parameters, ready to encode onto the wire.\n *\n * @internal\n */\nexport function intoParams(advert: Advert): Parameters {\n\tvalidate(advert.hops);\n\n\tconst params = new Parameters();\n\n\t// The entries fill the value, so they are written with no count of their own and framed\n\t// by the parameter's own length prefix.\n\tconst hops = advert.hops.map((hop) => Varint.encodeLeadingOnes(hop));\n\tconst value = new Uint8Array(hops.reduce((total, hop) => total + hop.length, 0));\n\tlet offset = 0;\n\tfor (const hop of hops) {\n\t\tvalue.set(hop, offset);\n\t\toffset += hop.length;\n\t}\n\tparams.hopPath = value;\n\n\t// Absent means 0, so a free path sends nothing.\n\tif (advert.cost !== 0n) params.routeCost = advert.cost;\n\n\treturn params;\n}\n\n/**\n * Read the parameters a negotiated session puts on every advertisement.\n *\n * The parameter block is mandatory there, so a message that ends before it (the base form)\n * or one whose block does not parse is the peer's violation, same as a block that parses but\n * carries no HOP_PATH.\n *\n * @internal\n */\nexport async function decodeParams(r: Reader, version: IetfVersion): Promise<Advert> {\n\tlet params: Parameters;\n\ttry {\n\t\tparams = await Parameters.decode(r, version);\n\t} catch (err) {\n\t\tthrow new ProtocolViolation(reason(err), { cause: err });\n\t}\n\n\treturn fromParams(params);\n}\n\n/**\n * Read an advertisement out of a decoded parameter block.\n *\n * Every way this fails is one the draft says to close the session over: a negotiated session\n * that omits HOP_PATH, entries that do not exactly fill the parameter, an empty list, or a\n * non-zero Hop ID appearing twice. So they all surface as one {@link ProtocolViolation},\n * which the session dispatch acts on.\n *\n * @internal\n */\nexport function fromParams(params: Parameters): Advert {\n\ttry {\n\t\tconst value = params.hopPath;\n\t\tif (value === undefined) throw new Error(\"advertisement is missing HOP_PATH\");\n\n\t\tconst hops: Origin[] = [];\n\t\tlet rest = value;\n\t\twhile (rest.length > 0) {\n\t\t\t// A short read here means the entries did not exactly fill the length.\n\t\t\tconst [hop, remain] = Varint.decodeLeadingOnes(rest);\n\t\t\thops.push(OriginSchema.parse(hop));\n\t\t\trest = remain;\n\n\t\t\t// Bail before the buffer does: a hostile length would otherwise cost us one\n\t\t\t// allocation per entry all the way to 64KB of parameter value.\n\t\t\tif (hops.length > MAX_HOPS) throw new Error(`hop count exceeds maximum ${MAX_HOPS}`);\n\t\t}\n\n\t\tvalidate(hops);\n\t\treturn { hops, cost: params.routeCost ?? 0n };\n\t} catch (err) {\n\t\tthrow new ProtocolViolation(reason(err), { cause: err });\n\t}\n}\n\n/**\n * Reject a path that cannot have come from a conforming sender: an empty list, one longer\n * than we accept, or a non-zero Hop ID appearing twice (a loop). Duplicate zeros are legal,\n * since 0 identifies nothing.\n */\nfunction validate(hops: Origin[]) {\n\tif (hops.length === 0) throw new Error(\"hop path is empty\");\n\tif (hops.length > MAX_HOPS) throw new Error(`hop count ${hops.length} exceeds maximum ${MAX_HOPS}`);\n\n\t// MAX_HOPS is 32, so the quadratic scan is cheaper than allocating a set.\n\tfor (let i = 0; i < hops.length; i++) {\n\t\tconst hop = hops[i];\n\t\tif (hop === UNKNOWN_ORIGIN) continue;\n\t\tif (hops.indexOf(hop, i + 1) !== -1) throw new Error(`hop ${hop} appears twice in the hop path`);\n\t}\n}\n"]}
1
+ {"version":3,"file":"cluster.js","sourceRoot":"","sources":["../../src/ietf/cluster.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The MoQ Cluster extension (draft-lcurley-moq-cluster-01).\n *\n * moq-transport carries no routing information, so a peer cannot tell whether an\n * advertisement already passed through us. This extension adds it:\n *\n * - each endpoint declares its own Hop ID via the HOP_ID Setup Option, which is also what\n * negotiates the extension;\n * - every advertisement carries the HOP_PATH it traversed and the accumulated ROUTE_COST\n * of that path, as Key-Value-Pair message parameters on PUBLISH_NAMESPACE and NAMESPACE,\n * and a PUBLISH_NAMESPACE is repriced with the same parameters on a REQUEST_UPDATE.\n *\n * These are the semantics moq-lite carries natively on every announcement (see\n * `lite/announce.ts`); this module is the moq-transport binding, negotiated on draft-17+\n * only, where SETUP is a Key-Value-Pair block.\n *\n * A broadcast we produce carries a single-entry HOP_PATH and a cost of 0, and a route we\n * forward carries its own hop chain and warm cost with our id appended. What we get out of\n * declaring is also the other direction. A relay that knows our Hop ID withholds the advertisements that already flowed\n * through us, so publishing a broadcast no longer announces it back to us, which is what\n * moq-lite has always done with its own hop chain.\n *\n * @module\n * @internal\n */\n\nimport { ProtocolViolation, reason } from \"../error.ts\";\nimport { type Hop, HopSchema, MAX_HOPS, UNKNOWN_HOP } from \"../hop.ts\";\nimport type { Reader } from \"../stream.ts\";\nimport * as Varint from \"../varint.ts\";\nimport { Parameters, SetupOption, type SetupOptions } from \"./parameters.ts\";\nimport { type IetfVersion, Version } from \"./version.ts\";\n\n/**\n * Whether a version can negotiate the extension.\n *\n * Draft-17 is the first with the unified SETUP: a bare Key-Value-Pair block that a new\n * option slots into without touching the message shape. Earlier drafts exchange SETUP over\n * the legacy control stream, which this extension does not cover.\n *\n * @internal\n */\nexport function supported(version: IetfVersion): boolean {\n\treturn version !== Version.DRAFT_14 && version !== Version.DRAFT_15 && version !== Version.DRAFT_16;\n}\n\n/**\n * The Hop IDs this session declared, one per direction.\n *\n * @internal\n */\nexport interface Hops {\n\t/** Our own Hop ID, declared in SETUP and stamped onto every advertisement we send. */\n\tself: Hop;\n\n\t/**\n\t * The peer's Hop ID, or `undefined` when it declared no HOP_ID.\n\t *\n\t * This is what decides whether advertisements carry the parameters at all, in both\n\t * directions: a peer that declared nothing has not read ours either, so sending it a\n\t * HOP_PATH would be a protocol violation and expecting one back would reject a legal\n\t * message. A peer that declared the reserved 0 negotiated the extension but withheld its\n\t * identity, so the parameters flow and there is simply nothing to exclude.\n\t */\n\tpeer?: Hop;\n}\n\n/**\n * The cluster parameters carried on one advertisement.\n *\n * Present on every PUBLISH_NAMESPACE and NAMESPACE of a session that negotiated the\n * extension, and on none of a session that did not.\n *\n * @internal\n */\nexport interface Advert {\n\t/** HOP_PATH: the path this advertisement traversed, publisher first, sender last. */\n\thops: Hop[];\n\n\t/** ROUTE_COST: the marginal cost of subscribing via this advertisement. Absent on the\n\t * wire means 0, so an endpoint that prices nothing sends nothing. */\n\tcost: bigint;\n}\n\n/**\n * The cluster parameters a REQUEST_UPDATE on a PUBLISH_NAMESPACE carries: only what\n * changed, since an omitted parameter keeps its value.\n *\n * @internal\n */\nexport interface Update {\n\t/** HOP_PATH, when the route changed. */\n\thops?: Hop[];\n\n\t/** ROUTE_COST, when the cost changed. A cost that dropped to 0 arrives as an explicit\n\t * 0 here, unlike the advertisement itself where absent means 0. */\n\tcost?: bigint;\n}\n\n/**\n * Read the peer's Hop ID out of its SETUP, or `undefined` when it did not negotiate.\n *\n * @internal\n */\nexport function fromSetup(params: SetupOptions, version: IetfVersion): Hop | undefined {\n\tif (!supported(version)) return undefined;\n\n\t// 0 is legal: the peer speaks the extension but withholds its identity.\n\tconst value = params.getVarint(SetupOption.HopId);\n\tif (value === undefined) return undefined;\n\treturn HopSchema.parse(value);\n}\n\n/**\n * Declare our own Hop ID, which is also what negotiates the extension.\n *\n * @internal\n */\nexport function intoSetup(params: SetupOptions, self: Hop, version: IetfVersion) {\n\tif (!supported(version)) return;\n\tparams.setVarint(SetupOption.HopId, self);\n}\n\n/**\n * Whether the session negotiated the extension, which is what decides whether an\n * advertisement carries the parameters in either direction.\n *\n * @internal\n */\nexport function negotiated(ids: Hops | undefined): boolean {\n\treturn ids?.peer !== undefined;\n}\n\n/**\n * The advertisement to send for one of our own broadcasts, or `undefined` on a session\n * that negotiated nothing.\n *\n * The sender's own Hop ID is always the last entry, so a receiver reconstructs the full\n * path without the sender restating it. We originate everything we advertise, so the path\n * is just us and the cost is 0: a subscription costs the peer nothing but the transfer,\n * since we are already producing the content.\n *\n * @internal\n */\nexport function advertise(ids: Hops | undefined): Advert | undefined {\n\tif (ids === undefined || ids.peer === undefined) return undefined;\n\treturn { hops: [ids.self], cost: 0n };\n}\n\n/**\n * Whether this advertisement looped back through `self`.\n *\n * A receiver discards such an advertisement: forwarding it would extend the loop, and\n * subscribing through it would route the receiver back to itself. Hop ID 0 identifies\n * nothing, so a receiver that withheld its own identity detects nothing.\n *\n * @internal\n */\nexport function loops(advert: Advert, self: Hop): boolean {\n\treturn self !== UNKNOWN_HOP && advert.hops.includes(self);\n}\n\n/**\n * Write an advertisement's parameters, ready to encode onto the wire.\n *\n * @internal\n */\nexport function intoParams(advert: Advert): Parameters {\n\tconst params = new Parameters();\n\tparams.hopPath = encodeHops(advert.hops);\n\n\t// Absent means 0, so a free path sends nothing.\n\tif (advert.cost !== 0n) params.routeCost = advert.cost;\n\n\treturn params;\n}\n\n/**\n * Write a REQUEST_UPDATE's parameters: only what changed, and a cost of 0 explicitly,\n * since an omitted parameter keeps its value.\n *\n * @internal\n */\nexport function updateIntoParams(update: Update): Parameters {\n\tconst params = new Parameters();\n\tif (update.hops !== undefined) params.hopPath = encodeHops(update.hops);\n\tif (update.cost !== undefined) params.routeCost = update.cost;\n\treturn params;\n}\n\n/** Write a HOP_PATH value: the entries fill it with no count of their own, framed by the\n * parameter's own length prefix. */\nfunction encodeHops(hops: Hop[]): Uint8Array {\n\tvalidate(hops);\n\n\tconst encoded = hops.map((hop) => Varint.encodeLeadingOnes(hop));\n\tconst value = new Uint8Array(encoded.reduce((total, hop) => total + hop.length, 0));\n\tlet offset = 0;\n\tfor (const hop of encoded) {\n\t\tvalue.set(hop, offset);\n\t\toffset += hop.length;\n\t}\n\treturn value;\n}\n\n/**\n * Read the parameters a negotiated session puts on every advertisement.\n *\n * The parameter block is mandatory there, so a message that ends before it (the base form)\n * or one whose block does not parse is the peer's violation, same as a block that parses but\n * carries no HOP_PATH.\n *\n * @internal\n */\nexport async function decodeParams(r: Reader, version: IetfVersion): Promise<Advert> {\n\tlet params: Parameters;\n\ttry {\n\t\tparams = await Parameters.decode(r, version);\n\t} catch (err) {\n\t\tthrow new ProtocolViolation(reason(err), { cause: err });\n\t}\n\n\treturn fromParams(params);\n}\n\n/**\n * Read an advertisement out of a decoded parameter block.\n *\n * Every way this fails is one the draft says to close the session over: a negotiated session\n * that omits HOP_PATH, entries that do not exactly fill the parameter, an empty list, or a\n * non-zero Hop ID appearing twice. So they all surface as one {@link ProtocolViolation},\n * which the session dispatch acts on.\n *\n * @internal\n */\nexport function fromParams(params: Parameters): Advert {\n\ttry {\n\t\tconst value = params.hopPath;\n\t\tif (value === undefined) throw new Error(\"advertisement is missing HOP_PATH\");\n\t\treturn { hops: decodeHops(value), cost: params.routeCost ?? 0n };\n\t} catch (err) {\n\t\tthrow new ProtocolViolation(reason(err), { cause: err });\n\t}\n}\n\n/**\n * Read a REQUEST_UPDATE's parameters: each is optional, and a malformed HOP_PATH is the\n * same violation it is on an advertisement.\n *\n * @internal\n */\nexport function updateFromParams(params: Parameters): Update {\n\ttry {\n\t\tconst value = params.hopPath;\n\t\treturn { hops: value === undefined ? undefined : decodeHops(value), cost: params.routeCost };\n\t} catch (err) {\n\t\tthrow new ProtocolViolation(reason(err), { cause: err });\n\t}\n}\n\n/**\n * The advertisement after `update` lands on `held`: an omitted parameter keeps its value.\n *\n * @internal\n */\nexport function apply(held: Advert, update: Update): Advert {\n\treturn { hops: update.hops ?? held.hops, cost: update.cost ?? held.cost };\n}\n\n/** Read a HOP_PATH value: bare varints filling the parameter, with no count of their own. */\nfunction decodeHops(value: Uint8Array): Hop[] {\n\tconst hops: Hop[] = [];\n\tlet rest = value;\n\twhile (rest.length > 0) {\n\t\t// A short read here means the entries did not exactly fill the length.\n\t\tconst [hop, remain] = Varint.decodeLeadingOnes(rest);\n\t\thops.push(HopSchema.parse(hop));\n\t\trest = remain;\n\n\t\t// Bail before the buffer does: a hostile length would otherwise cost us one\n\t\t// allocation per entry all the way to 64KB of parameter value.\n\t\tif (hops.length > MAX_HOPS) throw new Error(`hop count exceeds maximum ${MAX_HOPS}`);\n\t}\n\n\tvalidate(hops);\n\treturn hops;\n}\n\n/**\n * Reject a path that cannot have come from a conforming sender: an empty list, one longer\n * than we accept, or a non-zero Hop ID appearing twice (a loop). Duplicate zeros are legal,\n * since 0 identifies nothing.\n */\nfunction validate(hops: Hop[]) {\n\tif (hops.length === 0) throw new Error(\"hop path is empty\");\n\tif (hops.length > MAX_HOPS) throw new Error(`hop count ${hops.length} exceeds maximum ${MAX_HOPS}`);\n\n\t// MAX_HOPS is 32, so the quadratic scan is cheaper than allocating a set.\n\tfor (let i = 0; i < hops.length; i++) {\n\t\tconst hop = hops[i];\n\t\tif (hop === UNKNOWN_HOP) continue;\n\t\tif (hops.indexOf(hop, i + 1) !== -1) throw new Error(`hop ${hop} appears twice in the hop path`);\n\t}\n}\n"]}
@@ -1,13 +1,9 @@
1
1
  import { type Getter } from "@norskvideo/moq-signals";
2
- import * as announce from "../announced";
3
- import type * as broadcast from "../broadcast";
2
+ import type * as announce from "../announced";
4
3
  import type { Established } from "../connection/established";
5
4
  import { type Probe, type Stats } from "../connection/stats";
6
5
  import { type Transport } from "../connection/transport";
7
- import * as Path from "../path";
8
- import { type Stream } from "../stream";
9
- import * as Cluster from "./cluster";
10
- import { type IetfVersion } from "./version";
6
+ import type * as Path from "../path";
11
7
  /**
12
8
  * Represents a connection to a MoQ server using moq-transport protocol.
13
9
  *
@@ -22,77 +18,15 @@ export declare class Connection implements Established {
22
18
  readonly discovery: boolean;
23
19
  /** moq-transport has no PROBE, so this stays empty; see {@link Established.probe}. */
24
20
  readonly probe: Getter<Probe>;
25
- /**
26
- * Creates a new Connection instance.
27
- * @param url - The URL of the connection
28
- * @param quic - The WebTransport session
29
- * @param control - The control/setup stream
30
- * @param maxRequestId - The initial max request ID
31
- * @param version - The negotiated protocol version
32
- * @param solicit - What the peer's SETUP declared (undefined when it declared nothing)
33
- * @param cluster - The Hop IDs the SETUP exchange settled, on the versions that negotiate them
34
- *
35
- * @internal
36
- */
37
- constructor({ url, quic, control, maxRequestId, version, client, discovery, solicit, cluster, }: {
38
- url: URL;
39
- quic: WebTransport;
40
- control: Stream;
41
- maxRequestId: bigint;
42
- version: IetfVersion;
43
- /** Whether this peer initiated the session, selecting the even request-ID space. */
44
- client: boolean;
45
- discovery?: boolean;
46
- /**
47
- * What the peer declared about being solicited. `undefined` means it declared
48
- * nothing, which is the one case where announcing at us unasked is not a bug.
49
- */
50
- solicit?: boolean;
51
- /**
52
- * The Hop IDs this session declared (MoQ Cluster). `undefined` on a version that
53
- * cannot negotiate the extension, as is a `peer` the peer never declared.
54
- */
55
- cluster?: Cluster.Hops;
56
- });
57
21
  /** Snapshot the transport's counters; see {@link Established.stats}. */
58
22
  stats(): Promise<Stats>;
59
23
  /**
60
24
  * Closes the connection.
61
25
  */
62
26
  close(): void;
63
- /**
64
- * Publishes a broadcast to the connection.
65
- * @param name - The broadcast path to publish
66
- * @param broadcast - The broadcast to publish
67
- */
68
- publish(path: Path.Valid, producer: broadcast.Producer): void;
69
- /**
70
- * Gets an announced reader for the specified prefix.
71
- * @param prefix - The prefix for announcements
72
- * @returns An Announced instance
73
- */
74
- announced(prefix?: Path.Valid): announce.Consumer;
75
- /**
76
- * Consumes a broadcast from the connection.
77
- *
78
- * @remarks
79
- * If the broadcast is not found, a "not found" error will be thrown when requesting any tracks.
80
- *
81
- * @param broadcast - The path of the broadcast to consume
82
- * @returns A Broadcast instance
83
- */
84
- consume(path: Path.Valid): broadcast.Consumer;
85
- /**
86
- * Watches a broadcast, live only while it is announced.
87
- *
88
- * @param path - The path of the broadcast to watch
89
- * @returns A reactive handle to the broadcast
90
- */
91
- announcedBroadcast(path: Path.Valid): announce.Broadcast;
92
- /**
93
- * Returns a promise that resolves when the connection is closed.
94
- * @returns A promise that resolves when closed
95
- */
96
- get closed(): Promise<void>;
27
+ /** Gets an announced reader for `scope`; see {@link Established.announced}. */
28
+ announced(scope?: Path.Pattern, options?: announce.Options): announce.Consumer;
29
+ /** Resolves when the session closes, decoding the peer's close code; see {@link Established.closed}. */
30
+ get closed(): Promise<Error | null>;
97
31
  }
98
32
  //# sourceMappingURL=connection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/ietf/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAU,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,EAAkB,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,4BAA4B,CAAC;AAEzE,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AACnC,OAAO,EAAwB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAEjE,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAUxC,OAAO,EAAE,KAAK,WAAW,EAAwB,MAAM,cAAc,CAAC;AAEtE;;;;GAIG;AACH,qBAAa,UAAW,YAAW,WAAW;;IAE7C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAGlB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAGzB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,yFAAyF;IACzF,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAE5B,sFAAsF;IACtF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAyB;IAuBtD;;;;;;;;;;;OAWG;IACH,YAAY,EACX,GAAG,EACH,IAAI,EACJ,OAAO,EACP,YAAY,EACZ,OAAO,EACP,MAAM,EACN,SAAgB,EAChB,OAAO,EACP,OAAO,GACP,EAAE;QACF,GAAG,EAAE,GAAG,CAAC;QACT,IAAI,EAAE,YAAY,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,WAAW,CAAC;QACrB,oFAAoF;QACpF,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB;;;WAGG;QACH,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB;;;WAGG;QACH,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC;KACvB,EAiCA;IAED,wEAAwE;IAClE,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAE5B;IAED;;OAEG;IACH,KAAK,SAaJ;IAcD;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,QAErD;IAED;;;;OAIG;IACH,SAAS,CAAC,MAAM,aAAe,GAAG,QAAQ,CAAC,QAAQ,CAElD;IAED;;;;;;;;OAQG;IACH,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,QAAQ,CAE5C;IAED;;;;;OAKG;IACH,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,SAAS,CAEvD;IAuJD;;;OAGG;IACH,IAAI,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAE1B;CACD"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/ietf/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAU,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,EAAkB,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,4BAA4B,CAAC;AAGzE,OAAO,KAAK,KAAK,IAAI,MAAM,YAAY,CAAC;AAoBxC;;;;GAIG;AACH,qBAAa,UAAW,YAAW,WAAW;;IAE7C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAGlB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAGzB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,yFAAyF;IACzF,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAE5B,sFAAsF;IACtF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAyB;IA4GtD,wEAAwE;IAClE,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAE5B;IAED;;OAEG;IACH,KAAK,SAEJ;IAoCD,+EAA+E;IAC/E,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAE7E;IAqLD,wGAAwG;IACxG,IAAI,MAAM,IAAI,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAElC;CACD"}
@@ -1,13 +1,13 @@
1
1
  /* @ts-self-types="./connection.d.ts" */
2
2
  import { Signal } from "@norskvideo/moq-signals";
3
- import * as announce from "../announced.js";
4
3
  import { transportStats } from "../connection/stats.js";
5
4
  import { transportOf } from "../connection/transport.js";
6
- import { ProtocolViolation } from "../error.js";
7
- import * as Path from "../path.js";
5
+ import { error, fromClose, ProtocolViolation, SessionCode, StreamCode, StreamError } from "../error.js";
8
6
  import { Readers } from "../stream.js";
7
+ import { registerWire } from "../wire.js";
9
8
  import { ControlStreamAdapter, NativeSession } from "./adapter.js";
10
9
  import * as Cluster from "./cluster.js";
10
+ import { Fetch, FetchHeader } from "./fetch.js";
11
11
  import { GoAway } from "./goaway.js";
12
12
  import { Group } from "./object.js";
13
13
  import { Publish } from "./publish.js";
@@ -18,6 +18,8 @@ import { SubscribeNamespace, SubscribeNamespaceLegacy } from "./subscribe_namesp
18
18
  import { Subscriber } from "./subscriber.js";
19
19
  import { TrackStatusRequest } from "./track.js";
20
20
  import { Version, versionName } from "./version.js";
21
+ // The PADDING stream type (draft-18+): bytes a peer sends to probe for bandwidth.
22
+ const PADDING = 0x132b3e28n;
21
23
  /**
22
24
  * Represents a connection to a MoQ server using moq-transport protocol.
23
25
  *
@@ -60,7 +62,7 @@ export class Connection {
60
62
  *
61
63
  * @internal
62
64
  */
63
- constructor({ url, quic, control, maxRequestId, version, client, discovery = true, solicit, cluster, }) {
65
+ constructor({ url, quic, control, maxRequestId, version, client, discovery = true, publish, solicit, hidden = false, cluster, }) {
64
66
  this.url = url;
65
67
  this.discovery = discovery;
66
68
  this.version = versionName(version);
@@ -85,12 +87,15 @@ export class Connection {
85
87
  this.#publisher = new Publisher({
86
88
  quic: this.#quic,
87
89
  session: this.#session,
90
+ publish,
88
91
  requiresSolicitation: solicit ?? false,
92
+ hidden,
89
93
  cluster,
90
94
  });
91
95
  this.#solicit = solicit;
92
96
  this.#cluster = cluster;
93
- this.#subscriber = new Subscriber({ session: this.#session, cluster });
97
+ this.#subscriber = new Subscriber({ session: this.#session, quic, cluster, hidden });
98
+ registerWire(this, { consume: (path) => this.#subscriber.consume(path) });
94
99
  void this.#run();
95
100
  }
96
101
  /** Snapshot the transport's counters; see {@link Established.stats}. */
@@ -101,17 +106,26 @@ export class Connection {
101
106
  * Closes the connection.
102
107
  */
103
108
  close() {
109
+ this.#close();
110
+ }
111
+ // Close with the session code the peer should see, a clean close by default.
112
+ #close(info) {
104
113
  if (this.#closed)
105
114
  return;
106
115
  this.#closed = true;
107
- this.#publisher.close();
108
- this.#session.close();
116
+ // Before the session, whose own close would send a clean code first.
109
117
  try {
110
- this.#quic.close();
118
+ this.#quic.close(info);
111
119
  }
112
120
  catch {
113
121
  // ignore
114
122
  }
123
+ this.#session.close();
124
+ }
125
+ // The peer broke the protocol, so losing the stream is not enough: nothing stops it
126
+ // repeating the violation on the next one.
127
+ #violated(err) {
128
+ this.#close({ closeCode: SessionCode.ProtocolViolation, reason: err.message });
115
129
  }
116
130
  async #run() {
117
131
  try {
@@ -126,42 +140,9 @@ export class Connection {
126
140
  this.close();
127
141
  }
128
142
  }
129
- /**
130
- * Publishes a broadcast to the connection.
131
- * @param name - The broadcast path to publish
132
- * @param broadcast - The broadcast to publish
133
- */
134
- publish(path, producer) {
135
- this.#publisher.publish(path, producer);
136
- }
137
- /**
138
- * Gets an announced reader for the specified prefix.
139
- * @param prefix - The prefix for announcements
140
- * @returns An Announced instance
141
- */
142
- announced(prefix = Path.empty()) {
143
- return this.#subscriber.announced(prefix);
144
- }
145
- /**
146
- * Consumes a broadcast from the connection.
147
- *
148
- * @remarks
149
- * If the broadcast is not found, a "not found" error will be thrown when requesting any tracks.
150
- *
151
- * @param broadcast - The path of the broadcast to consume
152
- * @returns A Broadcast instance
153
- */
154
- consume(path) {
155
- return this.#subscriber.consume(path);
156
- }
157
- /**
158
- * Watches a broadcast, live only while it is announced.
159
- *
160
- * @param path - The path of the broadcast to watch
161
- * @returns A reactive handle to the broadcast
162
- */
163
- announcedBroadcast(path) {
164
- return new announce.Broadcast({ connection: this, path });
143
+ /** Gets an announced reader for `scope`; see {@link Established.announced}. */
144
+ announced(scope, options) {
145
+ return this.#subscriber.announced(scope, options);
165
146
  }
166
147
  /**
167
148
  * Accepts bidi streams (virtual for v14-v16, real for v17) and dispatches.
@@ -174,10 +155,8 @@ export class Connection {
174
155
  void this.#runBidi(stream).catch((err) => {
175
156
  console.error("error processing bidi stream", err);
176
157
  stream.abort(new Error("bidi stream error"));
177
- // The peer broke the protocol, so losing the stream is not enough: nothing
178
- // stops it repeating the violation on the next one.
179
158
  if (err instanceof ProtocolViolation)
180
- this.close();
159
+ this.#violated(err);
181
160
  });
182
161
  }
183
162
  }
@@ -197,7 +176,11 @@ export class Connection {
197
176
  }
198
177
  case SubscribeNamespaceLegacy.id: {
199
178
  const legacy = await SubscribeNamespaceLegacy.decode(stream.reader, this.#session.version);
200
- const msg = new SubscribeNamespace({ requestId: legacy.requestId, namespace: legacy.namespace });
179
+ const msg = new SubscribeNamespace({
180
+ requestId: legacy.requestId,
181
+ namespace: legacy.namespace,
182
+ hidden: legacy.hidden,
183
+ });
201
184
  await this.#publisher.runSubscribeNamespace(msg, stream);
202
185
  break;
203
186
  }
@@ -217,6 +200,11 @@ export class Connection {
217
200
  await this.#publisher.runTrackStatusRequest(msg, stream);
218
201
  break;
219
202
  }
203
+ case Fetch.id: {
204
+ const msg = await Fetch.decode(stream.reader, this.#session.version);
205
+ await this.#publisher.runFetch(msg, stream);
206
+ break;
207
+ }
220
208
  // Subscriber handles incoming notifications
221
209
  case PublishNamespace.id: {
222
210
  const msg = await PublishNamespace.decode(stream.reader, this.#session.version, Cluster.negotiated(this.#cluster));
@@ -260,17 +248,38 @@ export class Connection {
260
248
  break;
261
249
  this.#runUni(stream)
262
250
  .then(() => {
263
- stream.stop(new Error("cancel"));
251
+ stream.stop(new StreamError(StreamCode.Cancel, { message: "cancel" }));
264
252
  })
265
253
  .catch((err) => {
266
254
  console.error("error processing object stream", err);
267
255
  stream.stop(err);
256
+ // An unknown or invalid stream type MUST close the session, not just the stream.
257
+ if (err instanceof ProtocolViolation)
258
+ this.#violated(err);
268
259
  });
269
260
  }
270
261
  }
271
262
  async #runUni(stream) {
272
- const header = await Group.decode(stream, this.#session.version);
273
- await this.#subscriber.handleGroup(header, stream);
263
+ const version = this.#session.version;
264
+ // Full width, so an unknown type past 2^53 is still classified rather than thrown.
265
+ const type = await stream.u62();
266
+ // SUBGROUP_HEADER types match 0b0XX1XXXX; Group.decode validates the bits per draft.
267
+ if (type <= 0xffn && (type & 0x90n) === 0x10n) {
268
+ const header = await Group.decode(stream, version, Number(type));
269
+ await this.#subscriber.handleGroup(header, stream);
270
+ return;
271
+ }
272
+ // The receiver MUST discard padding. We read it to the end rather than cancel,
273
+ // so a peer probing for bandwidth gets the throughput it is measuring.
274
+ if (type === PADDING && version >= Version.DRAFT_18) {
275
+ await stream.discard();
276
+ return;
277
+ }
278
+ // We never FETCH, so a fetch response answers nothing of ours.
279
+ if (type === BigInt(FetchHeader.type))
280
+ throw new Error("unexpected fetch stream");
281
+ // Anything else is unknown, and a second SETUP is a violation too.
282
+ throw new ProtocolViolation(`unknown uni stream type: 0x${type.toString(16)}`);
274
283
  }
275
284
  /**
276
285
  * v17+ only: reads GoAway from the setup/control stream.
@@ -298,12 +307,9 @@ export class Connection {
298
307
  this.close();
299
308
  }
300
309
  }
301
- /**
302
- * Returns a promise that resolves when the connection is closed.
303
- * @returns A promise that resolves when closed
304
- */
310
+ /** Resolves when the session closes, decoding the peer's close code; see {@link Established.closed}. */
305
311
  get closed() {
306
- return this.#quic.closed.then(() => undefined);
312
+ return this.#quic.closed.then(fromClose, (err) => error(err));
307
313
  }
308
314
  }
309
315
  //# sourceMappingURL=connection.js.map