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 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 43 DialRegistry::~DialRegistry() { 44 DCHECK(thread_checker_.CalledOnValidThread()); 45 DCHECK_EQ(0, num_listeners_); 46 NetworkChangeNotifier::RemoveNetworkChangeObserver(this); 47 } 48 49 DialService* DialRegistry::CreateDialService() { 50 DCHECK(g_browser_process->net_log()); 51 return new DialServiceImpl(g_browser_process->net_log()); 52 } 53 54 void DialRegistry::ClearDialService() { 55 dial_.reset(); 56 } 57 58 base::Time DialRegistry::Now() const { 59 return Time::Now(); 60 } 61 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 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 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 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 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 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 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 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 164 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 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 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 218 std::string DialRegistry::NextLabel() { 219 DCHECK(thread_checker_.CalledOnValidThread()); 220 return base::IntToString(++label_count_); 221 } 222 223 void DialRegistry::OnDiscoveryRequest(DialService* service) { 224 DCHECK(thread_checker_.CalledOnValidThread()); 225 MaybeSendEvent(); 226 } 227 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 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 275 void DialRegistry::OnDiscoveryFinished(DialService* service) { 276 DCHECK(thread_checker_.CalledOnValidThread()); 277 if (PruneExpiredDevices()) 278 registry_generation_++; 279 MaybeSendEvent(); 280 } 281 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 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