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

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

Powered by Google App Engine
This is Rietveld 408576698