buffet: Add property update signals to ExportedPropertSet

BUG=chromium:356368
TEST=unit tests pass, added more

Change-Id: I4c4beabce9bf6d3daf444066b2ce26cf13d50d10
Reviewed-on: https://chromium-review.googlesource.com/192725
Tested-by: Christopher Wiley <wiley@chromium.org>
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Christopher Wiley <wiley@chromium.org>
diff --git a/buffet/exported_property_set.cc b/buffet/exported_property_set.cc
index fe1ef65..caf0c31 100644
--- a/buffet/exported_property_set.cc
+++ b/buffet/exported_property_set.cc
@@ -138,7 +138,34 @@
     const std::string& interface,
     const std::string& name,
     const ExportedPropertyBase* property) {
-  // TODO(wiley): Send a signal from the exported object here.
+  dbus::Signal signal(dbus::kPropertiesInterface, dbus::kPropertiesChanged);
+  WriteSignalForPropertyUpdate(interface, name, property, &signal);
+  // This sends the signal asyncronously.  However, the raw message inside
+  // the signal object is ref-counted, so we're fine to allocate the Signal
+  // object on our local stack.
+  exported_object_->SendSignal(&signal);
+}
+
+void ExportedPropertySet::WriteSignalForPropertyUpdate(
+    const std::string& interface,
+    const std::string& name,
+    const ExportedPropertyBase* property,
+    dbus::Signal* signal) const {
+  dbus::MessageWriter writer(signal);
+  dbus::MessageWriter array_writer(nullptr);
+  dbus::MessageWriter dict_writer(nullptr);
+  writer.AppendString(interface);
+  writer.OpenArray("{sv}", &array_writer);
+  array_writer.OpenDictEntry(&dict_writer);
+  dict_writer.AppendString(name);
+  property->AppendValueToWriter(&dict_writer);
+  array_writer.CloseContainer(&dict_writer);
+  writer.CloseContainer(&array_writer);
+  // The interface specification tells us to include this list of properties
+  // which have changed, but for whom no value is conveyed.  Currently, we
+  // don't do anything interesting here.
+  std::vector<std::string> invalidated_properties;
+  writer.AppendArrayOfStrings(invalidated_properties);
 }
 
 template <typename T>
@@ -259,7 +286,8 @@
 }
 
 template <typename T>
-void ExportedProperty<T>::AppendValueToWriter(dbus::MessageWriter* writer) {
+void ExportedProperty<T>::AppendValueToWriter(
+    dbus::MessageWriter* writer) const {
   AppendPropertyToWriter(writer, value_);
 }
 
diff --git a/buffet/exported_property_set.h b/buffet/exported_property_set.h
index 7ed9d9b..84dbe2a 100644
--- a/buffet/exported_property_set.h
+++ b/buffet/exported_property_set.h
@@ -89,7 +89,7 @@
   // needed to write out properties to Get and GetAll methods implemented
   // by the ExportedPropertySet since it doesn't actually know the type
   // of each property.
-  virtual void AppendValueToWriter(dbus::MessageWriter* writer) = 0;
+  virtual void AppendValueToWriter(dbus::MessageWriter* writer) const = 0;
 };
 
 class ExportedPropertySet {
@@ -123,6 +123,11 @@
                                      const std::string& name,
                                      const ExportedPropertyBase* property);
 
+  void WriteSignalForPropertyUpdate(const std::string& interface,
+                                    const std::string& name,
+                                    const ExportedPropertyBase* property,
+                                    dbus::Signal* signal) const;
+
   dbus::ExportedObject* exported_object_;  // weak; owned by the Bus object.
   // This is a map from interface name -> property name -> pointer to property.
   std::map<std::string,
@@ -154,7 +159,7 @@
   virtual void SetUpdateCallback(const OnUpdateCallback& cb) override;
 
   // Implementation provided by specialization.
-  virtual void AppendValueToWriter(dbus::MessageWriter* writer) override;
+  virtual void AppendValueToWriter(dbus::MessageWriter* writer) const override;
 
  private:
   OnUpdateCallback on_update_;
diff --git a/buffet/exported_property_set_unittest.cc b/buffet/exported_property_set_unittest.cc
index 257614e..0bd3c3d 100644
--- a/buffet/exported_property_set_unittest.cc
+++ b/buffet/exported_property_set_unittest.cc
@@ -104,6 +104,13 @@
       HandleSet(method_call, response_sender);
     }
 
+    void CallWriteSignalForPropertyUpdate(const std::string& interface,
+                                          const std::string& name,
+                                          const ExportedPropertyBase* property,
+                                          dbus::Signal* signal) {
+      WriteSignalForPropertyUpdate(interface, name, property, signal);
+    }
+
     MOCK_METHOD3(PropertyUpdated, void(const std::string&, const std::string&,
                                        const ExportedPropertyBase*));
 
@@ -470,6 +477,35 @@
       dynamic_cast<dbus::ErrorResponse*>(last_response_.get()) != nullptr);
 }
 
+TEST_F(ExportedPropertySetTest, SignalsAreParsable) {
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface1, kUint8PropName,
+                                  &p_.uint8_prop_)).Times(1);
+  p_.uint8_prop_.SetValue(57);
+  dbus::Signal signal(dbus::kPropertiesInterface, dbus::kPropertiesChanged);
+  p_.CallWriteSignalForPropertyUpdate(kTestInterface1, kUint8PropName,
+                                      &p_.uint8_prop_, &signal);
+  std::string interface_name;
+  std::string property_name;
+  uint8 value;
+  dbus::MessageReader reader(&signal);
+  dbus::MessageReader array_reader(nullptr);
+  dbus::MessageReader dict_reader(nullptr);
+  ASSERT_TRUE(reader.PopString(&interface_name));
+  ASSERT_TRUE(reader.PopArray(&array_reader));
+  ASSERT_TRUE(array_reader.PopDictEntry(&dict_reader));
+  ASSERT_TRUE(dict_reader.PopString(&property_name));
+  ASSERT_TRUE(dict_reader.PopVariantOfByte(&value));
+  ASSERT_FALSE(dict_reader.HasMoreData());
+  ASSERT_FALSE(array_reader.HasMoreData());
+  // Read the (empty) list of invalidated property names.
+  std::vector<std::string> invalidated_properties;
+  ASSERT_TRUE(reader.PopArrayOfStrings(&invalidated_properties));
+  ASSERT_FALSE(reader.HasMoreData());
+  ASSERT_EQ(value, 57);
+  ASSERT_EQ(property_name, std::string(kUint8PropName));
+  ASSERT_EQ(interface_name, std::string(kTestInterface1));
+}
+
 }  // namespace dbus_utils
 
 }  // namespace buffet