Commit 62ce5fc8 authored by Wez's avatar Wez Committed by Commit Bot

[fuchsia] Implement |cdm_data_quota_bytes| enforcement.

Implement a soft quota check when the FuchsiaCdmManager is started, to
avoid persisted CDM data growing indefinitely.

Bug: 1148334, 1071393
Bug: b/162078899
Change-Id: I589ba4aa2d9ca3f1090e9081259c2ed014c29171
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2536459
Commit-Queue: Wez <wez@chromium.org>
Reviewed-by: default avatarDavid Dorwin <ddorwin@chromium.org>
Auto-Submit: Wez <wez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#828228}
parent 0c78d03c
......@@ -146,8 +146,8 @@ std::unique_ptr<media::FuchsiaCdmManager> CreateCdmManager() {
media::FuchsiaCdmManager::CreateKeySystemCallbackMap
create_key_system_callbacks;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableWidevine)) {
const auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kEnableWidevine)) {
create_key_system_callbacks.emplace(
kWidevineKeySystem,
base::BindRepeating(
......@@ -155,8 +155,7 @@ std::unique_ptr<media::FuchsiaCdmManager> CreateCdmManager() {
}
std::string playready_key_system =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kPlayreadyKeySystem);
command_line->GetSwitchValueASCII(switches::kPlayreadyKeySystem);
if (!playready_key_system.empty()) {
create_key_system_callbacks.emplace(
playready_key_system,
......@@ -165,12 +164,20 @@ std::unique_ptr<media::FuchsiaCdmManager> CreateCdmManager() {
}
std::string cdm_data_directory =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kCdmDataDirectory);
command_line->GetSwitchValueASCII(switches::kCdmDataDirectory);
base::Optional<uint64_t> cdm_data_quota_bytes;
if (command_line->HasSwitch(switches::kCdmDataQuotaBytes)) {
uint64_t value = 0;
CHECK(base::StringToUint64(
command_line->GetSwitchValueASCII(switches::kCdmDataQuotaBytes),
&value));
cdm_data_quota_bytes = value;
}
return std::make_unique<media::FuchsiaCdmManager>(
std::move(create_key_system_callbacks),
base::FilePath(cdm_data_directory));
base::FilePath(cdm_data_directory), cdm_data_quota_bytes);
}
} // namespace
......
......@@ -18,6 +18,7 @@
#include "content/public/common/main_function_params.h"
#include "fuchsia/base/legacymetrics_client.h"
#include "fuchsia/engine/browser/context_impl.h"
#include "fuchsia/engine/browser/media_resource_provider_service.h"
#include "fuchsia/engine/browser/web_engine_browser_context.h"
#include "fuchsia/engine/browser/web_engine_devtools_controller.h"
#include "fuchsia/engine/switches.h"
......@@ -100,6 +101,12 @@ void WebEngineBrowserMainParts::PreMainMessageLoopRun() {
legacy_metrics_client_->Start(kMetricsReportingInterval);
}
// Create the MediaResourceProviderService at startup rather than on-demand,
// to allow it to perform potentially expensive startup work in the
// background.
media_resource_provider_service_ =
std::make_unique<MediaResourceProviderService>();
// Quit the browser main loop when the Context connection is dropped.
context_binding_->set_error_handler([this](zx_status_t status) {
ZX_LOG_IF(ERROR, status != ZX_ERR_PEER_CLOSED, status)
......
......@@ -27,6 +27,8 @@ namespace cr_fuchsia {
class LegacyMetricsClient;
}
class MediaResourceProviderService;
class WebEngineBrowserMainParts : public content::BrowserMainParts {
public:
explicit WebEngineBrowserMainParts(
......@@ -40,6 +42,9 @@ class WebEngineBrowserMainParts : public content::BrowserMainParts {
WebEngineDevToolsController* devtools_controller() const {
return devtools_controller_.get();
}
MediaResourceProviderService* media_resource_provider_service() const {
return media_resource_provider_service_.get();
}
// content::BrowserMainParts overrides.
void PreMainMessageLoopRun() override;
......@@ -60,6 +65,8 @@ class WebEngineBrowserMainParts : public content::BrowserMainParts {
std::unique_ptr<fidl::Binding<fuchsia::web::Context>> context_binding_;
std::unique_ptr<WebEngineDevToolsController> devtools_controller_;
std::unique_ptr<cr_fuchsia::LegacyMetricsClient> legacy_metrics_client_;
std::unique_ptr<MediaResourceProviderService>
media_resource_provider_service_;
bool run_message_loop_ = true;
base::OnceClosure quit_closure_;
......
......@@ -134,7 +134,10 @@ void WebEngineContentBrowserClient::OverrideWebkitPrefs(
void WebEngineContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
content::RenderFrameHost* render_frame_host,
mojo::BinderMapWithContext<content::RenderFrameHost*>* map) {
PopulateFuchsiaFrameBinders(map, &media_resource_provider_service_);
MediaResourceProviderService* const provider =
main_parts_->media_resource_provider_service();
DCHECK(provider);
PopulateFuchsiaFrameBinders(map, provider);
}
void WebEngineContentBrowserClient::
......
......@@ -15,7 +15,6 @@
#include "base/macros.h"
#include "content/public/browser/content_browser_client.h"
#include "fuchsia/engine/browser/content_directory_loader_factory.h"
#include "fuchsia/engine/browser/media_resource_provider_service.h"
#include "mojo/public/cpp/bindings/binder_map.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
......@@ -78,8 +77,6 @@ class WebEngineContentBrowserClient : public content::ContentBrowserClient {
// Owned by content::BrowserMainLoop.
WebEngineBrowserMainParts* main_parts_;
MediaResourceProviderService media_resource_provider_service_;
DISALLOW_COPY_AND_ASSIGN(WebEngineContentBrowserClient);
};
......
......@@ -492,6 +492,12 @@ void ContextProviderImpl::Create(
kCdmDataPath);
launch_options.paths_to_transfer.push_back(base::PathToTransfer{
base::FilePath(kCdmDataPath), cdm_data_directory_channel.get()});
if (params.has_cdm_data_quota_bytes()) {
launch_command.AppendSwitchNative(
switches::kCdmDataQuotaBytes,
base::NumberToString(params.cdm_data_quota_bytes()));
}
}
bool enable_hardware_video_decoder =
......
......@@ -18,6 +18,7 @@ const char kUseLegacyMetricsService[] = "use-legacy-metrics-service";
const char kCorsExemptHeaders[] = "cors-exempt-headers";
const char kEnableCastStreamingReceiver[] = "enable-cast-streaming-receiver";
const char kCdmDataDirectory[] = "cdm-data-directory";
const char kCdmDataQuotaBytes[] = "cdm-data-quota-bytes";
const char kUseLegacyAndroidUserAgent[] = "use-legacy-android-user-agent";
const char kDataQuotaBytes[] = "data-quota-bytes";
......
......@@ -53,6 +53,9 @@ extern const char kEnableCastStreamingReceiver[];
// Data directory to be used for CDM user data.
extern const char kCdmDataDirectory[];
// Quota to apply to the CDM user data directory, in bytes.
extern const char kCdmDataQuotaBytes[];
// Enables reporting of an Android-like User Agent string.
extern const char kUseLegacyAndroidUserAgent[];
......
......@@ -39,9 +39,9 @@ source_set("unittests") {
]
sources = [
"fuchsia_cdm_manager_test.cc",
"fuchsia_cdm_manager_unittest.cc",
"mock_provision_fetcher.cc",
"mock_provision_fetcher.h",
"provisioning_fetcher_impl_test.cc",
"provisioning_fetcher_impl_unittest.cc",
]
}
......@@ -11,6 +11,7 @@
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/fuchsia/file_utils.h"
......@@ -28,6 +29,97 @@ namespace media {
namespace {
struct CdmDirectoryInfo {
base::FilePath path;
base::Time last_used;
uint64_t size_bytes;
};
// Enumerates all the files in the directory to determine its size and
// the most recent "last used" time.
// The implementation is based on base::ComputeDirectorySize(), with the
// addition of most-recently-modified calculation, and inclusion of directory
// node sizes toward the total.
CdmDirectoryInfo GetCdmDirectoryInfo(const base::FilePath& path) {
int64_t directory_size = 0;
base::Time last_used;
base::FileEnumerator enumerator(
path, true /* recursive */,
base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
while (!enumerator.Next().empty()) {
const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
if (info.GetSize() > 0)
directory_size += info.GetSize();
last_used = std::max(last_used, info.GetLastModifiedTime());
}
return {
.path = path,
.last_used = last_used,
.size_bytes = directory_size,
};
}
void ApplyCdmStorageQuota(base::FilePath cdm_data_path,
uint64_t cdm_data_quota_bytes) {
// TODO(crbug.com/1148334): Migrate to using a platform-provided quota
// mechanism to manage CDM storage.
VLOG(2) << "Enumerating CDM data directories.";
uint64_t directories_size_bytes = 0;
std::vector<CdmDirectoryInfo> directories_info;
// CDM storage consistes of per-origin directories, each containing one or
// more per-key-system sub-directories. Each per-origin-per-key-system
// directory is assumed to be independent of other CDM data.
base::FileEnumerator by_origin(cdm_data_path, false /* recursive */,
base::FileEnumerator::DIRECTORIES);
for (;;) {
const base::FilePath origin_directory = by_origin.Next();
if (origin_directory.empty())
break;
base::FileEnumerator by_key_system(origin_directory, false /* recursive */,
base::FileEnumerator::DIRECTORIES);
for (;;) {
const base::FilePath key_system_directory = by_key_system.Next();
if (key_system_directory.empty())
break;
directories_info.push_back(GetCdmDirectoryInfo(key_system_directory));
directories_size_bytes += directories_info.back().size_bytes;
}
}
if (directories_size_bytes <= cdm_data_quota_bytes)
return;
VLOG(1) << "Removing least recently accessed CDM data.";
// Enumerate directories starting with the least most recently "used",
// deleting them until the the total amount of CDM data is within quota.
std::sort(directories_info.begin(), directories_info.end(),
[](const CdmDirectoryInfo& lhs, const CdmDirectoryInfo& rhs) {
return lhs.last_used < rhs.last_used;
});
base::flat_set<base::FilePath> affected_origin_directories;
for (const auto& directory_info : directories_info) {
if (directories_size_bytes <= cdm_data_quota_bytes)
break;
VLOG(1) << "Removing " << directory_info.path;
base::DeletePathRecursively(directory_info.path);
affected_origin_directories.insert(directory_info.path.DirName());
DCHECK_GE(directories_size_bytes, directory_info.size_bytes);
directories_size_bytes -= directory_info.size_bytes;
}
// Enumerate all the origin directories that had sub-directories deleted,
// and delete any that are now empty.
for (const auto& origin_directory : affected_origin_directories) {
if (base::IsDirectoryEmpty(origin_directory))
base::DeleteFile(origin_directory);
}
}
std::string HexEncodeHash(const std::string& name) {
uint32_t hash = base::PersistentHash(name);
return base::HexEncode(&hash, sizeof(uint32_t));
......@@ -172,10 +264,21 @@ class FuchsiaCdmManager::KeySystemClient {
FuchsiaCdmManager::FuchsiaCdmManager(
CreateKeySystemCallbackMap create_key_system_callbacks_by_name,
base::FilePath cdm_data_path)
base::FilePath cdm_data_path,
base::Optional<uint64_t> cdm_data_quota_bytes)
: create_key_system_callbacks_by_name_(
std::move(create_key_system_callbacks_by_name)),
cdm_data_path_(std::move(cdm_data_path)) {}
cdm_data_path_(std::move(cdm_data_path)),
cdm_data_quota_bytes_(std::move(cdm_data_quota_bytes)),
storage_task_runner_(
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})) {
// To avoid potential for the CDM directory "cleanup" task removing
// CDM data directories that are in active use, the |storage_task_runner_| is
// sequenced, thereby ensuring cleanup completes before any CDM activities
// start.
if (cdm_data_quota_bytes)
ApplyCdmStorageQuota(cdm_data_path_, *cdm_data_quota_bytes);
}
FuchsiaCdmManager::~FuchsiaCdmManager() = default;
......@@ -189,9 +292,8 @@ void FuchsiaCdmManager::CreateAndProvision(
base::FilePath storage_path = GetStoragePath(key_system, origin);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&CreateStorageDirectory, storage_path),
storage_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&CreateStorageDirectory, storage_path),
base::BindOnce(&FuchsiaCdmManager::CreateCdm, weak_factory_.GetWeakPtr(),
key_system, std::move(create_fetcher_cb),
std::move(request), storage_path));
......
......@@ -15,6 +15,7 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/sequenced_task_runner.h"
#include "base/threading/thread_checker.h"
#include "media/base/provision_fetcher.h"
......@@ -35,9 +36,12 @@ class FuchsiaCdmManager {
using CreateKeySystemCallbackMap =
base::flat_map<std::string, CreateKeySystemCallback>;
// |cdm_data_quota_bytes| is currently only applied once, when the manager is
// created.
FuchsiaCdmManager(
CreateKeySystemCallbackMap create_key_system_callbacks_by_name,
base::FilePath cdm_data_path);
base::FilePath cdm_data_path,
base::Optional<uint64_t> cdm_data_quota_bytes);
~FuchsiaCdmManager();
......@@ -78,6 +82,10 @@ class FuchsiaCdmManager {
// A map of callbacks to create KeySystem channels indexed by their EME name.
const CreateKeySystemCallbackMap create_key_system_callbacks_by_name_;
const base::FilePath cdm_data_path_;
const base::Optional<uint64_t> cdm_data_quota_bytes_;
// Used for operations on the CDM data directory.
const scoped_refptr<base::SequencedTaskRunner> storage_task_runner_;
// A map of the active KeySystem clients indexed by their EME name. Entries
// in this map will be added on the first CreateAndProvision call for that
......
......@@ -12,6 +12,7 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
......@@ -76,7 +77,8 @@ class FuchsiaCdmManagerTest : public ::testing::Test {
FuchsiaCdmManagerTest() { EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); }
std::unique_ptr<FuchsiaCdmManager> CreateFuchsiaCdmManager(
std::vector<base::StringPiece> key_systems) {
std::vector<base::StringPiece> key_systems,
base::Optional<uint64_t> cdm_data_quota_bytes = base::nullopt) {
FuchsiaCdmManager::CreateKeySystemCallbackMap create_key_system_callbacks;
for (const base::StringPiece& name : key_systems) {
......@@ -86,7 +88,8 @@ class FuchsiaCdmManagerTest : public ::testing::Test {
base::Unretained(&key_system)));
}
return std::make_unique<FuchsiaCdmManager>(
std::move(create_key_system_callbacks), temp_dir_.GetPath());
std::move(create_key_system_callbacks), temp_dir_.GetPath(),
cdm_data_quota_bytes);
}
protected:
......@@ -284,5 +287,135 @@ TEST_F(FuchsiaCdmManagerTest, DifferentOriginDoNotShareDataStore) {
run_loop.Run();
}
void CreateDummyCdmDirectory(const base::FilePath& cdm_data_path,
base::StringPiece origin,
base::StringPiece key_system,
uint64_t size) {
const base::FilePath path = cdm_data_path.Append(origin).Append(key_system);
CHECK(base::CreateDirectory(path));
if (size) {
std::vector<uint8_t> zeroes(size);
CHECK(base::WriteFile(path.Append("zeroes"), zeroes));
}
}
// Verify that the least recently used CDM data directories are removed, until
// the quota is met. Also verify that old directories are removed regardless
// of whether they are empty or not.
TEST_F(FuchsiaCdmManagerTest, CdmDataQuotaBytes) {
constexpr uint64_t kTestQuotaBytes = 1024;
constexpr char kOriginDirectory1[] = "origin1";
constexpr char kOriginDirectory2[] = "origin2";
constexpr char kKeySystemDirectory1[] = "key_system1";
constexpr char kKeySystemDirectory2[] = "key_system2";
constexpr char kEmptyKeySystemDirectory[] = "empty_key_system";
// Create fake CDM data directories for two origins, each with two key
// systems, with each directory consuming 50% of the total quota, so that
// two directories must be removed to meet quota.
// Create least-recently-used directories & their contents.
const base::FilePath temp_path = temp_dir_.GetPath();
CreateDummyCdmDirectory(temp_path, kOriginDirectory1, kKeySystemDirectory1,
kTestQuotaBytes / 2);
CreateDummyCdmDirectory(temp_path, kOriginDirectory2, kKeySystemDirectory2,
kTestQuotaBytes / 2);
CreateDummyCdmDirectory(temp_path, kOriginDirectory1,
kEmptyKeySystemDirectory, 0);
// Sleep to account for coarse-grained filesystem timestamps.
base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
// Create the recently-used directories.
CreateDummyCdmDirectory(temp_path, kOriginDirectory1, kKeySystemDirectory2,
kTestQuotaBytes / 2);
CreateDummyCdmDirectory(temp_path, kOriginDirectory2, kKeySystemDirectory1,
kTestQuotaBytes / 2);
CreateDummyCdmDirectory(temp_path, kOriginDirectory2,
kEmptyKeySystemDirectory, 0);
// Create the CDM manager, to run the data directory quota enforcement.
std::unique_ptr<FuchsiaCdmManager> cdm_manager =
CreateFuchsiaCdmManager({}, kTestQuotaBytes);
// Use a CreateAndProvision() request as a proxy to wait for quota enforcement
// to finish being applied.
base::RunLoop run_loop;
drm::ContentDecryptionModulePtr cdm_ptr;
cdm_ptr.set_error_handler([&](zx_status_t status) {
EXPECT_EQ(status, ZX_ERR_NOT_FOUND);
run_loop.Quit();
});
cdm_manager->CreateAndProvision(
"com.key_system", url::Origin(),
base::BindRepeating(&CreateMockProvisionFetcher), cdm_ptr.NewRequest());
run_loop.Run();
EXPECT_FALSE(base::PathExists(
temp_path.Append(kOriginDirectory1).Append(kKeySystemDirectory1)));
EXPECT_FALSE(base::PathExists(
temp_path.Append(kOriginDirectory2).Append(kKeySystemDirectory2)));
EXPECT_TRUE(base::PathExists(
temp_path.Append(kOriginDirectory1).Append(kKeySystemDirectory2)));
EXPECT_TRUE(base::PathExists(
temp_path.Append(kOriginDirectory2).Append(kKeySystemDirectory1)));
// Empty directories are currently always treated as old, causing them all to
// be deleted if the CDM data directory exceeds its quota.
EXPECT_FALSE(base::PathExists(
temp_path.Append(kOriginDirectory1).Append(kEmptyKeySystemDirectory)));
EXPECT_FALSE(base::PathExists(
temp_path.Append(kOriginDirectory2).Append(kEmptyKeySystemDirectory)));
}
// Verify that if all key-system sub-directories for a given origin have been
// deleted then the origin's directory is also deleted.
TEST_F(FuchsiaCdmManagerTest, EmptyOriginDirectory) {
constexpr uint64_t kTestQuotaBytes = 1024;
constexpr char kInactiveOriginDirectory[] = "origin1";
constexpr char kActiveOriginDirectory[] = "origin2";
constexpr char kKeySystemDirectory1[] = "key_system1";
constexpr char kKeySystemDirectory2[] = "key_system2";
// Create dummy data for an inactive origin.
const base::FilePath temp_path = temp_dir_.GetPath();
CreateDummyCdmDirectory(temp_path, kInactiveOriginDirectory,
kKeySystemDirectory1, kTestQuotaBytes / 2);
CreateDummyCdmDirectory(temp_path, kInactiveOriginDirectory,
kKeySystemDirectory2, kTestQuotaBytes / 2);
// Sleep to account for coarse-grained filesystem timestamps.
base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
// Create dummy data for a recently-used, active origin.
CreateDummyCdmDirectory(temp_path, kActiveOriginDirectory,
kKeySystemDirectory2, kTestQuotaBytes);
// Create the CDM manager, to run the data directory quota enforcement.
std::unique_ptr<FuchsiaCdmManager> cdm_manager =
CreateFuchsiaCdmManager({}, kTestQuotaBytes);
// Use a CreateAndProvision() request as a proxy to wait for quota enforcement
// to finish being applied.
base::RunLoop run_loop;
drm::ContentDecryptionModulePtr cdm_ptr;
cdm_ptr.set_error_handler([&](zx_status_t status) {
EXPECT_EQ(status, ZX_ERR_NOT_FOUND);
run_loop.Quit();
});
cdm_manager->CreateAndProvision(
"com.key_system", url::Origin(),
base::BindRepeating(&CreateMockProvisionFetcher), cdm_ptr.NewRequest());
run_loop.Run();
EXPECT_FALSE(base::PathExists(temp_path.Append(kInactiveOriginDirectory)));
EXPECT_TRUE(base::PathExists(
temp_path.Append(kActiveOriginDirectory).Append(kKeySystemDirectory2)));
}
} // namespace
} // namespace media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment