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