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

/*
 * Most of tests in this file are reused from test_trr.js, except tests listed
 * below. Since ODoH doesn't support push and customerized DNS resolver, some
 * related tests are skipped.
 *
 * test_trr_flags
 * test_push
 * test_clearCacheOnURIChange // TODO: Clear DNS cache when ODoH prefs changed.
 * test_dnsSuffix
 * test_async_resolve_with_trr_server
 * test_content_encoding_gzip
 * test_redirect
 * test_confirmation
 * test_detected_uri
 * test_pref_changes
 * test_dohrollout_mode
 * test_purge_trr_cache_on_mode_change
 * test_old_bootstrap_pref
 */

"use strict";

/* import-globals-from trr_common.js */

ChromeUtils.import("resource://gre/modules/NetUtil.jsm");

const certOverrideService = Cc[
  "@mozilla.org/security/certoverride;1"
].getService(Ci.nsICertOverrideService);

add_setup(async function setup() {
  h2Port = trr_test_setup();
  runningODoHTests = true;

  // This is for skiping the security check for the odoh host.
  certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
    true
  );

  registerCleanupFunction(() => {
    trr_clear_prefs();
    Services.prefs.clearUserPref("network.trr.odoh.enabled");
    Services.prefs.clearUserPref("network.trr.odoh.target_path");
    Services.prefs.clearUserPref("network.trr.odoh.configs_uri");
    certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
      false
    );
  });

  if (mozinfo.socketprocess_networking) {
    Services.dns; // Needed to trigger socket process.
    await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
  }

  Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
});

add_task(async function testODoHConfig() {
  // use the h2 server as DOH provider
  Services.prefs.setCharPref(
    "network.trr.uri",
    "https://foo.example.com:" + h2Port + "/odohconfig"
  );

  let { inRecord } = await new TRRDNSListener("odoh_host.example.com", {
    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
  });
  let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
  Assert.equal(answer[0].priority, 1);
  Assert.equal(answer[0].name, "odoh_host.example.com");
  Assert.equal(answer[0].values.length, 1);
  let odohconfig = answer[0].values[0].QueryInterface(Ci.nsISVCParamODoHConfig)
    .ODoHConfig;
  Assert.equal(odohconfig.length, 46);
});

let testIndex = 0;

async function ODoHConfigTest(query, ODoHHost, expectedResult = false) {
  Services.prefs.setCharPref(
    "network.trr.uri",
    "https://foo.example.com:" + h2Port + "/odohconfig?" + query
  );

  // Setting the pref "network.trr.odoh.target_host" will trigger the reload of
  // the ODoHConfigs.
  if (ODoHHost != undefined) {
    Services.prefs.setCharPref("network.trr.odoh.target_host", ODoHHost);
  } else {
    Services.prefs.setCharPref(
      "network.trr.odoh.target_host",
      `https://odoh_host_${testIndex++}.com`
    );
  }

  await topicObserved("odoh-service-activated");
  Assert.equal(Services.dns.ODoHActivated, expectedResult);
}

add_task(async function testODoHConfig1() {
  await ODoHConfigTest("invalid=empty");
});

add_task(async function testODoHConfig2() {
  await ODoHConfigTest("invalid=version");
});

add_task(async function testODoHConfig3() {
  await ODoHConfigTest("invalid=configLength");
});

add_task(async function testODoHConfig4() {
  await ODoHConfigTest("invalid=totalLength");
});

add_task(async function testODoHConfig5() {
  await ODoHConfigTest("invalid=kemId");
});

add_task(async function testODoHConfig6() {
  // Use a very short TTL.
  Services.prefs.setIntPref("network.trr.odoh.min_ttl", 1);
  await ODoHConfigTest("invalid=kemId&ttl=1");

  // This is triggered by the expiration of the TTL.
  await topicObserved("odoh-service-activated");
  Assert.ok(!Services.dns.ODoHActivated);
  Services.prefs.clearUserPref("network.trr.odoh.min_ttl");
});

add_task(async function testODoHConfig7() {
  Services.dns.clearCache(true);
  Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
  Services.prefs.setBoolPref("network.trr.odoh.enabled", true);
  // At this point, we've queried the ODoHConfig, but there is no usable config
  // (kemId is not supported). So, we should see the DNS result is from the
  // native resolver.
  await new TRRDNSListener("bar.example.com", "127.0.0.1");
});

async function ODoHConfigTestHTTP(configUri, expectedResult) {
  // Setting the pref "network.trr.odoh.configs_uri" will trigger the reload of
  // the ODoHConfigs.
  Services.prefs.setCharPref("network.trr.odoh.configs_uri", configUri);

  await topicObserved("odoh-service-activated");
  Assert.equal(Services.dns.ODoHActivated, expectedResult);
}

add_task(async function testODoHConfig8() {
  Services.dns.clearCache(true);
  Services.prefs.setCharPref("network.trr.uri", "");

  await ODoHConfigTestHTTP(
    `https://foo.example.com:${h2Port}/odohconfig?downloadFrom=http`,
    true
  );
});

add_task(async function testODoHConfig9() {
  // Reset the prefs back to the correct values, so we will get valid
  // ODoHConfigs when "network.trr.odoh.configs_uri" is cleared below.
  Services.prefs.setCharPref(
    "network.trr.uri",
    `https://foo.example.com:${h2Port}/odohconfig`
  );
  Services.prefs.setCharPref(
    "network.trr.odoh.target_host",
    `https://odoh_host.example.com:${h2Port}`
  );

  // Use a very short TTL.
  Services.prefs.setIntPref("network.trr.odoh.min_ttl", 1);
  // This will trigger to download ODoHConfigs from HTTPS RR.
  Services.prefs.clearUserPref("network.trr.odoh.configs_uri");

  await topicObserved("odoh-service-activated");
  Assert.ok(Services.dns.ODoHActivated);

  await ODoHConfigTestHTTP(
    `https://foo.example.com:${h2Port}/odohconfig?downloadFrom=http`,
    true
  );

  // This is triggered by the expiration of the TTL.
  await topicObserved("odoh-service-activated");
  Assert.ok(Services.dns.ODoHActivated);
  Services.prefs.clearUserPref("network.trr.odoh.min_ttl");
});

add_task(async function testODoHConfig10() {
  // We can still get ODoHConfigs from HTTPS RR.
  await ODoHConfigTestHTTP("http://invalid_odoh_config.com", true);
});

add_task(async function ensureODoHConfig() {
  Services.prefs.setBoolPref("network.trr.odoh.enabled", true);
  // Make sure we can connect to ODOH target successfully.
  Services.prefs.setCharPref(
    "network.dns.localDomains",
    "odoh_host.example.com"
  );

  // Make sure we have an usable ODoHConfig to use.
  await ODoHConfigTestHTTP(
    `https://foo.example.com:${h2Port}/odohconfig?downloadFrom=http`,
    true
  );
});

add_task(test_A_record);

add_task(test_AAAA_records);

add_task(test_RFC1918);

add_task(test_GET_ECS);

add_task(test_timeout_mode3);

add_task(test_strict_native_fallback);

add_task(test_no_answers_fallback);

add_task(test_404_fallback);

add_task(test_mode_1_and_4);

add_task(test_CNAME);

add_task(test_name_mismatch);

add_task(test_mode_2);

add_task(test_excluded_domains);

add_task(test_captiveportal_canonicalURL);

add_task(test_parentalcontrols);

// TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref
add_task(test_builtin_excluded_domains);

add_task(test_excluded_domains_mode3);

add_task(test25e);

add_task(test_parentalcontrols_mode3);

add_task(test_builtin_excluded_domains_mode3);

add_task(count_cookies);

add_task(test_connection_closed);

add_task(test_fetch_time);

add_task(test_fqdn);

add_task(test_ipv6_trr_fallback);

add_task(test_ipv4_trr_fallback);

add_task(test_no_retry_without_doh);

add_task(test_connection_reuse_and_cycling).skip(); // Bug 1742743

add_task(async function testODoHConfigNotAvailableInMode3() {
  Services.dns.clearCache(true);
  Services.prefs.setIntPref("network.trr.mode", 3);
  Services.prefs.setCharPref("network.trr.uri", "");

  await ODoHConfigTestHTTP("https://failed_odoh_config.com", false);

  // In mode 3, the DNS lookup should fail.
  let { inStatus } = await new TRRDNSListener("test.example.com", {
    expectedSuccess: false,
  });

  Assert.equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
});

add_task(async function testODoHConfigNotAvailableInMode2() {
  Services.dns.clearCache(true);
  Services.prefs.setIntPref("network.trr.mode", 2);
  Services.prefs.setCharPref("network.trr.uri", "");

  await ODoHConfigTestHTTP("https://failed_odoh_config_1.com", false);

  // In mode 2, we fallback to native.
  await new TRRDNSListener("test.example.com", "127.0.0.1");
});
