quic: add QuicEndpoint.listening & QuicStream.destroy() and tests · nodejs/node@f697c55
1+// Flags: --experimental-quic --no-warnings
2+3+import { hasQuic, skip, mustCall } from '../common/index.mjs';
4+import assert from 'node:assert';
5+import * as fixtures from '../common/fixtures.mjs';
6+7+if (!hasQuic) {
8+skip('QUIC is not enabled');
9+}
10+11+// Import after the hasQuic check
12+const quic = await import('node:quic');
13+const { createPrivateKey } = await import('node:crypto');
14+15+const keys = createPrivateKey(fixtures.readKey('agent1-key.pem'));
16+const certs = fixtures.readKey('agent1-cert.pem');
17+18+const serverDone = Promise.withResolvers();
19+const clientDone = Promise.withResolvers();
20+21+// Create a server endpoint
22+const serverEndpoint = await quic.listen(mustCall((serverSession) => {
23+serverSession.opened.then((info) => {
24+assert.ok(serverSession.endpoint !== null);
25+assert.strictEqual(serverSession.destroyed, false);
26+27+const stats = serverSession.stats;
28+assert.strictEqual(stats.isConnected, true);
29+assert.ok(stats.handshakeCompletedAt > 0n);
30+assert.ok(stats.handshakeConfirmedAt > 0n);
31+assert.strictEqual(stats.closingAt, 0n);
32+33+serverDone.resolve();
34+serverSession.close();
35+}).then(mustCall());
36+}), { sni: { '*': { keys, certs } } });
37+38+assert.strictEqual(serverEndpoint.busy, false);
39+assert.strictEqual(serverEndpoint.closing, false);
40+assert.strictEqual(serverEndpoint.destroyed, false);
41+assert.strictEqual(serverEndpoint.listening, true);
42+43+assert.ok(serverEndpoint.address !== undefined);
44+assert.strictEqual(serverEndpoint.address.family, 'ipv4');
45+assert.strictEqual(serverEndpoint.address.address, '127.0.0.1');
46+assert.ok(typeof serverEndpoint.address.port === 'number');
47+assert.ok(serverEndpoint.address.port > 0);
48+49+const epStats = serverEndpoint.stats;
50+assert.strictEqual(epStats.isConnected, true);
51+assert.ok(epStats.createdAt > 0n);
52+53+// Connect with a client
54+const clientSession = await quic.connect(serverEndpoint.address);
55+56+assert.strictEqual(clientSession.destroyed, false);
57+assert.ok(clientSession.endpoint !== null);
58+assert.strictEqual(clientSession.stats.isConnected, true);
59+60+clientSession.opened.then((clientInfo) => {
61+assert.strictEqual(clientInfo.servername, 'localhost');
62+assert.strictEqual(clientInfo.protocol, 'h3');
63+assert.strictEqual(clientInfo.cipherVersion, 'TLSv1.3');
64+assert.ok(clientInfo.local !== undefined);
65+assert.ok(clientInfo.remote !== undefined);
66+67+const cStats = clientSession.stats;
68+assert.strictEqual(cStats.isConnected, true);
69+assert.ok(cStats.handshakeCompletedAt > 0n);
70+assert.ok(cStats.bytesSent > 0n, 'Expected bytesSent > 0 after handshake');
71+72+clientDone.resolve();
73+}).then(mustCall());
74+75+await Promise.all([serverDone.promise, clientDone.promise]);
76+77+// Open a bidirectional stream.
78+const stream = await clientSession.createBidirectionalStream();
79+80+assert.strictEqual(stream.destroyed, false);
81+assert.strictEqual(stream.direction, 'bidi');
82+assert.strictEqual(stream.session, clientSession);
83+assert.ok(stream.id !== null, 'Non-pending stream should have an id');
84+assert.strictEqual(typeof stream.id, 'bigint');
85+assert.strictEqual(stream.pending, false);
86+assert.strictEqual(stream.stats.isConnected, true);
87+assert.ok(stream.readable instanceof ReadableStream);
88+89+// Destroying the session should destroy it and the stream, and clear its properties.
90+clientSession.destroy();
91+assert.strictEqual(clientSession.destroyed, true);
92+assert.strictEqual(clientSession.endpoint, null);
93+assert.strictEqual(clientSession.stats.isConnected, false);
94+95+assert.strictEqual(stream.destroyed, true);
96+assert.strictEqual(stream.session, null);
97+assert.strictEqual(stream.id, null);
98+assert.strictEqual(stream.direction, null);