bolthole 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/mux.js +51 -4
- package/src/version.js +1 -1
package/package.json
CHANGED
package/src/mux.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import { concat, enc, dec, u32, readU32, deferred } from './bytes.js';
|
|
4
4
|
|
|
5
5
|
export const T = { OPEN: 1, ACCEPT: 2, DATA: 3, FIN: 4, RESET: 5, WINDOW: 6, PING: 7, PONG: 8, GOAWAY: 9 };
|
|
6
|
-
export const INITIAL_WINDOW =
|
|
6
|
+
export const INITIAL_WINDOW = 4 * 1024 * 1024;
|
|
7
|
+
export const SESSION_WINDOW = 64 * 1024 * 1024;
|
|
7
8
|
export const MAX_CHUNK = 32 * 1024;
|
|
8
9
|
export const DEFAULT_MAX_FRAME = (1 << 20) + 5;
|
|
9
10
|
|
|
@@ -54,6 +55,11 @@ export class Session {
|
|
|
54
55
|
this.lastRecv = Date.now();
|
|
55
56
|
this.lastSend = Date.now();
|
|
56
57
|
this.tail = Promise.resolve();
|
|
58
|
+
// Session-level flow control (spec §3.4), shared by all streams.
|
|
59
|
+
this.sendCredit = SESSION_WINDOW;
|
|
60
|
+
this.recvCredit = SESSION_WINDOW;
|
|
61
|
+
this.recvUnack = 0;
|
|
62
|
+
this._wake = deferred();
|
|
57
63
|
this.readLoop();
|
|
58
64
|
if (this.heartbeatMs > 0) {
|
|
59
65
|
this.timer = setInterval(() => this.tick(), this.heartbeatMs / 4);
|
|
@@ -136,6 +142,17 @@ export class Session {
|
|
|
136
142
|
}
|
|
137
143
|
}
|
|
138
144
|
|
|
145
|
+
_release(n) {
|
|
146
|
+
if (n <= 0) return;
|
|
147
|
+
this.recvUnack += n;
|
|
148
|
+
if (this.recvUnack >= SESSION_WINDOW / 2) {
|
|
149
|
+
const credit = this.recvUnack;
|
|
150
|
+
this.recvUnack = 0;
|
|
151
|
+
this.recvCredit += credit;
|
|
152
|
+
this.send(T.WINDOW, 0, u32(credit)).catch(() => {});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
139
156
|
isPeerId(id) {
|
|
140
157
|
return id !== 0 && (id % 2 === 1) !== this.dialer;
|
|
141
158
|
}
|
|
@@ -177,8 +194,22 @@ export class Session {
|
|
|
177
194
|
case T.FIN:
|
|
178
195
|
case T.RESET:
|
|
179
196
|
case T.WINDOW: {
|
|
197
|
+
if (f.type === T.WINDOW && f.id === 0) {
|
|
198
|
+
if (f.payload.length !== 4) throw new Error('bolthole: bad session window');
|
|
199
|
+
this.sendCredit += readU32(f.payload);
|
|
200
|
+
if (this.sendCredit > 2 ** 31) throw new Error('bolthole: session window overflow');
|
|
201
|
+
const w = this._wake;
|
|
202
|
+
this._wake = deferred();
|
|
203
|
+
w.resolve();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (f.type === T.DATA) {
|
|
207
|
+
if (f.payload.length > this.recvCredit) throw new Error('bolthole: session window exceeded');
|
|
208
|
+
this.recvCredit -= f.payload.length;
|
|
209
|
+
}
|
|
180
210
|
const st = this.streams.get(f.id);
|
|
181
211
|
if (!st) {
|
|
212
|
+
if (f.type === T.DATA) this._release(f.payload.length);
|
|
182
213
|
if (f.type === T.ACCEPT || f.type === T.DATA) this.send(T.RESET, f.id, json({ code: 'unknown_stream' })).catch(() => {});
|
|
183
214
|
return;
|
|
184
215
|
}
|
|
@@ -203,6 +234,7 @@ export class Stream {
|
|
|
203
234
|
this.recvFin = false;
|
|
204
235
|
this.recvCredit = INITIAL_WINDOW;
|
|
205
236
|
this.unacked = 0;
|
|
237
|
+
this.held = 0; // buffered bytes still charged to the session window
|
|
206
238
|
this.sendCredit = INITIAL_WINDOW;
|
|
207
239
|
this.localFin = false;
|
|
208
240
|
this.isAccepted = false;
|
|
@@ -243,6 +275,9 @@ export class Stream {
|
|
|
243
275
|
const chunk = this.buf.shift();
|
|
244
276
|
this.bufLen -= chunk.length;
|
|
245
277
|
this.unacked += chunk.length;
|
|
278
|
+
const release = Math.min(chunk.length, this.held);
|
|
279
|
+
this.held -= release;
|
|
280
|
+
this.session._release(release);
|
|
246
281
|
if (this.unacked >= INITIAL_WINDOW / 2 && !this.recvFin && !this.error) {
|
|
247
282
|
const credit = this.unacked;
|
|
248
283
|
this.unacked = 0;
|
|
@@ -275,8 +310,14 @@ export class Stream {
|
|
|
275
310
|
await this._wake.promise;
|
|
276
311
|
continue;
|
|
277
312
|
}
|
|
278
|
-
const
|
|
313
|
+
const s = this.session;
|
|
314
|
+
if (s.sendCredit <= 0) {
|
|
315
|
+
await Promise.race([this._wake.promise, s._wake.promise]);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const n = Math.min(data.length - off, MAX_CHUNK, this.sendCredit, s.sendCredit);
|
|
279
319
|
this.sendCredit -= n;
|
|
320
|
+
s.sendCredit -= n;
|
|
280
321
|
await this.session.send(T.DATA, this.id, data.subarray(off, off + n));
|
|
281
322
|
off += n;
|
|
282
323
|
}
|
|
@@ -308,6 +349,8 @@ export class Stream {
|
|
|
308
349
|
if (!this.error && !this.finished) this.error = err;
|
|
309
350
|
const was = this.finished;
|
|
310
351
|
this.finished = true;
|
|
352
|
+
this.session._release(this.held);
|
|
353
|
+
this.held = 0;
|
|
311
354
|
this._accept.reject(err);
|
|
312
355
|
this._signal();
|
|
313
356
|
if (!was) this._done.resolve();
|
|
@@ -336,9 +379,13 @@ export class Stream {
|
|
|
336
379
|
}
|
|
337
380
|
return;
|
|
338
381
|
case T.DATA:
|
|
339
|
-
if (this.recvFin || this.error) return;
|
|
340
|
-
if (f.payload.length > this.recvCredit)
|
|
382
|
+
if (this.recvFin || this.error) return this.session._release(f.payload.length);
|
|
383
|
+
if (f.payload.length > this.recvCredit) {
|
|
384
|
+
this.session._release(f.payload.length);
|
|
385
|
+
return this.reset('flow_control', 'window exceeded');
|
|
386
|
+
}
|
|
341
387
|
this.recvCredit -= f.payload.length;
|
|
388
|
+
this.held += f.payload.length;
|
|
342
389
|
this.buf.push(f.payload.slice());
|
|
343
390
|
this.bufLen += f.payload.length;
|
|
344
391
|
this._signal();
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Package version, in one place so every module agrees.
|
|
2
|
-
export const VERSION = '0.
|
|
2
|
+
export const VERSION = '0.2.0';
|