@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/stream.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
/* @ts-self-types="./stream.d.ts" */
|
|
2
|
-
import {
|
|
2
|
+
import { race } from "@norskvideo/moq-signals";
|
|
3
|
+
import { fromTransport, StreamCode, StreamError, toStreamCode, toTransport } from "./error.js";
|
|
3
4
|
import { Version } from "./ietf/version.js";
|
|
5
|
+
import { Version as Lite } from "./lite/version.js";
|
|
4
6
|
import { TimeoutError, withTimeout } from "./util/timeout.js";
|
|
7
|
+
import { POW32, toBigInt, toNumber, U64 } from "./util/u64.js";
|
|
5
8
|
import { decodeUtf8 } from "./util/utf8.js";
|
|
6
|
-
import
|
|
9
|
+
import { lengthLeadingOnes, lengthQuic, parts, peekLeadingOnes, peekQuic, readLeadingOnes, readQuic, split, writeLeadingOnes, writeQuic, } from "./util/varint.js";
|
|
10
|
+
// Decode raw transport errors before mapping so they cannot bypass the negotiated
|
|
11
|
+
// registry. Ordinary errors already send 0 and retain their local identity.
|
|
12
|
+
function withCode(reason, stream) {
|
|
13
|
+
const version = asIetf(stream);
|
|
14
|
+
const decoded = fromTransport(reason, { version });
|
|
15
|
+
const code = toStreamCode(decoded, { version });
|
|
16
|
+
return code === StreamCode.Internal && decoded === reason ? reason : toTransport(code, decoded.message);
|
|
17
|
+
}
|
|
7
18
|
const MAX_U31 = 2 ** 31 - 1;
|
|
8
19
|
const MAX_READ_SIZE = 1024 * 1024 * 64; // don't allocate more than 64MB for a message
|
|
9
20
|
/**
|
|
@@ -35,18 +46,56 @@ async function openWithin(opening, timeout, discard) {
|
|
|
35
46
|
throw err;
|
|
36
47
|
}
|
|
37
48
|
}
|
|
49
|
+
const LITE = new Set(Object.values(Lite));
|
|
50
|
+
function isLite(version) {
|
|
51
|
+
return LITE.has(version);
|
|
52
|
+
}
|
|
53
|
+
/** The moq-transport draft a stream follows, or undefined on moq-lite, whose stream codes are the same on every draft. */
|
|
54
|
+
export function asIetf(version) {
|
|
55
|
+
return isLite(version) ? undefined : version;
|
|
56
|
+
}
|
|
57
|
+
// Every draft newer than these counts leading ones, so a new version falls forward.
|
|
38
58
|
function isLeadingOnes(version) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
59
|
+
switch (version) {
|
|
60
|
+
case Version.DRAFT_14:
|
|
61
|
+
case Version.DRAFT_15:
|
|
62
|
+
case Version.DRAFT_16:
|
|
63
|
+
case Lite.DRAFT_01:
|
|
64
|
+
case Lite.DRAFT_02:
|
|
65
|
+
case Lite.DRAFT_03:
|
|
66
|
+
case Lite.DRAFT_04:
|
|
67
|
+
case Lite.DRAFT_05:
|
|
68
|
+
case Lite.DRAFT_06:
|
|
69
|
+
return false;
|
|
70
|
+
default:
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Encode `hi`/`lo` into `dst` in the varint form `version` uses: QUIC up to 2^62-1, leading-ones up to 2^64-1.
|
|
75
|
+
function encodeTo(dst, hi, lo, version) {
|
|
76
|
+
let buf;
|
|
77
|
+
if (isLeadingOnes(version)) {
|
|
78
|
+
buf = new Uint8Array(dst, 0, lengthLeadingOnes(hi, lo));
|
|
79
|
+
writeLeadingOnes(buf, hi, lo, buf.length);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
buf = new Uint8Array(dst, 0, lengthQuic(hi, lo));
|
|
83
|
+
writeQuic(buf, hi, lo, buf.length);
|
|
84
|
+
}
|
|
85
|
+
return buf;
|
|
86
|
+
}
|
|
87
|
+
/** Encode one varint in the form `version` uses, for a body written outside a {@link Writer}. */
|
|
88
|
+
export function encodeVarint(v, version) {
|
|
89
|
+
const lo = split(v);
|
|
90
|
+
return encodeTo(new ArrayBuffer(9), parts.hi, lo, version);
|
|
43
91
|
}
|
|
44
92
|
export class Stream {
|
|
45
93
|
reader;
|
|
46
94
|
writer;
|
|
47
95
|
constructor(props) {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
96
|
+
const version = props.version;
|
|
97
|
+
const writer = props.writer ?? (props.writable && version !== undefined && new Writer(props.writable, version));
|
|
98
|
+
const reader = props.reader ?? (props.readable && version !== undefined && new Reader(props.readable, undefined, version));
|
|
50
99
|
if (!writer || !reader)
|
|
51
100
|
throw new Error("stream needs both halves");
|
|
52
101
|
this.writer = writer;
|
|
@@ -74,11 +123,14 @@ export class Stream {
|
|
|
74
123
|
void stream.writable.abort().catch(() => void 0);
|
|
75
124
|
void stream.readable.cancel().catch(() => void 0);
|
|
76
125
|
});
|
|
77
|
-
return new Stream({ readable, writable, version: options
|
|
126
|
+
return new Stream({ readable, writable, version: options.version });
|
|
78
127
|
}
|
|
79
128
|
close() {
|
|
80
129
|
this.writer.close();
|
|
81
|
-
|
|
130
|
+
// A routine unsubscribe, so send CANCELLED. A bare Error would put 0 on the wire,
|
|
131
|
+
// which the stream registry reads as INTERNAL_ERROR: the peer would log a failure
|
|
132
|
+
// for every subscription we walk away from.
|
|
133
|
+
this.reader.stop(new StreamError(StreamCode.Cancel, { message: "cancel" }));
|
|
82
134
|
}
|
|
83
135
|
abort(reason) {
|
|
84
136
|
this.writer.reset(reason);
|
|
@@ -88,10 +140,16 @@ export class Stream {
|
|
|
88
140
|
// Reader wraps a stream and provides convience methods for reading pieces from a stream
|
|
89
141
|
// Unfortunately we can't use a BYOB reader because it's not supported with WebTransport+WebWorkers yet.
|
|
90
142
|
export class Reader {
|
|
143
|
+
// Contiguous unread bytes, followed by chunks not yet joined onto it. Joining only once a
|
|
144
|
+
// read needs the bytes keeps a frame arriving in N chunks linear rather than quadratic.
|
|
91
145
|
#buffer;
|
|
146
|
+
#chunks = [];
|
|
147
|
+
#chunked = 0; // bytes across #chunks
|
|
92
148
|
#stream; // if undefined, the buffer is consumed then EOF
|
|
93
149
|
#reader;
|
|
94
150
|
#closed;
|
|
151
|
+
// The decode that last ran short and how far, so a retry can wait for those bytes.
|
|
152
|
+
#short;
|
|
95
153
|
version;
|
|
96
154
|
constructor(stream, buffer, version) {
|
|
97
155
|
this.#buffer = buffer ?? new Uint8Array();
|
|
@@ -107,7 +165,7 @@ export class Reader {
|
|
|
107
165
|
// Every read of this stream funnels through here, so decoding the peer's reset code
|
|
108
166
|
// once is enough to keep the raw transport error out of every caller (and every app).
|
|
109
167
|
const result = await this.#reader.read().catch((err) => {
|
|
110
|
-
throw fromTransport(err);
|
|
168
|
+
throw fromTransport(err, { version: asIetf(this.version) });
|
|
111
169
|
});
|
|
112
170
|
if (result.done) {
|
|
113
171
|
return false;
|
|
@@ -115,16 +173,8 @@ export class Reader {
|
|
|
115
173
|
if (result.value.byteLength === 0) {
|
|
116
174
|
throw new Error("unexpected empty chunk");
|
|
117
175
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.#buffer = buffer;
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
const temp = new Uint8Array(this.#buffer.byteLength + buffer.byteLength);
|
|
124
|
-
temp.set(this.#buffer);
|
|
125
|
-
temp.set(buffer, this.#buffer.byteLength);
|
|
126
|
-
this.#buffer = temp;
|
|
127
|
-
}
|
|
176
|
+
this.#chunks.push(result.value);
|
|
177
|
+
this.#chunked += result.value.byteLength;
|
|
128
178
|
return true;
|
|
129
179
|
}
|
|
130
180
|
// Add more data to the buffer until it's at least size bytes.
|
|
@@ -132,11 +182,34 @@ export class Reader {
|
|
|
132
182
|
if (size > MAX_READ_SIZE) {
|
|
133
183
|
throw new Error(`read size ${size} exceeds max size ${MAX_READ_SIZE}`);
|
|
134
184
|
}
|
|
135
|
-
|
|
185
|
+
if (this.#buffer.byteLength >= size)
|
|
186
|
+
return;
|
|
187
|
+
while (this.#buffer.byteLength + this.#chunked < size) {
|
|
136
188
|
if (!(await this.#fill())) {
|
|
137
189
|
throw new Error("unexpected end of stream");
|
|
138
190
|
}
|
|
139
191
|
}
|
|
192
|
+
this.#join();
|
|
193
|
+
}
|
|
194
|
+
// Move every pending chunk into the buffer, copying only when there's more than one piece.
|
|
195
|
+
#join() {
|
|
196
|
+
if (this.#chunks.length === 0)
|
|
197
|
+
return;
|
|
198
|
+
if (this.#buffer.byteLength === 0 && this.#chunks.length === 1) {
|
|
199
|
+
this.#buffer = this.#chunks[0];
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const joined = new Uint8Array(this.#buffer.byteLength + this.#chunked);
|
|
203
|
+
joined.set(this.#buffer);
|
|
204
|
+
let offset = this.#buffer.byteLength;
|
|
205
|
+
for (const chunk of this.#chunks) {
|
|
206
|
+
joined.set(chunk, offset);
|
|
207
|
+
offset += chunk.byteLength;
|
|
208
|
+
}
|
|
209
|
+
this.#buffer = joined;
|
|
210
|
+
}
|
|
211
|
+
this.#chunks = [];
|
|
212
|
+
this.#chunked = 0;
|
|
140
213
|
}
|
|
141
214
|
// Consumes the first size bytes of the buffer.
|
|
142
215
|
#slice(size) {
|
|
@@ -144,135 +217,293 @@ export class Reader {
|
|
|
144
217
|
this.#buffer = new Uint8Array(this.#buffer.buffer, this.#buffer.byteOffset + size, this.#buffer.byteLength - size);
|
|
145
218
|
return result;
|
|
146
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Run a synchronous decode over the buffered bytes and consume what it read.
|
|
222
|
+
*
|
|
223
|
+
* Returns undefined and consumes nothing when the decode ran past the buffered bytes, so a
|
|
224
|
+
* caller can drain every complete message already here without waiting on the stream.
|
|
225
|
+
*/
|
|
226
|
+
tryDecode(decode) {
|
|
227
|
+
const result = this.#try(decode);
|
|
228
|
+
return result instanceof Short ? undefined : result;
|
|
229
|
+
}
|
|
230
|
+
/** Run a synchronous decode, filling from the stream until it has the bytes it needs. */
|
|
231
|
+
async decode(decode) {
|
|
232
|
+
for (;;) {
|
|
233
|
+
const result = this.#try(decode);
|
|
234
|
+
if (!(result instanceof Short))
|
|
235
|
+
return result;
|
|
236
|
+
await this.#fillTo(result.need);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Like {@link decode}, but returns undefined if the stream ends cleanly first. */
|
|
240
|
+
async decodeMaybe(decode) {
|
|
241
|
+
if (await this.done())
|
|
242
|
+
return undefined;
|
|
243
|
+
return this.decode(decode);
|
|
244
|
+
}
|
|
245
|
+
#try(decode) {
|
|
246
|
+
// A retry of the decode that last ran short, before the bytes it needs have arrived,
|
|
247
|
+
// would only throw again. Every decode reads at least a byte, so none can succeed on
|
|
248
|
+
// an empty buffer either.
|
|
249
|
+
const available = this.#buffer.byteLength + this.#chunked;
|
|
250
|
+
if (available === 0)
|
|
251
|
+
return EMPTY;
|
|
252
|
+
if (decode === this.#short?.decode && available < this.#short.err.need)
|
|
253
|
+
return this.#short.err;
|
|
254
|
+
this.#join();
|
|
255
|
+
const cursor = new Cursor(this.#buffer, this.version);
|
|
256
|
+
try {
|
|
257
|
+
const result = decode(cursor);
|
|
258
|
+
this.#slice(cursor.offset);
|
|
259
|
+
this.#short = undefined;
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
if (!(err instanceof Short))
|
|
264
|
+
throw err;
|
|
265
|
+
// Filling could never satisfy it, so retrying would spin.
|
|
266
|
+
if (err.need <= this.#buffer.byteLength)
|
|
267
|
+
throw new Error("decode ran short of bytes it already had");
|
|
268
|
+
this.#short = { decode, err };
|
|
269
|
+
return err;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
147
272
|
async read(size) {
|
|
148
273
|
if (size === 0)
|
|
149
274
|
return new Uint8Array();
|
|
150
|
-
|
|
151
|
-
return this.#slice(size);
|
|
275
|
+
return this.decode((c) => c.read(size));
|
|
152
276
|
}
|
|
153
277
|
async readAll() {
|
|
154
278
|
while (await this.#fill()) {
|
|
155
279
|
// keep going
|
|
156
280
|
}
|
|
281
|
+
this.#join();
|
|
157
282
|
return this.#slice(this.#buffer.byteLength);
|
|
158
283
|
}
|
|
284
|
+
// Reads to the end of the stream, dropping every byte instead of buffering it.
|
|
285
|
+
async discard() {
|
|
286
|
+
this.#buffer = new Uint8Array();
|
|
287
|
+
do {
|
|
288
|
+
this.#chunks = [];
|
|
289
|
+
this.#chunked = 0;
|
|
290
|
+
} while (await this.#fill());
|
|
291
|
+
}
|
|
159
292
|
async string() {
|
|
160
|
-
|
|
161
|
-
const buffer = await this.read(length);
|
|
162
|
-
return decodeUtf8(buffer);
|
|
293
|
+
return this.decode(STRING);
|
|
163
294
|
}
|
|
164
295
|
async bool() {
|
|
165
|
-
|
|
166
|
-
if (v === 0)
|
|
167
|
-
return false;
|
|
168
|
-
if (v === 1)
|
|
169
|
-
return true;
|
|
170
|
-
throw new Error("invalid bool value");
|
|
296
|
+
return this.decode(BOOL);
|
|
171
297
|
}
|
|
172
298
|
async u8() {
|
|
173
|
-
|
|
174
|
-
return this.#slice(1)[0];
|
|
299
|
+
return this.decode(U8);
|
|
175
300
|
}
|
|
176
301
|
async u16() {
|
|
177
|
-
|
|
178
|
-
const view = new DataView(this.#buffer.buffer, this.#buffer.byteOffset, 2);
|
|
179
|
-
const result = view.getUint16(0);
|
|
180
|
-
this.#slice(2);
|
|
181
|
-
return result;
|
|
302
|
+
return this.decode(U16);
|
|
182
303
|
}
|
|
183
304
|
// Returns a Number using 53-bits, the max Javascript can use for integer math.
|
|
184
305
|
async u53() {
|
|
185
|
-
|
|
186
|
-
if (v > Varint.MAX_U53) {
|
|
187
|
-
throw new Error(`value larger than 53-bits: ${v.toString()}`);
|
|
188
|
-
}
|
|
189
|
-
return Number(v);
|
|
306
|
+
return this.decode(U53);
|
|
190
307
|
}
|
|
191
308
|
// NOTE: Returns a bigint instead of a number since it may be larger than 53-bits
|
|
192
309
|
async u62() {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return this
|
|
197
|
-
}
|
|
198
|
-
async #readQuicVarint() {
|
|
199
|
-
await this.#fillTo(1);
|
|
200
|
-
const size = (this.#buffer[0] & 0xc0) >> 6;
|
|
201
|
-
if (size === 0) {
|
|
202
|
-
const first = this.#slice(1)[0];
|
|
203
|
-
return BigInt(first) & 0x3fn;
|
|
204
|
-
}
|
|
205
|
-
if (size === 1) {
|
|
206
|
-
await this.#fillTo(2);
|
|
207
|
-
const slice = this.#slice(2);
|
|
208
|
-
const view = new DataView(slice.buffer, slice.byteOffset, slice.byteLength);
|
|
209
|
-
return BigInt(view.getUint16(0)) & 0x3fffn;
|
|
210
|
-
}
|
|
211
|
-
if (size === 2) {
|
|
212
|
-
await this.#fillTo(4);
|
|
213
|
-
const slice = this.#slice(4);
|
|
214
|
-
const view = new DataView(slice.buffer, slice.byteOffset, slice.byteLength);
|
|
215
|
-
return BigInt(view.getUint32(0)) & 0x3fffffffn;
|
|
216
|
-
}
|
|
217
|
-
await this.#fillTo(8);
|
|
218
|
-
const slice = this.#slice(8);
|
|
219
|
-
const view = new DataView(slice.buffer, slice.byteOffset, slice.byteLength);
|
|
220
|
-
return view.getBigUint64(0) & 0x3fffffffffffffffn;
|
|
221
|
-
}
|
|
222
|
-
async #readLeadingOnes() {
|
|
223
|
-
await this.#fillTo(1);
|
|
224
|
-
const b = this.#buffer[0];
|
|
225
|
-
// Count leading 1-bits
|
|
226
|
-
let ones = 0;
|
|
227
|
-
for (let bit = 7; bit >= 0; bit--) {
|
|
228
|
-
if (b & (1 << bit))
|
|
229
|
-
ones++;
|
|
230
|
-
else
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
// 1111110x is a 7-byte form. Draft-17 rejects it; draft-18+ allows it per #1595.
|
|
234
|
-
if (ones === 6 && this.version === Version.DRAFT_17) {
|
|
235
|
-
throw new Error("invalid leading-ones varint: 1111110x prefix is reserved on draft-17");
|
|
236
|
-
}
|
|
237
|
-
let totalSize;
|
|
238
|
-
if (ones <= 5)
|
|
239
|
-
totalSize = ones + 1;
|
|
240
|
-
else if (ones === 6)
|
|
241
|
-
totalSize = 7;
|
|
242
|
-
else if (ones === 7)
|
|
243
|
-
totalSize = 8;
|
|
244
|
-
else
|
|
245
|
-
totalSize = 9; // ones === 8
|
|
246
|
-
await this.#fillTo(totalSize);
|
|
247
|
-
const slice = this.#slice(totalSize);
|
|
248
|
-
const [value] = Varint.decodeLeadingOnes(slice);
|
|
249
|
-
return value;
|
|
310
|
+
return this.decode(U62);
|
|
311
|
+
}
|
|
312
|
+
async varint() {
|
|
313
|
+
return this.decode(VARINT);
|
|
250
314
|
}
|
|
251
315
|
// Returns false if there is more data to read, blocking if it hasn't been received yet.
|
|
252
316
|
async done() {
|
|
253
|
-
if (this.#buffer.byteLength > 0)
|
|
317
|
+
if (this.#buffer.byteLength > 0 || this.#chunked > 0)
|
|
254
318
|
return false;
|
|
255
319
|
return !(await this.#fill());
|
|
256
320
|
}
|
|
257
321
|
stop(reason) {
|
|
258
|
-
this.#reader?.cancel(reason).catch(() => void 0);
|
|
322
|
+
this.#reader?.cancel(withCode(reason, this.version)).catch(() => void 0);
|
|
259
323
|
}
|
|
260
324
|
// Decoded like #fill: a caller racing this against a read must not get a different error
|
|
261
325
|
// shape depending on which one won. Derived once, so racing it per frame doesn't allocate.
|
|
262
326
|
get closed() {
|
|
263
327
|
this.#closed ??= (this.#reader?.closed ?? Promise.resolve()).catch((err) => {
|
|
264
|
-
throw fromTransport(err);
|
|
328
|
+
throw fromTransport(err, { version: asIetf(this.version) });
|
|
265
329
|
});
|
|
266
330
|
return this.#closed;
|
|
267
331
|
}
|
|
268
332
|
}
|
|
333
|
+
// Thrown by a Cursor read that runs past the buffered bytes, carrying how many bytes from the
|
|
334
|
+
// start of the buffer the decode needs. Not an Error: it ends every chunk, so it must not
|
|
335
|
+
// capture a stack.
|
|
336
|
+
class Short {
|
|
337
|
+
need;
|
|
338
|
+
constructor(need) {
|
|
339
|
+
this.need = need;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const EMPTY = new Short(1);
|
|
343
|
+
/**
|
|
344
|
+
* A synchronous view over a {@link Reader}'s buffered bytes, handed to {@link Reader.decode}.
|
|
345
|
+
*
|
|
346
|
+
* A read past the buffered bytes throws an internal signal that the Reader catches: it consumes
|
|
347
|
+
* nothing, fills, and runs the decode again from the start. A decode must therefore not mutate
|
|
348
|
+
* anything before its last read, must not swallow what it throws, and must read at least a byte.
|
|
349
|
+
*/
|
|
350
|
+
export class Cursor {
|
|
351
|
+
version;
|
|
352
|
+
#buffer;
|
|
353
|
+
#offset = 0;
|
|
354
|
+
// Resolved once, since every varint read branches on it.
|
|
355
|
+
#leadingOnes;
|
|
356
|
+
// First bytes below this are a whole 1-byte varint, and below this + 0x40 a 2-byte one whose
|
|
357
|
+
// value is the low 6 bits and the next byte. Both formats share that shape; only the bound moves.
|
|
358
|
+
#short;
|
|
359
|
+
// First bytes below this are a varint of at most 4 bytes: 0xc0 for QUIC, 0xf0 for leading-ones.
|
|
360
|
+
#word;
|
|
361
|
+
constructor(buffer, version) {
|
|
362
|
+
this.#buffer = buffer;
|
|
363
|
+
this.version = version;
|
|
364
|
+
this.#leadingOnes = isLeadingOnes(version);
|
|
365
|
+
this.#short = this.#leadingOnes ? 0x80 : 0x40;
|
|
366
|
+
this.#word = this.#leadingOnes ? 0xf0 : 0xc0;
|
|
367
|
+
}
|
|
368
|
+
/** How many bytes have been read. */
|
|
369
|
+
get offset() {
|
|
370
|
+
return this.#offset;
|
|
371
|
+
}
|
|
372
|
+
/** How many buffered bytes are left to read. */
|
|
373
|
+
get remaining() {
|
|
374
|
+
return this.#buffer.byteLength - this.#offset;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Decode the next `size` bytes on their own. Running past them, or leaving any unread, is
|
|
378
|
+
* malformed rather than a reason to wait for more.
|
|
379
|
+
*/
|
|
380
|
+
exact(size, decode) {
|
|
381
|
+
const inner = new Cursor(this.read(size), this.version);
|
|
382
|
+
let result;
|
|
383
|
+
try {
|
|
384
|
+
result = decode(inner);
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
if (err instanceof Short)
|
|
388
|
+
throw new Error(`message is shorter than its fields: ${size} bytes`);
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
391
|
+
if (inner.remaining > 0)
|
|
392
|
+
throw new Error(`message has ${inner.remaining} unread bytes`);
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
#ensure(size) {
|
|
396
|
+
const need = this.#offset + size;
|
|
397
|
+
// Checked here too, and on the whole decode like the fill, since bytes that are already
|
|
398
|
+
// buffered never reach the fill.
|
|
399
|
+
if (need > MAX_READ_SIZE)
|
|
400
|
+
throw new Error(`read size ${need} exceeds max size ${MAX_READ_SIZE}`);
|
|
401
|
+
if (need > this.#buffer.byteLength)
|
|
402
|
+
throw new Short(need);
|
|
403
|
+
}
|
|
404
|
+
/** Read `size` bytes, as a view onto the buffer rather than a copy. */
|
|
405
|
+
read(size) {
|
|
406
|
+
this.#ensure(size);
|
|
407
|
+
const start = this.#offset;
|
|
408
|
+
this.#offset += size;
|
|
409
|
+
return this.#buffer.subarray(start, this.#offset);
|
|
410
|
+
}
|
|
411
|
+
string() {
|
|
412
|
+
return decodeUtf8(this.read(this.u53()));
|
|
413
|
+
}
|
|
414
|
+
bool() {
|
|
415
|
+
const v = this.u8();
|
|
416
|
+
if (v === 0)
|
|
417
|
+
return false;
|
|
418
|
+
if (v === 1)
|
|
419
|
+
return true;
|
|
420
|
+
throw new Error("invalid bool value");
|
|
421
|
+
}
|
|
422
|
+
u8() {
|
|
423
|
+
this.#ensure(1);
|
|
424
|
+
return this.#buffer[this.#offset++];
|
|
425
|
+
}
|
|
426
|
+
u16() {
|
|
427
|
+
this.#ensure(2);
|
|
428
|
+
const b = this.#buffer;
|
|
429
|
+
const o = this.#offset;
|
|
430
|
+
this.#offset += 2;
|
|
431
|
+
return (b[o] << 8) | b[o + 1];
|
|
432
|
+
}
|
|
433
|
+
/** Read a varint as a `number`, throwing if it is above `Number.MAX_SAFE_INTEGER`. */
|
|
434
|
+
u53() {
|
|
435
|
+
// Most varints are 1 or 2 bytes, which skip the general decode.
|
|
436
|
+
this.#ensure(1);
|
|
437
|
+
const b = this.#buffer;
|
|
438
|
+
const o = this.#offset;
|
|
439
|
+
const first = b[o];
|
|
440
|
+
if (first < this.#short) {
|
|
441
|
+
this.#offset = o + 1;
|
|
442
|
+
return first;
|
|
443
|
+
}
|
|
444
|
+
if (first < this.#short + 0x40) {
|
|
445
|
+
this.#ensure(2);
|
|
446
|
+
this.#offset = o + 2;
|
|
447
|
+
return ((first & 0x3f) << 8) | b[o + 1];
|
|
448
|
+
}
|
|
449
|
+
// Up to 4 bytes still fits 28 (leading-ones) or 30 (QUIC) bits, with no upper half.
|
|
450
|
+
if (first < this.#word) {
|
|
451
|
+
const size = this.#leadingOnes ? peekLeadingOnes(first) : 4;
|
|
452
|
+
this.#ensure(size);
|
|
453
|
+
this.#offset = o + size;
|
|
454
|
+
if (size === 3)
|
|
455
|
+
return ((first & 0x1f) << 16) | (b[o + 1] << 8) | b[o + 2];
|
|
456
|
+
return ((first & (this.#leadingOnes ? 0x0f : 0x3f)) << 24) | (b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3];
|
|
457
|
+
}
|
|
458
|
+
const lo = this.#varint();
|
|
459
|
+
return toNumber(parts.hi, lo);
|
|
460
|
+
}
|
|
461
|
+
/** Read a varint as a bigint. A leading-ones varint may exceed 62 bits. */
|
|
462
|
+
u62() {
|
|
463
|
+
const lo = this.#varint();
|
|
464
|
+
return toBigInt(parts.hi, lo);
|
|
465
|
+
}
|
|
466
|
+
/** Read a varint. */
|
|
467
|
+
varint() {
|
|
468
|
+
const lo = this.#varint();
|
|
469
|
+
return new U64(parts.hi, lo);
|
|
470
|
+
}
|
|
471
|
+
// Decode the next varint in the version's format, returning its lower half and leaving the upper in `parts`.
|
|
472
|
+
#varint() {
|
|
473
|
+
this.#ensure(1);
|
|
474
|
+
const b = this.#buffer;
|
|
475
|
+
const o = this.#offset;
|
|
476
|
+
let size;
|
|
477
|
+
if (this.#leadingOnes) {
|
|
478
|
+
size = peekLeadingOnes(b[o]);
|
|
479
|
+
// 1111110x is a 7-byte form. Draft-17 rejects it; draft-18+ allows it per #1595.
|
|
480
|
+
if (size === 7 && this.version === Version.DRAFT_17) {
|
|
481
|
+
throw new Error("invalid leading-ones varint: 1111110x prefix is reserved on draft-17");
|
|
482
|
+
}
|
|
483
|
+
this.#ensure(size);
|
|
484
|
+
this.#offset += size;
|
|
485
|
+
return readLeadingOnes(b, o, size);
|
|
486
|
+
}
|
|
487
|
+
size = peekQuic(b[o]);
|
|
488
|
+
this.#ensure(size);
|
|
489
|
+
this.#offset += size;
|
|
490
|
+
return readQuic(b, o, size);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// Shared decodes for the Reader's async primitives, so a read allocates no closure.
|
|
494
|
+
const STRING = (c) => c.string();
|
|
495
|
+
const BOOL = (c) => c.bool();
|
|
496
|
+
const U8 = (c) => c.u8();
|
|
497
|
+
const U16 = (c) => c.u16();
|
|
498
|
+
const U53 = (c) => c.u53();
|
|
499
|
+
const U62 = (c) => c.u62();
|
|
500
|
+
const VARINT = (c) => c.varint();
|
|
269
501
|
// Writer wraps a stream and writes chunks of data
|
|
270
502
|
export class Writer {
|
|
271
503
|
#writer;
|
|
272
504
|
#stream;
|
|
273
505
|
#closed;
|
|
274
|
-
// Scratch buffer for
|
|
275
|
-
// Fixed at 9 bytes (leading-ones max).
|
|
506
|
+
// Scratch buffer for each primitive write, sized for the longest (a 9-byte leading-ones varint).
|
|
276
507
|
#scratch;
|
|
277
508
|
version;
|
|
278
509
|
constructor(stream, version) {
|
|
@@ -298,6 +529,9 @@ export class Writer {
|
|
|
298
529
|
await this.write(setUint8(this.#scratch, v ? 1 : 0));
|
|
299
530
|
}
|
|
300
531
|
async u8(v) {
|
|
532
|
+
if (!Number.isInteger(v) || v < 0 || v > 255) {
|
|
533
|
+
throw new RangeError(`invalid u8: ${v}`);
|
|
534
|
+
}
|
|
301
535
|
await this.write(setUint8(this.#scratch, v));
|
|
302
536
|
}
|
|
303
537
|
async u16(v) {
|
|
@@ -307,38 +541,31 @@ export class Writer {
|
|
|
307
541
|
if (Math.abs(v) > MAX_U31) {
|
|
308
542
|
throw new Error(`overflow, value larger than 32-bits: ${v.toString()}`);
|
|
309
543
|
}
|
|
310
|
-
// We don't use a
|
|
544
|
+
// We don't use a varint, so it always takes 4 bytes.
|
|
311
545
|
// This could be improved but nothing is standardized yet.
|
|
312
546
|
await this.write(setInt32(this.#scratch, v));
|
|
313
547
|
}
|
|
314
548
|
async u53(v) {
|
|
315
|
-
if (v
|
|
316
|
-
|
|
317
|
-
// the wire, but downgrade overflow to warn so an upstream miscount
|
|
318
|
-
// doesn't tear down the whole stream. The encoded varint will reflect
|
|
319
|
-
// the truncated Number value.
|
|
320
|
-
console.warn(`value larger than 53-bits; use u62 instead (precision lost): ${v.toString()}`);
|
|
321
|
-
}
|
|
322
|
-
if (isLeadingOnes(this.version)) {
|
|
323
|
-
await this.write(Varint.encodeLeadingOnesTo(this.#scratch, v));
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
await this.write(Varint.encodeTo(this.#scratch, v));
|
|
549
|
+
if (!Number.isSafeInteger(v) || v < 0) {
|
|
550
|
+
throw new RangeError(`invalid u53: ${v}`);
|
|
327
551
|
}
|
|
552
|
+
await this.#varint(Math.floor(v / POW32), v >>> 0);
|
|
328
553
|
}
|
|
329
554
|
async u62(v) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
555
|
+
const lo = split(v);
|
|
556
|
+
await this.#varint(parts.hi, lo);
|
|
557
|
+
}
|
|
558
|
+
async varint(v) {
|
|
559
|
+
await this.#varint(v.hi, v.lo);
|
|
560
|
+
}
|
|
561
|
+
#varint(hi, lo) {
|
|
562
|
+
return this.write(encodeTo(this.#scratch, hi, lo, this.version));
|
|
336
563
|
}
|
|
337
564
|
async write(v) {
|
|
338
565
|
// Mirrors Reader.#fill: every write funnels through here, so a STOP_SENDING from the
|
|
339
566
|
// peer surfaces as a typed code rather than the transport's own error shape.
|
|
340
567
|
await this.#writer.write(v).catch((err) => {
|
|
341
|
-
throw fromTransport(err);
|
|
568
|
+
throw fromTransport(err, { version: asIetf(this.version) });
|
|
342
569
|
});
|
|
343
570
|
}
|
|
344
571
|
async string(str) {
|
|
@@ -353,12 +580,12 @@ export class Writer {
|
|
|
353
580
|
// typed code it would get from a write.
|
|
354
581
|
get closed() {
|
|
355
582
|
this.#closed ??= this.#writer.closed.catch((err) => {
|
|
356
|
-
throw fromTransport(err);
|
|
583
|
+
throw fromTransport(err, { version: asIetf(this.version) });
|
|
357
584
|
});
|
|
358
585
|
return this.#closed;
|
|
359
586
|
}
|
|
360
587
|
reset(reason) {
|
|
361
|
-
this.#writer.abort(reason).catch(() => void 0);
|
|
588
|
+
this.#writer.abort(withCode(reason, this.version)).catch(() => void 0);
|
|
362
589
|
}
|
|
363
590
|
/**
|
|
364
591
|
* Open an outgoing unidirectional stream.
|
|
@@ -368,7 +595,7 @@ export class Writer {
|
|
|
368
595
|
*/
|
|
369
596
|
static async open(quic, options) {
|
|
370
597
|
const writable = await openWithin(quic.createUnidirectionalStream(sendOptions(options)), options?.timeout ?? OPEN_TIMEOUT_MS, (stream) => void stream.abort().catch(() => void 0));
|
|
371
|
-
return new Writer(writable, options
|
|
598
|
+
return new Writer(writable, options.version);
|
|
372
599
|
}
|
|
373
600
|
/**
|
|
374
601
|
* Like {@link Writer.open}, but gives up when `cancel` settles or `timeout` elapses,
|
|
@@ -379,25 +606,29 @@ export class Writer {
|
|
|
379
606
|
* an over-limit open instead of rejecting it.
|
|
380
607
|
*/
|
|
381
608
|
static async tryOpen(quic, options) {
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
// is free right now.
|
|
386
|
-
const cancelled = options.cancel.then(() => undefined, () => undefined);
|
|
609
|
+
// Raced ahead of the open, so an already-cancelled caller wins even against a slot
|
|
610
|
+
// that is free right now. `race` rather than `Promise.race`: the caller shares one
|
|
611
|
+
// `cancel` across every group of a subscription, which must not gain a reaction per call.
|
|
387
612
|
const open = Writer.open(quic, options);
|
|
613
|
+
// Resets a stream that opens after we gave up; a no-op if the open itself failed.
|
|
614
|
+
const abandon = () => {
|
|
615
|
+
const abandoned = new Error("abandoned waiting for a stream slot");
|
|
616
|
+
open.then((w) => w.reset(abandoned)).catch(() => void 0);
|
|
617
|
+
};
|
|
388
618
|
try {
|
|
389
|
-
const stream = await
|
|
619
|
+
const stream = await race([options.cancel, open]);
|
|
390
620
|
if (stream)
|
|
391
621
|
return stream;
|
|
392
622
|
}
|
|
393
623
|
catch (err) {
|
|
394
624
|
// open already discarded the late stream on its way out.
|
|
395
|
-
if (
|
|
396
|
-
|
|
397
|
-
|
|
625
|
+
if (err instanceof TimeoutError)
|
|
626
|
+
return undefined;
|
|
627
|
+
// A rejected `cancel` still leaves the open pending.
|
|
628
|
+
abandon();
|
|
629
|
+
throw err;
|
|
398
630
|
}
|
|
399
|
-
|
|
400
|
-
open.then((w) => w.reset(abandoned)).catch(() => void 0);
|
|
631
|
+
abandon();
|
|
401
632
|
return undefined;
|
|
402
633
|
}
|
|
403
634
|
}
|