Commit 15ef80c0 authored by edgul's avatar edgul
Browse files

Bug 1818828 - Added basic DOM-using xpcshell testing for simple webtransport...

Bug 1818828 - Added basic DOM-using xpcshell testing for simple webtransport coverage. r=necko-reviewers,kershaw,jesup

Differential Revision: https://phabricator.services.mozilla.com/D170955
parent 3ed74a8d
Loading
Loading
Loading
Loading
+129 −0
Original line number Diff line number Diff line
/* 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/. */

// Some basic WebTransport tests for:
// * session rejection and redirection
// * session and stream creation
// * reading from incoming streams (uni)
//
// keep eslint happy until it knows about WebTransport
/* global WebTransport:false */

"use strict";

var h3Port;
var host;

registerCleanupFunction(async () => {
  Services.prefs.clearUserPref("network.dns.localDomains");
  Services.prefs.clearUserPref("network.webtransport.enabled");
  Services.prefs.clearUserPref("network.webtransport.datagrams.enabled");
  Services.prefs.clearUserPref("network.webtransport.redirect.enabled");
});

var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");

function readFile(file) {
  let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
    Ci.nsIFileInputStream
  );
  fstream.init(file, -1, 0, 0);
  let data = NetUtil.readInputStreamToString(fstream, fstream.available());
  fstream.close();
  return data;
}

function addCertFromFile(certdb, filename, trustString) {
  let certFile = do_get_file(filename, false);
  let pem = readFile(certFile)
    .replace(/-----BEGIN CERTIFICATE-----/, "")
    .replace(/-----END CERTIFICATE-----/, "")
    .replace(/[\r\n]/g, "");
  certdb.addCertFromBase64(pem, trustString);
}

add_setup(async function setup() {
  Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
  Services.prefs.setBoolPref("network.webtransport.enabled", true);
  Services.prefs.setBoolPref("network.webtransport.datagrams.enabled", true);
  Services.prefs.setBoolPref("network.webtransport.redirect.enabled", true);

  h3Port = Services.env.get("MOZHTTP3_PORT");
  Assert.notEqual(h3Port, null);
  Assert.notEqual(h3Port, "");
  host = "foo.example.com:" + h3Port;
  do_get_profile();

  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    Ci.nsIX509CertDB
  );
  // `../unit/` so that unit_ipc tests can use as well
  addCertFromFile(
    certdb,
    "../../../../netwerk/test/unit/http2-ca.pem",
    "CTu,u,u"
  );
});

add_task(async function test_webtransport_create() {
  const wt = new WebTransport("https://" + host + "/success");
  await wt.ready;
  wt.close();
});

add_task(async function test_redirect_wt() {
  let wt = new WebTransport("https://" + host + "/redirect");
  const e1 = await wt.ready.catch(e => e);
  const e2 = await wt.closed.catch(e => e);

  Assert.equal(e1, "WebTransportError: WebTransport connection rejected");
  Assert.equal(e2, "WebTransportError: WebTransport connection rejected");
});

add_task(async function test_reject_wt() {
  let wt = new WebTransport("https://" + host + "/reject");
  const e1 = await wt.ready.catch(e => e);
  const e2 = await wt.closed.catch(e => e);
  Assert.equal(e1, "WebTransportError: WebTransport connection rejected");
  Assert.equal(e2, "WebTransportError: WebTransport connection rejected");
});

add_task(async function test_immediate_server_close() {
  let wt = new WebTransport("https://" + host + "/closeafter0ms");
  await wt.ready;
  await wt.closed;
  Assert.ok(true);
});

add_task(async function test_delayed_server_close() {
  let wt = new WebTransport("https://" + host + "/closeafter100ms");
  await wt.ready;
  await wt.closed;
  Assert.ok(true);
});

add_task(async function test_wt_stream_create_bidi() {
  let wt = new WebTransport("https://" + host + "/success");
  await wt.ready;

  let bds = await wt.createBidirectionalStream();
  await bds.writable.close();
  await bds.readable.cancel();
  Assert.notEqual(bds, null);
  wt.close();
});

add_task(async function test_wt_stream_create_uni() {
  let wt = new WebTransport("https://" + host + "/success");
  await wt.ready;

  let uds = await wt.createUnidirectionalStream();
  Assert.notEqual(uds, null);
  await uds.close();
  wt.close();
});

// TODO: datagram test
// TODO: getStats tests
// TODO: fix the crash discussed in bug 1822154
+168 −0
Original line number Diff line number Diff line
/* 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/. */

// keep eslint happy until it knows about WebTransport
/* global WebTransport:false */
/* global TextDecoderStream:false */

// Using multiple files to reduce racing
// This file tests reading/writing to incoming/outgoing streams (uni & bidi)
//
"use strict";

var h3Port;
var host;

registerCleanupFunction(async () => {
  Services.prefs.clearUserPref("network.dns.localDomains");
  Services.prefs.clearUserPref("network.webtransport.enabled");
  Services.prefs.clearUserPref("network.webtransport.datagrams.enabled");
  Services.prefs.clearUserPref("network.webtransport.redirect.enabled");
});

var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");

function readFile(file) {
  let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
    Ci.nsIFileInputStream
  );
  fstream.init(file, -1, 0, 0);
  let data = NetUtil.readInputStreamToString(fstream, fstream.available());
  fstream.close();
  return data;
}

function addCertFromFile(certdb, filename, trustString) {
  let certFile = do_get_file(filename, false);
  let pem = readFile(certFile)
    .replace(/-----BEGIN CERTIFICATE-----/, "")
    .replace(/-----END CERTIFICATE-----/, "")
    .replace(/[\r\n]/g, "");
  certdb.addCertFromBase64(pem, trustString);
}

add_setup(async function setup() {
  Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
  Services.prefs.setBoolPref("network.webtransport.enabled", true);
  Services.prefs.setBoolPref("network.webtransport.datagrams.enabled", true);
  Services.prefs.setBoolPref("network.webtransport.redirect.enabled", true);

  h3Port = Services.env.get("MOZHTTP3_PORT");
  Assert.notEqual(h3Port, null);
  Assert.notEqual(h3Port, "");
  host = "foo.example.com:" + h3Port;
  do_get_profile();

  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    Ci.nsIX509CertDB
  );
  // `../unit/` so that unit_ipc tests can use as well
  addCertFromFile(
    certdb,
    "../../../../netwerk/test/unit/http2-ca.pem",
    "CTu,u,u"
  );
});

// Read all chunks from |readable_stream|, decode chunks to a utf-8 string, then
// return the string. (borrowed from wpt tests)
async function read_stream_as_string(readable_stream) {
  const decoder = new TextDecoderStream();
  const decode_stream = readable_stream.pipeThrough(decoder);
  const reader = decode_stream.getReader();

  let chunks = "";
  while (true) {
    const { value: chunk, done } = await reader.read();
    if (done) {
      break;
    }
    chunks += chunk;
  }
  reader.releaseLock();
  return chunks;
}

add_task(async function test_wt_incoming_unidi_stream() {
  // trigger stream creation server side and default echo
  let wt = new WebTransport(
    "https://" + host + "/create_unidi_stream_and_hello"
  );
  await wt.ready;

  const streams = await wt.incomingUnidirectionalStreams;
  const stream_reader = streams.getReader();
  const { value: recv_stream } = await stream_reader.read();
  let str = await read_stream_as_string(recv_stream);
  stream_reader.releaseLock();
  Assert.equal(str, "qwerty");

  wt.close();
});

add_task(async function test_wt_incoming_and_outgoing_unidi_stream() {
  // create the client's incoming stream from the server side
  // we need it to listen to the echo back
  let wt = new WebTransport("https://" + host + "/create_unidi_stream");
  await wt.ready;

  // send hello to server
  let expected = "uni_hello";
  let writableStream = await wt.createUnidirectionalStream(); // only triggers NewStream OnWrite
  let wsDefaultWriter = writableStream.getWriter();
  await wsDefaultWriter.ready;
  let data = new TextEncoder().encode(expected);
  await wsDefaultWriter.write(data); // triggers Http3ServerEvent::Data
  await wsDefaultWriter.close();
  wsDefaultWriter.releaseLock();

  // read the echo
  const streams = await wt.incomingUnidirectionalStreams;
  const stream_reader = streams.getReader();
  const { value: recv_stream } = await stream_reader.read();
  let str = await read_stream_as_string(recv_stream);
  Assert.equal(str, expected);
  stream_reader.releaseLock();
  await recv_stream.closed;

  wt.close();
});

add_task(async function test_wt_outgoing_bidi_stream() {
  let wt = new WebTransport("https://" + host + "/success");
  await wt.ready;

  // write to server
  let wtbds = await wt.createBidirectionalStream();
  let writableStream = wtbds.writable;
  let wsDefaultWriter = writableStream.getWriter();
  await wsDefaultWriter.ready;
  let expected = "xyzhello";
  let data = new TextEncoder().encode(expected);
  await wsDefaultWriter.write(data);
  await wsDefaultWriter.close();
  wsDefaultWriter.releaseLock();

  // string goes through server and is echoed back here
  const str = await read_stream_as_string(wtbds.readable);
  Assert.equal(str, expected);

  wt.close();
});

add_task(async function test_wt_incoming_bidi_stream() {
  let wt = new WebTransport(
    "https://" + host + "/create_bidi_stream_and_hello"
  );
  // await wt.ready; // causes occasional hang on release --verify

  const stream_reader = wt.incomingBidirectionalStreams.getReader();
  const { value: bidi_stream } = await stream_reader.read();
  stream_reader.releaseLock();

  const str = await read_stream_as_string(bidi_stream.readable);
  Assert.equal(str, "asdfg");

  wt.close();
});
+12 −0
Original line number Diff line number Diff line
@@ -9,3 +9,15 @@
skip-if =
  os == 'android' || socketprocess_networking
  os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807925
[test_simple_conn.js]
skip-if =
  os == 'android'
  socketprocess_networking
  os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807925
run-sequentially = http3server
[test_simple_stream.js]
skip-if =
  os == 'android' 
  socketprocess_networking
  os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807925
run-sequentially = http3server
+82 −10
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ use neqo_http3::{
    Error, Http3OrWebTransportStream, Http3Parameters, Http3Server, Http3ServerEvent,
    WebTransportRequest, WebTransportServerEvent, WebTransportSessionAcceptAction,
};
use neqo_transport::server::Server;
use neqo_transport::server::{Server, ActiveConnectionRef};
use neqo_transport::{
    ConnectionEvent, ConnectionParameters, Output, RandomConnectionIdGenerator, StreamId,
    StreamType,
@@ -81,8 +81,10 @@ struct Http3TestServer {
    responses: HashMap<Http3OrWebTransportStream, Vec<u8>>,
    current_connection_hash: u64,
    sessions_to_close: HashMap<Instant, Vec<WebTransportRequest>>,
    sessions_to_create_stream: Vec<(WebTransportRequest, StreamType)>,
    sessions_to_create_stream: Vec<(WebTransportRequest, StreamType, bool)>,
    webtransport_bidi_stream: HashSet<Http3OrWebTransportStream>,
    wt_unidi_conn_to_stream: HashMap<ActiveConnectionRef, Http3OrWebTransportStream>,
    wt_unidi_echo_back: HashMap<Http3OrWebTransportStream, Http3OrWebTransportStream>,
}

impl ::std::fmt::Display for Http3TestServer {
@@ -101,6 +103,8 @@ impl Http3TestServer {
            sessions_to_close: HashMap::new(),
            sessions_to_create_stream: Vec::new(),
            webtransport_bidi_stream: HashSet::new(),
            wt_unidi_conn_to_stream: HashMap::new(),
            wt_unidi_echo_back: HashMap::new(),
        }
    }

@@ -162,13 +166,28 @@ impl Http3TestServer {
        let mut session = tuple.0;
        let mut wt_server_stream = session.create_stream(tuple.1).unwrap();
        if tuple.1 == StreamType::UniDi {
            let content = b"0123456789".to_vec();
            wt_server_stream.send_data(&content).unwrap();
            if tuple.2 {
                wt_server_stream.send_data(b"qwerty").unwrap();
                wt_server_stream.stream_close_send().unwrap();
            } else {
                // relaying Http3ServerEvent::Data to uni streams
                // slows down netwerk/test/unit/test_webtransport_simple.js
                // to the point of failure. Only do so when necessary.
                self.wt_unidi_conn_to_stream.insert(wt_server_stream.conn.clone(), wt_server_stream);
            }
        } else {
            if tuple.2 {
                wt_server_stream.send_data(b"asdfg").unwrap();
                wt_server_stream.stream_close_send().unwrap();
                wt_server_stream
                    .stream_stop_sending(Error::HttpNoError.code())
                    .unwrap();
            } else {
                self.webtransport_bidi_stream.insert(wt_server_stream);
            }
        }
    }
}

impl HttpServer for Http3TestServer {
    fn process(&mut self, dgram: Option<Datagram>) -> Output {
@@ -378,10 +397,21 @@ impl HttpServer for Http3TestServer {
                    data,
                    fin,
                } => {
                    // echo bidirectional input back to client
                    if self.webtransport_bidi_stream.contains(&stream) {
                        self.new_response(stream, data);
                        break;
                    }

                    // echo unidirectional input to back to client
                    // need to close or we hang
                    if self.wt_unidi_echo_back.contains_key(&stream) {
                        let mut echo_back = self.wt_unidi_echo_back.remove(&stream).unwrap();
                        echo_back.send_data(&data).unwrap();
                        echo_back.stream_close_send().unwrap();
                        break;
                    }

                    if let Some(r) = self.posts.get_mut(&stream) {
                        *r += data.len();
                    }
@@ -457,6 +487,11 @@ impl HttpServer for Http3TestServer {
                                session
                                    .response(&WebTransportSessionAcceptAction::Accept)
                                    .unwrap();
                                let now = Instant::now();
                                if !self.sessions_to_close.contains_key(&now) {
                                    self.sessions_to_close.insert(now, Vec::new());
                                }
                                self.sessions_to_close.get_mut(&now).unwrap().push(session);
                            } else if path == "/closeafter100ms" {
                                session
                                    .response(&WebTransportSessionAcceptAction::Accept)
@@ -473,14 +508,39 @@ impl HttpServer for Http3TestServer {
                                session
                                    .response(&WebTransportSessionAcceptAction::Accept)
                                    .unwrap();
                                self.sessions_to_create_stream
                                    .push((session, StreamType::UniDi));
                                self.sessions_to_create_stream.push((
                                    session,
                                    StreamType::UniDi,
                                    false,
                                ));
                            } else if path == "/create_unidi_stream_and_hello" {
                                session
                                    .response(&WebTransportSessionAcceptAction::Accept)
                                    .unwrap();
                                self.sessions_to_create_stream.push((
                                    session,
                                    StreamType::UniDi,
                                    true,
                                ));
                            } else if path == "/create_bidi_stream" {
                                session
                                    .response(&WebTransportSessionAcceptAction::Accept)
                                    .unwrap();
                                self.sessions_to_create_stream
                                    .push((session, StreamType::BiDi));
                                self.sessions_to_create_stream.push((
                                    session,
                                    StreamType::BiDi,
                                    false,
                                ));
                            } else if path == "/create_bidi_stream_and_hello" {
                                self.webtransport_bidi_stream.clear();
                                session
                                    .response(&WebTransportSessionAcceptAction::Accept)
                                    .unwrap();
                                self.sessions_to_create_stream.push((
                                    session,
                                    StreamType::BiDi,
                                    true,
                                ));
                            } else {
                                session
                                    .response(&WebTransportSessionAcceptAction::Accept)
@@ -508,8 +568,20 @@ impl HttpServer for Http3TestServer {
                    );
                }
                Http3ServerEvent::WebTransport(WebTransportServerEvent::NewStream(stream)) => {
                    // new stream could be from client-outgoing unidirectional
                    // or bidirectional
                    if !stream.stream_info.is_http() {
                        if stream.stream_id().is_bidi() {
                            self.webtransport_bidi_stream.insert(stream);
                        } else {
                            // Newly created stream happens on same connection
                            // as the stream creation for client's incoming stream.
                            // Link the streams with map for echo back
                            if self.wt_unidi_conn_to_stream.contains_key(&stream.conn) {
                                let s = self.wt_unidi_conn_to_stream.remove(&stream.conn).unwrap();
                                self.wt_unidi_echo_back.insert(stream, s);
                            }
                        }
                    }
                }
                Http3ServerEvent::WebTransport(WebTransportServerEvent::Datagram {
+4 −4
Original line number Diff line number Diff line
@@ -180,11 +180,11 @@ async function test_closed(path) {
}

add_task(async function test_closed_0ms() {
  test_closed("/closeafter0ms");
  await test_closed("/closeafter0ms");
});

add_task(async function test_closed_100ms() {
  test_closed("/closeafter100ms");
  await test_closed("/closeafter100ms");
});

add_task(async function test_wt_stream_create() {
@@ -266,7 +266,7 @@ add_task(async function test_wt_receive_stream_and_stats() {
    listener.streamAvailable = resolve;
  });
  webTransport.asyncConnect(
    NetUtil.newURI(`https://${host}/create_unidi_stream`),
    NetUtil.newURI(`https://${host}/create_unidi_stream_and_hello`),
    Services.scriptSecurityManager.getSystemPrincipal(),
    Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
    listener
@@ -285,7 +285,7 @@ add_task(async function test_wt_receive_stream_and_stats() {
  });

  info("data: " + data);
  Assert.equal(data, "0123456789");
  Assert.equal(data, "qwerty");

  let stats = await receiveStreamStatsPromise(stream);
  Assert.equal(stats.bytesReceived, data.length);