shill: WiFi: Add TDLS operations to Device RPC API

Add a "PerformTDLSOperation" method to the Device API.  This is
only implemented in WiFi, which dispatches to the various TDLS
methods available on the supplicant proxy.

CQ-DEPEND=CL:176670
BUG=chromium:316758
TEST=Unit tests

Change-Id: I9c2179111fe61d5f520ced5f1f6561c01d9aeeb7
Reviewed-on: https://chromium-review.googlesource.com/176720
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Paul Stewart <pstew@chromium.org>
diff --git a/dbus_bindings/org.chromium.flimflam.Device.xml b/dbus_bindings/org.chromium.flimflam.Device.xml
index 8120263..c76cbd6 100644
--- a/dbus_bindings/org.chromium.flimflam.Device.xml
+++ b/dbus_bindings/org.chromium.flimflam.Device.xml
@@ -38,6 +38,11 @@
 			<arg type="s" direction="in"/>
 			<arg type="s" direction="in"/>
 		</method>
+		<method name="PerformTDLSOperation">
+			<arg type="s" direction="in"/>
+			<arg type="s" direction="in"/>
+			<arg type="s" direction="out"/>
+		</method>
 		<method name="Reset"/>
 		<method name="ResetByteCounters"/>
 		<method name="SetCarrier">
diff --git a/device.cc b/device.cc
index 70e95cb..a93b99b 100644
--- a/device.cc
+++ b/device.cc
@@ -647,6 +647,12 @@
   return true;
 }
 
+string Device::PerformTDLSOperation(const string &/* operation */,
+                                    const string &/* peer */,
+                                    Error */* error */) {
+  return "";
+}
+
 void Device::ResetByteCounters() {
   manager_->device_info()->GetByteCounts(
       interface_index_, &receive_byte_offset_, &transmit_byte_offset_);
diff --git a/device.h b/device.h
index 95f8ff8..0c1995a 100644
--- a/device.h
+++ b/device.h
@@ -172,6 +172,16 @@
   virtual uint64 GetReceiveByteCount();
   virtual uint64 GetTransmitByteCount();
 
+  // Perform a TDLS |operation| on the underlying device, with respect
+  // to a given |peer|.  The string returned is empty for any operation
+  // other than kTDLSOperationStatus, which returns the state of the
+  // TDLS link with |peer|.  This method is only valid for WiFi devices,
+  // but needs to be declared here since it is part of the Device RPC
+  // API.
+  virtual std::string PerformTDLSOperation(const std::string &operation,
+                                           const std::string &peer,
+                                           Error *error);
+
   // Reset the persisted byte counters associated with the device.
   void ResetByteCounters();
 
diff --git a/device_dbus_adaptor.cc b/device_dbus_adaptor.cc
index f415c9d..49f22e4 100644
--- a/device_dbus_adaptor.cc
+++ b/device_dbus_adaptor.cc
@@ -179,6 +179,15 @@
   ReturnResultOrDefer(tag, e, &error);
 }
 
+string DeviceDBusAdaptor::PerformTDLSOperation(const string &operation,
+                                               const string &peer,
+                                               DBus::Error &error) {
+  Error e;
+  string return_value = device_->PerformTDLSOperation(operation, peer, &e);
+  e.ToDBusError(&error);
+  return return_value;
+}
+
 void DeviceDBusAdaptor::ResetByteCounters(DBus::Error &error) {
   device_->ResetByteCounters();
 }
diff --git a/device_dbus_adaptor.h b/device_dbus_adaptor.h
index 24be142..0663d0b 100644
--- a/device_dbus_adaptor.h
+++ b/device_dbus_adaptor.h
@@ -66,6 +66,9 @@
   virtual void ChangePin(const std::string &old_pin,
                          const std::string &new_pin,
                          ::DBus::Error &error);
+  virtual std::string PerformTDLSOperation(const std::string &operation,
+                                           const std::string &peer,
+                                           ::DBus::Error &error);
   virtual void Reset(::DBus::Error &error);
   virtual void ResetByteCounters(::DBus::Error &error);
   virtual void SetCarrier(const std::string &carrier, ::DBus::Error &error);
diff --git a/device_unittest.cc b/device_unittest.cc
index ef2a942..2d57f43 100644
--- a/device_unittest.cc
+++ b/device_unittest.cc
@@ -689,6 +689,11 @@
   EXPECT_FALSE(device_->ShouldUseMinimalDHCPConfig());
 }
 
+TEST_F(DeviceTest, PerformTDLSOperation) {
+  EXPECT_EQ("",
+            device_->PerformTDLSOperation("do something", "to someone", NULL));
+}
+
 class DevicePortalDetectionTest : public DeviceTest {
  public:
   DevicePortalDetectionTest()
diff --git a/doc/device-api.txt b/doc/device-api.txt
index 2c2bdcd..6370b18 100644
--- a/doc/device-api.txt
+++ b/doc/device-api.txt
@@ -169,6 +169,26 @@
 
 			Reset the device's persisted counters of transmitted
 
+		string PerformTDLSOperation(string operation,
+					    string peer) [readwrite]
+
+			(WiFi only) Perform a TDLS operation on a peer
+			station.  The |peer| argument should be a mac
+			address specified in traditional colon-separated
+			hexidecimal notation, e.g., "aa:bb:cc:dd:ee:ff".
+			algorithm.  The |operation| parameter should
+			be one of the following:
+
+				"Discover" : Perform TDLS discovery with |peer|.
+				"Setup" : Setup TDLS peering with |peer|.
+				"Status" : Return TDLS status for |peer|.
+				"Teardown" : Tear down TDLS peering with |peer|.
+
+			The method returns without an error if the operation
+			is initiated successfully with the supplicant, but
+			before it is clear whether the operation actually
+			succeeded.
+
 Signals		PropertyChanged(string name, variant value)
 
 			This signal indicates a changed value of the given
diff --git a/wifi.cc b/wifi.cc
index 4723290..7593cf8 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -2418,6 +2418,7 @@
   try {
     supplicant_interface_proxy_->TDLSDiscover(peer);
   } catch (const DBus::Error &e) {  // NOLINT
+    LOG(ERROR) << "exception while performing TDLS discover: " << e.what();
     return false;
   }
   return true;
@@ -2427,6 +2428,7 @@
   try {
     supplicant_interface_proxy_->TDLSSetup(peer);
   } catch (const DBus::Error &e) {  // NOLINT
+    LOG(ERROR) << "exception while performing TDLS setup: " << e.what();
     return false;
   }
   return true;
@@ -2436,6 +2438,7 @@
   try {
     return supplicant_interface_proxy_->TDLSStatus(peer);
   } catch (const DBus::Error &e) {  // NOLINT
+    LOG(ERROR) << "exception while getting TDLS status: " << e.what();
     return "";
   }
 }
@@ -2444,9 +2447,54 @@
   try {
     supplicant_interface_proxy_->TDLSTeardown(peer);
   } catch (const DBus::Error &e) {  // NOLINT
+    LOG(ERROR) << "exception while performing TDLS teardown: " << e.what();
     return false;
   }
   return true;
 }
 
+string WiFi::PerformTDLSOperation(const string &operation,
+                                  const string &peer,
+                                  Error *error) {
+  bool success = false;
+
+  SLOG(WiFi, 2) << "TDLS command received: " << operation
+                << " for peer " << peer;
+  if (operation == kTDLSDiscoverOperation) {
+    success = TDLSDiscover(peer);
+  } else if (operation == kTDLSSetupOperation) {
+    success = TDLSSetup(peer);
+  } else if (operation == kTDLSStatusOperation) {
+    string supplicant_status = TDLSStatus(peer);
+    SLOG(WiFi, 2) << "TDLS status returned: " << supplicant_status;
+    if (!supplicant_status.empty()) {
+      if (supplicant_status == WPASupplicant::kTDLSStateConnected) {
+        return kTDLSConnectedState;
+      } else if (supplicant_status == WPASupplicant::kTDLSStateDisabled) {
+        return kTDLSDisabledState;
+      } else if (supplicant_status ==
+                 WPASupplicant::kTDLSStatePeerDoesNotExist) {
+        return kTDLSNonexistentState;
+      } else if (supplicant_status ==
+                 WPASupplicant::kTDLSStatePeerNotConnected) {
+        return kTDLSDisconnectedState;
+      } else {
+        return kTDLSUnknownState;
+      }
+    }
+  } else if (operation == kTDLSTeardownOperation) {
+    success = TDLSTeardown(peer);
+  } else {
+    error->Populate(Error::kInvalidArguments, "Unknown operation");
+    return "";
+  }
+
+  if (!success) {
+    Error::PopulateAndLog(error, Error::kOperationFailed,
+                          "TDLS operation failed");
+  }
+
+  return "";
+}
+
 }  // namespace shill
diff --git a/wifi.h b/wifi.h
index 2f26271..8e844ec 100644
--- a/wifi.h
+++ b/wifi.h
@@ -212,6 +212,10 @@
   // initiated successfully.
   bool TDLSTeardown(const std::string &peer);
 
+  // Perform TDLS |operation| on |peer|.
+  virtual std::string PerformTDLSOperation(const std::string &operation,
+                                           const std::string &peer,
+                                           Error *error) override;
  private:
   enum ScanMethod {
     kScanMethodNone,
diff --git a/wifi_unittest.cc b/wifi_unittest.cc
index d728777..8851d53 100644
--- a/wifi_unittest.cc
+++ b/wifi_unittest.cc
@@ -843,6 +843,12 @@
     return wifi_->TDLSTeardown(peer);
   }
 
+  string PerformTDLSOperation(const string &operation,
+                              const string &peer,
+                              Error *error) {
+    return wifi_->PerformTDLSOperation(operation, peer, error);
+  }
+
   void TimeoutPendingConnection() {
     wifi_->PendingTimeoutHandler();
   }
@@ -3396,7 +3402,7 @@
   ExpectScanIdle();
 }
 
-TEST_F(WiFiMainTest, TDLS) {
+TEST_F(WiFiMainTest, TDLSInterfaceFunctions) {
   StartWiFi();
   const char kPeer[] = "peer";
 
@@ -3442,4 +3448,101 @@
   Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
 }
 
+TEST_F(WiFiMainTest, PerformTDLSOperation) {
+  StartWiFi();
+  const char kPeer[] = "peer";
+
+  {
+    Error error;
+    EXPECT_EQ("", PerformTDLSOperation("Do the thing", kPeer, &error));
+    EXPECT_EQ(Error::kInvalidArguments, error.type());
+  }
+
+  // This is the same test as TDLSInterfaceFunctions above, but using the
+  // method called by the RPC adapter.
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), TDLSDiscover(StrEq(kPeer)))
+      .WillOnce(Return())
+      .WillOnce(Throw(
+          DBus::Error(
+              "fi.w1.wpa_supplicant1.UnknownError",
+              "test threw fi.w1.wpa_supplicant1.UnknownError")));
+  {
+    Error error;
+    EXPECT_EQ("", PerformTDLSOperation(kTDLSDiscoverOperation, kPeer, &error));
+    EXPECT_TRUE(error.IsSuccess());
+  }
+  {
+    Error error;
+    EXPECT_EQ("", PerformTDLSOperation(kTDLSDiscoverOperation, kPeer, &error));
+    EXPECT_EQ(Error::kOperationFailed, error.type());
+  }
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), TDLSSetup(StrEq(kPeer)))
+      .WillOnce(Return())
+      .WillOnce(Throw(
+          DBus::Error(
+              "fi.w1.wpa_supplicant1.UnknownError",
+              "test threw fi.w1.wpa_supplicant1.UnknownError")));
+  {
+    Error error;
+    EXPECT_EQ("", PerformTDLSOperation(kTDLSSetupOperation, kPeer, &error));
+    EXPECT_TRUE(error.IsSuccess());
+  }
+  {
+    Error error;
+    EXPECT_EQ("", PerformTDLSOperation(kTDLSSetupOperation, kPeer, &error));
+    EXPECT_EQ(Error::kOperationFailed, error.type());
+  }
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+
+
+  const map<string, string> kTDLSStatusMap {
+    { "Baby, I don't care", kTDLSUnknownState },
+    { WPASupplicant::kTDLSStateConnected, kTDLSConnectedState },
+    { WPASupplicant::kTDLSStateDisabled, kTDLSDisabledState },
+    { WPASupplicant::kTDLSStatePeerDoesNotExist, kTDLSNonexistentState },
+    { WPASupplicant::kTDLSStatePeerNotConnected, kTDLSDisconnectedState },
+  };
+
+  for (const auto &it : kTDLSStatusMap) {
+    EXPECT_CALL(*GetSupplicantInterfaceProxy(), TDLSStatus(StrEq(kPeer)))
+        .WillOnce(Return(it.first));
+    Error error;
+    EXPECT_EQ(it.second,
+              PerformTDLSOperation(kTDLSStatusOperation, kPeer, &error));
+    EXPECT_TRUE(error.IsSuccess());
+    Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+  }
+
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), TDLSStatus(StrEq(kPeer)))
+      .WillOnce(Throw(
+          DBus::Error(
+              "fi.w1.wpa_supplicant1.UnknownError",
+              "test threw fi.w1.wpa_supplicant1.UnknownError")));
+  {
+    Error error;
+    EXPECT_EQ("", PerformTDLSOperation(kTDLSStatusOperation, kPeer, &error));
+    EXPECT_EQ(Error::kOperationFailed, error.type());
+  }
+  Mock::VerifyAndClearExpectations(GetSupplicantInterfaceProxy());
+
+  EXPECT_CALL(*GetSupplicantInterfaceProxy(), TDLSTeardown(StrEq(kPeer)))
+      .WillOnce(Return())
+      .WillOnce(Throw(
+          DBus::Error(
+              "fi.w1.wpa_supplicant1.UnknownError",
+              "test threw fi.w1.wpa_supplicant1.UnknownError")));
+  {
+    Error error;
+    EXPECT_EQ("", PerformTDLSOperation(kTDLSTeardownOperation, kPeer, &error));
+    EXPECT_TRUE(error.IsSuccess());
+  }
+  {
+    Error error;
+    EXPECT_EQ("", PerformTDLSOperation(kTDLSTeardownOperation, kPeer, &error));
+    EXPECT_EQ(Error::kOperationFailed, error.type());
+  }
+}
+
 }  // namespace shill
diff --git a/wpa_supplicant.cc b/wpa_supplicant.cc
index d7a4acc..91a8493 100644
--- a/wpa_supplicant.cc
+++ b/wpa_supplicant.cc
@@ -132,6 +132,11 @@
 const char WPASupplicant::kSecurityModeRSN[] = "RSN";
 const char WPASupplicant::kSecurityModeWPA[] = "WPA";
 
+const char WPASupplicant::kTDLSStateConnected[] = "connected";
+const char WPASupplicant::kTDLSStateDisabled[] = "disabled";
+const char WPASupplicant::kTDLSStatePeerDoesNotExist[] = "peer does not exist";
+const char WPASupplicant::kTDLSStatePeerNotConnected[] = "peer not connected";
+
 const uint32_t WPASupplicant::kDefaultEngine = 1;
 const uint32_t WPASupplicant::kNetworkIeee80211wDisabled = 0;
 const uint32_t WPASupplicant::kNetworkIeee80211wEnabled = 1;
diff --git a/wpa_supplicant.h b/wpa_supplicant.h
index 9736bc0..7736bd1 100644
--- a/wpa_supplicant.h
+++ b/wpa_supplicant.h
@@ -122,6 +122,10 @@
   static const char kSecurityMethodPropertyKeyManagement[];
   static const char kSecurityModeRSN[];
   static const char kSecurityModeWPA[];
+  static const char kTDLSStateConnected[];
+  static const char kTDLSStateDisabled[];
+  static const char kTDLSStatePeerDoesNotExist[];
+  static const char kTDLSStatePeerNotConnected[];
 
   static const uint32_t kDefaultEngine;
   static const uint32_t kNetworkIeee80211wDisabled;