blob: aa45b7486923e7acb57b6e31b0bef9b69f369905 [file] [log] [blame]
// 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 "components/variations/variations_layers.h"
#include "base/metrics/field_trial.h"
namespace variations {
namespace {
// Returns a double in the [0, 1] range to be used to select a slot within
// a layer with the given randomization seed value.
double GetEntropyForLayer(
const base::FieldTrial::EntropyProvider* entropy_provider,
uint32_t randomization_seed) {
// GetEntropyForTrial will ignore the trial_name parameter in favor of the
// randomization_seed.
return entropy_provider->GetEntropyForTrial(/*trial_name=*/"",
randomization_seed);
}
// Iterates through the members of the given layer proto definition, and
// returns the ID of the member which contains that slot (if any).
base::Optional<uint32_t> FindActiveMemberBySlot(uint32_t chosen_slot,
const Layer& layer_proto) {
for (const Layer::LayerMember& member : layer_proto.members()) {
if (!member.id())
continue;
for (const Layer::LayerMember::SlotRange& slot : member.slots()) {
if (slot.start() <= chosen_slot && chosen_slot <= slot.end())
return member.id();
}
}
return base::nullopt;
}
} // namespace
VariationsLayers::LayerInfo::LayerInfo(
base::Optional<uint32_t> active_member_id,
Layer::EntropyMode entropy_mode)
: active_member_id(active_member_id), entropy_mode(entropy_mode) {}
VariationsLayers::LayerInfo::~LayerInfo() = default;
VariationsLayers::LayerInfo::LayerInfo(const LayerInfo& other) {
active_member_id = other.active_member_id;
entropy_mode = other.entropy_mode;
}
VariationsLayers::VariationsLayers(
const VariationsSeed& seed,
const base::FieldTrial::EntropyProvider* low_entropy_provider) {
if (!low_entropy_provider) {
// Android WebView does not support low-entropy field trials.
return;
}
// TODO(crbug.com/1154033): Support a way to expire old/unused layers so they
// no longer get processed by the clients.
for (const Layer& layer_proto : seed.layers())
ConstructLayer(*low_entropy_provider, layer_proto);
}
VariationsLayers::VariationsLayers() = default;
VariationsLayers::~VariationsLayers() = default;
void VariationsLayers::ConstructLayer(
const base::FieldTrial::EntropyProvider& low_entropy_provider,
const Layer& layer_proto) {
if (layer_proto.id() == 0 || layer_proto.num_slots() == 0 ||
layer_proto.members_size() == 0) {
return;
}
double entropy_value;
if (layer_proto.entropy_mode() == Layer::LOW) {
entropy_value =
GetEntropyForLayer(&low_entropy_provider, layer_proto.salt());
} else {
const base::FieldTrial::EntropyProvider* default_entropy_provider =
base::FieldTrialList::GetEntropyProviderForOneTimeRandomization();
CHECK(default_entropy_provider);
entropy_value =
GetEntropyForLayer(default_entropy_provider, layer_proto.salt());
}
const double kEpsilon = 1e-8;
// Add a tiny epsilon to get consistent values when converting the double
// to the integer slots; see comment in
// base::FieldTrialList::GetGroupBoundaryValue() for more details.
uint32_t chosen_slot = std::min(
static_cast<uint32_t>(layer_proto.num_slots() * entropy_value + kEpsilon),
layer_proto.num_slots() - 1);
active_member_for_layer_.emplace(
layer_proto.id(),
LayerInfo{FindActiveMemberBySlot(chosen_slot, layer_proto),
layer_proto.entropy_mode()});
}
bool VariationsLayers::IsLayerMemberActive(uint32_t layer_id,
uint32_t member_id) const {
auto layer_iter = active_member_for_layer_.find(layer_id);
if (layer_iter == active_member_for_layer_.end())
return false;
return layer_iter->second.active_member_id &&
(member_id == layer_iter->second.active_member_id.value());
}
bool VariationsLayers::IsLayerUsingDefaultEntropy(uint32_t layer_id) const {
auto layer_iter = active_member_for_layer_.find(layer_id);
if (layer_iter == active_member_for_layer_.end())
return false;
return layer_iter->second.entropy_mode == Layer::DEFAULT;
}
} // namespace variations