/* 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/. */

"use strict";

var { setTimeout } = ChromeUtils.importESModule(
  "resource://gre/modules/Timer.sys.mjs"
);

const { HTTP3Server } = ChromeUtils.importESModule(
  "resource://testing-common/NodeServer.sys.mjs"
);

let trrServer;
let h3NoResponsePort;
let tcpNoResponseSocket;
let tcpNoResponsePort;

add_setup(async function () {
  h3NoResponsePort = Services.env.get("MOZHTTP3_PORT_NO_RESPONSE");
  Assert.notEqual(h3NoResponsePort, null);
  Assert.notEqual(h3NoResponsePort, "");

  let h3ServerPath = Services.env.get("MOZ_HTTP3_SERVER_PATH");
  let h3DBPath = Services.env.get("MOZ_HTTP3_CERT_DB_PATH");

  let server = new HTTP3Server();
  await server.start(h3ServerPath, h3DBPath);

  tcpNoResponsePort = server.no_response_port();

  // A TCP server socket that listens but never accepts connections.
  // TCP handshake completes (OS handles SYN/SYN-ACK) but TLS never starts.
  tcpNoResponseSocket = Cc[
    "@mozilla.org/network/server-socket;1"
  ].createInstance(Ci.nsIServerSocket);
  tcpNoResponseSocket.init(tcpNoResponsePort, true, -1);

  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    Ci.nsIX509CertDB
  );
  addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");

  Services.prefs.setBoolPref("network.http.happy_eyeballs_enabled", true);

  trrServer = new TRRServer();
  await trrServer.start();
  trr_test_setup();
  Services.prefs.setIntPref("network.trr.mode", 3);
  Services.prefs.setCharPref(
    "network.trr.uri",
    `https://foo.example.com:${trrServer.port()}/dns-query`
  );

  registerCleanupFunction(async () => {
    Services.prefs.clearUserPref("network.http.happy_eyeballs_enabled");
    Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
    Services.prefs.clearUserPref(
      "network.http.http3.alt-svc-mapping-for-testing"
    );
    trr_clear_prefs();
    if (trrServer) {
      await trrServer.stop();
    }
    if (tcpNoResponseSocket) {
      tcpNoResponseSocket.close();
    }
    await server.stop();
  });
});

async function resetConnections() {
  Services.obs.notifyObservers(null, "net:cancel-all-connections");
  let nssComponent = Cc["@mozilla.org/network/ssl-tokens-cache;1"].getService(
    Ci.nsISSLTokensCache
  );
  await nssComponent.asyncClearSSLExternalAndInternalSessionCache();
  Services.dns.clearCache(true);
  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
  await new Promise(resolve => setTimeout(resolve, 1000));
}

async function do_test_h3_no_response(host) {
  await resetConnections();

  let chan = NetUtil.newChannel({
    uri: `https://${host}:${tcpNoResponsePort}/`,
    loadUsingSystemPrincipal: true,
    contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
  }).QueryInterface(Ci.nsIHttpChannel);
  chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;

  await new Promise(resolve => {
    chan.asyncOpen(
      new ChannelListener(
        (req, _buf) => {
          Assert.ok(
            !Components.isSuccessCode(req.status),
            `Channel should fail, status=0x${req.status.toString(16)}`
          );
          Assert.equal(
            req.status,
            Cr.NS_ERROR_NET_TIMEOUT,
            "Request should fail with NS_ERROR_NET_TIMEOUT"
          );
          resolve();
        },
        null,
        CL_EXPECT_FAILURE
      )
    );
  });
}

add_task(async function test_h3_connection_no_response() {
  Services.prefs.setIntPref("network.http.connection-timeout", 2);

  let host = "noresponse.example.com";
  Services.prefs.setCharPref(
    "network.http.http3.alt-svc-mapping-for-testing",
    `${host};h3=:${h3NoResponsePort}`
  );
  await trrServer.registerDoHAnswers(host, "A", {
    answers: [
      { name: host, ttl: 55, type: "A", flush: false, data: "127.0.0.1" },
    ],
  });

  Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
  await do_test_h3_no_response(host);

  Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
  await do_test_h3_no_response(host);

  Services.prefs.clearUserPref("network.http.connection-timeout");
  Services.prefs.clearUserPref(
    "network.http.http3.alt-svc-mapping-for-testing"
  );
});
