Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(757)

Unified Diff: chrome/browser/media/router/discovery/dial/device_description_service.cc

Issue 2701633002: [Media Router] Add DialMediaSinkService and DeviceDescriptionService (Closed)
Patch Set: resolve code review comments from Mark and Derek Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/media/router/discovery/dial/device_description_service.cc
diff --git a/chrome/browser/media/router/discovery/dial/device_description_service.cc b/chrome/browser/media/router/discovery/dial/device_description_service.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d837be725575d6ecd7e7439a2b06c87a8316191e
--- /dev/null
+++ b/chrome/browser/media/router/discovery/dial/device_description_service.cc
@@ -0,0 +1,298 @@
+// Copyright 2017 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 "chrome/browser/media/router/discovery/dial/device_description_service.h"
+
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "chrome/browser/media/router/discovery/dial/device_description_fetcher.h"
+#include "chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.h"
+#include "net/base/ip_address.h"
+#include "url/gurl.h"
+
+namespace {
+
+enum ErrorType {
+ NONE,
+ MISSING_UNIQUE_ID,
+ MISSING_FRIENDLY_NAME,
+ MISSING_APP_URL,
+ INVALID_APP_URL
+};
+
+// How long to cache a device description.
+const int kDeviceDescriptionCacheTimeMillis = 30 * 60 * 1000;
mark a. foltz 2017/04/18 18:16:26 Per my earlier comment this could be much longer I
zhaobin 2017/04/21 23:12:58 Done.
+
+// Time interval to clean up cache entries.
+const int kCacheCleanUpTimeoutMins = 30;
+
+// Maximum size on the number of cached entries.
+const int kCacheLimit = 256;
mark a. foltz 2017/04/18 18:16:26 kCacheMaxEntries ?
zhaobin 2017/04/21 23:12:57 Done.
+
+#ifndef NDEBUG
+// Replaces "<element_name>content</element_name>" with
+// "<element_name>***</element_name>"
+void Scrub(const std::string& element_name, std::string* xml_text) {
+ size_t pos = xml_text->find("<" + element_name + ">");
+ size_t end_pos = xml_text->find("</" + element_name + ">");
+
+ if (pos == std::string::npos || end_pos == std::string::npos)
+ return;
+
+ size_t start_pos = pos + element_name.length() + 2;
+ if (end_pos > start_pos)
+ xml_text->replace(start_pos, end_pos - start_pos, "***");
+}
+
+// Removes unique identifiers from the device description.
+// |xml_text|: The device description XML.
+// Returns original XML text if <UDN> or <serialNumber> field does not exist.
+std::string ScrubDeviceDescriptionXml(const std::string& xml_text) {
+ std::string scrubbed_xml(xml_text);
+ Scrub("UDN", &scrubbed_xml);
+ Scrub("serialNumber", &scrubbed_xml);
+ return scrubbed_xml;
+}
+
+std::string CachedDeviceDescriptionToString(
+ const media_router::DeviceDescriptionService::CacheEntry& cached_data) {
+ std::stringstream ss;
mark a. foltz 2017/04/18 18:16:26 #ifndef NDEBUG #include <sstream> #endif
zhaobin 2017/04/21 23:12:58 Done.
+ ss << "CachedDialDeviceDescription [unique_id]: "
+ << cached_data.description_data.unique_id
+ << " [friendly_name]: " << cached_data.description_data.friendly_name
+ << " [model_name]: " << cached_data.description_data.model_name
+ << " [app_url]: " << cached_data.description_data.app_url
+ << " [expire_time]: " << cached_data.expire_time
+ << " [config_id]: " << cached_data.config_id;
+
+ return ss.str();
+}
+#endif
+
+bool IsValidAppUrl(const GURL& app_url, const std::string& ip_address) {
+ return app_url.SchemeIs(url::kHttpScheme) && app_url.host() == ip_address;
+}
+
+// Checks mandatory fields. Returns ErrorType::NONE if device description is
+// valid; Otherwise returns specific error type.
+ErrorType ValidateParsedDeviceDescription(
+ const std::string& expected_ip_address,
+ const media_router::ParsedDialDeviceDescription& description_data) {
+ if (description_data.unique_id.empty()) {
+ return ErrorType::MISSING_UNIQUE_ID;
+ }
+ if (description_data.friendly_name.empty()) {
+ return ErrorType::MISSING_FRIENDLY_NAME;
+ }
+ if (!description_data.app_url.is_valid()) {
+ return ErrorType::MISSING_APP_URL;
+ }
+ if (!IsValidAppUrl(description_data.app_url, expected_ip_address)) {
+ return ErrorType::INVALID_APP_URL;
+ }
+ return ErrorType::NONE;
+}
+
+} // namespace
+
+namespace media_router {
+
+DeviceDescriptionService::DeviceDescriptionService(
+ const DeviceDescriptionParseSuccessCallback& success_cb,
+ const DeviceDescriptionParseErrorCallback& error_cb)
+ : success_cb_(success_cb), error_cb_(error_cb) {}
+
+DeviceDescriptionService::~DeviceDescriptionService() = default;
+
+void DeviceDescriptionService::GetDeviceDescriptions(
+ const std::vector<DialDeviceData>& devices,
+ net::URLRequestContextGetter* request_context) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::map<std::string, std::unique_ptr<DeviceDescriptionFetcher>>
+ existing_fetcher_map;
+ for (auto& fetcher_it : device_description_fetcher_map_) {
+ std::string device_label = fetcher_it.first;
+ const auto& device_it =
+ std::find_if(devices.begin(), devices.end(),
+ [&device_label](const DialDeviceData& device_data) {
+ return device_data.label() == device_label;
+ });
+ if (device_it == devices.end() ||
+ device_it->device_description_url() ==
+ fetcher_it.second->device_description_url()) {
+ existing_fetcher_map.insert(
+ std::make_pair(device_label, std::move(fetcher_it.second)));
+ }
+ }
+
+ // Remove all out dated fetchers.
+ device_description_fetcher_map_ = std::move(existing_fetcher_map);
+
+ for (const auto& device_data : devices) {
+ auto* cache_entry = CheckAndUpdateCache(device_data);
+ if (cache_entry) {
+ // Get device description from cache.
+ success_cb_.Run(device_data, cache_entry->description_data);
+ continue;
+ }
+
+ FetchDeviceDescription(device_data, request_context);
+ }
+
+ // Start a clean up timer.
+ if (!clean_up_timer_) {
+ clean_up_timer_.reset(new base::RepeatingTimer());
+ clean_up_timer_->Start(
+ FROM_HERE, base::TimeDelta::FromMinutes(kCacheCleanUpTimeoutMins), this,
+ &DeviceDescriptionService::CleanUpCacheEntries);
+ }
+}
+
+void DeviceDescriptionService::CleanUpCacheEntries() {
+ // TODO(zhaobin): instead of removing them directly, issue GET request to
+ // check if expired device descriptions are still alive.
mark a. foltz 2017/04/18 18:16:26 This is probably okay - if the device is found thr
zhaobin 2017/04/21 23:12:58 Done.
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::Time now = GetNow();
+
+ DVLOG(2) << "Before clean up, cache size: " << description_map_.size();
+ base::EraseIf(description_map_,
+ [&now](const std::pair<std::string, CacheEntry>& cache_pair) {
+ return cache_pair.second.expire_time < now;
+ });
+ DVLOG(2) << "After clean up, cache size: " << description_map_.size();
mark a. foltz 2017/04/18 18:16:26 If the cache is emptied and there are no pending r
zhaobin 2017/04/21 23:12:58 Done.
+}
+
+base::Time DeviceDescriptionService::GetNow() {
mark a. foltz 2017/04/18 18:16:26 Is this for unit testing?
zhaobin 2017/04/21 23:12:58 Yes.
+ return base::Time::Now();
+}
+
+void DeviceDescriptionService::FetchDeviceDescription(
+ const DialDeviceData& device_data,
+ net::URLRequestContextGetter* request_context) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto device_description_fetcher = base::MakeUnique<DeviceDescriptionFetcher>(
+ device_data.device_description_url(), request_context,
+ base::BindOnce(
+ &DeviceDescriptionService::OnDeviceDescriptionFetchComplete,
+ base::Unretained(this), device_data),
+ base::BindOnce(&DeviceDescriptionService::OnDeviceDescriptionFetchError,
+ base::Unretained(this), device_data));
+
+ device_description_fetcher->Start();
+ device_description_fetcher_map_.insert(std::make_pair(
+ device_data.label(), std::move(device_description_fetcher)));
+}
+
+const DeviceDescriptionService::CacheEntry*
+DeviceDescriptionService::CheckAndUpdateCache(
+ const DialDeviceData& device_data) {
+ const auto& it = description_map_.find(device_data.label());
+ if (it == description_map_.end())
+ return nullptr;
+
+ // If the entry's config_id does not match, or it has expired, remove it.
+ if (it->second.config_id != device_data.config_id() ||
+ GetNow() >= it->second.expire_time) {
+ DVLOG(2) << "Removing invalid entry " << it->first;
+ description_map_.erase(it);
+ return nullptr;
+ }
+
+ // Entry is valid.
+ return &it->second;
+}
+
+void DeviceDescriptionService::OnParsedDeviceDescription(
+ const DialDeviceData& device_data,
+ const GURL& app_url,
+ chrome::mojom::DialDeviceDescriptionPtr parsed_device_description) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Last callback for current utility process. Release |parser_| and
+ // SafeDialDeviceDescriptionParser object will be destroyed after this
+ // callback.
+ if (parser_ && parser_->GetPendingParsingRequests() == 0)
mark a. foltz 2017/04/18 18:16:26 For a given list of devices, is it possible for on
zhaobin 2017/04/21 23:12:57 Done.
+ parser_ = nullptr;
+
+ if (!parsed_device_description) {
+ error_cb_.Run(device_data, "Failed to parse device description xml");
+ return;
+ }
+
+ ParsedDialDeviceDescription description_data;
+ description_data.unique_id = parsed_device_description->unique_id;
+ description_data.friendly_name = parsed_device_description->friendly_name;
+ description_data.model_name = parsed_device_description->model_name;
+ description_data.app_url = app_url;
+
+ auto error = ValidateParsedDeviceDescription(
+ device_data.device_description_url().host(), description_data);
+
+ if (error != ErrorType::NONE) {
+ DLOG(WARNING) << "Device description failed to validate: " << error;
+ error_cb_.Run(device_data, "Failed to process fetch result");
+ return;
+ }
+
+ CacheEntry cached_description_data;
+ cached_description_data.expire_time =
+ GetNow() +
+ base::TimeDelta::FromMilliseconds(kDeviceDescriptionCacheTimeMillis);
mark a. foltz 2017/04/18 18:16:26 Can you convert the cache timeout constant to use
zhaobin 2017/04/21 23:12:58 Done.
+ cached_description_data.config_id = device_data.config_id();
+ cached_description_data.description_data = description_data;
+
+ DVLOG(2) << "Got device description for " << device_data.label()
+ << "... device description was: "
+ << CachedDeviceDescriptionToString(cached_description_data);
+
+ // Stick it in the cache if the description has a config (version) id.
+ // NOTE: We could cache descriptions without a config id and rely on the
mark a. foltz 2017/04/18 18:16:26 Yes we should do this, to avoid repeatedly request
zhaobin 2017/04/21 23:12:58 Done.
+ // timeout to eventually update the cache.
+ if (description_map_.size() < kCacheLimit &&
mark a. foltz 2017/04/18 18:16:26 If the cache size limit has been hit, you can earl
zhaobin 2017/04/21 23:12:57 Done.
+ cached_description_data.config_id) {
+ DVLOG(2) << "Caching device description for " << device_data.label();
+ description_map_.insert(
+ std::make_pair(device_data.label(), cached_description_data));
+ }
+
+ success_cb_.Run(device_data, description_data);
+}
+
+void DeviceDescriptionService::OnDeviceDescriptionFetchComplete(
+ const DialDeviceData& device_data,
+ const DialDeviceDescriptionData& description_data) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!parser_)
+ parser_ = new SafeDialDeviceDescriptionParser();
+
+ parser_->Start(
+ description_data.device_description,
+ base::Bind(&DeviceDescriptionService::OnParsedDeviceDescription,
+ base::Unretained(this), device_data,
+ description_data.app_url));
+
+ DVLOG(2) << "Device description: "
+ << ScrubDeviceDescriptionXml(description_data.device_description);
+ device_description_fetcher_map_.erase(device_data.label());
+}
+
+void DeviceDescriptionService::OnDeviceDescriptionFetchError(
+ const DialDeviceData& device_data,
+ const std::string& error_message) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(2) << "OnDeviceDescriptionFetchError [label]: " << device_data.label();
+
+ error_cb_.Run(device_data, error_message);
+ device_description_fetcher_map_.erase(device_data.label());
+}
+
+DeviceDescriptionService::CacheEntry::CacheEntry() : config_id(-1) {}
+DeviceDescriptionService::CacheEntry::CacheEntry(const CacheEntry& other) =
+ default;
+DeviceDescriptionService::CacheEntry::~CacheEntry() = default;
+
+} // namespace media_router

Powered by Google App Engine
This is Rietveld 408576698