@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.
- package/README.md +2 -2
- package/announce.d.ts +7 -0
- package/announce.d.ts.map +1 -0
- package/announce.js +8 -0
- package/announce.js.map +1 -0
- package/announced.d.ts +49 -91
- package/announced.d.ts.map +1 -1
- package/announced.js +21 -156
- package/announced.js.map +1 -1
- package/bandwidth.d.ts +163 -0
- package/bandwidth.d.ts.map +1 -0
- package/bandwidth.js +304 -0
- package/bandwidth.js.map +1 -0
- package/bandwidth_api.d.ts +7 -0
- package/bandwidth_api.d.ts.map +1 -0
- package/bandwidth_api.js +8 -0
- package/bandwidth_api.js.map +1 -0
- package/broadcast.d.ts +44 -35
- package/broadcast.d.ts.map +1 -1
- package/broadcast.js +104 -60
- package/broadcast.js.map +1 -1
- package/connection/accept.d.ts +16 -1
- package/connection/accept.d.ts.map +1 -1
- package/connection/accept.js +52 -28
- package/connection/accept.js.map +1 -1
- package/connection/browser.d.ts.map +1 -1
- package/connection/browser.js +9 -7
- package/connection/browser.js.map +1 -1
- package/connection/connect.d.ts +30 -6
- package/connection/connect.d.ts.map +1 -1
- package/connection/connect.js +110 -54
- package/connection/connect.js.map +1 -1
- package/connection/established.d.ts +17 -21
- package/connection/established.d.ts.map +1 -1
- package/connection/established.js.map +1 -1
- package/connection/forward.d.ts +2 -0
- package/connection/forward.d.ts.map +1 -0
- package/connection/forward.js +173 -0
- package/connection/forward.js.map +1 -0
- package/connection/handshake.d.ts +1 -0
- package/connection/handshake.d.ts.map +1 -1
- package/connection/handshake.js +5 -2
- package/connection/handshake.js.map +1 -1
- package/connection/index.d.ts +5 -5
- package/connection/index.d.ts.map +1 -1
- package/connection/index.js +4 -5
- package/connection/index.js.map +1 -1
- package/connection/pool.d.ts +186 -0
- package/connection/pool.d.ts.map +1 -0
- package/connection/pool.js +361 -0
- package/connection/pool.js.map +1 -0
- package/connection/reload.d.ts +14 -93
- package/connection/reload.d.ts.map +1 -1
- package/connection/reload.js +217 -81
- package/connection/reload.js.map +1 -1
- package/connection/stats.d.ts +3 -26
- package/connection/stats.d.ts.map +1 -1
- package/connection/stats.js.map +1 -1
- package/connection/transport.d.ts +0 -7
- package/connection/transport.d.ts.map +1 -1
- package/consume.d.ts +1 -43
- package/consume.d.ts.map +1 -1
- package/consume.js +1 -1
- package/consume.js.map +1 -1
- package/error.d.ts +180 -29
- package/error.d.ts.map +1 -1
- package/error.js +331 -16
- package/error.js.map +1 -1
- package/errors.d.ts +7 -0
- package/errors.d.ts.map +1 -0
- package/errors.js +8 -0
- package/errors.js.map +1 -0
- package/group.d.ts +9 -43
- package/group.d.ts.map +1 -1
- package/group.js +284 -69
- package/group.js.map +1 -1
- package/hop.d.ts +115 -0
- package/hop.d.ts.map +1 -0
- package/hop.js +119 -0
- package/hop.js.map +1 -0
- package/ietf/adapter.d.ts +5 -1
- package/ietf/adapter.d.ts.map +1 -1
- package/ietf/adapter.js +105 -60
- package/ietf/adapter.js.map +1 -1
- package/ietf/aliases.d.ts +1 -78
- package/ietf/aliases.d.ts.map +1 -1
- package/ietf/cluster.d.ts +9 -123
- package/ietf/cluster.d.ts.map +1 -1
- package/ietf/cluster.js +84 -44
- package/ietf/cluster.js.map +1 -1
- package/ietf/connection.d.ts +6 -72
- package/ietf/connection.d.ts.map +1 -1
- package/ietf/connection.js +62 -56
- package/ietf/connection.js.map +1 -1
- package/ietf/error.d.ts +11 -0
- package/ietf/error.d.ts.map +1 -0
- package/ietf/error.js +193 -0
- package/ietf/error.js.map +1 -0
- package/ietf/fetch.d.ts +7 -20
- package/ietf/fetch.d.ts.map +1 -1
- package/ietf/fetch.js +52 -22
- package/ietf/fetch.js.map +1 -1
- package/ietf/filter.d.ts +2 -0
- package/ietf/filter.d.ts.map +1 -1
- package/ietf/filter.js +10 -1
- package/ietf/filter.js.map +1 -1
- package/ietf/goaway.d.ts.map +1 -1
- package/ietf/goaway.js +22 -5
- package/ietf/goaway.js.map +1 -1
- package/ietf/hidden.d.ts +2 -0
- package/ietf/hidden.d.ts.map +1 -0
- package/ietf/hidden.js +30 -0
- package/ietf/hidden.js.map +1 -0
- package/ietf/index.d.ts +2 -0
- package/ietf/index.d.ts.map +1 -1
- package/ietf/index.js +2 -0
- package/ietf/index.js.map +1 -1
- package/ietf/object.d.ts +15 -8
- package/ietf/object.d.ts.map +1 -1
- package/ietf/object.js +51 -36
- package/ietf/object.js.map +1 -1
- package/ietf/parameters.d.ts +14 -2
- package/ietf/parameters.d.ts.map +1 -1
- package/ietf/parameters.js +97 -29
- package/ietf/parameters.js.map +1 -1
- package/ietf/properties.d.ts +1 -0
- package/ietf/properties.d.ts.map +1 -1
- package/ietf/properties.js +14 -0
- package/ietf/properties.js.map +1 -1
- package/ietf/publish.d.ts +19 -2
- package/ietf/publish.d.ts.map +1 -1
- package/ietf/publish.js +40 -6
- package/ietf/publish.js.map +1 -1
- package/ietf/publish_namespace.d.ts +25 -0
- package/ietf/publish_namespace.d.ts.map +1 -1
- package/ietf/publish_namespace.js +63 -0
- package/ietf/publish_namespace.js.map +1 -1
- package/ietf/publisher.d.ts +1 -82
- package/ietf/publisher.d.ts.map +1 -1
- package/ietf/publisher.js +483 -235
- package/ietf/publisher.js.map +1 -1
- package/ietf/solicit.d.ts +1 -40
- package/ietf/solicit.d.ts.map +1 -1
- package/ietf/subscribe.d.ts +8 -6
- package/ietf/subscribe.d.ts.map +1 -1
- package/ietf/subscribe.js +33 -27
- package/ietf/subscribe.js.map +1 -1
- package/ietf/subscribe_namespace.d.ts +8 -2
- package/ietf/subscribe_namespace.d.ts.map +1 -1
- package/ietf/subscribe_namespace.js +18 -8
- package/ietf/subscribe_namespace.js.map +1 -1
- package/ietf/subscriber.d.ts +1 -65
- package/ietf/subscriber.d.ts.map +1 -1
- package/ietf/subscriber.js +337 -123
- package/ietf/subscriber.js.map +1 -1
- package/ietf/token.d.ts +2 -0
- package/ietf/token.d.ts.map +1 -0
- package/ietf/token.js +99 -0
- package/ietf/token.js.map +1 -0
- package/ietf/track.d.ts +4 -0
- package/ietf/track.d.ts.map +1 -1
- package/ietf/track.js +6 -20
- package/ietf/track.js.map +1 -1
- package/ietf/version.d.ts +12 -1
- package/ietf/version.d.ts.map +1 -1
- package/ietf/version.js +13 -0
- package/ietf/version.js.map +1 -1
- package/index.d.ts +12 -7
- package/index.d.ts.map +1 -1
- package/index.js +10 -5
- package/index.js.map +1 -1
- package/internal.d.ts +115 -1
- package/internal.d.ts.map +1 -1
- package/internal.js +108 -0
- package/internal.js.map +1 -1
- package/lite/announce.d.ts +60 -10
- package/lite/announce.d.ts.map +1 -1
- package/lite/announce.js +176 -31
- package/lite/announce.js.map +1 -1
- package/lite/connection.d.ts +9 -59
- package/lite/connection.d.ts.map +1 -1
- package/lite/connection.js +34 -35
- package/lite/connection.js.map +1 -1
- package/lite/datagram.d.ts +3 -2
- package/lite/datagram.d.ts.map +1 -1
- package/lite/datagram.js +7 -8
- package/lite/datagram.js.map +1 -1
- package/lite/fetch.d.ts +15 -1
- package/lite/fetch.d.ts.map +1 -1
- package/lite/fetch.js +39 -7
- package/lite/fetch.js.map +1 -1
- package/lite/goaway.d.ts.map +1 -1
- package/lite/goaway.js +7 -1
- package/lite/goaway.js.map +1 -1
- package/lite/group.d.ts +30 -12
- package/lite/group.d.ts.map +1 -1
- package/lite/group.js +68 -26
- package/lite/group.js.map +1 -1
- package/lite/message.d.ts +2 -2
- package/lite/message.d.ts.map +1 -1
- package/lite/message.js +14 -5
- package/lite/message.js.map +1 -1
- package/lite/priority.d.ts +1 -61
- package/lite/priority.d.ts.map +1 -1
- package/lite/priority.js +4 -5
- package/lite/priority.js.map +1 -1
- package/lite/publisher.d.ts +1 -69
- package/lite/publisher.d.ts.map +1 -1
- package/lite/publisher.js +630 -262
- package/lite/publisher.js.map +1 -1
- package/lite/setup.d.ts +8 -8
- package/lite/setup.d.ts.map +1 -1
- package/lite/setup.js +33 -30
- package/lite/setup.js.map +1 -1
- package/lite/subscribe.d.ts +71 -17
- package/lite/subscribe.d.ts.map +1 -1
- package/lite/subscribe.js +205 -53
- package/lite/subscribe.js.map +1 -1
- package/lite/subscriber.d.ts +15 -56
- package/lite/subscriber.d.ts.map +1 -1
- package/lite/subscriber.js +451 -247
- package/lite/subscriber.js.map +1 -1
- package/lite/track.d.ts +4 -10
- package/lite/track.d.ts.map +1 -1
- package/lite/track.js +34 -29
- package/lite/track.js.map +1 -1
- package/lite/version.d.ts +42 -6
- package/lite/version.d.ts.map +1 -1
- package/lite/version.js +126 -10
- package/lite/version.js.map +1 -1
- package/origin.d.ts +256 -29
- package/origin.d.ts.map +1 -1
- package/origin.js +1427 -37
- package/origin.js.map +1 -1
- package/package.json +8 -3
- package/path.d.ts +25 -7
- package/path.d.ts.map +1 -1
- package/path.js +5 -3
- package/path.js.map +1 -1
- package/stream.d.ts +73 -14
- package/stream.d.ts.map +1 -1
- package/stream.js +372 -141
- package/stream.js.map +1 -1
- package/tail.d.ts +18 -0
- package/tail.d.ts.map +1 -0
- package/tail.js +167 -0
- package/tail.js.map +1 -0
- package/time.d.ts +15 -2
- package/time.d.ts.map +1 -1
- package/time.js +28 -9
- package/time.js.map +1 -1
- package/track.d.ts +211 -83
- package/track.d.ts.map +1 -1
- package/track.js +816 -205
- package/track.js.map +1 -1
- package/util/abort.d.ts +2 -0
- package/util/abort.d.ts.map +1 -0
- package/util/abort.js +20 -0
- package/util/abort.js.map +1 -0
- package/util/log.d.ts +5 -0
- package/util/log.d.ts.map +1 -0
- package/util/log.js +17 -0
- package/util/log.js.map +1 -0
- package/util/u64.d.ts +39 -0
- package/util/u64.d.ts.map +1 -0
- package/util/u64.js +83 -0
- package/util/u64.js.map +1 -0
- package/util/varint.d.ts +29 -0
- package/util/varint.d.ts.map +1 -0
- package/util/varint.js +198 -0
- package/util/varint.js.map +1 -0
- package/varint.d.ts +10 -6
- package/varint.d.ts.map +1 -1
- package/varint.js +40 -237
- package/varint.js.map +1 -1
- package/wire.d.ts +80 -0
- package/wire.d.ts.map +1 -0
- package/wire.js +32 -0
- package/wire.js.map +1 -0
- package/zod.d.ts +1 -1
- package/zod.d.ts.map +1 -1
- package/zod.js.map +1 -1
- package/mock.d.ts +0 -66
- package/mock.d.ts.map +0 -1
- package/mock.js +0 -243
- 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-
|
|
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
|
|
9
|
-
*
|
|
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
|
-
*
|
|
18
|
-
*
|
|
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 {
|
|
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
|
-
//
|
|
52
|
-
|
|
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
|
-
|
|
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.
|
|
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 !==
|
|
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
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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 ===
|
|
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`);
|
package/ietf/cluster.js.map
CHANGED
|
@@ -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"]}
|
package/ietf/connection.d.ts
CHANGED
|
@@ -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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
package/ietf/connection.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/ietf/connection.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
|
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({
|
|
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
|
|
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
|
|
273
|
-
|
|
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(() =>
|
|
312
|
+
return this.#quic.closed.then(fromClose, (err) => error(err));
|
|
307
313
|
}
|
|
308
314
|
}
|
|
309
315
|
//# sourceMappingURL=connection.js.map
|