123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- // 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/json/json_writer.h"
- #include "base/logging.h"
- #include "base/memory/ptr_util.h"
- #include "base/strings/string_util.h"
- #include "base/task/single_thread_task_runner.h"
- #include "base/threading/thread_task_runner_handle.h"
- #include "base/values.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, base::Value(false));
- capabilities->SetCapability(kKeyDisplaySupported, base::Value(true));
- capabilities->SetCapability(kKeyHiResAudioSupported, base::Value(false));
- capabilities->SetCapability(kKeyAssistantSupported, base::Value(true));
- return base::WrapUnique(capabilities);
- }
- scoped_refptr<DeviceCapabilities::Data> DeviceCapabilities::CreateData() {
- return base::WrapRefCounted(new Data);
- }
- scoped_refptr<DeviceCapabilities::Data> DeviceCapabilities::CreateData(
- base::Value dictionary) {
- DCHECK(dictionary.is_dict());
- 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,
- base::Value new_value) const {
- capabilities_->SetPublicValidatedValue(path, std::move(new_value));
- }
- void DeviceCapabilities::Validator::SetPrivateValidatedValue(
- const std::string& path,
- base::Value new_value) const {
- capabilities_->SetPrivateValidatedValue(path, std::move(new_value));
- }
- DeviceCapabilities::Data::Data() : dictionary_(base::Value::Type::DICTIONARY) {
- base::JSONWriter::Write(dictionary_, &json_string_);
- }
- DeviceCapabilities::Data::Data(base::Value dictionary)
- : dictionary_(std::move(dictionary)) {
- DCHECK(dictionary_.is_dict());
- base::JSONWriter::Write(dictionary_, &json_string_);
- }
- 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,
- 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();
- auto bluetooth_supported =
- data_ref->dictionary().FindBoolKey(kKeyBluetoothSupported);
- DCHECK(bluetooth_supported);
- return *bluetooth_supported;
- }
- bool DeviceCapabilitiesImpl::DisplaySupported() const {
- scoped_refptr<Data> data_ref = GetAllData();
- auto display_supported =
- data_ref->dictionary().FindBoolKey(kKeyDisplaySupported);
- DCHECK(display_supported);
- return *display_supported;
- }
- bool DeviceCapabilitiesImpl::HiResAudioSupported() const {
- scoped_refptr<Data> data_ref = GetAllData();
- auto hi_res_audio_supported =
- data_ref->dictionary().FindBoolKey(kKeyHiResAudioSupported);
- DCHECK(hi_res_audio_supported);
- return *hi_res_audio_supported;
- }
- bool DeviceCapabilitiesImpl::AssistantSupported() const {
- scoped_refptr<Data> data_ref = GetAllData();
- auto assistant_supported =
- data_ref->dictionary().FindBoolKey(kKeyAssistantSupported);
- DCHECK(assistant_supported);
- return *assistant_supported;
- }
- base::Value DeviceCapabilitiesImpl::GetCapability(
- const std::string& path) const {
- scoped_refptr<Data> data_ref = GetAllData();
- const base::Value* value = data_ref->dictionary().FindPath(path);
- return value ? value->Clone() : 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,
- base::Value proposed_value) {
- 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::Value& dict_value) {
- DCHECK(dict_value.is_dict());
- for (const auto kv : dict_value.DictItems()) {
- SetCapability(kv.first, kv.second.Clone());
- }
- }
- 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,
- 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));
- // 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().FindKey(path) &&
- !public_data_->dictionary().FindKey(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 = public_data_->dictionary().FindPath(path);
- bool capability_unchanged = cur_value && *cur_value == new_value;
- 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.Clone());
- 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,
- 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));
- // If the capability exists, it must be private (present in all_data_ only).
- // We cannot change the privacy of an already existing capability.
- const auto* is_public = public_data_->dictionary().FindKey(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 = all_data_->dictionary().FindPath(path);
- bool capability_unchanged = cur_value && *cur_value == new_value;
- 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::Value& dict,
- const std::string& path,
- base::Value new_value) {
- base::Value dict_deep_copy(dict.Clone());
- dict_deep_copy.SetPath(path, std::move(new_value));
- return CreateData(std::move(dict_deep_copy));
- }
- } // namespace chromecast
|