Skip to content

Commit

Permalink
Library to support Churn Cohort use case.
Browse files Browse the repository at this point in the history
Implement the library used to support the churn cohort use case.

BUG=chromium:1386189

Change-Id: I6dc57c9686348f665fe3468842e2f748f2dafc60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4129802
Commit-Queue: Eric Wang <qianwan@google.com>
Reviewed-by: Hirthanan Subenderan <hirthanan@google.com>
Cr-Commit-Position: refs/heads/main@{#1096342}
  • Loading branch information
Eric Wang authored and Chromium LUCI CQ committed Jan 24, 2023
1 parent 01e5a05 commit efe65d1
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 4 deletions.
12 changes: 12 additions & 0 deletions ash/constants/ash_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2202,6 +2202,18 @@ BASE_FEATURE(kDeviceActiveClientDailyCheckMembership,
"DeviceActiveClientDailyCheckMembership",
base::FEATURE_ENABLED_BY_DEFAULT);

// Enables or disables PSM CheckIn for the churn cohort device active pings
// on ChromeOS.
BASE_FEATURE(kDeviceActiveClientChurnCohortCheckIn,
"DeviceActiveClientChurnCohortCheckIn",
base::FEATURE_DISABLED_BY_DEFAULT);

// Enables or disables PSM CheckMembership for the churn cohort device active
// pings on ChromeOS.
BASE_FEATURE(kDeviceActiveClientChurnCohortCheckMembership,
"DeviceActiveClientChurnCohortCheckMembership",
base::FEATURE_DISABLED_BY_DEFAULT);

// Enables or disables forced reboots when DeviceScheduledReboot policy is set.
BASE_FEATURE(kDeviceForceScheduledReboot,
"DeviceForceScheduledReboot",
Expand Down
4 changes: 4 additions & 0 deletions ash/constants/ash_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ BASE_DECLARE_FEATURE(kDeviceActiveClient28DayActiveCheckMembership);
COMPONENT_EXPORT(ASH_CONSTANTS)
BASE_DECLARE_FEATURE(kDeviceActiveClientDailyCheckMembership);
COMPONENT_EXPORT(ASH_CONSTANTS)
BASE_DECLARE_FEATURE(kDeviceActiveClientChurnCohortCheckIn);
COMPONENT_EXPORT(ASH_CONSTANTS)
BASE_DECLARE_FEATURE(kDeviceActiveClientChurnCohortCheckMembership);
COMPONENT_EXPORT(ASH_CONSTANTS)
BASE_DECLARE_FEATURE(kDeviceForceScheduledReboot);
COMPONENT_EXPORT(ASH_CONSTANTS)
extern const base::FeatureParam<int> kDeviceForceScheduledRebootMaxDelay;
Expand Down
3 changes: 3 additions & 0 deletions chromeos/ash/components/device_activity/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ component("device_activity") {
sources = [
"churn_active_status.cc",
"churn_active_status.h",
"churn_cohort_use_case_impl.cc",
"churn_cohort_use_case_impl.h",
"daily_use_case_impl.cc",
"daily_use_case_impl.h",
"device_active_use_case.cc",
Expand All @@ -59,6 +61,7 @@ source_set("unit_tests") {
testonly = true

sources = [
"churn_cohort_use_case_impl_unittest.cc",
"daily_use_case_impl_unittest.cc",
"device_active_use_case_unittest.cc",
"device_activity_client_unittest.cc",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/device_activity/churn_cohort_use_case_impl.h"

#include "ash/constants/ash_features.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "chromeos/ash/components/device_activity/fresnel_pref_names.h"
#include "chromeos/ash/components/device_activity/fresnel_service.pb.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/channel.h"
#include "third_party/private_membership/src/private_membership_rlwe_client.h"

namespace ash::device_activity {

namespace psm_rlwe = private_membership::rlwe;

ChurnCohortUseCaseImpl::ChurnCohortUseCaseImpl(
const std::string& psm_device_active_secret,
const ChromeDeviceMetadataParameters& chrome_passed_device_params,
PrefService* local_state,
std::unique_ptr<PsmDelegateInterface> psm_delegate)
: DeviceActiveUseCase(
psm_device_active_secret,
chrome_passed_device_params,
prefs::kDeviceActiveChurnCohortMonthlyPingTimestamp,
psm_rlwe::RlweUseCase::CROS_FRESNEL_CHURN_MONTHLY_COHORT,
local_state,
std::move(psm_delegate)) {}

ChurnCohortUseCaseImpl::~ChurnCohortUseCaseImpl() = default;

std::string ChurnCohortUseCaseImpl::GenerateWindowIdentifier(
base::Time ts) const {
base::Time::Exploded exploded;
ts.UTCExplode(&exploded);
return base::StringPrintf("%04d%02d", exploded.year, exploded.month);
}

FresnelImportDataRequest ChurnCohortUseCaseImpl::GenerateImportRequestBody() {
std::string psm_id_str = GetPsmIdentifier().value().sensitive_id();
std::string window_id_str = GetWindowIdentifier().value();

// Generate Fresnel PSM import request body.
FresnelImportDataRequest import_request;
import_request.set_window_identifier(window_id_str);

// Create fresh |DeviceMetadata| object.
// Note every dimension added to this proto must be approved by privacy.
DeviceMetadata* device_metadata = import_request.mutable_device_metadata();
device_metadata->set_chromeos_version(GetChromeOSVersion());
device_metadata->set_chromeos_channel(GetChromeOSChannel());
device_metadata->set_market_segment(GetMarketSegment());
device_metadata->set_hardware_id(GetFullHardwareClass());

import_request.set_use_case(GetPsmUseCase());
import_request.set_plaintext_identifier(psm_id_str);

return import_request;
}

bool ChurnCohortUseCaseImpl::IsEnabledCheckIn() {
return base::FeatureList::IsEnabled(
features::kDeviceActiveClientChurnCohortCheckIn);
}

bool ChurnCohortUseCaseImpl::IsEnabledCheckMembership() {
return base::FeatureList::IsEnabled(
features::kDeviceActiveClientChurnCohortCheckMembership);
}

private_computing::ActiveStatus ChurnCohortUseCaseImpl::GenerateActiveStatus() {
private_computing::ActiveStatus status;

status.set_use_case(private_computing::PrivateComputingUseCase::
CROS_FRESNEL_CHURN_MONTHLY_COHORT);

std::string last_ping_pt_date =
FormatPTDateString(GetLastKnownPingTimestamp());
status.set_last_ping_date(last_ping_pt_date);

return status;
}
} // namespace ash::device_activity
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROMEOS_ASH_COMPONENTS_DEVICE_ACTIVITY_CHURN_COHORT_USE_CASE_IMPL_H_
#define CHROMEOS_ASH_COMPONENTS_DEVICE_ACTIVITY_CHURN_COHORT_USE_CASE_IMPL_H_

#include "base/component_export.h"
#include "base/time/time.h"
#include "chromeos/ash/components/device_activity/device_active_use_case.h"

class PrefService;

namespace version_info {
enum class Channel;
} // namespace version_info

namespace ash::device_activity {

// Forward declaration from fresnel_service.proto.
class FresnelImportDataRequest;

// Contains the methods required to report the churn cohort device active.
class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_DEVICE_ACTIVITY)
ChurnCohortUseCaseImpl : public DeviceActiveUseCase {
public:
ChurnCohortUseCaseImpl(
const std::string& psm_device_active_secret,
const ChromeDeviceMetadataParameters& chrome_passed_device_params,
PrefService* local_state,
std::unique_ptr<PsmDelegateInterface> psm_delegate);
ChurnCohortUseCaseImpl(const ChurnCohortUseCaseImpl&) = delete;
ChurnCohortUseCaseImpl& operator=(const ChurnCohortUseCaseImpl&) = delete;
~ChurnCohortUseCaseImpl() override;

// The Churn Cohort window identifier is the year-month when the device
// report its cohort active request to Fresnel.
//
// For example, if the device has reported its active on `20221202`,
// then the Churn Cohort window identifier is `202212`
std::string GenerateWindowIdentifier(base::Time ts) const override;

FresnelImportDataRequest GenerateImportRequestBody() override;

// Whether current device active use case check-in is enabled or not.
bool IsEnabledCheckIn() override;

// Whether current device active use case check membership is enabled or not.
bool IsEnabledCheckMembership() override;

private_computing::ActiveStatus GenerateActiveStatus() override;
};

} // namespace ash::device_activity

#endif // CHROMEOS_ASH_COMPONENTS_DEVICE_ACTIVITY_CHURN_COHORT_USE_CASE_IMPL_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/device_activity/churn_cohort_use_case_impl.h"

#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "chromeos/ash/components/device_activity/device_activity_controller.h"
#include "chromeos/ash/components/device_activity/fake_psm_delegate.h"
#include "chromeos/ash/components/device_activity/fresnel_pref_names.h"
#include "chromeos/ash/components/system/fake_statistics_provider.h"
#include "components/prefs/testing_pref_service.h"
#include "components/version_info/channel.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/private_membership/src/private_membership_rlwe_client.h"

namespace ash::device_activity {

namespace psm_rlwe = private_membership::rlwe;

namespace {

// This secret should be of exactly length 64, since it is a 256 bit string
// encoded as a hexadecimal.
constexpr char kFakePsmDeviceActiveSecret[] =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

constexpr ChromeDeviceMetadataParameters kFakeChromeParameters = {
version_info::Channel::STABLE /* chromeos_channel */,
MarketSegment::MARKET_SEGMENT_UNKNOWN /* market_segment */,
};

} // namespace

class ChurnCohortUseCaseImplTest : public testing::Test {
public:
ChurnCohortUseCaseImplTest() = default;
ChurnCohortUseCaseImplTest(const ChurnCohortUseCaseImplTest&) = delete;
ChurnCohortUseCaseImplTest& operator=(const ChurnCohortUseCaseImplTest&) =
delete;
~ChurnCohortUseCaseImplTest() override = default;

protected:
// testing::Test:
void SetUp() override {
DeviceActivityController::RegisterPrefs(local_state_.registry());

system::StatisticsProvider::SetTestProvider(&statistics_provider_);

const std::vector<psm_rlwe::RlwePlaintextId> plaintext_ids;
churn_cohort_use_case_impl_ = std::make_unique<ChurnCohortUseCaseImpl>(
kFakePsmDeviceActiveSecret, kFakeChromeParameters, &local_state_,
// |FakePsmDelegate| can use any test case parameters.
std::make_unique<FakePsmDelegate>(std::string() /* ec_cipher_key */,
std::string() /* seed */,
std::move(plaintext_ids)));
}

void TearDown() override { churn_cohort_use_case_impl_.reset(); }

std::unique_ptr<ChurnCohortUseCaseImpl> churn_cohort_use_case_impl_;

// Fake pref service for unit testing the local state.
TestingPrefServiceSimple local_state_;
system::FakeStatisticsProvider statistics_provider_;
};

TEST_F(ChurnCohortUseCaseImplTest, ValidateWindowIdFormattedCorrectly) {
// Create fixed timestamp used to generate a fixed window identifier.
base::Time new_daily_ts;
EXPECT_TRUE(
base::Time::FromString("01 Jan 2022 23:59:59 GMT", &new_daily_ts));

std::string window_id =
churn_cohort_use_case_impl_->GenerateWindowIdentifier(new_daily_ts);

EXPECT_EQ(static_cast<int>(window_id.size()), 6);
EXPECT_EQ(window_id, "202201");
}
} // namespace ash::device_activity
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,6 @@ DeviceActivityClient::GetSaveStatusRequest() {

for (auto* use_case : GetUseCases()) {
private_computing::ActiveStatus status = use_case->GenerateActiveStatus();

if (status.has_use_case()) {
*request.add_active_status() = status;
}
Expand Down Expand Up @@ -457,6 +456,11 @@ void DeviceActivityClient::OnGetLastPingDatesStatusFetched(
device_active_use_case_ptr =
GetUseCasePtr(psm_rlwe::RlweUseCase::CROS_FRESNEL_28DAY_ACTIVE);
break;
case private_computing::PrivateComputingUseCase::
CROS_FRESNEL_CHURN_MONTHLY_COHORT:
device_active_use_case_ptr = GetUseCasePtr(
psm_rlwe::RlweUseCase::CROS_FRESNEL_CHURN_MONTHLY_COHORT);
break;
default:
LOG(ERROR) << "PSM use case is not supported yet.";
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "chromeos/ash/components/dbus/private_computing/fake_private_computing_client.h"
#include "chromeos/ash/components/dbus/private_computing/private_computing_service.pb.h"
#include "chromeos/ash/components/dbus/system_clock/system_clock_client.h"
#include "chromeos/ash/components/device_activity/churn_cohort_use_case_impl.h"
#include "chromeos/ash/components/device_activity/daily_use_case_impl.h"
#include "chromeos/ash/components/device_activity/device_active_use_case.h"
#include "chromeos/ash/components/device_activity/device_activity_controller.h"
Expand Down Expand Up @@ -252,6 +253,25 @@ class TwentyEightDayActiveUseCaseImplUnderTest
~TwentyEightDayActiveUseCaseImplUnderTest() override = default;
};

class ChurnCohortUseCaseImplUnderTest : public ChurnCohortUseCaseImpl {
public:
ChurnCohortUseCaseImplUnderTest(
PrefService* local_state,
const psm_rlwe::PrivateMembershipRlweClientRegressionTestData::TestCase&
test_case)
: ChurnCohortUseCaseImpl(
kFakePsmDeviceActiveSecret,
kFakeChromeParameters,
local_state,
std::make_unique<FakePsmDelegate>(test_case.ec_cipher_key(),
test_case.seed(),
GetPlaintextIds(test_case))) {}
ChurnCohortUseCaseImplUnderTest(const ChurnCohortUseCaseImplUnderTest&) =
delete;
ChurnCohortUseCaseImplUnderTest& operator=(
const ChurnCohortUseCaseImplUnderTest&) = delete;
~ChurnCohortUseCaseImplUnderTest() override = default;
};
} // namespace

// TODO(crbug/1317652): Refactor checking if current use case local pref is
Expand Down Expand Up @@ -414,6 +434,8 @@ class DeviceActivityClientTest : public testing::Test {
features::kDeviceActiveClientDailyCheckMembership,
features::kDeviceActiveClient28DayActiveCheckIn,
features::kDeviceActiveClient28DayActiveCheckMembership,
features::kDeviceActiveClientChurnCohortCheckIn,
features::kDeviceActiveClientChurnCohortCheckMembership,
},
GetPsmNonMemberTestCase(),
GetPrivateComputingRegressionTestCase(
Expand Down Expand Up @@ -479,6 +501,13 @@ class DeviceActivityClientTest : public testing::Test {
std::make_unique<TwentyEightDayActiveUseCaseImplUnderTest>(
&local_state_, psm_test_case));
}
if (base::FeatureList::IsEnabled(
features::kDeviceActiveClientChurnCohortCheckIn) ||
base::FeatureList::IsEnabled(
features::kDeviceActiveClientChurnCohortCheckMembership)) {
use_cases.push_back(std::make_unique<ChurnCohortUseCaseImplUnderTest>(
&local_state_, psm_test_case));
}

device_activity_client_ = std::make_unique<DeviceActivityClient>(
network_state_test_helper_->network_state_handler(),
Expand All @@ -493,6 +522,8 @@ class DeviceActivityClientTest : public testing::Test {
prefs::kDeviceActiveLastKnownDailyPingTimestamp);
local_state_.RemoveUserPref(
prefs::kDeviceActiveLastKnown28DayActivePingTimestamp);
local_state_.RemoveUserPref(
prefs::kDeviceActiveChurnCohortMonthlyPingTimestamp);
}

void SimulateOprfResponse(const std::string& serialized_response_body,
Expand Down Expand Up @@ -568,7 +599,7 @@ class DeviceActivityClientTest : public testing::Test {
};

TEST_F(DeviceActivityClientTest, ValidateActiveUseCases) {
EXPECT_EQ(static_cast<int>(device_activity_client_->GetUseCases().size()), 2);
EXPECT_EQ(static_cast<int>(device_activity_client_->GetUseCases().size()), 3);
}

TEST_F(DeviceActivityClientTest,
Expand Down

0 comments on commit efe65d1

Please sign in to comment.