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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 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 "chrome/browser/media/router/discovery/dial/device_description_service. h"
6
7 #include "base/stl_util.h"
8 #include "base/strings/string_util.h"
9 #include "chrome/browser/media/router/discovery/dial/device_description_fetcher. h"
10 #include "chrome/browser/media/router/discovery/dial/safe_dial_device_descriptio n_parser.h"
11 #include "net/base/ip_address.h"
12 #include "url/gurl.h"
13
14 namespace {
15
16 enum ErrorType {
17 NONE,
18 MISSING_UNIQUE_ID,
19 MISSING_FRIENDLY_NAME,
20 MISSING_APP_URL,
21 INVALID_APP_URL
22 };
23
24 // How long to cache a device description.
25 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.
26
27 // Time interval to clean up cache entries.
28 const int kCacheCleanUpTimeoutMins = 30;
29
30 // Maximum size on the number of cached entries.
31 const int kCacheLimit = 256;
mark a. foltz 2017/04/18 18:16:26 kCacheMaxEntries ?
zhaobin 2017/04/21 23:12:57 Done.
32
33 #ifndef NDEBUG
34 // Replaces "<element_name>content</element_name>" with
35 // "<element_name>***</element_name>"
36 void Scrub(const std::string& element_name, std::string* xml_text) {
37 size_t pos = xml_text->find("<" + element_name + ">");
38 size_t end_pos = xml_text->find("</" + element_name + ">");
39
40 if (pos == std::string::npos || end_pos == std::string::npos)
41 return;
42
43 size_t start_pos = pos + element_name.length() + 2;
44 if (end_pos > start_pos)
45 xml_text->replace(start_pos, end_pos - start_pos, "***");
46 }
47
48 // Removes unique identifiers from the device description.
49 // |xml_text|: The device description XML.
50 // Returns original XML text if <UDN> or <serialNumber> field does not exist.
51 std::string ScrubDeviceDescriptionXml(const std::string& xml_text) {
52 std::string scrubbed_xml(xml_text);
53 Scrub("UDN", &scrubbed_xml);
54 Scrub("serialNumber", &scrubbed_xml);
55 return scrubbed_xml;
56 }
57
58 std::string CachedDeviceDescriptionToString(
59 const media_router::DeviceDescriptionService::CacheEntry& cached_data) {
60 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.
61 ss << "CachedDialDeviceDescription [unique_id]: "
62 << cached_data.description_data.unique_id
63 << " [friendly_name]: " << cached_data.description_data.friendly_name
64 << " [model_name]: " << cached_data.description_data.model_name
65 << " [app_url]: " << cached_data.description_data.app_url
66 << " [expire_time]: " << cached_data.expire_time
67 << " [config_id]: " << cached_data.config_id;
68
69 return ss.str();
70 }
71 #endif
72
73 bool IsValidAppUrl(const GURL& app_url, const std::string& ip_address) {
74 return app_url.SchemeIs(url::kHttpScheme) && app_url.host() == ip_address;
75 }
76
77 // Checks mandatory fields. Returns ErrorType::NONE if device description is
78 // valid; Otherwise returns specific error type.
79 ErrorType ValidateParsedDeviceDescription(
80 const std::string& expected_ip_address,
81 const media_router::ParsedDialDeviceDescription& description_data) {
82 if (description_data.unique_id.empty()) {
83 return ErrorType::MISSING_UNIQUE_ID;
84 }
85 if (description_data.friendly_name.empty()) {
86 return ErrorType::MISSING_FRIENDLY_NAME;
87 }
88 if (!description_data.app_url.is_valid()) {
89 return ErrorType::MISSING_APP_URL;
90 }
91 if (!IsValidAppUrl(description_data.app_url, expected_ip_address)) {
92 return ErrorType::INVALID_APP_URL;
93 }
94 return ErrorType::NONE;
95 }
96
97 } // namespace
98
99 namespace media_router {
100
101 DeviceDescriptionService::DeviceDescriptionService(
102 const DeviceDescriptionParseSuccessCallback& success_cb,
103 const DeviceDescriptionParseErrorCallback& error_cb)
104 : success_cb_(success_cb), error_cb_(error_cb) {}
105
106 DeviceDescriptionService::~DeviceDescriptionService() = default;
107
108 void DeviceDescriptionService::GetDeviceDescriptions(
109 const std::vector<DialDeviceData>& devices,
110 net::URLRequestContextGetter* request_context) {
111 DCHECK(thread_checker_.CalledOnValidThread());
112
113 std::map<std::string, std::unique_ptr<DeviceDescriptionFetcher>>
114 existing_fetcher_map;
115 for (auto& fetcher_it : device_description_fetcher_map_) {
116 std::string device_label = fetcher_it.first;
117 const auto& device_it =
118 std::find_if(devices.begin(), devices.end(),
119 [&device_label](const DialDeviceData& device_data) {
120 return device_data.label() == device_label;
121 });
122 if (device_it == devices.end() ||
123 device_it->device_description_url() ==
124 fetcher_it.second->device_description_url()) {
125 existing_fetcher_map.insert(
126 std::make_pair(device_label, std::move(fetcher_it.second)));
127 }
128 }
129
130 // Remove all out dated fetchers.
131 device_description_fetcher_map_ = std::move(existing_fetcher_map);
132
133 for (const auto& device_data : devices) {
134 auto* cache_entry = CheckAndUpdateCache(device_data);
135 if (cache_entry) {
136 // Get device description from cache.
137 success_cb_.Run(device_data, cache_entry->description_data);
138 continue;
139 }
140
141 FetchDeviceDescription(device_data, request_context);
142 }
143
144 // Start a clean up timer.
145 if (!clean_up_timer_) {
146 clean_up_timer_.reset(new base::RepeatingTimer());
147 clean_up_timer_->Start(
148 FROM_HERE, base::TimeDelta::FromMinutes(kCacheCleanUpTimeoutMins), this,
149 &DeviceDescriptionService::CleanUpCacheEntries);
150 }
151 }
152
153 void DeviceDescriptionService::CleanUpCacheEntries() {
154 // TODO(zhaobin): instead of removing them directly, issue GET request to
155 // 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.
156 DCHECK(thread_checker_.CalledOnValidThread());
157 base::Time now = GetNow();
158
159 DVLOG(2) << "Before clean up, cache size: " << description_map_.size();
160 base::EraseIf(description_map_,
161 [&now](const std::pair<std::string, CacheEntry>& cache_pair) {
162 return cache_pair.second.expire_time < now;
163 });
164 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.
165 }
166
167 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.
168 return base::Time::Now();
169 }
170
171 void DeviceDescriptionService::FetchDeviceDescription(
172 const DialDeviceData& device_data,
173 net::URLRequestContextGetter* request_context) {
174 DCHECK(thread_checker_.CalledOnValidThread());
175
176 auto device_description_fetcher = base::MakeUnique<DeviceDescriptionFetcher>(
177 device_data.device_description_url(), request_context,
178 base::BindOnce(
179 &DeviceDescriptionService::OnDeviceDescriptionFetchComplete,
180 base::Unretained(this), device_data),
181 base::BindOnce(&DeviceDescriptionService::OnDeviceDescriptionFetchError,
182 base::Unretained(this), device_data));
183
184 device_description_fetcher->Start();
185 device_description_fetcher_map_.insert(std::make_pair(
186 device_data.label(), std::move(device_description_fetcher)));
187 }
188
189 const DeviceDescriptionService::CacheEntry*
190 DeviceDescriptionService::CheckAndUpdateCache(
191 const DialDeviceData& device_data) {
192 const auto& it = description_map_.find(device_data.label());
193 if (it == description_map_.end())
194 return nullptr;
195
196 // If the entry's config_id does not match, or it has expired, remove it.
197 if (it->second.config_id != device_data.config_id() ||
198 GetNow() >= it->second.expire_time) {
199 DVLOG(2) << "Removing invalid entry " << it->first;
200 description_map_.erase(it);
201 return nullptr;
202 }
203
204 // Entry is valid.
205 return &it->second;
206 }
207
208 void DeviceDescriptionService::OnParsedDeviceDescription(
209 const DialDeviceData& device_data,
210 const GURL& app_url,
211 chrome::mojom::DialDeviceDescriptionPtr parsed_device_description) {
212 DCHECK(thread_checker_.CalledOnValidThread());
213
214 // Last callback for current utility process. Release |parser_| and
215 // SafeDialDeviceDescriptionParser object will be destroyed after this
216 // callback.
217 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.
218 parser_ = nullptr;
219
220 if (!parsed_device_description) {
221 error_cb_.Run(device_data, "Failed to parse device description xml");
222 return;
223 }
224
225 ParsedDialDeviceDescription description_data;
226 description_data.unique_id = parsed_device_description->unique_id;
227 description_data.friendly_name = parsed_device_description->friendly_name;
228 description_data.model_name = parsed_device_description->model_name;
229 description_data.app_url = app_url;
230
231 auto error = ValidateParsedDeviceDescription(
232 device_data.device_description_url().host(), description_data);
233
234 if (error != ErrorType::NONE) {
235 DLOG(WARNING) << "Device description failed to validate: " << error;
236 error_cb_.Run(device_data, "Failed to process fetch result");
237 return;
238 }
239
240 CacheEntry cached_description_data;
241 cached_description_data.expire_time =
242 GetNow() +
243 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.
244 cached_description_data.config_id = device_data.config_id();
245 cached_description_data.description_data = description_data;
246
247 DVLOG(2) << "Got device description for " << device_data.label()
248 << "... device description was: "
249 << CachedDeviceDescriptionToString(cached_description_data);
250
251 // Stick it in the cache if the description has a config (version) id.
252 // 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.
253 // timeout to eventually update the cache.
254 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.
255 cached_description_data.config_id) {
256 DVLOG(2) << "Caching device description for " << device_data.label();
257 description_map_.insert(
258 std::make_pair(device_data.label(), cached_description_data));
259 }
260
261 success_cb_.Run(device_data, description_data);
262 }
263
264 void DeviceDescriptionService::OnDeviceDescriptionFetchComplete(
265 const DialDeviceData& device_data,
266 const DialDeviceDescriptionData& description_data) {
267 DCHECK(thread_checker_.CalledOnValidThread());
268
269 if (!parser_)
270 parser_ = new SafeDialDeviceDescriptionParser();
271
272 parser_->Start(
273 description_data.device_description,
274 base::Bind(&DeviceDescriptionService::OnParsedDeviceDescription,
275 base::Unretained(this), device_data,
276 description_data.app_url));
277
278 DVLOG(2) << "Device description: "
279 << ScrubDeviceDescriptionXml(description_data.device_description);
280 device_description_fetcher_map_.erase(device_data.label());
281 }
282
283 void DeviceDescriptionService::OnDeviceDescriptionFetchError(
284 const DialDeviceData& device_data,
285 const std::string& error_message) {
286 DCHECK(thread_checker_.CalledOnValidThread());
287 DVLOG(2) << "OnDeviceDescriptionFetchError [label]: " << device_data.label();
288
289 error_cb_.Run(device_data, error_message);
290 device_description_fetcher_map_.erase(device_data.label());
291 }
292
293 DeviceDescriptionService::CacheEntry::CacheEntry() : config_id(-1) {}
294 DeviceDescriptionService::CacheEntry::CacheEntry(const CacheEntry& other) =
295 default;
296 DeviceDescriptionService::CacheEntry::~CacheEntry() = default;
297
298 } // namespace media_router
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698