quico 0.4.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.
@@ -0,0 +1,557 @@
1
+ /*
2
+ * quico: HTTP/3 and QUIC implementation for Node.js
3
+ * Copyright 2025 colocohen
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at:
8
+ *
9
+ * https://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * This file is part of the open-source project hosted at:
12
+ * https://github.com/colocohen/quico
13
+ */
14
+
15
+ import net from 'node:net';
16
+ import crypto from 'node:crypto';
17
+ import { Emitter } from './http_common.js';
18
+ import { serverTlsOptions } from './tls_options.js';
19
+
20
+
21
+ // ============================================================
22
+ // TCP transport: sockets, TLS, and ALPN dispatch.
23
+ //
24
+ // The mirror image of quic_server.js. That file owns the UDP socket,
25
+ // demultiplexes datagrams and hands a raw QUICConnection to whatever
26
+ // application protocol layers on top; this one owns the TCP socket, runs the
27
+ // TLS handshake, and hands a ready Duplex to whichever protocol ALPN selected.
28
+ // Neither knows anything about HTTP.
29
+ //
30
+ // It exists because h1_server, h2_server and ws_server must share one port —
31
+ // a client picks between them with ALPN, and something has to make that
32
+ // choice. Putting the choice in each server means three copies of
33
+ // net.createServer, three SNICallback bridges, three ALPN lists that can
34
+ // disagree, and three close()/address() implementations. Here it is written
35
+ // once and the protocol layers become pure: they receive a socket and never
36
+ // create one.
37
+ //
38
+ // TLS comes from lemon-tls, passed in as `options.tls`. Without it the
39
+ // listener is cleartext, which is what a local dev server or a service behind
40
+ // a terminating proxy wants — and what h2c and ws:// need.
41
+ // ============================================================
42
+
43
+ // The HTTP/2 connection preface (RFC 9113 §3.4). Used here only to sniff a
44
+ // cleartext connection; the h2 layer validates it properly.
45
+ var H2_PREFACE = Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n', 'latin1');
46
+
47
+
48
+ function createTcpListener(options) {
49
+ options = options || {};
50
+
51
+ var ev = Emitter();
52
+ var server = null;
53
+ var handlers = {}; // alpn protocol -> handler(socket, protocol)
54
+ var handlerOpts = {}; // alpn protocol -> { sniff, paused }
55
+ // Session tickets: ONE key for every connection of this server, so a
56
+ // ticket issued on one connection opens on the next. Left to lemon-tls, each
57
+ // connection drew its own and no client ever resumed. Random when not given:
58
+ // tickets then last until this server restarts; pass ticketKeys (48 bytes)
59
+ // kept across restarts to have them outlive it.
60
+ var ticketKeys = options.ticketKeys || crypto.randomBytes(48);
61
+ // Encrypted Client Hello keys (lemon-tls), read per connection.
62
+ var ech = options.ech || null;
63
+ var ocspRequest = typeof options.ocspRequest === 'function' ? options.ocspRequest : null;
64
+ // node:tls's options, read once (tls_options.js).
65
+ var tlsOpts = serverTlsOptions(options, false);
66
+
67
+ var order = []; // registration order = ALPN preference order
68
+ var sockets = new Set();
69
+ var closing = false;
70
+
71
+ var tls = options.tls || null;
72
+ var cachedContext = null;
73
+ // The certificate's context made now, not at the first connection, and
74
+ // its chain compressed in the background (RFC 8879, lemon-tls
75
+ // precompressCertificate): the first client's handshake did both while it
76
+ // waited — brotli at quality 11 alone is 4–18 ms for a chain the first
77
+ // time. A bad key or certificate throws from createServer, as over QUIC.
78
+ if (tls && options.key && options.cert && !options.SNICallback && !options.secureContext) {
79
+ cachedContext = tls.createSecureContext({ key: options.key, cert: options.cert, ca: options.ca, passphrase: tlsOpts.passphrase });
80
+ if (typeof tls.precompressCertificate === 'function' && !(tlsOpts.certificateCompression && tlsOpts.certificateCompression.length === 0))
81
+ tls.precompressCertificate(cachedContext, tlsOpts.certificateCompression || undefined);
82
+ }
83
+
84
+ // TCP options, as node:http.createServer takes them and with its defaults:
85
+ // noDelay on (see handleConnection), keepAlive off.
86
+ // The server's counters (server_stats.js), when it keeps them.
87
+ var stats = options.stats || null;
88
+ var noDelay = options.noDelay !== false;
89
+ var keepAlive = options.keepAlive === true;
90
+ var keepAliveInitialDelay = options.keepAliveInitialDelay || 0;
91
+
92
+
93
+ /**
94
+ * Register a protocol handler. Registration order is the server's ALPN
95
+ * preference: the first registered protocol wins when the client offers
96
+ * several, which is how a server expresses "h2 if you can, h1 if you must".
97
+ */
98
+ //
99
+ // opts (both optional):
100
+ // sniff(bytes) Recognise this protocol from the first decrypted bytes of
101
+ // a TLS connection that negotiated NO ALPN. Return true
102
+ // (mine), false (not mine), or null (need more bytes).
103
+ // A browser connecting to turns:host:443 is the case: it
104
+ // offers no ALPN at all, while every HTTPS request from a
105
+ // browser offers h2 / http/1.1 — so this only ever runs for
106
+ // connections that would otherwise have been HTTP/1.1 by
107
+ // default, and costs HTTPS nothing.
108
+ // paused Hand the socket over paused, with any sniffed bytes put
109
+ // back, and let the handler resume it. For a handler that
110
+ // attaches its reader asynchronously — turn-server waits
111
+ // for its 'accept' hook — and would otherwise lose the
112
+ // first message to a stream already flowing.
113
+ function register(protocol, handler, opts) {
114
+ if (!handlers[protocol]) order.push(protocol);
115
+ handlers[protocol] = handler;
116
+ handlerOpts[protocol] = opts || {};
117
+ return listener;
118
+ }
119
+
120
+
121
+ function dispatch(socket, protocol) {
122
+ if (closing) { try { socket.destroy(); } catch (e) {} return; }
123
+
124
+ var handler = handlers[protocol];
125
+ if (!handler) {
126
+ // Nothing registered for what was negotiated. Should be impossible —
127
+ // we only advertise what is registered — but a client can also connect
128
+ // with no ALPN at all, and then `protocol` is whatever the fallback
129
+ // decided.
130
+ ev.emit('clientError', new Error('no handler for protocol ' + JSON.stringify(protocol)), socket);
131
+ try { socket.destroy(); } catch (e) {}
132
+ return;
133
+ }
134
+ if (stats) stats.protocol(protocol);
135
+
136
+ sockets.add(socket);
137
+ socket.on('close', function () { sockets.delete(socket); });
138
+ if (handlerOpts[protocol] && handlerOpts[protocol].paused) socket.pause();
139
+ handler(socket, protocol);
140
+ }
141
+
142
+
143
+ /**
144
+ * Decide which protocol a TLS connection WITHOUT ALPN is speaking, from its
145
+ * first decrypted bytes — the encrypted counterpart of sniffCleartext.
146
+ *
147
+ * Only protocols registered with a sniff() take part. Bytes are gathered
148
+ * until each of them has answered, or a cap is reached; the first to claim
149
+ * the bytes wins, and if none does, the connection goes to `fallback`
150
+ * exactly as it would have before. Whatever was read is put back at the
151
+ * front of the stream, so the chosen handler sees the connection from its
152
+ * first byte.
153
+ */
154
+ function sniffSecure(socket, fallback) {
155
+ var sniffers = [];
156
+ for (var i = 0; i < order.length; i++) {
157
+ var o = handlerOpts[order[i]];
158
+ if (o && typeof o.sniff === 'function') sniffers.push(order[i]);
159
+ }
160
+
161
+ var seen = Buffer.alloc(0);
162
+ var decided = false;
163
+ var SNIFF_CAP = 64; // bytes: enough for any header-shaped test
164
+ var SNIFF_TIMEOUT = 10000; // a client that says nothing goes to the fallback
165
+
166
+ function decide(protocol) {
167
+ if (decided) return;
168
+ decided = true;
169
+ clearTimeout(timer);
170
+ socket.removeListener('data', onData);
171
+ socket.removeListener('close', onClose);
172
+ // Paused before the bytes go back and the handler is chosen: nothing may
173
+ // flow until a reader exists. dispatch() leaves a 'paused' handler
174
+ // paused; any other is resumed below, as the connection would have been.
175
+ socket.pause();
176
+ if (seen.length) socket.unshift(seen);
177
+ dispatch(socket, protocol);
178
+ if (!(handlerOpts[protocol] && handlerOpts[protocol].paused) && !socket.destroyed) socket.resume();
179
+ }
180
+
181
+ function onData(chunk) {
182
+ seen = seen.length ? Buffer.concat([seen, chunk]) : Buffer.from(chunk);
183
+ var pending = false;
184
+ for (var k = 0; k < sniffers.length; k++) {
185
+ var r = null;
186
+ try { r = handlerOpts[sniffers[k]].sniff(seen); } catch (e) { r = false; }
187
+ if (r === true) { decide(sniffers[k]); return; }
188
+ if (r === null) pending = true;
189
+ }
190
+ if (!pending || seen.length >= SNIFF_CAP) decide(fallback);
191
+ }
192
+
193
+ function onClose() { decided = true; clearTimeout(timer); }
194
+
195
+ var timer = setTimeout(function () { decide(fallback); }, SNIFF_TIMEOUT);
196
+ if (timer.unref) timer.unref();
197
+ socket.on('data', onData);
198
+ socket.on('close', onClose);
199
+ }
200
+
201
+
202
+ /**
203
+ * Decide which protocol a CLEARTEXT connection is speaking.
204
+ *
205
+ * There is no ALPN without TLS, so the two cleartext protocols have to be
206
+ * told apart another way. RFC 9113 §3.3 calls this "prior knowledge": an h2c
207
+ * client simply opens with the connection preface, and the preface was
208
+ * designed to be something no HTTP/1.1 request can begin with. Peeking at
209
+ * the first bytes is therefore unambiguous — and the bytes are pushed back
210
+ * so the chosen layer sees the whole stream.
211
+ */
212
+ function sniffCleartext(socket) {
213
+ if (order.length === 1) { dispatch(socket, order[0]); return; }
214
+ if (!handlers['h2'] && !handlers['h2c']) { dispatch(socket, order[0]); return; }
215
+
216
+ var seen = Buffer.alloc(0);
217
+ var decided = false;
218
+
219
+ function onData(chunk) {
220
+ if (decided) return;
221
+ seen = Buffer.concat([seen, chunk]);
222
+
223
+ var n = Math.min(seen.length, H2_PREFACE.length);
224
+ var matches = seen.subarray(0, n).equals(H2_PREFACE.subarray(0, n));
225
+
226
+ if (!matches) { decide(handlers['http/1.1'] ? 'http/1.1' : order[0]); return; }
227
+ if (seen.length >= H2_PREFACE.length) { decide(handlers['h2'] ? 'h2' : 'h2c'); }
228
+ // Otherwise a prefix of the preface so far: wait for more.
229
+ }
230
+
231
+ function decide(protocol) {
232
+ decided = true;
233
+ socket.removeListener('data', onData);
234
+ socket.removeListener('error', onError);
235
+ socket.removeListener('close', onClose);
236
+ // Put the sniffed bytes back at the FRONT of the stream. unshift is the
237
+ // documented way to do this; without it the protocol layer never sees
238
+ // the request line (or the preface) it is waiting for and the connection
239
+ // hangs until a timeout — a failure that looks like a parser bug.
240
+ if (seen.length) socket.unshift(seen);
241
+ dispatch(socket, protocol);
242
+ }
243
+
244
+ function onError(err) { if (!decided) { decided = true; ev.emit('clientError', err, socket); } }
245
+ function onClose() { decided = true; }
246
+
247
+ socket.on('data', onData);
248
+ socket.on('error', onError);
249
+ socket.on('close', onClose);
250
+ }
251
+
252
+
253
+ /**
254
+ * Accept a socket the caller already has — a shared port, a test harness, or
255
+ * a listener owned elsewhere. The equivalent of quic_server's handlePacket.
256
+ */
257
+ function handleConnection(socket) {
258
+ if (closing) { try { socket.destroy(); } catch (e) {} return; }
259
+ if (stats) stats.connections.tcp++;
260
+
261
+ // TCP_NODELAY, as node:http sets it on every socket it accepts. With
262
+ // Nagle's algorithm on, a write smaller than one segment waits while
263
+ // anything sent before it is unacknowledged — a full round trip. The TLS
264
+ // server flight is several such writes (ServerHello, then Certificate,
265
+ // CertificateVerify, Finished): the ServerHello went out, the rest sat
266
+ // until the client's ACK came back, and TLS 1.3's one-round-trip
267
+ // handshake took two. The same stall hits the tail of every response.
268
+ // Applied here rather than only on net.createServer, because sockets are
269
+ // also handed in from a listener owned elsewhere.
270
+ if (noDelay && typeof socket.setNoDelay === 'function') socket.setNoDelay(true);
271
+ if (keepAlive && typeof socket.setKeepAlive === 'function') socket.setKeepAlive(true, keepAliveInitialDelay);
272
+
273
+ // Emitted BEFORE the TLS handshake, so a consumer that screens by address
274
+ // (an IP blacklist, a rate limiter) can drop the connection without paying
275
+ // for a handshake first. node:net and node:http both call this event
276
+ // 'connection' and hand over the socket, so a screening function written
277
+ // against either works unchanged.
278
+ ev.emit('connection', socket);
279
+ if (socket.destroyed) return;
280
+
281
+ if (!tls) { sniffCleartext(socket); return; }
282
+
283
+ var tlsOpts = buildTlsOptions(socket);
284
+ var secure;
285
+ var settled = false;
286
+
287
+ // The error listener has to be in place BEFORE the handshake can produce
288
+ // one, and the constructor starts reading immediately — so it is attached
289
+ // to the underlying socket first and moved up as soon as the TLSSocket
290
+ // exists.
291
+ //
292
+ // The gap is not theoretical. A TLS handshake can fail asynchronously:
293
+ // an SNI lookup that answers "no certificate for that name" fires its
294
+ // alert from a callback, long after the constructor returned. With no
295
+ // listener at that moment the error is unhandled, and an unhandled error
296
+ // on an EventEmitter takes the process down — meaning any scanner that
297
+ // connects to the IP without SNI, which is a continuous background event
298
+ // on a public address, can stop the server.
299
+ // Reported as 'tlsClientError' (err, tlsSocket), node:tls's event for a
300
+ // handshake that failed; 'clientError' is the HTTP layer's, for a
301
+ // connection that is up. It was reported as 'clientError'.
302
+ function onHandshakeError(err) {
303
+ if (!settled) {
304
+ settled = true;
305
+ countHandshake(false);
306
+ ev.emit('tlsClientError', err, secure || socket);
307
+ }
308
+ // The alert that says why (no_application_protocol, bad_certificate,
309
+ // ...) is written to the socket just before this; destroy() threw it
310
+ // away with whatever else was still queued, and the client saw only a
311
+ // reset. The socket is ended instead, which sends what is queued, and
312
+ // destroyed if it has not closed shortly after.
313
+ if (socket.destroyed) return;
314
+ try { socket.end(); } catch (e) {}
315
+ var t = setTimeout(function () { try { socket.destroy(); } catch (e) {} }, 1000);
316
+ if (t.unref) t.unref();
317
+ }
318
+
319
+ socket.on('error', onHandshakeError);
320
+
321
+ // The handshake's end, counted once (server_stats.js): completed, or
322
+ // failed — an error, or the client gone before it finished.
323
+ var hsCounted = false;
324
+ function countHandshake(ok, resumed) {
325
+ if (hsCounted || !stats) return;
326
+ hsCounted = true;
327
+ stats.handshake('tcp', ok, resumed);
328
+ }
329
+ if (stats) socket.on('close', function () { countHandshake(false); });
330
+
331
+ try {
332
+ // The TCP socket underneath stays open for writing when the client
333
+ // half-closes: the TLS socket over it decides — it ends both with the
334
+ // client's (allowHalfOpen off, HTTP/1.1, as node:http), unless the
335
+ // protocol above keeps it open to finish its responses (h2_server).
336
+ socket.allowHalfOpen = true;
337
+ secure = new tls.TLSSocket(socket, tlsOpts);
338
+ } catch (err) {
339
+ onHandshakeError(err);
340
+ return;
341
+ }
342
+
343
+ secure.on('error', onHandshakeError);
344
+
345
+ // Early data taken: the protocol above gets the connection now, to read
346
+ // it as it comes (socket.isEarlyData) and answer as soon as the server's
347
+ // Finished is out — not one round trip later, at 'secure'. The ALPN is
348
+ // decided by then (the ticket's, or there is no early data).
349
+ var dispatchedEarly = false;
350
+ secure.on('earlyData', function (accepted) {
351
+ if (stats) stats.earlyData[accepted ? 'accepted' : 'rejected']++;
352
+ if (!accepted || settled || dispatchedEarly) return;
353
+ var p = secure.alpnProtocol;
354
+ if (!p || !handlers[p]) return;
355
+ dispatchedEarly = true;
356
+ dispatch(secure, p);
357
+ });
358
+
359
+ // 'secure' rather than 'secureConnect': lemon-tls documents that
360
+ // secureConnect is not emitted for a socket built with new TLSSocket(),
361
+ // matching Node, while 'secure' fires on both sides and in both
362
+ // construction paths. Listening for the wrong one is a server that accepts
363
+ // connections and then never does anything with them.
364
+ secure.on('secure', function () {
365
+ if (settled) return;
366
+ settled = true;
367
+ countHandshake(true, typeof secure.isSessionReused === 'function' && secure.isSessionReused());
368
+
369
+ // 'secureConnection': the handshake is done — the name and the moment
370
+ // node:tls uses. 'connection' fired before any TLS, knowing only an
371
+ // address; here the connection can be asked what the handshake decided
372
+ // (isSessionReused(), getTicketInfo()), which is what a caller needs to
373
+ // tell a returning client from a new one. A listener may destroy() it.
374
+ ev.emit('secureConnection', secure);
375
+ if (secure.destroyed || dispatchedEarly) return;
376
+
377
+ var protocol = secure.alpnProtocol;
378
+ if (!protocol) {
379
+ // The client offered nothing, or offered nothing we advertise. RFC
380
+ // 7301 lets the server proceed without ALPN, and HTTP/1.1 is the only
381
+ // safe assumption — an h2 client always offers 'h2'. Unless a
382
+ // registered protocol can tell itself apart by its first bytes: then
383
+ // those decide (see sniffSecure).
384
+ var fallback = handlers['http/1.1'] ? 'http/1.1' : order[0];
385
+ for (var sp = 0; sp < order.length; sp++) {
386
+ var so = handlerOpts[order[sp]];
387
+ if (so && typeof so.sniff === 'function') { sniffSecure(secure, fallback); return; }
388
+ }
389
+ protocol = fallback;
390
+ }
391
+ dispatch(secure, protocol);
392
+ });
393
+ }
394
+
395
+
396
+ /**
397
+ * Who this connection is, for an SNICallback that wants to know.
398
+ *
399
+ * A certificate can only be chosen by name when the client sends one, and
400
+ * a client connecting to a bare IP address never does — RFC 6066 §3 forbids
401
+ * an IP literal in SNI, so browsers leave the extension out entirely. The
402
+ * callback then sees an empty servername and nothing else, with no way to
403
+ * tell which of the server's addresses was dialled. That is the one thing
404
+ * it needs to serve a certificate issued for an IP.
405
+ *
406
+ * The raw socket knows. A dual-stack listener reports an IPv4 peer as
407
+ * ::ffff:a.b.c.d on an IPv6 socket; that is unwrapped here so the family
408
+ * reported is the one the client actually used.
409
+ */
410
+ function connectionInfo(socket) {
411
+ var local = socket.localAddress || null;
412
+ var family = socket.localFamily || (local && net.isIPv6(local) ? 'IPv6' : 'IPv4');
413
+ if (local && local.indexOf('::ffff:') === 0 && net.isIPv4(local.substring(7))) {
414
+ local = local.substring(7);
415
+ family = 'IPv4';
416
+ }
417
+ return {
418
+ transport: 'tcp',
419
+ localAddress: local,
420
+ localPort: socket.localPort || null,
421
+ family: family,
422
+ remoteAddress: socket.remoteAddress || null,
423
+ remotePort: socket.remotePort || null
424
+ };
425
+ }
426
+
427
+
428
+ function buildTlsOptions(socket) {
429
+ var o = {
430
+ isServer: true,
431
+ // Advertised in registration order, which is the server's preference.
432
+ ALPNProtocols: order.slice()
433
+ };
434
+ if (options.ca) o.ca = options.ca;
435
+
436
+ // Credentials reach a server-side TLSSocket through SNICallback.
437
+ //
438
+ // Passing key/cert (or secureContext) straight through is what node:tls
439
+ // documents and what this did first — and the handshake simply never
440
+ // completed, with no error on either side. lemon-tls resolves a server
441
+ // certificate through its SNI path, so a plain key/cert pair has to be
442
+ // wrapped in a callback that hands back the same context every time. The
443
+ // context is built ONCE here rather than per connection: compiling the PEM
444
+ // on the handshake path is measurable, and the hand-written SNICallback
445
+ // most servers end up with usually gets that wrong.
446
+ if (options.SNICallback) {
447
+ // Bound per connection, with the connection's details as a third
448
+ // argument. Third, after cb, so a callback written against the
449
+ // (servername, cb) signature node:tls documents keeps working
450
+ // unchanged — it just never reads it.
451
+ var userSni = options.SNICallback;
452
+ var info = socket ? connectionInfo(socket) : null;
453
+ o.SNICallback = function (servername, cb) { return userSni(servername, cb, info); };
454
+ } else if (options.secureContext) {
455
+ var fixed = options.secureContext;
456
+ o.SNICallback = function (servername, cb) { cb(null, fixed); };
457
+ } else if (options.key && options.cert) {
458
+ if (!cachedContext) {
459
+ cachedContext = tls.createSecureContext({
460
+ key: options.key, cert: options.cert, ca: options.ca, passphrase: tlsOpts.passphrase
461
+ });
462
+ }
463
+ var ctx = cachedContext;
464
+ o.SNICallback = function (servername, cb) { cb(null, ctx); };
465
+ }
466
+ o.ticketKeys = ticketKeys;
467
+ if (options.ticketData) o.ticketData = options.ticketData;
468
+ if (ech) o.ech = ech;
469
+ if (ocspRequest) o.ocspRequest = ocspRequest;
470
+ if (tlsOpts.sessionTimeout !== undefined) o.ticketLifetime = tlsOpts.sessionTimeout;
471
+ if (tlsOpts.requestCert) {
472
+ o.requestCert = true;
473
+ o.rejectUnauthorized = tlsOpts.rejectUnauthorized;
474
+ }
475
+ if (tlsOpts.minVersion) o.minVersion = tlsOpts.minVersion;
476
+ if (tlsOpts.maxVersion) o.maxVersion = tlsOpts.maxVersion;
477
+ // node:tls's default, 120 s: lemon-tls's own is none, and a client that
478
+ // sent half a ClientHello held its socket for as long as it liked.
479
+ o.handshakeTimeout = tlsOpts.handshakeTimeout !== undefined ? tlsOpts.handshakeTimeout : 120000;
480
+ if (tlsOpts.cipherSuites) o.allowedCipherSuites = tlsOpts.cipherSuites;
481
+ if (tlsOpts.groups) o.groups = tlsOpts.groups;
482
+ if (tlsOpts.sigalgs) o.signatureAlgorithms = tlsOpts.sigalgs;
483
+ if (tlsOpts.certificateCompression) o.certificateCompression = tlsOpts.certificateCompression;
484
+ if (tlsOpts.honorCipherOrder) o.honorCipherOrder = true;
485
+ // 0-RTT (RFC 8446 §4.2.10), as the server's options say now (they change
486
+ // while it runs): tickets that allow it, and taking it — under its own
487
+ // context, so a ticket of the QUIC side's carries none here.
488
+ var ed = typeof options.earlyDataNow === 'function' ? options.earlyDataNow() : null;
489
+ if (ed && ed.on) {
490
+ o.earlyData = true;
491
+ o.earlyDataMaxAge = ed.maxAge;
492
+ o.earlyDataReplay = options.earlyDataReplay;
493
+ o.earlyDataContext = 'tcp';
494
+ }
495
+ return o;
496
+ }
497
+
498
+
499
+ function listen(port, host, callback) {
500
+ if (typeof host === 'function') { callback = host; host = undefined; }
501
+ if (order.length === 0) throw new Error('no protocol handlers registered');
502
+
503
+ server = net.createServer({ allowHalfOpen: false }, handleConnection);
504
+ server.on('error', function (err) { ev.emit('error', err); });
505
+ server.listen(port, host, function () {
506
+ ev.emit('listening');
507
+ if (typeof callback === 'function') callback();
508
+ });
509
+ return listener;
510
+ }
511
+
512
+
513
+ function close(callback) {
514
+ closing = true;
515
+
516
+ // Idle sockets would hold the close open for their full keep-alive
517
+ // timeout. Protocol layers that have work in flight mark the socket busy;
518
+ // theirs are left to finish.
519
+ //
520
+ // Done whether or not this listener owns a port. With an external owner
521
+ // — sockets handed in through handleConnection — there is no net.Server
522
+ // here to close, and returning early (as this did) left every one of
523
+ // those connections open after shutdown.
524
+ for (var socket of sockets) {
525
+ if (!socket._quicoBusy) { try { socket.end(); } catch (e) {} }
526
+ }
527
+
528
+ if (!server) { if (callback) callback(); return; }
529
+ server.close(function () { if (typeof callback === 'function') callback(); });
530
+ }
531
+
532
+
533
+ var listener = {
534
+ register: register,
535
+ listen: listen,
536
+ close: close,
537
+ handleConnection: handleConnection,
538
+ address: function () { return server && server.address ? server.address() : null; },
539
+ on: function (n, f) { ev.on(n, f); return listener; },
540
+ off: function (n, f) { ev.off(n, f); return listener; },
541
+ get protocols() { return order.slice(); },
542
+ get connections() { return sockets.size; },
543
+
544
+ // The session ticket keys — tls.Server's names. A new key takes effect
545
+ // for the next connection; tickets sealed with the one before no longer
546
+ // open (those clients do a full handshake once, and get a new ticket).
547
+ getTicketKeys: function () { return ticketKeys; },
548
+ setTicketKeys: function (keys) { ticketKeys = keys; },
549
+ getEchKeys: function () { return ech; },
550
+ setEchKeys: function (keys) { ech = keys || null; }
551
+ };
552
+
553
+ return listener;
554
+ }
555
+
556
+
557
+ export { createTcpListener, H2_PREFACE };