Commit 41c7ad6d authored by Asanka Herath's avatar Asanka Herath Committed by Commit Bot

[privacy_budget] IdentifiableTokens are not digests.

The identifiability metrics reporting APIs should be able to consume
digestible parameters directly without passing it through a non-typesafe
conversion. In addition, the generated "digests" aren't really digests
in the sense that the output depends quite directly on partitioning of
the input, something that shouldn't affect digests.

In order to account for these, this CL introduces a concept of a
IdentifiabilityToken which can automatically consume supported data
types. It is a 'sample encoding' rather than a digest and cannot be used
as an input for the purpose of constructing stateful digests, because by
design it is not a digest.

From the perspective of a call site, these changes mean that the
invocation for recording a metric will go from this:

     IdentifiabilityMetricBuilder(
         base::UkmSourceId::FromInt64(document->UkmSourceID()))
       .Set(IdentifiableSurface::FromTypeAndInput(
                IdentifiableSurface::Type::kWebFeature,
                static_cast<uint64_t>(feature)),
            IdentifiabilityDigestHelper(value))
       .Record(document->UkmRecorder());

To:

     IdentifiabilityMetricBuilder(document->UkmSourceID())
       .SetWebFeature(feature, value)
       .Record(document->UkmRecorder());

Bug: 973801
Change-Id: I326bc5a4ead5efbb3a04bf8314692a151fa082f3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2207291
Commit-Queue: Asanka Herath <asanka@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarCaleb Raitto <caraitto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#775313}
parent f871e278
...@@ -207,9 +207,11 @@ jumbo_source_set("common_unittests_sources") { ...@@ -207,9 +207,11 @@ jumbo_source_set("common_unittests_sources") {
"notifications/notification_mojom_traits_unittest.cc", "notifications/notification_mojom_traits_unittest.cc",
"origin_trials/trial_token_unittest.cc", "origin_trials/trial_token_unittest.cc",
"origin_trials/trial_token_validator_unittest.cc", "origin_trials/trial_token_validator_unittest.cc",
"privacy_budget/identifiability_internal_templates_unittest.cc",
"privacy_budget/identifiability_metric_builder_unittest.cc", "privacy_budget/identifiability_metric_builder_unittest.cc",
"privacy_budget/identifiability_metrics_unittest.cc", "privacy_budget/identifiability_metrics_unittest.cc",
"privacy_budget/identifiable_surface_unittest.cc", "privacy_budget/identifiable_surface_unittest.cc",
"privacy_budget/identifiable_token_unittest.cc",
"test/run_all_unittests.cc", "test/run_all_unittests.cc",
"user_agent/user_agent_metadata_unittest.cc", "user_agent/user_agent_metadata_unittest.cc",
"web_package/web_package_request_matcher_unittest.cc", "web_package/web_package_request_matcher_unittest.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/public/common/privacy_budget/identifiability_internal_templates.h"
#include <cstdint>
#include <limits>
#include <type_traits>
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace internal {
namespace {
struct PodType {
int x;
float y;
char c;
char g[10];
};
#if !defined(ARCH_CPU_LITTLE_ENDIAN) && !defined(ARCH_CPU_BIG_ENDIAN)
#error "What kind of CPU is this?"
#endif
} // namespace
// remove_cvref_t
static_assert(std::is_same<int, remove_cvref_t<const int>>::value, "");
static_assert(std::is_same<int, remove_cvref_t<const volatile int>>::value, "");
static_assert(std::is_same<int, remove_cvref_t<int&>>::value, "");
static_assert(std::is_same<int, remove_cvref_t<const int&>>::value, "");
static_assert(std::is_same<int, remove_cvref_t<const volatile int&>>::value,
"");
static_assert(std::is_same<int, remove_cvref_t<int&&>>::value, "");
static_assert(std::is_same<PodType, remove_cvref_t<const PodType&>>::value, "");
static_assert(std::is_same<int*, remove_cvref_t<int*>>::value, "");
// has_unique_object_representations
static_assert(has_unique_object_representations<int>::value, "");
static_assert(has_unique_object_representations<float>::value, "");
static_assert(has_unique_object_representations<double>::value, "");
// long double: check_blink_style doesn't let us use the word 'long' here.
static_assert(has_unique_object_representations<decltype(1.0l)>::value, "");
// Pointers aren't considered to have a unique representation.
static_assert(!has_unique_object_representations<int*>::value, "");
// Nor are POD types though they could be if they are dense and don't have any
// internal padding.
static_assert(!has_unique_object_representations<PodType>::value, "");
TEST(IdentifiabilityInternalTemplatesTest, DigestOfObjectRepresentation) {
const int kV = 5;
const int& kRV = kV;
const volatile int& kRVV = kV;
// Note that both little and big endian systems produce the same result from
// DigestOfObjectRepresentation();
// Positive unsigned integers.
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(UINT8_C(5)));
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(UINT16_C(5)));
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(UINT32_C(5)));
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(UINT64_C(5)));
// Positive signed integers.
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(INT8_C(5)));
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(INT16_C(5)));
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(INT32_C(5)));
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(INT64_C(5)));
// char
EXPECT_EQ(INT64_C(65), DigestOfObjectRepresentation('A'));
// Negative integers.
EXPECT_EQ(INT64_C(-5), DigestOfObjectRepresentation(INT8_C(-5)));
EXPECT_EQ(INT64_C(-5), DigestOfObjectRepresentation(INT16_C(-5)));
EXPECT_EQ(INT64_C(-5), DigestOfObjectRepresentation(INT32_C(-5)));
EXPECT_EQ(INT64_C(-5), DigestOfObjectRepresentation(INT64_C(-5)));
// Large unsigned integer. These wrap around for 2s complement arithmetic.
EXPECT_EQ(INT64_C(-1),
DigestOfObjectRepresentation(std::numeric_limits<uint64_t>::max()));
// CV qualified types should be unwrapped.
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(kV));
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(kRV));
EXPECT_EQ(INT64_C(5), DigestOfObjectRepresentation(kRVV));
}
TEST(IdentifiabilityInternalTemplatesTest,
DigestOfObjectRepresentation_Floats) {
// IEEE 754 32-bit single precision float.
if (sizeof(float) == 4)
EXPECT_EQ(INT64_C(1069547520), DigestOfObjectRepresentation(1.5f));
// IEEE 754 64-bit double precision float.
if (sizeof(double) == 8)
EXPECT_EQ(INT64_C(4609434218613702656), DigestOfObjectRepresentation(1.5));
}
} // namespace internal
} // namespace blink
...@@ -18,8 +18,8 @@ IdentifiabilityMetricBuilder::~IdentifiabilityMetricBuilder() = default; ...@@ -18,8 +18,8 @@ IdentifiabilityMetricBuilder::~IdentifiabilityMetricBuilder() = default;
IdentifiabilityMetricBuilder& IdentifiabilityMetricBuilder::Set( IdentifiabilityMetricBuilder& IdentifiabilityMetricBuilder::Set(
IdentifiableSurface surface, IdentifiableSurface surface,
int64_t value) { IdentifiableToken value) {
SetMetricInternal(surface.ToUkmMetricHash(), value); SetMetricInternal(surface.ToUkmMetricHash(), value.value_);
return *this; return *this;
} }
......
...@@ -4,9 +4,15 @@ ...@@ -4,9 +4,15 @@
#include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h" #include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h"
#include <cinttypes>
#include <limits>
#include "base/metrics/ukm_source_id.h" #include "base/metrics/ukm_source_id.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source_id.h" #include "services/metrics/public/cpp/ukm_source_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/privacy_budget/identifiable_surface.h" #include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom.h" #include "third_party/blink/public/mojom/web_feature/web_feature.mojom.h"
...@@ -74,4 +80,138 @@ TEST(IdentifiabilityMetricBuilderTest, SetWebfeature) { ...@@ -74,4 +80,138 @@ TEST(IdentifiabilityMetricBuilderTest, SetWebfeature) {
EXPECT_EQ(entry->metrics, expected_entry->metrics); EXPECT_EQ(entry->metrics, expected_entry->metrics);
} }
namespace {
MATCHER_P(FirstMetricIs,
entry,
base::StringPrintf("entry is %s0x%" PRIx64,
negation ? "not " : "",
entry)) {
return arg->metrics.begin()->second == entry;
} // namespace
enum class Never { kGonna, kGive, kYou, kUp };
constexpr IdentifiableSurface kTestSurface =
IdentifiableSurface::FromTypeAndInput(
IdentifiableSurface::Type::kReservedInternal,
0);
// Sample values
const char kAbcd[] = "abcd";
const int64_t kExpectedHashOfAbcd = -0x08a5c475eb66bd73;
// 5.1f
const int64_t kExpectedHashOfOnePointFive = 0x3ff8000000000000;
} // namespace
TEST(IdentifiabilityMetricBuilderTest, SetChar) {
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, 'A')
.TakeEntry(),
FirstMetricIs(INT64_C(65)));
}
TEST(IdentifiabilityMetricBuilderTest, SetCharArray) {
IdentifiableToken sample(kAbcd);
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, sample)
.TakeEntry(),
FirstMetricIs(kExpectedHashOfAbcd));
}
TEST(IdentifiabilityMetricBuilderTest, SetStringPiece) {
// StringPiece() needs an explicit constructor invocation.
EXPECT_THAT(
IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, IdentifiableToken(base::StringPiece(kAbcd)))
.TakeEntry(),
FirstMetricIs(kExpectedHashOfAbcd));
}
TEST(IdentifiabilityMetricBuilderTest, SetStdString) {
IdentifiableToken sample((std::string(kAbcd)));
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, sample)
.TakeEntry(),
FirstMetricIs(kExpectedHashOfAbcd));
}
TEST(IdentifiabilityMetricBuilderTest, SetInt) {
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, -5)
.TakeEntry(),
FirstMetricIs(INT64_C(-5)));
}
TEST(IdentifiabilityMetricBuilderTest, SetIntRef) {
int x = -5;
int& xref = x;
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, xref)
.TakeEntry(),
FirstMetricIs(INT64_C(-5)));
}
TEST(IdentifiabilityMetricBuilderTest, SetIntConstRef) {
int x = -5;
const int& xref = x;
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, xref)
.TakeEntry(),
FirstMetricIs(INT64_C(-5)));
}
TEST(IdentifiabilityMetricBuilderTest, SetUnsigned) {
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, 5u)
.TakeEntry(),
FirstMetricIs(INT64_C(5)));
}
TEST(IdentifiabilityMetricBuilderTest, SetUint64) {
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, UINT64_C(5))
.TakeEntry(),
FirstMetricIs(INT64_C(5)));
}
TEST(IdentifiabilityMetricBuilderTest, SetBigUnsignedInt) {
// Slightly different in that this value cannot be converted into the sample
// type without loss. Hence it is digested as raw bytes.
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, std::numeric_limits<uint64_t>::max())
.TakeEntry(),
FirstMetricIs(INT64_C(-1)));
}
TEST(IdentifiabilityMetricBuilderTest, SetFloat) {
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, 1.5f)
.TakeEntry(),
FirstMetricIs(kExpectedHashOfOnePointFive));
}
TEST(IdentifiabilityMetricBuilderTest, SetDouble) {
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, 1.5l)
.TakeEntry(),
FirstMetricIs(kExpectedHashOfOnePointFive));
}
TEST(IdentifiabilityMetricBuilderTest, SetEnum) {
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, Never::kUp)
.TakeEntry(),
FirstMetricIs(INT64_C(3)));
}
TEST(IdentifiabilityMetricBuilderTest, SetParameterPack) {
EXPECT_THAT(IdentifiabilityMetricBuilder(base::UkmSourceId{})
.Set(kTestSurface, IdentifiableToken(1, 2, 3.0, 4, 'a'))
.TakeEntry(),
FirstMetricIs(INT64_C(0x672cf4c107b5b22)));
}
} // namespace blink } // namespace blink
...@@ -4,10 +4,11 @@ ...@@ -4,10 +4,11 @@
#include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h" #include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h"
#include "stdint.h" #include <cstdint>
#include <limits>
#include <vector> #include <vector>
#include "base/strings/string_piece_forward.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace blink { namespace blink {
...@@ -61,7 +62,7 @@ TEST(IdentifiabilityMetricsTest, PassUint16) { ...@@ -61,7 +62,7 @@ TEST(IdentifiabilityMetricsTest, PassUint16) {
EXPECT_EQ(UINT64_C(5), IdentifiabilityDigestHelper(static_cast<uint16_t>(5))); EXPECT_EQ(UINT64_C(5), IdentifiabilityDigestHelper(static_cast<uint16_t>(5)));
} }
TEST(IdentifiabilityMetricsTest, PassSizet) { TEST(IdentifiabilityMetricsTest, PassSizeT) {
EXPECT_EQ(UINT64_C(1), IdentifiabilityDigestHelper(sizeof(char))); EXPECT_EQ(UINT64_C(1), IdentifiabilityDigestHelper(sizeof(char)));
} }
...@@ -80,24 +81,30 @@ TEST(IdentifiabilityMetricsTest, PassEnum) { ...@@ -80,24 +81,30 @@ TEST(IdentifiabilityMetricsTest, PassEnum) {
EXPECT_EQ(UINT64_C(2730421), IdentifiabilityDigestHelper(kSimpleValue)); EXPECT_EQ(UINT64_C(2730421), IdentifiabilityDigestHelper(kSimpleValue));
} }
namespace {
// Use an arbitrary, large number to make accidental matches unlikely. // Use an arbitrary, large number to make accidental matches unlikely.
enum Simple64Enum : uint64_t { kSimple64Value = 4983422 }; enum Simple64Enum : uint64_t { kSimple64Value = 4983422 };
// Use an arbitrary, large number to make accidental matches unlikely.
enum class SimpleEnumClass { kSimpleValue = 3498249 };
// Use an arbitrary, large number to make accidental matches unlikely.
enum class SimpleEnumClass64 : uint64_t { kSimple64Value = 4398372 };
constexpr uint64_t kExpectedCombinationResult = UINT64_C(0xa5e30a57547cd49b);
} // namespace
TEST(IdentifiabilityMetricsTest, PassEnum64) { TEST(IdentifiabilityMetricsTest, PassEnum64) {
EXPECT_EQ(UINT64_C(4983422), IdentifiabilityDigestHelper(kSimple64Value)); EXPECT_EQ(UINT64_C(4983422), IdentifiabilityDigestHelper(kSimple64Value));
} }
// Use an arbitrary, large number to make accidental matches unlikely.
enum class SimpleEnumClass { kSimpleValue = 3498249 };
TEST(IdentifiabilityMetricsTest, PassEnumClass) { TEST(IdentifiabilityMetricsTest, PassEnumClass) {
EXPECT_EQ(UINT64_C(3498249), EXPECT_EQ(UINT64_C(3498249),
IdentifiabilityDigestHelper(SimpleEnumClass::kSimpleValue)); IdentifiabilityDigestHelper(SimpleEnumClass::kSimpleValue));
} }
// Use an arbitrary, large number to make accidental matches unlikely.
enum class SimpleEnumClass64 : uint64_t { kSimple64Value = 4398372 };
TEST(IdentifiabilityMetricsTest, PassEnumClass64) { TEST(IdentifiabilityMetricsTest, PassEnumClass64) {
EXPECT_EQ(UINT64_C(4398372), EXPECT_EQ(UINT64_C(4398372),
IdentifiabilityDigestHelper(SimpleEnumClass64::kSimple64Value)); IdentifiabilityDigestHelper(SimpleEnumClass64::kSimple64Value));
...@@ -115,8 +122,6 @@ TEST(IdentifiabilityMetricsTest, PassSpanDouble) { ...@@ -115,8 +122,6 @@ TEST(IdentifiabilityMetricsTest, PassSpanDouble) {
IdentifiabilityDigestHelper(base::make_span(data))); IdentifiabilityDigestHelper(base::make_span(data)));
} }
constexpr uint64_t kExpectedCombinationResult = UINT64_C(0xa5e30a57547cd49b);
TEST(IdentifiabilityMetricsTest, Combination) { TEST(IdentifiabilityMetricsTest, Combination) {
const int data[] = {1, 2, 3}; const int data[] = {1, 2, 3};
EXPECT_EQ(kExpectedCombinationResult, EXPECT_EQ(kExpectedCombinationResult,
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/public/common/privacy_budget/identifiable_token.h"
#include "base/strings/string_piece.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
// The set of candidate conversion templates depend on whether the conversion is
// explicit or implicit. This class is used to exercise implicit conversion of
// IdIdentifiableApiSample.
struct ImplicitConverter {
// NOLINTNEXTLINE(google-explicit-constructor)
ImplicitConverter(IdentifiableToken sample) : sample(sample) {}
IdentifiableToken sample;
};
} // namespace
TEST(IdentifiableTokenTest, SampleSignedChar) {
auto source_value = static_cast<signed char>(-65);
auto expected_value = INT64_C(-65);
EXPECT_EQ(IdentifiableToken(expected_value), IdentifiableToken(source_value));
EXPECT_EQ(IdentifiableToken(expected_value),
ImplicitConverter(source_value).sample);
}
TEST(IdentifiableTokenTest, SampleChar) {
auto source_value = 'A';
auto expected_value = INT64_C(65);
EXPECT_EQ(IdentifiableToken(expected_value), IdentifiableToken(source_value));
EXPECT_EQ(IdentifiableToken(expected_value),
ImplicitConverter(source_value).sample);
}
TEST(IdentifiableTokenTest, SampleInt) {
auto source_value = 123;
auto expected_value = INT64_C(123);
EXPECT_EQ(IdentifiableToken(expected_value), IdentifiableToken(source_value));
EXPECT_EQ(IdentifiableToken(expected_value),
ImplicitConverter(source_value).sample);
}
TEST(IdentifiableTokenTest, SampleNegativeInt) {
auto source_value = -123;
auto expected_value = INT64_C(-123);
EXPECT_EQ(IdentifiableToken(expected_value), IdentifiableToken(source_value));
EXPECT_EQ(IdentifiableToken(expected_value),
ImplicitConverter(source_value).sample);
}
TEST(IdentifiableTokenTest, SampleUnsigned) {
auto source_value = UINT64_C(123);
auto expected_value = INT64_C(123);
EXPECT_EQ(IdentifiableToken(expected_value), IdentifiableToken(source_value));
EXPECT_EQ(IdentifiableToken(expected_value),
ImplicitConverter(source_value).sample);
}
TEST(IdentifiableTokenTest, SampleBigUnsignedThatFits) {
auto source_value =
static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1;
auto expected_value = std::numeric_limits<int64_t>::min();
EXPECT_EQ(IdentifiableToken(expected_value), IdentifiableToken(source_value));
EXPECT_EQ(IdentifiableToken(expected_value),
ImplicitConverter(source_value).sample);
}
TEST(IdentifiableTokenTest, SampleFloat) {
auto source_value = 5.1f;
auto expected_value = INT64_C(0x4014666660000000);
EXPECT_EQ(IdentifiableToken(expected_value), IdentifiableToken(source_value));
EXPECT_EQ(IdentifiableToken(expected_value),
ImplicitConverter(source_value).sample);
}
TEST(IdentifiableTokenTest, SampleConstCharArray) {
EXPECT_EQ(IdentifiableToken(INT64_C(0xf75a3b8a1499428d)),
IdentifiableToken("abcd"));
// No implicit converter for const char[].
}
TEST(IdentifiableTokenTest, SampleStdString) {
EXPECT_EQ(IdentifiableToken(INT64_C(0xf75a3b8a1499428d)),
IdentifiableToken(std::string("abcd")));
// No implicit converter for std::string.
}
TEST(IdentifiableTokenTest, SampleStringPiece) {
auto source_value = base::StringPiece("abcd");
auto expected_value = INT64_C(0xf75a3b8a1499428d);
EXPECT_EQ(IdentifiableToken(expected_value), IdentifiableToken(source_value));
// No implicit converter for StringPiece.
}
TEST(IdentifiableTokenTest, SampleCharSpan) {
auto source_value = base::make_span("abcd", 4);
auto expected_value = INT64_C(0xf75a3b8a1499428d);
EXPECT_EQ(IdentifiableToken(expected_value), IdentifiableToken(source_value));
EXPECT_EQ(IdentifiableToken(expected_value),
ImplicitConverter(source_value).sample);
}
TEST(IdentifiableTokenTest, SampleStringSpan) {
std::string strings[] = {"baby", "shark", "du duu du duu du du"};
auto source_value = base::make_span(strings);
auto expected_value = INT64_C(0xd37aad882e58faa5);
EXPECT_EQ(IdentifiableToken(expected_value), IdentifiableToken(source_value));
EXPECT_EQ(IdentifiableToken(expected_value),
ImplicitConverter(source_value).sample);
}
TEST(IdentifiableTokenTest, SampleTuple) {
EXPECT_EQ(IdentifiableToken(INT64_C(0x5848123245be627a)),
IdentifiableToken(1, 2, 3, 4, 5));
// No implicit converter for tuples.
}
TEST(IdentifiableTokenTest, SampleHeterogenousTuple) {
EXPECT_EQ(IdentifiableToken(INT64_C(0x672cf4c107b5b22)),
IdentifiableToken(1, 2, 3.0, 4, 'a'));
// No implicit converter for tuples.
}
} // namespace blink
...@@ -142,10 +142,12 @@ source_set("headers") { ...@@ -142,10 +142,12 @@ source_set("headers") {
"peerconnection/webrtc_ip_handling_policy.h", "peerconnection/webrtc_ip_handling_policy.h",
"permissions/permission_utils.h", "permissions/permission_utils.h",
"prerender/prerender_rel_type.h", "prerender/prerender_rel_type.h",
"privacy_budget/identifiability_internal_templates.h",
"privacy_budget/identifiability_metric_builder.h", "privacy_budget/identifiability_metric_builder.h",
"privacy_budget/identifiability_metrics.h", "privacy_budget/identifiability_metrics.h",
"privacy_budget/identifiability_study_participation.h", "privacy_budget/identifiability_study_participation.h",
"privacy_budget/identifiable_surface.h", "privacy_budget/identifiable_surface.h",
"privacy_budget/identifiable_token.h",
"scheduler/web_scheduler_tracked_feature.h", "scheduler/web_scheduler_tracked_feature.h",
"screen_orientation/web_screen_orientation_lock_type.h", "screen_orientation/web_screen_orientation_lock_type.h",
"screen_orientation/web_screen_orientation_type.h", "screen_orientation/web_screen_orientation_type.h",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_BUDGET_IDENTIFIABILITY_INTERNAL_TEMPLATES_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_BUDGET_IDENTIFIABILITY_INTERNAL_TEMPLATES_H_
#include <type_traits>
#include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h"
namespace blink {
namespace internal {
// Handroll a remove_cv_t until we get to C++20.
template <typename T>
using remove_cvref_t = typename std::remove_cv_t<std::remove_reference_t<T>>;
// Kinda conservative implementation of
// std::has_unique_object_representations<>. Perhaps not as conservative as we'd
// like.
//
// At a minimum, this predicate should require that the data type not contain
// internal padding since uninitialized padding bytes defeat the uniqueness of
// the representation. Trailing padding is allowed.
//
// Not checking <version> because we don't want to use the feature
// automatically. We should wait until C++-17 library functionality is
// explicitly allowed in Chromium.
template <typename T>
using has_unique_object_representations = std::is_arithmetic<T>;
// Calculate a digest of an object with a unique representation.
//
// In a perfect world, we should also require that this representation be
// portable or made to be portable. Such a transformation could be done, for
// example, by adopting a consistent byte ordering on all platforms.
//
// This function should only be invoked on a bare (sans qualifiers and
// references) type for the sake of simplicity.
//
// Should not be used as a primitive for manually constructing a unique
// representation. For such cases, use the byte-wise digest functions instead.
//
// Should not be used outside of the narrow use cases in this file.
//
// This implementation does not work for x86 extended precision floating point
// numbers. These are 80-bits wide, but in practice includes 6 bytes of padding
// in order to extend the size to 16 bytes. The extra bytes are uninitialized
// and will not contribute a stable digest.
template <
typename T,
typename std::enable_if_t<std::is_same<T, remove_cvref_t<T>>::value &&
std::is_trivially_copyable<T>::value &&
has_unique_object_representations<T>::value &&
sizeof(T) <= sizeof(int64_t)>* = nullptr>
constexpr int64_t DigestOfObjectRepresentation(T in) {
// If |in| is small enough, the digest is itself. There's no point hashing
// this value since the identity has all the properties we are looking for
// in a digest.
if (std::is_integral<T>::value && std::is_signed<T>::value)
return in;
if (std::is_integral<T>::value && sizeof(T) < sizeof(int64_t))
return in;
int64_t result = 0;
memcpy(&result, &in, sizeof(in));
return result;
}
} // namespace internal
} // namespace blink
#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_BUDGET_IDENTIFIABILITY_INTERNAL_TEMPLATES_H_
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "services/metrics/public/cpp/ukm_source_id.h" #include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/blink/public/common/common_export.h" #include "third_party/blink/public/common/common_export.h"
#include "third_party/blink/public/common/privacy_budget/identifiable_surface.h" #include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
#include "third_party/blink/public/common/privacy_budget/identifiable_token.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-forward.h" #include "third_party/blink/public/mojom/web_feature/web_feature.mojom-forward.h"
namespace blink { namespace blink {
...@@ -112,16 +113,21 @@ class BLINK_COMMON_EXPORT IdentifiabilityMetricBuilder ...@@ -112,16 +113,21 @@ class BLINK_COMMON_EXPORT IdentifiabilityMetricBuilder
// Set the metric using a previously constructed |IdentifiableSurface|. // Set the metric using a previously constructed |IdentifiableSurface|.
IdentifiabilityMetricBuilder& Set(IdentifiableSurface surface, IdentifiabilityMetricBuilder& Set(IdentifiableSurface surface,
int64_t result); IdentifiableToken sample);
// Set the metric using and IdentifiableSurface::Type and an |input|.
IdentifiabilityMetricBuilder& Set(IdentifiableSurface::Type type,
uint64_t input,
IdentifiableToken sample);
// Convenience method for recording the result of invoking a simple API // Convenience method for recording the result of invoking a simple API
// surface with a UseCounter. // surface with a |UseCounter|.
IdentifiabilityMetricBuilder& SetWebfeature(mojom::WebFeature feature, IdentifiabilityMetricBuilder& SetWebfeature(mojom::WebFeature feature,
int64_t result) { IdentifiableToken sample) {
return Set(IdentifiableSurface::FromTypeAndInput( return Set(IdentifiableSurface::FromTypeAndInput(
IdentifiableSurface::Type::kWebFeature, IdentifiableSurface::Type::kWebFeature,
static_cast<uint64_t>(feature)), static_cast<uint64_t>(feature)),
result); sample);
} }
// Shadow the underlying Record() implementation until the upstream pipeline // Shadow the underlying Record() implementation until the upstream pipeline
......
...@@ -5,8 +5,7 @@ ...@@ -5,8 +5,7 @@
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_BUDGET_IDENTIFIABILITY_METRICS_H_ #ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_BUDGET_IDENTIFIABILITY_METRICS_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_BUDGET_IDENTIFIABILITY_METRICS_H_ #define THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_BUDGET_IDENTIFIABILITY_METRICS_H_
#include <stdint.h> #include <cstdint>
#include <cstring> #include <cstring>
#include <type_traits> #include <type_traits>
...@@ -15,9 +14,9 @@ ...@@ -15,9 +14,9 @@
namespace blink { namespace blink {
// DigestForMetrics, which is NOT a cryptographic hash function, takes a span of // IdentifiabilityDigestOfBytes, which is NOT a cryptographic hash function,
// bytes as input and calculates a digest that can be used with identifiability // takes a span of bytes as input and calculates a digest that can be used with
// metric reporting functions. // identifiability metric reporting functions.
// //
// The returned digest ...: // The returned digest ...:
// //
...@@ -46,6 +45,9 @@ IdentifiabilityDigestOfBytes(base::span<const uint8_t> in); ...@@ -46,6 +45,9 @@ IdentifiabilityDigestOfBytes(base::span<const uint8_t> in);
// IdentifiabilityDigestHelper(); such declarations should be made in a header // IdentifiabilityDigestHelper(); such declarations should be made in a header
// included before this header so that they can be used by the span and // included before this header so that they can be used by the span and
// parameter pack overloads of IdentifiabilityDigestHelper. // parameter pack overloads of IdentifiabilityDigestHelper.
//
// TODO(asanka): Remove once callers have been migrated to
// IdentifiabilityToken().
// Integer version. // Integer version.
template <typename T, template <typename T,
......
...@@ -32,7 +32,8 @@ class IdentifiableSurface { ...@@ -32,7 +32,8 @@ class IdentifiableSurface {
// Bitmask for extracting Type value from a surface hash. // Bitmask for extracting Type value from a surface hash.
static constexpr uint64_t kTypeMask = (1 << kTypeBits) - 1; static constexpr uint64_t kTypeMask = (1 << kTypeBits) - 1;
// Indicator for an uninitialized IdentifiableSurface. // Indicator for an uninitialized IdentifiableSurface. Maps to
// {Type::kReservedInternal, 0} which is not possible for a valid surface.
static constexpr uint64_t kInvalidHash = 0; static constexpr uint64_t kInvalidHash = 0;
// Type of identifiable surface. // Type of identifiable surface.
...@@ -74,6 +75,11 @@ class IdentifiableSurface { ...@@ -74,6 +75,11 @@ class IdentifiableSurface {
return IdentifiableSurface(KeyFromSurfaceTypeAndInput(type, input)); return IdentifiableSurface(KeyFromSurfaceTypeAndInput(type, input));
} }
// Construct an invalid identifiable surface.
static constexpr IdentifiableSurface Invalid() {
return IdentifiableSurface(kInvalidHash);
}
// Returns the UKM metric hash corresponding to this IdentifiableSurface. // Returns the UKM metric hash corresponding to this IdentifiableSurface.
constexpr uint64_t ToUkmMetricHash() const { return metric_hash_; } constexpr uint64_t ToUkmMetricHash() const { return metric_hash_; }
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_BUDGET_IDENTIFIABLE_TOKEN_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_BUDGET_IDENTIFIABLE_TOKEN_H_
#include <cstdint>
#include <type_traits>
#include "base/containers/span.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_piece.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_internal_templates.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h"
namespace blink {
// Constructs a token that can be used for reporting a metric or constructing an
// identifiable surface.
//
// The token construction is a single step conversion that takes one of several
// constrained inputs and emits a value. The method by which the value is
// constructed intentionally cannot be chained. If such behavior is required,
// then this class should be modified to accommodate the new use case rather
// than implementing custom chaining schemes at call sites.
//
// Once constructed, a token can only be consumed by
// IdentifiabiltyMetricsBuilder and IdentifiableSurface. For all others, it is a
// copyable, opaque token.
//
// Reliance on implicit conversion imposes limitations on how
// IdentifiableToken class is to be used. For example the following works:
//
// std::string foo = ....;
// IdentifiableToken sample(foo);
//
// .. due to the following implicit conversion:
//
// 1. std::string -> const std::string&
// : lvalue -> lvalue reference + cv-qualification
// 2. const std::string& -> base::StringPiece
// : user-defined conversion via constructor
// base::StringPiece(const std::string&)
//
// However, when used within a builder expression, the user-defined conversion
// doesn't occur due to there not being a single user defined conversion from
// std::string -> IdentifiableToken. I.e. the following does not work:
//
// std::string foo = ....;
// IdentifiabilityMetricBuilder(...).Set(surface, foo);
// ^^^
// The compiler can't deduce a two step user-defined conversion for |foo|.
//
// All overrides of the constructor should ensure that there exists a unique
// representation of the data type being sampled, and that the sample value is
// constructed based on this unique representation.
//
// TODO(asanka): Also require that the representation be portable.
//
// Extending IdentifiableToken to support more data types:
// -----------------------------------------------------------
//
// This class is intentionally placed in blink/public/common due to the
// requirement that these primitives be made available to both the renderer and
// the browser. However, it would be desirable to have renderer or browser
// specific functions for mapping common types in either domain into a sample.
//
// The recommended methods to do so are (one-of):
//
// 1. Use an existing byte span representation.
//
// E.g.: Assuming |v| is a WTF::Vector
// IdentifiabilityMetricBuilder(...).Set(...,
// base::as_bytes(base::make_span(v.Data(), v.Size())));
//
// Note again that serializing to a stream of bytes may not be sufficient
// if the underlying types don't have a unique representation.
//
// 2. Construct a byte-wise unique representation and invoke
// IdentifiableToken(ByteSpan) either explicitly or implicitly via
// user-defined conversions.
//
// Note: Avoid doing template magic. There's already too much here. Templates
// make it difficult to verify that the correct stable representation is
// the one getting ingested into the reporting workflow.
//
// Instead, explicitly invoke some wrapper that emits a ByteSpan (a.k.a.
// base::span<const uint8_t>.
class IdentifiableToken {
public:
// Generic buffer of bytes.
using ByteSpan = base::span<const uint8_t>;
// Representation type of the sample.
using TokenType = int64_t;
// A byte buffer specified as a span.
//
// This is essentially the base case. If it were the base case, then
// IdentifiableToken would be closer to a proper digest.
//
// NOLINTNEXTLINE(google-explicit-constructor)
IdentifiableToken(ByteSpan span)
: value_(IdentifiabilityDigestOfBytes(span)) {}
// Integers, big and small. Includes char.
template <typename T,
typename U = internal::remove_cvref_t<T>,
typename std::enable_if_t<std::is_integral<U>::value>* = nullptr>
constexpr IdentifiableToken(T in) // NOLINT(google-explicit-constructor)
: value_(base::IsValueInRangeForNumericType<TokenType, U>(in)
? in
: internal::DigestOfObjectRepresentation<U>(in)) {}
// Enums. Punt to the underlying type.
template <typename T,
typename U = typename std::underlying_type<T>::type,
typename std::enable_if_t<std::is_enum<T>::value>* = nullptr>
constexpr IdentifiableToken(T in) // NOLINT(google-explicit-constructor)
: IdentifiableToken(static_cast<U>(in)) {}
// All floating point values get converted to double before encoding.
//
// Why? We'd like to minimize accidental divergence of values due to the data
// type that the callsite happened to be using at the time.
//
// On some platforms sizeof(long double) gives us 16 (i.e. 128 bits), while
// only 10 of those bytes are initialized. If the whole sizeof(long double)
// buffer were to be ingested, then the uninitialized memory will cause the
// resulting digest to be useless.
template <
typename T,
typename U = internal::remove_cvref_t<T>,
typename std::enable_if_t<std::is_floating_point<U>::value>* = nullptr>
constexpr IdentifiableToken(T in) // NOLINT(google-explicit-constructor)
: value_(internal::DigestOfObjectRepresentation<double>(
static_cast<double>(in))) {}
// StringPiece. Decays to base::span<> but requires an explicit constructor
// invocation.
//
// Care must be taken when using string types with IdentifiableToken() since
// there's not privacy expectation in the resulting token value. If the string
// used as an input is privacy sensitive, it should not be passed in as-is.
explicit IdentifiableToken(base::StringPiece s)
: IdentifiableToken(base::as_bytes(base::make_span(s))) {
// The cart is before the horse, but it's a static_assert<>.
static_assert(
std::is_same<ByteSpan,
decltype(base::as_bytes(base::make_span(s)))>::value,
"base::as_bytes() doesn't return ByteSpan");
}
// Span of known trivial types except for BytesSpan, which is the base case.
template <typename T,
size_t Extent,
typename U = internal::remove_cvref_t<T>,
typename std::enable_if_t<
std::is_arithmetic<U>::value &&
!std::is_same<ByteSpan::element_type, T>::value>* = nullptr>
// NOLINTNEXTLINE(google-explicit-constructor)
IdentifiableToken(base::span<T, Extent> span)
: IdentifiableToken(base::as_bytes(span)) {}
// A span of non-trivial things where each thing can be digested individually.
template <typename T,
size_t Extent,
typename std::enable_if_t<
!std::is_arithmetic<T>::value &&
!std::is_same<ByteSpan::element_type, T>::value>* = nullptr>
// NOLINTNEXTLINE(google-explicit-constructor)
IdentifiableToken(base::span<T, Extent> span) {
TokenType cur_digest = 0;
for (const auto& element : span) {
TokenType digests[2];
digests[0] = cur_digest;
digests[1] = IdentifiableToken(element).value_;
cur_digest = IdentifiabilityDigestOfBytes(
base::as_bytes(base::make_span(digests)));
}
value_ = cur_digest;
}
// Parameter pack where each parameter can be digested individually. Requires
// at least two parameters.
template <typename T1, typename T2, typename... Trest>
constexpr IdentifiableToken(T1 first, T2 second, Trest... rest) {
TokenType samples[] = {IdentifiableToken(first).value_,
IdentifiableToken(second).value_,
(IdentifiableToken(rest).value_)...};
value_ = IdentifiableToken(base::make_span(samples)).value_;
}
constexpr bool operator<(const IdentifiableToken& that) const {
return value_ < that.value_;
}
constexpr bool operator<=(const IdentifiableToken& that) const {
return value_ <= that.value_;
}
constexpr bool operator>(const IdentifiableToken& that) const {
return value_ > that.value_;
}
constexpr bool operator>=(const IdentifiableToken& that) const {
return value_ >= that.value_;
}
constexpr bool operator==(const IdentifiableToken& that) const {
return value_ == that.value_;
}
constexpr bool operator!=(const IdentifiableToken& that) const {
return value_ != that.value_;
}
private:
friend class IdentifiabilityMetricBuilder;
friend class IdentifiableSurface;
// TODO(asanka): This should be const. Switch over once the incremental digest
// functions land.
TokenType value_ = 0;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_PRIVACY_BUDGET_IDENTIFIABLE_TOKEN_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment