1 // Copyright (c) 2013 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 "chromeos/dbus/shill_service_client_stub.h" 6 7 #include "base/bind.h" 8 #include "base/command_line.h" 9 #include "base/message_loop/message_loop.h" 10 #include "base/stl_util.h" 11 #include "base/strings/string_util.h" 12 #include "base/values.h" 13 #include "chromeos/chromeos_switches.h" 14 #include "chromeos/dbus/dbus_thread_manager.h" 15 #include "chromeos/dbus/shill_manager_client.h" 16 #include "chromeos/dbus/shill_profile_client_stub.h" 17 #include "chromeos/dbus/shill_property_changed_observer.h" 18 #include "dbus/bus.h" 19 #include "dbus/message.h" 20 #include "dbus/object_proxy.h" 21 #include "third_party/cros_system_api/dbus/service_constants.h" 22 23 namespace chromeos { 24 25 namespace { 26 27 const char kStubPortalledWifiPath[] = "portalled_wifi"; 28 const char kStubPortalledWifiName[] = "Portalled Wifi"; 29 30 void ErrorFunction(const std::string& error_name, 31 const std::string& error_message) { 32 LOG(ERROR) << "Shill Error: " << error_name << " : " << error_message; 33 } 34 35 void PassStubListValue(const ShillServiceClient::ListValueCallback& callback, 36 base::ListValue* value) { 37 callback.Run(*value); 38 } 39 40 void PassStubServiceProperties( 41 const ShillServiceClient::DictionaryValueCallback& callback, 42 DBusMethodCallStatus call_status, 43 const base::DictionaryValue* properties) { 44 callback.Run(call_status, *properties); 45 } 46 47 } // namespace 48 49 ShillServiceClientStub::ShillServiceClientStub() : weak_ptr_factory_(this) { 50 } 51 52 ShillServiceClientStub::~ShillServiceClientStub() { 53 STLDeleteContainerPairSecondPointers( 54 observer_list_.begin(), observer_list_.end()); 55 } 56 57 // static 58 bool ShillServiceClientStub::IsStubPortalledWifiEnabled( 59 const std::string& path) { 60 if (!CommandLine::ForCurrentProcess()->HasSwitch( 61 chromeos::switches::kEnableStubPortalledWifi)) { 62 return false; 63 } 64 return path == kStubPortalledWifiPath; 65 } 66 67 // ShillServiceClient overrides. 68 69 void ShillServiceClientStub::AddPropertyChangedObserver( 70 const dbus::ObjectPath& service_path, 71 ShillPropertyChangedObserver* observer) { 72 GetObserverList(service_path).AddObserver(observer); 73 } 74 75 void ShillServiceClientStub::RemovePropertyChangedObserver( 76 const dbus::ObjectPath& service_path, 77 ShillPropertyChangedObserver* observer) { 78 GetObserverList(service_path).RemoveObserver(observer); 79 } 80 81 void ShillServiceClientStub::GetProperties( 82 const dbus::ObjectPath& service_path, 83 const DictionaryValueCallback& callback) { 84 base::DictionaryValue* nested_dict = NULL; 85 scoped_ptr<base::DictionaryValue> result_properties; 86 DBusMethodCallStatus call_status; 87 stub_services_.GetDictionaryWithoutPathExpansion(service_path.value(), 88 &nested_dict); 89 if (nested_dict) { 90 result_properties.reset(nested_dict->DeepCopy()); 91 // Remove credentials that Shill wouldn't send. 92 result_properties->RemoveWithoutPathExpansion(flimflam::kPassphraseProperty, 93 NULL); 94 call_status = DBUS_METHOD_CALL_SUCCESS; 95 } else { 96 result_properties.reset(new base::DictionaryValue); 97 call_status = DBUS_METHOD_CALL_FAILURE; 98 } 99 100 base::MessageLoop::current()->PostTask( 101 FROM_HERE, 102 base::Bind(&PassStubServiceProperties, 103 callback, 104 call_status, 105 base::Owned(result_properties.release()))); 106 } 107 108 void ShillServiceClientStub::SetProperty(const dbus::ObjectPath& service_path, 109 const std::string& name, 110 const base::Value& value, 111 const base::Closure& callback, 112 const ErrorCallback& error_callback) { 113 if (!SetServiceProperty(service_path.value(), name, value)) { 114 LOG(ERROR) << "Service not found: " << service_path.value(); 115 error_callback.Run("Error.InvalidService", "Invalid Service"); 116 return; 117 } 118 base::MessageLoop::current()->PostTask(FROM_HERE, callback); 119 } 120 121 void ShillServiceClientStub::SetProperties( 122 const dbus::ObjectPath& service_path, 123 const base::DictionaryValue& properties, 124 const base::Closure& callback, 125 const ErrorCallback& error_callback) { 126 for (base::DictionaryValue::Iterator iter(properties); 127 !iter.IsAtEnd(); iter.Advance()) { 128 if (!SetServiceProperty(service_path.value(), iter.key(), iter.value())) { 129 LOG(ERROR) << "Service not found: " << service_path.value(); 130 error_callback.Run("Error.InvalidService", "Invalid Service"); 131 return; 132 } 133 } 134 base::MessageLoop::current()->PostTask(FROM_HERE, callback); 135 } 136 137 void ShillServiceClientStub::ClearProperty( 138 const dbus::ObjectPath& service_path, 139 const std::string& name, 140 const base::Closure& callback, 141 const ErrorCallback& error_callback) { 142 base::DictionaryValue* dict = NULL; 143 if (!stub_services_.GetDictionaryWithoutPathExpansion( 144 service_path.value(), &dict)) { 145 error_callback.Run("Error.InvalidService", "Invalid Service"); 146 return; 147 } 148 dict->RemoveWithoutPathExpansion(name, NULL); 149 // Note: Shill does not send notifications when properties are cleared. 150 base::MessageLoop::current()->PostTask(FROM_HERE, callback); 151 } 152 153 void ShillServiceClientStub::ClearProperties( 154 const dbus::ObjectPath& service_path, 155 const std::vector<std::string>& names, 156 const ListValueCallback& callback, 157 const ErrorCallback& error_callback) { 158 base::DictionaryValue* dict = NULL; 159 if (!stub_services_.GetDictionaryWithoutPathExpansion( 160 service_path.value(), &dict)) { 161 error_callback.Run("Error.InvalidService", "Invalid Service"); 162 return; 163 } 164 scoped_ptr<base::ListValue> results(new base::ListValue); 165 for (std::vector<std::string>::const_iterator iter = names.begin(); 166 iter != names.end(); ++iter) { 167 dict->RemoveWithoutPathExpansion(*iter, NULL); 168 // Note: Shill does not send notifications when properties are cleared. 169 results->AppendBoolean(true); 170 } 171 base::MessageLoop::current()->PostTask( 172 FROM_HERE, 173 base::Bind(&PassStubListValue, 174 callback, base::Owned(results.release()))); 175 } 176 177 void ShillServiceClientStub::Connect(const dbus::ObjectPath& service_path, 178 const base::Closure& callback, 179 const ErrorCallback& error_callback) { 180 VLOG(1) << "ShillServiceClientStub::Connect: " << service_path.value(); 181 base::DictionaryValue* service_properties; 182 if (!stub_services_.GetDictionary( 183 service_path.value(), &service_properties)) { 184 LOG(ERROR) << "Service not found: " << service_path.value(); 185 error_callback.Run("Error.InvalidService", "Invalid Service"); 186 return; 187 } 188 189 // Set any other services of the same Type to 'offline' first, before setting 190 // State to Association which will trigger sorting Manager.Services and 191 // sending an update. 192 SetOtherServicesOffline(service_path.value()); 193 194 // Set Associating. 195 base::StringValue associating_value(flimflam::kStateAssociation); 196 SetServiceProperty(service_path.value(), 197 flimflam::kStateProperty, 198 associating_value); 199 200 // Set Online after a delay. 201 base::TimeDelta delay; 202 if (CommandLine::ForCurrentProcess()->HasSwitch( 203 chromeos::switches::kEnableStubInteractive)) { 204 const int kConnectDelaySeconds = 5; 205 delay = base::TimeDelta::FromSeconds(kConnectDelaySeconds); 206 } 207 base::StringValue online_value(flimflam::kStateOnline); 208 if (service_path.value() == kStubPortalledWifiPath) 209 online_value = base::StringValue(flimflam::kStatePortal); 210 std::string passphrase; 211 service_properties->GetStringWithoutPathExpansion( 212 flimflam::kPassphraseProperty, &passphrase); 213 if (passphrase == "failure") 214 online_value = base::StringValue(flimflam::kStateFailure); 215 base::MessageLoop::current()->PostDelayedTask( 216 FROM_HERE, 217 base::Bind(&ShillServiceClientStub::SetProperty, 218 weak_ptr_factory_.GetWeakPtr(), 219 service_path, 220 flimflam::kStateProperty, 221 online_value, 222 base::Bind(&base::DoNothing), 223 error_callback), 224 delay); 225 callback.Run(); 226 // On failure, also set the Error property. 227 if (passphrase == "failure") { 228 base::MessageLoop::current()->PostDelayedTask( 229 FROM_HERE, 230 base::Bind(&ShillServiceClientStub::SetProperty, 231 weak_ptr_factory_.GetWeakPtr(), 232 service_path, 233 flimflam::kErrorProperty, 234 base::StringValue(flimflam::kErrorBadPassphrase), 235 base::Bind(&base::DoNothing), 236 error_callback), 237 delay); 238 } 239 } 240 241 void ShillServiceClientStub::Disconnect(const dbus::ObjectPath& service_path, 242 const base::Closure& callback, 243 const ErrorCallback& error_callback) { 244 base::Value* service; 245 if (!stub_services_.Get(service_path.value(), &service)) { 246 error_callback.Run("Error.InvalidService", "Invalid Service"); 247 return; 248 } 249 base::TimeDelta delay; 250 if (CommandLine::ForCurrentProcess()->HasSwitch( 251 chromeos::switches::kEnableStubInteractive)) { 252 const int kConnectDelaySeconds = 2; 253 delay = base::TimeDelta::FromSeconds(kConnectDelaySeconds); 254 } 255 // Set Idle after a delay 256 base::StringValue idle_value(flimflam::kStateIdle); 257 base::MessageLoop::current()->PostDelayedTask( 258 FROM_HERE, 259 base::Bind(&ShillServiceClientStub::SetProperty, 260 weak_ptr_factory_.GetWeakPtr(), 261 service_path, 262 flimflam::kStateProperty, 263 idle_value, 264 base::Bind(&base::DoNothing), 265 error_callback), 266 delay); 267 callback.Run(); 268 } 269 270 void ShillServiceClientStub::Remove(const dbus::ObjectPath& service_path, 271 const base::Closure& callback, 272 const ErrorCallback& error_callback) { 273 base::MessageLoop::current()->PostTask(FROM_HERE, callback); 274 } 275 276 void ShillServiceClientStub::ActivateCellularModem( 277 const dbus::ObjectPath& service_path, 278 const std::string& carrier, 279 const base::Closure& callback, 280 const ErrorCallback& error_callback) { 281 base::MessageLoop::current()->PostTask(FROM_HERE, callback); 282 } 283 284 void ShillServiceClientStub::CompleteCellularActivation( 285 const dbus::ObjectPath& service_path, 286 const base::Closure& callback, 287 const ErrorCallback& error_callback) { 288 base::MessageLoop::current()->PostTask(FROM_HERE, callback); 289 } 290 291 bool ShillServiceClientStub::CallActivateCellularModemAndBlock( 292 const dbus::ObjectPath& service_path, 293 const std::string& carrier) { 294 return true; 295 } 296 297 void ShillServiceClientStub::GetLoadableProfileEntries( 298 const dbus::ObjectPath& service_path, 299 const DictionaryValueCallback& callback) { 300 // Provide a dictionary with a single { profile_path, service_path } entry 301 // if the Profile property is set, or an empty dictionary. 302 scoped_ptr<base::DictionaryValue> result_properties( 303 new base::DictionaryValue); 304 base::DictionaryValue* service_properties = 305 GetModifiableServiceProperties(service_path.value()); 306 if (service_properties) { 307 std::string profile_path; 308 if (service_properties->GetStringWithoutPathExpansion( 309 flimflam::kProfileProperty, &profile_path)) { 310 result_properties->SetStringWithoutPathExpansion( 311 profile_path, service_path.value()); 312 } 313 } else { 314 LOG(WARNING) << "Service not in profile: " << service_path.value(); 315 } 316 317 DBusMethodCallStatus call_status = DBUS_METHOD_CALL_SUCCESS; 318 base::MessageLoop::current()->PostTask( 319 FROM_HERE, 320 base::Bind(&PassStubServiceProperties, 321 callback, 322 call_status, 323 base::Owned(result_properties.release()))); 324 } 325 326 ShillServiceClient::TestInterface* ShillServiceClientStub::GetTestInterface() { 327 return this; 328 } 329 330 // ShillServiceClient::TestInterface overrides. 331 332 void ShillServiceClientStub::AddService(const std::string& service_path, 333 const std::string& name, 334 const std::string& type, 335 const std::string& state, 336 bool add_to_visible_list, 337 bool add_to_watch_list) { 338 std::string nstate = state; 339 if (CommandLine::ForCurrentProcess()->HasSwitch( 340 chromeos::switches::kDefaultStubNetworkStateIdle)) { 341 nstate = flimflam::kStateIdle; 342 } 343 AddServiceWithIPConfig(service_path, name, type, nstate, "", 344 add_to_visible_list, add_to_watch_list); 345 } 346 347 void ShillServiceClientStub::AddServiceWithIPConfig( 348 const std::string& service_path, 349 const std::string& name, 350 const std::string& type, 351 const std::string& state, 352 const std::string& ipconfig_path, 353 bool add_to_visible_list, 354 bool add_to_watch_list) { 355 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()-> 356 AddManagerService(service_path, add_to_visible_list, add_to_watch_list); 357 358 base::DictionaryValue* properties = 359 GetModifiableServiceProperties(service_path); 360 properties->SetWithoutPathExpansion( 361 flimflam::kSSIDProperty, 362 base::Value::CreateStringValue(service_path)); 363 properties->SetWithoutPathExpansion( 364 flimflam::kNameProperty, 365 base::Value::CreateStringValue(name)); 366 properties->SetWithoutPathExpansion( 367 flimflam::kTypeProperty, 368 base::Value::CreateStringValue(type)); 369 properties->SetWithoutPathExpansion( 370 flimflam::kStateProperty, 371 base::Value::CreateStringValue(state)); 372 if (!ipconfig_path.empty()) 373 properties->SetWithoutPathExpansion( 374 shill::kIPConfigProperty, 375 base::Value::CreateStringValue(ipconfig_path)); 376 } 377 378 void ShillServiceClientStub::RemoveService(const std::string& service_path) { 379 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()-> 380 RemoveManagerService(service_path); 381 382 stub_services_.RemoveWithoutPathExpansion(service_path, NULL); 383 } 384 385 bool ShillServiceClientStub::SetServiceProperty(const std::string& service_path, 386 const std::string& property, 387 const base::Value& value) { 388 base::DictionaryValue* dict = NULL; 389 if (!stub_services_.GetDictionaryWithoutPathExpansion(service_path, &dict)) 390 return false; 391 392 VLOG(1) << "Service.SetProperty: " << property << " = " << value 393 << " For: " << service_path; 394 395 base::DictionaryValue new_properties; 396 std::string changed_property; 397 bool case_sensitive = true; 398 if (StartsWithASCII(property, "Provider.", case_sensitive) || 399 StartsWithASCII(property, "OpenVPN.", case_sensitive) || 400 StartsWithASCII(property, "L2TPIPsec.", case_sensitive)) { 401 // These properties are only nested within the Provider dictionary if read 402 // from Shill. 403 base::DictionaryValue* provider = new base::DictionaryValue; 404 provider->SetWithoutPathExpansion(property, value.DeepCopy()); 405 new_properties.SetWithoutPathExpansion(flimflam::kProviderProperty, 406 provider); 407 changed_property = flimflam::kProviderProperty; 408 } else { 409 new_properties.SetWithoutPathExpansion(property, value.DeepCopy()); 410 changed_property = property; 411 } 412 413 dict->MergeDictionary(&new_properties); 414 415 if (property == flimflam::kStateProperty) { 416 // When State changes the sort order of Services may change. 417 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()-> 418 SortManagerServices(); 419 } 420 421 base::MessageLoop::current()->PostTask( 422 FROM_HERE, 423 base::Bind(&ShillServiceClientStub::NotifyObserversPropertyChanged, 424 weak_ptr_factory_.GetWeakPtr(), 425 dbus::ObjectPath(service_path), changed_property)); 426 return true; 427 } 428 429 const base::DictionaryValue* ShillServiceClientStub::GetServiceProperties( 430 const std::string& service_path) const { 431 const base::DictionaryValue* properties = NULL; 432 stub_services_.GetDictionaryWithoutPathExpansion(service_path, &properties); 433 return properties; 434 } 435 436 void ShillServiceClientStub::ClearServices() { 437 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()-> 438 ClearManagerServices(); 439 440 stub_services_.Clear(); 441 } 442 443 void ShillServiceClientStub::AddDefaultServices() { 444 const bool add_to_visible = true; 445 const bool add_to_watchlist = true; 446 447 if (!CommandLine::ForCurrentProcess()->HasSwitch( 448 chromeos::switches::kDisableStubEthernet)) { 449 AddService("eth1", "eth1", 450 flimflam::kTypeEthernet, 451 flimflam::kStateOnline, 452 add_to_visible, add_to_watchlist); 453 } 454 455 // Wifi 456 457 AddService("wifi1", "wifi1", 458 flimflam::kTypeWifi, 459 flimflam::kStateOnline, 460 add_to_visible, add_to_watchlist); 461 SetServiceProperty("wifi1", 462 flimflam::kSecurityProperty, 463 base::StringValue(flimflam::kSecurityWep)); 464 465 AddService("wifi2", "wifi2_PSK", 466 flimflam::kTypeWifi, 467 flimflam::kStateIdle, 468 add_to_visible, add_to_watchlist); 469 SetServiceProperty("wifi2", 470 flimflam::kSecurityProperty, 471 base::StringValue(flimflam::kSecurityPsk)); 472 base::FundamentalValue strength_value(80); 473 SetServiceProperty("wifi2", 474 flimflam::kSignalStrengthProperty, 475 strength_value); 476 477 if (CommandLine::ForCurrentProcess()->HasSwitch( 478 chromeos::switches::kEnableStubPortalledWifi)) { 479 AddService(kStubPortalledWifiPath, kStubPortalledWifiName, 480 flimflam::kTypeWifi, 481 flimflam::kStatePortal, 482 add_to_visible, add_to_watchlist); 483 SetServiceProperty(kStubPortalledWifiPath, 484 flimflam::kSecurityProperty, 485 base::StringValue(flimflam::kSecurityNone)); 486 } 487 488 // Wimax 489 490 AddService("wimax1", "wimax1", 491 flimflam::kTypeWimax, 492 flimflam::kStateIdle, 493 add_to_visible, add_to_watchlist); 494 SetServiceProperty("wimax1", 495 flimflam::kConnectableProperty, 496 base::FundamentalValue(true)); 497 498 // Cellular 499 500 AddService("cellular1", "cellular1", 501 flimflam::kTypeCellular, 502 flimflam::kStateIdle, 503 add_to_visible, add_to_watchlist); 504 base::StringValue technology_value(flimflam::kNetworkTechnologyGsm); 505 SetServiceProperty("cellular1", 506 flimflam::kNetworkTechnologyProperty, 507 technology_value); 508 SetServiceProperty("cellular1", 509 flimflam::kActivationStateProperty, 510 base::StringValue(flimflam::kActivationStateNotActivated)); 511 SetServiceProperty("cellular1", 512 flimflam::kRoamingStateProperty, 513 base::StringValue(flimflam::kRoamingStateHome)); 514 515 // VPN 516 517 // Set the "Provider" dictionary properties. Note: when setting these in 518 // Shill, "Provider.Type", etc keys are used, but when reading the values 519 // "Provider" . "Type", etc keys are used. Here we are setting the values 520 // that will be read (by the UI, tests, etc). 521 base::DictionaryValue provider_properties; 522 provider_properties.SetString(flimflam::kTypeProperty, 523 flimflam::kProviderOpenVpn); 524 provider_properties.SetString(flimflam::kHostProperty, "vpn_host"); 525 526 AddService("vpn1", "vpn1", 527 flimflam::kTypeVPN, 528 flimflam::kStateOnline, 529 add_to_visible, add_to_watchlist); 530 SetServiceProperty("vpn1", 531 flimflam::kProviderProperty, 532 provider_properties); 533 534 AddService("vpn2", "vpn2", 535 flimflam::kTypeVPN, 536 flimflam::kStateOffline, 537 add_to_visible, add_to_watchlist); 538 SetServiceProperty("vpn2", 539 flimflam::kProviderProperty, 540 provider_properties); 541 542 DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface()-> 543 AddService(ShillProfileClientStub::kSharedProfilePath, "wifi2"); 544 545 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()-> 546 SortManagerServices(); 547 } 548 549 void ShillServiceClientStub::NotifyObserversPropertyChanged( 550 const dbus::ObjectPath& service_path, 551 const std::string& property) { 552 base::DictionaryValue* dict = NULL; 553 std::string path = service_path.value(); 554 if (!stub_services_.GetDictionaryWithoutPathExpansion(path, &dict)) { 555 LOG(ERROR) << "Notify for unknown service: " << path; 556 return; 557 } 558 base::Value* value = NULL; 559 if (!dict->GetWithoutPathExpansion(property, &value)) { 560 LOG(ERROR) << "Notify for unknown property: " 561 << path << " : " << property; 562 return; 563 } 564 FOR_EACH_OBSERVER(ShillPropertyChangedObserver, 565 GetObserverList(service_path), 566 OnPropertyChanged(property, *value)); 567 } 568 569 base::DictionaryValue* ShillServiceClientStub::GetModifiableServiceProperties( 570 const std::string& service_path) { 571 base::DictionaryValue* properties = NULL; 572 if (!stub_services_.GetDictionaryWithoutPathExpansion( 573 service_path, &properties)) { 574 properties = new base::DictionaryValue; 575 stub_services_.Set(service_path, properties); 576 } 577 return properties; 578 } 579 580 ShillServiceClientStub::PropertyObserverList& 581 ShillServiceClientStub::GetObserverList(const dbus::ObjectPath& device_path) { 582 std::map<dbus::ObjectPath, PropertyObserverList*>::iterator iter = 583 observer_list_.find(device_path); 584 if (iter != observer_list_.end()) 585 return *(iter->second); 586 PropertyObserverList* observer_list = new PropertyObserverList(); 587 observer_list_[device_path] = observer_list; 588 return *observer_list; 589 } 590 591 void ShillServiceClientStub::SetOtherServicesOffline( 592 const std::string& service_path) { 593 const base::DictionaryValue* service_properties = GetServiceProperties( 594 service_path); 595 if (!service_properties) { 596 LOG(ERROR) << "Missing service: " << service_path; 597 return; 598 } 599 std::string service_type; 600 service_properties->GetString(flimflam::kTypeProperty, &service_type); 601 // Set all other services of the same type to offline (Idle). 602 for (base::DictionaryValue::Iterator iter(stub_services_); 603 !iter.IsAtEnd(); iter.Advance()) { 604 std::string path = iter.key(); 605 if (path == service_path) 606 continue; 607 base::DictionaryValue* properties; 608 if (!stub_services_.GetDictionaryWithoutPathExpansion(path, &properties)) 609 NOTREACHED(); 610 611 std::string type; 612 properties->GetString(flimflam::kTypeProperty, &type); 613 if (type != service_type) 614 continue; 615 properties->SetWithoutPathExpansion( 616 flimflam::kStateProperty, 617 base::Value::CreateStringValue(flimflam::kStateIdle)); 618 } 619 } 620 621 } // namespace chromeos 622