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 19using base::Time; 20using base::TimeDelta; 21using net::NetworkChangeNotifier; 22 23namespace extensions { 24 25DialRegistry::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 43DialRegistry::~DialRegistry() { 44 DCHECK(thread_checker_.CalledOnValidThread()); 45 DCHECK_EQ(0, num_listeners_); 46 NetworkChangeNotifier::RemoveNetworkChangeObserver(this); 47} 48 49DialService* DialRegistry::CreateDialService() { 50 DCHECK(g_browser_process->net_log()); 51 return new DialServiceImpl(g_browser_process->net_log()); 52} 53 54void DialRegistry::ClearDialService() { 55 dial_.reset(); 56} 57 58base::Time DialRegistry::Now() const { 59 return Time::Now(); 60} 61 62void 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 70void 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 79bool 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 96bool 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 112void 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 126void 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 134void 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 144bool 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 164bool 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 182void DialRegistry::Clear() { 183 DCHECK(thread_checker_.CalledOnValidThread()); 184 device_by_id_map_.clear(); 185 device_by_label_map_.clear(); 186 registry_generation_++; 187} 188 189void 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 218std::string DialRegistry::NextLabel() { 219 DCHECK(thread_checker_.CalledOnValidThread()); 220 return base::IntToString(++label_count_); 221} 222 223void DialRegistry::OnDiscoveryRequest(DialService* service) { 224 DCHECK(thread_checker_.CalledOnValidThread()); 225 MaybeSendEvent(); 226} 227 228void 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 260bool 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 275void DialRegistry::OnDiscoveryFinished(DialService* service) { 276 DCHECK(thread_checker_.CalledOnValidThread()); 277 if (PruneExpiredDevices()) 278 registry_generation_++; 279 MaybeSendEvent(); 280} 281 282void 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 299void 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