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

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: fix chromeos compile error Created 3 years, 7 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 #if DCHECK_IS_ON()
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 constexpr int kDeviceDescriptionCacheTimeHours = 12;
30
31 // Time interval to clean up cache entries.
32 constexpr int kCacheCleanUpTimeoutMins = 30;
33
34 // Maximum size on the number of cached entries.
35 constexpr int kCacheMaxEntries = 256;
36
37 #if DCHECK_IS_ON()
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);
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_cache_.size();
167 base::EraseIf(description_cache_,
168 [&now](const std::pair<std::string, CacheEntry>& cache_pair) {
169 return cache_pair.second.expire_time < now;
170 });
171 DVLOG(2) << "After clean up, cache size: " << description_cache_.size();
172
173 if (description_cache_.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 // Existing Fetcher.
185 const auto& it = device_description_fetcher_map_.find(device_data.label());
186 if (it != device_description_fetcher_map_.end())
187 return;
188
189 auto device_description_fetcher =
190 base::WrapUnique(new DeviceDescriptionFetcher(
191 device_data.device_description_url(), request_context,
192 base::BindOnce(
193 &DeviceDescriptionService::OnDeviceDescriptionFetchComplete,
194 base::Unretained(this), device_data),
195 base::BindOnce(
196 &DeviceDescriptionService::OnDeviceDescriptionFetchError,
197 base::Unretained(this), device_data)));
198
199 device_description_fetcher->Start();
200 device_description_fetcher_map_.insert(std::make_pair(
201 device_data.label(), std::move(device_description_fetcher)));
202
203 pending_device_labels_.insert(device_data.label());
204 }
205
206 const DeviceDescriptionService::CacheEntry*
207 DeviceDescriptionService::CheckAndUpdateCache(
208 const DialDeviceData& device_data) {
209 const auto& it = description_cache_.find(device_data.label());
210 if (it == description_cache_.end())
211 return nullptr;
212
213 // If the entry's config_id does not match, or it has expired, remove it.
214 if (it->second.config_id != device_data.config_id() ||
215 GetNow() >= it->second.expire_time) {
216 DVLOG(2) << "Removing invalid entry " << it->first;
217 description_cache_.erase(it);
218 return nullptr;
219 }
220
221 // Entry is valid.
222 return &it->second;
223 }
224
225 void DeviceDescriptionService::OnParsedDeviceDescription(
226 const DialDeviceData& device_data,
227 const GURL& app_url,
228 chrome::mojom::DialDeviceDescriptionPtr parsed_device_description) {
229 DCHECK(thread_checker_.CalledOnValidThread());
230
231 // Last callback for current utility process. Release |parser_| and
232 // SafeDialDeviceDescriptionParser object will be destroyed after this
233 // callback.
234 pending_device_labels_.erase(device_data.label());
235 if (pending_device_labels_.empty()) {
236 parser_.reset();
237 }
238
239 if (!parsed_device_description) {
240 error_cb_.Run(device_data, "Failed to parse device description XML");
241 return;
242 }
243
244 ParsedDialDeviceDescription description_data;
245 description_data.unique_id = parsed_device_description->unique_id;
246 description_data.friendly_name = parsed_device_description->friendly_name;
247 description_data.model_name = parsed_device_description->model_name;
248 description_data.app_url = app_url;
249
250 auto error = ValidateParsedDeviceDescription(
251 device_data.device_description_url().host(), description_data);
252
253 if (error != ErrorType::NONE) {
254 DLOG(WARNING) << "Device description failed to validate: " << error;
255 error_cb_.Run(device_data, "Failed to process fetch result");
256 return;
257 }
258
259 if (description_cache_.size() >= kCacheMaxEntries) {
260 success_cb_.Run(device_data, description_data);
261 return;
262 }
263
264 CacheEntry cached_description_data;
265 cached_description_data.expire_time =
266 GetNow() + base::TimeDelta::FromHours(kDeviceDescriptionCacheTimeHours);
267 cached_description_data.config_id = device_data.config_id();
268 cached_description_data.description_data = description_data;
269
270 #if DCHECK_IS_ON()
271 DVLOG(2) << "Got device description for " << device_data.label()
272 << "... device description was: "
273 << CachedDeviceDescriptionToString(cached_description_data);
274 #endif
275
276 DVLOG(2) << "Caching device description for " << device_data.label();
277 description_cache_.insert(
278 std::make_pair(device_data.label(), cached_description_data));
279
280 success_cb_.Run(device_data, description_data);
281 }
282
283 void DeviceDescriptionService::OnDeviceDescriptionFetchComplete(
284 const DialDeviceData& device_data,
285 const DialDeviceDescriptionData& description_data) {
286 DCHECK(thread_checker_.CalledOnValidThread());
287
288 if (!parser_)
289 parser_ = base::MakeUnique<SafeDialDeviceDescriptionParser>();
290
291 parser_->Start(
292 description_data.device_description,
293 base::Bind(&DeviceDescriptionService::OnParsedDeviceDescription,
294 base::Unretained(this), device_data,
295 description_data.app_url));
296
297 #if DCHECK_IS_ON()
298 DVLOG(2) << "Device description: "
299 << ScrubDeviceDescriptionXml(description_data.device_description);
300 #endif
301
302 device_description_fetcher_map_.erase(device_data.label());
303 }
304
305 void DeviceDescriptionService::OnDeviceDescriptionFetchError(
306 const DialDeviceData& device_data,
307 const std::string& error_message) {
308 DCHECK(thread_checker_.CalledOnValidThread());
309 DVLOG(2) << "OnDeviceDescriptionFetchError [label]: " << device_data.label();
310
311 error_cb_.Run(device_data, error_message);
312 device_description_fetcher_map_.erase(device_data.label());
313 }
314
315 base::Time DeviceDescriptionService::GetNow() {
316 return base::Time::Now();
317 }
318
319 DeviceDescriptionService::CacheEntry::CacheEntry() : config_id(-1) {}
320 DeviceDescriptionService::CacheEntry::CacheEntry(const CacheEntry& other) =
321 default;
322 DeviceDescriptionService::CacheEntry::~CacheEntry() = default;
323
324 } // namespace media_router
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698