Skip to content

Commit 70dbb13

Browse files
committed
quic-transport: test cases for Node.js
1 parent 16d8175 commit 70dbb13

4 files changed

Lines changed: 137 additions & 0 deletions

File tree

pkg/quic-transport/src/h3-transport.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ export namespace H3Transport {
8181
* @defaultValue 10000
8282
*/
8383
connectTimeout?: number;
84+
85+
/**
86+
* Skip TLS certificate verification (Node.js only).
87+
* @defaultValue false
88+
*/
89+
insecureSkipVerify?: boolean;
8490
}
8591

8692
/** Create a transport and add to forwarder. */

pkg/quic-transport/src/wt_node.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { WebTransport as bunWebTransport } from "@webtransport-bun/webtransport";
22

3+
import type { H3Transport } from "./h3-transport";
4+
35
export const supported = true;
46

57
export function makeWebTransport(uri: string, opts: WebTransportOptions): WebTransport {
8+
const { insecureSkipVerify = false } = opts as H3Transport.Options;
69
void opts;
710
return new bunWebTransport(uri, {
811
strictW3CErrors: true,
12+
tls: { insecureSkipVerify },
913
}) as unknown as WebTransport;
1014
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { TestServer } from "@ndn/node-transport/test-fixture/net-server";
2+
import { assert } from "@ndn/util";
3+
import { type ClientSession, createServer, type ServerSession, type WebTransportServer } from "@webtransport-bun/webtransport";
4+
5+
/** WebTransport test server. */
6+
export class WtServer extends TestServer<WebTransportServer, ServerSession> {
7+
public static async create(): Promise<WtServer> {
8+
let self!: WtServer; // eslint-disable-line prefer-const
9+
10+
const port = 16384 + Math.trunc(Math.random() * 16384);
11+
const server = createServer({
12+
port,
13+
tls: { certPem: "", keyPem: "", allowSelfSigned: true },
14+
onSession: (sess) => self.onSession(sess),
15+
});
16+
17+
self = new WtServer(server, port);
18+
return self;
19+
}
20+
21+
/** WebTransport server URI. */
22+
public readonly uri: string;
23+
24+
constructor(server: WebTransportServer, port: number) {
25+
super(server);
26+
this.uri = `https://127.0.0.1:${port}`;
27+
}
28+
29+
private waitNewSession?: () => void;
30+
31+
private readonly onSession = async (sess: ServerSession) => {
32+
this.mClients.add(sess);
33+
this.waitNewSession?.();
34+
try {
35+
await sess.closed;
36+
} catch (err: unknown) {
37+
console.warn("WtServer await sess.closed error", err);
38+
} finally {
39+
this.mClients.delete(sess);
40+
}
41+
};
42+
43+
public async [Symbol.asyncDispose](): Promise<void> {
44+
await this.server.close();
45+
const closedPromises = Array.from(this.clients, (c) => c.closed);
46+
for (const client of this.clients) {
47+
client.close();
48+
}
49+
this.mClients.clear();
50+
await Promise.allSettled(closedPromises);
51+
}
52+
53+
protected override async waitNClientsImpl(n: number, timeout: number): Promise<void> {
54+
assert(!this.waitNewSession); // waitNClientsImpl is designed to be non-reentrant
55+
try {
56+
while (this.clients.size < n) {
57+
await new Promise<void>((resolve) => {
58+
this.waitNewSession = resolve;
59+
});
60+
}
61+
} finally {
62+
this.waitNewSession = undefined;
63+
}
64+
}
65+
}
66+
67+
type CommonSession = ServerSession | ClientSession;
68+
69+
/** Connect several WebTransport sessions and relay messages among them. */
70+
export function bridgeSessions(sessions: readonly CommonSession[]): void {
71+
const relayDatagrams = async (src: CommonSession) => {
72+
for await (const datagram of src.incomingDatagrams()) {
73+
for (const dst of sessions) {
74+
if (dst !== src) {
75+
await dst.sendDatagram(datagram);
76+
}
77+
}
78+
}
79+
};
80+
81+
for (const sess of sessions) {
82+
void relayDatagrams(sess);
83+
}
84+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as TestReopen from "@ndn/l3face/test-fixture/reopen";
2+
import * as TestTransport from "@ndn/l3face/test-fixture/transport";
3+
import { Closers } from "@ndn/util";
4+
import { beforeEach, expect, test } from "vitest";
5+
6+
import { H3Transport } from "..";
7+
import { bridgeSessions, WtServer } from "../test-fixture/wt-server";
8+
9+
const closers = new Closers();
10+
let server: WtServer;
11+
beforeEach(async () => {
12+
server = await WtServer.create();
13+
closers.push(server);
14+
return closers.close;
15+
});
16+
17+
test("pair", async () => {
18+
const [tA, tB, sockets] = await Promise.all([
19+
H3Transport.connect(server.uri, { insecureSkipVerify: true }),
20+
H3Transport.connect(server.uri, { insecureSkipVerify: true }),
21+
server.waitNClients(2),
22+
]);
23+
24+
expect(tA.toString()).toBe(`H3(${server.uri})`);
25+
26+
bridgeSessions(sockets);
27+
TestTransport.check(await TestTransport.execute(tA, tB));
28+
});
29+
30+
test("connect error", async () => {
31+
const { uri } = server;
32+
await server[Symbol.asyncDispose]();
33+
await expect(H3Transport.connect(uri, { connectTimeout: 500 })).rejects.toThrow();
34+
});
35+
36+
test("reopen", async () => {
37+
const transport = await H3Transport.connect(server.uri, { insecureSkipVerify: true });
38+
await TestReopen.run(
39+
transport,
40+
server.waitNClients,
41+
(sess) => sess.close(),
42+
);
43+
});

0 commit comments

Comments
 (0)