blob: 8fd0b3b726871289aad0283dd2e4ba031b23f06a [file] [log] [blame]
Darin Petkovb72b62e2012-05-15 16:55:36 +02001// Copyright (c) 2012 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 "shill/wimax_provider.h"
6
Darin Petkove4b27022012-05-16 13:28:50 +02007#include <algorithm>
Darin Petkovfc00fd42012-05-30 11:30:06 +02008#include <set>
Darin Petkove4b27022012-05-16 13:28:50 +02009
Darin Petkovb72b62e2012-05-15 16:55:36 +020010#include <base/bind.h>
Darin Petkove4b27022012-05-16 13:28:50 +020011#include <base/string_util.h>
Ben Chanb879d142012-05-15 11:10:32 -070012#include <chromeos/dbus/service_constants.h>
Darin Petkovb72b62e2012-05-15 16:55:36 +020013
Darin Petkovb72b62e2012-05-15 16:55:36 +020014#include "shill/error.h"
Darin Petkovd1cd7972012-05-22 15:26:15 +020015#include "shill/key_value_store.h"
Christopher Wileyb691efd2012-08-09 13:51:51 -070016#include "shill/logging.h"
Darin Petkove4b27022012-05-16 13:28:50 +020017#include "shill/manager.h"
Darin Petkovc63dcf02012-05-24 11:51:43 +020018#include "shill/profile.h"
Darin Petkovb72b62e2012-05-15 16:55:36 +020019#include "shill/proxy_factory.h"
Darin Petkovc63dcf02012-05-24 11:51:43 +020020#include "shill/store_interface.h"
Darin Petkove4b27022012-05-16 13:28:50 +020021#include "shill/wimax.h"
Darin Petkovb72b62e2012-05-15 16:55:36 +020022#include "shill/wimax_manager_proxy_interface.h"
Darin Petkovd1cd7972012-05-22 15:26:15 +020023#include "shill/wimax_service.h"
Darin Petkovb72b62e2012-05-15 16:55:36 +020024
25using base::Bind;
26using base::Unretained;
Darin Petkove4b27022012-05-16 13:28:50 +020027using std::find;
28using std::map;
Darin Petkovc63dcf02012-05-24 11:51:43 +020029using std::set;
Darin Petkovb72b62e2012-05-15 16:55:36 +020030using std::string;
Darin Petkovb72b62e2012-05-15 16:55:36 +020031
32namespace shill {
33
Darin Petkovb72b62e2012-05-15 16:55:36 +020034WiMaxProvider::WiMaxProvider(ControlInterface *control,
35 EventDispatcher *dispatcher,
36 Metrics *metrics,
37 Manager *manager)
38 : control_(control),
39 dispatcher_(dispatcher),
40 metrics_(metrics),
41 manager_(manager),
42 proxy_factory_(ProxyFactory::GetInstance()) {}
43
Darin Petkovc63dcf02012-05-24 11:51:43 +020044WiMaxProvider::~WiMaxProvider() {}
Darin Petkovb72b62e2012-05-15 16:55:36 +020045
46void WiMaxProvider::Start() {
47 SLOG(WiMax, 2) << __func__;
Darin Petkovb501ad22012-07-03 12:50:52 +020048 if (!on_wimax_manager_appear_.IsCancelled()) {
Darin Petkovb72b62e2012-05-15 16:55:36 +020049 return;
50 }
Darin Petkovb501ad22012-07-03 12:50:52 +020051 // Registers a watcher for the WiMaxManager service. This provider will
52 // connect to it if/when the OnWiMaxManagerAppear callback is invoked.
53 on_wimax_manager_appear_.Reset(
54 Bind(&WiMaxProvider::OnWiMaxManagerAppear, Unretained(this)));
55 on_wimax_manager_vanish_.Reset(
56 Bind(&WiMaxProvider::DisconnectFromWiMaxManager, Unretained(this)));
57 manager_->dbus_manager()->WatchName(
58 wimax_manager::kWiMaxManagerServiceName,
59 on_wimax_manager_appear_.callback(),
60 on_wimax_manager_vanish_.callback());
Darin Petkovb72b62e2012-05-15 16:55:36 +020061}
62
63void WiMaxProvider::Stop() {
64 SLOG(WiMax, 2) << __func__;
Darin Petkovb501ad22012-07-03 12:50:52 +020065 // TODO(petkov): Deregister the watcher from DBusManager to avoid potential
66 // memory leaks (crosbug.com/32226).
67 on_wimax_manager_appear_.Cancel();
68 on_wimax_manager_vanish_.Cancel();
69 DisconnectFromWiMaxManager();
Darin Petkovc63dcf02012-05-24 11:51:43 +020070 DestroyAllServices();
Darin Petkovb72b62e2012-05-15 16:55:36 +020071}
72
Darin Petkovb501ad22012-07-03 12:50:52 +020073void WiMaxProvider::ConnectToWiMaxManager() {
74 DCHECK(!wimax_manager_proxy_.get());
75 LOG(INFO) << "Connecting to WiMaxManager.";
76 wimax_manager_proxy_.reset(proxy_factory_->CreateWiMaxManagerProxy());
77 wimax_manager_proxy_->set_devices_changed_callback(
78 Bind(&WiMaxProvider::OnDevicesChanged, Unretained(this)));
79 Error error;
80 OnDevicesChanged(wimax_manager_proxy_->Devices(&error));
81}
82
83void WiMaxProvider::DisconnectFromWiMaxManager() {
84 SLOG(WiMax, 2) << __func__;
85 if (!wimax_manager_proxy_.get()) {
86 return;
87 }
88 LOG(INFO) << "Disconnecting from WiMaxManager.";
89 wimax_manager_proxy_.reset();
90 OnDevicesChanged(RpcIdentifiers());
91}
92
93void WiMaxProvider::OnWiMaxManagerAppear(const string &owner) {
94 SLOG(WiMax, 2) << __func__ << "(" << owner << ")";
95 DisconnectFromWiMaxManager();
96 ConnectToWiMaxManager();
97}
98
Darin Petkove4b27022012-05-16 13:28:50 +020099void WiMaxProvider::OnDeviceInfoAvailable(const string &link_name) {
100 SLOG(WiMax, 2) << __func__ << "(" << link_name << ")";
Darin Petkovfc00fd42012-05-30 11:30:06 +0200101 map<string, RpcIdentifier>::const_iterator find_it =
Darin Petkovc1e52732012-05-25 15:23:45 +0200102 pending_devices_.find(link_name);
Darin Petkove4b27022012-05-16 13:28:50 +0200103 if (find_it != pending_devices_.end()) {
104 RpcIdentifier path = find_it->second;
105 CreateDevice(link_name, path);
106 }
107}
108
Darin Petkovc63dcf02012-05-24 11:51:43 +0200109void WiMaxProvider::OnNetworksChanged() {
110 SLOG(WiMax, 2) << __func__;
Darin Petkovfc00fd42012-05-30 11:30:06 +0200111 // Collects a set of live networks from all devices.
112 set<RpcIdentifier> live_networks;
113 for (map<string, WiMaxRefPtr>::const_iterator it = devices_.begin();
Darin Petkovc63dcf02012-05-24 11:51:43 +0200114 it != devices_.end(); ++it) {
115 const set<RpcIdentifier> &networks = it->second->networks();
Darin Petkovfc00fd42012-05-30 11:30:06 +0200116 live_networks.insert(networks.begin(), networks.end());
Darin Petkovc63dcf02012-05-24 11:51:43 +0200117 }
Darin Petkovfc00fd42012-05-30 11:30:06 +0200118 // Removes dead networks from |networks_|.
119 for (map<RpcIdentifier, NetworkInfo>::iterator it = networks_.begin();
120 it != networks_.end(); ) {
Darin Petkovb96a4512012-06-04 11:02:49 +0200121 const RpcIdentifier &path = it->first;
122 if (ContainsKey(live_networks, path)) {
Darin Petkovfc00fd42012-05-30 11:30:06 +0200123 ++it;
124 } else {
Darin Petkovb96a4512012-06-04 11:02:49 +0200125 LOG(INFO) << "WiMAX network disappeared: " << path;
Darin Petkovfc00fd42012-05-30 11:30:06 +0200126 networks_.erase(it++);
127 }
128 }
129 // Retrieves network info into |networks_| for the live networks.
130 for (set<RpcIdentifier>::const_iterator it = live_networks.begin();
131 it != live_networks.end(); ++it) {
132 RetrieveNetworkInfo(*it);
133 }
134 // Stops dead and starts live services based on the current |networks_|.
Darin Petkovc63dcf02012-05-24 11:51:43 +0200135 StopDeadServices();
136 StartLiveServices();
137}
138
Darin Petkovc1e52732012-05-25 15:23:45 +0200139bool WiMaxProvider::OnServiceUnloaded(const WiMaxServiceRefPtr &service) {
140 SLOG(WiMax, 2) << __func__ << "(" << service->GetStorageIdentifier() << ")";
141 if (service->is_default()) {
142 return false;
143 }
144 // Removes the service from the managed service set. The service will be
145 // deregistered from Manager when we release ownership by returning true.
146 services_.erase(service->GetStorageIdentifier());
147 return true;
148}
149
Darin Petkovd1cd7972012-05-22 15:26:15 +0200150WiMaxServiceRefPtr WiMaxProvider::GetService(const KeyValueStore &args,
151 Error *error) {
152 SLOG(WiMax, 2) << __func__;
153 CHECK_EQ(args.GetString(flimflam::kTypeProperty), flimflam::kTypeWimax);
Darin Petkovd1cd7972012-05-22 15:26:15 +0200154 WiMaxNetworkId id = args.LookupString(WiMaxService::kNetworkIdProperty, "");
155 if (id.empty()) {
156 Error::PopulateAndLog(
157 error, Error::kInvalidArguments, "Missing WiMAX network id.");
158 return NULL;
159 }
160 string name = args.LookupString(flimflam::kNameProperty, "");
161 if (name.empty()) {
162 Error::PopulateAndLog(
163 error, Error::kInvalidArguments, "Missing WiMAX service name.");
164 return NULL;
165 }
Darin Petkovc63dcf02012-05-24 11:51:43 +0200166 WiMaxServiceRefPtr service = GetUniqueService(id, name);
Darin Petkovd1cd7972012-05-22 15:26:15 +0200167 CHECK(service);
Darin Petkovfc00fd42012-05-30 11:30:06 +0200168 // Configures the service using the rest of the passed-in arguments.
Darin Petkovd1cd7972012-05-22 15:26:15 +0200169 service->Configure(args, error);
Darin Petkovfc00fd42012-05-30 11:30:06 +0200170 // Starts the service if there's a matching live network.
Darin Petkovc63dcf02012-05-24 11:51:43 +0200171 StartLiveServices();
Darin Petkovd1cd7972012-05-22 15:26:15 +0200172 return service;
173}
174
Darin Petkovc63dcf02012-05-24 11:51:43 +0200175void WiMaxProvider::CreateServicesFromProfile(const ProfileRefPtr &profile) {
176 SLOG(WiMax, 2) << __func__;
177 bool created = false;
178 const StoreInterface *storage = profile->GetConstStorage();
179 set<string> groups = storage->GetGroupsWithKey(Service::kStorageType);
Darin Petkovfc00fd42012-05-30 11:30:06 +0200180 for (set<string>::const_iterator it = groups.begin();
181 it != groups.end(); ++it) {
Darin Petkovc63dcf02012-05-24 11:51:43 +0200182 const string &storage_id = *it;
183 string type;
184 if (!storage->GetString(storage_id, Service::kStorageType, &type) ||
185 type != Technology::NameFromIdentifier(Technology::kWiMax)) {
186 continue;
187 }
188 if (FindService(storage_id)) {
189 continue;
190 }
191 WiMaxNetworkId id;
192 if (!storage->GetString(storage_id, WiMaxService::kStorageNetworkId, &id) ||
193 id.empty()) {
194 LOG(ERROR) << "Unable to load network id: " << storage_id;
195 continue;
196 }
197 string name;
198 if (!storage->GetString(storage_id, Service::kStorageName, &name) ||
199 name.empty()) {
200 LOG(ERROR) << "Unable to load service name: " << storage_id;
201 continue;
202 }
203 WiMaxServiceRefPtr service = GetUniqueService(id, name);
204 CHECK(service);
205 if (!profile->ConfigureService(service)) {
206 LOG(ERROR) << "Could not configure service: " << storage_id;
207 }
208 created = true;
209 }
210 if (created) {
211 StartLiveServices();
212 }
213}
214
Darin Petkov6b9b2e12012-07-10 15:51:42 +0200215WiMaxRefPtr WiMaxProvider::SelectCarrier(
216 const WiMaxServiceConstRefPtr &service) {
Darin Petkovc63dcf02012-05-24 11:51:43 +0200217 SLOG(WiMax, 2) << __func__ << "(" << service->GetStorageIdentifier() << ")";
218 if (devices_.empty()) {
219 LOG(ERROR) << "No WiMAX devices available.";
220 return NULL;
221 }
222 // TODO(petkov): For now, just return the first available device. We need to
223 // be smarter here and select a device that sees |service|'s network.
224 return devices_.begin()->second;
225}
226
227void WiMaxProvider::OnDevicesChanged(const RpcIdentifiers &devices) {
228 SLOG(WiMax, 2) << __func__;
229 DestroyDeadDevices(devices);
230 for (RpcIdentifiers::const_iterator it = devices.begin();
231 it != devices.end(); ++it) {
232 const RpcIdentifier &path = *it;
233 string link_name = GetLinkName(path);
234 if (!link_name.empty()) {
235 CreateDevice(link_name, path);
236 }
237 }
238}
239
Darin Petkove4b27022012-05-16 13:28:50 +0200240void WiMaxProvider::CreateDevice(const string &link_name,
241 const RpcIdentifier &path) {
242 SLOG(WiMax, 2) << __func__ << "(" << link_name << ", " << path << ")";
Darin Petkov0fcd3462012-05-17 11:25:11 +0200243 if (ContainsKey(devices_, link_name)) {
244 SLOG(WiMax, 2) << "Device already exists.";
245 CHECK_EQ(path, devices_[link_name]->path());
246 return;
247 }
Darin Petkove4b27022012-05-16 13:28:50 +0200248 pending_devices_.erase(link_name);
249 DeviceInfo *device_info = manager_->device_info();
250 if (device_info->IsDeviceBlackListed(link_name)) {
Darin Petkovc63dcf02012-05-24 11:51:43 +0200251 LOG(INFO) << "WiMAX device not created, interface blacklisted: "
Darin Petkove4b27022012-05-16 13:28:50 +0200252 << link_name;
253 return;
254 }
255 int index = device_info->GetIndex(link_name);
256 if (index < 0) {
257 SLOG(WiMax, 2) << link_name << " pending device info.";
258 // Adds the link to the pending device map, waiting for a notification from
259 // DeviceInfo that it's received information about the device from RTNL.
260 pending_devices_[link_name] = path;
261 return;
262 }
263 ByteString address_bytes;
264 if (!device_info->GetMACAddress(index, &address_bytes)) {
Darin Petkovc63dcf02012-05-24 11:51:43 +0200265 LOG(ERROR) << "Unable to create a WiMAX device with no MAC address: "
Darin Petkove4b27022012-05-16 13:28:50 +0200266 << link_name;
267 return;
268 }
269 string address = address_bytes.HexEncode();
270 WiMaxRefPtr device(new WiMax(control_, dispatcher_, metrics_, manager_,
271 link_name, address, index, path));
Darin Petkov0fcd3462012-05-17 11:25:11 +0200272 devices_[link_name] = device;
Darin Petkove4b27022012-05-16 13:28:50 +0200273 device_info->RegisterDevice(device);
Darin Petkovc63dcf02012-05-24 11:51:43 +0200274 LOG(INFO) << "Created WiMAX device: " << link_name << " @ " << path;
Darin Petkove4b27022012-05-16 13:28:50 +0200275}
276
277void WiMaxProvider::DestroyDeadDevices(const RpcIdentifiers &live_devices) {
278 SLOG(WiMax, 2) << __func__ << "(" << live_devices.size() << ")";
Darin Petkovc1e52732012-05-25 15:23:45 +0200279 for (map<string, RpcIdentifier>::iterator it = pending_devices_.begin();
Darin Petkove4b27022012-05-16 13:28:50 +0200280 it != pending_devices_.end(); ) {
281 if (find(live_devices.begin(), live_devices.end(), it->second) ==
282 live_devices.end()) {
Darin Petkovc63dcf02012-05-24 11:51:43 +0200283 LOG(INFO) << "Forgetting pending device: " << it->second;
Darin Petkove4b27022012-05-16 13:28:50 +0200284 pending_devices_.erase(it++);
285 } else {
286 ++it;
287 }
288 }
Darin Petkov0fcd3462012-05-17 11:25:11 +0200289 for (map<string, WiMaxRefPtr>::iterator it = devices_.begin();
Darin Petkove4b27022012-05-16 13:28:50 +0200290 it != devices_.end(); ) {
Darin Petkov0fcd3462012-05-17 11:25:11 +0200291 if (find(live_devices.begin(), live_devices.end(), it->second->path()) ==
Darin Petkove4b27022012-05-16 13:28:50 +0200292 live_devices.end()) {
Darin Petkovc63dcf02012-05-24 11:51:43 +0200293 LOG(INFO) << "Destroying device: " << it->first;
Darin Petkovb96a4512012-06-04 11:02:49 +0200294 const WiMaxRefPtr &device = it->second;
295 device->OnDeviceVanished();
296 manager_->device_info()->DeregisterDevice(device);
Darin Petkov0fcd3462012-05-17 11:25:11 +0200297 devices_.erase(it++);
Darin Petkove4b27022012-05-16 13:28:50 +0200298 } else {
299 ++it;
300 }
301 }
302}
303
304string WiMaxProvider::GetLinkName(const RpcIdentifier &path) {
Darin Petkov9893d9c2012-05-17 15:27:31 -0700305 if (StartsWithASCII(path, wimax_manager::kDeviceObjectPathPrefix, true)) {
306 return path.substr(strlen(wimax_manager::kDeviceObjectPathPrefix));
Darin Petkove4b27022012-05-16 13:28:50 +0200307 }
308 LOG(ERROR) << "Unable to determine link name from RPC path: " << path;
309 return string();
Darin Petkovb72b62e2012-05-15 16:55:36 +0200310}
311
Darin Petkovfc00fd42012-05-30 11:30:06 +0200312void WiMaxProvider::RetrieveNetworkInfo(const RpcIdentifier &path) {
313 if (ContainsKey(networks_, path)) {
314 // Nothing to do, the network info is already available.
315 return;
316 }
Darin Petkovb96a4512012-06-04 11:02:49 +0200317 LOG(INFO) << "WiMAX network appeared: " << path;
Darin Petkovfc00fd42012-05-30 11:30:06 +0200318 scoped_ptr<WiMaxNetworkProxyInterface> proxy(
319 proxy_factory_->CreateWiMaxNetworkProxy(path));
320 Error error;
321 NetworkInfo info;
322 info.name = proxy->Name(&error);
323 if (error.IsFailure()) {
324 return;
325 }
326 uint32 identifier = proxy->Identifier(&error);
327 if (error.IsFailure()) {
328 return;
329 }
330 info.id = WiMaxService::ConvertIdentifierToNetworkId(identifier);
331 networks_[path] = info;
332}
333
Darin Petkovc63dcf02012-05-24 11:51:43 +0200334WiMaxServiceRefPtr WiMaxProvider::FindService(const string &storage_id) {
335 SLOG(WiMax, 2) << __func__ << "(" << storage_id << ")";
Darin Petkovc1e52732012-05-25 15:23:45 +0200336 map<string, WiMaxServiceRefPtr>::const_iterator find_it =
337 services_.find(storage_id);
338 if (find_it == services_.end()) {
339 return NULL;
Darin Petkovc63dcf02012-05-24 11:51:43 +0200340 }
Darin Petkovc1e52732012-05-25 15:23:45 +0200341 const WiMaxServiceRefPtr &service = find_it->second;
342 LOG_IF(ERROR, storage_id != service->GetStorageIdentifier());
343 return service;
Darin Petkovc63dcf02012-05-24 11:51:43 +0200344}
345
346WiMaxServiceRefPtr WiMaxProvider::GetUniqueService(const WiMaxNetworkId &id,
347 const string &name) {
348 SLOG(WiMax, 2) << __func__ << "(" << id << ", " << name << ")";
349 string storage_id = WiMaxService::CreateStorageIdentifier(id, name);
350 WiMaxServiceRefPtr service = FindService(storage_id);
351 if (service) {
352 SLOG(WiMax, 2) << "Service already exists.";
353 return service;
354 }
355 service = new WiMaxService(control_, dispatcher_, metrics_, manager_);
356 service->set_network_id(id);
357 service->set_friendly_name(name);
358 service->InitStorageIdentifier();
Darin Petkovc1e52732012-05-25 15:23:45 +0200359 services_[service->GetStorageIdentifier()] = service;
Darin Petkovc63dcf02012-05-24 11:51:43 +0200360 manager_->RegisterService(service);
361 LOG(INFO) << "Registered WiMAX service: " << service->GetStorageIdentifier();
362 return service;
363}
364
Darin Petkovc63dcf02012-05-24 11:51:43 +0200365void WiMaxProvider::StartLiveServices() {
Darin Petkovfc00fd42012-05-30 11:30:06 +0200366 SLOG(WiMax, 2) << __func__ << "(" << networks_.size() << ")";
367 for (map<RpcIdentifier, NetworkInfo>::const_iterator nit = networks_.begin();
368 nit != networks_.end(); ++nit) {
369 const RpcIdentifier &path = nit->first;
370 const NetworkInfo &info = nit->second;
Darin Petkovc63dcf02012-05-24 11:51:43 +0200371
Darin Petkovfc00fd42012-05-30 11:30:06 +0200372 // Creates the default service for the network, if not already created.
373 GetUniqueService(info.id, info.name)->set_is_default(true);
374
375 // Starts services for this live network.
376 for (map<string, WiMaxServiceRefPtr>::const_iterator it = services_.begin();
377 it != services_.end(); ++it) {
378 const WiMaxServiceRefPtr &service = it->second;
379 if (service->network_id() != info.id || service->IsStarted()) {
380 continue;
381 }
382 if (!service->Start(proxy_factory_->CreateWiMaxNetworkProxy(path))) {
383 LOG(ERROR) << "Unable to start service: "
384 << service->GetStorageIdentifier();
385 }
Darin Petkovc63dcf02012-05-24 11:51:43 +0200386 }
387 }
388}
389
390void WiMaxProvider::StopDeadServices() {
391 SLOG(WiMax, 2) << __func__ << "(" << networks_.size() << ")";
Darin Petkovc1e52732012-05-25 15:23:45 +0200392 for (map<string, WiMaxServiceRefPtr>::iterator it = services_.begin();
393 it != services_.end(); ) {
Darin Petkovc33eba82012-06-28 10:51:16 +0200394 // Keeps a local reference until we're done with this service.
395 WiMaxServiceRefPtr service = it->second;
Darin Petkovc1e52732012-05-25 15:23:45 +0200396 if (service->IsStarted() &&
397 !ContainsKey(networks_, service->GetNetworkObjectPath())) {
398 service->Stop();
399 // Default services are created and registered when a network becomes
400 // live. They need to be deregistered and destroyed when the network
401 // disappears.
402 if (service->is_default()) {
Darin Petkovc33eba82012-06-28 10:51:16 +0200403 // Removes |service| from the managed service set before deregistering
404 // it from Manager to ensure correct iterator increment (consider
405 // Manager::DeregisterService -> WiMaxService::Unload ->
406 // WiMaxProvider::OnServiceUnloaded -> services_.erase).
Darin Petkovc1e52732012-05-25 15:23:45 +0200407 services_.erase(it++);
Darin Petkovc33eba82012-06-28 10:51:16 +0200408 manager_->DeregisterService(service);
Darin Petkovc1e52732012-05-25 15:23:45 +0200409 continue;
410 }
Darin Petkovc63dcf02012-05-24 11:51:43 +0200411 }
Darin Petkovc1e52732012-05-25 15:23:45 +0200412 ++it;
Darin Petkovc63dcf02012-05-24 11:51:43 +0200413 }
414}
415
416void WiMaxProvider::DestroyAllServices() {
417 SLOG(WiMax, 2) << __func__;
Darin Petkovc33eba82012-06-28 10:51:16 +0200418 while (!services_.empty()) {
419 // Keeps a local reference until we're done with this service.
420 WiMaxServiceRefPtr service = services_.begin()->second;
421 services_.erase(services_.begin());
422 // Stops the service so that it can notify its carrier device, if any.
Darin Petkovc63dcf02012-05-24 11:51:43 +0200423 service->Stop();
424 manager_->DeregisterService(service);
425 LOG(INFO) << "Deregistered WiMAX service: "
426 << service->GetStorageIdentifier();
Darin Petkovc63dcf02012-05-24 11:51:43 +0200427 }
428}
429
Darin Petkovb72b62e2012-05-15 16:55:36 +0200430} // namespace shill