blob: 09cafa7b4535ac63d97c4f816c16ef3b5208cd94 [file] [log] [blame]
// Copyright 2022 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 "content/services/worklet_utils/private_aggregation_utils.h"
#include <stdint.h>
#include <string>
#include "base/check.h"
#include "base/strings/string_util.h"
#include "content/common/aggregatable_report.mojom.h"
#include "content/common/private_aggregation_host.mojom.h"
#include "gin/arguments.h"
#include "gin/converter.h"
#include "gin/dictionary.h"
#include "third_party/abseil-cpp/absl/numeric/int128.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-exception.h"
#include "v8/include/v8-isolate.h"
#include "v8/include/v8-local-handle.h"
#include "v8/include/v8-primitive.h"
namespace worklet_utils {
namespace {
v8::Local<v8::String> CreateStringFromLiteral(v8::Isolate* isolate,
const char* ascii_string) {
DCHECK(base::IsStringASCII(ascii_string));
return v8::String::NewFromUtf8(isolate, ascii_string,
v8::NewStringType::kNormal,
strlen(ascii_string))
.ToLocalChecked();
}
v8::MaybeLocal<v8::String> CreateUtf8String(v8::Isolate* isolate,
base::StringPiece utf8_string) {
if (!base::IsStringUTF8(utf8_string))
return v8::MaybeLocal<v8::String>();
return v8::String::NewFromUtf8(isolate, utf8_string.data(),
v8::NewStringType::kNormal,
utf8_string.length());
}
// In case of failure, will return `absl::nullopt` and output an error to
// `error_out`.
absl::optional<uint64_t> ParseDebugKey(v8::Local<v8::Value> js_debug_key,
v8::Local<v8::Context>& context,
std::string* error_out) {
if (js_debug_key.IsEmpty() || js_debug_key->IsNullOrUndefined()) {
return absl::nullopt;
}
if (js_debug_key->IsBigInt()) {
absl::optional<absl::uint128> maybe_debug_key =
ConvertBigIntToUint128(js_debug_key.As<v8::BigInt>(), error_out);
if (!maybe_debug_key.has_value()) {
return absl::nullopt;
}
if (absl::Uint128High64(maybe_debug_key.value()) != 0) {
*error_out = "BigInt is too large";
return absl::nullopt;
}
return absl::Uint128Low64(maybe_debug_key.value());
}
*error_out = "debug_key must be a BigInt";
return absl::nullopt;
}
} // namespace
absl::optional<absl::uint128> ConvertBigIntToUint128(
v8::Local<v8::BigInt> bigint,
std::string* error_out) {
if (bigint.IsEmpty()) {
*error_out = "Failed to interpret as BigInt";
return absl::nullopt;
}
if (bigint->WordCount() > 2) {
*error_out = "BigInt is too large";
return absl::nullopt;
}
// Signals the size of the `words` array to `ToWordsArray()`. The number of
// elements actually used is then written here by the function.
int word_count = 2;
int sign_bit = 0;
uint64_t words[2] = {0, 0}; // Least significant to most significant.
bigint->ToWordsArray(&sign_bit, &word_count, words);
if (sign_bit) {
*error_out = "BigInt must be non-negative";
return absl::nullopt;
}
return absl::MakeUint128(words[1], words[0]);
}
content::mojom::AggregatableReportHistogramContributionPtr
ParseSendHistogramReportArguments(
const gin::Arguments& args,
bool private_aggregation_permissions_policy_allowed) {
v8::Isolate* isolate = args.isolate();
if (!private_aggregation_permissions_policy_allowed) {
isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
isolate,
"The \"private-aggregation\" Permissions Policy denied the method on "
"privateAggregation")));
return nullptr;
}
std::vector<v8::Local<v8::Value>> argument_list = args.GetAll();
// Any additional arguments are ignored.
if (argument_list.size() == 0 || argument_list[0].IsEmpty() ||
!argument_list[0]->IsObject()) {
isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
isolate, "sendHistogramReport requires 1 object parameter")));
return nullptr;
}
gin::Dictionary dict(isolate);
bool success = gin::ConvertFromV8(isolate, argument_list[0], &dict);
DCHECK(success);
v8::Local<v8::Value> js_bucket;
v8::Local<v8::Value> js_value;
if (!dict.Get("bucket", &js_bucket)) {
// Propagate any exception
return nullptr;
}
if (js_bucket.IsEmpty() || js_bucket->IsNullOrUndefined()) {
isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
isolate, "Invalid or missing bucket in sendHistogramReport argument")));
return nullptr;
}
if (!dict.Get("value", &js_value)) {
// Propagate any exception
return nullptr;
}
if (js_value.IsEmpty() || js_value->IsNullOrUndefined()) {
isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
isolate, "Invalid or missing value in sendHistogramReport argument")));
return nullptr;
}
absl::uint128 bucket;
int value;
if (js_bucket->IsBigInt()) {
std::string error;
absl::optional<absl::uint128> maybe_bucket =
ConvertBigIntToUint128(js_bucket.As<v8::BigInt>(), &error);
if (!maybe_bucket.has_value()) {
DCHECK(base::IsStringUTF8(error));
isolate->ThrowException(v8::Exception::TypeError(
CreateUtf8String(isolate, error).ToLocalChecked()));
return nullptr;
}
bucket = maybe_bucket.value();
} else {
isolate->ThrowException(v8::Exception::TypeError(
CreateStringFromLiteral(isolate, "bucket must be a BigInt")));
return nullptr;
}
if (js_value->IsInt32()) {
value = js_value.As<v8::Int32>()->Value();
} else if (js_value->IsBigInt()) {
isolate->ThrowException(v8::Exception::TypeError(
CreateStringFromLiteral(isolate, "Value cannot be a BigInt")));
return nullptr;
} else {
isolate->ThrowException(v8::Exception::TypeError(
CreateStringFromLiteral(isolate, "Value must be an integer Number")));
return nullptr;
}
if (value < 0) {
isolate->ThrowException(v8::Exception::TypeError(
CreateStringFromLiteral(isolate, "Value must be non-negative")));
return nullptr;
}
return content::mojom::AggregatableReportHistogramContribution::New(bucket,
value);
}
void ParseAndApplyEnableDebugModeArguments(
const gin::Arguments& args,
bool private_aggregation_permissions_policy_allowed,
content::mojom::DebugModeDetails& debug_mode_details) {
v8::Isolate* isolate = args.isolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
if (!private_aggregation_permissions_policy_allowed) {
isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
isolate,
"The \"private-aggregation\" Permissions Policy denied the method on "
"privateAggregation")));
return;
}
std::vector<v8::Local<v8::Value>> argument_list = args.GetAll();
if (debug_mode_details.is_enabled) {
isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
isolate, "enableDebugMode may be called at most once")));
return;
}
// If no arguments are provided, no debug key is set.
if (argument_list.size() >= 1 && !argument_list[0].IsEmpty()) {
gin::Dictionary dict(isolate);
if (!gin::ConvertFromV8(isolate, argument_list[0], &dict)) {
isolate->ThrowException(v8::Exception::TypeError(CreateStringFromLiteral(
isolate, "Invalid argument in enableDebugMode")));
return;
}
v8::Local<v8::Value> js_debug_key;
if (!dict.Get("debug_key", &js_debug_key)) {
// Propagate any exception
return;
}
std::string error;
absl::optional<uint64_t> maybe_debug_key =
ParseDebugKey(js_debug_key, context, &error);
if (!maybe_debug_key.has_value()) {
DCHECK(base::IsStringUTF8(error));
isolate->ThrowException(v8::Exception::TypeError(
CreateUtf8String(isolate, error).ToLocalChecked()));
return;
}
debug_mode_details.debug_key =
content::mojom::DebugKey::New(maybe_debug_key.value());
}
debug_mode_details.is_enabled = true;
}
} // namespace worklet_utils