// 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 #include #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::Create() { return base::WrapUnique(new DeviceCapabilitiesImpl); } // static std::unique_ptr 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::CreateData() { return base::WrapRefCounted(new Data); } scoped_refptr 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) { 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 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(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_ref = GetAllData(); auto bluetooth_supported = data_ref->dictionary().FindBoolKey(kKeyBluetoothSupported); DCHECK(bluetooth_supported); return *bluetooth_supported; } bool DeviceCapabilitiesImpl::DisplaySupported() const { scoped_refptr data_ref = GetAllData(); auto display_supported = data_ref->dictionary().FindBoolKey(kKeyDisplaySupported); DCHECK(display_supported); return *display_supported; } bool DeviceCapabilitiesImpl::HiResAudioSupported() const { scoped_refptr 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_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_ref = GetAllData(); const base::Value* value = data_ref->dictionary().FindPath(path); return value ? value->Clone() : base::Value(); } scoped_refptr 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 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 new_public_data = GenerateDataWithNewValue( public_data_->dictionary(), path, new_value.Clone()); scoped_refptr 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 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 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