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& name, 309 const std::string& type, 310 const std::string& state, 311 bool visible) { 312 AddServiceWithIPConfig(service_path, "" /* guid */, name, 313 type, state, "" /* ipconfig_path */, 314 visible); 315 } 316 317 void FakeShillServiceClient::AddServiceWithIPConfig( 318 const std::string& service_path, 319 const std::string& guid, 320 const std::string& name, 321 const std::string& type, 322 const std::string& state, 323 const std::string& ipconfig_path, 324 bool visible) { 325 base::DictionaryValue* properties = SetServiceProperties( 326 service_path, guid, name, type, state, visible); 327 328 std::string profile_path; 329 if (properties->GetStringWithoutPathExpansion(shill::kProfileProperty, 330 &profile_path) && 331 !profile_path.empty()) { 332 DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface()-> 333 UpdateService(profile_path, service_path); 334 } 335 336 if (!ipconfig_path.empty()) { 337 properties->SetWithoutPathExpansion( 338 shill::kIPConfigProperty, 339 new base::StringValue(ipconfig_path)); 340 } 341 342 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()-> 343 AddManagerService(service_path, true); 344 } 345 346 347 base::DictionaryValue* FakeShillServiceClient::SetServiceProperties( 348 const std::string& service_path, 349 const std::string& guid, 350 const std::string& name, 351 const std::string& type, 352 const std::string& state, 353 bool visible) { 354 base::DictionaryValue* properties = 355 GetModifiableServiceProperties(service_path, true); 356 connect_behavior_.erase(service_path); 357 358 std::string profile_path; 359 base::DictionaryValue profile_properties; 360 if (DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface()-> 361 GetService(service_path, &profile_path, &profile_properties)) { 362 properties->SetWithoutPathExpansion( 363 shill::kProfileProperty, 364 new base::StringValue(profile_path)); 365 } 366 367 // If |guid| is provided, set Service.GUID to that. Otherwise if a GUID is 368 // stored in a profile entry, use that. Otherwise leave it blank. Shill does 369 // not enforce a valid guid, we do that at the NetworkStateHandler layer. 370 std::string guid_to_set = guid; 371 if (guid_to_set.empty()) { 372 profile_properties.GetStringWithoutPathExpansion( 373 shill::kGuidProperty, &guid_to_set); 374 } 375 if (!guid_to_set.empty()) { 376 properties->SetWithoutPathExpansion(shill::kGuidProperty, 377 new base::StringValue(guid_to_set)); 378 } 379 shill_property_util::SetSSID(name, properties); 380 properties->SetWithoutPathExpansion( 381 shill::kNameProperty, 382 new base::StringValue(name)); 383 std::string device_path = 384 DBusThreadManager::Get()->GetShillDeviceClient()->GetTestInterface()-> 385 GetDevicePathForType(type); 386 properties->SetWithoutPathExpansion( 387 shill::kDeviceProperty, 388 new base::StringValue(device_path)); 389 properties->SetWithoutPathExpansion( 390 shill::kTypeProperty, 391 new base::StringValue(type)); 392 properties->SetWithoutPathExpansion( 393 shill::kStateProperty, 394 new base::StringValue(state)); 395 properties->SetWithoutPathExpansion( 396 shill::kVisibleProperty, 397 new base::FundamentalValue(visible)); 398 if (type == shill::kTypeWifi) { 399 properties->SetWithoutPathExpansion( 400 shill::kSecurityProperty, 401 new base::StringValue(shill::kSecurityNone)); 402 } 403 return properties; 404 } 405 406 void FakeShillServiceClient::RemoveService(const std::string& service_path) { 407 stub_services_.RemoveWithoutPathExpansion(service_path, NULL); 408 connect_behavior_.erase(service_path); 409 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()-> 410 RemoveManagerService(service_path); 411 } 412 413 bool FakeShillServiceClient::SetServiceProperty(const std::string& service_path, 414 const std::string& property, 415 const base::Value& value) { 416 base::DictionaryValue* dict = NULL; 417 if (!stub_services_.GetDictionaryWithoutPathExpansion(service_path, &dict)) 418 return false; 419 420 VLOG(1) << "Service.SetProperty: " << property << " = " << value 421 << " For: " << service_path; 422 423 base::DictionaryValue new_properties; 424 std::string changed_property; 425 bool case_sensitive = true; 426 if (StartsWithASCII(property, "Provider.", case_sensitive) || 427 StartsWithASCII(property, "OpenVPN.", case_sensitive) || 428 StartsWithASCII(property, "L2TPIPsec.", case_sensitive)) { 429 // These properties are only nested within the Provider dictionary if read 430 // from Shill. 431 base::DictionaryValue* provider = new base::DictionaryValue; 432 provider->SetWithoutPathExpansion(property, value.DeepCopy()); 433 new_properties.SetWithoutPathExpansion(shill::kProviderProperty, provider); 434 changed_property = shill::kProviderProperty; 435 } else { 436 new_properties.SetWithoutPathExpansion(property, value.DeepCopy()); 437 changed_property = property; 438 } 439 440 dict->MergeDictionary(&new_properties); 441 442 // Add or update the profile entry. 443 if (property == shill::kProfileProperty) { 444 std::string profile_path; 445 if (value.GetAsString(&profile_path)) { 446 DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface()-> 447 AddService(profile_path, service_path); 448 } else { 449 LOG(ERROR) << "Profile value is not a String!"; 450 } 451 } else { 452 std::string profile_path; 453 if (dict->GetStringWithoutPathExpansion( 454 shill::kProfileProperty, &profile_path) && !profile_path.empty()) { 455 DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface()-> 456 UpdateService(profile_path, service_path); 457 } 458 } 459 460 // Notify the Manager if the state changed (affects DefaultService). 461 if (property == shill::kStateProperty) { 462 std::string state; 463 value.GetAsString(&state); 464 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()-> 465 ServiceStateChanged(service_path, state); 466 } 467 468 // If the State or Visibility changes, the sort order of service lists may 469 // change and the DefaultService property may change. 470 if (property == shill::kStateProperty || 471 property == shill::kVisibleProperty) { 472 base::MessageLoop::current()->PostTask( 473 FROM_HERE, base::Bind(&CallSortManagerServices)); 474 } 475 476 // Notifiy Chrome of the property change. 477 base::MessageLoop::current()->PostTask( 478 FROM_HERE, 479 base::Bind(&FakeShillServiceClient::NotifyObserversPropertyChanged, 480 weak_ptr_factory_.GetWeakPtr(), 481 dbus::ObjectPath(service_path), changed_property)); 482 return true; 483 } 484 485 const base::DictionaryValue* FakeShillServiceClient::GetServiceProperties( 486 const std::string& service_path) const { 487 const base::DictionaryValue* properties = NULL; 488 stub_services_.GetDictionaryWithoutPathExpansion(service_path, &properties); 489 return properties; 490 } 491 492 void FakeShillServiceClient::ClearServices() { 493 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()-> 494 ClearManagerServices(); 495 496 stub_services_.Clear(); 497 connect_behavior_.clear(); 498 } 499 500 void FakeShillServiceClient::SetConnectBehavior(const std::string& service_path, 501 const base::Closure& behavior) { 502 connect_behavior_[service_path] = behavior; 503 } 504 505 void FakeShillServiceClient::NotifyObserversPropertyChanged( 506 const dbus::ObjectPath& service_path, 507 const std::string& property) { 508 base::DictionaryValue* dict = NULL; 509 std::string path = service_path.value(); 510 if (!stub_services_.GetDictionaryWithoutPathExpansion(path, &dict)) { 511 LOG(ERROR) << "Notify for unknown service: " << path; 512 return; 513 } 514 base::Value* value = NULL; 515 if (!dict->GetWithoutPathExpansion(property, &value)) { 516 LOG(ERROR) << "Notify for unknown property: " 517 << path << " : " << property; 518 return; 519 } 520 FOR_EACH_OBSERVER(ShillPropertyChangedObserver, 521 GetObserverList(service_path), 522 OnPropertyChanged(property, *value)); 523 } 524 525 base::DictionaryValue* FakeShillServiceClient::GetModifiableServiceProperties( 526 const std::string& service_path, bool create_if_missing) { 527 base::DictionaryValue* properties = NULL; 528 if (!stub_services_.GetDictionaryWithoutPathExpansion(service_path, 529 &properties) && 530 create_if_missing) { 531 properties = new base::DictionaryValue; 532 stub_services_.Set(service_path, properties); 533 } 534 return properties; 535 } 536 537 FakeShillServiceClient::PropertyObserverList& 538 FakeShillServiceClient::GetObserverList(const dbus::ObjectPath& device_path) { 539 std::map<dbus::ObjectPath, PropertyObserverList*>::iterator iter = 540 observer_list_.find(device_path); 541 if (iter != observer_list_.end()) 542 return *(iter->second); 543 PropertyObserverList* observer_list = new PropertyObserverList(); 544 observer_list_[device_path] = observer_list; 545 return *observer_list; 546 } 547 548 void FakeShillServiceClient::SetOtherServicesOffline( 549 const std::string& service_path) { 550 const base::DictionaryValue* service_properties = GetServiceProperties( 551 service_path); 552 if (!service_properties) { 553 LOG(ERROR) << "Missing service: " << service_path; 554 return; 555 } 556 std::string service_type; 557 service_properties->GetString(shill::kTypeProperty, &service_type); 558 // Set all other services of the same type to offline (Idle). 559 for (base::DictionaryValue::Iterator iter(stub_services_); 560 !iter.IsAtEnd(); iter.Advance()) { 561 std::string path = iter.key(); 562 if (path == service_path) 563 continue; 564 base::DictionaryValue* properties; 565 if (!stub_services_.GetDictionaryWithoutPathExpansion(path, &properties)) 566 NOTREACHED(); 567 568 std::string type; 569 properties->GetString(shill::kTypeProperty, &type); 570 if (type != service_type) 571 continue; 572 properties->SetWithoutPathExpansion( 573 shill::kStateProperty, 574 new base::StringValue(shill::kStateIdle)); 575 } 576 } 577 578 void FakeShillServiceClient::SetCellularActivated( 579 const dbus::ObjectPath& service_path, 580 const ErrorCallback& error_callback) { 581 SetProperty(service_path, 582 shill::kActivationStateProperty, 583 base::StringValue(shill::kActivationStateActivated), 584 base::Bind(&base::DoNothing), 585 error_callback); 586 SetProperty(service_path, 587 shill::kConnectableProperty, 588 base::FundamentalValue(true), 589 base::Bind(&base::DoNothing), 590 error_callback); 591 } 592 593 void FakeShillServiceClient::ContinueConnect( 594 const std::string& service_path) { 595 VLOG(1) << "FakeShillServiceClient::ContinueConnect: " << service_path; 596 base::DictionaryValue* service_properties = NULL; 597 if (!stub_services_.GetDictionary(service_path, &service_properties)) { 598 LOG(ERROR) << "Service not found: " << service_path; 599 return; 600 } 601 602 if (ContainsKey(connect_behavior_, service_path)) { 603 const base::Closure& custom_connect_behavior = 604 connect_behavior_[service_path]; 605 custom_connect_behavior.Run(); 606 return; 607 } 608 609 // No custom connect behavior set, continue with the default connect behavior. 610 std::string passphrase; 611 service_properties->GetStringWithoutPathExpansion( 612 shill::kPassphraseProperty, &passphrase); 613 if (passphrase == "failure") { 614 // Simulate a password failure. 615 SetServiceProperty(service_path, 616 shill::kStateProperty, 617 base::StringValue(shill::kStateFailure)); 618 base::MessageLoop::current()->PostTask( 619 FROM_HERE, 620 base::Bind( 621 base::IgnoreResult(&FakeShillServiceClient::SetServiceProperty), 622 weak_ptr_factory_.GetWeakPtr(), 623 service_path, 624 shill::kErrorProperty, 625 base::StringValue(shill::kErrorBadPassphrase))); 626 } else { 627 // Set Online. 628 SetServiceProperty(service_path, 629 shill::kStateProperty, 630 base::StringValue(shill::kStateOnline)); 631 } 632 } 633 634 } // namespace chromeos 635