/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var { setTimeout } = ChromeUtils.importESModule(
"resource://gre/modules/Timer.sys.mjs"
);
const { NodeHTTP2Server } = ChromeUtils.importESModule(
"resource://testing-common/NodeServer.sys.mjs"
);
registerCleanupFunction(async () => {
http3_clear_prefs();
Services.prefs.clearUserPref(
"network.http.http3.0rtt_timeout");
});
add_task(async
function setup() {
await http3_setup_tests(
"h3");
});
function makeChan(uri) {
let chan = NetUtil.newChannel({
uri,
loadUsingSystemPrincipal:
true,
}).QueryInterface(Ci.nsIHttpChannel);
chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
return chan;
}
function channelOpenPromise(chan, flags) {
return new Promise(resolve => {
function finish(req, buffer) {
resolve([req, buffer]);
}
chan.asyncOpen(
new ChannelListener(finish,
null, flags));
});
}
// Test the normal case.
add_task(async
function test_0rtt_timeout_normal_operation() {
info(
"Testing 0-RTT timeout with normal operation");
// Enable 0-RTT
Services.prefs.setBoolPref(
"network.http.http3.enable_0rtt",
true);
Services.prefs.setIntPref(
"network.http.http3.0rtt_timeout",
5000);
// Clear any existing connections
Services.obs.notifyObservers(
null,
"net:cancel-all-connections");
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await
new Promise(resolve => setTimeout(resolve,
1000));
// Make first request to establish a resumable session
let chan = makeChan(
"https://foo.example.com/30");
let [req] = await channelOpenPromise(chan);
Assert.equal(req.status, Cr.NS_OK);
// Wait to ensure session info is saved
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await
new Promise(resolve => setTimeout(resolve,
500));
// Clear connections to force new connection with 0-RTT
Services.obs.notifyObservers(
null,
"net:cancel-all-connections");
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await
new Promise(resolve => setTimeout(resolve,
1000));
// Make multiple requests - they should all succeed quickly
// (well under the 5 second timeout)
let promises = [];
for (let i =
0; i <
3; i++) {
let c = makeChan(`https:
//foo.example.com/30?timeout_test=${i}`);
promises.push(channelOpenPromise(c));
}
await Promise.all(promises);
});
// Test whether all transactions is successfully restarted.
add_task(async
function test_stuck_0rtt_session() {
info(
"Testing stuck 0-RTT session timeout");
// Start a separate HTTP/3 server for this test
let h3Port = await create_h3_server();
Assert.notEqual(h3Port,
null);
info(`Started H3 test server on port ${h3Port}`);
// Start HTTP/2 server on the same port for fallback
let h2Server =
new NodeHTTP2Server();
await h2Server.start(h3Port);
registerCleanupFunction(async () => {
await h2Server.stop();
});
// Register a simple handler for the H2 server
await h2Server.registerPathHandler(
"/30", (_req, resp) => {
resp.writeHead(
200, {
"content-type":
"text/plain" });
resp.end(
"H2 fallback response");
});
info(`Started H2 fallback server on port ${h3Port}`);
// Configure for a different hostname
// The origin will use h3Port, and H3 is the alt-svc
let testHost =
"alt1.example.com";
Services.prefs.setCharPref(
"network.dns.localDomains", testHost);
Services.prefs.setCharPref(
"network.http.http3.alt-svc-mapping-for-testing",
`${testHost};h3=:${h3Port}`
);
function makeStuckTestChan(path) {
let chan = NetUtil.newChannel({
uri: `https:
//${testHost}:${h3Port}${path}`,
loadUsingSystemPrincipal:
true,
}).QueryInterface(Ci.nsIHttpChannel);
chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
return chan;
}
// Clear any existing connections
Services.obs.notifyObservers(
null,
"net:cancel-all-connections");
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await
new Promise(resolve => setTimeout(resolve,
1000));
// Make first request to establish a resumable session
info(
"Establishing initial session for 0-RTT");
let chan = makeStuckTestChan(
"/30");
let [req] = await channelOpenPromise(chan);
Assert.equal(req.status, Cr.NS_OK);
// Wait to ensure session info is saved
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await
new Promise(resolve => setTimeout(resolve,
500));
// Tell the server to enable stuck 0-RTT mode for the next connection
info(
"Enabling stuck 0-RTT mode on test server");
let setupChan = makeStuckTestChan(
"/SetStuckZeroRtt");
let [setupReq] = await channelOpenPromise(setupChan);
Assert.equal(setupReq.status, Cr.NS_OK);
info(
"Stuck 0-RTT mode enabled on server");
// Wait a bit
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await
new Promise(resolve => setTimeout(resolve,
500));
// Clear connections to force a new 0-RTT connection
Services.obs.notifyObservers(
null,
"net:cancel-all-connections");
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await
new Promise(resolve => setTimeout(resolve,
1000));
info(
"Making multiple requests that will encounter stuck 0-RTT session");
let numRequests =
3;
let promises = [];
let startTime = Date.now();
for (let i =
0; i < numRequests; i++) {
let stuckChan = makeStuckTestChan(`/
30?stuck_test=${i}`);
promises.push(channelOpenPromise(stuckChan, CL_ALLOW_UNKNOWN_CL));
}
// Wait for all requests to complete
let results = await Promise.all(promises);
let elapsed = Date.now() - startTime;
info(`All ${numRequests} requests completed after ${elapsed}ms`);
// Verify all requests completed successfully with H2 fallback
results.forEach(([req, buffer], index) => {
Assert.equal(req.status, Cr.NS_OK, `Request ${index} should succeed`);
Assert.equal(
buffer,
"H2 fallback response",
`Request ${index} should receive H2 fallback response`
);
info(`Request ${index} completed successfully with H2 fallback`);
});
// All requests should complete within a reasonable time
Assert.less(
elapsed,
10000,
"All requests should complete within 10 seconds via fallback"
);
});