commit 18c91fcc4f00dd5ed460ece22fa9e212c7d5868b
parent c4e71c1f46c5f3f49c4af06f930358637e69c2a7
Author: Nathan Barrett <nbarrett@mozilla.com>
Date: Wed, 5 Nov 2025 15:55:35 +0000
Bug 1992020 - Split out DAPSender into its own module separate from telemetry r=gleonard,mconley
Differential Revision: https://phabricator.services.mozilla.com/D270897
Diffstat:
6 files changed, 448 insertions(+), 271 deletions(-)
diff --git a/toolkit/components/dap/DAPSender.sys.mjs b/toolkit/components/dap/DAPSender.sys.mjs
@@ -0,0 +1,327 @@
+/* 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/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+import { HPKEConfigManager } from "resource://gre/modules/HPKEConfigManager.sys.mjs";
+
+let lazy = {};
+
+ChromeUtils.defineLazyGetter(lazy, "logConsole", function () {
+ return console.createInstance({
+ prefix: "DAPSender",
+ maxLogLevelPref: "toolkit.telemetry.dap.logLevel",
+ });
+});
+ChromeUtils.defineESModuleGetters(lazy, {
+ setTimeout: "resource://gre/modules/Timer.sys.mjs",
+ ObliviousHTTP: "resource://gre/modules/ObliviousHTTP.sys.mjs",
+});
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "gDapEndpoint",
+ "toolkit.telemetry.dap.leader.url"
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "gLeaderHpke",
+ "toolkit.telemetry.dap.leader.hpke"
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "gHelperHpke",
+ "toolkit.telemetry.dap.helper.hpke"
+);
+
+/**
+ * The purpose of this singleton is to handle the core DAP (Distributed Aggregation Protocol) functionality.
+ * The current DAP draft standard is available here:
+ * https://github.com/ietf-wg-ppm/draft-ietf-ppm-dap
+ *
+ * This module provides the low-level DAP report generation and sending capabilities,
+ * independent of telemetry preferences. It can be used by any feature that needs
+ * privacy-preserving aggregation.
+ *
+ * NOTE: Do not, under any circumstances, use this mechanism directly
+ * to send telemetry. Use DAPTelemetrySender for that instead.
+ */
+
+export const DAPSender = new (class {
+ /**
+ * @typedef { 'sum' | 'sumvec' | 'histogram' } VDAF
+ */
+
+ /**
+ * Task configuration must match a configured task on the DAP server.
+ *
+ * @typedef {object} Task
+ * @property {string} id - The task ID in urlsafe_base64 encoding.
+ * @property {VDAF} vdaf - The VDAF used by the task.
+ * @property {number} [bits] - The bit-width of integers in sum/sumvec measurements.
+ * @property {number} [length] - The number of vector/histogram elements.
+ * @property {number} time_precision - The rounding granularity in seconds
+ * that is applied to timestamps attached
+ * to the report.
+ */
+
+ /**
+ * Internal testing function to verify the DAP aggregator keys match current
+ * values advertised by servers.
+ */
+ async checkHpkeKeys() {
+ async function check_key(url, expected) {
+ let response = await fetch(url + "/hpke_config");
+ let body = await response.arrayBuffer();
+ let actual = ChromeUtils.base64URLEncode(body, { pad: false });
+ if (actual != expected) {
+ throw new Error(`HPKE for ${url} does not match`);
+ }
+ }
+ await Promise.allSettled([
+ await check_key(
+ Services.prefs.getStringPref("toolkit.telemetry.dap.leader.url"),
+ Services.prefs.getStringPref("toolkit.telemetry.dap.leader.hpke")
+ ),
+ await check_key(
+ Services.prefs.getStringPref("toolkit.telemetry.dap.helper.url"),
+ Services.prefs.getStringPref("toolkit.telemetry.dap.helper.hpke")
+ ),
+ ]);
+ }
+
+ /**
+ * Sends test reports with randomly generated measurements for testing the DAP infrastructure.
+ *
+ * @param {Array<Task>} tasks
+ * Array of task definitions to send test reports for.
+ * @param {object} options
+ * Options to pass to sendDAPMeasurement.
+ */
+ async sendTestReports(tasks, options = {}) {
+ for (let task of tasks) {
+ let measurement;
+ if (task.vdaf == "sum") {
+ measurement = 3;
+ } else if (task.vdaf == "sumvec") {
+ measurement = new Array(20).fill(0);
+ let r = Math.floor(Math.random() * 10);
+ measurement[r] += 1;
+ measurement[19] += 1;
+ } else if (task.vdaf == "histogram") {
+ measurement = Math.floor(Math.random() * 15);
+ } else {
+ throw new Error(`Unknown VDAF ${task.vdaf}`);
+ }
+
+ await this.sendDAPMeasurement(task, measurement, options);
+ }
+ }
+
+ /**
+ * Periodically sends test reports on a timer.
+ *
+ * @param {Array<Task>} tasks
+ * Array of task definitions to send test reports for.
+ */
+ async timedSendTestReports(tasks) {
+ lazy.logConsole.debug("Sending on timer.");
+ await this.sendTestReports(tasks);
+ lazy.setTimeout(
+ () => this.timedSendTestReports(tasks),
+ this.timeout_value()
+ );
+ }
+
+ /**
+ * @returns {number} A random timeout value between 9-11 minutes in milliseconds.
+ */
+ timeout_value() {
+ const MINUTE = 60 * 1000;
+ return MINUTE * (9 + Math.random() * 2); // 9 - 11 minutes
+ }
+
+ /**
+ * Creates a DAP report for a specific task from a measurement and sends it.
+ *
+ * @param {Task} task
+ * Definition of the task for which the measurement was taken.
+ * @param {number|Array<number>} measurement
+ * The measured value for which a report is generated.
+ * @param {object} options
+ * @param {number} options.timeout
+ * The timeout for request in milliseconds. Defaults to 30s.
+ * @param {string} options.reason
+ * A string to indicate the reason for triggering a submission. This is
+ * currently ignored and not recorded.
+ * @param {string} options.ohttp_relay
+ * @param {Uint8Array} options.ohttp_hpke
+ * If an OHTTP relay is specified, the reports are uploaded over OHTTP.
+ */
+ async sendDAPMeasurement(task, measurement, options = {}) {
+ try {
+ const controller = new AbortController();
+ lazy.setTimeout(() => controller.abort(), options.timeout ?? 30_000);
+
+ let keys = {
+ leader_hpke: HPKEConfigManager.decodeKey(lazy.gLeaderHpke),
+ helper_hpke: HPKEConfigManager.decodeKey(lazy.gHelperHpke),
+ };
+
+ let report = this.generateReport(task, measurement, keys);
+
+ await this.sendReport(
+ lazy.gDapEndpoint,
+ task.id,
+ report,
+ controller.signal,
+ options
+ );
+ } catch (e) {
+ if (e.name === "AbortError") {
+ lazy.logConsole.error("Aborted DAP report generation: ", e);
+ } else {
+ lazy.logConsole.error("DAP report generation failed: " + e);
+ }
+
+ throw e;
+ }
+ }
+
+ /**
+ * @typedef {object} AggregatorKeys
+ * @property {Uint8Array} leader_hpke - The leader's DAP HPKE key.
+ * @property {Uint8Array} helper_hpke - The helper's DAP HPKE key.
+ */
+
+ /**
+ * Generates the encrypted DAP report.
+ *
+ * @param {Task} task
+ * Definition of the task for which the measurement was taken.
+ * @param {number|Array<number>} measurement
+ * The measured value for which a report is generated.
+ * @param {AggregatorKeys} keys
+ * The DAP encryption keys for each aggregator.
+ *
+ * @returns {ArrayBuffer} The generated binary report data.
+ */
+ generateReport(task, measurement, keys) {
+ let task_id = new Uint8Array(
+ ChromeUtils.base64URLDecode(task.id, { padding: "ignore" })
+ );
+
+ let reportOut = {};
+
+ if (task.vdaf === "sum") {
+ Services.DAPTelemetry.GetReportPrioSum(
+ keys.leader_hpke,
+ keys.helper_hpke,
+ measurement,
+ task_id,
+ task.bits,
+ task.time_precision,
+ reportOut
+ );
+ } else if (task.vdaf === "sumvec") {
+ if (measurement.length != task.length) {
+ throw new Error(
+ "Measurement vector length doesn't match task configuration"
+ );
+ }
+ Services.DAPTelemetry.GetReportPrioSumVec(
+ keys.leader_hpke,
+ keys.helper_hpke,
+ measurement,
+ task_id,
+ task.bits,
+ task.time_precision,
+ reportOut
+ );
+ } else if (task.vdaf === "histogram") {
+ Services.DAPTelemetry.GetReportPrioHistogram(
+ keys.leader_hpke,
+ keys.helper_hpke,
+ measurement,
+ task_id,
+ task.length,
+ task.time_precision,
+ reportOut
+ );
+ } else {
+ throw new Error(
+ `Unknown measurement type for task ${task.id}: ${task.vdaf} ${task.bits}`
+ );
+ }
+
+ return new Uint8Array(reportOut.value).buffer;
+ }
+
+ /**
+ * Sends a report to the leader.
+ *
+ * @param {string} leader_endpoint
+ * The URL for the leader.
+ * @param {string} task_id
+ * Base64 encoded task_id as it appears in the upload path.
+ * @param {ArrayBuffer} report
+ * Raw bytes of the TLS encoded report.
+ * @param {AbortSignal} abortSignal
+ * Can be used to cancel network requests. Does not cancel computation.
+ * @param {object} options
+ * @param {string} options.ohttp_relay
+ * @param {Uint8Array} options.ohttp_hpke
+ * If an OHTTP relay is specified, the reports are uploaded over OHTTP. In
+ * this case, the OHTTP and DAP keys must be provided and this code will not
+ * attempt to fetch them.
+ *
+ * @returns {Promise<undefined>} Once the attempt to send the report completes, whether or not it was successful.
+ */
+ async sendReport(leader_endpoint, task_id, report, abortSignal, options) {
+ const upload_path = leader_endpoint + "/tasks/" + task_id + "/reports";
+ try {
+ let requestOptions = {
+ method: "PUT",
+ headers: { "Content-Type": "application/dap-report" },
+ body: report,
+ signal: abortSignal,
+ };
+ let response;
+ if (options.ohttp_relay) {
+ response = await lazy.ObliviousHTTP.ohttpRequest(
+ options.ohttp_relay,
+ options.ohttp_hpke,
+ upload_path,
+ requestOptions
+ );
+ } else {
+ response = await fetch(upload_path, requestOptions);
+ }
+
+ if (response.status != 200) {
+ const content_type = response.headers.get("content-type");
+ if (content_type && content_type === "application/json") {
+ let error = await response.json();
+ throw new Error(
+ `Sending failed. HTTP response: ${response.status} ${response.statusText}. Error: ${error.type} ${error.title}`
+ );
+ } else {
+ let error = await response.text();
+ throw new Error(
+ `Sending failed. HTTP response: ${response.status} ${response.statusText}. Error: ${error}`
+ );
+ }
+ } else {
+ lazy.logConsole.debug("DAP report sent");
+ }
+ } catch (err) {
+ if (err.name === "AbortError") {
+ lazy.logConsole.error("Aborted DAP report sending: ", err);
+ } else {
+ lazy.logConsole.error("Failed to send report: ", err);
+ }
+
+ throw err;
+ }
+ }
+})();
diff --git a/toolkit/components/dap/DAPTelemetrySender.sys.mjs b/toolkit/components/dap/DAPTelemetrySender.sys.mjs
@@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
-import { HPKEConfigManager } from "resource://gre/modules/HPKEConfigManager.sys.mjs";
+import { DAPSender } from "resource://gre/modules/DAPSender.sys.mjs";
let lazy = {};
@@ -13,11 +13,11 @@ ChromeUtils.defineLazyGetter(lazy, "logConsole", function () {
maxLogLevelPref: "toolkit.telemetry.dap.logLevel",
});
});
+
ChromeUtils.defineESModuleGetters(lazy, {
AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
setTimeout: "resource://gre/modules/Timer.sys.mjs",
- ObliviousHTTP: "resource://gre/modules/ObliviousHTTP.sys.mjs",
});
XPCOMUtils.defineLazyPreferenceGetter(
@@ -27,28 +27,10 @@ XPCOMUtils.defineLazyPreferenceGetter(
false
);
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "gDapEndpoint",
- "toolkit.telemetry.dap.leader.url"
-);
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "gLeaderHpke",
- "toolkit.telemetry.dap.leader.hpke"
-);
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "gHelperHpke",
- "toolkit.telemetry.dap.helper.hpke"
-);
-
/**
- * The purpose of this singleton is to handle sending of DAP telemetry data.
- * The current DAP draft standard is available here:
- * https://github.com/ietf-wg-ppm/draft-ietf-ppm-dap
*
- * The specific purpose of this singleton is to make the necessary calls to fetch to do networking.
+ * This class wraps DAPSender and adds telemetry-specific logic, ensuring that
+ * DAP reports are only sent when telemetry is enabled.
*/
export const DAPTelemetrySender = new (class {
@@ -106,13 +88,13 @@ export const DAPTelemetrySender = new (class {
tasks.push(task);
lazy.setTimeout(
- () => this.timedSendTestReports(tasks),
- this.timeout_value()
+ () => DAPSender.timedSendTestReports(tasks),
+ DAPSender.timeout_value()
);
lazy.NimbusFeatures.dapTelemetry.onUpdate(async () => {
if (typeof this.counters !== "undefined") {
- await this.sendTestReports(tasks, { reason: "nimbus-update" });
+ await DAPSender.sendTestReports(tasks, { reason: "nimbus-update" });
}
});
}
@@ -120,7 +102,7 @@ export const DAPTelemetrySender = new (class {
this._asyncShutdownBlocker = async () => {
lazy.logConsole.debug(`Sending on shutdown.`);
// Shorter timeout to prevent crashing due to blocking shutdown
- await this.sendTestReports(tasks, {
+ await DAPSender.sendTestReports(tasks, {
timeout: 2_000,
reason: "shutdown",
});
@@ -133,67 +115,8 @@ export const DAPTelemetrySender = new (class {
}
}
- async sendTestReports(tasks, options = {}) {
- for (let task of tasks) {
- let measurement;
- if (task.vdaf == "sum") {
- measurement = 3;
- } else if (task.vdaf == "sumvec") {
- measurement = new Array(20).fill(0);
- let r = Math.floor(Math.random() * 10);
- measurement[r] += 1;
- measurement[19] += 1;
- } else if (task.vdaf == "histogram") {
- measurement = Math.floor(Math.random() * 15);
- } else {
- throw new Error(`Unknown VDAF ${task.vdaf}`);
- }
-
- await this.sendDAPMeasurement(task, measurement, options);
- }
- }
-
- async timedSendTestReports(tasks) {
- lazy.logConsole.debug("Sending on timer.");
- await this.sendTestReports(tasks);
- lazy.setTimeout(
- () => this.timedSendTestReports(tasks),
- this.timeout_value()
- );
- }
-
- timeout_value() {
- const MINUTE = 60 * 1000;
- return MINUTE * (9 + Math.random() * 2); // 9 - 11 minutes
- }
-
/**
- * Internal testing function to verify the DAP aggregator keys match current
- * values advertised by servers.
- */
- async checkHpkeKeys() {
- async function check_key(url, expected) {
- let response = await fetch(url + "/hpke_config");
- let body = await response.arrayBuffer();
- let actual = ChromeUtils.base64URLEncode(body, { pad: false });
- if (actual != expected) {
- throw new Error(`HPKE for ${url} does not match`);
- }
- }
- await Promise.allSettled([
- await check_key(
- Services.prefs.getStringPref("toolkit.telemetry.dap.leader.url"),
- Services.prefs.getStringPref("toolkit.telemetry.dap.leader.hpke")
- ),
- await check_key(
- Services.prefs.getStringPref("toolkit.telemetry.dap.helper.url"),
- Services.prefs.getStringPref("toolkit.telemetry.dap.helper.hpke")
- ),
- ]);
- }
-
- /**
- * Creates a DAP report for a specific task from a measurement and sends it.
+ * Creates a DAP report for a specific task from a measurement and sends it if telemetry is enabled.
*
* @param {Task} task
* Definition of the task for which the measurement was taken.
@@ -210,176 +133,10 @@ export const DAPTelemetrySender = new (class {
* If an OHTTP relay is specified, the reports are uploaded over OHTTP.
*/
async sendDAPMeasurement(task, measurement, options = {}) {
- try {
- const controller = new AbortController();
- lazy.setTimeout(() => controller.abort(), options.timeout ?? 30_000);
-
- let keys = {
- leader_hpke: HPKEConfigManager.decodeKey(lazy.gLeaderHpke),
- helper_hpke: HPKEConfigManager.decodeKey(lazy.gHelperHpke),
- };
-
- let report = this.generateReport(task, measurement, keys);
-
- await this.sendReport(
- lazy.gDapEndpoint,
- task.id,
- report,
- controller.signal,
- options
- );
- } catch (e) {
- if (e.name === "AbortError") {
- lazy.logConsole.error("Aborted DAP report generation: ", e);
- } else {
- lazy.logConsole.error("DAP report generation failed: " + e);
- }
-
- throw e;
- }
- }
-
- /**
- * @typedef {object} AggregatorKeys
- * @property {Uint8Array} leader_hpke - The leader's DAP HPKE key.
- * @property {Uint8Array} helper_hpke - The helper's DAP HPKE key.
- */
-
- /**
- * Generates the encrypted DAP report.
- *
- * @param {Task} task
- * Definition of the task for which the measurement was taken.
- * @param {number|Array<number>} measurement
- * The measured value for which a report is generated.
- * @param {AggregatorKeys} keys
- * The DAP encryption keys for each aggregator.
- *
- * @returns {ArrayBuffer} The generated binary report data.
- */
- generateReport(task, measurement, keys) {
- let task_id = new Uint8Array(
- ChromeUtils.base64URLDecode(task.id, { padding: "ignore" })
- );
-
- let reportOut = {};
-
- if (task.vdaf === "sum") {
- Services.DAPTelemetry.GetReportPrioSum(
- keys.leader_hpke,
- keys.helper_hpke,
- measurement,
- task_id,
- task.bits,
- task.time_precision,
- reportOut
- );
- } else if (task.vdaf === "sumvec") {
- if (measurement.length != task.length) {
- throw new Error(
- "Measurement vector length doesn't match task configuration"
- );
- }
- Services.DAPTelemetry.GetReportPrioSumVec(
- keys.leader_hpke,
- keys.helper_hpke,
- measurement,
- task_id,
- task.bits,
- task.time_precision,
- reportOut
- );
- } else if (task.vdaf === "histogram") {
- Services.DAPTelemetry.GetReportPrioHistogram(
- keys.leader_hpke,
- keys.helper_hpke,
- measurement,
- task_id,
- task.length,
- task.time_precision,
- reportOut
- );
- } else {
- throw new Error(
- `Unknown measurement type for task ${task.id}: ${task.vdaf} ${task.bits}`
- );
- }
-
- return new Uint8Array(reportOut.value).buffer;
- }
-
- /**
- * Sends a report to the leader.
- *
- * @param {string} leader_endpoint
- * The URL for the leader.
- * @param {string} task_id
- * Base64 encoded task_id as it appears in the upload path.
- * @param {ArrayBuffer} report
- * Raw bytes of the TLS encoded report.
- * @param {AbortSignal} abortSignal
- * Can be used to cancel network requests. Does not cancel computation.
- * @param {object} options
- * @param {string} options.ohttp_relay
- * @param {Uint8Array} options.ohttp_hpke
- * If an OHTTP relay is specified, the reports are uploaded over OHTTP. In
- * this case, the OHTTP and DAP keys must be provided and this code will not
- * attempt to fetch them.
- *
- * @returns {Promise<undefined>} Once the attempt to send the report completes, whether or not it was successful.
- */
- async sendReport(leader_endpoint, task_id, report, abortSignal, options) {
- // If telemetry disabled, don't upload DAP reports either.
if (!lazy.gTelemetryEnabled) {
return;
}
- const upload_path = leader_endpoint + "/tasks/" + task_id + "/reports";
- try {
- let requestOptions = {
- method: "PUT",
- headers: { "Content-Type": "application/dap-report" },
- body: report,
- signal: abortSignal,
- };
- let response;
- if (options.ohttp_relay) {
- response = await lazy.ObliviousHTTP.ohttpRequest(
- options.ohttp_relay,
- options.ohttp_hpke,
- upload_path,
- requestOptions
- );
- } else {
- response = await fetch(upload_path, requestOptions);
- }
-
- if (response.status != 200) {
- const content_type = response.headers.get("content-type");
- if (content_type && content_type === "application/json") {
- // A JSON error from the DAP server.
- let error = await response.json();
- throw new Error(
- `Sending failed. HTTP response: ${response.status} ${response.statusText}. Error: ${error.type} ${error.title}`
- );
- } else {
- // A different error, e.g. from a load-balancer.
- let error = await response.text();
- throw new Error(
- `Sending failed. HTTP response: ${response.status} ${response.statusText}. Error: ${error}`
- );
- }
- } else {
- lazy.logConsole.debug("DAP report sent");
- }
- } catch (err) {
- if (err.name === "AbortError") {
- lazy.logConsole.error("Aborted DAP report sending: ", err);
- } else {
- lazy.logConsole.error("Failed to send report: ", err);
- }
-
- throw err;
- }
+ await DAPSender.sendDAPMeasurement(task, measurement, options);
}
})();
diff --git a/toolkit/components/dap/moz.build b/toolkit/components/dap/moz.build
@@ -39,6 +39,7 @@ UNIFIED_SOURCES += [
EXTRA_JS_MODULES += [
"DAPIncrementality.sys.mjs",
"DAPReportController.sys.mjs",
+ "DAPSender.sys.mjs",
"DAPTelemetrySender.sys.mjs",
"DAPVisitCounter.sys.mjs",
]
diff --git a/toolkit/components/dap/tests/xpcshell/test_dap.js b/toolkit/components/dap/tests/xpcshell/test_dap.js
@@ -11,7 +11,7 @@ const { HttpServer } = ChromeUtils.importESModule(
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
- DAPTelemetrySender: "resource://gre/modules/DAPTelemetrySender.sys.mjs",
+ DAPSender: "resource://gre/modules/DAPSender.sys.mjs",
});
const BinaryInputStream = Components.Constructor(
@@ -22,7 +22,6 @@ const BinaryInputStream = Components.Constructor(
const PREF_LEADER = "toolkit.telemetry.dap.leader.url";
const PREF_HELPER = "toolkit.telemetry.dap.helper.url";
-
const PREF_DATAUPLOAD = "datareporting.healthreport.uploadEnabled";
let server;
@@ -109,7 +108,7 @@ add_setup(async function () {
add_task(async function testVerificationTask() {
server_requests = [];
- await lazy.DAPTelemetrySender.sendTestReports(tasks, { timeout: 5000 });
+ await lazy.DAPSender.sendTestReports(tasks, { timeout: 5000 });
Assert.deepEqual(
server_requests,
task_report_sizes,
@@ -125,7 +124,7 @@ add_task(async function testNetworkError() {
let thrownErr;
try {
- await lazy.DAPTelemetrySender.sendTestReports(tasks, { timeout: 5000 });
+ await lazy.DAPSender.sendTestReports(tasks, { timeout: 5000 });
} catch (e) {
thrownErr = e;
}
@@ -136,21 +135,14 @@ add_task(async function testNetworkError() {
Services.prefs.setStringPref(PREF_LEADER, test_leader);
});
-add_task(async function testTelemetryToggle() {
- // Normal
- server_requests = [];
- await lazy.DAPTelemetrySender.sendTestReports(tasks, { timeout: 5000 });
- Assert.deepEqual(server_requests, task_report_sizes);
-
- // Telemetry off
+add_task(async function testTelemetryDisabled() {
server_requests = [];
Services.prefs.setBoolPref(PREF_DATAUPLOAD, false);
- await lazy.DAPTelemetrySender.sendTestReports(tasks, { timeout: 5000 });
- Assert.deepEqual(server_requests, []);
-
- // Normal
- server_requests = [];
+ await lazy.DAPSender.sendTestReports(tasks, { timeout: 5000 });
+ Assert.deepEqual(
+ server_requests,
+ task_report_sizes,
+ "Report upload successful even with telemetry disabled."
+ );
Services.prefs.clearUserPref(PREF_DATAUPLOAD);
- await lazy.DAPTelemetrySender.sendTestReports(tasks, { timeout: 5000 });
- Assert.deepEqual(server_requests, task_report_sizes);
});
diff --git a/toolkit/components/dap/tests/xpcshell/test_dap_telemetry.js b/toolkit/components/dap/tests/xpcshell/test_dap_telemetry.js
@@ -0,0 +1,97 @@
+/* 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";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ DAPTelemetrySender: "resource://gre/modules/DAPTelemetrySender.sys.mjs",
+});
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+const PREF_LEADER = "toolkit.telemetry.dap.leader.url";
+const PREF_HELPER = "toolkit.telemetry.dap.helper.url";
+const PREF_DATAUPLOAD = "datareporting.healthreport.uploadEnabled";
+
+let server;
+let server_addr;
+
+let server_requests = [];
+
+const task = {
+ id: "QjMD4n8l_MHBoLrbCfLTFi8hC264fC59SKHPviPF0q8",
+ vdaf: "sum",
+ bits: 8,
+ time_precision: 300,
+};
+
+const task_report_size = 886;
+
+function uploadHandler(request, response) {
+ Assert.equal(
+ request.getHeader("Content-Type"),
+ "application/dap-report",
+ "Wrong Content-Type header."
+ );
+
+ let body = new BinaryInputStream(request.bodyInputStream);
+ server_requests.push(body.available());
+
+ response.setStatusLine(request.httpVersion, 200);
+}
+
+add_setup(async function () {
+ do_get_profile();
+
+ server = new HttpServer();
+ server.registerPrefixHandler("/leader_endpoint/tasks/", uploadHandler);
+ server.start(-1);
+ const i = server.identity;
+ server_addr = i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort;
+
+ Services.prefs.setStringPref(PREF_LEADER, server_addr + "/leader_endpoint");
+ Services.prefs.setStringPref(PREF_HELPER, server_addr + "/helper_endpoint");
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(PREF_LEADER);
+ Services.prefs.clearUserPref(PREF_HELPER);
+
+ return new Promise(resolve => {
+ server.stop(resolve);
+ });
+ });
+});
+
+add_task(async function testTelemetryToggle() {
+ server_requests = [];
+ await lazy.DAPTelemetrySender.sendDAPMeasurement(task, 3, { timeout: 5000 });
+ Assert.deepEqual(
+ server_requests,
+ [task_report_size],
+ "Telemetry enabled works."
+ );
+
+ server_requests = [];
+ Services.prefs.setBoolPref(PREF_DATAUPLOAD, false);
+ await lazy.DAPTelemetrySender.sendDAPMeasurement(task, 3, { timeout: 5000 });
+ Assert.deepEqual(server_requests, [], "Telemetry disabled blocks sending.");
+
+ server_requests = [];
+ Services.prefs.clearUserPref(PREF_DATAUPLOAD);
+ await lazy.DAPTelemetrySender.sendDAPMeasurement(task, 3, { timeout: 5000 });
+ Assert.deepEqual(
+ server_requests,
+ [task_report_size],
+ "Telemetry re-enabled works."
+ );
+});
diff --git a/toolkit/components/dap/tests/xpcshell/xpcshell.toml b/toolkit/components/dap/tests/xpcshell/xpcshell.toml
@@ -10,5 +10,8 @@ skip-if = ["os == 'android'"] # DAP is not supported on Android
["test_dap_report_controller.js"]
skip-if = ["os == 'android'"] # DAP is not supported on Android
+["test_dap_telemetry.js"]
+skip-if = ["os == 'android'"] # DAP is not supported on Android
+
["test_dap_visit_counter.js"]
skip-if = ["os == 'android'"] # DAP is not supported on Android