tor-browser

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

test_ppa.js (21407B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { HttpServer } = ChromeUtils.importESModule(
      7  "resource://testing-common/httpd.sys.mjs"
      8 );
      9 
     10 const { PrivateAttributionService } = ChromeUtils.importESModule(
     11  "resource://gre/modules/PrivateAttributionService.sys.mjs"
     12 );
     13 
     14 const { AppConstants } = ChromeUtils.importESModule(
     15  "resource://gre/modules/AppConstants.sys.mjs"
     16 );
     17 
     18 const BinaryInputStream = Components.Constructor(
     19  "@mozilla.org/binaryinputstream;1",
     20  "nsIBinaryInputStream",
     21  "setInputStream"
     22 );
     23 
     24 const PREF_LEADER = "toolkit.telemetry.dap.leader.url";
     25 const PREF_HELPER = "toolkit.telemetry.dap.helper.url";
     26 const TASK_ID = "DSZGMFh26hBYXNaKvhL_N4AHA3P5lDn19on1vFPBxJM";
     27 const MAX_CONVERSIONS = 2;
     28 const DAY_IN_MILLI = 1000 * 60 * 60 * 24;
     29 const LOOKBACK_DAYS = 1;
     30 const HISTOGRAM_SIZE = 5;
     31 
     32 class MockDateProvider {
     33  constructor() {
     34    this._now = Date.now();
     35  }
     36 
     37  now() {
     38    return this._now;
     39  }
     40 
     41  add(interval_ms) {
     42    this._now += interval_ms;
     43  }
     44 }
     45 
     46 class MockDAPTelemetrySender {
     47  constructor() {
     48    this.receivedMeasurements = [];
     49  }
     50 
     51  async sendDAPMeasurement(task, measurement, { timeout }) {
     52    this.receivedMeasurements.push({
     53      task,
     54      measurement,
     55      timeout,
     56    });
     57  }
     58 }
     59 
     60 class MockServer {
     61  constructor() {
     62    this.receivedReports = [];
     63 
     64    const server = new HttpServer();
     65 
     66    server.registerPrefixHandler(
     67      "/leader_endpoint/tasks/",
     68      this.uploadHandler.bind(this)
     69    );
     70 
     71    this._server = server;
     72  }
     73 
     74  start() {
     75    this._server.start(-1);
     76 
     77    this.orig_leader = Services.prefs.getStringPref(PREF_LEADER);
     78    this.orig_helper = Services.prefs.getStringPref(PREF_HELPER);
     79 
     80    const i = this._server.identity;
     81    const serverAddr =
     82      i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort;
     83    Services.prefs.setStringPref(PREF_LEADER, serverAddr + "/leader_endpoint");
     84    Services.prefs.setStringPref(PREF_HELPER, serverAddr + "/helper_endpoint");
     85  }
     86 
     87  async stop() {
     88    Services.prefs.setStringPref(PREF_LEADER, this.orig_leader);
     89    Services.prefs.setStringPref(PREF_HELPER, this.orig_helper);
     90 
     91    await this._server.stop();
     92  }
     93 
     94  uploadHandler(request, response) {
     95    let body = new BinaryInputStream(request.bodyInputStream);
     96 
     97    this.receivedReports.push({
     98      contentType: request.getHeader("Content-Type"),
     99      size: body.available(),
    100    });
    101 
    102    response.setStatusLine(request.httpVersion, 200);
    103  }
    104 }
    105 
    106 add_setup(async function () {
    107  do_get_profile();
    108  Services.fog.initializeFOG();
    109 });
    110 
    111 add_task(async function testIsEnabled() {
    112  Services.fog.testResetFOG();
    113 
    114  // isEnabled should match the telemetry preference without the test flag
    115  const ppa1 = new PrivateAttributionService();
    116  Assert.equal(
    117    ppa1.isEnabled(),
    118    AppConstants.MOZ_TELEMETRY_REPORTING &&
    119      Services.prefs.getBoolPref("datareporting.healthreport.uploadEnabled") &&
    120      Services.prefs.getBoolPref("dom.private-attribution.submission.enabled")
    121  );
    122 
    123  // Should always be enabled with the test flag
    124  const ppa2 = new PrivateAttributionService({ testForceEnabled: true });
    125  Assert.ok(ppa2.isEnabled());
    126 });
    127 
    128 add_task(async function testSuccessfulConversion() {
    129  Services.fog.testResetFOG();
    130 
    131  const mockSender = new MockDAPTelemetrySender();
    132  const privateAttribution = new PrivateAttributionService({
    133    dapTelemetrySender: mockSender,
    134    testForceEnabled: true,
    135    testDapOptions: { ohttp_relay: null },
    136  });
    137 
    138  const sourceHost = "source.test";
    139  const targetHost = "target.test";
    140  const adIdentifier = "ad_identifier";
    141  const adIndex = 1;
    142 
    143  await privateAttribution.onAttributionEvent(
    144    sourceHost,
    145    "view",
    146    adIndex,
    147    adIdentifier,
    148    targetHost
    149  );
    150 
    151  await privateAttribution.onAttributionEvent(
    152    sourceHost,
    153    "click",
    154    adIndex,
    155    adIdentifier,
    156    targetHost
    157  );
    158 
    159  await privateAttribution.onAttributionConversion(
    160    targetHost,
    161    TASK_ID,
    162    HISTOGRAM_SIZE,
    163    LOOKBACK_DAYS,
    164    "view",
    165    [adIdentifier],
    166    [sourceHost]
    167  );
    168 
    169  const expectedMeasurement = {
    170    task: {
    171      id: TASK_ID,
    172      vdaf: "sumvec",
    173      bits: 8,
    174      length: HISTOGRAM_SIZE,
    175      time_precision: 60,
    176    },
    177    measurement: [0, 1, 0, 0, 0],
    178    timeout: 30000,
    179  };
    180 
    181  const receivedMeasurement = mockSender.receivedMeasurements.pop();
    182  Assert.deepEqual(receivedMeasurement, expectedMeasurement);
    183 
    184  Assert.equal(mockSender.receivedMeasurements.length, 0);
    185 });
    186 
    187 add_task(async function testImpressionsOnMultipleSites() {
    188  Services.fog.testResetFOG();
    189 
    190  const mockSender = new MockDAPTelemetrySender();
    191  const mockDateProvider = new MockDateProvider();
    192  const privateAttribution = new PrivateAttributionService({
    193    dapTelemetrySender: mockSender,
    194    dateProvider: mockDateProvider,
    195    testForceEnabled: true,
    196    testDapOptions: { ohttp_relay: null },
    197  });
    198 
    199  const sourceHost1 = "source-multiple-1.test";
    200  const adIndex1 = 1;
    201 
    202  const sourceHost2 = "source-multiple-2.test";
    203  const adIndex2 = 3;
    204 
    205  const targetHost = "target-multiple.test";
    206  const adIdentifier = "ad_identifier_multiple";
    207 
    208  // View at adIndex1 on sourceHost1 at t=0
    209  await privateAttribution.onAttributionEvent(
    210    sourceHost1,
    211    "view",
    212    adIndex1,
    213    adIdentifier,
    214    targetHost
    215  );
    216 
    217  // Click at adIndex2 on sourceHost2 at t=1
    218  mockDateProvider.add(1);
    219  await privateAttribution.onAttributionEvent(
    220    sourceHost2,
    221    "click",
    222    adIndex2,
    223    adIdentifier,
    224    targetHost
    225  );
    226 
    227  // Click at adIndex1 on sourceHost1 at t=2
    228  mockDateProvider.add(1);
    229  await privateAttribution.onAttributionEvent(
    230    sourceHost1,
    231    "click",
    232    adIndex1,
    233    adIdentifier,
    234    targetHost
    235  );
    236 
    237  // View at adIndex2 on sourceHost2 at t=3
    238  mockDateProvider.add(1);
    239  await privateAttribution.onAttributionEvent(
    240    sourceHost2,
    241    "view",
    242    adIndex2,
    243    adIdentifier,
    244    targetHost
    245  );
    246 
    247  // Conversion for "click" matches most recent click on sourceHost1
    248  await privateAttribution.onAttributionConversion(
    249    targetHost,
    250    TASK_ID,
    251    HISTOGRAM_SIZE,
    252    LOOKBACK_DAYS,
    253    "click",
    254    [adIdentifier],
    255    [sourceHost1, sourceHost2]
    256  );
    257 
    258  let receivedMeasurement = mockSender.receivedMeasurements.pop();
    259  Assert.deepEqual(receivedMeasurement.measurement, [0, 1, 0, 0, 0]);
    260 
    261  // Conversion for "view" matches most recent view on sourceHost2
    262  await privateAttribution.onAttributionConversion(
    263    targetHost,
    264    TASK_ID,
    265    HISTOGRAM_SIZE,
    266    LOOKBACK_DAYS,
    267    "view",
    268    [adIdentifier],
    269    [sourceHost1, sourceHost2]
    270  );
    271 
    272  receivedMeasurement = mockSender.receivedMeasurements.pop();
    273  Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 1, 0]);
    274 
    275  Assert.equal(mockSender.receivedMeasurements.length, 0);
    276 });
    277 
    278 add_task(async function testConversionWithoutImpression() {
    279  Services.fog.testResetFOG();
    280 
    281  const mockSender = new MockDAPTelemetrySender();
    282  const privateAttribution = new PrivateAttributionService({
    283    dapTelemetrySender: mockSender,
    284    testForceEnabled: true,
    285    testDapOptions: { ohttp_relay: null },
    286  });
    287 
    288  const sourceHost = "source-no-impression.test";
    289  const targetHost = "target-no-impression.test";
    290  const adIdentifier = "ad_identifier_no_impression";
    291 
    292  await privateAttribution.onAttributionConversion(
    293    targetHost,
    294    TASK_ID,
    295    HISTOGRAM_SIZE,
    296    LOOKBACK_DAYS,
    297    "view",
    298    [adIdentifier],
    299    [sourceHost]
    300  );
    301 
    302  const receivedMeasurement = mockSender.receivedMeasurements.pop();
    303  Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 0, 0]);
    304 
    305  Assert.equal(mockSender.receivedMeasurements.length, 0);
    306 });
    307 
    308 add_task(async function testSelectionByInteractionType() {
    309  Services.fog.testResetFOG();
    310 
    311  const mockSender = new MockDAPTelemetrySender();
    312  const privateAttribution = new PrivateAttributionService({
    313    dapTelemetrySender: mockSender,
    314    testForceEnabled: true,
    315    testDapOptions: { ohttp_relay: null },
    316  });
    317 
    318  const sourceHost = "source-by-type.test";
    319  const targetHost = "target-by-type.test";
    320  const adIdentifier = "ad_identifier_by_type";
    321 
    322  const viewAdIndex = 2;
    323  const clickAdIndex = 3;
    324 
    325  // View at viewAdIndex
    326  await privateAttribution.onAttributionEvent(
    327    sourceHost,
    328    "view",
    329    viewAdIndex,
    330    adIdentifier,
    331    targetHost
    332  );
    333 
    334  // Click at clickAdIndex clobbers previous view at viewAdIndex
    335  await privateAttribution.onAttributionEvent(
    336    sourceHost,
    337    "click",
    338    clickAdIndex,
    339    adIdentifier,
    340    targetHost
    341  );
    342 
    343  // Conversion filtering for "view" matches the interaction at clickAdIndex
    344  await privateAttribution.onAttributionConversion(
    345    targetHost,
    346    TASK_ID,
    347    HISTOGRAM_SIZE,
    348    LOOKBACK_DAYS,
    349    "view",
    350    [adIdentifier],
    351    [sourceHost]
    352  );
    353 
    354  let receivedMeasurement = mockSender.receivedMeasurements.pop();
    355  Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 1, 0]);
    356 
    357  Assert.equal(mockSender.receivedMeasurements.length, 0);
    358 });
    359 
    360 add_task(async function testSelectionBySourceSite() {
    361  Services.fog.testResetFOG();
    362 
    363  const mockSender = new MockDAPTelemetrySender();
    364  const privateAttribution = new PrivateAttributionService({
    365    dapTelemetrySender: mockSender,
    366    testForceEnabled: true,
    367    testDapOptions: { ohttp_relay: null },
    368  });
    369 
    370  const sourceHost1 = "source-by-site-1.test";
    371  const adIndex1 = 1;
    372 
    373  const sourceHost2 = "source-by-site-2.test";
    374  const adIndex2 = 4;
    375 
    376  const targetHost = "target-by-site.test";
    377  const adIdentifier = "ad_identifier_by_site";
    378 
    379  // Impression on sourceHost1 at adIndex1
    380  await privateAttribution.onAttributionEvent(
    381    sourceHost1,
    382    "view",
    383    adIndex1,
    384    adIdentifier,
    385    targetHost
    386  );
    387 
    388  // Impression for the same ad ID on sourceHost2 at adIndex2
    389  await privateAttribution.onAttributionEvent(
    390    sourceHost2,
    391    "view",
    392    adIndex2,
    393    adIdentifier,
    394    targetHost
    395  );
    396 
    397  // Conversion filtering for sourceHost1 matches impression at adIndex1
    398  await privateAttribution.onAttributionConversion(
    399    targetHost,
    400    TASK_ID,
    401    HISTOGRAM_SIZE,
    402    LOOKBACK_DAYS,
    403    "view",
    404    [adIdentifier],
    405    [sourceHost1]
    406  );
    407 
    408  let receivedMeasurement = mockSender.receivedMeasurements.pop();
    409  Assert.deepEqual(receivedMeasurement.measurement, [0, 1, 0, 0, 0]);
    410 
    411  // Conversion filtering for sourceHost2 matches impression at adIndex2
    412  await privateAttribution.onAttributionConversion(
    413    targetHost,
    414    TASK_ID,
    415    HISTOGRAM_SIZE,
    416    LOOKBACK_DAYS,
    417    "view",
    418    [adIdentifier],
    419    [sourceHost2]
    420  );
    421 
    422  receivedMeasurement = mockSender.receivedMeasurements.pop();
    423  Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 0, 1]);
    424 
    425  Assert.equal(mockSender.receivedMeasurements.length, 0);
    426 });
    427 
    428 add_task(async function testSelectionByAdIdentifier() {
    429  Services.fog.testResetFOG();
    430 
    431  const mockSender = new MockDAPTelemetrySender();
    432  const privateAttribution = new PrivateAttributionService({
    433    dapTelemetrySender: mockSender,
    434    testForceEnabled: true,
    435    testDapOptions: { ohttp_relay: null },
    436  });
    437 
    438  const sourceHost = "source-by-ad-id.test";
    439  const targetHost = "target-by-ad-id.test";
    440 
    441  const adIdentifier1 = "ad_identifier_1";
    442  const adIndex1 = 1;
    443 
    444  const adIdentifier2 = "ad_identifier_2";
    445  const adIndex2 = 2;
    446 
    447  // Impression for adIdentifier1 at adIndex1
    448  await privateAttribution.onAttributionEvent(
    449    sourceHost,
    450    "view",
    451    adIndex1,
    452    adIdentifier1,
    453    targetHost
    454  );
    455 
    456  // Impression for adIdentifier2 at adIndex2
    457  await privateAttribution.onAttributionEvent(
    458    sourceHost,
    459    "view",
    460    adIndex2,
    461    adIdentifier2,
    462    targetHost
    463  );
    464 
    465  // Conversion filtering for adIdentifier1 matches impression at adIndex1
    466  await privateAttribution.onAttributionConversion(
    467    targetHost,
    468    TASK_ID,
    469    HISTOGRAM_SIZE,
    470    LOOKBACK_DAYS,
    471    "view",
    472    [adIdentifier1],
    473    [sourceHost]
    474  );
    475 
    476  let receivedMeasurement = mockSender.receivedMeasurements.pop();
    477  Assert.deepEqual(receivedMeasurement.measurement, [0, 1, 0, 0, 0]);
    478 
    479  // Conversion filtering for adIdentifier2 matches impression at adIndex2
    480  await privateAttribution.onAttributionConversion(
    481    targetHost,
    482    TASK_ID,
    483    HISTOGRAM_SIZE,
    484    LOOKBACK_DAYS,
    485    "view",
    486    [adIdentifier2],
    487    [sourceHost]
    488  );
    489 
    490  receivedMeasurement = mockSender.receivedMeasurements.pop();
    491  Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 1, 0, 0]);
    492 
    493  Assert.equal(mockSender.receivedMeasurements.length, 0);
    494 });
    495 
    496 add_task(async function testExpiredImpressions() {
    497  Services.fog.testResetFOG();
    498 
    499  const mockSender = new MockDAPTelemetrySender();
    500  const mockDateProvider = new MockDateProvider();
    501  const privateAttribution = new PrivateAttributionService({
    502    dapTelemetrySender: mockSender,
    503    dateProvider: mockDateProvider,
    504    testForceEnabled: true,
    505    testDapOptions: { ohttp_relay: null },
    506  });
    507 
    508  const sourceHost = "source-expired.test";
    509  const targetHost = "target-expired.test";
    510  const adIdentifier = "ad_identifier_expired";
    511  const adIndex = 2;
    512 
    513  // Register impression
    514  await privateAttribution.onAttributionEvent(
    515    sourceHost,
    516    "view",
    517    adIndex,
    518    adIdentifier,
    519    targetHost
    520  );
    521 
    522  // Fast-forward time by LOOKBACK_DAYS days + 1 ms
    523  mockDateProvider.add(LOOKBACK_DAYS * DAY_IN_MILLI + 1);
    524 
    525  // Conversion doesn't match expired impression
    526  await privateAttribution.onAttributionConversion(
    527    targetHost,
    528    TASK_ID,
    529    HISTOGRAM_SIZE,
    530    LOOKBACK_DAYS,
    531    "view",
    532    [adIdentifier],
    533    [sourceHost]
    534  );
    535 
    536  const receivedMeasurement = mockSender.receivedMeasurements.pop();
    537  Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 0, 0]);
    538 
    539  Assert.equal(mockSender.receivedMeasurements.length, 0);
    540 });
    541 
    542 add_task(async function testConversionBudget() {
    543  Services.fog.testResetFOG();
    544 
    545  const mockSender = new MockDAPTelemetrySender();
    546  const privateAttribution = new PrivateAttributionService({
    547    dapTelemetrySender: mockSender,
    548    testForceEnabled: true,
    549    testDapOptions: { ohttp_relay: null },
    550  });
    551 
    552  const sourceHost = "source-budget.test";
    553  const targetHost = "target-budget.test";
    554  const adIdentifier = "ad_identifier_budget";
    555  const adIndex = 3;
    556 
    557  await privateAttribution.onAttributionEvent(
    558    sourceHost,
    559    "view",
    560    adIndex,
    561    adIdentifier,
    562    targetHost
    563  );
    564 
    565  // Measurements uploaded for conversions up to MAX_CONVERSIONS
    566  for (let i = 0; i < MAX_CONVERSIONS; i++) {
    567    await privateAttribution.onAttributionConversion(
    568      targetHost,
    569      TASK_ID,
    570      HISTOGRAM_SIZE,
    571      LOOKBACK_DAYS,
    572      "view",
    573      [adIdentifier],
    574      [sourceHost]
    575    );
    576 
    577    const receivedMeasurement = mockSender.receivedMeasurements.pop();
    578    Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 1, 0]);
    579  }
    580 
    581  // Empty report uploaded on subsequent conversions
    582  await privateAttribution.onAttributionConversion(
    583    targetHost,
    584    TASK_ID,
    585    HISTOGRAM_SIZE,
    586    LOOKBACK_DAYS,
    587    "view",
    588    [adIdentifier],
    589    [sourceHost]
    590  );
    591 
    592  const receivedMeasurement = mockSender.receivedMeasurements.pop();
    593  Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 0, 0]);
    594 
    595  Assert.equal(mockSender.receivedMeasurements.length, 0);
    596 });
    597 
    598 add_task(async function testHistogramSize() {
    599  Services.fog.testResetFOG();
    600 
    601  const mockSender = new MockDAPTelemetrySender();
    602  const privateAttribution = new PrivateAttributionService({
    603    dapTelemetrySender: mockSender,
    604    testForceEnabled: true,
    605    testDapOptions: { ohttp_relay: null },
    606  });
    607 
    608  const sourceHost = "source-histogram.test";
    609  const targetHost = "target-histogram.test";
    610  const adIdentifier = "ad_identifier_histogram";
    611 
    612  // Zero-based ad index is equal to histogram size, pushing the measurement out of the histogram's bounds
    613  const adIndex = HISTOGRAM_SIZE;
    614 
    615  await privateAttribution.onAttributionEvent(
    616    sourceHost,
    617    "view",
    618    adIndex,
    619    adIdentifier,
    620    targetHost
    621  );
    622 
    623  await privateAttribution.onAttributionConversion(
    624    targetHost,
    625    TASK_ID,
    626    HISTOGRAM_SIZE,
    627    LOOKBACK_DAYS,
    628    "view",
    629    [adIdentifier],
    630    [sourceHost]
    631  );
    632 
    633  const receivedMeasurement = mockSender.receivedMeasurements.pop();
    634  Assert.deepEqual(receivedMeasurement.measurement, [0, 0, 0, 0, 0]);
    635 
    636  Assert.equal(mockSender.receivedMeasurements.length, 0);
    637 });
    638 
    639 add_task(async function testWithRealDAPSender() {
    640  Services.fog.testResetFOG();
    641 
    642  // Omit mocking DAP telemetry sender in this test to defend against mock
    643  // sender getting out of sync
    644  const mockServer = new MockServer();
    645  mockServer.start();
    646 
    647  const privateAttribution = new PrivateAttributionService({
    648    testForceEnabled: true,
    649    testDapOptions: { ohttp_relay: null },
    650  });
    651 
    652  const sourceHost = "source-telemetry.test";
    653  const targetHost = "target-telemetry.test";
    654  const adIdentifier = "ad_identifier_telemetry";
    655  const adIndex = 4;
    656 
    657  await privateAttribution.onAttributionEvent(
    658    sourceHost,
    659    "view",
    660    adIndex,
    661    adIdentifier,
    662    targetHost
    663  );
    664 
    665  await privateAttribution.onAttributionConversion(
    666    targetHost,
    667    TASK_ID,
    668    HISTOGRAM_SIZE,
    669    LOOKBACK_DAYS,
    670    "view",
    671    [adIdentifier],
    672    [sourceHost]
    673  );
    674 
    675  await mockServer.stop();
    676 
    677  Assert.equal(mockServer.receivedReports.length, 1);
    678 
    679  const expectedReport = {
    680    contentType: "application/dap-report",
    681    size: 1318,
    682  };
    683 
    684  const receivedReport = mockServer.receivedReports.pop();
    685  Assert.deepEqual(receivedReport, expectedReport);
    686 });
    687 
    688 function BinaryHttpResponse(status, headerNames, headerValues, content) {
    689  this.status = status;
    690  this.headerNames = headerNames;
    691  this.headerValues = headerValues;
    692  this.content = content;
    693 }
    694 
    695 BinaryHttpResponse.prototype = {
    696  QueryInterface: ChromeUtils.generateQI(["nsIBinaryHttpResponse"]),
    697 };
    698 
    699 class MockOHTTPBackend {
    700  HOST;
    701  #ohttpServer;
    702 
    703  constructor() {
    704    let ohttp = Cc["@mozilla.org/network/oblivious-http;1"].getService(
    705      Ci.nsIObliviousHttp
    706    );
    707    this.#ohttpServer = ohttp.server();
    708 
    709    let httpServer = new HttpServer(); // This is the OHTTP relay endpoint.
    710    httpServer.registerPathHandler(
    711      new URL(this.getRelayAddress()).pathname,
    712      this.handle_relay_request.bind(this)
    713    );
    714    httpServer.start(-1);
    715    this.HOST = `localhost:${httpServer.identity.primaryPort}`;
    716    registerCleanupFunction(() => {
    717      return new Promise(resolve => {
    718        httpServer.stop(resolve);
    719      });
    720    });
    721  }
    722 
    723  getRelayAddress() {
    724    return `http://${this.HOST}/relay/`;
    725  }
    726 
    727  getOHTTPConfig() {
    728    return this.#ohttpServer.encodedConfig;
    729  }
    730 
    731  async handle_relay_request(request, response) {
    732    let inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
    733      Ci.nsIBinaryInputStream
    734    );
    735    inputStream.setInputStream(request.bodyInputStream);
    736    let requestBody = inputStream.readByteArray(inputStream.available());
    737    let ohttpRequest = this.#ohttpServer.decapsulate(requestBody);
    738    let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
    739      Ci.nsIBinaryHttp
    740    );
    741    let decodedRequest = bhttp.decodeRequest(ohttpRequest.request);
    742    response.processAsync();
    743    let real_destination =
    744      decodedRequest.scheme +
    745      "://" +
    746      decodedRequest.authority +
    747      decodedRequest.path;
    748    let innerBody = new Uint8Array(decodedRequest.content);
    749    let innerRequestHeaders = Object.fromEntries(
    750      decodedRequest.headerNames.map((name, index) => {
    751        return [name, decodedRequest.headerValues[index]];
    752      })
    753    );
    754    let innerResponse = await fetch(real_destination, {
    755      method: decodedRequest.method,
    756      headers: innerRequestHeaders,
    757      body: innerBody,
    758    });
    759    let bytes = new Uint8Array(await innerResponse.arrayBuffer());
    760    let binaryResponse = new BinaryHttpResponse(
    761      innerResponse.status,
    762      ["Content-Type"],
    763      ["application/octet-stream"],
    764      bytes
    765    );
    766    let encResponse = ohttpRequest.encapsulate(
    767      bhttp.encodeResponse(binaryResponse)
    768    );
    769    response.setStatusLine(request.httpVersion, 200, "OK");
    770    response.setHeader("Content-Type", "message/ohttp-res", false);
    771 
    772    let bstream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
    773      Ci.nsIBinaryOutputStream
    774    );
    775    bstream.setOutputStream(response.bodyOutputStream);
    776    bstream.writeByteArray(encResponse);
    777    response.finish();
    778  }
    779 }
    780 
    781 add_task(async function testWithRealDAPSenderAndOHTTP() {
    782  Services.fog.testResetFOG();
    783 
    784  const mockServer = new MockServer();
    785  mockServer.start();
    786 
    787  let ohttpBackend = new MockOHTTPBackend();
    788 
    789  let testDapOptions = {
    790    ohttp_relay: ohttpBackend.getRelayAddress(),
    791    ohttp_hpke: ohttpBackend.getOHTTPConfig(),
    792  };
    793 
    794  const privateAttribution = new PrivateAttributionService({
    795    testForceEnabled: true,
    796    testDapOptions,
    797  });
    798 
    799  const sourceHost = "source-telemetry.test";
    800  const targetHost = "target-telemetry.test";
    801  const adIdentifier = "ad_identifier_telemetry";
    802  const adIndex = 4;
    803 
    804  await privateAttribution.onAttributionEvent(
    805    sourceHost,
    806    "view",
    807    adIndex,
    808    adIdentifier,
    809    targetHost
    810  );
    811 
    812  await privateAttribution.onAttributionConversion(
    813    targetHost,
    814    TASK_ID,
    815    HISTOGRAM_SIZE,
    816    LOOKBACK_DAYS,
    817    "view",
    818    [adIdentifier],
    819    [sourceHost]
    820  );
    821 
    822  await mockServer.stop();
    823 
    824  Assert.equal(mockServer.receivedReports.length, 1);
    825 
    826  const expectedReport = {
    827    contentType: "application/dap-report",
    828    size: 1318,
    829  };
    830 
    831  const receivedReport = mockServer.receivedReports.pop();
    832  Assert.deepEqual(receivedReport, expectedReport);
    833 });