/* 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";

let trrServer;

function setup() {
  trr_test_setup();

  Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
  Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
  Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
}

setup();
registerCleanupFunction(async () => {
  trr_clear_prefs();
  Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
  Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
  Services.prefs.clearUserPref("network.dns.echconfig.enabled");
  Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
  Services.prefs.clearUserPref("network.http.http3.enable");
  Services.prefs.clearUserPref("network.http.http2.enabled");
  if (trrServer) {
    await trrServer.stop();
  }
});

function checkResult(inRecord, noHttp2, noHttp3, result) {
  if (!result) {
    Assert.throws(
      () => {
        inRecord
          .QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
          .GetServiceModeRecord(noHttp2, noHttp3);
      },
      /NS_ERROR_NOT_AVAILABLE/,
      "Should get an error"
    );
    return;
  }

  let record = inRecord
    .QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
    .GetServiceModeRecord(noHttp2, noHttp3);
  Assert.equal(record.priority, result.expectedPriority);
  Assert.equal(record.name, result.expectedName);
  Assert.equal(record.selectedAlpn, result.expectedAlpn);
}

// Test configuration:
//   There are two records: one has a echConfig and the other doesn't.
// We want to test if the record with echConfig is preferred.
add_task(async function testEchConfigEnabled() {
  let trrServer = new TRRServer();
  await trrServer.start();

  Services.prefs.setIntPref("network.trr.mode", 3);
  Services.prefs.setCharPref(
    "network.trr.uri",
    `https://foo.example.com:${trrServer.port}/dns-query`
  );
  Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);

  await trrServer.registerDoHAnswers("test.bar.com", "HTTPS", {
    answers: [
      {
        name: "test.bar.com",
        ttl: 55,
        type: "HTTPS",
        flush: false,
        data: {
          priority: 1,
          name: "test.bar_1.com",
          values: [{ key: "alpn", value: ["h3-29"] }],
        },
      },
      {
        name: "test.bar.com",
        ttl: 55,
        type: "HTTPS",
        flush: false,
        data: {
          priority: 2,
          name: "test.bar_2.com",
          values: [
            { key: "alpn", value: ["h2"] },
            { key: "echconfig", value: "456..." },
          ],
        },
      },
    ],
  });

  let { inRecord } = await new TRRDNSListener("test.bar.com", {
    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
  });

  checkResult(inRecord, false, false, {
    expectedPriority: 1,
    expectedName: "test.bar_1.com",
    expectedAlpn: "h3-29",
  });
  checkResult(inRecord, false, true, {
    expectedPriority: 2,
    expectedName: "test.bar_2.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, true, false, {
    expectedPriority: 1,
    expectedName: "test.bar_1.com",
    expectedAlpn: "h3-29",
  });
  checkResult(inRecord, true, true);

  Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
  Services.dns.clearCache(true);

  ({ inRecord } = await new TRRDNSListener("test.bar.com", {
    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
  }));

  checkResult(inRecord, false, false, {
    expectedPriority: 2,
    expectedName: "test.bar_2.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, false, true, {
    expectedPriority: 2,
    expectedName: "test.bar_2.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, true, false, {
    expectedPriority: 1,
    expectedName: "test.bar_1.com",
    expectedAlpn: "h3-29",
  });
  checkResult(inRecord, true, true);

  await trrServer.stop();
  trrServer = null;
});

// Test configuration:
//   There are two records: both have echConfigs, and only one supports h3.
// This test is about testing which record should we get when
// network.dns.http3_echconfig.enabled is true and false.
// When network.dns.http3_echconfig.enabled is false, we should try to
// connect with h2 and echConfig.
add_task(async function testTwoRecordsHaveEchConfig() {
  Services.dns.clearCache(true);

  let trrServer = new TRRServer();
  await trrServer.start();

  Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
  Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
  Services.prefs.setIntPref("network.trr.mode", 3);
  Services.prefs.setCharPref(
    "network.trr.uri",
    `https://foo.example.com:${trrServer.port}/dns-query`
  );

  await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
    answers: [
      {
        name: "test.foo.com",
        ttl: 55,
        type: "HTTPS",
        flush: false,
        data: {
          priority: 1,
          name: "test.foo_h3.com",
          values: [
            { key: "alpn", value: ["h3"] },
            { key: "echconfig", value: "456..." },
          ],
        },
      },
      {
        name: "test.foo.com",
        ttl: 55,
        type: "HTTPS",
        flush: false,
        data: {
          priority: 2,
          name: "test.foo_h2.com",
          values: [
            { key: "alpn", value: ["h2"] },
            { key: "echconfig", value: "456..." },
          ],
        },
      },
    ],
  });

  let { inRecord } = await new TRRDNSListener("test.foo.com", {
    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
  });

  checkResult(inRecord, false, false, {
    expectedPriority: 2,
    expectedName: "test.foo_h2.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, false, true, {
    expectedPriority: 2,
    expectedName: "test.foo_h2.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, true, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, true, true);

  Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
  Services.dns.clearCache(true);
  ({ inRecord } = await new TRRDNSListener("test.foo.com", {
    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
  }));

  checkResult(inRecord, false, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, false, true, {
    expectedPriority: 2,
    expectedName: "test.foo_h2.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, true, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, true, true);

  await trrServer.stop();
  trrServer = null;
});

// Test configuration:
//   There are two records: both have echConfigs, and one supports h3 and h2.
// When network.dns.http3_echconfig.enabled is false, we should use the record
// that supports h3 and h2 (the alpn is h2).
add_task(async function testTwoRecordsHaveEchConfig1() {
  Services.dns.clearCache(true);

  let trrServer = new TRRServer();
  await trrServer.start();

  Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
  Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
  Services.prefs.setIntPref("network.trr.mode", 3);
  Services.prefs.setCharPref(
    "network.trr.uri",
    `https://foo.example.com:${trrServer.port}/dns-query`
  );

  await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
    answers: [
      {
        name: "test.foo.com",
        ttl: 55,
        type: "HTTPS",
        flush: false,
        data: {
          priority: 1,
          name: "test.foo_h3.com",
          values: [
            { key: "alpn", value: ["h3", "h2"] },
            { key: "echconfig", value: "456..." },
          ],
        },
      },
      {
        name: "test.foo.com",
        ttl: 55,
        type: "HTTPS",
        flush: false,
        data: {
          priority: 2,
          name: "test.foo_h2.com",
          values: [
            { key: "alpn", value: ["h2", "http/1.1"] },
            { key: "echconfig", value: "456..." },
          ],
        },
      },
    ],
  });

  let { inRecord } = await new TRRDNSListener("test.foo.com", {
    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
  });

  checkResult(inRecord, false, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, false, true, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, true, false, {
    expectedPriority: 2,
    expectedName: "test.foo_h2.com",
    expectedAlpn: "http/1.1",
  });
  checkResult(inRecord, true, true, {
    expectedPriority: 2,
    expectedName: "test.foo_h2.com",
    expectedAlpn: "http/1.1",
  });

  Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
  Services.dns.clearCache(true);
  ({ inRecord } = await new TRRDNSListener("test.foo.com", {
    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
  }));

  checkResult(inRecord, false, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, false, true, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, true, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, true, true, {
    expectedPriority: 2,
    expectedName: "test.foo_h2.com",
    expectedAlpn: "http/1.1",
  });

  await trrServer.stop();
  trrServer = null;
});

// Test configuration:
//   There are two records: only one support h3 and only one has echConfig.
// This test is about never usng the record without echConfig.
add_task(async function testOneRecordsHasEchConfig() {
  Services.dns.clearCache(true);

  let trrServer = new TRRServer();
  await trrServer.start();

  Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
  Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
  Services.prefs.setIntPref("network.trr.mode", 3);
  Services.prefs.setCharPref(
    "network.trr.uri",
    `https://foo.example.com:${trrServer.port}/dns-query`
  );

  await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
    answers: [
      {
        name: "test.foo.com",
        ttl: 55,
        type: "HTTPS",
        flush: false,
        data: {
          priority: 1,
          name: "test.foo_h3.com",
          values: [
            { key: "alpn", value: ["h3"] },
            { key: "echconfig", value: "456..." },
          ],
        },
      },
      {
        name: "test.foo.com",
        ttl: 55,
        type: "HTTPS",
        flush: false,
        data: {
          priority: 2,
          name: "test.foo_h2.com",
          values: [{ key: "alpn", value: ["h2"] }],
        },
      },
    ],
  });

  let { inRecord } = await new TRRDNSListener("test.foo.com", {
    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
  });

  checkResult(inRecord, false, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, false, true, {
    expectedPriority: 2,
    expectedName: "test.foo_h2.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, true, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, true, true);

  Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
  Services.dns.clearCache(true);
  ({ inRecord } = await new TRRDNSListener("test.foo.com", {
    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
  }));

  checkResult(inRecord, false, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, false, true, {
    expectedPriority: 2,
    expectedName: "test.foo_h2.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, true, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, true, true);

  await trrServer.stop();
  trrServer = null;
});

// Test the case that "network.http.http3.enable" and
// "network.http.http2.enabled" are true/false.
add_task(async function testHttp3AndHttp2Pref() {
  Services.dns.clearCache(true);

  let trrServer = new TRRServer();
  await trrServer.start();

  Services.prefs.setBoolPref("network.http.http3.enable", false);
  Services.prefs.setBoolPref("network.dns.echconfig.enabled", false);
  Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", false);
  Services.prefs.setIntPref("network.trr.mode", 3);
  Services.prefs.setCharPref(
    "network.trr.uri",
    `https://foo.example.com:${trrServer.port}/dns-query`
  );

  await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
    answers: [
      {
        name: "test.foo.com",
        ttl: 55,
        type: "HTTPS",
        flush: false,
        data: {
          priority: 1,
          name: "test.foo_h3.com",
          values: [
            { key: "alpn", value: ["h3", "h2"] },
            { key: "echconfig", value: "456..." },
          ],
        },
      },
      {
        name: "test.foo.com",
        ttl: 55,
        type: "HTTPS",
        flush: false,
        data: {
          priority: 2,
          name: "test.foo_h2.com",
          values: [
            { key: "alpn", value: ["h2"] },
            { key: "echconfig", value: "456..." },
          ],
        },
      },
    ],
  });

  let { inRecord } = await new TRRDNSListener("test.foo.com", {
    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
  });

  checkResult(inRecord, false, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, false, true, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h2",
  });
  checkResult(inRecord, true, false);
  checkResult(inRecord, true, true);

  Services.prefs.setBoolPref("network.http.http2.enabled", false);
  checkResult(inRecord, false, false);

  Services.prefs.setBoolPref("network.http.http3.enable", true);
  checkResult(inRecord, false, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, false, true);
  checkResult(inRecord, true, false, {
    expectedPriority: 1,
    expectedName: "test.foo_h3.com",
    expectedAlpn: "h3",
  });
  checkResult(inRecord, true, true);

  await trrServer.stop();
  trrServer = null;
});
