blob: 13ff14e835798135d4fb12f1deacdfc49501dac1 [file] [log] [blame]
// Copyright 2015 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 "chromecast/base/device_capabilities_impl.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "chromecast/base/serializers.h"
namespace chromecast {
namespace {
const char kPathSeparator = '.';
// Determines if a key passed to Register() is valid. No path separators can
// be present in the key and it must not be empty.
bool IsValidRegisterKey(const std::string& key) {
return !key.empty() && key.find(kPathSeparator) == std::string::npos;
}
// Determines if a path is valid. This is true if there are no empty keys
// anywhere in the path (ex: .foo, foo., foo..bar are all invalid).
bool IsValidPath(const std::string& path) {
return !path.empty() && *path.begin() != kPathSeparator &&
*path.rbegin() != kPathSeparator &&
path.find("..") == std::string::npos;
}
// Given a path, gets the first key present in the path (ex: for path "foo.bar"
// return "foo").
std::string GetFirstKey(const std::string& path) {
std::size_t length_to_first_separator = path.find(kPathSeparator);
return (length_to_first_separator == std::string::npos)
? path
: path.substr(0, length_to_first_separator);
}
} // namespace
// static Default Capability Keys
const char DeviceCapabilities::kKeyAssistantSupported[] = "assistant_supported";
const char DeviceCapabilities::kKeyBluetoothSupported[] = "bluetooth_supported";
const char DeviceCapabilities::kKeyDisplaySupported[] = "display_supported";
const char DeviceCapabilities::kKeyHiResAudioSupported[] =
"hi_res_audio_supported";
// static
std::unique_ptr<DeviceCapabilities> DeviceCapabilities::Create() {
return base::WrapUnique(new DeviceCapabilitiesImpl);
}
// static
std::unique_ptr<DeviceCapabilities> DeviceCapabilities::CreateForTesting() {
DeviceCapabilities* capabilities = new DeviceCapabilitiesImpl;
capabilities->SetCapability(kKeyBluetoothSupported,
std::make_unique<base::Value>(false));
capabilities->SetCapability(kKeyDisplaySupported,
std::make_unique<base::Value>(true));
capabilities->SetCapability(kKeyHiResAudioSupported,
std::make_unique<base::Value>(false));
capabilities->SetCapability(kKeyAssistantSupported,
std::make_unique<base::Value>(true));
return base::WrapUnique(capabilities);
}
scoped_refptr<DeviceCapabilities::Data> DeviceCapabilities::CreateData() {
return base::WrapRefCounted(new Data);
}
scoped_refptr<DeviceCapabilities::Data> DeviceCapabilities::CreateData(
std::unique_ptr<const base::DictionaryValue> dictionary) {
DCHECK(dictionary.get());
return base::WrapRefCounted(new Data(std::move(dictionary)));
}
DeviceCapabilities::Validator::Validator(DeviceCapabilities* capabilities)
: capabilities_(capabilities) {
DCHECK(capabilities);
}
void DeviceCapabilities::Validator::SetPublicValidatedValue(
const std::string& path,
std::unique_ptr<base::Value> new_value) const {
capabilities_->SetPublicValidatedValue(path, std::move(new_value));
}
void DeviceCapabilities::Validator::SetPrivateValidatedValue(
const std::string& path,
std::unique_ptr<base::Value> new_value) const {
capabilities_->SetPrivateValidatedValue(path, std::move(new_value));
}
DeviceCapabilities::Data::Data()
: dictionary_(new base::DictionaryValue),
json_string_(*SerializeToJson(*dictionary_)) {}
DeviceCapabilities::Data::Data(
std::unique_ptr<const base::DictionaryValue> dictionary)
: dictionary_(std::move(dictionary)),
json_string_(*SerializeToJson(*dictionary_)) {
DCHECK(dictionary_.get());
}
DeviceCapabilitiesImpl::Data::~Data() {}
DeviceCapabilitiesImpl::ValidatorInfo::ValidatorInfo(Validator* validator)
: validator_(validator), task_runner_(base::ThreadTaskRunnerHandle::Get()) {
DCHECK(validator_);
DCHECK(task_runner_.get());
}
DeviceCapabilitiesImpl::ValidatorInfo::~ValidatorInfo() {
// Check that ValidatorInfo is being destroyed on the same thread that it was
// constructed on.
DCHECK(task_runner_->BelongsToCurrentThread());
}
void DeviceCapabilitiesImpl::ValidatorInfo::Validate(
const std::string& path,
std::unique_ptr<base::Value> proposed_value) const {
// Check that we are running Validate on the same thread that ValidatorInfo
// was constructed on.
DCHECK(task_runner_->BelongsToCurrentThread());
validator_->Validate(path, std::move(proposed_value));
}
DeviceCapabilitiesImpl::DeviceCapabilitiesImpl()
: all_data_(CreateData()),
public_data_(CreateData()),
task_runner_for_writes_(base::ThreadTaskRunnerHandle::Get()),
observer_list_(new base::ObserverListThreadSafe<Observer>) {
DCHECK(task_runner_for_writes_.get());
}
DeviceCapabilitiesImpl::~DeviceCapabilitiesImpl() {
// Make sure that any registered Validators have unregistered at this point
DCHECK(validator_map_.empty())
<< "Some validators weren't properly unregistered: " << [this] {
std::vector<std::string> keys;
for (const auto& pair : validator_map_) {
keys.push_back(pair.first);
}
return base::JoinString(keys, ", ");
}();
// Make sure that all observers have been removed at this point
observer_list_->AssertEmpty();
}
void DeviceCapabilitiesImpl::Register(const std::string& key,
Validator* validator) {
DCHECK(IsValidRegisterKey(key));
DCHECK(validator);
base::AutoLock auto_lock(validation_lock_);
// Check that a validator has not already been registered for this key
DCHECK_EQ(0u, validator_map_.count(key));
validator_map_[key] = std::make_unique<ValidatorInfo>(validator);
}
void DeviceCapabilitiesImpl::Unregister(const std::string& key,
const Validator* validator) {
base::AutoLock auto_lock(validation_lock_);
auto validator_it = validator_map_.find(key);
DCHECK(validator_it != validator_map_.end());
// Check that validator being unregistered matches the original for |key|.
// This prevents managers from accidentally unregistering incorrect
// validators.
DCHECK_EQ(validator, validator_it->second->validator());
// Check that validator is unregistering on same thread that it was
// registered on
DCHECK(validator_it->second->task_runner()->BelongsToCurrentThread());
validator_map_.erase(validator_it);
}
DeviceCapabilities::Validator* DeviceCapabilitiesImpl::GetValidator(
const std::string& key) const {
base::AutoLock auto_lock(validation_lock_);
auto validator_it = validator_map_.find(key);
return validator_it == validator_map_.end()
? nullptr
: validator_it->second->validator();
}
bool DeviceCapabilitiesImpl::BluetoothSupported() const {
scoped_refptr<Data> data_ref = GetAllData();
bool bluetooth_supported = false;
bool found_key = data_ref->dictionary().GetBoolean(kKeyBluetoothSupported,
&bluetooth_supported);
DCHECK(found_key);
return bluetooth_supported;
}
bool DeviceCapabilitiesImpl::DisplaySupported() const {
scoped_refptr<Data> data_ref = GetAllData();
bool display_supported = false;
bool found_key = data_ref->dictionary().GetBoolean(kKeyDisplaySupported,
&display_supported);
DCHECK(found_key);
return display_supported;
}
bool DeviceCapabilitiesImpl::HiResAudioSupported() const {
scoped_refptr<Data> data_ref = GetAllData();
bool hi_res_audio_supported = false;
bool found_key = data_ref->dictionary().GetBoolean(kKeyHiResAudioSupported,
&hi_res_audio_supported);
DCHECK(found_key);
return hi_res_audio_supported;
}
bool DeviceCapabilitiesImpl::AssistantSupported() const {
scoped_refptr<Data> data_ref = GetAllData();
bool assistant_supported = false;
bool found_key = data_ref->dictionary().GetBoolean(kKeyAssistantSupported,
&assistant_supported);
DCHECK(found_key);
return assistant_supported;
}
std::unique_ptr<base::Value> DeviceCapabilitiesImpl::GetCapability(
const std::string& path) const {
scoped_refptr<Data> data_ref = GetAllData();
const base::Value* value = nullptr;
bool found_path = data_ref->dictionary().Get(path, &value);
return found_path ? value->CreateDeepCopy() : std::unique_ptr<base::Value>();
}
scoped_refptr<DeviceCapabilities::Data> DeviceCapabilitiesImpl::GetAllData()
const {
// Need to acquire lock here when copy constructing all_data_ otherwise we
// could concurrently be writing to scoped_refptr in SetPublicValidatedValue()
// or SetPrivateValidatedValue(), which could cause a bad scoped_refptr read.
base::AutoLock auto_lock(data_lock_);
return all_data_;
}
scoped_refptr<DeviceCapabilities::Data> DeviceCapabilitiesImpl::GetPublicData()
const {
// Need to acquire lock here when copy constructing public_data_ otherwise we
// could concurrently be writing to scoped_refptr in SetPublicValidatedValue()
// or SetPrivateValidatedValue(), which could cause a bad scoped_refptr read.
base::AutoLock auto_lock(data_lock_);
return public_data_;
}
void DeviceCapabilitiesImpl::SetCapability(
const std::string& path,
std::unique_ptr<base::Value> proposed_value) {
DCHECK(proposed_value.get());
if (!IsValidPath(path)) {
LOG(DFATAL) << "Invalid capability path encountered for SetCapability()";
return;
}
{
base::AutoLock auto_lock(validation_lock_);
// Check for Validator registered under first key per the Register()
// interface.
auto validator_it = validator_map_.find(GetFirstKey(path));
if (validator_it != validator_map_.end()) {
// We do not want to post a task directly for the Validator's Validate()
// method here because if another thread is in the middle of unregistering
// that Validator, there will be an outstanding call to Validate() that
// occurs after it has unregistered. Since ValidatorInfo gets destroyed
// in Unregister() on same thread that validation should run on, we can
// post a task to the Validator's thread with weak_ptr. This way, if the
// Validator gets unregistered, the call to Validate will get skipped.
validator_it->second->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ValidatorInfo::Validate,
validator_it->second->AsWeakPtr(), path,
std::move(proposed_value)));
return;
}
}
// Since we are done checking for a registered Validator at this point, we
// can release the lock. All further member access will be for capabilities.
// By default, a capability without a validator will be public.
SetPublicValidatedValue(path, std::move(proposed_value));
}
void DeviceCapabilitiesImpl::MergeDictionary(
const base::DictionaryValue& dict_value) {
for (base::DictionaryValue::Iterator it(dict_value); !it.IsAtEnd();
it.Advance()) {
SetCapability(it.key(), it.value().CreateDeepCopy());
}
}
void DeviceCapabilitiesImpl::AddCapabilitiesObserver(Observer* observer) {
DCHECK(observer);
observer_list_->AddObserver(observer);
}
void DeviceCapabilitiesImpl::RemoveCapabilitiesObserver(Observer* observer) {
DCHECK(observer);
observer_list_->RemoveObserver(observer);
}
void DeviceCapabilitiesImpl::SetPublicValidatedValue(
const std::string& path,
std::unique_ptr<base::Value> new_value) {
// All internal writes/modifications of capabilities must occur on same
// thread to avoid race conditions.
if (!task_runner_for_writes_->BelongsToCurrentThread()) {
task_runner_for_writes_->PostTask(
FROM_HERE,
base::BindOnce(&DeviceCapabilitiesImpl::SetPublicValidatedValue,
base::Unretained(this), path, std::move(new_value)));
return;
}
DCHECK(IsValidPath(path));
DCHECK(new_value.get());
// If the capability exists, it must be public (present in all_data_ and
// public_data_). We cannot change the privacy of an already existing
// capability.
bool is_private = all_data_->dictionary().HasKey(path) &&
!public_data_->dictionary().HasKey(path);
if (is_private) {
NOTREACHED() << "Cannot make a private capability '" << path << "' public.";
return;
}
// We don't need to acquire lock here when reading public_data_ because we
// know that all writes to public_data_ must occur serially on thread that
// we're on.
const base::Value* cur_value = nullptr;
bool capability_unchanged =
public_data_->dictionary().Get(path, &cur_value) &&
cur_value->Equals(new_value.get());
if (capability_unchanged) {
DVLOG(1) << "Ignoring unchanged public capability: " << path;
return;
}
// In this sequence, we create deep copies for both dictionaries, modify the
// copies, and then do a pointer swap. We do this to have minimal time spent
// in the data_lock_. If we were to lock and modify the capabilities
// dictionary directly, there may be expensive writes that block other
// threads.
scoped_refptr<Data> new_public_data = GenerateDataWithNewValue(
public_data_->dictionary(), path, new_value->CreateDeepCopy());
scoped_refptr<Data> new_data = GenerateDataWithNewValue(
all_data_->dictionary(), path, std::move(new_value));
{
base::AutoLock auto_lock(data_lock_);
// Using swap instead of assignment operator here because it's a little
// faster. Avoids an extra call to AddRef()/Release().
public_data_.swap(new_public_data);
all_data_.swap(new_data);
}
// Even though ObserverListThreadSafe notifications are always asynchronous
// (posts task even if to same thread), no locks should be held at this point
// in the code. This is just to be safe that no deadlocks occur if Observers
// call DeviceCapabilities methods in OnCapabilitiesChanged().
observer_list_->Notify(FROM_HERE, &Observer::OnCapabilitiesChanged, path);
}
void DeviceCapabilitiesImpl::SetPrivateValidatedValue(
const std::string& path,
std::unique_ptr<base::Value> new_value) {
// All internal writes/modifications of capabilities must occur on same
// thread to avoid race conditions.
if (!task_runner_for_writes_->BelongsToCurrentThread()) {
task_runner_for_writes_->PostTask(
FROM_HERE,
base::BindOnce(&DeviceCapabilitiesImpl::SetPrivateValidatedValue,
base::Unretained(this), path, std::move(new_value)));
return;
}
DCHECK(IsValidPath(path));
DCHECK(new_value.get());
// If the capability exists, it must be private (present in all_data_ only).
// We cannot change the privacy of an already existing capability.
bool is_public = public_data_->dictionary().HasKey(path);
if (is_public) {
NOTREACHED() << "Cannot make a public capability '" << path << "' private.";
return;
}
// We don't need to acquire lock here when reading all_data_ because we know
// that all writes to all_data_ must occur serially on thread that we're on.
const base::Value* cur_value = nullptr;
bool capability_unchanged = all_data_->dictionary().Get(path, &cur_value) &&
cur_value->Equals(new_value.get());
if (capability_unchanged) {
DVLOG(1) << "Ignoring unchanged capability: " << path;
return;
}
// In this sequence, we create a deep copy, modify the deep copy, and then
// do a pointer swap. We do this to have minimal time spent in the
// data_lock_. If we were to lock and modify the capabilities
// dictionary directly, there may be expensive writes that block other
// threads.
scoped_refptr<Data> new_data = GenerateDataWithNewValue(
all_data_->dictionary(), path, std::move(new_value));
{
base::AutoLock auto_lock(data_lock_);
// Using swap instead of assignment operator here because it's a little
// faster. Avoids an extra call to AddRef()/Release().
all_data_.swap(new_data);
}
// Even though ObserverListThreadSafe notifications are always asynchronous
// (posts task even if to same thread), no locks should be held at this point
// in the code. This is just to be safe that no deadlocks occur if Observers
// call DeviceCapabilities methods in OnCapabilitiesChanged().
observer_list_->Notify(FROM_HERE, &Observer::OnCapabilitiesChanged, path);
}
scoped_refptr<DeviceCapabilities::Data>
DeviceCapabilitiesImpl::GenerateDataWithNewValue(
const base::DictionaryValue& dict,
const std::string& path,
std::unique_ptr<base::Value> new_value) {
std::unique_ptr<base::DictionaryValue> dict_deep_copy(dict.CreateDeepCopy());
dict_deep_copy->Set(path, std::move(new_value));
return CreateData(std::move(dict_deep_copy));
}
} // namespace chromecast