blob: 781a6f587e36f29d74fb160475001f6233487bb1 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 "src/logger/encoder.h"
#include <memory>
#include <string>
#include "src/algorithms/rappor/rappor_config_helper.h"
#include "src/algorithms/rappor/rappor_encoder.h"
#include "src/lib/crypto_util/hash.h"
#include "src/logger/project_context.h"
#include "src/logging.h"
#include "src/pb/observation2.pb.h"
#include "src/registry/buckets_config.h"
#include "src/registry/packed_event_codes.h"
#include "src/tracing.h"
namespace cobalt::logger {
using ::cobalt::config::IntegerBucketConfig;
using ::cobalt::crypto::byte;
using ::cobalt::crypto::hash::DIGEST_SIZE;
using ::cobalt::rappor::BasicRapporEncoder;
using ::cobalt::rappor::RapporConfigHelper;
using ::cobalt::system_data::ClientSecret;
using ::cobalt::system_data::SystemDataInterface;
using ::google::protobuf::RepeatedField;
namespace {
// Populates |*hash_out| with the SHA256 of |component|, unless |component|
// is empty in which case *hash_out is set to the empty string also. An
// empty string indicates that the component_name feature is not being used.
// We expect this to be a common case and in this case there is no point
// in using 32 bytes to represent the empty string. Returns true on success
// and false on failure (unexpected).
bool HashComponentNameIfNotEmpty(const std::string& component, std::string* hash_out) {
CHECK(hash_out);
if (component.empty()) {
hash_out->resize(0);
return true;
}
hash_out->resize(DIGEST_SIZE);
return cobalt::crypto::hash::Hash(reinterpret_cast<const byte*>(component.data()),
component.size(), reinterpret_cast<byte*>(&hash_out->front()));
}
// Translates a rappor::Status |status| into a logger::Status and prints a debug
// message if |status| is not kOK.
Status TranslateBasicRapporEncoderStatus(MetricRef metric, const ReportDefinition* report,
const rappor::Status& status) {
switch (status) {
case rappor::kOK:
return kOK;
case rappor::kInvalidConfig:
LOG(ERROR) << "BasicRapporEncoder returned kInvalidConfig for: Report "
<< report->report_name() << " for metric " << metric.metric_name()
<< " in project " << metric.ProjectDebugString() << ".";
return kInvalidConfig;
case rappor::kInvalidInput:
LOG(ERROR) << "BasicRapporEncoder returned kInvalidInput for: Report "
<< report->report_name() << " for metric " << metric.metric_name()
<< " in project " << metric.ProjectDebugString() << ".";
return kInvalidArguments;
}
}
} // namespace
Encoder::Encoder(ClientSecret client_secret, const system_data::SystemDataInterface* system_data)
: client_secret_(std::move(client_secret)), system_data_(system_data) {}
Encoder::Result Encoder::EncodeBasicRapporObservation(MetricRef metric,
const ReportDefinition* report,
uint32_t day_index, uint32_t value_index,
uint32_t num_categories) const {
TRACE_DURATION("cobalt_core", "Encoder::EncodeBasicRapporObservation");
auto result = MakeObservation(metric, report, day_index);
auto* observation = result.observation.get();
auto* basic_rappor_observation = observation->mutable_basic_rappor();
rappor::BasicRapporConfig basic_rappor_config;
basic_rappor_config.prob_rr = RapporConfigHelper::kProbRR;
basic_rappor_config.categories.set_indexed(num_categories);
float prob_bit_flip = RapporConfigHelper::ProbBitFlip(*report, metric.FullyQualifiedName());
basic_rappor_config.prob_0_becomes_1 = prob_bit_flip;
basic_rappor_config.prob_1_stays_1 = 1.0f - prob_bit_flip;
// TODO(rudominer) Stop copying the client_secret_ on each Encode*()
// operation.
BasicRapporEncoder basic_rappor_encoder(basic_rappor_config, client_secret_);
ValuePart index_value;
index_value.set_index_value(value_index);
result.status = TranslateBasicRapporEncoderStatus(
metric, report, basic_rappor_encoder.Encode(index_value, basic_rappor_observation));
return result;
}
Encoder::Result Encoder::EncodeIntegerEventObservation(
MetricRef metric, const ReportDefinition* report, uint32_t day_index,
const RepeatedField<uint32_t>& event_codes, const std::string& component, int64_t value) const {
auto result = MakeObservation(metric, report, day_index);
auto* observation = result.observation.get();
auto* integer_event_observation = observation->mutable_numeric_event();
integer_event_observation->set_event_code(config::PackEventCodes(event_codes));
if (!HashComponentNameIfNotEmpty(component,
integer_event_observation->mutable_component_name_hash())) {
LOG(ERROR) << "Hashing the component name failed for: Report " << report->report_name()
<< " for metric " << metric.metric_name() << " in project "
<< metric.ProjectDebugString() << ".";
result.status = kOther;
}
integer_event_observation->set_value(value);
return result;
}
Encoder::Result Encoder::EncodeHistogramObservation(MetricRef metric,
const ReportDefinition* report,
uint32_t day_index,
const RepeatedField<uint32_t>& event_codes,
const std::string& component,
HistogramPtr histogram) const {
auto result = MakeObservation(metric, report, day_index);
auto* observation = result.observation.get();
auto* histogram_observation = observation->mutable_histogram();
histogram_observation->set_event_code(config::PackEventCodes(event_codes));
if (!HashComponentNameIfNotEmpty(component,
histogram_observation->mutable_component_name_hash())) {
LOG(ERROR) << "Hashing the component name failed for: Report " << report->report_name()
<< " for metric " << metric.metric_name() << " in project "
<< metric.ProjectDebugString() << ".";
result.status = kOther;
}
histogram_observation->mutable_buckets()->Swap(histogram.get());
return result;
}
Encoder::Result Encoder::EncodeCustomObservation(MetricRef metric, const ReportDefinition* report,
uint32_t day_index,
EventValuesPtr event_values) const {
auto result = MakeObservation(metric, report, day_index);
auto* observation = result.observation.get();
auto* custom_observation = observation->mutable_custom();
custom_observation->mutable_values()->swap(*event_values);
return result;
}
Encoder::Result Encoder::EncodeUniqueActivesObservation(
MetricRef metric, const ReportDefinition* report, uint32_t day_index, uint32_t event_code,
bool was_active, const OnDeviceAggregationWindow& aggregation_window) const {
auto result = MakeObservation(metric, report, day_index);
Encoder::Result basic_rappor_result;
if (was_active) {
// Encode a single 1 bit
basic_rappor_result = EncodeBasicRapporObservation(metric, report, day_index, 0u, 1u);
} else {
// Encode a single 0 bit
basic_rappor_result = EncodeNullBasicRapporObservation(metric, report, day_index, 1u);
}
if (basic_rappor_result.status != kOK) {
result.status = basic_rappor_result.status;
return result;
}
auto* activity_observation = result.observation->mutable_unique_actives();
*activity_observation->mutable_aggregation_window() = aggregation_window;
activity_observation->set_event_code(event_code);
activity_observation->mutable_basic_rappor_obs()->mutable_data()->swap(
*(basic_rappor_result.observation->mutable_basic_rappor()->mutable_data()));
return result;
}
Encoder::Result Encoder::EncodePerDeviceNumericObservation(
MetricRef metric, const ReportDefinition* report, uint32_t day_index,
const std::string& component, const RepeatedField<uint32_t>& event_codes, int64_t value,
const OnDeviceAggregationWindow& aggregation_window) const {
auto result =
EncodeIntegerEventObservation(metric, report, day_index, event_codes, component, value);
auto* integer_event_observation = result.observation->release_numeric_event();
auto* per_device_observation = result.observation->mutable_per_device_numeric();
per_device_observation->set_allocated_integer_event_obs(integer_event_observation);
*per_device_observation->mutable_aggregation_window() = aggregation_window;
return result;
}
Encoder::Result Encoder::EncodePerDeviceHistogramObservation(
MetricRef metric, const ReportDefinition* report, uint32_t day_index,
const std::string& component, const RepeatedField<uint32_t>& event_codes, int64_t value,
const OnDeviceAggregationWindow& aggregation_window) const {
auto result = MakeObservation(metric, report, day_index);
auto* per_device_histogram_obs = result.observation->mutable_per_device_histogram();
*per_device_histogram_obs->mutable_aggregation_window() = aggregation_window;
auto* histogram_observation = per_device_histogram_obs->mutable_histogram();
histogram_observation->set_event_code(config::PackEventCodes(event_codes));
if (!HashComponentNameIfNotEmpty(component,
histogram_observation->mutable_component_name_hash())) {
LOG(ERROR) << "Hashing the component name failed for: Report " << report->report_name()
<< " for metric " << metric.metric_name() << " in project "
<< metric.ProjectDebugString() << ".";
result.status = kOther;
}
auto integer_bucket_config = IntegerBucketConfig::CreateFromProto(report->int_buckets());
if (integer_bucket_config == nullptr) {
LOG(ERROR) << "Invalid IntBucketConfig for: Report " << report->report_name() << " for metric "
<< metric.metric_name() << " in project " << metric.ProjectDebugString() << ".";
result.status = kOther;
return result;
}
auto bucket = histogram_observation->add_buckets();
bucket->set_index(integer_bucket_config->BucketIndex(value));
bucket->set_count(1);
return result;
}
Encoder::Result Encoder::EncodeReportParticipationObservation(MetricRef metric,
const ReportDefinition* report,
uint32_t day_index) const {
auto result = MakeObservation(metric, report, day_index);
auto* observation = result.observation.get();
observation->mutable_report_participation();
return result;
}
Encoder::Result Encoder::EncodeNullBasicRapporObservation(MetricRef metric,
const ReportDefinition* report,
uint32_t day_index,
uint32_t num_categories) const {
auto result = MakeObservation(metric, report, day_index);
auto* observation = result.observation.get();
auto* basic_rappor_observation = observation->mutable_basic_rappor();
rappor::BasicRapporConfig basic_rappor_config;
basic_rappor_config.prob_rr = RapporConfigHelper::kProbRR;
basic_rappor_config.categories.set_indexed(num_categories);
float prob_bit_flip = RapporConfigHelper::ProbBitFlip(*report, metric.FullyQualifiedName());
basic_rappor_config.prob_0_becomes_1 = prob_bit_flip;
basic_rappor_config.prob_1_stays_1 = 1.0f - prob_bit_flip;
// TODO(rudominer) Stop copying the client_secret_ on each Encode*()
// operation.
BasicRapporEncoder basic_rappor_encoder(basic_rappor_config, client_secret_);
result.status = TranslateBasicRapporEncoderStatus(
metric, report, basic_rappor_encoder.EncodeNullObservation(basic_rappor_observation));
return result;
}
Encoder::Result Encoder::MakeObservation(MetricRef metric, const ReportDefinition* report,
uint32_t day_index) const {
Result result;
result.status = kOK;
result.observation = std::make_unique<Observation2>();
auto* observation = result.observation.get();
result.metadata = std::make_unique<ObservationMetadata>();
auto* metadata = result.metadata.get();
// Generate the random_id field. Currently we use 8 bytes but our
// infrastructure allows us to change that in the future if we wish to. The
// random_id is used by the Analyzer Service as part of a unique row key
// for the observation in the Observation Store.
static const size_t kNumRandomBytes = 8;
observation->set_allocated_random_id(new std::string(kNumRandomBytes, 0));
random_.RandomString(observation->mutable_random_id());
metadata->set_customer_id(metric.project().customer_id());
metadata->set_project_id(metric.project().project_id());
metadata->set_metric_id(metric.metric_id());
metadata->set_report_id(report->id());
metadata->set_day_index(day_index);
if (system_data_) {
const auto& profile = system_data_->system_profile();
for (const auto& field : report->system_profile_field()) {
switch (field) {
case SystemProfileField::OS:
metadata->mutable_system_profile()->set_os(profile.os());
break;
case SystemProfileField::ARCH:
metadata->mutable_system_profile()->set_arch(profile.arch());
break;
case SystemProfileField::BOARD_NAME:
metadata->mutable_system_profile()->set_board_name(profile.board_name());
break;
case SystemProfileField::PRODUCT_NAME:
metadata->mutable_system_profile()->set_product_name(profile.product_name());
break;
case SystemProfileField::SYSTEM_VERSION:
metadata->mutable_system_profile()->set_system_version(profile.system_version());
break;
case SystemProfileField::CHANNEL:
metadata->mutable_system_profile()->set_channel(profile.channel());
break;
}
}
}
return result;
}
} // namespace cobalt::logger