1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chromecast/base/device_capabilities_impl.h"
6
7 #include <stddef.h>
8
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/logging.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/single_thread_task_runner.h"
16 #include "base/strings/string_util.h"
17 #include "base/threading/thread_task_runner_handle.h"
18 #include "base/values.h"
19 #include "chromecast/base/serializers.h"
20
21 namespace chromecast {
22
23 namespace {
24
25 const char kPathSeparator = '.';
26
27 // Determines if a key passed to Register() is valid. No path separators can
28 // be present in the key and it must not be empty.
IsValidRegisterKey(const std::string & key)29 bool IsValidRegisterKey(const std::string& key) {
30 return !key.empty() && key.find(kPathSeparator) == std::string::npos;
31 }
32
33 // Determines if a path is valid. This is true if there are no empty keys
34 // anywhere in the path (ex: .foo, foo., foo..bar are all invalid).
IsValidPath(const std::string & path)35 bool IsValidPath(const std::string& path) {
36 return !path.empty() && *path.begin() != kPathSeparator &&
37 *path.rbegin() != kPathSeparator &&
38 path.find("..") == std::string::npos;
39 }
40
41 // Given a path, gets the first key present in the path (ex: for path "foo.bar"
42 // return "foo").
GetFirstKey(const std::string & path)43 std::string GetFirstKey(const std::string& path) {
44 std::size_t length_to_first_separator = path.find(kPathSeparator);
45 return (length_to_first_separator == std::string::npos)
46 ? path
47 : path.substr(0, length_to_first_separator);
48 }
49
50 } // namespace
51
52 // static Default Capability Keys
53 const char DeviceCapabilities::kKeyAssistantSupported[] = "assistant_supported";
54 const char DeviceCapabilities::kKeyBluetoothSupported[] = "bluetooth_supported";
55 const char DeviceCapabilities::kKeyDisplaySupported[] = "display_supported";
56 const char DeviceCapabilities::kKeyHiResAudioSupported[] =
57 "hi_res_audio_supported";
58
59 // static
Create()60 std::unique_ptr<DeviceCapabilities> DeviceCapabilities::Create() {
61 return base::WrapUnique(new DeviceCapabilitiesImpl);
62 }
63
64 // static
CreateForTesting()65 std::unique_ptr<DeviceCapabilities> DeviceCapabilities::CreateForTesting() {
66 DeviceCapabilities* capabilities = new DeviceCapabilitiesImpl;
67 capabilities->SetCapability(kKeyBluetoothSupported,
68 std::make_unique<base::Value>(false));
69 capabilities->SetCapability(kKeyDisplaySupported,
70 std::make_unique<base::Value>(true));
71 capabilities->SetCapability(kKeyHiResAudioSupported,
72 std::make_unique<base::Value>(false));
73 capabilities->SetCapability(kKeyAssistantSupported,
74 std::make_unique<base::Value>(true));
75 return base::WrapUnique(capabilities);
76 }
77
CreateData()78 scoped_refptr<DeviceCapabilities::Data> DeviceCapabilities::CreateData() {
79 return base::WrapRefCounted(new Data);
80 }
81
CreateData(std::unique_ptr<const base::DictionaryValue> dictionary)82 scoped_refptr<DeviceCapabilities::Data> DeviceCapabilities::CreateData(
83 std::unique_ptr<const base::DictionaryValue> dictionary) {
84 DCHECK(dictionary.get());
85 return base::WrapRefCounted(new Data(std::move(dictionary)));
86 }
87
Validator(DeviceCapabilities * capabilities)88 DeviceCapabilities::Validator::Validator(DeviceCapabilities* capabilities)
89 : capabilities_(capabilities) {
90 DCHECK(capabilities);
91 }
92
SetPublicValidatedValue(const std::string & path,std::unique_ptr<base::Value> new_value) const93 void DeviceCapabilities::Validator::SetPublicValidatedValue(
94 const std::string& path,
95 std::unique_ptr<base::Value> new_value) const {
96 capabilities_->SetPublicValidatedValue(path, std::move(new_value));
97 }
98
SetPrivateValidatedValue(const std::string & path,std::unique_ptr<base::Value> new_value) const99 void DeviceCapabilities::Validator::SetPrivateValidatedValue(
100 const std::string& path,
101 std::unique_ptr<base::Value> new_value) const {
102 capabilities_->SetPrivateValidatedValue(path, std::move(new_value));
103 }
104
Data()105 DeviceCapabilities::Data::Data()
106 : dictionary_(new base::DictionaryValue),
107 json_string_(*SerializeToJson(*dictionary_)) {}
108
Data(std::unique_ptr<const base::DictionaryValue> dictionary)109 DeviceCapabilities::Data::Data(
110 std::unique_ptr<const base::DictionaryValue> dictionary)
111 : dictionary_(std::move(dictionary)),
112 json_string_(*SerializeToJson(*dictionary_)) {
113 DCHECK(dictionary_.get());
114 }
115
~Data()116 DeviceCapabilitiesImpl::Data::~Data() {}
117
ValidatorInfo(Validator * validator)118 DeviceCapabilitiesImpl::ValidatorInfo::ValidatorInfo(Validator* validator)
119 : validator_(validator), task_runner_(base::ThreadTaskRunnerHandle::Get()) {
120 DCHECK(validator_);
121 DCHECK(task_runner_.get());
122 }
123
~ValidatorInfo()124 DeviceCapabilitiesImpl::ValidatorInfo::~ValidatorInfo() {
125 // Check that ValidatorInfo is being destroyed on the same thread that it was
126 // constructed on.
127 DCHECK(task_runner_->BelongsToCurrentThread());
128 }
129
Validate(const std::string & path,std::unique_ptr<base::Value> proposed_value) const130 void DeviceCapabilitiesImpl::ValidatorInfo::Validate(
131 const std::string& path,
132 std::unique_ptr<base::Value> proposed_value) const {
133 // Check that we are running Validate on the same thread that ValidatorInfo
134 // was constructed on.
135 DCHECK(task_runner_->BelongsToCurrentThread());
136 validator_->Validate(path, std::move(proposed_value));
137 }
138
DeviceCapabilitiesImpl()139 DeviceCapabilitiesImpl::DeviceCapabilitiesImpl()
140 : all_data_(CreateData()),
141 public_data_(CreateData()),
142 task_runner_for_writes_(base::ThreadTaskRunnerHandle::Get()),
143 observer_list_(new base::ObserverListThreadSafe<Observer>) {
144 DCHECK(task_runner_for_writes_.get());
145 }
146
~DeviceCapabilitiesImpl()147 DeviceCapabilitiesImpl::~DeviceCapabilitiesImpl() {
148 // Make sure that any registered Validators have unregistered at this point
149 DCHECK(validator_map_.empty())
150 << "Some validators weren't properly unregistered: " << [this] {
151 std::vector<std::string> keys;
152 for (const auto& pair : validator_map_) {
153 keys.push_back(pair.first);
154 }
155 return base::JoinString(keys, ", ");
156 }();
157 // Make sure that all observers have been removed at this point
158 observer_list_->AssertEmpty();
159 }
160
Register(const std::string & key,Validator * validator)161 void DeviceCapabilitiesImpl::Register(const std::string& key,
162 Validator* validator) {
163 DCHECK(IsValidRegisterKey(key));
164 DCHECK(validator);
165
166 base::AutoLock auto_lock(validation_lock_);
167 // Check that a validator has not already been registered for this key
168 DCHECK_EQ(0u, validator_map_.count(key));
169 validator_map_[key] = std::make_unique<ValidatorInfo>(validator);
170 }
171
Unregister(const std::string & key,const Validator * validator)172 void DeviceCapabilitiesImpl::Unregister(const std::string& key,
173 const Validator* validator) {
174 base::AutoLock auto_lock(validation_lock_);
175 auto validator_it = validator_map_.find(key);
176 DCHECK(validator_it != validator_map_.end());
177 // Check that validator being unregistered matches the original for |key|.
178 // This prevents managers from accidentally unregistering incorrect
179 // validators.
180 DCHECK_EQ(validator, validator_it->second->validator());
181 // Check that validator is unregistering on same thread that it was
182 // registered on
183 DCHECK(validator_it->second->task_runner()->BelongsToCurrentThread());
184 validator_map_.erase(validator_it);
185 }
186
GetValidator(const std::string & key) const187 DeviceCapabilities::Validator* DeviceCapabilitiesImpl::GetValidator(
188 const std::string& key) const {
189 base::AutoLock auto_lock(validation_lock_);
190 auto validator_it = validator_map_.find(key);
191 return validator_it == validator_map_.end()
192 ? nullptr
193 : validator_it->second->validator();
194 }
195
BluetoothSupported() const196 bool DeviceCapabilitiesImpl::BluetoothSupported() const {
197 scoped_refptr<Data> data_ref = GetAllData();
198 bool bluetooth_supported = false;
199 bool found_key = data_ref->dictionary().GetBoolean(kKeyBluetoothSupported,
200 &bluetooth_supported);
201 DCHECK(found_key);
202 return bluetooth_supported;
203 }
204
DisplaySupported() const205 bool DeviceCapabilitiesImpl::DisplaySupported() const {
206 scoped_refptr<Data> data_ref = GetAllData();
207 bool display_supported = false;
208 bool found_key = data_ref->dictionary().GetBoolean(kKeyDisplaySupported,
209 &display_supported);
210 DCHECK(found_key);
211 return display_supported;
212 }
213
HiResAudioSupported() const214 bool DeviceCapabilitiesImpl::HiResAudioSupported() const {
215 scoped_refptr<Data> data_ref = GetAllData();
216 bool hi_res_audio_supported = false;
217 bool found_key = data_ref->dictionary().GetBoolean(kKeyHiResAudioSupported,
218 &hi_res_audio_supported);
219 DCHECK(found_key);
220 return hi_res_audio_supported;
221 }
222
AssistantSupported() const223 bool DeviceCapabilitiesImpl::AssistantSupported() const {
224 scoped_refptr<Data> data_ref = GetAllData();
225 bool assistant_supported = false;
226 bool found_key = data_ref->dictionary().GetBoolean(kKeyAssistantSupported,
227 &assistant_supported);
228 DCHECK(found_key);
229 return assistant_supported;
230 }
231
GetCapability(const std::string & path) const232 std::unique_ptr<base::Value> DeviceCapabilitiesImpl::GetCapability(
233 const std::string& path) const {
234 scoped_refptr<Data> data_ref = GetAllData();
235 const base::Value* value = nullptr;
236 bool found_path = data_ref->dictionary().Get(path, &value);
237 return found_path ? value->CreateDeepCopy() : std::unique_ptr<base::Value>();
238 }
239
GetAllData() const240 scoped_refptr<DeviceCapabilities::Data> DeviceCapabilitiesImpl::GetAllData()
241 const {
242 // Need to acquire lock here when copy constructing all_data_ otherwise we
243 // could concurrently be writing to scoped_refptr in SetPublicValidatedValue()
244 // or SetPrivateValidatedValue(), which could cause a bad scoped_refptr read.
245 base::AutoLock auto_lock(data_lock_);
246 return all_data_;
247 }
248
GetPublicData() const249 scoped_refptr<DeviceCapabilities::Data> DeviceCapabilitiesImpl::GetPublicData()
250 const {
251 // Need to acquire lock here when copy constructing public_data_ otherwise we
252 // could concurrently be writing to scoped_refptr in SetPublicValidatedValue()
253 // or SetPrivateValidatedValue(), which could cause a bad scoped_refptr read.
254 base::AutoLock auto_lock(data_lock_);
255 return public_data_;
256 }
257
SetCapability(const std::string & path,std::unique_ptr<base::Value> proposed_value)258 void DeviceCapabilitiesImpl::SetCapability(
259 const std::string& path,
260 std::unique_ptr<base::Value> proposed_value) {
261 DCHECK(proposed_value.get());
262 if (!IsValidPath(path)) {
263 LOG(DFATAL) << "Invalid capability path encountered for SetCapability()";
264 return;
265 }
266
267 {
268 base::AutoLock auto_lock(validation_lock_);
269 // Check for Validator registered under first key per the Register()
270 // interface.
271 auto validator_it = validator_map_.find(GetFirstKey(path));
272 if (validator_it != validator_map_.end()) {
273 // We do not want to post a task directly for the Validator's Validate()
274 // method here because if another thread is in the middle of unregistering
275 // that Validator, there will be an outstanding call to Validate() that
276 // occurs after it has unregistered. Since ValidatorInfo gets destroyed
277 // in Unregister() on same thread that validation should run on, we can
278 // post a task to the Validator's thread with weak_ptr. This way, if the
279 // Validator gets unregistered, the call to Validate will get skipped.
280 validator_it->second->task_runner()->PostTask(
281 FROM_HERE, base::BindOnce(&ValidatorInfo::Validate,
282 validator_it->second->AsWeakPtr(), path,
283 std::move(proposed_value)));
284 return;
285 }
286 }
287 // Since we are done checking for a registered Validator at this point, we
288 // can release the lock. All further member access will be for capabilities.
289 // By default, a capability without a validator will be public.
290 SetPublicValidatedValue(path, std::move(proposed_value));
291 }
292
MergeDictionary(const base::DictionaryValue & dict_value)293 void DeviceCapabilitiesImpl::MergeDictionary(
294 const base::DictionaryValue& dict_value) {
295 for (base::DictionaryValue::Iterator it(dict_value); !it.IsAtEnd();
296 it.Advance()) {
297 SetCapability(it.key(), it.value().CreateDeepCopy());
298 }
299 }
300
AddCapabilitiesObserver(Observer * observer)301 void DeviceCapabilitiesImpl::AddCapabilitiesObserver(Observer* observer) {
302 DCHECK(observer);
303 observer_list_->AddObserver(observer);
304 }
305
RemoveCapabilitiesObserver(Observer * observer)306 void DeviceCapabilitiesImpl::RemoveCapabilitiesObserver(Observer* observer) {
307 DCHECK(observer);
308 observer_list_->RemoveObserver(observer);
309 }
310
SetPublicValidatedValue(const std::string & path,std::unique_ptr<base::Value> new_value)311 void DeviceCapabilitiesImpl::SetPublicValidatedValue(
312 const std::string& path,
313 std::unique_ptr<base::Value> new_value) {
314 // All internal writes/modifications of capabilities must occur on same
315 // thread to avoid race conditions.
316 if (!task_runner_for_writes_->BelongsToCurrentThread()) {
317 task_runner_for_writes_->PostTask(
318 FROM_HERE,
319 base::BindOnce(&DeviceCapabilitiesImpl::SetPublicValidatedValue,
320 base::Unretained(this), path, std::move(new_value)));
321 return;
322 }
323
324 DCHECK(IsValidPath(path));
325 DCHECK(new_value.get());
326
327 // If the capability exists, it must be public (present in all_data_ and
328 // public_data_). We cannot change the privacy of an already existing
329 // capability.
330 bool is_private = all_data_->dictionary().HasKey(path) &&
331 !public_data_->dictionary().HasKey(path);
332 if (is_private) {
333 NOTREACHED() << "Cannot make a private capability '" << path << "' public.";
334 return;
335 }
336
337 // We don't need to acquire lock here when reading public_data_ because we
338 // know that all writes to public_data_ must occur serially on thread that
339 // we're on.
340 const base::Value* cur_value = nullptr;
341 bool capability_unchanged =
342 public_data_->dictionary().Get(path, &cur_value) &&
343 cur_value->Equals(new_value.get());
344 if (capability_unchanged) {
345 DVLOG(1) << "Ignoring unchanged public capability: " << path;
346 return;
347 }
348
349 // In this sequence, we create deep copies for both dictionaries, modify the
350 // copies, and then do a pointer swap. We do this to have minimal time spent
351 // in the data_lock_. If we were to lock and modify the capabilities
352 // dictionary directly, there may be expensive writes that block other
353 // threads.
354 scoped_refptr<Data> new_public_data = GenerateDataWithNewValue(
355 public_data_->dictionary(), path, new_value->CreateDeepCopy());
356 scoped_refptr<Data> new_data = GenerateDataWithNewValue(
357 all_data_->dictionary(), path, std::move(new_value));
358
359 {
360 base::AutoLock auto_lock(data_lock_);
361 // Using swap instead of assignment operator here because it's a little
362 // faster. Avoids an extra call to AddRef()/Release().
363 public_data_.swap(new_public_data);
364 all_data_.swap(new_data);
365 }
366
367 // Even though ObserverListThreadSafe notifications are always asynchronous
368 // (posts task even if to same thread), no locks should be held at this point
369 // in the code. This is just to be safe that no deadlocks occur if Observers
370 // call DeviceCapabilities methods in OnCapabilitiesChanged().
371 observer_list_->Notify(FROM_HERE, &Observer::OnCapabilitiesChanged, path);
372 }
373
SetPrivateValidatedValue(const std::string & path,std::unique_ptr<base::Value> new_value)374 void DeviceCapabilitiesImpl::SetPrivateValidatedValue(
375 const std::string& path,
376 std::unique_ptr<base::Value> new_value) {
377 // All internal writes/modifications of capabilities must occur on same
378 // thread to avoid race conditions.
379 if (!task_runner_for_writes_->BelongsToCurrentThread()) {
380 task_runner_for_writes_->PostTask(
381 FROM_HERE,
382 base::BindOnce(&DeviceCapabilitiesImpl::SetPrivateValidatedValue,
383 base::Unretained(this), path, std::move(new_value)));
384 return;
385 }
386
387 DCHECK(IsValidPath(path));
388 DCHECK(new_value.get());
389
390 // If the capability exists, it must be private (present in all_data_ only).
391 // We cannot change the privacy of an already existing capability.
392 bool is_public = public_data_->dictionary().HasKey(path);
393 if (is_public) {
394 NOTREACHED() << "Cannot make a public capability '" << path << "' private.";
395 return;
396 }
397
398 // We don't need to acquire lock here when reading all_data_ because we know
399 // that all writes to all_data_ must occur serially on thread that we're on.
400 const base::Value* cur_value = nullptr;
401 bool capability_unchanged = all_data_->dictionary().Get(path, &cur_value) &&
402 cur_value->Equals(new_value.get());
403 if (capability_unchanged) {
404 DVLOG(1) << "Ignoring unchanged capability: " << path;
405 return;
406 }
407
408 // In this sequence, we create a deep copy, modify the deep copy, and then
409 // do a pointer swap. We do this to have minimal time spent in the
410 // data_lock_. If we were to lock and modify the capabilities
411 // dictionary directly, there may be expensive writes that block other
412 // threads.
413 scoped_refptr<Data> new_data = GenerateDataWithNewValue(
414 all_data_->dictionary(), path, std::move(new_value));
415
416 {
417 base::AutoLock auto_lock(data_lock_);
418 // Using swap instead of assignment operator here because it's a little
419 // faster. Avoids an extra call to AddRef()/Release().
420 all_data_.swap(new_data);
421 }
422
423 // Even though ObserverListThreadSafe notifications are always asynchronous
424 // (posts task even if to same thread), no locks should be held at this point
425 // in the code. This is just to be safe that no deadlocks occur if Observers
426 // call DeviceCapabilities methods in OnCapabilitiesChanged().
427 observer_list_->Notify(FROM_HERE, &Observer::OnCapabilitiesChanged, path);
428 }
429
430 scoped_refptr<DeviceCapabilities::Data>
GenerateDataWithNewValue(const base::DictionaryValue & dict,const std::string & path,std::unique_ptr<base::Value> new_value)431 DeviceCapabilitiesImpl::GenerateDataWithNewValue(
432 const base::DictionaryValue& dict,
433 const std::string& path,
434 std::unique_ptr<base::Value> new_value) {
435 std::unique_ptr<base::DictionaryValue> dict_deep_copy(dict.CreateDeepCopy());
436 dict_deep_copy->Set(path, std::move(new_value));
437 return CreateData(std::move(dict_deep_copy));
438 }
439
440 } // namespace chromecast
441