shill: WiFi: Mark pending service idle on disconnect

When disconnecting from the pending service, we cannot depend on
wpa_supplicant to set the CurrentBSS in order to drive the service
state to "idle".  This case was covered when connecting to a new
service while there was already a pending service, but this issue
can happen in other situations, such as when all the pending
service's BSSes are removed while connecting, or any other situation
where DisconnectFrom is called on the pending service.

BUG=chrome-os-partner:12111,chromium-os:33512
TEST=network_WiFiRoaming.010RetryReconnectHidden, new unit test

Change-Id: I77e4fcf52ea6b2ad67bc21f11fb2786ee9409c1a
Reviewed-on: https://gerrit.chromium.org/gerrit/30080
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Commit-Ready: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/wifi.cc b/wifi.cc
index 33a84b5..bb83755 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -285,9 +285,6 @@
   }
 
   if (pending_service_ && pending_service_ != service) {
-    // Since wpa_supplicant has not yet set CurrentBSS, we can't depend
-    // on this to drive the service state back to idle.  Do that here.
-    pending_service_->SetState(Service::kStateIdle);
     DisconnectFrom(pending_service_);
   }
 
@@ -347,6 +344,12 @@
     return;
   }
 
+  if (pending_service_) {
+    // Since wpa_supplicant has not yet set CurrentBSS, we can't depend
+    // on this to drive the service state back to idle.  Do that here.
+    pending_service_->SetState(Service::kStateIdle);
+  }
+
   SetPendingService(NULL);
   StopReconnectTimer();
 
@@ -1381,6 +1384,8 @@
 }
 
 void WiFi::SetPendingService(const WiFiServiceRefPtr &service) {
+  SLOG(WiFi, 2) << "WiFi " << link_name() << " setting pending service to "
+                << (service ? service->friendly_name(): "NULL");
   if (service) {
     service->SetState(Service::kStateAssociating);
     StartPendingTimer();
@@ -1393,8 +1398,10 @@
 void WiFi::PendingTimeoutHandler() {
   LOG(INFO) << "WiFi Device " << link_name() << ": " << __func__;
   CHECK(pending_service_);
-  pending_service_->SetFailure(Service::kFailureOutOfRange);
+  // Take a reference since pending_service_ will reset below.
+  ServiceRefPtr service = pending_service_;
   DisconnectFrom(pending_service_);
+  service->SetFailure(Service::kFailureOutOfRange);
 }
 
 void WiFi::StartReconnectTimer() {
diff --git a/wifi_unittest.cc b/wifi_unittest.cc
index ec8a9e7..9530205 100644
--- a/wifi_unittest.cc
+++ b/wifi_unittest.cc
@@ -1082,8 +1082,10 @@
   InitiateConnect(service);
 
   EXPECT_FALSE(GetPendingService() == NULL);
+  EXPECT_TRUE(service->IsConnecting());
   EXPECT_CALL(supplicant_interface_proxy, Disconnect());
   InitiateDisconnect(service);
+  EXPECT_FALSE(service->IsConnecting());
 
   EXPECT_TRUE(GetPendingService() == NULL);
 }
@@ -1175,8 +1177,12 @@
   EXPECT_FALSE(pending_timeout.IsCancelled());
   EXPECT_EQ(service, GetPendingService());
 
-  EXPECT_CALL(*service, SetFailure(Service::kFailureOutOfRange));
-  pending_timeout.callback().Run();
+  {
+    InSequence seq;
+    EXPECT_CALL(*service, SetState(Service::kStateIdle));
+    EXPECT_CALL(*service, SetFailure(Service::kFailureOutOfRange));
+    pending_timeout.callback().Run();
+  }
   EXPECT_EQ(NULL, GetPendingService().get());
 
   // Verify expectations now, because WiFi may report other state changes