// // Simple WebTransport test // /* import-globals-from head_webtransport.js */ "use strict"; var h3Port; var host; var { setTimeout } = ChromeUtils.importESModule( "resource://gre/modules/Timer.sys.mjs" ); registerCleanupFunction(async () => { Services.prefs.clearUserPref("network.dns.localDomains"); Services.prefs.clearUserPref("network.webtransport.datagrams.enabled"); Services.prefs.clearUserPref( "network.http.http3.alt-svc-mapping-for-testing" ); }); add_task(async function setup() { await http3_setup_tests("h3"); Services.prefs.setBoolPref("network.webtransport.datagrams.enabled", true); h3Port = Services.env.get("MOZHTTP3_PORT"); Assert.notEqual(h3Port, null); Assert.notEqual(h3Port, ""); host = "foo.example.com:" + h3Port; }); function makeChan(url) { let chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true, contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, }).QueryInterface(Ci.nsIHttpChannel); chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; return chan; } function channelOpenPromise(chan, flags) { // eslint-disable-next-line no-async-promise-executor return new Promise(async resolve => { function finish(req, buffer) { resolve([req, buffer]); } let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal); internal.setWaitForHTTPSSVCRecord(); chan.asyncOpen(new ChannelListener(finish, null, flags)); }); } function bytesFromString(str) { return new TextEncoder().encode(str); } add_task(async function test_wt_datagram() { let webTransport = NetUtil.newWebTransport(); let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); let pReady = new Promise(resolve => { listener.ready = resolve; }); let pSize = new Promise(resolve => { listener.onMaxDatagramSize = resolve; }); let pOutcome = new Promise(resolve => { listener.onDatagramOutcome = resolve; }); webTransport.asyncConnect( NetUtil.newURI(`https://${host}/success`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); await pReady; webTransport.getMaxDatagramSize(); let size = await pSize; info("max size:" + size); let rawData = new Uint8Array(size); rawData.fill(42); webTransport.sendDatagram(rawData, 1); let { id, outcome } = await pOutcome; Assert.equal(id, 1); Assert.equal(outcome, Ci.WebTransportSessionEventListener.SENT); let chan = makeChan(`https://${host}/get_webtransport_datagram`); let [req, buffer] = await channelOpenPromise(chan); Assert.equal(req.protocolVersion, "h3"); Assert.deepEqual(bytesFromString(buffer), rawData); webTransport.getMaxDatagramSize(); size = await pSize; info("max size:" + size); rawData = new Uint8Array(size + 1); webTransport.sendDatagram(rawData, 2); pOutcome = new Promise(resolve => { listener.onDatagramOutcome = resolve; }); ({ id, outcome } = await pOutcome); Assert.equal(id, 2); Assert.equal( outcome, Ci.WebTransportSessionEventListener.DROPPED_TOO_MUCH_DATA ); webTransport.closeSession(0, ""); }); add_task(async function test_connect_wt() { let webTransport = NetUtil.newWebTransport(); await new Promise(resolve => { let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); listener.ready = resolve; webTransport.asyncConnect( NetUtil.newURI(`https://${host}/success`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); }); webTransport.closeSession(0, ""); }); add_task(async function test_redirect_wt() { let webTransport = NetUtil.newWebTransport(); await new Promise(resolve => { let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); listener.closed = resolve; webTransport.asyncConnect( NetUtil.newURI(`https://${host}/redirect`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); }); }); add_task(async function test_reject() { let webTransport = NetUtil.newWebTransport(); await new Promise(resolve => { let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); listener.closed = resolve; webTransport.asyncConnect( NetUtil.newURI(`https://${host}/reject`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); }); }); async function test_closed(path) { let webTransport = NetUtil.newWebTransport(); let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); let pReady = new Promise(resolve => { listener.ready = resolve; }); let pClose = new Promise(resolve => { listener.closed = resolve; }); webTransport.asyncConnect( NetUtil.newURI(`https://${host}${path}`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); await pReady; await pClose; } add_task(async function test_closed_0ms() { await test_closed("/closeafter0ms"); }); add_task(async function test_closed_100ms() { await test_closed("/closeafter100ms"); }); add_task(async function test_wt_stream_create() { let webTransport = NetUtil.newWebTransport().QueryInterface( Ci.nsIWebTransport ); await new Promise(resolve => { let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); listener.ready = resolve; webTransport.asyncConnect( NetUtil.newURI(`https://${host}/success`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); }); await streamCreatePromise(webTransport, true); await streamCreatePromise(webTransport, false); webTransport.closeSession(0, ""); }); add_task(async function test_wt_stream_send_and_stats() { let webTransport = NetUtil.newWebTransport().QueryInterface( Ci.nsIWebTransport ); await new Promise(resolve => { let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); listener.ready = resolve; webTransport.asyncConnect( NetUtil.newURI(`https://${host}/success`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); }); let stream = await streamCreatePromise(webTransport, false); let outputStream = stream.outputStream; let data = "1234567890ABC"; outputStream.write(data, data.length); // We need some time to send the packet out. // eslint-disable-next-line mozilla/no-arbitrary-setTimeout await new Promise(resolve => setTimeout(resolve, 2000)); let stats = await sendStreamStatsPromise(stream); Assert.equal(stats.bytesSent, data.length); webTransport.closeSession(0, ""); }); add_task(async function test_wt_receive_stream_and_stats() { let webTransport = NetUtil.newWebTransport().QueryInterface( Ci.nsIWebTransport ); let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); let pReady = new Promise(resolve => { listener.ready = resolve; }); let pStreamReady = new Promise(resolve => { listener.streamAvailable = resolve; }); webTransport.asyncConnect( NetUtil.newURI(`https://${host}/create_unidi_stream_and_hello`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); await pReady; let stream = await pStreamReady; let data = await new Promise(resolve => { let handler = new inputStreamReader().QueryInterface( Ci.nsIInputStreamCallback ); handler.finish = resolve; let inputStream = stream.inputStream; inputStream.asyncWait(handler, 0, 0, Services.tm.currentThread); }); info("data: " + data); Assert.equal(data, "qwerty"); let stats = await receiveStreamStatsPromise(stream); Assert.equal(stats.bytesReceived, data.length); stream.sendStopSending(0); webTransport.closeSession(0, ""); }); add_task(async function test_wt_outgoing_bidi_stream() { let webTransport = NetUtil.newWebTransport().QueryInterface( Ci.nsIWebTransport ); await new Promise(resolve => { let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); listener.ready = resolve; webTransport.asyncConnect( NetUtil.newURI(`https://${host}/success`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); }); let stream = await streamCreatePromise(webTransport, true); let outputStream = stream.outputStream; let data = "1234567"; outputStream.write(data, data.length); let received = await new Promise(resolve => { let handler = new inputStreamReader().QueryInterface( Ci.nsIInputStreamCallback ); handler.finish = resolve; let inputStream = stream.inputStream; inputStream.asyncWait(handler, 0, 0, Services.tm.currentThread); }); info("received: " + received); Assert.equal(received, data); let stats = await sendStreamStatsPromise(stream); Assert.equal(stats.bytesSent, data.length); stats = await receiveStreamStatsPromise(stream); Assert.equal(stats.bytesReceived, data.length); webTransport.closeSession(0, ""); }); add_task(async function test_wt_incoming_bidi_stream() { let webTransport = NetUtil.newWebTransport().QueryInterface( Ci.nsIWebTransport ); let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); let pReady = new Promise(resolve => { listener.ready = resolve; }); let pStreamReady = new Promise(resolve => { listener.streamAvailable = resolve; }); webTransport.asyncConnect( NetUtil.newURI(`https://${host}/create_bidi_stream`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); await pReady; let stream = await pStreamReady; let outputStream = stream.outputStream; let data = "12345678"; outputStream.write(data, data.length); let received = await new Promise(resolve => { let handler = new inputStreamReader().QueryInterface( Ci.nsIInputStreamCallback ); handler.finish = resolve; let inputStream = stream.inputStream; inputStream.asyncWait(handler, 0, 0, Services.tm.currentThread); }); info("received: " + received); Assert.equal(received, data); let stats = await sendStreamStatsPromise(stream); Assert.equal(stats.bytesSent, data.length); stats = await receiveStreamStatsPromise(stream); Assert.equal(stats.bytesReceived, data.length); webTransport.closeSession(0, ""); }); async function createWebTransportAndConnect() { let webTransport = NetUtil.newWebTransport(); await new Promise(resolve => { let listener = new WebTransportListener().QueryInterface( Ci.WebTransportSessionEventListener ); listener.ready = resolve; webTransport.asyncConnect( NetUtil.newURI(`https://${host}/success`), true, [], Services.scriptSecurityManager.getSystemPrincipal(), Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, listener ); }); return webTransport; } add_task(async function test_multple_webtransport_connnection() { let webTransports = []; for (let i = 0; i < 3; i++) { let transport = await createWebTransportAndConnect(); webTransports.push(transport); } let first = webTransports[0]; await streamCreatePromise(first, true); for (let i = 0; i < 3; i++) { webTransports[i].closeSession(0, ""); } });