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