1 // Copyright (c) 2012 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/extensions/api/dial/dial_registry.h"
6
7 #include "base/memory/scoped_ptr.h"
8 #include "base/stl_util.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/time/time.h"
11 #include "base/values.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/extensions/api/dial/dial_api.h"
14 #include "chrome/browser/extensions/api/dial/dial_device_data.h"
15 #include "chrome/browser/extensions/api/dial/dial_service.h"
16 #include "chrome/browser/net/chrome_net_log.h"
17 #include "chrome/common/extensions/api/dial.h"
18
19 using base::Time;
20 using base::TimeDelta;
21 using net::NetworkChangeNotifier;
22
23 namespace extensions {
24
DialRegistry(Observer * dial_api,const base::TimeDelta & refresh_interval,const base::TimeDelta & expiration,const size_t max_devices)25 DialRegistry::DialRegistry(Observer* dial_api,
26 const base::TimeDelta& refresh_interval,
27 const base::TimeDelta& expiration,
28 const size_t max_devices)
29 : num_listeners_(0),
30 discovery_generation_(0),
31 registry_generation_(0),
32 last_event_discovery_generation_(0),
33 last_event_registry_generation_(0),
34 label_count_(0),
35 refresh_interval_delta_(refresh_interval),
36 expiration_delta_(expiration),
37 max_devices_(max_devices),
38 dial_api_(dial_api) {
39 DCHECK(max_devices_ > 0);
40 NetworkChangeNotifier::AddNetworkChangeObserver(this);
41 }
42
~DialRegistry()43 DialRegistry::~DialRegistry() {
44 DCHECK(thread_checker_.CalledOnValidThread());
45 DCHECK_EQ(0, num_listeners_);
46 NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
47 }
48
CreateDialService()49 DialService* DialRegistry::CreateDialService() {
50 DCHECK(g_browser_process->net_log());
51 return new DialServiceImpl(g_browser_process->net_log());
52 }
53
ClearDialService()54 void DialRegistry::ClearDialService() {
55 dial_.reset();
56 }
57
Now() const58 base::Time DialRegistry::Now() const {
59 return Time::Now();
60 }
61
OnListenerAdded()62 void DialRegistry::OnListenerAdded() {
63 DCHECK(thread_checker_.CalledOnValidThread());
64 if (++num_listeners_ == 1) {
65 VLOG(2) << "Listener added; starting periodic discovery.";
66 StartPeriodicDiscovery();
67 }
68 }
69
OnListenerRemoved()70 void DialRegistry::OnListenerRemoved() {
71 DCHECK(thread_checker_.CalledOnValidThread());
72 DCHECK(num_listeners_ > 0);
73 if (--num_listeners_ == 0) {
74 VLOG(2) << "Listeners removed; stopping periodic discovery.";
75 StopPeriodicDiscovery();
76 }
77 }
78
ReadyToDiscover()79 bool DialRegistry::ReadyToDiscover() {
80 if (num_listeners_ == 0) {
81 dial_api_->OnDialError(DIAL_NO_LISTENERS);
82 return false;
83 }
84 if (NetworkChangeNotifier::IsOffline()) {
85 dial_api_->OnDialError(DIAL_NETWORK_DISCONNECTED);
86 return false;
87 }
88 if (NetworkChangeNotifier::IsConnectionCellular(
89 NetworkChangeNotifier::GetConnectionType())) {
90 dial_api_->OnDialError(DIAL_CELLULAR_NETWORK);
91 return false;
92 }
93 return true;
94 }
95
DiscoverNow()96 bool DialRegistry::DiscoverNow() {
97 DCHECK(thread_checker_.CalledOnValidThread());
98 if (!ReadyToDiscover()) {
99 return false;
100 }
101 if (!dial_.get()) {
102 dial_api_->OnDialError(DIAL_UNKNOWN);
103 return false;
104 }
105
106 if (!dial_->HasObserver(this))
107 NOTREACHED() << "DiscoverNow() called without observing dial_";
108 discovery_generation_++;
109 return dial_->Discover();
110 }
111
StartPeriodicDiscovery()112 void DialRegistry::StartPeriodicDiscovery() {
113 DCHECK(thread_checker_.CalledOnValidThread());
114 if (!ReadyToDiscover() || dial_.get())
115 return;
116
117 dial_.reset(CreateDialService());
118 dial_->AddObserver(this);
119 DoDiscovery();
120 repeating_timer_.Start(FROM_HERE,
121 refresh_interval_delta_,
122 this,
123 &DialRegistry::DoDiscovery);
124 }
125
DoDiscovery()126 void DialRegistry::DoDiscovery() {
127 DCHECK(thread_checker_.CalledOnValidThread());
128 DCHECK(dial_.get());
129 discovery_generation_++;
130 VLOG(2) << "About to discover! Generation = " << discovery_generation_;
131 dial_->Discover();
132 }
133
StopPeriodicDiscovery()134 void DialRegistry::StopPeriodicDiscovery() {
135 DCHECK(thread_checker_.CalledOnValidThread());
136 if (!dial_.get())
137 return;
138
139 repeating_timer_.Stop();
140 dial_->RemoveObserver(this);
141 ClearDialService();
142 }
143
PruneExpiredDevices()144 bool DialRegistry::PruneExpiredDevices() {
145 DCHECK(thread_checker_.CalledOnValidThread());
146 bool pruned_device = false;
147 DeviceByLabelMap::iterator i = device_by_label_map_.begin();
148 while (i != device_by_label_map_.end()) {
149 linked_ptr<DialDeviceData> device = i->second;
150 if (IsDeviceExpired(*device)) {
151 VLOG(2) << "Device " << device->label() << " expired, removing";
152 const size_t num_erased_by_id =
153 device_by_id_map_.erase(device->device_id());
154 DCHECK_EQ(num_erased_by_id, 1u);
155 device_by_label_map_.erase(i++);
156 pruned_device = true;
157 } else {
158 ++i;
159 }
160 }
161 return pruned_device;
162 }
163
IsDeviceExpired(const DialDeviceData & device) const164 bool DialRegistry::IsDeviceExpired(const DialDeviceData& device) const {
165 Time now = Now();
166
167 // Check against our default expiration timeout.
168 Time default_expiration_time = device.response_time() + expiration_delta_;
169 if (now > default_expiration_time)
170 return true;
171
172 // Check against the device's cache-control header, if set.
173 if (device.has_max_age()) {
174 Time max_age_expiration_time =
175 device.response_time() + TimeDelta::FromSeconds(device.max_age());
176 if (now > max_age_expiration_time)
177 return true;
178 }
179 return false;
180 }
181
Clear()182 void DialRegistry::Clear() {
183 DCHECK(thread_checker_.CalledOnValidThread());
184 device_by_id_map_.clear();
185 device_by_label_map_.clear();
186 registry_generation_++;
187 }
188
MaybeSendEvent()189 void DialRegistry::MaybeSendEvent() {
190 DCHECK(thread_checker_.CalledOnValidThread());
191
192 // We need to send an event if:
193 // (1) We haven't sent one yet in this round of discovery, or
194 // (2) The device list changed since the last MaybeSendEvent.
195 bool needs_event =
196 (last_event_discovery_generation_ < discovery_generation_ ||
197 last_event_registry_generation_ < registry_generation_);
198 VLOG(2) << "ledg = " << last_event_discovery_generation_ << ", dg = "
199 << discovery_generation_
200 << ", lerg = " << last_event_registry_generation_ << ", rg = "
201 << registry_generation_
202 << ", needs_event = " << needs_event;
203 if (!needs_event)
204 return;
205
206 DeviceList device_list;
207 for (DeviceByLabelMap::const_iterator i = device_by_label_map_.begin();
208 i != device_by_label_map_.end(); i++) {
209 device_list.push_back(*(i->second));
210 }
211 dial_api_->OnDialDeviceEvent(device_list);
212
213 // Reset watermarks.
214 last_event_discovery_generation_ = discovery_generation_;
215 last_event_registry_generation_ = registry_generation_;
216 }
217
NextLabel()218 std::string DialRegistry::NextLabel() {
219 DCHECK(thread_checker_.CalledOnValidThread());
220 return base::IntToString(++label_count_);
221 }
222
OnDiscoveryRequest(DialService * service)223 void DialRegistry::OnDiscoveryRequest(DialService* service) {
224 DCHECK(thread_checker_.CalledOnValidThread());
225 MaybeSendEvent();
226 }
227
OnDeviceDiscovered(DialService * service,const DialDeviceData & device)228 void DialRegistry::OnDeviceDiscovered(DialService* service,
229 const DialDeviceData& device) {
230 DCHECK(thread_checker_.CalledOnValidThread());
231
232 // Adds |device| to our list of devices or updates an existing device, unless
233 // |device| is a duplicate. Returns true if the list was modified and
234 // increments the list generation.
235 linked_ptr<DialDeviceData> device_data(new DialDeviceData(device));
236 DCHECK(!device_data->device_id().empty());
237 DCHECK(device_data->label().empty());
238
239 bool did_modify_list = false;
240 DeviceByIdMap::iterator lookup_result =
241 device_by_id_map_.find(device_data->device_id());
242
243 if (lookup_result != device_by_id_map_.end()) {
244 VLOG(2) << "Found device " << device_data->device_id() << ", merging";
245
246 // Already have previous response. Merge in data from this response and
247 // track if there were any API visible changes.
248 did_modify_list = lookup_result->second->UpdateFrom(*device_data);
249 } else {
250 did_modify_list = MaybeAddDevice(device_data);
251 }
252
253 if (did_modify_list)
254 registry_generation_++;
255
256 VLOG(2) << "did_modify_list = " << did_modify_list
257 << ", generation = " << registry_generation_;
258 }
259
MaybeAddDevice(const linked_ptr<DialDeviceData> & device_data)260 bool DialRegistry::MaybeAddDevice(
261 const linked_ptr<DialDeviceData>& device_data) {
262 DCHECK(thread_checker_.CalledOnValidThread());
263 if (device_by_id_map_.size() == max_devices_) {
264 VLOG(1) << "Maximum registry size reached. Cannot add device.";
265 return false;
266 }
267 device_data->set_label(NextLabel());
268 device_by_id_map_[device_data->device_id()] = device_data;
269 device_by_label_map_[device_data->label()] = device_data;
270 VLOG(2) << "Added device, id = " << device_data->device_id()
271 << ", label = " << device_data->label();
272 return true;
273 }
274
OnDiscoveryFinished(DialService * service)275 void DialRegistry::OnDiscoveryFinished(DialService* service) {
276 DCHECK(thread_checker_.CalledOnValidThread());
277 if (PruneExpiredDevices())
278 registry_generation_++;
279 MaybeSendEvent();
280 }
281
OnError(DialService * service,const DialService::DialServiceErrorCode & code)282 void DialRegistry::OnError(DialService* service,
283 const DialService::DialServiceErrorCode& code) {
284 DCHECK(thread_checker_.CalledOnValidThread());
285 switch (code) {
286 case DialService::DIAL_SERVICE_SOCKET_ERROR:
287 dial_api_->OnDialError(DIAL_SOCKET_ERROR);
288 break;
289 case DialService::DIAL_SERVICE_NO_INTERFACES:
290 dial_api_->OnDialError(DIAL_NO_INTERFACES);
291 break;
292 default:
293 NOTREACHED();
294 dial_api_->OnDialError(DIAL_UNKNOWN);
295 break;
296 }
297 }
298
OnNetworkChanged(NetworkChangeNotifier::ConnectionType type)299 void DialRegistry::OnNetworkChanged(
300 NetworkChangeNotifier::ConnectionType type) {
301 switch (type) {
302 case NetworkChangeNotifier::CONNECTION_NONE:
303 if (dial_.get()) {
304 VLOG(2) << "Lost connection, shutting down discovery and clearing"
305 << " list.";
306 dial_api_->OnDialError(DIAL_NETWORK_DISCONNECTED);
307
308 StopPeriodicDiscovery();
309 // TODO(justinlin): As an optimization, we can probably keep our device
310 // list around and restore it if we reconnected to the exact same
311 // network.
312 Clear();
313 MaybeSendEvent();
314 }
315 break;
316 case NetworkChangeNotifier::CONNECTION_2G:
317 case NetworkChangeNotifier::CONNECTION_3G:
318 case NetworkChangeNotifier::CONNECTION_4G:
319 case NetworkChangeNotifier::CONNECTION_ETHERNET:
320 case NetworkChangeNotifier::CONNECTION_WIFI:
321 case NetworkChangeNotifier::CONNECTION_UNKNOWN:
322 case NetworkChangeNotifier::CONNECTION_BLUETOOTH:
323 if (!dial_.get()) {
324 VLOG(2) << "Connection detected, restarting discovery.";
325 StartPeriodicDiscovery();
326 }
327 break;
328 }
329 }
330
331 } // namespace extensions
332