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