summaryrefslogtreecommitdiffstats
path: root/dom/prio
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/prio
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/prio')
-rw-r--r--dom/prio/PrioEncoder.cpp227
-rw-r--r--dom/prio/PrioEncoder.h58
-rw-r--r--dom/prio/moz.build22
-rw-r--r--dom/prio/test/gtest/TestPrioEncoder.cpp307
-rw-r--r--dom/prio/test/gtest/moz.build17
5 files changed, 631 insertions, 0 deletions
diff --git a/dom/prio/PrioEncoder.cpp b/dom/prio/PrioEncoder.cpp
new file mode 100644
index 0000000000..d13ff81ca3
--- /dev/null
+++ b/dom/prio/PrioEncoder.cpp
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/TextUtils.h"
+
+#include "mozilla/dom/ToJSValue.h"
+
+#include "mprio.h"
+
+#include "PrioEncoder.h"
+
+namespace mozilla::dom {
+
+/* static */
+StaticRefPtr<PrioEncoder> PrioEncoder::sSingleton;
+
+/* static */
+PublicKey PrioEncoder::sPublicKeyA = nullptr;
+/* static */
+PublicKey PrioEncoder::sPublicKeyB = nullptr;
+
+// Production keys from bug 1552315 comment#3
+/* static */
+const char* kDefaultKeyA =
+ "E780C1A9C50E3FC5A9B39469FCC92D62D2527BAE6AF76BBDEF128883FA400846";
+/* static */
+const char* kDefaultKeyB =
+ "F992B575840AEC202289FBF99D6C04FB2A37B1DA1CDEB1DF8036E1340D46C561";
+
+PrioEncoder::PrioEncoder() = default;
+PrioEncoder::~PrioEncoder() {
+ if (sPublicKeyA) {
+ PublicKey_clear(sPublicKeyA);
+ sPublicKeyA = nullptr;
+ }
+
+ if (sPublicKeyB) {
+ PublicKey_clear(sPublicKeyB);
+ sPublicKeyB = nullptr;
+ }
+
+ Prio_clear();
+}
+
+/* static */
+void PrioEncoder::Encode(GlobalObject& aGlobal, const nsCString& aBatchID,
+ const PrioParams& aPrioParams,
+ RootedDictionary<PrioEncodedData>& aData,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ nsCString aResult;
+ nsCString bResult;
+ nsresult rv = PrioEncoder::EncodeNative(aBatchID, aPrioParams.mBooleans,
+ aResult, bResult);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsTArray<uint8_t> arrayForServerA;
+ nsTArray<uint8_t> arrayForServerB;
+
+ if (!arrayForServerA.AppendElements(
+ reinterpret_cast<const uint8_t*>(aResult.BeginReading()),
+ aResult.Length(), fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ if (!arrayForServerB.AppendElements(
+ reinterpret_cast<const uint8_t*>(bResult.BeginReading()),
+ bResult.Length(), fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::Rooted<JS::Value> valueA(aGlobal.Context());
+ if (!ToJSValue(aGlobal.Context(),
+ TypedArrayCreator<Uint8Array>(arrayForServerA), &valueA)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ aData.mA.Construct().Init(&valueA.toObject());
+
+ JS::Rooted<JS::Value> valueB(aGlobal.Context());
+ if (!ToJSValue(aGlobal.Context(),
+ TypedArrayCreator<Uint8Array>(arrayForServerB), &valueB)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ aData.mB.Construct().Init(&valueB.toObject());
+}
+
+/* static */
+nsresult PrioEncoder::EncodeNative(const nsCString& aBatchID,
+ const nsTArray<bool>& aData,
+ nsCString& aResult, nsCString& bResult) {
+ SECStatus prio_rv = SECSuccess;
+
+ nsresult rv = PrioEncoder::LazyInitSingleton();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aData.Length() > gNumBooleans) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PrioConfig prioConfig = PrioConfig_new(
+ aData.Length(), sPublicKeyA, sPublicKeyB,
+ reinterpret_cast<const unsigned char*>(aBatchID.BeginReading()),
+ aBatchID.Length());
+
+ if (!prioConfig) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto configGuard = MakeScopeExit([&] { PrioConfig_clear(prioConfig); });
+
+ unsigned char* forServerA = nullptr;
+ unsigned int lenA = 0;
+ unsigned char* forServerB = nullptr;
+ unsigned int lenB = 0;
+
+ prio_rv = PrioClient_encode(prioConfig, aData.Elements(), &forServerA, &lenA,
+ &forServerB, &lenB);
+
+ aResult.Adopt(reinterpret_cast<char*>(forServerA), lenA);
+ bResult.Adopt(reinterpret_cast<char*>(forServerB), lenB);
+
+ if (prio_rv != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+bool PrioEncoder::IsValidHexPublicKey(mozilla::Span<const char> aStr) {
+ if (aStr.Length() != CURVE25519_KEY_LEN_HEX) {
+ return false;
+ }
+
+ for (auto c : aStr) {
+ if (!IsAsciiHexDigit(c)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* static */
+nsresult PrioEncoder::SetKeys(const char* aKeyA, const char* aKeyB) {
+ nsAutoCStringN<CURVE25519_KEY_LEN_HEX + 1> prioKeyA;
+ if (aKeyA == nullptr) {
+ prioKeyA = kDefaultKeyA;
+ } else {
+ prioKeyA = aKeyA;
+ }
+
+ nsAutoCStringN<CURVE25519_KEY_LEN_HEX + 1> prioKeyB;
+ if (aKeyB == nullptr) {
+ prioKeyB = kDefaultKeyB;
+ } else {
+ prioKeyB = aKeyB;
+ }
+
+ // Check that both public keys are of the right length
+ // and contain only hex digits 0-9a-fA-f
+ if (!PrioEncoder::IsValidHexPublicKey(prioKeyA) ||
+ !PrioEncoder::IsValidHexPublicKey(prioKeyB)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ SECStatus prio_rv = SECSuccess;
+ prio_rv = Prio_init();
+
+ if (prio_rv != SECSuccess) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ prio_rv = PublicKey_import_hex(
+ &sPublicKeyA,
+ reinterpret_cast<const unsigned char*>(prioKeyA.BeginReading()),
+ CURVE25519_KEY_LEN_HEX);
+ if (prio_rv != SECSuccess) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ prio_rv = PublicKey_import_hex(
+ &sPublicKeyB,
+ reinterpret_cast<const unsigned char*>(prioKeyB.BeginReading()),
+ CURVE25519_KEY_LEN_HEX);
+ if (prio_rv != SECSuccess) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult PrioEncoder::LazyInitSingleton() {
+ if (!sSingleton) {
+ // Init to the default keys.
+ nsresult rv = PrioEncoder::SetKeys();
+ if (!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ sSingleton = new PrioEncoder();
+ ClearOnShutdown(&sSingleton);
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/prio/PrioEncoder.h b/dom/prio/PrioEncoder.h
new file mode 100644
index 0000000000..3cacbef583
--- /dev/null
+++ b/dom/prio/PrioEncoder.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_PrioEncoder_h
+#define mozilla_dom_PrioEncoder_h
+
+#include "mozilla/dom/PrioEncoderBinding.h"
+#include "mozilla/dom/RootedDictionary.h"
+
+class nsIGlobalObject;
+using SECKEYPublicKey = struct SECKEYPublicKeyStr;
+using PublicKey = SECKEYPublicKey*;
+
+namespace mozilla::dom {
+
+class PrioEncoder {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(PrioEncoder)
+
+ // C++ API
+ static nsresult EncodeNative(const nsCString& aBatchID,
+ const nsTArray<bool>& aData, nsCString& aResult,
+ nsCString& bResult);
+
+ // DOM API
+ static void Encode(GlobalObject& aGlobal, const nsCString& aBatchID,
+ const PrioParams& aPrioParams,
+ RootedDictionary<PrioEncodedData>& aData,
+ ErrorResult& aRv);
+
+ // maximum number of booleans that can be prio-encoded in a single batch.
+ const static uint32_t gNumBooleans = 2046;
+
+ // Set (or, by passing nullptrs, reset) the keys used to encode the payloads.
+ static nsresult SetKeys(const char* aKeyA = nullptr,
+ const char* aKeyB = nullptr);
+
+ private:
+ PrioEncoder();
+ ~PrioEncoder();
+
+ static PublicKey sPublicKeyA;
+
+ static PublicKey sPublicKeyB;
+
+ static StaticRefPtr<PrioEncoder> sSingleton;
+
+ static bool IsValidHexPublicKey(mozilla::Span<const char>);
+
+ static nsresult LazyInitSingleton();
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_PrioEncoder_h
diff --git a/dom/prio/moz.build b/dom/prio/moz.build
new file mode 100644
index 0000000000..49c89f75c9
--- /dev/null
+++ b/dom/prio/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+LOCAL_INCLUDES += ["/third_party/msgpack/include"]
+
+EXPORTS.mozilla.dom += [
+ "PrioEncoder.h",
+]
+
+UNIFIED_SOURCES += [
+ "PrioEncoder.cpp",
+]
+
+TEST_DIRS += ["test/gtest"]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/prio/test/gtest/TestPrioEncoder.cpp b/dom/prio/test/gtest/TestPrioEncoder.cpp
new file mode 100644
index 0000000000..087a1e12e0
--- /dev/null
+++ b/dom/prio/test/gtest/TestPrioEncoder.cpp
@@ -0,0 +1,307 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "gtest/gtest.h"
+
+#include "jsapi.h"
+#include "PrioEncoder.h"
+
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ErrorResult.h"
+
+#include "mprio.h"
+
+TEST(PrioEncoder, BadPublicKeys)
+{
+ mozilla::ErrorResult rv;
+ rv = mozilla::dom::PrioEncoder::SetKeys("badA", "badB");
+
+ ASSERT_TRUE(rv.Failed());
+ rv = mozilla::ErrorResult();
+}
+
+TEST(PrioEncoder, BooleanLimitExceeded)
+{
+ mozilla::dom::AutoJSAPI jsAPI;
+ ASSERT_TRUE(jsAPI.Init(xpc::PrivilegedJunkScope()));
+ JSContext* cx = jsAPI.cx();
+
+ mozilla::dom::GlobalObject global(cx, xpc::PrivilegedJunkScope());
+
+ nsCString batchID = "abc123"_ns;
+
+ mozilla::dom::PrioParams prioParams;
+ FallibleTArray<bool> sequence;
+
+ const int ndata = mozilla::dom::PrioEncoder::gNumBooleans + 1;
+ const int seed = time(nullptr);
+ srand(seed);
+
+ for (int i = 0; i < ndata; i++) {
+ // Arbitrary data)
+ *(sequence.AppendElement(mozilla::fallible)) = rand() % 2;
+ }
+
+ ASSERT_TRUE(prioParams.mBooleans.Assign(sequence));
+
+ mozilla::dom::RootedDictionary<mozilla::dom::PrioEncodedData> prioEncodedData(
+ cx);
+ mozilla::ErrorResult rv;
+
+ mozilla::dom::PrioEncoder::Encode(global, batchID, prioParams,
+ prioEncodedData, rv);
+ ASSERT_TRUE(rv.Failed());
+
+ // Reset error result so test runner does not fail.
+ rv = mozilla::ErrorResult();
+}
+
+TEST(PrioEncoder, VerifyFull)
+{
+ SECStatus prioRv = SECSuccess;
+
+ PublicKey pkA = nullptr;
+ PublicKey pkB = nullptr;
+ PrivateKey skA = nullptr;
+ PrivateKey skB = nullptr;
+
+ PrioConfig cfg = nullptr;
+ PrioServer sA = nullptr;
+ PrioServer sB = nullptr;
+ PrioVerifier vA = nullptr;
+ PrioVerifier vB = nullptr;
+ PrioPacketVerify1 p1A = nullptr;
+ PrioPacketVerify1 p1B = nullptr;
+ PrioPacketVerify2 p2A = nullptr;
+ PrioPacketVerify2 p2B = nullptr;
+ PrioTotalShare tA = nullptr;
+ PrioTotalShare tB = nullptr;
+
+ unsigned char* forServerA = nullptr;
+ unsigned char* forServerB = nullptr;
+
+ const int seed = time(nullptr);
+ srand(seed);
+
+ // Number of different boolean data fields we collect.
+ const int ndata = 3;
+
+ unsigned char batchIDStr[32];
+ memset(batchIDStr, 0, sizeof batchIDStr);
+ snprintf((char*)batchIDStr, sizeof batchIDStr, "%d", rand());
+
+ bool dataItems[ndata];
+ unsigned long long output[ndata];
+
+ // The client's data submission is an arbitrary boolean vector.
+ for (int i = 0; i < ndata; i++) {
+ // Arbitrary data
+ dataItems[i] = rand() % 2;
+ }
+
+ // Initialize NSS random number generator.
+ prioRv = Prio_init();
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ // Generate keypairs for servers
+ prioRv = Keypair_new(&skA, &pkA);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ prioRv = Keypair_new(&skB, &pkB);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ // Export public keys to hex and print to stdout
+ const int keyLength = CURVE25519_KEY_LEN_HEX + 1;
+ unsigned char pkHexA[keyLength];
+ unsigned char pkHexB[keyLength];
+ prioRv = PublicKey_export_hex(pkA, pkHexA, keyLength);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ prioRv = PublicKey_export_hex(pkB, pkHexB, keyLength);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ // Use the default configuration parameters.
+ cfg = PrioConfig_new(ndata, pkA, pkB, batchIDStr, strlen((char*)batchIDStr));
+ ASSERT_TRUE(cfg != nullptr);
+
+ PrioPRGSeed serverSecret;
+ prioRv = PrioPRGSeed_randomize(&serverSecret);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ // Initialize two server objects. The role of the servers need not
+ // be symmetric. In a deployment, we envision that:
+ // * Server A is the main telemetry server that is always online.
+ // Clients send their encrypted data packets to Server A and
+ // Server A stores them.
+ // * Server B only comes online when the two servers want to compute
+ // the final aggregate statistics.
+ sA = PrioServer_new(cfg, PRIO_SERVER_A, skA, serverSecret);
+ ASSERT_TRUE(sA != nullptr);
+ sB = PrioServer_new(cfg, PRIO_SERVER_B, skB, serverSecret);
+ ASSERT_TRUE(sB != nullptr);
+
+ // Initialize empty verifier objects
+ vA = PrioVerifier_new(sA);
+ ASSERT_TRUE(vA != nullptr);
+ vB = PrioVerifier_new(sB);
+ ASSERT_TRUE(vB != nullptr);
+
+ // Initialize shares of final aggregate statistics
+ tA = PrioTotalShare_new();
+ ASSERT_TRUE(tA != nullptr);
+ tB = PrioTotalShare_new();
+ ASSERT_TRUE(tB != nullptr);
+
+ // Initialize shares of verification packets
+ p1A = PrioPacketVerify1_new();
+ ASSERT_TRUE(p1A != nullptr);
+ p1B = PrioPacketVerify1_new();
+ ASSERT_TRUE(p1B != nullptr);
+ p2A = PrioPacketVerify2_new();
+ ASSERT_TRUE(p2A != nullptr);
+ p2B = PrioPacketVerify2_new();
+ ASSERT_TRUE(p2B != nullptr);
+
+ // I. CLIENT DATA SUBMISSION.
+ //
+ // Read in the client data packets
+ unsigned int aLen = 0, bLen = 0;
+
+ mozilla::dom::AutoJSAPI jsAPI;
+ ASSERT_TRUE(jsAPI.Init(xpc::PrivilegedJunkScope()));
+ JSContext* cx = jsAPI.cx();
+
+ mozilla::dom::GlobalObject global(cx, xpc::PrivilegedJunkScope());
+
+ nsCString batchID;
+ batchID = (char*)(batchIDStr);
+
+ mozilla::dom::PrioParams prioParams;
+ FallibleTArray<bool> sequence;
+ *(sequence.AppendElement(mozilla::fallible)) = dataItems[0];
+ *(sequence.AppendElement(mozilla::fallible)) = dataItems[1];
+ *(sequence.AppendElement(mozilla::fallible)) = dataItems[2];
+ ASSERT_TRUE(prioParams.mBooleans.Assign(sequence));
+
+ mozilla::dom::RootedDictionary<mozilla::dom::PrioEncodedData> prioEncodedData(
+ cx);
+ mozilla::ErrorResult rv;
+
+ rv =
+ mozilla::dom::PrioEncoder::SetKeys(reinterpret_cast<const char*>(pkHexA),
+ reinterpret_cast<const char*>(pkHexB));
+ ASSERT_FALSE(rv.Failed());
+
+ mozilla::dom::PrioEncoder::Encode(global, batchID, prioParams,
+ prioEncodedData, rv);
+ ASSERT_FALSE(rv.Failed());
+
+ prioEncodedData.mA.Value().ComputeState();
+ prioEncodedData.mB.Value().ComputeState();
+
+ forServerA = prioEncodedData.mA.Value().Data();
+ forServerB = prioEncodedData.mB.Value().Data();
+ aLen = prioEncodedData.mA.Value().Length();
+ bLen = prioEncodedData.mB.Value().Length();
+
+ // II. VALIDATION PROTOCOL. (at servers)
+ //
+ // The servers now run a short 2-step protocol to check each
+ // client's packet:
+ // 1) Servers A and B broadcast one message (PrioPacketVerify1)
+ // to each other.
+ // 2) Servers A and B broadcast another message (PrioPacketVerify2)
+ // to each other.
+ // 3) Servers A and B can both determine whether the client's data
+ // submission is well-formed (in which case they add it to their
+ // running total of aggregate statistics) or ill-formed
+ // (in which case they ignore it).
+ // These messages must be sent over an authenticated channel, so
+ // that each server is assured that every received message came
+ // from its peer.
+
+ // Set up a Prio verifier object.
+ prioRv = PrioVerifier_set_data(vA, forServerA, aLen);
+ ASSERT_TRUE(prioRv == SECSuccess);
+ prioRv = PrioVerifier_set_data(vB, forServerB, bLen);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ // Both servers produce a packet1. Server A sends p1A to Server B
+ // and vice versa.
+ prioRv = PrioPacketVerify1_set_data(p1A, vA);
+ ASSERT_TRUE(prioRv == SECSuccess);
+ prioRv = PrioPacketVerify1_set_data(p1B, vB);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ // Both servers produce a packet2. Server A sends p2A to Server B
+ // and vice versa.
+ prioRv = PrioPacketVerify2_set_data(p2A, vA, p1A, p1B);
+ ASSERT_TRUE(prioRv == SECSuccess);
+ prioRv = PrioPacketVerify2_set_data(p2B, vB, p1A, p1B);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ // Using p2A and p2B, the servers can determine whether the request
+ // is valid. (In fact, only Server A needs to perform this
+ // check, since Server A can just tell Server B whether the check
+ // succeeded or failed.)
+ prioRv = PrioVerifier_isValid(vA, p2A, p2B);
+ ASSERT_TRUE(prioRv == SECSuccess);
+ prioRv = PrioVerifier_isValid(vB, p2A, p2B);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ // If we get here, the client packet is valid, so add it to the aggregate
+ // statistic counter for both servers.
+ prioRv = PrioServer_aggregate(sA, vA);
+ ASSERT_TRUE(prioRv == SECSuccess);
+ prioRv = PrioServer_aggregate(sB, vB);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ // The servers repeat the steps above for each client submission.
+
+ // III. PRODUCTION OF AGGREGATE STATISTICS.
+ //
+ // After collecting aggregates from MANY clients, the servers can compute
+ // their shares of the aggregate statistics.
+ //
+ // Server B can send tB to Server A.
+ prioRv = PrioTotalShare_set_data(tA, sA);
+ ASSERT_TRUE(prioRv == SECSuccess);
+ prioRv = PrioTotalShare_set_data(tB, sB);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ // Once Server A has tA and tB, it can learn the aggregate statistics
+ // in the clear.
+ prioRv = PrioTotalShare_final(cfg, output, tA, tB);
+ ASSERT_TRUE(prioRv == SECSuccess);
+
+ for (int i = 0; i < ndata; i++) {
+ ASSERT_TRUE(output[i] == dataItems[i]);
+ }
+
+ PrioTotalShare_clear(tA);
+ PrioTotalShare_clear(tB);
+
+ PrioPacketVerify2_clear(p2A);
+ PrioPacketVerify2_clear(p2B);
+
+ PrioPacketVerify1_clear(p1A);
+ PrioPacketVerify1_clear(p1B);
+
+ PrioVerifier_clear(vA);
+ PrioVerifier_clear(vB);
+
+ PrioServer_clear(sA);
+ PrioServer_clear(sB);
+ PrioConfig_clear(cfg);
+
+ PublicKey_clear(pkA);
+ PublicKey_clear(pkB);
+
+ PrivateKey_clear(skA);
+ PrivateKey_clear(skB);
+
+ Prio_clear();
+}
diff --git a/dom/prio/test/gtest/moz.build b/dom/prio/test/gtest/moz.build
new file mode 100644
index 0000000000..2c175582fb
--- /dev/null
+++ b/dom/prio/test/gtest/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "TestPrioEncoder.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/prio",
+ "/third_party/msgpack/include",
+ "/third_party/prio/include",
+]
+
+FINAL_LIBRARY = "xul-gtest"