quico 0.3.0 → 0.7.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/src/utils.js CHANGED
@@ -24,32 +24,65 @@ var DEBUG = !!(typeof process !== 'undefined' && process.env && process.env.QUIC
24
24
 
25
25
 
26
26
  // ============================================================
27
- // Emitter — lightweight event emitter (single shared copy)
27
+ // Emitter — the one event emitter, in two behaviours
28
+ //
29
+ // createEmitter(swallow): with swallow, a listener that throws is caught
30
+ // and the others still run — the QUIC layers (quic_connection, h3,
31
+ // tls_bridge, quic_server) emit from inside packet processing, and an
32
+ // application's listener that throws must not unwind into it: the packet
33
+ // that delivered the data would be dropped (see the safety net in
34
+ // receivePackets). Without, the throw goes to the caller, which turns it
35
+ // into a protocol error (http_common's Emitter, for the parsers that pump
36
+ // bytes in). Two copies of nearly the same code were kept, one per
37
+ // behaviour.
28
38
  // ============================================================
29
39
 
30
- function Emitter() {
40
+ function createEmitter(swallow) {
31
41
  var listeners = {};
42
+ function call(fn, args) {
43
+ if (!swallow) { fn.apply(null, args); return; }
44
+ try { fn.apply(null, args); } catch (e) { /* swallowed: see above */ }
45
+ }
32
46
  return {
33
- on: function (name, fn) {
34
- (listeners[name] = listeners[name] || []).push(fn);
47
+ on: function (name, fn) { (listeners[name] = listeners[name] || []).push(fn); return this; },
48
+ once: function (name, fn) {
49
+ var self = this;
50
+ function wrapper() { self.off(name, wrapper); fn.apply(null, arguments); }
51
+ wrapper.__original = fn;
52
+ return this.on(name, wrapper);
35
53
  },
36
54
  off: function (name, fn) {
37
- var arr = listeners[name];
38
- if (arr) {
39
- var idx = arr.indexOf(fn);
40
- if (idx !== -1) arr.splice(idx, 1);
55
+ var a = listeners[name];
56
+ if (!a) return this;
57
+ for (var i = 0; i < a.length; i++) {
58
+ if (a[i] === fn || a[i].__original === fn) { a.splice(i, 1); break; }
41
59
  }
60
+ return this;
42
61
  },
43
62
  emit: function (name) {
63
+ var a = listeners[name];
64
+ if (!a || a.length === 0) return false;
44
65
  var args = Array.prototype.slice.call(arguments, 1);
45
- var arr = listeners[name] || [];
46
- for (var i = 0; i < arr.length; i++) {
47
- try { arr[i].apply(null, args); } catch (e) { /* swallow */ }
48
- }
66
+ // One listener, the usual case: called as it is. More: from a copy, as
67
+ // a listener may add or remove listeners, and mutating the array
68
+ // mid-loop silently skips one.
69
+ if (a.length === 1) { call(a[0], args); return true; }
70
+ a = a.slice();
71
+ for (var i = 0; i < a.length; i++) call(a[i], args);
72
+ return true;
73
+ },
74
+ listenerCount: function (name) { return (listeners[name] || []).length; },
75
+ removeAllListeners: function (name) {
76
+ if (name === undefined) listeners = {};
77
+ else delete listeners[name];
78
+ return this;
49
79
  }
50
80
  };
51
81
  }
52
82
 
83
+ /** The QUIC layers' emitter: a listener's throw is caught (see above). */
84
+ function Emitter() { return createEmitter(true); }
85
+
53
86
 
54
87
  // ============================================================
55
88
  // QUIC Variable-Length Integer (RFC 9000 §16)
@@ -331,7 +364,56 @@ function ranges_to_ack_frame(flatRanges, ecnStats, ackDelay) {
331
364
  }
332
365
 
333
366
 
367
+ // A stream's priority, updated from one of two sources — the local
368
+ // application's decision, or the peer's signal (fromPeer: RFC 9218's
369
+ // `priority` header and PRIORITY_UPDATE). A field the application set is its
370
+ // decision: a signal arriving later does not replace it; the application may
371
+ // still change it. The record: { urgency (-1..7), incremental, group, order,
372
+ // mine: the fields the application set }. Both senders keep theirs this way:
373
+ // QUIC's (setStreamPriority) and HTTP/2's (h2.js).
374
+ function mergePriority(cur, p, fromPeer) {
375
+ if (!p) return cur;
376
+ function put(k, v) {
377
+ if (fromPeer && cur.mine && cur.mine[k]) return;
378
+ cur[k] = v;
379
+ if (!fromPeer) (cur.mine || (cur.mine = {}))[k] = true;
380
+ }
381
+ if (p.urgency != null) put('urgency', Math.max(-1, Math.min(7, Math.floor(Number(p.urgency)) || 0)));
382
+ if (p.incremental != null) put('incremental', !!p.incremental);
383
+ if ('group' in p) put('group', p.group || null);
384
+ if (p.order != null) put('order', Number(p.order) || 0);
385
+ return cur;
386
+ }
387
+
388
+ // Flow-control credit for a Readable that the peer fills (a request body, a
389
+ // WebTransport stream): returned as the application reads — for the bytes
390
+ // that have left the Readable's buffer — so the peer is held to one window
391
+ // ahead of the reader. Counted after each read(), which every way of reading
392
+ // goes through (flowing mode, pipe(), async iteration, read() itself).
393
+ // Not in _read: that is also Node's own call to fill its buffer up to
394
+ // highWaterMark, read or not — credited there, a reader that read nothing
395
+ // let the peer a buffer further — and Node makes it before it takes out the
396
+ // bytes it is returning, then not again until the next push: counted there,
397
+ // the credit for the last bytes read waited for a push the peer could not
398
+ // send without it. credit(n) is called with what was read since; the
399
+ // returned function is told of each chunk pushed.
400
+ function creditAsRead(readable, credit) {
401
+ var received = 0, credited = 0;
402
+ var read = readable.read;
403
+ readable._read = function () {};
404
+ readable.read = function (n) {
405
+ var chunk = read.call(this, n);
406
+ var done = received - readable.readableLength;
407
+ if (done > credited) { var k = done - credited; credited = done; credit(k); }
408
+ return chunk;
409
+ };
410
+ return function (n) { received += n; };
411
+ }
412
+
334
413
  export {
414
+ createEmitter,
415
+ mergePriority,
416
+ creditAsRead,
335
417
  DEBUG,
336
418
  Emitter,
337
419
  writeVarInt,