shill: monitor bss signal strength

This wires WiFiEndpoint up to a proxy, to monitor
changes in the corresponding wpa_supplicant BSS
object.

The WiFiEndpoint object notifies its WiFi object
about the change, and WiFi relays the information
to the appropriate WiFiService.

BUG=chromium-os:16786
TEST=new unit tests, manual

Manual testing: ran on device (with --v=1000), observed
"signal is now" messages in log file.

Collateral changes:
- fix WiFiService leak in WiFi (reset |current_service| on Stop)
- suppress some "uninteresting call" messages in
  WiFiMainTest.CurrentBSSChangedUpdateServiceEndpoint

Change-Id: Ic329ecd28f73d93238d517a73105f2cd35cbff2e
Reviewed-on: https://gerrit.chromium.org/gerrit/15868
Commit-Ready: mukesh agrawal <quiche@chromium.org>
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Tested-by: mukesh agrawal <quiche@chromium.org>
diff --git a/Makefile b/Makefile
index 412f412..08fc7e5 100644
--- a/Makefile
+++ b/Makefile
@@ -163,6 +163,7 @@
 	shill_test_config.o \
 	shill_time.o \
 	sockets.o \
+	supplicant_bss_proxy.o \
 	supplicant_interface_proxy.o \
 	supplicant_process_proxy.o \
 	technology.o \
@@ -241,6 +242,7 @@
 	mock_service.o \
 	mock_sockets.o \
 	mock_store.o \
+	mock_supplicant_bss_proxy.o \
 	mock_supplicant_interface_proxy.o \
 	mock_supplicant_process_proxy.o \
 	mock_time.o \
diff --git a/mock_supplicant_bss_proxy.cc b/mock_supplicant_bss_proxy.cc
new file mode 100644
index 0000000..025dc6d
--- /dev/null
+++ b/mock_supplicant_bss_proxy.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2012 The Chromium OS 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 "shill/mock_supplicant_bss_proxy.h"
+
+namespace shill {
+
+MockSupplicantBSSProxy::MockSupplicantBSSProxy() {}
+
+MockSupplicantBSSProxy::~MockSupplicantBSSProxy() {
+  Die();
+}
+
+}  // namespace shill
diff --git a/mock_supplicant_bss_proxy.h b/mock_supplicant_bss_proxy.h
new file mode 100644
index 0000000..0de038b
--- /dev/null
+++ b/mock_supplicant_bss_proxy.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOCK_SUPPLICANT_BSS_PROXY_H_
+#define MOCK_SUPPLICANT_BSS_PROXY_H_
+
+#include <base/basictypes.h>
+#include <gmock/gmock.h>
+
+#include "shill/supplicant_bss_proxy_interface.h"
+
+namespace shill {
+
+class MockSupplicantBSSProxy : public SupplicantBSSProxyInterface {
+ public:
+  explicit MockSupplicantBSSProxy();
+  virtual ~MockSupplicantBSSProxy();
+
+  MOCK_METHOD0(Die, void());  // So we can EXPECT the dtor.
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockSupplicantBSSProxy);
+};
+
+}  // namespace shill
+
+#endif  // MOCK_SUPPLICANT_BSS_PROXY_H_
diff --git a/mock_wifi.h b/mock_wifi.h
index b91af46..78b1b9f 100644
--- a/mock_wifi.h
+++ b/mock_wifi.h
@@ -14,6 +14,7 @@
 #include "shill/key_value_store.h"
 #include "shill/refptr_types.h"
 #include "shill/wifi.h"
+#include "shill/wifi_endpoint.h"
 #include "shill/wifi_service.h"
 
 namespace shill {
@@ -43,6 +44,7 @@
                void(WiFiService *service,
                     std::map<std::string, ::DBus::Variant> service_params));
   MOCK_CONST_METHOD0(IsIdle, bool());
+  MOCK_METHOD1(NotifyEndpointChanged, void(const WiFiEndpoint &endpoint));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockWiFi);
diff --git a/proxy_factory.cc b/proxy_factory.cc
index 346df34..14c1117 100644
--- a/proxy_factory.cc
+++ b/proxy_factory.cc
@@ -15,6 +15,7 @@
 #include "shill/modem_proxy.h"
 #include "shill/modem_simple_proxy.h"
 #include "shill/power_manager_proxy.h"
+#include "shill/supplicant_bss_proxy.h"
 #include "shill/supplicant_interface_proxy.h"
 #include "shill/supplicant_process_proxy.h"
 
@@ -109,6 +110,14 @@
                                       dbus_addr);
 }
 
+SupplicantBSSProxyInterface *ProxyFactory::CreateSupplicantBSSProxy(
+    WiFiEndpoint *wifi_endpoint,
+    const DBus::Path &object_path,
+    const char *dbus_addr) {
+  return new SupplicantBSSProxy(
+      wifi_endpoint, connection(), object_path, dbus_addr);
+}
+
 DHCPProxyInterface *ProxyFactory::CreateDHCPProxy(const string &service) {
   return new DHCPCDProxy(connection(), service);
 }
diff --git a/proxy_factory.h b/proxy_factory.h
index 3976364..5b4b7ee 100644
--- a/proxy_factory.h
+++ b/proxy_factory.h
@@ -33,6 +33,7 @@
 class ModemSimpleProxyInterface;
 class PowerManagerProxyDelegate;
 class PowerManagerProxyInterface;
+class SupplicantBSSProxyInterface;
 class SupplicantInterfaceProxyInterface;
 class SupplicantProcessProxyInterface;
 
@@ -94,6 +95,12 @@
       const DBus::Path &object_path,
       const char *dbus_addr);
 
+  // See comment in supplicant_bss_proxy.h, about bare pointer.
+  virtual SupplicantBSSProxyInterface *CreateSupplicantBSSProxy(
+      WiFiEndpoint *wifi_endpoint,
+      const DBus::Path &object_path,
+      const char *dbus_addr);
+
   virtual DHCPProxyInterface *CreateDHCPProxy(const std::string &service);
 
   DBus::Connection *connection() const { return connection_.get(); }
diff --git a/supplicant_bss_proxy.cc b/supplicant_bss_proxy.cc
new file mode 100644
index 0000000..dc12b45
--- /dev/null
+++ b/supplicant_bss_proxy.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 The Chromium OS 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 "shill/supplicant_bss_proxy.h"
+
+#include <map>
+#include <string>
+
+#include <dbus-c++/dbus.h>
+
+#include "shill/wifi_endpoint.h"
+
+using std::map;
+using std::string;
+
+namespace shill {
+
+SupplicantBSSProxy::SupplicantBSSProxy(
+    WiFiEndpoint *wifi_endpoint,
+    DBus::Connection *bus,
+    const ::DBus::Path &object_path,
+    const char *dbus_addr)
+    : proxy_(wifi_endpoint, bus, object_path, dbus_addr) {}
+
+SupplicantBSSProxy::~SupplicantBSSProxy() {}
+
+// definitions for private class SupplicantBSSProxy::Proxy
+
+SupplicantBSSProxy::Proxy::Proxy(
+    WiFiEndpoint *wifi_endpoint, DBus::Connection *bus,
+    const DBus::Path &dbus_path, const char *dbus_addr)
+    : DBus::ObjectProxy(*bus, dbus_path, dbus_addr),
+      wifi_endpoint_(wifi_endpoint) {}
+
+SupplicantBSSProxy::Proxy::~Proxy() {}
+
+void SupplicantBSSProxy::Proxy::PropertiesChanged(
+    const std::map<string, ::DBus::Variant> &properties) {
+  wifi_endpoint_->PropertiesChanged(properties);
+}
+
+}  // namespace shill
diff --git a/supplicant_bss_proxy.h b/supplicant_bss_proxy.h
new file mode 100644
index 0000000..4d80403
--- /dev/null
+++ b/supplicant_bss_proxy.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SUPPLICANT_BSS_PROXY_H_
+#define SUPPLICANT_BSS_PROXY_H_
+
+#include <map>
+#include <string>
+
+#include <base/basictypes.h>
+#include <dbus-c++/dbus.h>
+
+#include "shill/dbus_bindings/supplicant-bss.h"
+#include "shill/supplicant_bss_proxy_interface.h"
+
+namespace shill {
+
+class WiFiEndpoint;
+
+class SupplicantBSSProxy : public SupplicantBSSProxyInterface {
+ public:
+  SupplicantBSSProxy(WiFiEndpoint *wifi_endpoint,
+                     DBus::Connection *bus,
+                     const ::DBus::Path &object_path,
+                     const char *dbus_addr);
+  virtual ~SupplicantBSSProxy();
+
+ private:
+  class Proxy : public fi::w1::wpa_supplicant1::BSS_proxy,
+    public ::DBus::ObjectProxy {
+   public:
+    Proxy(WiFiEndpoint *wifi_endpoint,
+          DBus::Connection *bus,
+          const ::DBus::Path &object_path,
+          const char *dbus_addr);
+    virtual ~Proxy();
+
+   private:
+    // signal handlers called by dbus-c++, via
+    // wpa_supplicant1_proxy interface.
+    virtual void PropertiesChanged(
+        const std::map<std::string, ::DBus::Variant> &properties);
+
+    // We use a bare pointer, because each SupplicantBSSProxy is owned
+    // (using a scoped_ptr) by a WiFiEndpoint. This means that if
+    // |wifi_endpoint_| is invalid, then so is |this|.
+    WiFiEndpoint *wifi_endpoint_;
+    DISALLOW_COPY_AND_ASSIGN(Proxy);
+  };
+
+  Proxy proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(SupplicantBSSProxy);
+};
+
+}  // namespace shill
+
+#endif   // SUPPLICANT_BSS_PROXY_H_
diff --git a/supplicant_bss_proxy_interface.h b/supplicant_bss_proxy_interface.h
new file mode 100644
index 0000000..7523867
--- /dev/null
+++ b/supplicant_bss_proxy_interface.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SUPPLICANT_BSS_PROXY_INTERFACE_H_
+#define SUPPLICANT_BSS_PROXY_INTERFACE_H_
+
+namespace shill {
+
+// SupplicantBSSProxyInterface declares only the subset of
+// fi::w1::wpa_supplicant1::BSS_proxy that is actually used by WiFi.
+class SupplicantBSSProxyInterface {
+ public:
+  virtual ~SupplicantBSSProxyInterface() {}
+};
+
+}  // namespace shill
+
+#endif   // SUPPLICANT_BSS_PROXY_INTERFACE_H_
diff --git a/wifi.cc b/wifi.cc
index f7694a5..68dd841 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -186,9 +186,12 @@
   for (vector<WiFiServiceRefPtr>::const_iterator it = services_.begin();
        it != services_.end();
        ++it) {
+    VLOG(3) << "WiFi " << link_name() << " deregistering service "
+            << (*it)->friendly_name();
     manager()->DeregisterService(*it);
   }
   services_.clear();                  // breaks reference cycles
+  current_service_ = NULL;            // breaks a reference cycle
   pending_service_ = NULL;            // breaks a reference cycle
 
   Device::Stop();
@@ -391,6 +394,15 @@
   }
 }
 
+void WiFi::NotifyEndpointChanged(const WiFiEndpoint &endpoint) {
+  WiFiService *service = FindServiceForEndpoint(endpoint);
+  DCHECK(service);
+  if (service) {
+    service->NotifyEndpointUpdated(endpoint);
+    return;
+  }
+}
+
 string WiFi::CreateBgscanConfigString() {
   return StringPrintf("%s:%d:%d:%d",
                       bgscan_method_.c_str(),
@@ -772,15 +784,11 @@
 void WiFi::BSSAddedTask(
     const ::DBus::Path &path,
     const map<string, ::DBus::Variant> &properties) {
-  // TODO(quiche): Write test to verify correct behavior in the case
-  // where we get multiple BSSAdded events for a single endpoint.
-  // (Old Endpoint's refcount should fall to zero, and old Endpoint
-  // should be destroyed.)
-  //
   // Note: we assume that BSSIDs are unique across endpoints. This
   // means that if an AP reuses the same BSSID for multiple SSIDs, we
   // lose.
-  WiFiEndpointRefPtr endpoint(new WiFiEndpoint(properties));
+  WiFiEndpointRefPtr endpoint(
+      new WiFiEndpoint(proxy_factory_, this, path, properties));
   LOG(INFO) << "Found endpoint. "
             << "RPC path: " << path << ", "
             << "ssid: " << endpoint->ssid_string() << ", "
@@ -824,7 +832,13 @@
 
   // Do this last, to maintain the invariant that any Endpoint we
   // know about has a corresponding Service.
+  //
+  // TODO(quiche): Write test to verify correct behavior in the case
+  // where we get multiple BSSAdded events for a single endpoint.
+  // (Old Endpoint's refcount should fall to zero, and old Endpoint
+  // should be destroyed.)
   endpoint_by_rpcid_[path] = endpoint;
+  endpoint->Start();
 }
 
 void WiFi::BSSRemovedTask(const ::DBus::Path &path) {
diff --git a/wifi.h b/wifi.h
index aab6439..80c91ac 100644
--- a/wifi.h
+++ b/wifi.h
@@ -61,6 +61,9 @@
   virtual bool IsIdle() const;
   virtual void ClearCachedCredentials();
 
+  // Called by WiFiEndpoint.
+  virtual void NotifyEndpointChanged(const WiFiEndpoint &endpoint);
+
   // Called by Manager.
   virtual WiFiServiceRefPtr GetService(const KeyValueStore &args, Error *error);
 
diff --git a/wifi_endpoint.cc b/wifi_endpoint.cc
index a36ad45..e78d6cd 100644
--- a/wifi_endpoint.cc
+++ b/wifi_endpoint.cc
@@ -12,7 +12,10 @@
 #include <chromeos/dbus/service_constants.h>
 
 #include "shill/ieee80211.h"
+#include "shill/proxy_factory.h"
+#include "shill/supplicant_bss_proxy_interface.h"
 #include "shill/wifi.h"
+#include "shill/wifi_endpoint.h"
 #include "shill/wpa_supplicant.h"
 
 using std::map;
@@ -22,9 +25,14 @@
 
 namespace shill {
 
-WiFiEndpoint::WiFiEndpoint(
-    const map<string, ::DBus::Variant> &properties)
-    : frequency_(0) {
+WiFiEndpoint::WiFiEndpoint(ProxyFactory *proxy_factory,
+                           const WiFiRefPtr &device,
+                           const string &rpc_id,
+                           const map<string, ::DBus::Variant> &properties)
+    : frequency_(0),
+      proxy_factory_(proxy_factory),
+      device_(device),
+      rpc_id_(rpc_id) {
   // XXX will segfault on missing properties
   ssid_ =
       properties.find(wpa_supplicant::kBSSPropertySSID)->second.
@@ -59,6 +67,25 @@
 
 WiFiEndpoint::~WiFiEndpoint() {}
 
+void WiFiEndpoint::Start() {
+  supplicant_bss_proxy_.reset(
+      proxy_factory_->CreateSupplicantBSSProxy(
+          this, rpc_id_, wpa_supplicant::kDBusAddr));
+}
+
+void WiFiEndpoint::PropertiesChanged(
+    const map<string, ::DBus::Variant> &properties) {
+  LOG(INFO) << __func__;
+  map<string, ::DBus::Variant>::const_iterator properties_it =
+      properties.find(wpa_supplicant::kBSSPropertySignal);
+  if (properties_it != properties.end()) {
+    signal_strength_ = properties_it->second.reader().get_int16();
+    VLOG(2) << "WiFiEndpoint " << bssid_string_ << " signal is now "
+            << signal_strength_;
+    device_->NotifyEndpointChanged(*this);
+  }
+}
+
 // static
 uint32_t WiFiEndpoint::ModeStringToUint(const std::string &mode_string) {
   if (mode_string == flimflam::kModeManaged)
@@ -112,8 +139,10 @@
 }
 
 // static
-WiFiEndpoint *WiFiEndpoint::MakeOpenEndpoint(
-    const string &ssid, const string &bssid) {
+WiFiEndpoint *WiFiEndpoint::MakeOpenEndpoint(ProxyFactory *proxy_factory,
+                                             const WiFiRefPtr &wifi,
+                                             const string &ssid,
+                                             const string &bssid) {
   map <string, ::DBus::Variant> args;
   ::DBus::MessageIter writer;
 
@@ -132,7 +161,8 @@
       wpa_supplicant::kNetworkModeInfrastructure);
   // We indicate this is an open BSS by leaving out all security properties.
 
-  return new WiFiEndpoint(args);
+  return new WiFiEndpoint(
+      proxy_factory, wifi, bssid, args); // |bssid| fakes an RPC ID
 }
 
 // static
diff --git a/wifi_endpoint.h b/wifi_endpoint.h
index 4de7a15..902eb90 100644
--- a/wifi_endpoint.h
+++ b/wifi_endpoint.h
@@ -17,14 +17,31 @@
 #include "shill/endpoint.h"
 #include "shill/event_dispatcher.h"
 #include "shill/metrics.h"
+#include "shill/refptr_types.h"
 
 namespace shill {
 
+class ProxyFactory;
+class SupplicantBSSProxyInterface;
+
 class WiFiEndpoint : public Endpoint {
  public:
-  WiFiEndpoint(const std::map<std::string, ::DBus::Variant> &properties);
+  WiFiEndpoint(ProxyFactory *proxy_factory,
+               const WiFiRefPtr &device,
+               const std::string &rpc_id,
+               const std::map<std::string, ::DBus::Variant> &properties);
   virtual ~WiFiEndpoint();
 
+  // Set up RPC channel. Broken out from the ctor, so that WiFi can
+  // look over the Endpoint details before commiting to setting up
+  // RPC.
+  virtual void Start();
+
+  // Called by SupplicantBSSProxy, in response to events from
+  // wpa_supplicant.
+  void PropertiesChanged(
+      const std::map<std::string, ::DBus::Variant> &properties);
+
   // Maps mode strings from flimflam's nomenclature, as defined
   // in chromeos/dbus/service_constants.h, to uints used by supplicant
   static uint32_t ModeStringToUint(const std::string &mode_string);
@@ -44,7 +61,6 @@
   friend class WiFiEndpointTest;
   friend class WiFiMainTest;  // for MakeOpenEndpoint
   friend class WiFiServiceTest;  // for MakeOpenEndpoint
-  FRIEND_TEST(WiFiEndpointTest, SSIDWithNull);  // for MakeOpenEndpoint
   // these test cases need access to the KeyManagement enum
   FRIEND_TEST(WiFiEndpointTest, ParseKeyManagementMethodsEAP);
   FRIEND_TEST(WiFiEndpointTest, ParseKeyManagementMethodsPSK);
@@ -57,8 +73,10 @@
   };
 
   // Build a simple WiFiEndpoint, for testing purposes.
-  static WiFiEndpoint *MakeOpenEndpoint(
-      const std::string &ssid, const std::string &bssid);
+  static WiFiEndpoint *MakeOpenEndpoint(ProxyFactory *proxy_factory,
+                                        const WiFiRefPtr &wifi,
+                                        const std::string &ssid,
+                                        const std::string &bssid);
   // Maps mode strings from supplicant into flimflam's nomenclature, as defined
   // in chromeos/dbus/service_constants.h
   static const char *ParseMode(const std::string &mode_string);
@@ -66,7 +84,7 @@
   // security property value, as defined in chromeos/dbus/service_constants.h
   static const char *ParseSecurity(
       const std::map<std::string, ::DBus::Variant> &properties);
-  // Parses and Endpoint's properties' "RSN" or "WPA" sub-dictionary, to
+  // Parses an Endpoint's properties' "RSN" or "WPA" sub-dictionary, to
   // identify supported key management methods (802.1x or PSK).
   static void ParseKeyManagementMethods(
       const std::map<std::string, ::DBus::Variant> &security_method_properties,
@@ -96,6 +114,11 @@
   std::string network_mode_;
   std::string security_mode_;
 
+  ProxyFactory *proxy_factory_;
+  WiFiRefPtr device_;
+  std::string rpc_id_;
+  scoped_ptr<SupplicantBSSProxyInterface> supplicant_bss_proxy_;
+
   DISALLOW_COPY_AND_ASSIGN(WiFiEndpoint);
 };
 
diff --git a/wifi_endpoint_unittest.cc b/wifi_endpoint_unittest.cc
index 1e682c7..556b966 100644
--- a/wifi_endpoint_unittest.cc
+++ b/wifi_endpoint_unittest.cc
@@ -11,9 +11,12 @@
 
 #include <base/stl_util-inl.h>
 #include <chromeos/dbus/service_constants.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include "shill/ieee80211.h"
+#include "shill/mock_wifi.h"
+#include "shill/property_store_unittest.h"
 #include "shill/refptr_types.h"
 #include "shill/wpa_supplicant.h"
 
@@ -21,14 +24,22 @@
 using std::set;
 using std::string;
 using std::vector;
-
-using ::testing::Test;
+using ::testing::_;
+using ::testing::NiceMock;
 
 namespace shill {
 
-class WiFiEndpointTest : public Test {
+class WiFiEndpointTest : public PropertyStoreTest {
  public:
-  WiFiEndpointTest() {}
+  WiFiEndpointTest() : wifi_(
+      new NiceMock<MockWiFi>(
+          control_interface(),
+          dispatcher(),
+          metrics(),
+          manager(),
+          "wifi",
+          "aabbccddeeff",  // fake mac
+          0)) {}
   virtual ~WiFiEndpointTest() {}
 
  protected:
@@ -77,6 +88,18 @@
     ies->insert(ies->end(), 3, 0);  // OUI
     ies->push_back(0);              // data
   }
+
+  WiFiEndpoint *MakeOpenEndpoint(ProxyFactory *proxy_factory,
+                                 const WiFiRefPtr &wifi,
+                                 const std::string &ssid,
+                                 const std::string &bssid) {
+    return WiFiEndpoint::MakeOpenEndpoint(proxy_factory, wifi, ssid, bssid);
+  }
+
+  scoped_refptr<MockWiFi> wifi() { return wifi_; }
+
+ private:
+  scoped_refptr<MockWiFi> wifi_;
 };
 
 TEST_F(WiFiEndpointTest, ParseKeyManagementMethodsEAP) {
@@ -146,7 +169,7 @@
 
 TEST_F(WiFiEndpointTest, SSIDWithNull) {
   WiFiEndpointRefPtr endpoint =
-      WiFiEndpoint::MakeOpenEndpoint(string(1, 0), "00:00:00:00:00:01");
+      MakeOpenEndpoint(NULL, NULL, string(1, 0), "00:00:00:00:00:01");
   EXPECT_EQ("?", endpoint->ssid_string());
 }
 
@@ -217,4 +240,21 @@
   }
 }
 
+TEST_F(WiFiEndpointTest, PropertiesChanged) {
+  WiFiEndpointRefPtr endpoint =
+      MakeOpenEndpoint(NULL, wifi(), "ssid", "00:00:00:00:00:01");
+  map<string, ::DBus::Variant> changed_properties;
+  ::DBus::MessageIter writer;
+  int16_t signal_strength = 10;
+
+  EXPECT_NE(signal_strength, endpoint->signal_strength());
+  writer =
+      changed_properties[wpa_supplicant::kBSSPropertySignal].writer();
+  writer << signal_strength;
+
+  EXPECT_CALL(*wifi(), NotifyEndpointChanged(_));
+  endpoint->PropertiesChanged(changed_properties);
+  EXPECT_EQ(signal_strength, endpoint->signal_strength());
+}
+
 }  // namespace shill
diff --git a/wifi_service.cc b/wifi_service.cc
index 0567278..42c1329 100644
--- a/wifi_service.cc
+++ b/wifi_service.cc
@@ -195,6 +195,13 @@
   // (crosbug.com/16786)
 }
 
+void WiFiService::NotifyEndpointUpdated(const WiFiEndpoint &endpoint) {
+  DCHECK(endpoints_.find(&endpoint) != endpoints_.end());
+  // TODO(quiche): If this is the connected endpoint, or the "representative"
+  // endpoint (when the service is disconnected), then update signal and
+  // frequency. crosbug.com/16786
+}
+
 string WiFiService::GetStorageIdentifier() const {
   return storage_identifier_;
 }
diff --git a/wifi_service.h b/wifi_service.h
index 01a3fc8..f57df7c 100644
--- a/wifi_service.h
+++ b/wifi_service.h
@@ -54,7 +54,11 @@
   virtual void AddEndpoint(WiFiEndpointConstRefPtr endpoint);
   virtual void RemoveEndpoint(WiFiEndpointConstRefPtr endpoint);
   bool NumEndpoints() const { return endpoints_.size(); }
+  // Called to update the identity of the currently connected endpoint.
   void NotifyCurrentEndpoint(const WiFiEndpoint &endpoint);
+  // Called to inform of changes in the properties of an endpoint.
+  // (Not necessarily the currently connected endpoint.)
+  void NotifyEndpointUpdated(const WiFiEndpoint &endpoint);
 
   // wifi_<MAC>_<BSSID>_<mode_string>_<security_string>
   std::string GetStorageIdentifier() const;
diff --git a/wifi_service_unittest.cc b/wifi_service_unittest.cc
index 5d1f433..e167109 100644
--- a/wifi_service_unittest.cc
+++ b/wifi_service_unittest.cc
@@ -28,8 +28,6 @@
 using std::map;
 using std::string;
 using std::vector;
-
-namespace shill {
 using ::testing::_;
 using ::testing::DoAll;
 using ::testing::NiceMock;
@@ -38,6 +36,8 @@
 using ::testing::StrEq;
 using ::testing::StrNe;
 
+namespace shill {
+
 class WiFiServiceTest : public PropertyStoreTest {
  public:
   WiFiServiceTest() : wifi_(
@@ -74,7 +74,7 @@
     return service->connectable();
   }
   WiFiEndpoint *MakeEndpoint(const string &ssid, const string &bssid) {
-    return WiFiEndpoint::MakeOpenEndpoint(ssid, bssid);
+    return WiFiEndpoint::MakeOpenEndpoint(NULL, NULL, ssid, bssid);
   }
   scoped_refptr<MockWiFi> wifi() { return wifi_; }
 
diff --git a/wifi_unittest.cc b/wifi_unittest.cc
index d3bcfde..d7366ef 100644
--- a/wifi_unittest.cc
+++ b/wifi_unittest.cc
@@ -37,6 +37,7 @@
 #include "shill/mock_metrics.h"
 #include "shill/mock_rtnl_handler.h"
 #include "shill/mock_store.h"
+#include "shill/mock_supplicant_bss_proxy.h"
 #include "shill/mock_supplicant_interface_proxy.h"
 #include "shill/mock_supplicant_process_proxy.h"
 #include "shill/mock_wifi_service.h"
@@ -57,6 +58,7 @@
 using ::testing::DefaultValue;
 using ::testing::DoAll;
 using ::testing::InSequence;
+using ::testing::Invoke;
 using ::testing::Mock;
 using ::testing::NiceMock;
 using ::testing::Return;
@@ -166,6 +168,8 @@
         supplicant_process_proxy_(new NiceMock<MockSupplicantProcessProxy>()),
         supplicant_interface_proxy_(
             new NiceMock<MockSupplicantInterfaceProxy>(wifi_)),
+        supplicant_bss_proxy_(
+            new NiceMock<MockSupplicantBSSProxy>()),
         dhcp_config_(new MockDHCPConfig(&control_interface_,
                                         &dispatcher_,
                                         &dhcp_provider_,
@@ -194,10 +198,14 @@
     ON_CALL(manager_, device_info()).
         WillByDefault(Return(&device_info_));
     EXPECT_CALL(manager_, DeregisterService(_)).Times(AnyNumber());
+    EXPECT_CALL(*supplicant_bss_proxy_, Die()).Times(AnyNumber());
   }
 
   virtual void TearDown() {
     EXPECT_CALL(*manager(), UpdateService(_)).Times(AnyNumber());
+    if (supplicant_bss_proxy_.get()) {
+      EXPECT_CALL(*supplicant_bss_proxy_, Die());
+    }
     wifi_->proxy_factory_ = NULL;
     // must Stop WiFi instance, to clear its list of services.
     // otherwise, the WiFi instance will not be deleted. (because
@@ -211,7 +219,7 @@
 
   class TestProxyFactory : public ProxyFactory {
    public:
-    explicit TestProxyFactory(WiFiMainTest *test) : test_(test) {}
+    explicit TestProxyFactory(WiFiMainTest *test);
 
     virtual SupplicantProcessProxyInterface *CreateSupplicantProcessProxy(
         const char */*dbus_path*/, const char */*dbus_addr*/) {
@@ -225,7 +233,20 @@
       return test_->supplicant_interface_proxy_.release();
     }
 
+    MOCK_METHOD3(CreateSupplicantBSSProxy,
+                 SupplicantBSSProxyInterface *(
+                     WiFiEndpoint *wifi_endpoint,
+                     const DBus::Path &object_path,
+                     const char *dbus_addr));
+
    private:
+    SupplicantBSSProxyInterface *CreateSupplicantBSSProxyInternal(
+        WiFiEndpoint */*wifi_endpoint*/,
+        const DBus::Path &/*object_path*/,
+        const char */*dbus_addr*/) {
+      return test_->supplicant_bss_proxy_.release();
+    }
+
     WiFiMainTest *test_;
   };
 
@@ -266,7 +287,7 @@
     wifi_->DisconnectFrom(service);
   }
   WiFiEndpointRefPtr MakeEndpoint(const string &ssid, const string &bssid) {
-    return WiFiEndpoint::MakeOpenEndpoint(ssid, bssid);
+    return WiFiEndpoint::MakeOpenEndpoint(&proxy_factory_, NULL, ssid, bssid);
   }
   MockWiFiServiceRefPtr MakeMockService() {
     vector<uint8_t> ssid(1, 'a');
@@ -397,6 +418,10 @@
     return wifi_;
   }
 
+  TestProxyFactory *proxy_factory() {
+    return &proxy_factory_;
+  }
+
   EventDispatcher dispatcher_;
   NiceMock<MockRTNLHandler> rtnl_handler_;
 
@@ -419,6 +444,7 @@
 
   scoped_ptr<MockSupplicantProcessProxy> supplicant_process_proxy_;
   scoped_ptr<MockSupplicantInterfaceProxy> supplicant_interface_proxy_;
+  scoped_ptr<MockSupplicantBSSProxy> supplicant_bss_proxy_;
   MockDHCPProvider dhcp_provider_;
   scoped_refptr<MockDHCPConfig> dhcp_config_;
 
@@ -465,6 +491,14 @@
   wifi_->BSSAddedTask(bss_path, bss_properties);
 }
 
+WiFiMainTest::TestProxyFactory::TestProxyFactory(WiFiMainTest *test)
+    : test_(test) {
+  EXPECT_CALL(*this, CreateSupplicantBSSProxy(_, _, _)).Times(AnyNumber());
+  ON_CALL(*this, CreateSupplicantBSSProxy(_, _, _))
+      .WillByDefault(
+          Invoke(this, (&TestProxyFactory::CreateSupplicantBSSProxyInternal)));
+}
+
 TEST_F(WiFiMainTest, ProxiesSetUpDuringStart) {
   EXPECT_TRUE(GetSupplicantProcessProxy() == NULL);
   EXPECT_TRUE(GetSupplicantInterfaceProxy() == NULL);
@@ -1400,6 +1434,10 @@
 }
 
 TEST_F(WiFiMainTest, CurrentBSSChangedUpdateServiceEndpoint) {
+  EXPECT_CALL(*manager(), RegisterService(_)).Times(AnyNumber());
+  EXPECT_CALL(*manager(), HasService(_)).Times(AnyNumber());
+  EXPECT_CALL(*manager(), UpdateService(_)).Times(AnyNumber());
+
   const uint16 frequency1 = 2412;
   const uint16 frequency2 = 2442;
   StartWiFi();
@@ -1567,4 +1605,31 @@
   dispatcher_.DispatchPendingEvents();
 }
 
+TEST_F(WiFiMainTest, BSSAddedCreatesBSSProxy) {
+  EXPECT_CALL(*manager(), RegisterService(_)).Times(AnyNumber());
+  // TODO(quiche): Consider using a factory for WiFiEndpoints, so that
+  // we can test the interaction between WiFi and WiFiEndpoint. (Right
+  // now, we're testing across multiple layers.)
+  EXPECT_CALL(*supplicant_bss_proxy_, Die()).Times(AnyNumber());
+  EXPECT_CALL(*proxy_factory(), CreateSupplicantBSSProxy(_, _, _));
+  StartWiFi();
+  ReportBSS("bss0", "ssid0", "00:00:00:00:00:00", 0, 0, kNetworkModeAdHoc);
+}
+
+TEST_F(WiFiMainTest, BSSRemovedDestroysBSSProxy) {
+  // TODO(quiche): As for BSSAddedCreatesBSSProxy, consider using a
+  // factory for WiFiEndpoints.
+  EXPECT_CALL(*manager(), RegisterService(_)).Times(AnyNumber());
+
+  // Get the pointer before we transfer ownership.
+  MockSupplicantBSSProxy *proxy = supplicant_bss_proxy_.get();
+  EXPECT_CALL(*proxy, Die());
+  StartWiFi();
+  ReportBSS("bss0", "ssid0", "00:00:00:00:00:00", 0, 0, kNetworkModeAdHoc);
+  RemoveBSS("bss0");
+  // Check this now, to make sure RemoveBSS killed the proxy (rather
+  // than TearDown).
+  Mock::VerifyAndClearExpectations(proxy);
+}
+
 }  // namespace shill