tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_trr_httpssvc.js (23942B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const { TestUtils } = ChromeUtils.importESModule(
      8  "resource://testing-common/TestUtils.sys.mjs"
      9 );
     10 
     11 let h2Port;
     12 let trrServer;
     13 
     14 function inChildProcess() {
     15  return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
     16 }
     17 
     18 async function clearCache() {
     19  if (inChildProcess()) {
     20    do_send_remote_message("clearCache");
     21    await do_await_remote_message("clearCache-done");
     22  } else {
     23    Services.dns.clearCache(true);
     24  }
     25 }
     26 
     27 async function setTRRURI(uri) {
     28  if (!inChildProcess()) {
     29    Services.prefs.setCharPref("network.trr.uri", uri);
     30    Services.prefs.setIntPref("network.trr.mode", 3);
     31  } else {
     32    do_send_remote_message("set-trr-uri", uri);
     33    await do_await_remote_message("set-trr-uri-done");
     34  }
     35 }
     36 
     37 add_setup(async function setup() {
     38  trrServer = new TRRServer();
     39  await trrServer.start();
     40  h2Port = trrServer.port();
     41  ok(h2Port);
     42 
     43  registerCleanupFunction(async () => {
     44    if (trrServer) {
     45      await trrServer.stop();
     46    }
     47    if (inChildProcess()) {
     48      do_send_remote_message("set-trr-uri", "test-done");
     49      do_send_remote_message("clearCache", "test-done");
     50    }
     51  });
     52 
     53  if (inChildProcess()) {
     54    return;
     55  }
     56 
     57  trr_test_setup();
     58 
     59  registerCleanupFunction(async () => {
     60    trr_clear_prefs();
     61    Services.prefs.clearUserPref("network.dns.port_prefixed_qname_https_rr");
     62  });
     63 
     64  if (mozinfo.socketprocess_networking) {
     65    Services.dns; // Needed to trigger socket process.
     66    await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
     67  }
     68 });
     69 
     70 add_task(async function testHTTPSSVC() {
     71  // use the h2 server as DOH provider
     72  await setTRRURI("https://foo.example.com:" + h2Port + "/doh?httpssvc=1");
     73 
     74  let { inRecord } = await new TRRDNSListener("test.httpssvc.com", {
     75    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
     76  });
     77  Assert.ok(
     78    inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).IsTRR(),
     79    "resolved by TRR"
     80  );
     81  let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
     82  Assert.equal(answer[0].priority, 1);
     83  Assert.equal(answer[0].name, "h3pool");
     84  Assert.equal(answer[0].values.length, 7);
     85  Assert.deepEqual(
     86    answer[0].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
     87    ["h2", "h3"],
     88    "got correct answer"
     89  );
     90  Assert.ok(
     91    answer[0].values[1].QueryInterface(Ci.nsISVCParamNoDefaultAlpn),
     92    "got correct answer"
     93  );
     94  Assert.equal(
     95    answer[0].values[2].QueryInterface(Ci.nsISVCParamPort).port,
     96    8888,
     97    "got correct answer"
     98  );
     99  Assert.equal(
    100    answer[0].values[3].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0]
    101      .address,
    102    "1.2.3.4",
    103    "got correct answer"
    104  );
    105  Assert.equal(
    106    answer[0].values[4].QueryInterface(Ci.nsISVCParamEchConfig).echconfig,
    107    "123...",
    108    "got correct answer"
    109  );
    110  Assert.equal(
    111    answer[0].values[5].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0]
    112      .address,
    113    "::1",
    114    "got correct answer"
    115  );
    116  Assert.equal(
    117    answer[0].values[6].QueryInterface(Ci.nsISVCParamODoHConfig).ODoHConfig,
    118    "456...",
    119    "got correct answer"
    120  );
    121  Assert.equal(answer[1].priority, 2);
    122  Assert.equal(answer[1].name, "test.httpssvc.com");
    123  Assert.equal(answer[1].values.length, 5);
    124  Assert.deepEqual(
    125    answer[1].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
    126    ["h2"],
    127    "got correct answer"
    128  );
    129  Assert.equal(
    130    answer[1].values[1].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0]
    131      .address,
    132    "1.2.3.4",
    133    "got correct answer"
    134  );
    135  Assert.equal(
    136    answer[1].values[1].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[1]
    137      .address,
    138    "5.6.7.8",
    139    "got correct answer"
    140  );
    141  Assert.equal(
    142    answer[1].values[2].QueryInterface(Ci.nsISVCParamEchConfig).echconfig,
    143    "abc...",
    144    "got correct answer"
    145  );
    146  Assert.equal(
    147    answer[1].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0]
    148      .address,
    149    "::1",
    150    "got correct answer"
    151  );
    152  Assert.equal(
    153    answer[1].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[1]
    154      .address,
    155    "fe80::794f:6d2c:3d5e:7836",
    156    "got correct answer"
    157  );
    158  Assert.equal(
    159    answer[1].values[4].QueryInterface(Ci.nsISVCParamODoHConfig).ODoHConfig,
    160    "def...",
    161    "got correct answer"
    162  );
    163  Assert.equal(answer[2].priority, 3);
    164  Assert.equal(answer[2].name, "hello");
    165  Assert.equal(answer[2].values.length, 0);
    166 });
    167 
    168 add_task(async function test_aliasform() {
    169  await setTRRURI(`https://foo.example.com:${trrServer.port()}/dns-query`);
    170  await clearCache();
    171 
    172  // Make sure that HTTPS AliasForm is only treated as a CNAME for HTTPS requests
    173  await trrServer.registerDoHAnswers("test1.com", "A", {
    174    answers: [
    175      {
    176        name: "test1.com",
    177        ttl: 55,
    178        type: "HTTPS",
    179        flush: false,
    180        data: {
    181          priority: 0,
    182          name: "something1.com",
    183          values: [],
    184        },
    185      },
    186    ],
    187  });
    188  await trrServer.registerDoHAnswers("something1.com", "A", {
    189    answers: [
    190      {
    191        name: "something1.com",
    192        ttl: 55,
    193        type: "A",
    194        flush: false,
    195        data: "1.2.3.4",
    196      },
    197    ],
    198  });
    199 
    200  {
    201    let { inStatus } = await new TRRDNSListener("test1.com", {
    202      expectedSuccess: false,
    203    });
    204    Assert.ok(
    205      !Components.isSuccessCode(inStatus),
    206      `${inStatus} should be an error code`
    207    );
    208  }
    209 
    210  // Test that HTTPS priority = 0 (AliasForm) behaves like a CNAME
    211  await trrServer.registerDoHAnswers("test.com", "HTTPS", {
    212    answers: [
    213      {
    214        name: "test.com",
    215        ttl: 55,
    216        type: "HTTPS",
    217        flush: false,
    218        data: {
    219          priority: 0,
    220          name: "something.com",
    221          values: [],
    222        },
    223      },
    224    ],
    225  });
    226  await trrServer.registerDoHAnswers("something.com", "HTTPS", {
    227    answers: [
    228      {
    229        name: "something.com",
    230        ttl: 55,
    231        type: "HTTPS",
    232        flush: false,
    233        data: {
    234          priority: 1,
    235          name: "h3pool",
    236          values: [{ key: "alpn", value: ["h2", "h3"] }],
    237        },
    238      },
    239    ],
    240  });
    241 
    242  {
    243    let { inStatus, inRecord } = await new TRRDNSListener("test.com", {
    244      type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    245      expectedSuccess: false,
    246    });
    247    Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should succeed`);
    248    let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
    249    Assert.equal(answer[0].priority, 1);
    250    Assert.equal(answer[0].name, "h3pool");
    251  }
    252 
    253  // Test a chain of HTTPSSVC AliasForm and CNAMEs
    254  await trrServer.registerDoHAnswers("x.com", "HTTPS", {
    255    answers: [
    256      {
    257        name: "x.com",
    258        ttl: 55,
    259        type: "HTTPS",
    260        flush: false,
    261        data: {
    262          priority: 0,
    263          name: "y.com",
    264          values: [],
    265        },
    266      },
    267    ],
    268  });
    269  await trrServer.registerDoHAnswers("y.com", "HTTPS", {
    270    answers: [
    271      {
    272        name: "y.com",
    273        type: "CNAME",
    274        ttl: 55,
    275        class: "IN",
    276        flush: false,
    277        data: "z.com",
    278      },
    279    ],
    280  });
    281  await trrServer.registerDoHAnswers("z.com", "HTTPS", {
    282    answers: [
    283      {
    284        name: "z.com",
    285        ttl: 55,
    286        type: "HTTPS",
    287        flush: false,
    288        data: {
    289          priority: 0,
    290          name: "target.com",
    291          values: [],
    292        },
    293      },
    294    ],
    295  });
    296  await trrServer.registerDoHAnswers("target.com", "HTTPS", {
    297    answers: [
    298      {
    299        name: "target.com",
    300        ttl: 55,
    301        type: "HTTPS",
    302        flush: false,
    303        data: {
    304          priority: 1,
    305          name: "h3pool",
    306          values: [{ key: "alpn", value: ["h2", "h3"] }],
    307        },
    308      },
    309    ],
    310  });
    311 
    312  let { inStatus, inRecord } = await new TRRDNSListener("x.com", {
    313    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    314    expectedSuccess: false,
    315  });
    316  Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should succeed`);
    317  let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
    318  Assert.equal(answer[0].priority, 1);
    319  Assert.equal(answer[0].name, "h3pool");
    320 
    321  // We get a ServiceForm instead of a A answer, CNAME or AliasForm
    322  await trrServer.registerDoHAnswers("no-ip-host.com", "A", {
    323    answers: [
    324      {
    325        name: "no-ip-host.com",
    326        ttl: 55,
    327        type: "HTTPS",
    328        flush: false,
    329        data: {
    330          priority: 1,
    331          name: "h3pool",
    332          values: [
    333            { key: "alpn", value: ["h2", "h3"] },
    334            { key: "no-default-alpn" },
    335            { key: "port", value: 8888 },
    336            { key: "ipv4hint", value: "1.2.3.4" },
    337            { key: "echconfig", value: "123..." },
    338            { key: "ipv6hint", value: "::1" },
    339          ],
    340        },
    341      },
    342    ],
    343  });
    344 
    345  ({ inStatus } = await new TRRDNSListener("no-ip-host.com", {
    346    expectedSuccess: false,
    347  }));
    348  Assert.ok(
    349    !Components.isSuccessCode(inStatus),
    350    `${inStatus} should be an error code`
    351  );
    352 
    353  // Test CNAME/AliasForm loop
    354  await trrServer.registerDoHAnswers("loop.com", "HTTPS", {
    355    answers: [
    356      {
    357        name: "loop.com",
    358        type: "CNAME",
    359        ttl: 55,
    360        class: "IN",
    361        flush: false,
    362        data: "loop2.com",
    363      },
    364    ],
    365  });
    366  await trrServer.registerDoHAnswers("loop2.com", "HTTPS", {
    367    answers: [
    368      {
    369        name: "loop2.com",
    370        ttl: 55,
    371        type: "HTTPS",
    372        flush: false,
    373        data: {
    374          priority: 0,
    375          name: "loop.com",
    376          values: [],
    377        },
    378      },
    379    ],
    380  });
    381 
    382  // Make sure these are the first requests
    383  Assert.equal(await trrServer.requestCount("loop.com", "HTTPS"), 0);
    384  Assert.equal(await trrServer.requestCount("loop2.com", "HTTPS"), 0);
    385 
    386  ({ inStatus } = await new TRRDNSListener("loop.com", {
    387    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    388    expectedSuccess: false,
    389  }));
    390  Assert.ok(
    391    !Components.isSuccessCode(inStatus),
    392    `${inStatus} should be an error code`
    393  );
    394  // Make sure the error was actually triggered by a loop.
    395  Assert.greater(await trrServer.requestCount("loop.com", "HTTPS"), 2);
    396  Assert.greater(await trrServer.requestCount("loop2.com", "HTTPS"), 2);
    397 
    398  // Alias form for .
    399  await trrServer.registerDoHAnswers("empty.com", "A", {
    400    answers: [
    401      {
    402        name: "empty.com",
    403        ttl: 55,
    404        type: "HTTPS",
    405        flush: false,
    406        data: {
    407          priority: 0,
    408          name: "", // This is not allowed
    409          values: [],
    410        },
    411      },
    412    ],
    413  });
    414 
    415  ({ inStatus } = await new TRRDNSListener("empty.com", {
    416    expectedSuccess: false,
    417  }));
    418  Assert.ok(
    419    !Components.isSuccessCode(inStatus),
    420    `${inStatus} should be an error code`
    421  );
    422 
    423  // We should ignore ServiceForm if an AliasForm record is also present
    424  await trrServer.registerDoHAnswers("multi.com", "HTTPS", {
    425    answers: [
    426      {
    427        name: "multi.com",
    428        ttl: 55,
    429        type: "HTTPS",
    430        flush: false,
    431        data: {
    432          priority: 1,
    433          name: "h3pool",
    434          values: [
    435            { key: "alpn", value: ["h2", "h3"] },
    436            { key: "no-default-alpn" },
    437            { key: "port", value: 8888 },
    438            { key: "ipv4hint", value: "1.2.3.4" },
    439            { key: "echconfig", value: "123..." },
    440            { key: "ipv6hint", value: "::1" },
    441          ],
    442        },
    443      },
    444      {
    445        name: "multi.com",
    446        ttl: 55,
    447        type: "HTTPS",
    448        flush: false,
    449        data: {
    450          priority: 0,
    451          name: "example.com",
    452          values: [],
    453        },
    454      },
    455    ],
    456  });
    457 
    458  let { inStatus: inStatus2 } = await new TRRDNSListener("multi.com", {
    459    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    460    expectedSuccess: false,
    461  });
    462  Assert.ok(
    463    !Components.isSuccessCode(inStatus2),
    464    `${inStatus2} should be an error code`
    465  );
    466 
    467  // the svcparam keys are in reverse order
    468  await trrServer.registerDoHAnswers("order.com", "HTTPS", {
    469    answers: [
    470      {
    471        name: "order.com",
    472        ttl: 55,
    473        type: "HTTPS",
    474        flush: false,
    475        data: {
    476          priority: 1,
    477          name: "h3pool",
    478          values: [
    479            { key: "ipv6hint", value: "::1" },
    480            { key: "echconfig", value: "123..." },
    481            { key: "ipv4hint", value: "1.2.3.4" },
    482            { key: "port", value: 8888 },
    483            { key: "no-default-alpn" },
    484            { key: "alpn", value: ["h2", "h3"] },
    485          ],
    486        },
    487      },
    488    ],
    489  });
    490 
    491  ({ inStatus: inStatus2 } = await new TRRDNSListener("order.com", {
    492    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    493    expectedSuccess: false,
    494  }));
    495  Assert.ok(
    496    !Components.isSuccessCode(inStatus2),
    497    `${inStatus2} should be an error code`
    498  );
    499 
    500  // duplicate svcparam keys
    501  await trrServer.registerDoHAnswers("duplicate.com", "HTTPS", {
    502    answers: [
    503      {
    504        name: "duplicate.com",
    505        ttl: 55,
    506        type: "HTTPS",
    507        flush: false,
    508        data: {
    509          priority: 1,
    510          name: "h3pool",
    511          values: [
    512            { key: "alpn", value: ["h2", "h3"] },
    513            { key: "alpn", value: ["h2", "h3", "h4"] },
    514          ],
    515        },
    516      },
    517    ],
    518  });
    519 
    520  ({ inStatus: inStatus2 } = await new TRRDNSListener("duplicate.com", {
    521    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    522    expectedSuccess: false,
    523  }));
    524  Assert.ok(
    525    !Components.isSuccessCode(inStatus2),
    526    `${inStatus2} should be an error code`
    527  );
    528 
    529  // mandatory svcparam
    530  await trrServer.registerDoHAnswers("mandatory.com", "HTTPS", {
    531    answers: [
    532      {
    533        name: "mandatory.com",
    534        ttl: 55,
    535        type: "HTTPS",
    536        flush: false,
    537        data: {
    538          priority: 1,
    539          name: "h3pool",
    540          values: [
    541            { key: "mandatory", value: ["key100"] },
    542            { key: "alpn", value: ["h2", "h3"] },
    543            { key: "key100" },
    544          ],
    545        },
    546      },
    547    ],
    548  });
    549 
    550  ({ inStatus: inStatus2 } = await new TRRDNSListener("mandatory.com", {
    551    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    552    expectedSuccess: false,
    553  }));
    554  Assert.ok(!Components.isSuccessCode(inStatus2), `${inStatus2} should fail`);
    555 
    556  // mandatory svcparam
    557  await trrServer.registerDoHAnswers("mandatory2.com", "HTTPS", {
    558    answers: [
    559      {
    560        name: "mandatory2.com",
    561        ttl: 55,
    562        type: "HTTPS",
    563        flush: false,
    564        data: {
    565          priority: 1,
    566          name: "h3pool",
    567          values: [
    568            {
    569              key: "mandatory",
    570              value: [
    571                "alpn",
    572                "no-default-alpn",
    573                "port",
    574                "ipv4hint",
    575                "echconfig",
    576                "ipv6hint",
    577              ],
    578            },
    579            { key: "alpn", value: ["h2", "h3"] },
    580            { key: "no-default-alpn" },
    581            { key: "port", value: 8888 },
    582            { key: "ipv4hint", value: "1.2.3.4" },
    583            { key: "echconfig", value: "123..." },
    584            { key: "ipv6hint", value: "::1" },
    585          ],
    586        },
    587      },
    588    ],
    589  });
    590 
    591  ({ inStatus: inStatus2 } = await new TRRDNSListener("mandatory2.com", {
    592    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    593  }));
    594 
    595  Assert.ok(Components.isSuccessCode(inStatus2), `${inStatus2} should succeed`);
    596 
    597  // alias-mode with . targetName
    598  await trrServer.registerDoHAnswers("no-alias.com", "HTTPS", {
    599    answers: [
    600      {
    601        name: "no-alias.com",
    602        ttl: 55,
    603        type: "HTTPS",
    604        flush: false,
    605        data: {
    606          priority: 0,
    607          name: ".",
    608          values: [],
    609        },
    610      },
    611    ],
    612  });
    613 
    614  ({ inStatus: inStatus2 } = await new TRRDNSListener("no-alias.com", {
    615    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    616    expectedSuccess: false,
    617  }));
    618 
    619  Assert.ok(!Components.isSuccessCode(inStatus2), `${inStatus2} should fail`);
    620 
    621  // service-mode with . targetName
    622  await trrServer.registerDoHAnswers("service.com", "HTTPS", {
    623    answers: [
    624      {
    625        name: "service.com",
    626        ttl: 55,
    627        type: "HTTPS",
    628        flush: false,
    629        data: {
    630          priority: 1,
    631          name: ".",
    632          values: [{ key: "alpn", value: ["h2", "h3"] }],
    633        },
    634      },
    635    ],
    636  });
    637 
    638  ({ inRecord, inStatus: inStatus2 } = await new TRRDNSListener("service.com", {
    639    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    640  }));
    641  Assert.ok(Components.isSuccessCode(inStatus2), `${inStatus2} should work`);
    642  answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
    643  Assert.equal(answer[0].priority, 1);
    644  Assert.equal(answer[0].name, "service.com");
    645 });
    646 
    647 add_task(async function testNegativeResponse() {
    648  await setTRRURI(`https://foo.example.com:${trrServer.port()}/dns-query`);
    649  let { inStatus } = await new TRRDNSListener("negative_test.com", {
    650    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    651    expectedSuccess: false,
    652  });
    653  Assert.ok(
    654    !Components.isSuccessCode(inStatus),
    655    `${inStatus} should be an error code`
    656  );
    657 
    658  await trrServer.registerDoHAnswers("negative_test.com", "HTTPS", {
    659    answers: [
    660      {
    661        name: "negative_test.com",
    662        ttl: 55,
    663        type: "HTTPS",
    664        flush: false,
    665        data: {
    666          priority: 1,
    667          name: "negative_test.com",
    668          values: [{ key: "alpn", value: ["h2", "h3"] }],
    669        },
    670      },
    671    ],
    672  });
    673 
    674  // Should still be failed because a negative response is from DNS cache.
    675  ({ inStatus } = await new TRRDNSListener("negative_test.com", {
    676    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    677    expectedSuccess: false,
    678  }));
    679  Assert.ok(
    680    !Components.isSuccessCode(inStatus),
    681    `${inStatus} should be an error code`
    682  );
    683 
    684  await clearCache();
    685 
    686  let inRecord;
    687  ({ inRecord, inStatus } = await new TRRDNSListener("negative_test.com", {
    688    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    689  }));
    690  Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should work`);
    691  let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
    692  Assert.equal(answer[0].priority, 1);
    693  Assert.equal(answer[0].name, "negative_test.com");
    694 });
    695 
    696 add_task(async function testPortPrefixedName() {
    697  if (inChildProcess()) {
    698    do_send_remote_message("set-port-prefixed-pref");
    699    await do_await_remote_message("set-port-prefixed-pref-done");
    700  } else {
    701    Services.prefs.setBoolPref(
    702      "network.dns.port_prefixed_qname_https_rr",
    703      true
    704    );
    705  }
    706 
    707  await trrServer.registerDoHAnswers(
    708    "_4433._https.port_prefix.test.com",
    709    "HTTPS",
    710    {
    711      answers: [
    712        {
    713          name: "_4433._https.port_prefix.test.com",
    714          ttl: 55,
    715          type: "HTTPS",
    716          flush: false,
    717          data: {
    718            priority: 1,
    719            name: "port_prefix.test1.com",
    720            values: [{ key: "alpn", value: ["h2", "h3"] }],
    721          },
    722        },
    723      ],
    724    }
    725  );
    726 
    727  let { inRecord, inStatus } = await new TRRDNSListener(
    728    "port_prefix.test.com",
    729    {
    730      type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    731      port: 4433,
    732    }
    733  );
    734  Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should work`);
    735  let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
    736  Assert.equal(answer[0].priority, 1);
    737  Assert.equal(answer[0].name, "port_prefix.test1.com");
    738 });
    739 
    740 async function doTestFlattenRecordsWithECH(host, targetName, alpn, expected) {
    741  await clearCache();
    742 
    743  await trrServer.registerDoHAnswers(host, "HTTPS", {
    744    answers: [
    745      {
    746        name: host,
    747        ttl: 55,
    748        type: "HTTPS",
    749        flush: false,
    750        data: {
    751          priority: 1,
    752          name: targetName,
    753          values: [
    754            ...(alpn ? [{ key: "alpn", value: alpn }] : []),
    755            { key: "echconfig", value: "456..." },
    756          ],
    757        },
    758      },
    759    ],
    760  });
    761 
    762  let { inRecord } = await new TRRDNSListener(host, {
    763    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    764  });
    765 
    766  let notused = {};
    767  let answer = inRecord
    768    .QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
    769    .GetAllRecordsWithEchConfig(false, false, "", notused, notused);
    770 
    771  Assert.equal(answer.length, expected.length);
    772  for (let i = 0; i < answer.length; i++) {
    773    Assert.equal(answer[i].priority, expected[i].priority);
    774    Assert.equal(answer[i].name, expected[i].name);
    775    if (expected[i].selectedAlpn !== null) {
    776      Assert.equal(answer[i].selectedAlpn, expected[i].selectedAlpn);
    777    }
    778  }
    779 }
    780 
    781 add_task(async function testFlattenRecordsWithECH() {
    782  // Test when host name and targetName are the same.
    783  await doTestFlattenRecordsWithECH(
    784    "test.target.com",
    785    "test.target.com",
    786    ["h3"],
    787    [
    788      { priority: 1, name: "test.target.com", selectedAlpn: "h3" },
    789      { priority: 1, name: "test.target.com", selectedAlpn: "" },
    790    ]
    791  );
    792  await doTestFlattenRecordsWithECH(
    793    "test.target.com",
    794    ".",
    795    ["h3"],
    796    [
    797      { priority: 1, name: "test.target.com", selectedAlpn: "h3" },
    798      { priority: 1, name: "test.target.com", selectedAlpn: "" },
    799    ]
    800  );
    801 
    802  // Test when host name and targetName are not the same.
    803  // We add
    804  await doTestFlattenRecordsWithECH(
    805    "test.target.com",
    806    "test.target_1.com",
    807    ["h3"],
    808    [
    809      { priority: 1, name: "test.target_1.com", selectedAlpn: "h3" },
    810      { priority: 1, name: "test.target_1.com", selectedAlpn: "" },
    811    ]
    812  );
    813 
    814  // Test when alpn is empty.
    815  await doTestFlattenRecordsWithECH("test.target.com", ".", null, [
    816    { priority: 1, name: "test.target.com", selectedAlpn: null },
    817  ]);
    818  await doTestFlattenRecordsWithECH(
    819    "test.target.com",
    820    "test.target_1.com",
    821    null,
    822    [{ priority: 1, name: "test.target_1.com", selectedAlpn: null }]
    823  );
    824 });
    825 
    826 async function doTestFlattenRecordsWithoutECH(
    827  host,
    828  targetName,
    829  alpn,
    830  expected
    831 ) {
    832  await clearCache();
    833 
    834  await setTRRURI(`https://foo.example.com:${trrServer.port()}/dns-query`);
    835  await trrServer.registerDoHAnswers(host, "HTTPS", {
    836    answers: [
    837      {
    838        name: host,
    839        ttl: 55,
    840        type: "HTTPS",
    841        flush: false,
    842        data: {
    843          priority: 1,
    844          name: targetName,
    845          values: [...(alpn ? [{ key: "alpn", value: alpn }] : [])],
    846        },
    847      },
    848    ],
    849  });
    850 
    851  let { inRecord } = await new TRRDNSListener(host, {
    852    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    853  });
    854 
    855  let notused = {};
    856  let answer = inRecord
    857    .QueryInterface(Ci.nsIDNSHTTPSSVCRecord)
    858    .GetAllRecords(false, false, "", notused, notused);
    859 
    860  Assert.equal(answer.length, expected.length);
    861  for (let i = 0; i < answer.length; i++) {
    862    Assert.equal(answer[i].priority, expected[i].priority);
    863    Assert.equal(answer[i].name, expected[i].name);
    864    if (expected[i].selectedAlpn !== null) {
    865      Assert.equal(answer[i].selectedAlpn, expected[i].selectedAlpn);
    866    }
    867  }
    868 }
    869 
    870 add_task(async function testFlattenRecordsWithoutECH() {
    871  // Test when host name and targetName are the same.
    872  await doTestFlattenRecordsWithoutECH(
    873    "test.target_noech.com",
    874    "test.target_noech.com",
    875    ["h3"],
    876    [{ priority: 1, name: "test.target_noech.com", selectedAlpn: "h3" }]
    877  );
    878  await doTestFlattenRecordsWithoutECH(
    879    "test.target_noech.com",
    880    ".",
    881    ["h3"],
    882    [{ priority: 1, name: "test.target_noech.com", selectedAlpn: "h3" }]
    883  );
    884 
    885  // Test when host name and targetName are not the same.
    886  await doTestFlattenRecordsWithoutECH(
    887    "test.target_noech.com",
    888    "test.target_noech_1.com",
    889    ["h3"],
    890    [
    891      { priority: 1, name: "test.target_noech_1.com", selectedAlpn: "h3" },
    892      { priority: 1, name: "test.target_noech_1.com", selectedAlpn: "" },
    893    ]
    894  );
    895 
    896  // Test when alpn is empty.
    897  await doTestFlattenRecordsWithoutECH("test.target_noech.com", ".", null, [
    898    { priority: 1, name: "test.target_noech.com", selectedAlpn: null },
    899  ]);
    900  await doTestFlattenRecordsWithoutECH(
    901    "test.target_noech.com",
    902    "test.target_noech_1.com",
    903    null,
    904    [{ priority: 1, name: "test.target_noech_1.com", selectedAlpn: null }]
    905  );
    906 });