Chromium Code Reviews| 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..2a65113a93cdc0072fc3ff8f248b0a6817990145 |
| --- /dev/null |
| +++ b/chrome/browser/media/router/discovery/dial/device_description_service.cc |
| @@ -0,0 +1,274 @@ |
| +// 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/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 { |
| + |
| +// How long to cache a device description. |
| +// TODO: Use value from chrome.dial when http://crbug.com/165289 is fixed. |
|
mark a. foltz
2017/04/12 00:17:17
I'm going to close this as WontFix.
zhaobin
2017/04/18 06:58:26
Done.
|
| +const int kDeviceDescriptionCacheTimeMillis = 30 * 60 * 1000; |
| + |
| +// Replaces "<element_name>content</element_name>" with |
| +// "<element_name>***</element_name>" |
| +void Scrub(const std::string& element_name, std::string& xml_text) { |
|
imcheng
2017/04/12 19:33:50
nit: |xml_text| should be passed in as a pointer.
zhaobin
2017/04/18 06:58:25
Done.
|
| + size_t pos = xml_text.find("<" + element_name + ">"); |
| + size_t end_pos = xml_text.find("</" + element_name + ">"); |
| + size_t start_pos = pos + element_name.length() + 2; |
|
mark a. foltz
2017/04/12 00:17:17
What happens if pos == std::string::npos?
zhaobin
2017/04/18 06:58:26
Done.
|
| + |
| + if (pos != std::string::npos && end_pos > start_pos) |
| + xml_text.replace(start_pos, end_pos - start_pos, "***"); |
| +} |
| + |
| +// Removes unique identifiers from the device description. |
| +// |xml_text|: xmlDoc The device description. |
| +// Returns false if <UDN> or <serialNumber> field does not exist in |xml_text|. |
|
mark a. foltz
2017/04/12 00:17:17
This needs to be updated
zhaobin
2017/04/18 06:58:25
Done.
|
| +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; |
| +} |
| + |
| +// Validates whether a URL is valid for fetching device descriptions. |
| +// |descriptionUrl|: Device description URL. |
| +// Returns true if descriptionUrl is valid. |
| +static bool IsValidDeviceDescriptionUrl(const GURL& device_description_url) { |
|
imcheng
2017/04/12 19:33:50
Functions in anonymous namespace do not need to be
zhaobin
2017/04/18 06:58:25
Done.
|
| + if (!device_description_url.SchemeIs(url::kHttpScheme)) |
| + return false; |
| + |
| + net::IPAddress address; |
| + if (!net::ParseURLHostnameToAddress(device_description_url.host(), &address)) |
|
mark a. foltz
2017/04/12 00:17:17
This check could go earlier in DialService::ParseR
zhaobin
2017/04/18 06:58:26
Done.
|
| + return false; |
| + |
| + return address.IsReserved(); |
| +} |
| + |
| +static 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. |
|
mark a. foltz
2017/04/12 00:17:17
Please document meaning of return value.
zhaobin
2017/04/18 06:58:25
Done.
|
| +static std::string Validate( |
|
imcheng
2017/04/12 19:33:50
Maybe ValidateParsedDeviceDescription to be more c
zhaobin
2017/04/18 06:58:25
Done.
|
| + const std::string& expected_ip_address, |
| + const media_router::ParsedDialDeviceDescription& description_data) { |
| + if (description_data.unique_id.empty()) { |
| + return "Missing uniqueId"; |
|
imcheng
2017/04/12 19:33:50
IMO, enum is slightly preferable to strings for in
zhaobin
2017/04/18 06:58:25
Done.
|
| + } |
| + if (description_data.friendly_name.empty()) { |
| + return "Missing friendlyName"; |
| + } |
| + if (!description_data.app_url.is_valid()) { |
| + return "Missing appUrl"; |
| + } |
| + if (!IsValidAppUrl(description_data.app_url, expected_ip_address)) { |
| + return "Invalid appUrl"; |
| + } |
| + return ""; |
| +} |
| + |
| +static std::string ToString( |
|
mark a. foltz
2017/04/12 00:17:17
If this is only use with D*LOG statements, use #if
imcheng
2017/04/12 19:33:50
Naming suggestion: CachedDeviceDescriptionToString
zhaobin
2017/04/18 06:58:25
Done.
zhaobin
2017/04/18 06:58:26
Done.
|
| + const media_router::CachedParsedDialDeviceDescription& cached_data) { |
| + std::stringstream ss; |
| + 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 |
| + << " [device_type]: " << cached_data.description_data.device_type |
| + << " [app_url]: " << cached_data.description_data.app_url |
| + << " [fetch_time_millis]: " << cached_data.fetch_time_millis |
| + << " [expire_time_millis]: " << cached_data.expire_time_millis |
| + << " [config_id]: "; |
|
mark a. foltz
2017/04/12 00:17:17
Nit: Maybe only include this if config_id is set.
zhaobin
2017/04/18 06:58:26
Done.
|
| + |
| + if (cached_data.config_id) |
| + ss << cached_data.config_id.value(); |
| + |
| + return ss.str(); |
| +} |
| + |
| +} // namespace |
| + |
| +namespace media_router { |
| + |
| +DeviceDescriptionService::DeviceDescriptionService( |
| + DeviceDescriptionParseSuccessCallback success_cb, |
| + DeviceDescriptionParseErrorCallback error_cb) |
| + : success_cb_(success_cb), error_cb_(error_cb) {} |
| + |
| +DeviceDescriptionService::~DeviceDescriptionService() = default; |
| + |
| +void DeviceDescriptionService::GetDeviceDescriptions( |
| + const std::vector<DialDeviceData>& devices, |
|
imcheng
2017/04/12 19:33:50
Is the idea that this gets called with the full de
zhaobin
2017/04/18 06:58:26
Done.
|
| + net::URLRequestContextGetter* request_context) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + |
| + std::map<std::string, std::unique_ptr<DeviceDescriptionFetcher>> |
| + existing_fetcher_map; |
| + for (const auto& device_data : devices) { |
| + // Keep fetcher if it is for |device_data.label()| and has the same Url. |
|
mark a. foltz
2017/04/12 00:17:17
nit: URL
zhaobin
2017/04/18 06:58:25
Done.
|
| + const auto& it = device_description_fetcher_map_.find(device_data.label()); |
| + if (it != device_description_fetcher_map_.end() && |
| + it->second->device_description_url() == |
| + device_data.device_description_url()) { |
| + existing_fetcher_map.insert( |
| + std::make_pair(device_data.label(), std::move(it->second))); |
| + } |
| + } |
| + |
| + // Remove all out dated fetcheres. |
|
mark a. foltz
2017/04/12 00:17:17
typo in fetchers
zhaobin
2017/04/18 06:58:25
Done.
|
| + device_description_fetcher_map_ = std::move(existing_fetcher_map); |
|
mark a. foltz
2017/04/12 00:17:17
Will this delete all fetchers whose labels are not
zhaobin
2017/04/18 06:58:26
Seems to be safe. net::URLFetcher says "You may ca
|
| + |
| + for (const auto& device_data : devices) { |
| + CachedParsedDialDeviceDescription description_data; |
| + if (CheckAndUpdateCache(device_data, &description_data)) { |
| + // Get device description from cache. |
| + success_cb_.Run(device_data, description_data.description_data); |
| + continue; |
| + } |
| + |
| + FetchDeviceDescription(device_data, request_context); |
| + } |
| +} |
| + |
| +void DeviceDescriptionService::FetchDeviceDescription( |
| + const DialDeviceData& device_data, |
| + net::URLRequestContextGetter* request_context) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + |
| + if (!IsValidDeviceDescriptionUrl(device_data.device_description_url())) { |
| + std::string error_message = "Invalid device description URL: " + |
| + device_data.device_description_url().spec(); |
| + OnDeviceDescriptionFetchError(device_data, error_message); |
| + return; |
| + } |
| + |
| + 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))); |
| +} |
| + |
| +bool DeviceDescriptionService::CheckAndUpdateCache( |
| + const DialDeviceData& device_data, |
| + CachedParsedDialDeviceDescription* out_description) { |
| + const auto& it = description_map_.find(device_data.label()); |
| + if (it == description_map_.end()) |
| + return false; |
| + |
| + // If the entry's configId does not match, or it has expired, remove it. |
|
mark a. foltz
2017/04/12 00:17:17
nit: config_id
zhaobin
2017/04/18 06:58:25
Done.
|
| + if (it->second.config_id != device_data.config_id() || |
| + base::Time::Now() >= it->second.expire_time_millis) { |
|
imcheng
2017/04/12 19:33:50
General comment: for tests you might find it usefu
zhaobin
2017/04/18 06:58:25
Done.
|
| + DVLOG(2) << "Removing invalid entry " << it->first; |
| + description_map_.erase(it); |
|
mark a. foltz
2017/04/12 00:17:17
It looks like there will be some expired cached en
zhaobin
2017/04/18 06:58:26
On 2017/04/12 00:17:17, mark a. foltz wrote:
> It
|
| + return false; |
| + } |
| + |
| + // Entry is valid. |
| + (*out_description) = it->second; |
|
mark a. foltz
2017/04/12 00:17:17
It it necessary to make a copy here, or could Chec
zhaobin
2017/04/18 06:58:26
Done.
|
| + return true; |
| +} |
| + |
| +void DeviceDescriptionService::OnParsedDeviceDescription( |
| + const DialDeviceData& device_data, |
| + const GURL& app_url, |
| + chrome::mojom::DialDeviceDescriptionPtr parsed_device_description) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + |
| + if (!parsed_device_description) { |
| + error_cb_.Run(device_data.label(), |
| + "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.device_type = parsed_device_description->device_type; |
| + description_data.app_url = app_url; |
| + |
| + std::string error = |
| + Validate(device_data.device_description_url().host(), description_data); |
| + |
| + if (error.empty()) { |
|
mark a. foltz
2017/04/12 00:17:17
Nit: I would reverse the two cases of the if() and
zhaobin
2017/04/18 06:58:26
Done.
|
| + CachedParsedDialDeviceDescription cached_description_data; |
| + cached_description_data.fetch_time_millis = base::Time::Now(); |
| + cached_description_data.expire_time_millis = |
| + cached_description_data.fetch_time_millis + |
| + base::TimeDelta::FromMilliseconds(kDeviceDescriptionCacheTimeMillis); |
| + 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(); |
|
mark a. foltz
2017/04/12 00:17:17
Nit: These can be combined into one statement.
zhaobin
2017/04/18 06:58:26
Done.
|
| + DVLOG(2) << "... device description was: " |
| + << ToString(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 |
| + // timeout to eventually update the cache. |
|
mark a. foltz
2017/04/12 00:17:17
This would be fine with me. The only piece of dat
zhaobin
2017/04/18 06:58:26
Acknowledged.
|
| + if (cached_description_data.config_id) { |
| + DVLOG(2) << "Caching device description for " << device_data.label(); |
| + description_map_.insert( |
|
mark a. foltz
2017/04/12 00:17:17
Can we set a maximum size on the number of cached
zhaobin
2017/04/18 06:58:25
Done.
|
| + std::make_pair(device_data.label(), cached_description_data)); |
| + } |
| + |
| + success_cb_.Run(device_data, description_data); |
| + } else { |
| + DLOG(WARNING) << "Device description failed to validate: " << error; |
| + error_cb_.Run(device_data.label(), "Failed to process fetch result"); |
| + } |
| +} |
| + |
| +void DeviceDescriptionService::OnDeviceDescriptionFetchComplete( |
| + const DialDeviceData& device_data, |
| + const DialDeviceDescriptionData& description_data) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + |
| + scoped_refptr<SafeDialDeviceDescriptionParser> parser( |
|
mark a. foltz
2017/04/12 00:17:17
What keeps the parser alive after calling Start()?
zhaobin
2017/04/18 06:58:26
It is bound insdie parser->start() (https://codere
|
| + new SafeDialDeviceDescriptionParser()); |
|
mark a. foltz
2017/04/12 00:17:17
Could this potentially spawn multiple utility proc
zhaobin
2017/04/18 06:58:25
Done.
|
| + |
| + parser->Start(description_data.device_description, |
| + base::Bind(&DeviceDescriptionService::OnParsedDeviceDescription, |
| + base::Unretained(this), device_data, |
|
mark a. foltz
2017/04/12 00:17:17
What prevents the callback from being run after |t
zhaobin
2017/04/18 06:58:26
(I think) |this| is owned by DialMediaSinkService,
|
| + description_data.app_url)); |
| + |
| + std::string xml_logging = |
|
imcheng
2017/04/12 19:33:50
Similar to above, we want to guard this with #ifnd
zhaobin
2017/04/18 06:58:25
Done.
|
| + ScrubDeviceDescriptionXml(description_data.device_description); |
| + DVLOG(2) << "Device description: " << xml_logging; |
| + |
| + 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.label(), error_message); |
| + device_description_fetcher_map_.erase(device_data.label()); |
| +} |
| + |
| +CachedParsedDialDeviceDescription::CachedParsedDialDeviceDescription() = |
| + default; |
| +CachedParsedDialDeviceDescription::CachedParsedDialDeviceDescription( |
| + const CachedParsedDialDeviceDescription& other) = default; |
| +CachedParsedDialDeviceDescription::~CachedParsedDialDeviceDescription() = |
| + default; |
| + |
| +} // namespace media_router |