diff --git a/components/p3a/constellation_helper.cc b/components/p3a/constellation_helper.cc index fb9003c9a1f1..4fd9b9ac2ee5 100644 --- a/components/p3a/constellation_helper.cc +++ b/components/p3a/constellation_helper.cc @@ -123,7 +123,11 @@ bool ConstellationHelper::ConstructFinalMessage( const rust::Vec& resp_proofs, std::string* output) { auto* rnd_server_info = rand_meta_manager_.GetCachedRandomnessServerInfo(); - DCHECK(rnd_server_info); + if (!rnd_server_info) { + LOG(ERROR) << "ConstellationHelper: failed to get server info while " + "constructing message"; + return false; + } auto msg_res = constellation::construct_message( resp_points, resp_proofs, *randomness_request_state, resp_proofs.empty() ? *null_public_key_ : *rnd_server_info->public_key, diff --git a/components/p3a/constellation_log_store.cc b/components/p3a/constellation_log_store.cc index a8e1bc00f054..d4cc6e51ee1c 100644 --- a/components/p3a/constellation_log_store.cc +++ b/components/p3a/constellation_log_store.cc @@ -29,7 +29,7 @@ constexpr char kPrefName[] = "p3a.constellation_logs"; ConstellationLogStore::ConstellationLogStore(PrefService& local_state, size_t keep_epoch_count) : local_state_(local_state), keep_epoch_count_(keep_epoch_count) { - DCHECK_GT(keep_epoch_count, 0U); + CHECK_GT(keep_epoch_count, 0U); } ConstellationLogStore::~ConstellationLogStore() = default; diff --git a/components/p3a/message_manager.cc b/components/p3a/message_manager.cc index 5ec33cb28db6..b78e7bcb2da8 100644 --- a/components/p3a/message_manager.cc +++ b/components/p3a/message_manager.cc @@ -148,11 +148,9 @@ void MessageManager::DoConstellationRotation() { return; } constellation_prep_scheduler_->Stop(); - constellation_prep_log_store_->ResetUploadStamps(); VLOG(2) << "MessageManager doing Constellation rotation at " << base::Time::Now(); constellation_helper_->UpdateRandomnessServerInfo(); - delegate_->OnRotation(MetricLogType::kTypical, true); } void MessageManager::OnLogUploadComplete(bool is_ok, @@ -206,7 +204,14 @@ void MessageManager::OnRandomnessServerInfoReady( if (server_info == nullptr || !features::IsConstellationEnabled()) { return; } - VLOG(2) << "MessageManager::OnRandomnessServerInfoReady"; + VLOG(2) << "MessageManager::OnRandomnessServerInfoReady; epoch change = " + << server_info->epoch_change_detected; + if (server_info->epoch_change_detected) { + // a detected epoch change means that we can rotate + // the preparation store + constellation_prep_log_store_->ResetUploadStamps(); + delegate_->OnRotation(MetricLogType::kTypical, true); + } constellation_send_log_store_->SetCurrentEpoch(server_info->current_epoch); constellation_send_log_store_->LoadPersistedUnsentLogs(); constellation_prep_scheduler_->Start(); diff --git a/components/p3a/nitro_utils/attestation.cc b/components/p3a/nitro_utils/attestation.cc index 31789766f92a..b975dd3f59a6 100644 --- a/components/p3a/nitro_utils/attestation.cc +++ b/components/p3a/nitro_utils/attestation.cc @@ -34,6 +34,10 @@ namespace nitro_utils { namespace { +constexpr char kHashPrefix[] = "sha256:"; +constexpr size_t kHashPrefixLength = 7; +constexpr size_t kSHA256HashLength = 32; +constexpr size_t kUserDataMinLength = kSHA256HashLength + kHashPrefixLength; constexpr size_t kAttestationBodyMaxSize = 16384; constexpr net::SHA256HashValue kAWSRootCertFP{ .data = {0x64, 0x1A, 0x03, 0x21, 0xA3, 0xE2, 0x44, 0xEF, 0xE4, 0x56, 0x46, @@ -81,19 +85,25 @@ bool VerifyNonce(const cbor::Value::MapValue& cose_map, bool VerifyUserDataKey(scoped_refptr server_cert, const cbor::Value::MapValue& cose_map) { - DCHECK(server_cert); + CHECK(server_cert); const auto user_data_it = cose_map.find(cbor::Value("user_data")); if (user_data_it == cose_map.end() || !user_data_it->second.is_bytestring() || - user_data_it->second.GetBytestring().size() != 32) { + user_data_it->second.GetBytestring().size() < kUserDataMinLength) { LOG(ERROR) << "Nitro verification: user data is missing or is not bstr " - << "or is not 32 bytes"; + << "or is not at least " << kUserDataMinLength << " bytes"; + return false; + } + if (memcmp(user_data_it->second.GetBytestring().data(), kHashPrefix, + kHashPrefixLength) != 0) { + LOG(ERROR) << "Nitro verification: user data is missing sha256 hash prefix"; return false; } const net::SHA256HashValue server_cert_fp = net::X509Certificate::CalculateFingerprint256(server_cert->cert_buffer()); - if (memcmp(server_cert_fp.data, user_data_it->second.GetBytestring().data(), - 32) != 0) { + if (memcmp(server_cert_fp.data, + user_data_it->second.GetBytestring().data() + kHashPrefixLength, + kSHA256HashLength) != 0) { LOG(ERROR) << "Nitro verification: server cert fp does not match user data fp, " << "user data = " @@ -170,9 +180,10 @@ void ParseAndVerifyDocument( return; } - base::TrimString(*response_body, " ", response_body.get()); + base::StringPiece trimmed_body = + base::TrimWhitespaceASCII(*response_body, base::TrimPositions::TRIM_ALL); absl::optional> cose_encoded = - base::Base64Decode(*response_body); + base::Base64Decode(trimmed_body); if (!cose_encoded.has_value()) { LOG(ERROR) << "Nitro verification: Failed to decode base64 document"; std::move(result_callback).Run(scoped_refptr()); @@ -197,8 +208,7 @@ void ParseAndVerifyDocument( const network::mojom::URLResponseHead* response_info = url_loader->ResponseInfo(); - DCHECK(response_info); - if (!response_info->ssl_info.has_value() || + if (!response_info || !response_info->ssl_info.has_value() || response_info->ssl_info->cert == nullptr) { LOG(ERROR) << "Nitro verification: ssl info is missing from response info"; std::move(result_callback).Run(scoped_refptr()); diff --git a/components/p3a/nitro_utils/cose.cc b/components/p3a/nitro_utils/cose.cc index 3d033b8feda5..a4bc1148b4cd 100644 --- a/components/p3a/nitro_utils/cose.cc +++ b/components/p3a/nitro_utils/cose.cc @@ -36,13 +36,22 @@ bool ConvertCoseSignatureToDER(const std::vector& input, } BIGNUM* r_comp = BN_bin2bn(input.data(), kSignatureComponentSize, nullptr); - DCHECK(r_comp); + if (!r_comp) { + return false; + } BIGNUM* s_comp = BN_bin2bn(input.data() + kSignatureComponentSize, kSignatureComponentSize, nullptr); - DCHECK(s_comp); + if (!s_comp) { + BN_free(r_comp); + return false; + } ECDSA_SIG* ecdsa_sig = ECDSA_SIG_new(); - DCHECK(ecdsa_sig); + if (!ecdsa_sig) { + BN_free(r_comp); + BN_free(s_comp); + return false; + } if (ECDSA_SIG_set0(ecdsa_sig, r_comp, s_comp) != 1) { LOG(ERROR) << "COSE: Failed to construct ECDSA SIG struct"; @@ -53,7 +62,10 @@ bool ConvertCoseSignatureToDER(const std::vector& input, } CBB sig_cbb; - DCHECK_EQ(CBB_init(&sig_cbb, 0), 1); + if (CBB_init(&sig_cbb, 0) != 1) { + ECDSA_SIG_free(ecdsa_sig); + return false; + } if (ECDSA_SIG_marshal(&sig_cbb, ecdsa_sig) != 1) { LOG(ERROR) << "COSE: Failed to marshal ECDSA SIG struct"; @@ -91,9 +103,9 @@ bool CoseSign1::DecodeFromBytes(const std::vector& data) { *(cbor_config.error_code_out)); return false; } - DCHECK(decoded_val.has_value()); - if (!decoded_val->is_array() || decoded_val->GetArray().size() != 4) { + if (!decoded_val.has_value() || !decoded_val->is_array() || + decoded_val->GetArray().size() != 4) { LOG(ERROR) << "COSE: root decoded cbor is not array, or has incorrect size"; return false; } @@ -117,9 +129,8 @@ bool CoseSign1::DecodeFromBytes(const std::vector& data) { *(cbor_config.error_code_out)); return false; } - DCHECK(protected_decoded_val.has_value()); - if (!protected_decoded_val->is_map()) { + if (!protected_decoded_val.has_value() || !protected_decoded_val->is_map()) { LOG(ERROR) << "COSE: protected value is not a map"; return false; } @@ -156,14 +167,14 @@ bool CoseSign1::DecodeFromBytes(const std::vector& data) { absl::optional payload_dec_val = cbor::Reader::Read(payload_encoded_.GetBytestring(), cbor_config); - if (cbor_config.error_code_out != nullptr && - *cbor_config.error_code_out != - cbor::Reader::DecoderError::CBOR_NO_ERROR) { + if (!payload_dec_val.has_value() || + (cbor_config.error_code_out != nullptr && + *cbor_config.error_code_out != + cbor::Reader::DecoderError::CBOR_NO_ERROR)) { LOG(ERROR) << "COSE: failed to read payload cbor: " << cbor::Reader::ErrorCodeToString(*cbor_config.error_code_out); return false; } - DCHECK(payload_dec_val.has_value()); payload_ = payload_dec_val->Clone(); const cbor::Value& signature_val = cose_arr[3]; @@ -177,10 +188,10 @@ bool CoseSign1::DecodeFromBytes(const std::vector& data) { } bool CoseSign1::Verify(const net::ParsedCertificateList& cert_chain) { - DCHECK_GT(cert_chain.size(), 1U); + CHECK_GT(cert_chain.size(), 1U); net::der::GeneralizedTime time_now; - DCHECK(net::der::EncodeTimeAsGeneralizedTime(base::Time::Now(), &time_now)); + CHECK(net::der::EncodeTimeAsGeneralizedTime(base::Time::Now(), &time_now)); net::CertPathErrors cert_path_errors; @@ -205,7 +216,7 @@ bool CoseSign1::Verify(const net::ParsedCertificateList& cert_chain) { absl::optional> encoded_sig_data = cbor::Writer::Write(sig_data); - DCHECK(encoded_sig_data.has_value()); + CHECK(encoded_sig_data.has_value()); base::StringPiece low_cert_spki; if (!net::asn1::ExtractSPKIFromDERCert( diff --git a/components/p3a/p3a_config.cc b/components/p3a/p3a_config.cc index 72b65a766e2c..654876f178be 100644 --- a/components/p3a/p3a_config.cc +++ b/components/p3a/p3a_config.cc @@ -10,6 +10,7 @@ #include "base/check.h" #include "base/command_line.h" #include "base/logging.h" +#include "base/strings/string_number_conversions.h" #include "brave/components/p3a/buildflags.h" #include "brave/components/p3a/switches.h" @@ -119,6 +120,15 @@ P3AConfig P3AConfig::LoadFromCommandLine() { cmdline, switches::kP3AExpressRotationIntervalSeconds, std::move(config.json_rotation_intervals[MetricLogType::kExpress])); + if (cmdline->HasSwitch(switches::kP3AFakeStarEpoch)) { + unsigned fake_star_epoch; + if (base::StringToUint( + cmdline->GetSwitchValueASCII(switches::kP3AFakeStarEpoch), + &fake_star_epoch)) { + config.fake_star_epoch = fake_star_epoch; + } + } + config.p3a_json_upload_url = MaybeOverrideURLFromCommandLine(cmdline, switches::kP3AJsonUploadUrl, std::move(config.p3a_json_upload_url)); diff --git a/components/p3a/p3a_config.h b/components/p3a/p3a_config.h index 1dea68a3be06..cc6bda1f06f4 100644 --- a/components/p3a/p3a_config.h +++ b/components/p3a/p3a_config.h @@ -11,6 +11,7 @@ #include "base/containers/flat_map.h" #include "base/time/time.h" #include "brave/components/p3a/metric_log_type.h" +#include "third_party/abseil-cpp/absl/types/optional.h" #include "url/gurl.h" namespace p3a { @@ -22,6 +23,9 @@ struct P3AConfig { // Interval between rotations, only used for testing from the command line. base::flat_map json_rotation_intervals; + // Fake STAR epoch for testing purposes. + absl::optional fake_star_epoch = absl::nullopt; + // Endpoint for uploading P3A metrics in JSON format GURL p3a_json_upload_url; // Endpoint for uploading NTP-SI/creative P3A metrics in JSON format diff --git a/components/p3a/star_randomness_meta.cc b/components/p3a/star_randomness_meta.cc index 23b61622b9e2..f676995c27bc 100644 --- a/components/p3a/star_randomness_meta.cc +++ b/components/p3a/star_randomness_meta.cc @@ -12,6 +12,7 @@ #include "base/functional/bind.h" #include "base/json/json_reader.h" #include "base/logging.h" +#include "base/strings/strcat.h" #include "brave/components/p3a/network_annotations.h" #include "brave/components/p3a/nitro_utils/attestation.h" #include "components/prefs/pref_registry_simple.h" @@ -28,6 +29,7 @@ namespace { constexpr char kCurrentPKPrefName[] = "brave.p3a.current_pk"; constexpr char kCurrentEpochPrefName[] = "brave.p3a.current_epoch"; constexpr char kNextEpochTimePrefName[] = "brave.p3a.next_epoch_time"; +constexpr char kApprovedCertFPPrefName[] = "brave.p3a.approved_cert_fp"; // A generous arbitrary limit, 128KB constexpr std::size_t kMaxInfoResponseSize = 128 * 1024; const int kRndInfoRetryInitialBackoffSeconds = 5; @@ -63,9 +65,11 @@ ::rust::Box DecodeServerPublicKey( RandomnessServerInfo::RandomnessServerInfo( uint8_t current_epoch, base::Time next_epoch_time, + bool epoch_change_detected, ::rust::Box public_key) : current_epoch(current_epoch), next_epoch_time(next_epoch_time), + epoch_change_detected(epoch_change_detected), public_key(std::move(public_key)) {} RandomnessServerInfo::~RandomnessServerInfo() {} @@ -77,7 +81,16 @@ StarRandomnessMeta::StarRandomnessMeta( : url_loader_factory_(url_loader_factory), local_state_(local_state), info_callback_(info_callback), - config_(config) {} + config_(config) { + std::string approved_cert_fp_str = + local_state->GetString(kApprovedCertFPPrefName); + net::HashValue approved_cert_fp; + if (!approved_cert_fp_str.empty() && + approved_cert_fp.FromString(approved_cert_fp_str)) { + VLOG(2) << "StarRandomnessMeta: loaded cached approved cert"; + approved_cert_fp_ = absl::make_optional(approved_cert_fp); + } +} StarRandomnessMeta::~StarRandomnessMeta() {} @@ -85,6 +98,7 @@ void StarRandomnessMeta::RegisterPrefs(PrefRegistrySimple* registry) { registry->RegisterStringPref(kCurrentPKPrefName, std::string()); registry->RegisterIntegerPref(kCurrentEpochPrefName, -1); registry->RegisterTimePref(kNextEpochTimePrefName, base::Time()); + registry->RegisterStringPref(kApprovedCertFPPrefName, ""); } bool StarRandomnessMeta::VerifyRandomnessCert( @@ -95,7 +109,7 @@ bool StarRandomnessMeta::VerifyRandomnessCert( } const network::mojom::URLResponseHead* response_info = url_loader->ResponseInfo(); - if (approved_cert_ == nullptr) { + if (!approved_cert_fp_.has_value()) { LOG(ERROR) << "StarRandomnessMeta: approved cert is missing"; AttestServer(false); return false; @@ -105,10 +119,12 @@ bool StarRandomnessMeta::VerifyRandomnessCert( LOG(ERROR) << "StarRandomnessMeta: ssl info is missing from response info"; return false; } - if (!response_info->ssl_info->cert->EqualsIncludingChain( - approved_cert_.get())) { + net::HashValue cert_fp_hash = net::HashValue( + response_info->ssl_info->cert->CalculateChainFingerprint256()); + if (cert_fp_hash != *approved_cert_fp_) { LOG(ERROR) << "StarRandomnessMeta: approved cert mismatch, will retry " - "attestation"; + "attestation; fp = " + << cert_fp_hash.ToString(); AttestServer(false); return false; } @@ -118,32 +134,41 @@ bool StarRandomnessMeta::VerifyRandomnessCert( void StarRandomnessMeta::RequestServerInfo() { rnd_server_info_ = nullptr; - if (!config_->disable_star_attestation && approved_cert_ == nullptr) { + if (!config_->disable_star_attestation && !approved_cert_fp_.has_value()) { AttestServer(true); return; } - if (!has_used_cached_info) { + if (!has_used_cached_info_) { // Using cached server info, if available. + last_cached_epoch_ = + absl::make_optional(local_state_->GetInteger(kCurrentEpochPrefName)); base::Time saved_next_epoch_time = local_state_->GetTime(kNextEpochTimePrefName); - if (saved_next_epoch_time > base::Time::Now()) { - int saved_epoch = local_state_->GetInteger(kCurrentEpochPrefName); + // only return cached info if the epoch is not expired, + // and if the "fake" star epoch matches the last saved epoch (if specified). + // if "fake" star epoch does not match the saved epoch, then fresh server + // info should be requested to update the local state with the new epoch + // info + if (saved_next_epoch_time > base::Time::Now() && + (!config_->fake_star_epoch.has_value() || + config_->fake_star_epoch == last_cached_epoch_)) { std::string saved_pk = local_state_->GetString(kCurrentPKPrefName); rnd_server_info_ = std::make_unique( - static_cast(saved_epoch), saved_next_epoch_time, + *last_cached_epoch_, saved_next_epoch_time, false, DecodeServerPublicKey(&saved_pk)); VLOG(2) << "StarRandomnessMeta: using cached server info"; info_callback_.Run(rnd_server_info_.get()); - has_used_cached_info = true; + has_used_cached_info_ = true; return; } } rnd_server_info_ = nullptr; auto resource_request = std::make_unique(); - resource_request->url = GURL(config_->star_randomness_host + "/info"); + resource_request->url = + GURL(base::StrCat({config_->star_randomness_host, "/info"})); if (!resource_request->url.is_valid() || !resource_request->url.SchemeIsHTTPOrHTTPS()) { VLOG(2) << "StarRandomnessMeta: star randomness host invalid, skipping " @@ -171,10 +196,11 @@ void StarRandomnessMeta::AttestServer(bool make_info_request_after) { return; } attestation_pending_ = true; - approved_cert_ = nullptr; + approved_cert_fp_ = absl::nullopt; VLOG(2) << "StarRandomnessMeta: starting attestation"; - GURL attestation_url = GURL(config_->star_randomness_host + "/attestation"); + GURL attestation_url = GURL( + base::StrCat({config_->star_randomness_host, "/enclave/attestation"})); if (!attestation_url.is_valid() || !attestation_url.SchemeIsHTTPOrHTTPS()) { VLOG(2) << "StarRandomnessMeta: star randomness host invalid, skipping " "server attestation"; @@ -197,9 +223,13 @@ void StarRandomnessMeta::HandleAttestationResult( } return; } - approved_cert_ = approved_cert; + approved_cert_fp_ = absl::make_optional( + net::HashValue(approved_cert->CalculateChainFingerprint256())); + std::string approved_cert_fp_str = approved_cert_fp_->ToString(); + local_state_->SetString(kApprovedCertFPPrefName, approved_cert_fp_str); attestation_pending_ = false; - VLOG(2) << "StarRandomnessMeta: attestation succeeded"; + VLOG(2) << "StarRandomnessMeta: attestation succeeded; fp = " + << approved_cert_fp_str; if (make_info_request_after) { RequestServerInfo(); } @@ -255,6 +285,11 @@ void StarRandomnessMeta::HandleServerInfoResponse( ScheduleServerInfoRetry(); return; } + + if (config_->fake_star_epoch.has_value()) { + epoch = config_->fake_star_epoch; + } + base::Time next_epoch_time; if (!base::Time::FromString(next_epoch_time_str->c_str(), &next_epoch_time) || next_epoch_time <= base::Time::Now()) { @@ -271,8 +306,16 @@ void StarRandomnessMeta::HandleServerInfoResponse( } local_state_->SetInteger(kCurrentEpochPrefName, *epoch); local_state_->SetTime(kNextEpochTimePrefName, next_epoch_time); + + bool epoch_change_detected = false; + if (last_cached_epoch_ != epoch) { + last_cached_epoch_ = epoch; + epoch_change_detected = true; + } + rnd_server_info_ = std::make_unique( - static_cast(*epoch), next_epoch_time, std::move(pk)); + static_cast(*epoch), next_epoch_time, epoch_change_detected, + std::move(pk)); current_backoff_time_ = base::TimeDelta(); VLOG(2) << "StarRandomnessMeta: server info retrieved"; info_callback_.Run(rnd_server_info_.get()); diff --git a/components/p3a/star_randomness_meta.h b/components/p3a/star_randomness_meta.h index c45d63e6b04b..1f244f496ae2 100644 --- a/components/p3a/star_randomness_meta.h +++ b/components/p3a/star_randomness_meta.h @@ -12,12 +12,13 @@ #include "base/functional/callback.h" #include "base/memory/raw_ptr.h" #include "base/memory/raw_ref.h" -#include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "base/timer/timer.h" #include "brave/components/p3a/constellation/rs/cxx/src/lib.rs.h" #include "brave/components/p3a/p3a_config.h" +#include "net/base/hash_value.h" +#include "third_party/abseil-cpp/absl/types/optional.h" class PrefService; class PrefRegistrySimple; @@ -37,11 +38,13 @@ struct RandomnessServerInfo { RandomnessServerInfo( uint8_t current_epoch, base::Time next_epoch_time, + bool epoch_change_detected, ::rust::Box public_key); ~RandomnessServerInfo(); uint8_t current_epoch; base::Time next_epoch_time; + bool epoch_change_detected; ::rust::Box public_key; }; @@ -87,7 +90,8 @@ class StarRandomnessMeta { std::unique_ptr rnd_server_info_; - bool has_used_cached_info = false; + bool has_used_cached_info_ = false; + absl::optional last_cached_epoch_ = absl::nullopt; const raw_ptr local_state_; @@ -97,7 +101,7 @@ class StarRandomnessMeta { const raw_ptr config_; - scoped_refptr approved_cert_; + absl::optional approved_cert_fp_; bool attestation_pending_ = false; base::WeakPtrFactory weak_ptr_factory_{this}; diff --git a/components/p3a/star_randomness_points.cc b/components/p3a/star_randomness_points.cc index efed50462153..1db83aa816d7 100644 --- a/components/p3a/star_randomness_points.cc +++ b/components/p3a/star_randomness_points.cc @@ -14,6 +14,7 @@ #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" +#include "base/strings/strcat.h" #include "base/values.h" #include "brave/components/p3a/network_annotations.h" #include "brave/components/p3a/p3a_config.h" @@ -71,7 +72,8 @@ void StarRandomnessPoints::SendRandomnessRequest( randomness_request_state, const rust::Vec& rand_req_points) { auto resource_request = std::make_unique(); - resource_request->url = GURL(config_->star_randomness_host + "/randomness"); + resource_request->url = + GURL(base::StrCat({config_->star_randomness_host, "/randomness"})); resource_request->method = "POST"; url_loader_ = network::SimpleURLLoader::Create( diff --git a/components/p3a/switches.h b/components/p3a/switches.h index 2188204a1044..90b592f460e9 100644 --- a/components/p3a/switches.h +++ b/components/p3a/switches.h @@ -28,6 +28,11 @@ constexpr char kP3AExpressRotationIntervalSeconds[] = constexpr char kP3ASlowRotationIntervalSeconds[] = "p3a-slow-rotation-interval-seconds"; +// For specifying a fake STAR epoch, for the purpose of +// triggering the transmission of encrypted measurements before they are +// due to be sent, for testing purposes. +constexpr char kP3AFakeStarEpoch[] = "p3a-fake-star-epoch"; + // P3A cloud backend URL. constexpr char kP3AJsonUploadUrl[] = "p3a-json-upload-url"; constexpr char kP3ACreativeUploadUrl[] = "p3a-creative-upload-url"; diff --git a/ios/app/brave_core_switches.h b/ios/app/brave_core_switches.h index 57012f3e157d..546513b3de9a 100644 --- a/ios/app/brave_core_switches.h +++ b/ios/app/brave_core_switches.h @@ -57,6 +57,13 @@ OBJC_EXPORT const BraveCoreSwitchKey /// Expected value: A number (in seconds) OBJC_EXPORT const BraveCoreSwitchKey BraveCoreSwitchKeyP3ASlowRotationIntervalSeconds NS_SWIFT_NAME(p3aSlowRotationIntervalSeconds); // NOLINT +/// For specifying a fake STAR epoch, for the purpose of +/// triggering the transmission of encrypted measurements before they are +/// due to be sent, for testing purposes. +/// +/// Expected value: An 8-bit unsigned integer +OBJC_EXPORT const BraveCoreSwitchKey + BraveCoreSwitchKeyP3AFakeStarEpoch NS_SWIFT_NAME(p3aFakeStarEpoch); // NOLINT /// Overrides the P3A JSON backend URL. /// /// Expected value: A URL string diff --git a/ios/app/brave_core_switches.mm b/ios/app/brave_core_switches.mm index ca5abcf44223..7e5f91f14219 100644 --- a/ios/app/brave_core_switches.mm +++ b/ios/app/brave_core_switches.mm @@ -33,6 +33,8 @@ base::SysUTF8ToNSString(p3a::switches::kP3AExpressRotationIntervalSeconds); const BraveCoreSwitchKey BraveCoreSwitchKeyP3ASlowRotationIntervalSeconds = base::SysUTF8ToNSString(p3a::switches::kP3ASlowRotationIntervalSeconds); +const BraveCoreSwitchKey BraveCoreSwitchKeyP3AFakeStarEpoch = + base::SysUTF8ToNSString(p3a::switches::kP3AFakeStarEpoch); const BraveCoreSwitchKey BraveCoreSwitchKeyP3AJsonUploadServerURL = base::SysUTF8ToNSString(p3a::switches::kP3AJsonUploadUrl); const BraveCoreSwitchKey BraveCoreSwitchKeyP3ACreativeUploadServerURL =