| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "chromeos/network/network_connection_handler.h" | 
 |  | 
 | #include <map> | 
 | #include <memory> | 
 | #include <set> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/callback.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/json/json_reader.h" | 
 | #include "base/macros.h" | 
 | #include "base/message_loop/message_loop.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "chromeos/cert_loader.h" | 
 | #include "chromeos/dbus/dbus_thread_manager.h" | 
 | #include "chromeos/dbus/shill_device_client.h" | 
 | #include "chromeos/dbus/shill_manager_client.h" | 
 | #include "chromeos/dbus/shill_profile_client.h" | 
 | #include "chromeos/dbus/shill_service_client.h" | 
 | #include "chromeos/network/managed_network_configuration_handler_impl.h" | 
 | #include "chromeos/network/network_configuration_handler.h" | 
 | #include "chromeos/network/network_connection_observer.h" | 
 | #include "chromeos/network/network_profile_handler.h" | 
 | #include "chromeos/network/network_state_handler.h" | 
 | #include "chromeos/network/onc/onc_utils.h" | 
 | #include "components/onc/onc_constants.h" | 
 | #include "crypto/scoped_nss_types.h" | 
 | #include "crypto/scoped_test_nss_db.h" | 
 | #include "net/base/net_errors.h" | 
 | #include "net/cert/nss_cert_database_chromeos.h" | 
 | #include "net/cert/x509_certificate.h" | 
 | #include "net/test/cert_test_util.h" | 
 | #include "net/test/test_data_directory.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 | #include "third_party/cros_system_api/dbus/service_constants.h" | 
 |  | 
 | namespace chromeos { | 
 |  | 
 | namespace { | 
 |  | 
 | const char* kSuccessResult = "success"; | 
 | const char* kUsernameHash = "userhash"; | 
 |  | 
 | void ConfigureCallback(const dbus::ObjectPath& result) { | 
 | } | 
 |  | 
 | void ConfigureErrorCallback(const std::string& error_name, | 
 |                             const std::string& error_message) { | 
 | } | 
 |  | 
 | class TestNetworkConnectionObserver : public NetworkConnectionObserver { | 
 |  public: | 
 |   TestNetworkConnectionObserver() {} | 
 |   ~TestNetworkConnectionObserver() override {} | 
 |  | 
 |   // NetworkConnectionObserver | 
 |   void ConnectToNetworkRequested(const std::string& service_path) override { | 
 |     requests_.insert(service_path); | 
 |   } | 
 |  | 
 |   void ConnectSucceeded(const std::string& service_path) override { | 
 |     results_[service_path] = kSuccessResult; | 
 |   } | 
 |  | 
 |   void ConnectFailed(const std::string& service_path, | 
 |                      const std::string& error_name) override { | 
 |     results_[service_path] = error_name; | 
 |   } | 
 |  | 
 |   void DisconnectRequested(const std::string& service_path) override { | 
 |     requests_.insert(service_path); | 
 |   } | 
 |  | 
 |   bool GetRequested(const std::string& service_path) { | 
 |     return requests_.count(service_path) != 0; | 
 |   } | 
 |  | 
 |   std::string GetResult(const std::string& service_path) { | 
 |     auto iter = results_.find(service_path); | 
 |     if (iter == results_.end()) | 
 |       return ""; | 
 |     return iter->second; | 
 |   } | 
 |  | 
 |  private: | 
 |   std::set<std::string> requests_; | 
 |   std::map<std::string, std::string> results_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(TestNetworkConnectionObserver); | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | class NetworkConnectionHandlerTest : public testing::Test { | 
 |  public: | 
 |   NetworkConnectionHandlerTest() | 
 |       : test_manager_client_(nullptr), test_service_client_(nullptr) {} | 
 |  | 
 |   ~NetworkConnectionHandlerTest() override {} | 
 |  | 
 |   void SetUp() override { | 
 |     ASSERT_TRUE(test_nssdb_.is_open()); | 
 |  | 
 |     // Use the same DB for public and private slot. | 
 |     test_nsscertdb_.reset(new net::NSSCertDatabaseChromeOS( | 
 |         crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())), | 
 |         crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())))); | 
 |     test_nsscertdb_->SetSlowTaskRunnerForTest(message_loop_.task_runner()); | 
 |  | 
 |     CertLoader::Initialize(); | 
 |     CertLoader::ForceHardwareBackedForTesting(); | 
 |  | 
 |     DBusThreadManager::Initialize(); | 
 |     DBusThreadManager* dbus_manager = DBusThreadManager::Get(); | 
 |     test_manager_client_ = | 
 |         dbus_manager->GetShillManagerClient()->GetTestInterface(); | 
 |     test_service_client_ = | 
 |         dbus_manager->GetShillServiceClient()->GetTestInterface(); | 
 |  | 
 |     test_manager_client_->AddTechnology(shill::kTypeWifi, true /* enabled */); | 
 |     dbus_manager->GetShillDeviceClient()->GetTestInterface()->AddDevice( | 
 |         "/device/wifi1", shill::kTypeWifi, "wifi_device1"); | 
 |     test_manager_client_->AddTechnology(shill::kTypeCellular, | 
 |                                         true /* enabled */); | 
 |     dbus_manager->GetShillProfileClient()->GetTestInterface()->AddProfile( | 
 |         "shared_profile_path", std::string() /* shared profile */); | 
 |     dbus_manager->GetShillProfileClient()->GetTestInterface()->AddProfile( | 
 |         "user_profile_path", kUsernameHash); | 
 |  | 
 |     base::RunLoop().RunUntilIdle(); | 
 |     LoginState::Initialize(); | 
 |     network_state_handler_ = NetworkStateHandler::InitializeForTest(); | 
 |     network_config_handler_.reset( | 
 |         NetworkConfigurationHandler::InitializeForTest( | 
 |             network_state_handler_.get(), | 
 |             nullptr /* network_device_handler */)); | 
 |  | 
 |     network_profile_handler_.reset(new NetworkProfileHandler()); | 
 |     network_profile_handler_->Init(); | 
 |  | 
 |     managed_config_handler_.reset(new ManagedNetworkConfigurationHandlerImpl()); | 
 |     managed_config_handler_->Init( | 
 |         network_state_handler_.get(), network_profile_handler_.get(), | 
 |         network_config_handler_.get(), nullptr /* network_device_handler */, | 
 |         nullptr /* prohibited_tecnologies_handler */); | 
 |  | 
 |     network_connection_handler_.reset(new NetworkConnectionHandler); | 
 |     network_connection_handler_->Init(network_state_handler_.get(), | 
 |                                       network_config_handler_.get(), | 
 |                                       managed_config_handler_.get()); | 
 |     network_connection_observer_.reset(new TestNetworkConnectionObserver); | 
 |     network_connection_handler_->AddObserver( | 
 |         network_connection_observer_.get()); | 
 |  | 
 |     base::RunLoop().RunUntilIdle(); | 
 |   } | 
 |  | 
 |   void TearDown() override { | 
 |     network_state_handler_->Shutdown(); | 
 |     managed_config_handler_.reset(); | 
 |     network_profile_handler_.reset(); | 
 |     network_connection_handler_->RemoveObserver( | 
 |         network_connection_observer_.get()); | 
 |     network_connection_observer_.reset(); | 
 |     network_connection_handler_.reset(); | 
 |     network_config_handler_.reset(); | 
 |     network_state_handler_.reset(); | 
 |     CertLoader::Shutdown(); | 
 |     LoginState::Shutdown(); | 
 |     DBusThreadManager::Shutdown(); | 
 |   } | 
 |  | 
 |  protected: | 
 |   bool Configure(const std::string& json_string) { | 
 |     std::unique_ptr<base::DictionaryValue> json_dict = | 
 |         onc::ReadDictionaryFromJson(json_string); | 
 |     if (!json_dict) { | 
 |       LOG(ERROR) << "Error parsing json: " << json_string; | 
 |       return false; | 
 |     } | 
 |     DBusThreadManager::Get()->GetShillManagerClient()->ConfigureService( | 
 |         *json_dict, | 
 |         base::Bind(&ConfigureCallback), | 
 |         base::Bind(&ConfigureErrorCallback)); | 
 |     base::RunLoop().RunUntilIdle(); | 
 |     return true; | 
 |   } | 
 |  | 
 |   void Connect(const std::string& service_path) { | 
 |     const bool check_error_state = true; | 
 |     network_connection_handler_->ConnectToNetwork( | 
 |         service_path, | 
 |         base::Bind(&NetworkConnectionHandlerTest::SuccessCallback, | 
 |                    base::Unretained(this)), | 
 |         base::Bind(&NetworkConnectionHandlerTest::ErrorCallback, | 
 |                    base::Unretained(this)), | 
 |         check_error_state); | 
 |     base::RunLoop().RunUntilIdle(); | 
 |   } | 
 |  | 
 |   void Disconnect(const std::string& service_path) { | 
 |     network_connection_handler_->DisconnectNetwork( | 
 |         service_path, | 
 |         base::Bind(&NetworkConnectionHandlerTest::SuccessCallback, | 
 |                    base::Unretained(this)), | 
 |         base::Bind(&NetworkConnectionHandlerTest::ErrorCallback, | 
 |                    base::Unretained(this))); | 
 |     base::RunLoop().RunUntilIdle(); | 
 |   } | 
 |  | 
 |   void SuccessCallback() { | 
 |     result_ = kSuccessResult; | 
 |   } | 
 |  | 
 |   void ErrorCallback(const std::string& error_name, | 
 |                      std::unique_ptr<base::DictionaryValue> error_data) { | 
 |     result_ = error_name; | 
 |   } | 
 |  | 
 |   std::string GetResultAndReset() { | 
 |     std::string result; | 
 |     result.swap(result_); | 
 |     return result; | 
 |   } | 
 |  | 
 |   std::string GetServiceStringProperty(const std::string& service_path, | 
 |                                        const std::string& key) { | 
 |     std::string result; | 
 |     const base::DictionaryValue* properties = | 
 |         test_service_client_->GetServiceProperties(service_path); | 
 |     if (properties) | 
 |       properties->GetStringWithoutPathExpansion(key, &result); | 
 |     return result; | 
 |   } | 
 |  | 
 |   void StartCertLoader() { | 
 |     CertLoader::Get()->StartWithNSSDB(test_nsscertdb_.get()); | 
 |     base::RunLoop().RunUntilIdle(); | 
 |   } | 
 |  | 
 |   void LoginToRegularUser() { | 
 |     LoginState::Get()->SetLoggedInState(LoginState::LOGGED_IN_ACTIVE, | 
 |                                         LoginState::LOGGED_IN_USER_REGULAR); | 
 |     base::RunLoop().RunUntilIdle(); | 
 |   } | 
 |  | 
 |   scoped_refptr<net::X509Certificate> ImportTestClientCert() { | 
 |     net::CertificateList ca_cert_list(net::CreateCertificateListFromFile( | 
 |         net::GetTestCertsDirectory(), "client_1_ca.pem", | 
 |         net::X509Certificate::FORMAT_AUTO)); | 
 |     if (ca_cert_list.empty()) { | 
 |       LOG(ERROR) << "No CA cert loaded."; | 
 |       return nullptr; | 
 |     } | 
 |     net::NSSCertDatabase::ImportCertFailureList failures; | 
 |     EXPECT_TRUE(test_nsscertdb_->ImportCACerts( | 
 |         ca_cert_list, net::NSSCertDatabase::TRUST_DEFAULT, &failures)); | 
 |     if (!failures.empty()) { | 
 |       LOG(ERROR) << net::ErrorToString(failures[0].net_error); | 
 |       return nullptr; | 
 |     } | 
 |  | 
 |     // Import a client cert signed by that CA. | 
 |     scoped_refptr<net::X509Certificate> client_cert( | 
 |         net::ImportClientCertAndKeyFromFile(net::GetTestCertsDirectory(), | 
 |                                             "client_1.pem", "client_1.pk8", | 
 |                                             test_nssdb_.slot())); | 
 |     return client_cert; | 
 |   } | 
 |  | 
 |   void SetupPolicy(const std::string& network_configs_json, | 
 |                    const base::DictionaryValue& global_config, | 
 |                    bool user_policy) { | 
 |     std::string error; | 
 |     std::unique_ptr<base::Value> network_configs_value = | 
 |         base::JSONReader::ReadAndReturnError(network_configs_json, | 
 |                                              base::JSON_ALLOW_TRAILING_COMMAS, | 
 |                                              nullptr, &error); | 
 |     ASSERT_TRUE(network_configs_value) << error; | 
 |  | 
 |     base::ListValue* network_configs = nullptr; | 
 |     ASSERT_TRUE(network_configs_value->GetAsList(&network_configs)); | 
 |  | 
 |     if (user_policy) { | 
 |       managed_config_handler_->SetPolicy(::onc::ONC_SOURCE_USER_POLICY, | 
 |                                          kUsernameHash, *network_configs, | 
 |                                          global_config); | 
 |     } else { | 
 |       managed_config_handler_->SetPolicy(::onc::ONC_SOURCE_DEVICE_POLICY, | 
 |                                          std::string(),  // no username hash | 
 |                                          *network_configs, | 
 |                                          global_config); | 
 |     } | 
 |     base::RunLoop().RunUntilIdle(); | 
 |   } | 
 |  | 
 |   std::unique_ptr<NetworkStateHandler> network_state_handler_; | 
 |   std::unique_ptr<NetworkConfigurationHandler> network_config_handler_; | 
 |   std::unique_ptr<NetworkConnectionHandler> network_connection_handler_; | 
 |   std::unique_ptr<TestNetworkConnectionObserver> network_connection_observer_; | 
 |   std::unique_ptr<ManagedNetworkConfigurationHandlerImpl> | 
 |       managed_config_handler_; | 
 |   std::unique_ptr<NetworkProfileHandler> network_profile_handler_; | 
 |   ShillManagerClient::TestInterface* test_manager_client_; | 
 |   ShillServiceClient::TestInterface* test_service_client_; | 
 |   crypto::ScopedTestNSSDB test_nssdb_; | 
 |   std::unique_ptr<net::NSSCertDatabaseChromeOS> test_nsscertdb_; | 
 |   base::MessageLoopForUI message_loop_; | 
 |   std::string result_; | 
 |  | 
 |  private: | 
 |   DISALLOW_COPY_AND_ASSIGN(NetworkConnectionHandlerTest); | 
 | }; | 
 |  | 
 | namespace { | 
 |  | 
 | const char* kNoNetwork = "no-network"; | 
 | const char* kWifi0 = "wifi0"; | 
 | const char* kWifi1 = "wifi1"; | 
 | const char* kWifi2 = "wifi2"; | 
 | const char* kWifi3 = "wifi3"; | 
 |  | 
 | const char* kConfigConnectable = | 
 |     "{ \"GUID\": \"wifi0\", \"Type\": \"wifi\", \"State\": \"idle\", " | 
 |     "  \"Connectable\": true }"; | 
 | const char* kConfigConnected = | 
 |     "{ \"GUID\": \"wifi1\", \"Type\": \"wifi\", \"State\": \"online\" }"; | 
 | const char* kConfigConnecting = | 
 |     "{ \"GUID\": \"wifi2\", \"Type\": \"wifi\", \"State\": \"association\" }"; | 
 | const char* kConfigRequiresPassphrase = | 
 |     "{ \"GUID\": \"wifi3\", \"Type\": \"wifi\", " | 
 |     "  \"PassphraseRequired\": true }"; | 
 |  | 
 | const char* kPolicyWifi0 = | 
 |     "[{ \"GUID\": \"wifi0\",  \"IPAddressConfigType\": \"DHCP\", " | 
 |     "   \"Type\": \"WiFi\", \"Name\": \"My WiFi Network\"," | 
 |     "   \"WiFi\": { \"SSID\": \"wifi0\"}}]"; | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST_F(NetworkConnectionHandlerTest, NetworkConnectionHandlerConnectSuccess) { | 
 |   EXPECT_TRUE(Configure(kConfigConnectable)); | 
 |   Connect(kWifi0); | 
 |   EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
 |   EXPECT_EQ(shill::kStateOnline, | 
 |             GetServiceStringProperty(kWifi0, shill::kStateProperty)); | 
 |   // Observer expectations | 
 |   EXPECT_TRUE(network_connection_observer_->GetRequested(kWifi0)); | 
 |   EXPECT_EQ(kSuccessResult, network_connection_observer_->GetResult(kWifi0)); | 
 | } | 
 |  | 
 | TEST_F(NetworkConnectionHandlerTest, | 
 |        NetworkConnectionHandlerConnectProhibited) { | 
 |   EXPECT_TRUE(Configure(kConfigConnectable)); | 
 |   base::DictionaryValue global_config; | 
 |   global_config.SetBooleanWithoutPathExpansion( | 
 |       ::onc::global_network_config::kAllowOnlyPolicyNetworksToConnect, true); | 
 |   SetupPolicy("[]", global_config, false /* load as device policy */); | 
 |   LoginToRegularUser(); | 
 |   Connect(kWifi0); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorUnmanagedNetwork, | 
 |             GetResultAndReset()); | 
 |  | 
 |   SetupPolicy(kPolicyWifi0, global_config, false /* load as device policy */); | 
 |   Connect(kWifi0); | 
 |   EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
 | } | 
 |  | 
 | // Handles basic failure cases. | 
 | TEST_F(NetworkConnectionHandlerTest, NetworkConnectionHandlerConnectFailure) { | 
 |   Connect(kNoNetwork); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorConfigureFailed, | 
 |             GetResultAndReset()); | 
 |   EXPECT_TRUE(network_connection_observer_->GetRequested(kNoNetwork)); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorConfigureFailed, | 
 |             network_connection_observer_->GetResult(kNoNetwork)); | 
 |  | 
 |   EXPECT_TRUE(Configure(kConfigConnected)); | 
 |   Connect(kWifi1); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorConnected, GetResultAndReset()); | 
 |   EXPECT_TRUE(network_connection_observer_->GetRequested(kWifi1)); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorConnected, | 
 |             network_connection_observer_->GetResult(kWifi1)); | 
 |  | 
 |   EXPECT_TRUE(Configure(kConfigConnecting)); | 
 |   Connect(kWifi2); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorConnecting, GetResultAndReset()); | 
 |   EXPECT_TRUE(network_connection_observer_->GetRequested(kWifi2)); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorConnecting, | 
 |             network_connection_observer_->GetResult(kWifi2)); | 
 |  | 
 |   EXPECT_TRUE(Configure(kConfigRequiresPassphrase)); | 
 |   Connect(kWifi3); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorPassphraseRequired, | 
 |             GetResultAndReset()); | 
 |   EXPECT_TRUE(network_connection_observer_->GetRequested(kWifi3)); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorPassphraseRequired, | 
 |             network_connection_observer_->GetResult(kWifi3)); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | const char* kPolicyWithCertPatternTemplate = | 
 |     "[ { \"GUID\": \"wifi4\"," | 
 |     "    \"Name\": \"wifi4\"," | 
 |     "    \"Type\": \"WiFi\"," | 
 |     "    \"WiFi\": {" | 
 |     "      \"Security\": \"WPA-EAP\"," | 
 |     "      \"SSID\": \"wifi_ssid\"," | 
 |     "      \"EAP\": {" | 
 |     "        \"Outer\": \"EAP-TLS\"," | 
 |     "        \"ClientCertType\": \"Pattern\"," | 
 |     "        \"ClientCertPattern\": {" | 
 |     "          \"Subject\": {" | 
 |     "            \"CommonName\" : \"%s\"" | 
 |     "          }" | 
 |     "        }" | 
 |     "      }" | 
 |     "    }" | 
 |     "} ]"; | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Handle certificates. | 
 | TEST_F(NetworkConnectionHandlerTest, ConnectCertificateMissing) { | 
 |   StartCertLoader(); | 
 |   SetupPolicy(base::StringPrintf(kPolicyWithCertPatternTemplate, "unknown"), | 
 |               base::DictionaryValue(),  // no global config | 
 |               true);                    // load as user policy | 
 |  | 
 |   Connect("wifi4"); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorCertificateRequired, | 
 |             GetResultAndReset()); | 
 | } | 
 |  | 
 | TEST_F(NetworkConnectionHandlerTest, ConnectWithCertificateSuccess) { | 
 |   StartCertLoader(); | 
 |   scoped_refptr<net::X509Certificate> cert = ImportTestClientCert(); | 
 |   ASSERT_TRUE(cert.get()); | 
 |  | 
 |   SetupPolicy(base::StringPrintf(kPolicyWithCertPatternTemplate, | 
 |                                  cert->subject().common_name.c_str()), | 
 |               base::DictionaryValue(),  // no global config | 
 |               true);                    // load as user policy | 
 |  | 
 |   Connect("wifi4"); | 
 |   EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
 | } | 
 |  | 
 | // Disabled, see http://crbug.com/396729. | 
 | TEST_F(NetworkConnectionHandlerTest, | 
 |        DISABLED_ConnectWithCertificateRequestedBeforeCertsAreLoaded) { | 
 |   scoped_refptr<net::X509Certificate> cert = ImportTestClientCert(); | 
 |   ASSERT_TRUE(cert.get()); | 
 |  | 
 |   SetupPolicy(base::StringPrintf(kPolicyWithCertPatternTemplate, | 
 |                                  cert->subject().common_name.c_str()), | 
 |               base::DictionaryValue(),  // no global config | 
 |               true);                    // load as user policy | 
 |  | 
 |   Connect("wifi4"); | 
 |  | 
 |   // Connect request came before the cert loader loaded certificates, so the | 
 |   // connect request should have been throttled until the certificates are | 
 |   // loaded. | 
 |   EXPECT_EQ("", GetResultAndReset()); | 
 |  | 
 |   StartCertLoader(); | 
 |  | 
 |   // |StartCertLoader| should have triggered certificate loading. | 
 |   // When the certificates got loaded, the connection request should have | 
 |   // proceeded and eventually succeeded. | 
 |   EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
 | } | 
 |  | 
 | TEST_F(NetworkConnectionHandlerTest, | 
 |        NetworkConnectionHandlerDisconnectSuccess) { | 
 |   EXPECT_TRUE(Configure(kConfigConnected)); | 
 |   Disconnect(kWifi1); | 
 |   EXPECT_TRUE(network_connection_observer_->GetRequested(kWifi1)); | 
 |   EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
 | } | 
 |  | 
 | TEST_F(NetworkConnectionHandlerTest, | 
 |        NetworkConnectionHandlerDisconnectFailure) { | 
 |   Connect(kNoNetwork); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorConfigureFailed, | 
 |             GetResultAndReset()); | 
 |  | 
 |   EXPECT_TRUE(Configure(kConfigConnectable)); | 
 |   Disconnect(kWifi0); | 
 |   EXPECT_EQ(NetworkConnectionHandler::kErrorNotConnected, GetResultAndReset()); | 
 | } | 
 |  | 
 | }  // namespace chromeos |