blob: 0a1b2e28cefc99969391daf2ac3eb0533b4c2078 [file] [log] [blame]
Vitaly Buka7ce499f2015-06-09 08:04:11 -07001// Copyright 2014 The Chromium OS 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 "buffet/privet/shill_client.h"
6
7#include <set>
8
9#include <base/message_loop/message_loop.h>
10#include <base/stl_util.h>
11#include <chromeos/any.h>
12#include <chromeos/dbus/service_constants.h>
13#include <chromeos/errors/error.h>
14#include <chromeos/errors/error_codes.h>
15
16using chromeos::Any;
17using chromeos::VariantDictionary;
18using dbus::ObjectPath;
19using org::chromium::flimflam::DeviceProxy;
20using org::chromium::flimflam::ServiceProxy;
21using std::map;
22using std::set;
23using std::string;
24using std::vector;
25
26namespace privetd {
27
28namespace {
29
Vitaly Buka075b3d42015-06-09 08:34:25 -070030void IgnoreDetachEvent() {
31}
Vitaly Buka7ce499f2015-06-09 08:04:11 -070032
33bool GetStateForService(ServiceProxy* service, string* state) {
34 CHECK(service) << "|service| was nullptr in GetStateForService()";
35 VariantDictionary properties;
36 if (!service->GetProperties(&properties, nullptr)) {
37 LOG(WARNING) << "Failed to read properties from service.";
38 return false;
39 }
40 auto property_it = properties.find(shill::kStateProperty);
41 if (property_it == properties.end()) {
42 LOG(WARNING) << "No state found in service properties.";
43 return false;
44 }
45 string new_state = property_it->second.TryGet<string>();
46 if (new_state.empty()) {
47 LOG(WARNING) << "Invalid state value.";
48 return false;
49 }
50 *state = new_state;
51 return true;
52}
53
54ServiceState ShillServiceStateToServiceState(const string& state) {
55 // TODO(wiley) What does "unconfigured" mean in a world with multiple sets
56 // of WiFi credentials?
57 // TODO(wiley) Detect disabled devices, update state appropriately.
58 if ((state.compare(shill::kStateReady) == 0) ||
59 (state.compare(shill::kStatePortal) == 0) ||
60 (state.compare(shill::kStateOnline) == 0)) {
61 return ServiceState::kConnected;
62 }
63 if ((state.compare(shill::kStateAssociation) == 0) ||
64 (state.compare(shill::kStateConfiguration) == 0)) {
65 return ServiceState::kConnecting;
66 }
67 if ((state.compare(shill::kStateFailure) == 0) ||
68 (state.compare(shill::kStateActivationFailure) == 0)) {
69 // TODO(wiley) Get error information off the service object.
70 return ServiceState::kFailure;
71 }
72 if ((state.compare(shill::kStateIdle) == 0) ||
73 (state.compare(shill::kStateOffline) == 0) ||
74 (state.compare(shill::kStateDisconnect) == 0)) {
75 return ServiceState::kOffline;
76 }
77 LOG(WARNING) << "Unknown state found: '" << state << "'";
78 return ServiceState::kOffline;
79}
80
81} // namespace
82
83std::string ServiceStateToString(ServiceState state) {
84 switch (state) {
85 case ServiceState::kOffline:
86 return "offline";
87 case ServiceState::kFailure:
88 return "failure";
89 case ServiceState::kConnecting:
90 return "connecting";
91 case ServiceState::kConnected:
92 return "connected";
93 }
94 LOG(ERROR) << "Unknown ServiceState value!";
95 return "unknown";
96}
97
98ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus,
99 const set<string>& device_whitelist)
100 : bus_{bus},
101 manager_proxy_{bus_, ObjectPath{"/"}},
102 device_whitelist_{device_whitelist} {
103 manager_proxy_.RegisterPropertyChangedSignalHandler(
104 base::Bind(&ShillClient::OnManagerPropertyChange,
105 weak_factory_.GetWeakPtr()),
106 base::Bind(&ShillClient::OnManagerPropertyChangeRegistration,
107 weak_factory_.GetWeakPtr()));
108 auto owner_changed_cb = base::Bind(&ShillClient::OnShillServiceOwnerChange,
109 weak_factory_.GetWeakPtr());
Vitaly Buka075b3d42015-06-09 08:34:25 -0700110 bus_->GetObjectProxy(shill::kFlimflamServiceName, ObjectPath{"/"})
111 ->SetNameOwnerChangedCallback(owner_changed_cb);
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700112}
113
114void ShillClient::Init() {
115 VLOG(2) << "ShillClient::Init();";
116 CleanupConnectingService(false);
117 devices_.clear();
118 connectivity_state_ = ServiceState::kOffline;
119 VariantDictionary properties;
120 if (!manager_proxy_.GetProperties(&properties, nullptr)) {
121 LOG(ERROR) << "Unable to get properties from Manager, waiting for "
122 "Manager to come back online.";
123 return;
124 }
125 auto it = properties.find(shill::kDevicesProperty);
126 CHECK(it != properties.end()) << "shill should always publish a device list.";
127 OnManagerPropertyChange(shill::kDevicesProperty, it->second);
128}
129
130bool ShillClient::ConnectToService(const string& ssid,
131 const string& passphrase,
132 const base::Closure& on_success,
133 chromeos::ErrorPtr* error) {
134 CleanupConnectingService(false);
135 VariantDictionary service_properties;
136 service_properties[shill::kTypeProperty] = Any{string{shill::kTypeWifi}};
137 service_properties[shill::kSSIDProperty] = Any{ssid};
138 service_properties[shill::kPassphraseProperty] = Any{passphrase};
139 service_properties[shill::kSecurityProperty] = Any{
140 string{passphrase.empty() ? shill::kSecurityNone : shill::kSecurityPsk}};
141 service_properties[shill::kSaveCredentialsProperty] = Any{true};
142 service_properties[shill::kAutoConnectProperty] = Any{true};
143 ObjectPath service_path;
Vitaly Buka075b3d42015-06-09 08:34:25 -0700144 if (!manager_proxy_.ConfigureService(service_properties, &service_path,
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700145 error)) {
146 return false;
147 }
148 if (!manager_proxy_.RequestScan(shill::kTypeWifi, error)) {
149 return false;
150 }
151 connecting_service_.reset(new ServiceProxy{bus_, service_path});
152 on_connect_success_.Reset(on_success);
153 connecting_service_->RegisterPropertyChangedSignalHandler(
154 base::Bind(&ShillClient::OnServicePropertyChange,
Vitaly Buka075b3d42015-06-09 08:34:25 -0700155 weak_factory_.GetWeakPtr(), service_path),
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700156 base::Bind(&ShillClient::OnServicePropertyChangeRegistration,
Vitaly Buka075b3d42015-06-09 08:34:25 -0700157 weak_factory_.GetWeakPtr(), service_path));
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700158 return true;
159}
160
161ServiceState ShillClient::GetConnectionState() const {
162 return connectivity_state_;
163}
164
165bool ShillClient::AmOnline() const {
166 return connectivity_state_ == ServiceState::kConnected;
167}
168
169void ShillClient::RegisterConnectivityListener(
170 const ConnectivityListener& listener) {
171 connectivity_listeners_.push_back(listener);
172}
173
174bool ShillClient::IsMonitoredDevice(DeviceProxy* device) {
175 if (device_whitelist_.empty()) {
176 return true;
177 }
178 VariantDictionary device_properties;
179 if (!device->GetProperties(&device_properties, nullptr)) {
180 LOG(ERROR) << "Devices without properties aren't whitelisted.";
181 return false;
182 }
183 auto it = device_properties.find(shill::kInterfaceProperty);
184 if (it == device_properties.end()) {
185 LOG(ERROR) << "Failed to find interface property in device properties.";
186 return false;
187 }
188 return ContainsKey(device_whitelist_, it->second.TryGet<string>());
189}
190
191void ShillClient::OnShillServiceOwnerChange(const string& old_owner,
192 const string& new_owner) {
193 VLOG(1) << "Shill service owner name changed to '" << new_owner << "'";
194 if (new_owner.empty()) {
195 CleanupConnectingService(false);
196 devices_.clear();
197 connectivity_state_ = ServiceState::kOffline;
198 } else {
199 Init(); // New service owner means shill reset!
200 }
201}
202
203void ShillClient::OnManagerPropertyChangeRegistration(const string& interface,
204 const string& signal_name,
205 bool success) {
206 VLOG(3) << "Registered ManagerPropertyChange handler.";
207 CHECK(success) << "privetd requires Manager signals.";
208 VariantDictionary properties;
209 if (!manager_proxy_.GetProperties(&properties, nullptr)) {
210 LOG(ERROR) << "Unable to get properties from Manager, waiting for "
211 "Manager to come back online.";
212 return;
213 }
214 auto it = properties.find(shill::kDevicesProperty);
215 CHECK(it != properties.end()) << "Shill should always publish a device list.";
216 OnManagerPropertyChange(shill::kDevicesProperty, it->second);
217}
218
219void ShillClient::OnManagerPropertyChange(const string& property_name,
220 const Any& property_value) {
221 if (property_name != shill::kDevicesProperty) {
222 return;
223 }
224 VLOG(3) << "Manager's device list has changed.";
225 // We're going to remove every device we haven't seen in the update.
226 set<ObjectPath> device_paths_to_remove;
227 for (const auto& kv : devices_) {
228 device_paths_to_remove.insert(kv.first);
229 }
230 for (const auto& device_path : property_value.TryGet<vector<ObjectPath>>()) {
231 if (!device_path.IsValid()) {
232 LOG(ERROR) << "Ignoring invalid device path in Manager's device list.";
233 return;
234 }
235 auto it = devices_.find(device_path);
236 if (it != devices_.end()) {
237 // Found an existing proxy. Since the whitelist never changes,
238 // this still a valid device.
239 device_paths_to_remove.erase(device_path);
240 continue;
241 }
242 std::unique_ptr<DeviceProxy> device{new DeviceProxy{bus_, device_path}};
243 if (!IsMonitoredDevice(device.get())) {
244 continue;
245 }
246 device->RegisterPropertyChangedSignalHandler(
247 base::Bind(&ShillClient::OnDevicePropertyChange,
Vitaly Buka075b3d42015-06-09 08:34:25 -0700248 weak_factory_.GetWeakPtr(), device_path),
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700249 base::Bind(&ShillClient::OnDevicePropertyChangeRegistration,
Vitaly Buka075b3d42015-06-09 08:34:25 -0700250 weak_factory_.GetWeakPtr(), device_path));
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700251 VLOG(3) << "Creating device proxy at " << device_path.value();
252 devices_[device_path].device = std::move(device);
253 }
254 // Clean up devices/services related to removed devices.
255 if (!device_paths_to_remove.empty()) {
256 for (const ObjectPath& device_path : device_paths_to_remove) {
257 devices_.erase(device_path);
258 }
259 UpdateConnectivityState();
260 }
261}
262
263void ShillClient::OnDevicePropertyChangeRegistration(
264 const ObjectPath& device_path,
265 const string& interface,
266 const string& signal_name,
267 bool success) {
268 VLOG(3) << "Registered DevicePropertyChange handler.";
269 auto it = devices_.find(device_path);
270 if (it == devices_.end()) {
271 return;
272 }
273 CHECK(success) << "Failed to subscribe to Device property changes.";
274 DeviceProxy* device = it->second.device.get();
275 VariantDictionary properties;
276 if (!device->GetProperties(&properties, nullptr)) {
277 LOG(WARNING) << "Failed to get device properties?";
278 return;
279 }
280 auto prop_it = properties.find(shill::kSelectedServiceProperty);
281 if (prop_it == properties.end()) {
282 LOG(WARNING) << "Failed to get device's selected service?";
283 return;
284 }
Vitaly Buka075b3d42015-06-09 08:34:25 -0700285 OnDevicePropertyChange(device_path, shill::kSelectedServiceProperty,
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700286 prop_it->second);
287}
288
289void ShillClient::OnDevicePropertyChange(const ObjectPath& device_path,
290 const string& property_name,
291 const Any& property_value) {
292 // We only care about selected services anyway.
293 if (property_name != shill::kSelectedServiceProperty) {
294 return;
295 }
296 // If the device isn't our list of whitelisted devices, ignore it.
297 auto it = devices_.find(device_path);
298 if (it == devices_.end()) {
299 return;
300 }
301 DeviceState& device_state = it->second;
302 ObjectPath service_path{property_value.TryGet<ObjectPath>()};
303 if (!service_path.IsValid()) {
304 LOG(ERROR) << "Device at " << device_path.value()
305 << " selected invalid service path.";
306 return;
307 }
Vitaly Buka075b3d42015-06-09 08:34:25 -0700308 VLOG(3) << "Device at " << it->first.value() << " has selected service at "
309 << service_path.value();
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700310 bool removed_old_service{false};
311 if (device_state.selected_service) {
312 if (device_state.selected_service->GetObjectPath() == service_path) {
313 return; // Spurious update?
314 }
315 device_state.selected_service.reset();
316 device_state.service_state = ServiceState::kOffline;
317 removed_old_service = true;
318 }
319 std::shared_ptr<ServiceProxy> new_service;
320 const bool reuse_connecting_service =
Vitaly Buka075b3d42015-06-09 08:34:25 -0700321 service_path.value() != "/" && connecting_service_ &&
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700322 connecting_service_->GetObjectPath() == service_path;
323 if (reuse_connecting_service) {
324 new_service = connecting_service_;
325 // When we reuse the connecting service, we need to make sure that our
326 // cached state is correct. Normally, we do this by relying reading the
327 // state when our signal handlers finish registering, but this may have
328 // happened long in the past for the connecting service.
329 string state;
330 if (GetStateForService(new_service.get(), &state)) {
331 device_state.service_state = ShillServiceStateToServiceState(state);
332 } else {
333 LOG(WARNING) << "Failed to read properties from existing service "
334 "on selection.";
335 }
336 } else if (service_path.value() != "/") {
337 // The device has selected a new service we haven't see before.
338 new_service.reset(new ServiceProxy{bus_, service_path});
339 new_service->RegisterPropertyChangedSignalHandler(
340 base::Bind(&ShillClient::OnServicePropertyChange,
Vitaly Buka075b3d42015-06-09 08:34:25 -0700341 weak_factory_.GetWeakPtr(), service_path),
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700342 base::Bind(&ShillClient::OnServicePropertyChangeRegistration,
Vitaly Buka075b3d42015-06-09 08:34:25 -0700343 weak_factory_.GetWeakPtr(), service_path));
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700344 }
345 device_state.selected_service = new_service;
346 if (reuse_connecting_service || removed_old_service) {
347 UpdateConnectivityState();
348 }
349}
350
351void ShillClient::OnServicePropertyChangeRegistration(const ObjectPath& path,
352 const string& interface,
353 const string& signal_name,
354 bool success) {
355 VLOG(3) << "OnServicePropertyChangeRegistration(" << path.value() << ");";
356 ServiceProxy* service{nullptr};
357 if (connecting_service_ && connecting_service_->GetObjectPath() == path) {
358 // Note that the connecting service might also be a selected service.
359 service = connecting_service_.get();
360 if (!success) {
361 CleanupConnectingService(false);
362 }
363 } else {
364 for (const auto& kv : devices_) {
365 if (kv.second.selected_service &&
366 kv.second.selected_service->GetObjectPath() == path) {
367 service = kv.second.selected_service.get();
368 break;
369 }
370 }
371 }
372 if (service == nullptr || !success) {
373 return; // A failure or success for a proxy we no longer care about.
374 }
375 VariantDictionary properties;
376 if (!service->GetProperties(&properties, nullptr)) {
377 return;
378 }
379 // Give ourselves property changed signals for the initial property
380 // values.
381 auto it = properties.find(shill::kStateProperty);
382 if (it != properties.end()) {
Vitaly Buka075b3d42015-06-09 08:34:25 -0700383 OnServicePropertyChange(path, shill::kStateProperty, it->second);
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700384 }
385 it = properties.find(shill::kSignalStrengthProperty);
386 if (it != properties.end()) {
Vitaly Buka075b3d42015-06-09 08:34:25 -0700387 OnServicePropertyChange(path, shill::kSignalStrengthProperty, it->second);
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700388 }
389}
390
391void ShillClient::OnServicePropertyChange(const ObjectPath& service_path,
392 const string& property_name,
393 const Any& property_value) {
394 VLOG(3) << "ServicePropertyChange(" << service_path.value() << ", "
395 << property_name << ", ...);";
396 if (property_name == shill::kStateProperty) {
397 const string state{property_value.TryGet<string>()};
398 if (state.empty()) {
399 VLOG(3) << "Invalid service state update.";
400 return;
401 }
402 VLOG(3) << "New service state=" << state;
403 OnStateChangeForSelectedService(service_path, state);
404 OnStateChangeForConnectingService(service_path, state);
405 } else if (property_name == shill::kSignalStrengthProperty) {
Vitaly Buka075b3d42015-06-09 08:34:25 -0700406 OnStrengthChangeForConnectingService(service_path,
407 property_value.TryGet<uint8_t>());
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700408 }
409}
410
411void ShillClient::OnStateChangeForConnectingService(
412 const ObjectPath& service_path,
413 const string& state) {
414 if (!connecting_service_ ||
415 connecting_service_->GetObjectPath() != service_path ||
416 ShillServiceStateToServiceState(state) != ServiceState::kConnected) {
417 return;
418 }
419 connecting_service_reset_pending_ = true;
420 on_connect_success_.callback().Run();
421 CleanupConnectingService(true);
422}
423
424void ShillClient::OnStrengthChangeForConnectingService(
425 const ObjectPath& service_path,
426 uint8_t signal_strength) {
427 if (!connecting_service_ ||
428 connecting_service_->GetObjectPath() != service_path ||
Vitaly Buka075b3d42015-06-09 08:34:25 -0700429 signal_strength <= 0 || have_called_connect_) {
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700430 return;
431 }
432 VLOG(1) << "Connecting service has signal. Calling Connect().";
433 have_called_connect_ = true;
434 // Failures here indicate that we've already connected,
435 // or are connecting, or some other very unexciting thing.
436 // Ignore all that, and rely on state changes to detect
437 // connectivity.
438 connecting_service_->Connect(nullptr);
439}
440
441void ShillClient::OnStateChangeForSelectedService(
442 const ObjectPath& service_path,
443 const string& state) {
444 // Find the device/service pair responsible for this update
445 VLOG(3) << "State for potentially selected service " << service_path.value()
446 << " have changed to " << state;
447 for (auto& kv : devices_) {
448 if (kv.second.selected_service &&
449 kv.second.selected_service->GetObjectPath() == service_path) {
450 VLOG(3) << "Updated cached connection state for selected service.";
451 kv.second.service_state = ShillServiceStateToServiceState(state);
452 UpdateConnectivityState();
453 return;
454 }
455 }
456}
457
458void ShillClient::UpdateConnectivityState() {
459 // Update the connectivity state of the device by picking the
460 // state of the currently most connected selected service.
461 ServiceState new_connectivity_state{ServiceState::kOffline};
462 for (const auto& kv : devices_) {
463 if (kv.second.service_state > new_connectivity_state) {
464 new_connectivity_state = kv.second.service_state;
465 }
466 }
467 VLOG(3) << "New connectivity state is "
468 << ServiceStateToString(new_connectivity_state);
469 if (new_connectivity_state != connectivity_state_) {
470 connectivity_state_ = new_connectivity_state;
471 // We may call UpdateConnectivityState whenever we mutate a data structure
472 // such that our connectivity status could change. However, we don't want
473 // to allow people to call into ShillClient while some other operation is
474 // underway. Therefore, call our callbacks later, when we're in a good
475 // state.
476 base::MessageLoop::current()->PostTask(
Vitaly Buka075b3d42015-06-09 08:34:25 -0700477 FROM_HERE, base::Bind(&ShillClient::NotifyConnectivityListeners,
478 weak_factory_.GetWeakPtr(), AmOnline()));
Vitaly Buka7ce499f2015-06-09 08:04:11 -0700479 }
480}
481
482void ShillClient::NotifyConnectivityListeners(bool am_online) {
483 VLOG(3) << "Notifying connectivity listeners that online=" << am_online;
484 for (const auto& listener : connectivity_listeners_) {
485 listener.Run(am_online);
486 }
487}
488
489void ShillClient::CleanupConnectingService(bool check_for_reset_pending) {
490 if (check_for_reset_pending && !connecting_service_reset_pending_) {
491 return; // Must have called connect before we got here.
492 }
493 if (connecting_service_) {
494 connecting_service_->ReleaseObjectProxy(base::Bind(&IgnoreDetachEvent));
495 connecting_service_.reset();
496 }
497 on_connect_success_.Cancel();
498 have_called_connect_ = false;
499 connecting_service_reset_pending_ = false;
500}
501
502} // namespace privetd