blob: 733947b2d6fa049606a7982de8431ebac81b3f95 [file] [log] [blame]
mukesh agrawal8a3188d2011-12-01 20:56:44 +00001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Paul Stewart75897df2011-04-27 09:05:53 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Chris Masone8fe2c7e2011-06-09 15:51:19 -07005#include "shill/manager.h"
6
Paul Stewart75897df2011-04-27 09:05:53 -07007#include <time.h>
Paul Stewart75897df2011-04-27 09:05:53 -07008#include <stdio.h>
Chris Masoneee929b72011-05-10 10:02:18 -07009
Paul Stewart22aa71b2011-09-16 12:15:11 -070010#include <algorithm>
mukesh agrawal7a4e4002011-09-06 11:26:05 -070011#include <map>
Paul Stewart75897df2011-04-27 09:05:53 -070012#include <string>
Chris Masone52cd19b2011-06-29 17:23:04 -070013#include <vector>
Paul Stewart75897df2011-04-27 09:05:53 -070014
Paul Stewarte6132022011-08-16 09:11:02 -070015#include <base/file_util.h>
Chris Masoneee929b72011-05-10 10:02:18 -070016#include <base/logging.h>
Chris Masone9be4a9d2011-05-16 15:44:09 -070017#include <base/memory/ref_counted.h>
Gaurav Shah435de2c2011-11-17 19:01:07 -080018#include <base/stl_util-inl.h>
Paul Stewart22aa71b2011-09-16 12:15:11 -070019#include <base/string_split.h>
20#include <base/string_util.h>
Chris Masone3bd3c8c2011-06-13 08:20:26 -070021#include <chromeos/dbus/service_constants.h>
Chris Masoneee929b72011-05-10 10:02:18 -070022
Chris Masoned0ceb8c2011-06-02 10:05:39 -070023#include "shill/adaptor_interfaces.h"
Paul Stewartc1dec4d2011-12-08 15:25:28 -080024#include "shill/connection.h"
Paul Stewart75897df2011-04-27 09:05:53 -070025#include "shill/control_interface.h"
Chris Masoned0ceb8c2011-06-02 10:05:39 -070026#include "shill/dbus_adaptor.h"
Chris Masone7aa5f902011-07-11 11:13:35 -070027#include "shill/default_profile.h"
Chris Masone9be4a9d2011-05-16 15:44:09 -070028#include "shill/device.h"
29#include "shill/device_info.h"
Chris Masone6791a432011-07-12 13:23:19 -070030#include "shill/ephemeral_profile.h"
Chris Masone8fe2c7e2011-06-09 15:51:19 -070031#include "shill/error.h"
Paul Stewart26b327e2011-10-19 11:38:09 -070032#include "shill/event_dispatcher.h"
Chris Masone9d779932011-08-25 16:33:41 -070033#include "shill/key_file_store.h"
Thieu Lea20cbc22012-01-09 22:01:43 +000034#include "shill/metrics.h"
Chris Masone7aa5f902011-07-11 11:13:35 -070035#include "shill/profile.h"
Chris Masoneb925cc82011-06-22 15:39:57 -070036#include "shill/property_accessor.h"
Paul Stewarte6132022011-08-16 09:11:02 -070037#include "shill/resolver.h"
Chris Masone9be4a9d2011-05-16 15:44:09 -070038#include "shill/service.h"
Thieu Lea20cbc22012-01-09 22:01:43 +000039#include "shill/service_sorter.h"
mukesh agrawal7a4e4002011-09-06 11:26:05 -070040#include "shill/wifi.h"
41#include "shill/wifi_service.h"
Paul Stewart75897df2011-04-27 09:05:53 -070042
Paul Stewart22aa71b2011-09-16 12:15:11 -070043using std::map;
Gaurav Shah435de2c2011-11-17 19:01:07 -080044using std::set;
Paul Stewart75897df2011-04-27 09:05:53 -070045using std::string;
Chris Masone9be4a9d2011-05-16 15:44:09 -070046using std::vector;
Paul Stewart75897df2011-04-27 09:05:53 -070047
48namespace shill {
Paul Stewarte6132022011-08-16 09:11:02 -070049
mukesh agrawal7a4e4002011-09-06 11:26:05 -070050// static
51const char Manager::kManagerErrorNoDevice[] = "no wifi devices available";
52
Paul Stewart75897df2011-04-27 09:05:53 -070053Manager::Manager(ControlInterface *control_interface,
Darin Petkov887f2982011-07-14 16:10:17 -070054 EventDispatcher *dispatcher,
Thieu Le3426c8f2012-01-11 17:35:11 -080055 Metrics *metrics,
Chris Masone2ae797d2011-08-23 20:41:00 -070056 GLib *glib,
57 const string &run_directory,
58 const string &storage_directory,
59 const string &user_storage_format)
mukesh agrawal8a3188d2011-12-01 20:56:44 +000060 : dispatcher_(dispatcher),
61 task_factory_(this),
62 run_path_(FilePath(run_directory)),
63 storage_path_(FilePath(storage_directory)),
64 user_storage_format_(user_storage_format),
65 adaptor_(control_interface->CreateManagerAdaptor(this)),
Thieu Le3426c8f2012-01-11 17:35:11 -080066 device_info_(control_interface, dispatcher, metrics, this),
67 modem_info_(control_interface, dispatcher, metrics, this, glib),
mukesh agrawal8a3188d2011-12-01 20:56:44 +000068 running_(false),
69 connect_profiles_to_rpc_(true),
70 ephemeral_profile_(new EphemeralProfile(control_interface, this)),
71 control_interface_(control_interface),
Thieu Le3426c8f2012-01-11 17:35:11 -080072 metrics_(metrics),
mukesh agrawal8a3188d2011-12-01 20:56:44 +000073 glib_(glib) {
Chris Masone7aa5f902011-07-11 11:13:35 -070074 HelpRegisterDerivedString(flimflam::kActiveProfileProperty,
Paul Stewart1b253142012-01-26 14:05:52 -080075 &Manager::GetActiveProfileRpcIdentifier,
Chris Masone7aa5f902011-07-11 11:13:35 -070076 NULL);
Chris Masone27c4aa52011-07-02 13:10:14 -070077 HelpRegisterDerivedStrings(flimflam::kAvailableTechnologiesProperty,
78 &Manager::AvailableTechnologies,
79 NULL);
Chris Masone88cbd5f2011-07-03 14:30:04 -070080 store_.RegisterString(flimflam::kCheckPortalListProperty,
81 &props_.check_portal_list);
Chris Masone27c4aa52011-07-02 13:10:14 -070082 HelpRegisterDerivedStrings(flimflam::kConnectedTechnologiesProperty,
83 &Manager::ConnectedTechnologies,
84 NULL);
Chris Masone88cbd5f2011-07-03 14:30:04 -070085 store_.RegisterString(flimflam::kCountryProperty, &props_.country);
Chris Masone27c4aa52011-07-02 13:10:14 -070086 HelpRegisterDerivedString(flimflam::kDefaultTechnologyProperty,
87 &Manager::DefaultTechnology,
88 NULL);
Chris Masone27c4aa52011-07-02 13:10:14 -070089 HelpRegisterDerivedStrings(flimflam::kDevicesProperty,
90 &Manager::EnumerateDevices,
91 NULL);
Chris Masone88cbd5f2011-07-03 14:30:04 -070092 HelpRegisterDerivedStrings(flimflam::kEnabledTechnologiesProperty,
93 &Manager::EnabledTechnologies,
94 NULL);
95 store_.RegisterBool(flimflam::kOfflineModeProperty, &props_.offline_mode);
96 store_.RegisterString(flimflam::kPortalURLProperty, &props_.portal_url);
Paul Stewart1b253142012-01-26 14:05:52 -080097 HelpRegisterDerivedStrings(flimflam::kProfilesProperty,
98 &Manager::EnumerateProfiles,
99 NULL);
Paul Stewartd32f4842012-01-11 16:08:13 -0800100 store_.RegisterString(shill::kHostNameProperty, &props_.host_name);
Chris Masone88cbd5f2011-07-03 14:30:04 -0700101 HelpRegisterDerivedString(flimflam::kStateProperty,
102 &Manager::CalculateState,
103 NULL);
Chris Masone27c4aa52011-07-02 13:10:14 -0700104 HelpRegisterDerivedStrings(flimflam::kServicesProperty,
105 &Manager::EnumerateAvailableServices,
106 NULL);
107 HelpRegisterDerivedStrings(flimflam::kServiceWatchListProperty,
108 &Manager::EnumerateWatchedServices,
109 NULL);
Chris Masone3c3f6a12011-07-01 10:01:41 -0700110
Chris Masoneb07006b2011-05-14 16:10:04 -0700111 VLOG(2) << "Manager initialized.";
Paul Stewart75897df2011-04-27 09:05:53 -0700112}
113
Chris Masone6791a432011-07-12 13:23:19 -0700114Manager::~Manager() {
Chris Masone9d779932011-08-25 16:33:41 -0700115 profiles_.clear();
Chris Masone6791a432011-07-12 13:23:19 -0700116}
Paul Stewart75897df2011-04-27 09:05:53 -0700117
mukesh agrawal8f317b62011-07-15 11:53:23 -0700118void Manager::AddDeviceToBlackList(const string &device_name) {
119 device_info_.AddDeviceToBlackList(device_name);
120}
121
Paul Stewart75897df2011-04-27 09:05:53 -0700122void Manager::Start() {
Paul Stewart0af98bf2011-05-10 17:38:08 -0700123 LOG(INFO) << "Manager started.";
Paul Stewarte6132022011-08-16 09:11:02 -0700124
Chris Masone2ae797d2011-08-23 20:41:00 -0700125 CHECK(file_util::CreateDirectory(run_path_)) << run_path_.value();
Paul Stewarte6132022011-08-16 09:11:02 -0700126 Resolver::GetInstance()->set_path(run_path_.Append("resolv.conf"));
Chris Masone2ae797d2011-08-23 20:41:00 -0700127
Gaurav Shah71354762011-11-28 19:22:49 -0800128 InitializeProfiles();
Paul Stewart75897df2011-04-27 09:05:53 -0700129 running_ = true;
Chris Masone413a3192011-05-09 17:10:05 -0700130 adaptor_->UpdateRunning();
Paul Stewart0af98bf2011-05-10 17:38:08 -0700131 device_info_.Start();
Darin Petkov887f2982011-07-14 16:10:17 -0700132 modem_info_.Start();
Paul Stewart75897df2011-04-27 09:05:53 -0700133}
134
135void Manager::Stop() {
136 running_ = false;
Chris Masone9d779932011-08-25 16:33:41 -0700137 // Persist profile, device, service information to disk.
138 vector<ProfileRefPtr>::iterator it;
139 for (it = profiles_.begin(); it != profiles_.end(); ++it) {
Chris Masone6515aab2011-10-12 16:19:09 -0700140 (*it)->Save();
Chris Masone9d779932011-08-25 16:33:41 -0700141 }
Chris Masone9d779932011-08-25 16:33:41 -0700142
Thieu Le1271d682011-11-02 22:48:19 +0000143 vector<ServiceRefPtr>::iterator services_it;
mukesh agrawal0ed0f2e2011-12-05 20:36:17 +0000144 Error e;
Thieu Le1271d682011-11-02 22:48:19 +0000145 for (services_it = services_.begin(); services_it != services_.end();
146 ++services_it) {
mukesh agrawal0ed0f2e2011-12-05 20:36:17 +0000147 (*services_it)->Disconnect(&e);
Thieu Le1271d682011-11-02 22:48:19 +0000148 }
149
Chris Masone413a3192011-05-09 17:10:05 -0700150 adaptor_->UpdateRunning();
Darin Petkov887f2982011-07-14 16:10:17 -0700151 modem_info_.Stop();
152 device_info_.Stop();
Paul Stewart75897df2011-04-27 09:05:53 -0700153}
154
Gaurav Shah71354762011-11-28 19:22:49 -0800155void Manager::InitializeProfiles() {
156 DCHECK(profiles_.empty());
157 // The default profile must go first on the stack.
158 CHECK(file_util::CreateDirectory(storage_path_)) << storage_path_.value();
Paul Stewart870523b2012-01-11 17:00:42 -0800159 scoped_refptr<DefaultProfile>
160 default_profile(new DefaultProfile(control_interface_,
Gaurav Shah71354762011-11-28 19:22:49 -0800161 this,
162 storage_path_,
163 props_));
Paul Stewart870523b2012-01-11 17:00:42 -0800164 CHECK(default_profile->InitStorage(glib_, Profile::kCreateOrOpenExisting,
165 NULL));
166 CHECK(default_profile->LoadManagerProperties(&props_));
167 profiles_.push_back(default_profile.release());
Gaurav Shah71354762011-11-28 19:22:49 -0800168 Error error;
Paul Stewart19c871d2011-12-15 16:10:13 -0800169 string path;
Gaurav Shah71354762011-11-28 19:22:49 -0800170 for (vector<string>::iterator it = startup_profiles_.begin();
171 it != startup_profiles_.end(); ++it)
Paul Stewart19c871d2011-12-15 16:10:13 -0800172 PushProfile(*it, &path, &error);
Gaurav Shah71354762011-11-28 19:22:49 -0800173}
174
Paul Stewart19c871d2011-12-15 16:10:13 -0800175void Manager::CreateProfile(const string &name, string *path, Error *error) {
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700176 Profile::Identifier ident;
177 if (!Profile::ParseIdentifier(name, &ident)) {
Paul Stewartbe005172011-11-02 18:10:29 -0700178 Error::PopulateAndLog(error, Error::kInvalidArguments,
179 "Invalid profile name " + name);
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700180 return;
181 }
182 ProfileRefPtr profile(new Profile(control_interface_,
183 this,
184 ident,
185 user_storage_format_,
Paul Stewart19c871d2011-12-15 16:10:13 -0800186 connect_profiles_to_rpc_));
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700187 if (!profile->InitStorage(glib_, Profile::kCreateNew, error)) {
Paul Stewart19c871d2011-12-15 16:10:13 -0800188 // |error| will have been populated by InitStorage().
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700189 return;
190 }
191
192 // Save profile data out, and then let the scoped pointer fall out of scope.
193 if (!profile->Save()) {
Paul Stewartbe005172011-11-02 18:10:29 -0700194 Error::PopulateAndLog(error, Error::kInternalError,
195 "Profile name " + name + " could not be saved");
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700196 return;
197 }
Paul Stewart19c871d2011-12-15 16:10:13 -0800198
199 *path = profile->GetRpcIdentifier();
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700200}
201
Paul Stewart19c871d2011-12-15 16:10:13 -0800202void Manager::PushProfile(const string &name, string *path, Error *error) {
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700203 Profile::Identifier ident;
204 if (!Profile::ParseIdentifier(name, &ident)) {
Paul Stewartbe005172011-11-02 18:10:29 -0700205 Error::PopulateAndLog(error, Error::kInvalidArguments,
206 "Invalid profile name " + name);
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700207 return;
208 }
209
210 for (vector<ProfileRefPtr>::const_iterator it = profiles_.begin();
211 it != profiles_.end();
212 ++it) {
213 if ((*it)->MatchesIdentifier(ident)) {
Paul Stewartbe005172011-11-02 18:10:29 -0700214 Error::PopulateAndLog(error, Error::kAlreadyExists,
215 "Profile name " + name + " is already on stack");
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700216 return;
217 }
218 }
219
220 if (ident.user.empty()) {
221 // The manager will have only one machine-wide profile, and this is the
222 // DefaultProfile. This means no other profiles can be loaded that do
223 // not have a user component.
224 // TODO(pstew): This is all well and good, but WiFi autotests try to
225 // creating a default profile (by a name other than "default") in order
226 // to avoid leaving permanent side effects to devices under test. This
227 // whole thing will need to be reworked in order to allow that to happen,
228 // or the autotests (or their expectations) will need to change.
mukesh agrawal8a3188d2011-12-01 20:56:44 +0000229 // crosbug.com/24461
Paul Stewartbe005172011-11-02 18:10:29 -0700230 Error::PopulateAndLog(error, Error::kInvalidArguments,
231 "Cannot load non-default global profile " + name);
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700232 return;
233 }
234
235 ProfileRefPtr profile(new Profile(control_interface_,
236 this,
237 ident,
238 user_storage_format_,
239 connect_profiles_to_rpc_));
240 if (!profile->InitStorage(glib_, Profile::kOpenExisting, error)) {
Paul Stewart19c871d2011-12-15 16:10:13 -0800241 // |error| will have been populated by InitStorage().
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700242 return;
243 }
244
Paul Stewarta849a3d2011-11-03 05:54:09 -0700245 profiles_.push_back(profile);
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700246
247 // Offer each registered Service the opportunity to join this new Profile.
Paul Stewarta41e38d2011-11-11 07:47:29 -0800248 for (vector<ServiceRefPtr>::iterator it = services_.begin();
249 it != services_.end(); ++it) {
Paul Stewartbba6a5b2011-11-02 18:45:59 -0700250 profile->ConfigureService(*it);
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700251 }
252
Paul Stewarta41e38d2011-11-11 07:47:29 -0800253 // Shop the Profile contents around to Devices which can create
254 // non-visible services.
255 for (vector<DeviceRefPtr>::iterator it = devices_.begin();
256 it != devices_.end(); ++it) {
257 profile->ConfigureDevice(*it);
258 }
Paul Stewart3d9bcf52011-12-12 15:02:22 -0800259
Paul Stewart19c871d2011-12-15 16:10:13 -0800260 *path = profile->GetRpcIdentifier();
Paul Stewart3d9bcf52011-12-12 15:02:22 -0800261 SortServices();
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700262}
263
264void Manager::PopProfileInternal() {
265 CHECK(!profiles_.empty());
266 ProfileRefPtr active_profile = profiles_.back();
267 profiles_.pop_back();
Paul Stewart75225512012-01-26 22:51:33 -0800268 vector<ServiceRefPtr>::iterator it;
269 for (it = services_.begin(); it != services_.end(); ++it) {
270 if ((*it)->profile().get() == active_profile.get() &&
271 !MatchProfileWithService(*it)) {
272 (*it)->Unload();
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700273 }
274 }
Paul Stewart3d9bcf52011-12-12 15:02:22 -0800275 SortServices();
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700276}
277
Paul Stewarta41e38d2011-11-11 07:47:29 -0800278void Manager::PopProfile(const string &name, Error *error) {
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700279 Profile::Identifier ident;
280 if (profiles_.empty()) {
Paul Stewartbe005172011-11-02 18:10:29 -0700281 Error::PopulateAndLog(error, Error::kNotFound, "Profile stack is empty");
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700282 return;
283 }
284 ProfileRefPtr active_profile = profiles_.back();
285 if (!Profile::ParseIdentifier(name, &ident)) {
Paul Stewartbe005172011-11-02 18:10:29 -0700286 Error::PopulateAndLog(error, Error::kInvalidArguments,
287 "Invalid profile name " + name);
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700288 return;
289 }
290 if (!active_profile->MatchesIdentifier(ident)) {
Paul Stewartbe005172011-11-02 18:10:29 -0700291 Error::PopulateAndLog(error, Error::kNotSupported,
292 name + " is not the active profile");
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700293 return;
294 }
295 PopProfileInternal();
296}
297
298void Manager::PopAnyProfile(Error *error) {
299 Profile::Identifier ident;
300 if (profiles_.empty()) {
Paul Stewartbe005172011-11-02 18:10:29 -0700301 Error::PopulateAndLog(error, Error::kNotFound, "Profile stack is empty");
Paul Stewart5dc40aa2011-10-28 19:43:43 -0700302 return;
303 }
304 PopProfileInternal();
305}
306
Paul Stewart75225512012-01-26 22:51:33 -0800307bool Manager::HandleProfileEntryDeletion(const ProfileRefPtr &profile,
308 const std::string &entry_name) {
309 bool moved_services = false;
310 for (vector<ServiceRefPtr>::iterator it = services_.begin();
311 it != services_.end(); ++it) {
312 if ((*it)->profile().get() == profile.get() &&
313 (*it)->GetStorageIdentifier() == entry_name) {
314 profile->AbandonService(*it);
315 if (!MatchProfileWithService(*it)) {
316 (*it)->Unload();
317 }
318 moved_services = true;
319 }
320 }
321 return moved_services;
322}
323
Paul Stewart0756db92012-01-27 08:34:47 -0800324ServiceRefPtr Manager::GetServiceWithStorageIdentifier(
325 const ProfileRefPtr &profile, const std::string &entry_name, Error *error) {
326 for (vector<ServiceRefPtr>::iterator it = services_.begin();
327 it != services_.end(); ++it) {
328 if ((*it)->profile().get() == profile.get() &&
329 (*it)->GetStorageIdentifier() == entry_name) {
330 return *it;
331 }
332 }
333
334 Error::PopulateAndLog(error, Error::kNotFound,
335 base::StringPrintf("Entry %s is not registered in the manager",
336 entry_name.c_str()));
337 return NULL;
338}
339
Paul Stewart1b253142012-01-26 14:05:52 -0800340const ProfileRefPtr &Manager::ActiveProfile() const {
Chris Masoneb9c00592011-10-06 13:10:39 -0700341 DCHECK_NE(profiles_.size(), 0);
Chris Masone7aa5f902011-07-11 11:13:35 -0700342 return profiles_.back();
343}
344
Paul Stewart1b253142012-01-26 14:05:52 -0800345bool Manager::IsActiveProfile(const ProfileRefPtr &profile) const {
346 return (profiles_.size() > 0 &&
347 ActiveProfile().get() == profile.get());
348}
349
Chris Masone6515aab2011-10-12 16:19:09 -0700350bool Manager::MoveServiceToProfile(const ServiceRefPtr &to_move,
351 const ProfileRefPtr &destination) {
352 const ProfileRefPtr from = to_move->profile();
Paul Stewart3d9bcf52011-12-12 15:02:22 -0800353 VLOG(2) << "Moving service "
354 << to_move->UniqueName()
355 << " to profile "
356 << destination->GetFriendlyName()
357 << " from "
358 << from->GetFriendlyName();
Chris Masone6515aab2011-10-12 16:19:09 -0700359 return destination->AdoptService(to_move) &&
360 from->AbandonService(to_move);
Chris Masone6791a432011-07-12 13:23:19 -0700361}
362
Paul Stewart1b1a7f22012-01-06 16:24:06 -0800363void Manager::SetProfileForService(const ServiceRefPtr &to_set,
364 const string &profile_rpcid,
365 Error *error) {
366 for (vector<ProfileRefPtr>::iterator it = profiles_.begin();
367 it != profiles_.end();
368 ++it) {
369 if (profile_rpcid == (*it)->GetRpcIdentifier()) {
370 if (to_set->profile().get() == it->get()) {
371 Error::PopulateAndLog(error, Error::kInvalidArguments,
372 "Service is already connected to this profile");
373 } else if (!MoveServiceToProfile(to_set, *it)) {
374 Error::PopulateAndLog(error, Error::kInternalError,
375 "Unable to move service to profile");
376 }
377 return;
378 }
379 }
380 Error::PopulateAndLog(error, Error::kInvalidArguments,
381 "Unknown Profile requested for Service");
382}
383
Chris Masone2b105542011-06-22 10:58:09 -0700384void Manager::RegisterDevice(const DeviceRefPtr &to_manage) {
Chris Masonec1e50412011-06-07 13:04:53 -0700385 vector<DeviceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700386 for (it = devices_.begin(); it != devices_.end(); ++it) {
Chris Masonec1e50412011-06-07 13:04:53 -0700387 if (to_manage.get() == it->get())
Chris Masone9be4a9d2011-05-16 15:44:09 -0700388 return;
389 }
Chris Masonec1e50412011-06-07 13:04:53 -0700390 devices_.push_back(to_manage);
Paul Stewartf1ce5d22011-05-19 13:10:20 -0700391
Paul Stewarta41e38d2011-11-11 07:47:29 -0800392 // We are applying device properties from the DefaultProfile, and adding
393 // the union of hidden services in all loaded profiles to the device.
Chris Masone6515aab2011-10-12 16:19:09 -0700394 for (vector<ProfileRefPtr>::iterator it = profiles_.begin();
395 it != profiles_.end();
396 ++it) {
Paul Stewarta41e38d2011-11-11 07:47:29 -0800397 // Load device configuration, if any exists, as well as hidden services.
398 (*it)->ConfigureDevice(to_manage);
399
400 // Currently the only profile for which "Save" is implemented is the
401 // DefaultProfile. It iterates over all Devices and stores their state.
402 // We perform the Save now in case the device we have just registered
403 // is new and needs to be added to the stored DefaultProfile.
Chris Masone6515aab2011-10-12 16:19:09 -0700404 (*it)->Save();
405 }
Paul Stewarta41e38d2011-11-11 07:47:29 -0800406
407 // In normal usage, running_ will always be true when we are here, however
408 // unit tests sometimes do things in otherwise invalid states.
409 if (running_ && to_manage->powered())
410 to_manage->Start();
Gaurav Shah435de2c2011-11-17 19:01:07 -0800411
412 Error error;
413 adaptor_->EmitStringsChanged(flimflam::kAvailableTechnologiesProperty,
414 AvailableTechnologies(&error));
415 adaptor_->EmitStringsChanged(flimflam::kEnabledTechnologiesProperty,
416 EnabledTechnologies(&error));
Chris Masone9be4a9d2011-05-16 15:44:09 -0700417}
418
mukesh agrawal5029c6c2011-08-25 11:12:40 -0700419void Manager::DeregisterDevice(const DeviceRefPtr &to_forget) {
Chris Masonec1e50412011-06-07 13:04:53 -0700420 vector<DeviceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700421 for (it = devices_.begin(); it != devices_.end(); ++it) {
Chris Masonec1e50412011-06-07 13:04:53 -0700422 if (to_forget.get() == it->get()) {
mukesh agrawal5029c6c2011-08-25 11:12:40 -0700423 VLOG(2) << "Deregistered device: " << to_forget->UniqueName();
424 to_forget->Stop();
Chris Masone9be4a9d2011-05-16 15:44:09 -0700425 devices_.erase(it);
Gaurav Shah435de2c2011-11-17 19:01:07 -0800426 Error error;
427 adaptor_->EmitStringsChanged(flimflam::kAvailableTechnologiesProperty,
428 AvailableTechnologies(&error));
429 adaptor_->EmitStringsChanged(flimflam::kEnabledTechnologiesProperty,
430 EnabledTechnologies(&error));
Chris Masone9be4a9d2011-05-16 15:44:09 -0700431 return;
432 }
433 }
mukesh agrawal5029c6c2011-08-25 11:12:40 -0700434 VLOG(2) << __func__ << " unknown device: " << to_forget->UniqueName();
Chris Masone9be4a9d2011-05-16 15:44:09 -0700435}
436
mukesh agrawal4eb4d782011-12-05 17:34:37 +0000437bool Manager::HasService(const ServiceRefPtr &service) {
438 vector<ServiceRefPtr>::iterator it;
439 for (it = services_.begin(); it != services_.end(); ++it) {
440 if ((*it)->UniqueName() == service->UniqueName())
441 return true;
442 }
443 return false;
444}
445
Chris Masone2b105542011-06-22 10:58:09 -0700446void Manager::RegisterService(const ServiceRefPtr &to_manage) {
Gaurav Shahc6d6c722011-11-17 18:59:39 -0800447 VLOG(2) << "In " << __func__ << "(): Registering service "
448 << to_manage->UniqueName();
mukesh agrawald835b202011-10-07 15:26:47 -0700449
Paul Stewart75225512012-01-26 22:51:33 -0800450 MatchProfileWithService(to_manage);
Chris Masone6791a432011-07-12 13:23:19 -0700451
452 // Now add to OUR list.
Chris Masonec1e50412011-06-07 13:04:53 -0700453 vector<ServiceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700454 for (it = services_.begin(); it != services_.end(); ++it) {
mukesh agrawald835b202011-10-07 15:26:47 -0700455 CHECK(to_manage->UniqueName() != (*it)->UniqueName());
Chris Masone9be4a9d2011-05-16 15:44:09 -0700456 }
Chris Masonec1e50412011-06-07 13:04:53 -0700457 services_.push_back(to_manage);
Paul Stewart22aa71b2011-09-16 12:15:11 -0700458 SortServices();
Chris Masone9be4a9d2011-05-16 15:44:09 -0700459}
460
Chris Masone6515aab2011-10-12 16:19:09 -0700461void Manager::DeregisterService(const ServiceRefPtr &to_forget) {
Chris Masonec1e50412011-06-07 13:04:53 -0700462 vector<ServiceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700463 for (it = services_.begin(); it != services_.end(); ++it) {
Chris Masone6791a432011-07-12 13:23:19 -0700464 if (to_forget->UniqueName() == (*it)->UniqueName()) {
Paul Stewartc1dec4d2011-12-08 15:25:28 -0800465 DCHECK(!(*it)->connection());
Chris Masone9be4a9d2011-05-16 15:44:09 -0700466 services_.erase(it);
Paul Stewart22aa71b2011-09-16 12:15:11 -0700467 SortServices();
Chris Masone9be4a9d2011-05-16 15:44:09 -0700468 return;
469 }
470 }
471}
472
mukesh agrawal00917ce2011-11-22 23:56:55 +0000473void Manager::UpdateService(const ServiceRefPtr &to_update) {
474 CHECK(to_update);
Paul Stewart03dba0b2011-08-22 16:32:45 -0700475 LOG(INFO) << "Service " << to_update->UniqueName() << " updated;"
Gaurav Shahc6d6c722011-11-17 18:59:39 -0800476 << " state: " << Service::ConnectStateToString(to_update->state())
477 << " failure: "
478 << Service::ConnectFailureToString(to_update->failure());
mukesh agrawal8a3188d2011-12-01 20:56:44 +0000479 VLOG(2) << "IsConnected(): " << to_update->IsConnected();
480 VLOG(2) << "IsConnecting(): " << to_update->IsConnecting();
Paul Stewart3d9bcf52011-12-12 15:02:22 -0800481 if (to_update->IsConnected()) {
Thieu Led4e9e552012-02-16 16:26:07 -0800482 bool originally_favorite = to_update->favorite();
mukesh agrawal00917ce2011-11-22 23:56:55 +0000483 to_update->MakeFavorite();
Paul Stewart3d9bcf52011-12-12 15:02:22 -0800484 if (to_update->profile().get() == ephemeral_profile_.get()) {
485 if (profiles_.empty()) {
486 LOG(ERROR) << "Cannot assign profile to service: no profiles exist!";
487 } else {
488 MoveServiceToProfile(to_update, profiles_.back());
489 }
Thieu Led4e9e552012-02-16 16:26:07 -0800490 } else if (!originally_favorite) {
491 // Persists the updated favorite setting in the profile.
492 to_update->SaveToCurrentProfile();
Paul Stewart3d9bcf52011-12-12 15:02:22 -0800493 }
494 }
Paul Stewart22aa71b2011-09-16 12:15:11 -0700495 SortServices();
Paul Stewart03dba0b2011-08-22 16:32:45 -0700496}
497
Paul Stewartfdd16072011-09-16 12:41:35 -0700498void Manager::FilterByTechnology(Technology::Identifier tech,
Chris Masonec1e50412011-06-07 13:04:53 -0700499 vector<DeviceRefPtr> *found) {
Chris Masone9be4a9d2011-05-16 15:44:09 -0700500 CHECK(found);
Chris Masonec1e50412011-06-07 13:04:53 -0700501 vector<DeviceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700502 for (it = devices_.begin(); it != devices_.end(); ++it) {
503 if ((*it)->TechnologyIs(tech))
504 found->push_back(*it);
505 }
506}
507
Paul Stewart22aa71b2011-09-16 12:15:11 -0700508ServiceRefPtr Manager::FindService(const string& name) {
Chris Masonec1e50412011-06-07 13:04:53 -0700509 vector<ServiceRefPtr>::iterator it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700510 for (it = services_.begin(); it != services_.end(); ++it) {
Chris Masone6791a432011-07-12 13:23:19 -0700511 if (name == (*it)->UniqueName())
Chris Masonee0dea762011-06-09 09:06:03 -0700512 return *it;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700513 }
Chris Masonee0dea762011-06-09 09:06:03 -0700514 return NULL;
Chris Masone9be4a9d2011-05-16 15:44:09 -0700515}
516
mukesh agrawalffa3d042011-10-06 15:26:10 -0700517void Manager::HelpRegisterDerivedString(
518 const string &name,
Gaurav Shah1b7a6162011-11-09 11:41:01 -0800519 string(Manager::*get)(Error *),
mukesh agrawalffa3d042011-10-06 15:26:10 -0700520 void(Manager::*set)(const string&, Error *)) {
Chris Masone27c4aa52011-07-02 13:10:14 -0700521 store_.RegisterDerivedString(
522 name,
523 StringAccessor(new CustomAccessor<Manager, string>(this, get, set)));
Chris Masoneb925cc82011-06-22 15:39:57 -0700524}
525
mukesh agrawalffa3d042011-10-06 15:26:10 -0700526void Manager::HelpRegisterDerivedStrings(
527 const string &name,
Gaurav Shah1b7a6162011-11-09 11:41:01 -0800528 Strings(Manager::*get)(Error *),
mukesh agrawalffa3d042011-10-06 15:26:10 -0700529 void(Manager::*set)(const Strings &, Error *)) {
Chris Masone27c4aa52011-07-02 13:10:14 -0700530 store_.RegisterDerivedStrings(
531 name,
532 StringsAccessor(new CustomAccessor<Manager, Strings>(this, get, set)));
Chris Masoneb925cc82011-06-22 15:39:57 -0700533}
534
Paul Stewart22aa71b2011-09-16 12:15:11 -0700535void Manager::SortServices() {
Paul Stewartc1dec4d2011-12-08 15:25:28 -0800536 VLOG(4) << "In " << __func__;
Thieu Lea20cbc22012-01-09 22:01:43 +0000537 ServiceRefPtr default_service;
538
Paul Stewartc1dec4d2011-12-08 15:25:28 -0800539 if (!services_.empty()) {
Thieu Lea20cbc22012-01-09 22:01:43 +0000540 // Keep track of the service that was last considered default.
541 default_service = services_[0];
Paul Stewartc1dec4d2011-12-08 15:25:28 -0800542 }
Paul Stewart22aa71b2011-09-16 12:15:11 -0700543 sort(services_.begin(), services_.end(), ServiceSorter(technology_order_));
Paul Stewarta41e38d2011-11-11 07:47:29 -0800544
545 vector<string> service_paths;
546 vector<ServiceRefPtr>::iterator it;
547 for (it = services_.begin(); it != services_.end(); ++it) {
548 if ((*it)->IsVisible()) {
549 service_paths.push_back((*it)->GetRpcIdentifier());
550 }
551 }
552 adaptor_->EmitRpcIdentifierArrayChanged(flimflam::kServicesProperty,
553 service_paths);
Gaurav Shah435de2c2011-11-17 19:01:07 -0800554
555 Error error;
556 adaptor_->EmitStringsChanged(flimflam::kConnectedTechnologiesProperty,
557 ConnectedTechnologies(&error));
558 adaptor_->EmitStringChanged(flimflam::kDefaultTechnologyProperty,
559 DefaultTechnology(&error));
Paul Stewartc1dec4d2011-12-08 15:25:28 -0800560
561 if (!services_.empty()) {
Thieu Lea20cbc22012-01-09 22:01:43 +0000562 ConnectionRefPtr default_connection = default_service->connection();
Paul Stewartc1dec4d2011-12-08 15:25:28 -0800563 if (default_connection.get() &&
564 (services_[0]->connection().get() != default_connection.get())) {
565 default_connection->SetIsDefault(false);
Thieu Lea20cbc22012-01-09 22:01:43 +0000566 default_service = NULL;
Paul Stewartc1dec4d2011-12-08 15:25:28 -0800567 }
568 if (services_[0]->connection().get()) {
569 services_[0]->connection()->SetIsDefault(true);
Thieu Lea20cbc22012-01-09 22:01:43 +0000570 default_service = services_[0];
Paul Stewartc1dec4d2011-12-08 15:25:28 -0800571 }
Thieu Lea20cbc22012-01-09 22:01:43 +0000572 metrics_->NotifyDefaultServiceChanged(default_service);
Paul Stewartc1dec4d2011-12-08 15:25:28 -0800573 }
Paul Stewart3d9bcf52011-12-12 15:02:22 -0800574
mukesh agrawal8a3188d2011-12-01 20:56:44 +0000575 AutoConnect();
576}
577
Paul Stewart75225512012-01-26 22:51:33 -0800578bool Manager::MatchProfileWithService(const ServiceRefPtr &service) {
579 vector<ProfileRefPtr>::reverse_iterator it;
580 for (it = profiles_.rbegin(); it != profiles_.rend(); ++it) {
581 if ((*it)->ConfigureService(service)) {
582 break;
583 }
584 }
585 if (it == profiles_.rend()) {
586 ephemeral_profile_->AdoptService(service);
587 return false;
588 }
589 return true;
590}
591
mukesh agrawal8a3188d2011-12-01 20:56:44 +0000592void Manager::AutoConnect() {
593 // We might be called in the middle of another request (e.g., as a
594 // consequence of Service::SetState calling UpdateService). To avoid
595 // re-entrancy issues in dbus-c++, defer to the event loop.
596 dispatcher_->PostTask(
597 task_factory_.NewRunnableMethod(&Manager::AutoConnectTask));
598 return;
599}
600
601void Manager::AutoConnectTask() {
602 if (services_.empty()) {
603 LOG(INFO) << "No services.";
604 return;
605 }
606
607 if (VLOG_IS_ON(4)) {
mukesh agrawalddc378f2012-02-17 18:26:20 -0800608 VLOG(4) << "Sorted service list: ";
609 for (size_t i = 0; i < services_.size(); ++i) {
610 ServiceRefPtr service = services_[i];
611 const char *compare_reason = NULL;
612 if (i + 1 < services_.size()) {
613 Service::Compare(
614 service, services_[i+1], technology_order_, &compare_reason);
615 } else {
616 compare_reason = "";
617 }
618 VLOG(4) << "Service " << service->friendly_name()
619 << " IsConnected: " << service->IsConnected()
620 << " IsConnecting: " << service->IsConnecting()
621 << " IsFailed: " << service->IsFailed()
622 << " connectable: " << service->connectable()
623 << " auto_connect: " << service->auto_connect()
624 << " favorite: " << service->favorite()
625 << " priority: " << service->priority()
626 << " security_level: " << service->security_level()
627 << " strength: " << service->strength()
628 << " UniqueName: " << service->UniqueName()
629 << " " << compare_reason;
mukesh agrawal8a3188d2011-12-01 20:56:44 +0000630 }
631 }
632
Paul Stewart3d9bcf52011-12-12 15:02:22 -0800633 // Perform auto-connect.
mukesh agrawal8a3188d2011-12-01 20:56:44 +0000634 for (vector<ServiceRefPtr>::iterator it = services_.begin();
635 it != services_.end(); ++it) {
636 if ((*it)->auto_connect()) {
mukesh agrawal592516d2012-01-12 14:01:00 -0800637 LOG(INFO) << "Requesting autoconnect to service "
638 << (*it)->friendly_name() << ".";
mukesh agrawal8a3188d2011-12-01 20:56:44 +0000639 (*it)->AutoConnect();
Paul Stewart3d9bcf52011-12-12 15:02:22 -0800640 }
641 }
Paul Stewart22aa71b2011-09-16 12:15:11 -0700642}
643
Gaurav Shah1b7a6162011-11-09 11:41:01 -0800644string Manager::CalculateState(Error */*error*/) {
Chris Masoneb925cc82011-06-22 15:39:57 -0700645 return flimflam::kStateOffline;
646}
647
Gaurav Shah1b7a6162011-11-09 11:41:01 -0800648vector<string> Manager::AvailableTechnologies(Error */*error*/) {
Gaurav Shah435de2c2011-11-17 19:01:07 -0800649 set<string> unique_technologies;
650 for (vector<DeviceRefPtr>::iterator it = devices_.begin();
651 it != devices_.end(); ++it) {
652 unique_technologies.insert(
653 Technology::NameFromIdentifier((*it)->technology()));
654 }
655 return vector<string>(unique_technologies.begin(), unique_technologies.end());
Chris Masoneb925cc82011-06-22 15:39:57 -0700656}
657
Gaurav Shah1b7a6162011-11-09 11:41:01 -0800658vector<string> Manager::ConnectedTechnologies(Error */*error*/) {
Gaurav Shah435de2c2011-11-17 19:01:07 -0800659 set<string> unique_technologies;
660 for (vector<DeviceRefPtr>::iterator it = devices_.begin();
661 it != devices_.end(); ++it) {
662 if ((*it)->IsConnected())
663 unique_technologies.insert(
664 Technology::NameFromIdentifier((*it)->technology()));
665 }
666 return vector<string>(unique_technologies.begin(), unique_technologies.end());
Chris Masoneb925cc82011-06-22 15:39:57 -0700667}
668
Gaurav Shah435de2c2011-11-17 19:01:07 -0800669string Manager::DefaultTechnology(Error *error) {
670 return (!services_.empty() && services_[0]->IsConnected()) ?
671 services_[0]->GetTechnologyString(error) : "";
Chris Masoneb925cc82011-06-22 15:39:57 -0700672}
673
Gaurav Shah435de2c2011-11-17 19:01:07 -0800674vector<string> Manager::EnabledTechnologies(Error *error) {
675 // TODO(gauravsh): This must be wired up to the RPC interface to handle
676 // enabled/disabled devices as set by the user. crosbug.com/23319
677 return AvailableTechnologies(error);
Chris Masoneb925cc82011-06-22 15:39:57 -0700678}
679
Gaurav Shah1b7a6162011-11-09 11:41:01 -0800680vector<string> Manager::EnumerateDevices(Error */*error*/) {
Chris Masone3c3f6a12011-07-01 10:01:41 -0700681 vector<string> device_rpc_ids;
682 for (vector<DeviceRefPtr>::const_iterator it = devices_.begin();
683 it != devices_.end();
684 ++it) {
685 device_rpc_ids.push_back((*it)->GetRpcIdentifier());
686 }
687 return device_rpc_ids;
688}
689
Paul Stewart1b253142012-01-26 14:05:52 -0800690vector<string> Manager::EnumerateProfiles(Error */*error*/) {
691 vector<string> profile_rpc_ids;
692 for (vector<ProfileRefPtr>::const_iterator it = profiles_.begin();
693 it != profiles_.end();
694 ++it) {
695 profile_rpc_ids.push_back((*it)->GetRpcIdentifier());
696 }
697 return profile_rpc_ids;
698}
699
Gaurav Shah1b7a6162011-11-09 11:41:01 -0800700vector<string> Manager::EnumerateAvailableServices(Error */*error*/) {
Chris Masone3c3f6a12011-07-01 10:01:41 -0700701 vector<string> service_rpc_ids;
702 for (vector<ServiceRefPtr>::const_iterator it = services_.begin();
703 it != services_.end();
704 ++it) {
705 service_rpc_ids.push_back((*it)->GetRpcIdentifier());
706 }
707 return service_rpc_ids;
708}
709
Gaurav Shah1b7a6162011-11-09 11:41:01 -0800710vector<string> Manager::EnumerateWatchedServices(Error *error) {
Chris Masone6791a432011-07-12 13:23:19 -0700711 // TODO(cmasone): Filter this list for services in appropriate states.
Gaurav Shah1b7a6162011-11-09 11:41:01 -0800712 return EnumerateAvailableServices(error);
Chris Masone3c3f6a12011-07-01 10:01:41 -0700713}
714
Paul Stewart1b253142012-01-26 14:05:52 -0800715string Manager::GetActiveProfileRpcIdentifier(Error */*error*/) {
716 return ActiveProfile()->GetRpcIdentifier();
Chris Masone7aa5f902011-07-11 11:13:35 -0700717}
718
mukesh agrawal32399322011-09-01 10:53:43 -0700719// called via RPC (e.g., from ManagerDBusAdaptor)
mukesh agrawal7a4e4002011-09-06 11:26:05 -0700720WiFiServiceRefPtr Manager::GetWifiService(const KeyValueStore &args,
721 Error *error) {
Paul Stewarta41e38d2011-11-11 07:47:29 -0800722 vector<DeviceRefPtr> wifi_devices;
Paul Stewart22aa71b2011-09-16 12:15:11 -0700723 FilterByTechnology(Technology::kWifi, &wifi_devices);
mukesh agrawal7a4e4002011-09-06 11:26:05 -0700724 if (wifi_devices.empty()) {
725 error->Populate(Error::kInvalidArguments, kManagerErrorNoDevice);
726 return NULL;
727 } else {
728 WiFi *wifi = dynamic_cast<WiFi *>(wifi_devices.front().get());
729 CHECK(wifi);
730 return wifi->GetService(args, error);
731 }
732}
733
734// called via RPC (e.g., from ManagerDBusAdaptor)
Paul Stewart22aa71b2011-09-16 12:15:11 -0700735void Manager::RequestScan(const string &technology, Error *error) {
mukesh agrawal32399322011-09-01 10:53:43 -0700736 if (technology == flimflam::kTypeWifi || technology == "") {
737 vector<DeviceRefPtr> wifi_devices;
Paul Stewartfdd16072011-09-16 12:41:35 -0700738 FilterByTechnology(Technology::kWifi, &wifi_devices);
mukesh agrawal32399322011-09-01 10:53:43 -0700739
740 for (vector<DeviceRefPtr>::iterator it = wifi_devices.begin();
741 it != wifi_devices.end();
742 ++it) {
Darin Petkovc0865312011-09-16 15:31:20 -0700743 (*it)->Scan(error);
mukesh agrawal32399322011-09-01 10:53:43 -0700744 }
745 } else {
746 // TODO(quiche): support scanning for other technologies?
Paul Stewartbe005172011-11-02 18:10:29 -0700747 Error::PopulateAndLog(error, Error::kInvalidArguments,
748 "Unrecognized technology " + technology);
mukesh agrawal32399322011-09-01 10:53:43 -0700749 }
750}
751
Paul Stewart22aa71b2011-09-16 12:15:11 -0700752string Manager::GetTechnologyOrder() {
753 vector<string> technology_names;
754 for (vector<Technology::Identifier>::iterator it = technology_order_.begin();
755 it != technology_order_.end();
756 ++it) {
757 technology_names.push_back(Technology::NameFromIdentifier(*it));
758 }
759
760 return JoinString(technology_names, ',');
761}
762
763void Manager::SetTechnologyOrder(const string &order, Error *error) {
764 vector<Technology::Identifier> new_order;
765 map<Technology::Identifier, bool> seen;
766
767 vector<string> order_parts;
768 base::SplitString(order, ',', &order_parts);
769
770 for (vector<string>::iterator it = order_parts.begin();
771 it != order_parts.end();
772 ++it) {
773 Technology::Identifier identifier = Technology::IdentifierFromName(*it);
774
775 if (identifier == Technology::kUnknown) {
Paul Stewartbe005172011-11-02 18:10:29 -0700776 Error::PopulateAndLog(error, Error::kInvalidArguments,
777 *it + " is an unknown technology name");
Paul Stewart22aa71b2011-09-16 12:15:11 -0700778 return;
779 }
780
781 if (ContainsKey(seen, identifier)) {
Paul Stewartbe005172011-11-02 18:10:29 -0700782 Error::PopulateAndLog(error, Error::kInvalidArguments,
783 *it + " is duplicated in the list");
Paul Stewart22aa71b2011-09-16 12:15:11 -0700784 return;
785 }
786 seen[identifier] = true;
787 new_order.push_back(identifier);
788 }
789
790 technology_order_ = new_order;
791 SortServices();
792}
793
Paul Stewart75897df2011-04-27 09:05:53 -0700794} // namespace shill