OLD | NEW |
---|---|
(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 | |
OLD | NEW |