buffet: Add ExportedPropertySet delegate

This object makes it easy to export an org.freedesktop.DBus.Properties
interface.

BUG=chromium:356368
TEST=When integrated with the Manager, this correctly exposes
properties.  This can be tested with buffet_BasicDBusAPI.

Change-Id: I6c871ebbd225b6305ca9d4a309fb7b47ed305f9b
Reviewed-on: https://chromium-review.googlesource.com/192001
Tested-by: Christopher Wiley <wiley@chromium.org>
Reviewed-by: Chris Sosa <sosa@chromium.org>
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Christopher Wiley <wiley@chromium.org>
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp
index d396525..f44d8dc 100644
--- a/buffet/buffet.gyp
+++ b/buffet/buffet.gyp
@@ -34,9 +34,10 @@
       'type': 'static_library',
       'sources': [
         'data_encoding.cc',
-        'dbus_manager.cc',
         'dbus_constants.cc',
+        'dbus_manager.cc',
         'dbus_utils.cc',
+        'exported_property_set.cc',
         'http_request.cc',
         'http_transport_curl.cc',
         'http_utils.cc',
@@ -73,6 +74,7 @@
       'sources': [
         'buffet_testrunner.cc',
         'data_encoding_unittest.cc',
+        'exported_property_set_unittest.cc',
         'mime_utils_unittest.cc',
         'string_utils_unittest.cc',
       ],
diff --git a/buffet/exported_property_set.cc b/buffet/exported_property_set.cc
new file mode 100644
index 0000000..fe1ef65
--- /dev/null
+++ b/buffet/exported_property_set.cc
@@ -0,0 +1,283 @@
+// Copyright 2014 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 "buffet/exported_property_set.h"
+
+#include <base/bind.h>
+#include <dbus/property.h>  // For kPropertyInterface
+
+#include "buffet/dbus_utils.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+ExportedPropertySet::ExportedPropertySet(dbus::ExportedObject* exported_object)
+    : exported_object_(exported_object), weak_ptr_factory_(this) { }
+
+void ExportedPropertySet::ClaimPropertiesInterface() {
+  exported_object_->ExportMethodAndBlock(
+      dbus::kPropertiesInterface, dbus::kPropertiesGetAll,
+      base::Bind(&ExportedPropertySet::HandleGetAll,
+                 weak_ptr_factory_.GetWeakPtr()));
+  exported_object_->ExportMethodAndBlock(
+      dbus::kPropertiesInterface, dbus::kPropertiesGet,
+      base::Bind(&ExportedPropertySet::HandleGet,
+                 weak_ptr_factory_.GetWeakPtr()));
+  exported_object_->ExportMethodAndBlock(
+      dbus::kPropertiesInterface, dbus::kPropertiesSet,
+      base::Bind(&ExportedPropertySet::HandleSet,
+                 weak_ptr_factory_.GetWeakPtr()));
+}
+
+ExportedPropertySet::~ExportedPropertySet() { }
+
+void ExportedPropertySet::RegisterProperty(
+    const std::string& interface_name,
+    const std::string& property_name,
+    ExportedPropertyBase* exported_property) {
+  properties_[interface_name][property_name] = exported_property;
+  // Technically, the property set exists longer than the properties themselves,
+  // so we could use Unretained here rather than a weak pointer.
+  ExportedPropertyBase::OnUpdateCallback cb = base::Bind(
+      &ExportedPropertySet::HandlePropertyUpdated,
+      weak_ptr_factory_.GetWeakPtr(),
+      interface_name, property_name);
+  exported_property->SetUpdateCallback(cb);
+}
+
+void ExportedPropertySet::HandleGetAll(
+    dbus::MethodCall* method_call,
+    dbus::ExportedObject::ResponseSender response_sender) {
+  dbus::MessageReader reader(method_call);
+  std::string interface_name;
+  if (!reader.PopString(&interface_name)) {
+    response_sender.Run(
+        GetBadArgsError(method_call, "No interface name specified."));
+    return;
+  }
+  if (reader.HasMoreData()) {
+    response_sender.Run(
+        GetBadArgsError(method_call, "Too many arguments to GetAll."));
+    return;
+  }
+  auto property_map_itr = properties_.find(interface_name);
+  if (property_map_itr == properties_.end()) {
+    response_sender.Run(
+        GetBadArgsError(method_call, "No such interface on object."));
+    return;
+  }
+  scoped_ptr<dbus::Response> response(
+      dbus::Response::FromMethodCall(method_call));
+  dbus::MessageWriter resp_writer(response.get());
+  dbus::MessageWriter dict_writer(nullptr);
+  resp_writer.OpenArray("{sv}", &dict_writer);
+  for (const auto& kv : property_map_itr->second) {
+    dbus::MessageWriter entry_writer(nullptr);
+    dict_writer.OpenDictEntry(&entry_writer);
+    entry_writer.AppendString(kv.first);
+    kv.second->AppendValueToWriter(&entry_writer);
+    dict_writer.CloseContainer(&entry_writer);
+  }
+  resp_writer.CloseContainer(&dict_writer);
+  response_sender.Run(response.Pass());
+}
+
+void ExportedPropertySet::HandleGet(
+    dbus::MethodCall* method_call,
+    dbus::ExportedObject::ResponseSender response_sender) {
+  dbus::MessageReader reader(method_call);
+  std::string interface_name, property_name;
+  if (!reader.PopString(&interface_name)) {
+    response_sender.Run(
+        GetBadArgsError(method_call, "No interface name specified."));
+    return;
+  }
+  if (!reader.PopString(&property_name)) {
+    response_sender.Run(
+        GetBadArgsError(method_call, "No property name specified."));
+    return;
+  }
+  if (reader.HasMoreData()) {
+    response_sender.Run(
+        GetBadArgsError(method_call, "Too many arguments to Get."));
+    return;
+  }
+  auto property_map_itr = properties_.find(interface_name);
+  if (property_map_itr == properties_.end()) {
+    response_sender.Run(
+        GetBadArgsError(method_call, "No such interface on object."));
+    return;
+  }
+  LOG(ERROR) << "Looking for " << property_name << " on " << interface_name;
+  auto property_itr = property_map_itr->second.find(property_name);
+  if (property_itr == property_map_itr->second.end()) {
+    response_sender.Run(
+        GetBadArgsError(method_call, "No such property on interface."));
+    return;
+  }
+  scoped_ptr<dbus::Response> response(
+      dbus::Response::FromMethodCall(method_call));
+  dbus::MessageWriter resp_writer(response.get());
+  property_itr->second->AppendValueToWriter(&resp_writer);
+  response_sender.Run(response.Pass());
+}
+
+void ExportedPropertySet::HandleSet(
+    dbus::MethodCall* method_call,
+    dbus::ExportedObject::ResponseSender response_sender) {
+  scoped_ptr<dbus::ErrorResponse> error_resp(
+      dbus::ErrorResponse::FromMethodCall(
+          method_call, "org.freedesktop.DBus.Error.NotSupported", ""));
+  scoped_ptr<dbus::Response> response(error_resp.release());
+  response_sender.Run(response.Pass());
+}
+
+void ExportedPropertySet::HandlePropertyUpdated(
+    const std::string& interface,
+    const std::string& name,
+    const ExportedPropertyBase* property) {
+  // TODO(wiley): Send a signal from the exported object here.
+}
+
+template <typename T>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const T& value);
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const bool& value) {
+  writer->AppendVariantOfBool(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint8& value) {
+  writer->AppendVariantOfByte(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const int16& value) {
+  writer->AppendVariantOfInt16(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint16& value) {
+  writer->AppendVariantOfUint16(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const int32& value) {
+  writer->AppendVariantOfInt32(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint32& value) {
+  writer->AppendVariantOfUint32(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const int64& value) {
+  writer->AppendVariantOfInt64(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint64& value) {
+  writer->AppendVariantOfUint64(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const double& value) {
+  writer->AppendVariantOfDouble(value);
+}
+
+template <>
+void AppendPropertyToWriter(
+    dbus::MessageWriter* writer, const std::string& value) {
+  writer->AppendVariantOfString(value);
+}
+
+template <>
+void AppendPropertyToWriter(
+    dbus::MessageWriter* writer, const dbus::ObjectPath& value) {
+  writer->AppendVariantOfObjectPath(value);
+}
+
+template <>
+void AppendPropertyToWriter(
+    dbus::MessageWriter* writer, const std::vector<std::string>& value) {
+  dbus::MessageWriter variant_writer(nullptr);
+  writer->OpenVariant("as", &variant_writer);
+  variant_writer.AppendArrayOfStrings(value);
+  writer->CloseContainer(&variant_writer);
+}
+
+template <>
+void AppendPropertyToWriter(
+    dbus::MessageWriter* writer, const std::vector<dbus::ObjectPath>& value) {
+  dbus::MessageWriter variant_writer(nullptr);
+  writer->OpenVariant("ao", &variant_writer);
+  variant_writer.AppendArrayOfObjectPaths(value);
+  writer->CloseContainer(&variant_writer);
+}
+
+template <>
+void AppendPropertyToWriter(
+    dbus::MessageWriter* writer, const std::vector<uint8>& value) {
+  dbus::MessageWriter variant_writer(nullptr);
+  writer->OpenVariant("ay", &variant_writer);
+  variant_writer.AppendArrayOfBytes(value.data(), value.size());
+  writer->CloseContainer(&variant_writer);
+}
+
+template <typename T>
+ExportedProperty<T>::ExportedProperty() {}
+
+template <typename T>
+ExportedProperty<T>::~ExportedProperty() {}
+
+template <typename T>
+const T& ExportedProperty<T>::value() const { return value_; }
+
+template <typename T>
+void ExportedProperty<T>::SetValue(const T& new_value) {
+  if (value_ == new_value) {
+    return;
+  }
+  value_ = new_value;
+  // These is a brief period after the construction of an ExportedProperty
+  // when this callback is not initialized because the property has not
+  // been registered with the parent ExportedPropertySet.  During this period
+  // users should be initializing values via SetValue, and no notifications
+  // should be triggered by the ExportedPropertySet.
+  if (!on_update_.is_null()) {
+    on_update_.Run(this);
+  }
+}
+
+template <typename T>
+void ExportedProperty<T>::SetUpdateCallback(const OnUpdateCallback& cb) {
+  on_update_ = cb;
+}
+
+template <typename T>
+void ExportedProperty<T>::AppendValueToWriter(dbus::MessageWriter* writer) {
+  AppendPropertyToWriter(writer, value_);
+}
+
+template class ExportedProperty<bool>;
+template class ExportedProperty<uint8>;
+template class ExportedProperty<int16>;
+template class ExportedProperty<uint16>;
+template class ExportedProperty<int32>;
+template class ExportedProperty<uint32>;
+template class ExportedProperty<int64>;
+template class ExportedProperty<uint64>;
+template class ExportedProperty<double>;
+template class ExportedProperty<std::string>;
+template class ExportedProperty<dbus::ObjectPath>;
+template class ExportedProperty<std::vector<std::string>>;
+template class ExportedProperty<std::vector<dbus::ObjectPath>>;
+template class ExportedProperty<std::vector<uint8>>;
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
diff --git a/buffet/exported_property_set.h b/buffet/exported_property_set.h
new file mode 100644
index 0000000..7ed9d9b
--- /dev/null
+++ b/buffet/exported_property_set.h
@@ -0,0 +1,185 @@
+// Copyright 2014 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 BUFFET_EXPORTED_PROPERTY_SET_H_
+#define BUFFET_EXPORTED_PROPERTY_SET_H_
+
+#include <map>
+#include <string>
+
+#include <base/memory/weak_ptr.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.h>
+#include <gtest/gtest_prod.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+// This class may be used to implement the org.freedesktop.DBus.Properties
+// interface.  It sends the update signal on property updates:
+//
+//   org.freedesktop.DBus.Properties.PropertiesChanged (
+//       STRING interface_name,
+//       DICT<STRING,VARIANT> changed_properties,
+//       ARRAY<STRING> invalidated_properties);
+//
+//
+// and implements the required methods of the interface:
+//
+//   org.freedesktop.DBus.Properties.Get(in STRING interface_name,
+//                                       in STRING property_name,
+//                                       out VARIANT value);
+//   org.freedesktop.DBus.Properties.Set(in STRING interface_name,
+//                                       in STRING property_name,
+//                                       in VARIANT value);
+//   org.freedesktop.DBus.Properties.GetAll(in STRING interface_name,
+//                                          out DICT<STRING,VARIANT> props);
+//
+//  This class is very similar to the PropertySet class in Chrome, except that
+//  it allows objects to expose properties rather than to consume them.
+//
+//  Example usage:
+//
+//   class ExampleObjectExportingProperties {
+//    public:
+//     ExampleObjectExportingProperties(ExportedObject* exported_object)
+//         : p_(exported_object) {
+//       // Initialize properties appropriately.  Do this before
+//       // claiming the Properties interface so that daemons watching
+//       // this object don't see partial or inaccurate state.
+//       p_.ClaimPropertiesInterface();
+//     }
+//
+//    private:
+//     struct Properties : public buffet::dbus::ExportedPropertySet {
+//      public:
+//       buffet::dbus::ExportedProperty<std::string> name_;
+//       buffet::dbus::ExportedProperty<uint16> version_;
+//       buffet::dbus::ExportedProperty<dbus::ObjectPath> parent_;
+//       buffet::dbus::ExportedProperty<std::vector<std::string>> children_;
+//
+//       Properties(dbus::ExportedObject* exported_object)
+//           : buffet::dbus::ExportedPropertySet(exported_object) {
+//         RegisterProperty(kExampleInterfaceName, "Name", &name_);
+//         RegisterProperty(kExampleInterfaceName, "Version", &version_);
+//         RegisterProperty(kExampleInterfaceName, "Parent", &parent_);
+//         RegisterProperty(kExampleInterfaceName, "Children", &children_);
+//       }
+//       virtual ~Properties() {}
+//     };
+//
+//     Properties p_;
+//   };
+
+class ExportedPropertyBase {
+ public:
+  ExportedPropertyBase() {}
+  virtual ~ExportedPropertyBase() {}
+
+  typedef base::Callback<void(const ExportedPropertyBase*)> OnUpdateCallback;
+
+  // Called by ExportedPropertySet to register a callback.  This callback
+  // triggers ExportedPropertySet to send a signal from the properties
+  // interface of the exported object.
+  virtual void SetUpdateCallback(const OnUpdateCallback& cb) = 0;
+
+  // Appends a variant of the contained value to the writer.  This is
+  // 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;
+};
+
+class ExportedPropertySet {
+ public:
+  ExportedPropertySet(dbus::ExportedObject* exported_object);
+  ~ExportedPropertySet();
+
+  // Claims the org.freedesktop.DBus.Properties interface.  This
+  // needs to be done after all properties are initialized to
+  // appropriate values.
+  void ClaimPropertiesInterface();
+
+ protected:
+  void RegisterProperty(const std::string& interface_name,
+                        const std::string& property_name,
+                        ExportedPropertyBase* exported_property);
+
+ private:
+  void HandleGetAll(dbus::MethodCall* method_call,
+                    dbus::ExportedObject::ResponseSender response_sender);
+  void HandleGet(dbus::MethodCall* method_call,
+                 dbus::ExportedObject::ResponseSender response_sender);
+  // While Properties.Set has a handler to complete the interface,  we don't
+  // support writable properties.  This is almost a feature, since bindings for
+  // many languages don't support errors coming back from invalid writes.
+  // Instead, use setters in exposed interfaces.
+  void HandleSet(dbus::MethodCall* method_call,
+                 dbus::ExportedObject::ResponseSender response_sender);
+
+  virtual void HandlePropertyUpdated(const std::string& interface,
+                                     const std::string& name,
+                                     const ExportedPropertyBase* property);
+
+  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,
+           std::map<std::string, ExportedPropertyBase*>> properties_;
+
+  // D-Bus callbacks may last longer the property set exporting those methods.
+  base::WeakPtrFactory<ExportedPropertySet> weak_ptr_factory_;
+
+  friend class ExportedPropertySetTest;
+  DISALLOW_COPY_AND_ASSIGN(ExportedPropertySet);
+};
+
+template <typename T>
+class ExportedProperty : public ExportedPropertyBase {
+ public:
+  ExportedProperty();
+  virtual ~ExportedProperty() override;
+
+  // Retrieves the current value.
+  const T& value() const;
+
+  // Set the value exposed to remote applications.  This triggers notifications
+  // of changes over the Properties interface.
+  void SetValue(const T& new_value);
+
+  // Called by ExportedPropertySet.  This update callback triggers
+  // ExportedPropertySet to send a signal from the properties interface of the
+  // exported object.
+  virtual void SetUpdateCallback(const OnUpdateCallback& cb) override;
+
+  // Implementation provided by specialization.
+  virtual void AppendValueToWriter(dbus::MessageWriter* writer) override;
+
+ private:
+  OnUpdateCallback on_update_;
+  T value_{};
+
+  DISALLOW_COPY_AND_ASSIGN(ExportedProperty);
+};
+
+extern template class ExportedProperty<bool>;
+extern template class ExportedProperty<uint8>;
+extern template class ExportedProperty<int16>;
+extern template class ExportedProperty<uint16>;
+extern template class ExportedProperty<int32>;
+extern template class ExportedProperty<uint32>;
+extern template class ExportedProperty<int64>;
+extern template class ExportedProperty<uint64>;
+extern template class ExportedProperty<double>;
+extern template class ExportedProperty<std::string>;
+extern template class ExportedProperty<dbus::ObjectPath>;
+extern template class ExportedProperty<std::vector<std::string>>;
+extern template class ExportedProperty<std::vector<dbus::ObjectPath>>;
+extern template class ExportedProperty<std::vector<uint8>>;
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
+
+#endif  // BUFFET_EXPORTED_PROPERTY_SET_H_
diff --git a/buffet/exported_property_set_unittest.cc b/buffet/exported_property_set_unittest.cc
new file mode 100644
index 0000000..257614e
--- /dev/null
+++ b/buffet/exported_property_set_unittest.cc
@@ -0,0 +1,475 @@
+// Copyright 2014 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 "buffet/exported_property_set.h"
+
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+#include <base/bind.h>
+#include <dbus/message.h>
+#include <dbus/property.h>
+#include <dbus/object_path.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+
+const char kBoolPropName[] = "BoolProp";
+const char kUint8PropName[] = "Uint8Prop";
+const char kInt16PropName[] = "Int16Prop";
+const char kUint16PropName[] = "Uint16Prop";
+const char kInt32PropName[] = "Int32Prop";
+const char kUint32PropName[] = "Uint32Prop";
+const char kInt64PropName[] = "Int64Prop";
+const char kUint64PropName[] = "Uint64Prop";
+const char kDoublePropName[] = "DoubleProp";
+const char kStringPropName[] = "StringProp";
+const char kPathPropName[] = "PathProp";
+const char kStringListPropName[] = "StringListProp";
+const char kPathListPropName[] = "PathListProp";
+const char kUint8ListPropName[] = "Uint8ListProp";
+
+const char kTestInterface1[] = "org.chromium.TestInterface1";
+const char kTestInterface2[] = "org.chromium.TestInterface2";
+const char kTestInterface3[] = "org.chromium.TestInterface3";
+
+const std::string kTestString("lies");
+const dbus::ObjectPath kTestObjectPathInit(std::string("/path_init"));
+const dbus::ObjectPath kTestObjectPathUpdate(std::string("/path_update"));
+
+}  // namespace
+
+class ExportedPropertySetTest : public ::testing::Test {
+ public:
+  ExportedPropertySetTest() {}
+  struct Properties : public ExportedPropertySet {
+   public:
+    ExportedProperty<bool> bool_prop_;
+    ExportedProperty<uint8> uint8_prop_;
+    ExportedProperty<int16> int16_prop_;
+    ExportedProperty<uint16> uint16_prop_;
+    ExportedProperty<int32> int32_prop_;
+    ExportedProperty<uint32> uint32_prop_;
+    ExportedProperty<int64> int64_prop_;
+    ExportedProperty<uint64> uint64_prop_;
+    ExportedProperty<double> double_prop_;
+    ExportedProperty<std::string> string_prop_;
+    ExportedProperty<dbus::ObjectPath> path_prop_;
+    ExportedProperty<std::vector<std::string>> stringlist_prop_;
+    ExportedProperty<std::vector<dbus::ObjectPath>> pathlist_prop_;
+    ExportedProperty<std::vector<uint8>> uint8list_prop_;
+
+    Properties() : ExportedPropertySet(nullptr) {
+      // The empty string is not a valid value for an ObjectPath.
+      path_prop_.SetValue(kTestObjectPathInit);
+      RegisterProperty(kTestInterface1, kBoolPropName, &bool_prop_);
+      RegisterProperty(kTestInterface1, kUint8PropName, &uint8_prop_);
+      RegisterProperty(kTestInterface1, kInt16PropName, &int16_prop_);
+      // I chose this weird grouping because N=2 is about all the permutations
+      // of GetAll that I want to anticipate.
+      RegisterProperty(kTestInterface2, kUint16PropName, &uint16_prop_);
+      RegisterProperty(kTestInterface2, kInt32PropName, &int32_prop_);
+      RegisterProperty(kTestInterface3, kUint32PropName, &uint32_prop_);
+      RegisterProperty(kTestInterface3, kInt64PropName, &int64_prop_);
+      RegisterProperty(kTestInterface3, kUint64PropName, &uint64_prop_);
+      RegisterProperty(kTestInterface3, kDoublePropName, &double_prop_);
+      RegisterProperty(kTestInterface3, kStringPropName, &string_prop_);
+      RegisterProperty(kTestInterface3, kPathPropName, &path_prop_);
+      RegisterProperty(kTestInterface3, kStringListPropName, &stringlist_prop_);
+      RegisterProperty(kTestInterface3, kPathListPropName, &pathlist_prop_);
+      RegisterProperty(kTestInterface3, kUint8ListPropName, &uint8list_prop_);
+    }
+    virtual ~Properties() {}
+
+    void CallHandleGetAll(
+        dbus::MethodCall* method_call,
+        dbus::ExportedObject::ResponseSender response_sender) {
+      HandleGetAll(method_call, response_sender);
+    }
+
+    void CallHandleGet(dbus::MethodCall* method_call,
+                       dbus::ExportedObject::ResponseSender response_sender) {
+      HandleGet(method_call, response_sender);
+    }
+
+    void CallHandleSet(dbus::MethodCall* method_call,
+                       dbus::ExportedObject::ResponseSender response_sender) {
+      HandleSet(method_call, response_sender);
+    }
+
+    MOCK_METHOD3(PropertyUpdated, void(const std::string&, const std::string&,
+                                       const ExportedPropertyBase*));
+
+   private:
+    virtual void HandlePropertyUpdated(
+        const std::string& interface,
+        const std::string& name,
+        const ExportedPropertyBase* property) override {
+      PropertyUpdated(interface, name, property);
+    }
+  };
+
+  void StoreResponse(scoped_ptr<dbus::Response> method_response) {
+    last_response_.reset(method_response.release());
+  }
+
+  void AssertGetAllReturnsError(dbus::MethodCall* method_call) {
+    auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+                                      base::Unretained(this));
+    method_call->SetSerial(123);
+    p_.CallHandleGetAll(method_call, response_sender);
+    ASSERT_NE(dynamic_cast<dbus::ErrorResponse*>(last_response_.get()),
+              nullptr);
+  }
+
+  void AssertGetReturnsError(dbus::MethodCall* method_call) {
+    auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+                                      base::Unretained(this));
+    method_call->SetSerial(123);
+    p_.CallHandleGet(method_call, response_sender);
+    ASSERT_NE(dynamic_cast<dbus::ErrorResponse*>(last_response_.get()),
+              nullptr);
+  }
+
+  scoped_ptr<dbus::Response> GetPropertyOnInterface(
+      const std::string& interface_name, const std::string& property_name) {
+    dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                                 dbus::kPropertiesGet);
+    method_call.SetSerial(123);
+    dbus::MessageWriter writer(&method_call);
+    writer.AppendString(interface_name);
+    writer.AppendString(property_name);
+    auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+                                      base::Unretained(this));
+    p_.CallHandleGet(&method_call, response_sender);
+    return last_response_.Pass();
+  }
+
+  scoped_ptr<dbus::Response> last_response_;
+  Properties p_;
+};
+
+TEST_F(ExportedPropertySetTest, UpdateNotifications) {
+  ::testing::InSequence dummy;
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface1, kBoolPropName,
+                                  &p_.bool_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface1, kUint8PropName,
+                                  &p_.uint8_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface1, kInt16PropName,
+                                  &p_.int16_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface2, kUint16PropName,
+                                  &p_.uint16_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface2, kInt32PropName,
+                                  &p_.int32_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kUint32PropName,
+                                  &p_.uint32_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kInt64PropName,
+                                  &p_.int64_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kUint64PropName,
+                                  &p_.uint64_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kDoublePropName,
+                                  &p_.double_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kStringPropName,
+                                  &p_.string_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kPathPropName,
+                                  &p_.path_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kStringListPropName,
+                                  &p_.stringlist_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kPathListPropName,
+                                  &p_.pathlist_prop_));
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kUint8ListPropName,
+                                  &p_.uint8list_prop_));
+  p_.bool_prop_.SetValue(true);
+  p_.uint8_prop_.SetValue(1);
+  p_.int16_prop_.SetValue(1);
+  p_.uint16_prop_.SetValue(1);
+  p_.int32_prop_.SetValue(1);
+  p_.uint32_prop_.SetValue(1);
+  p_.int64_prop_.SetValue(1);
+  p_.uint64_prop_.SetValue(1);
+  p_.double_prop_.SetValue(1.0);
+  p_.string_prop_.SetValue(kTestString);
+  p_.path_prop_.SetValue(kTestObjectPathUpdate);
+  p_.stringlist_prop_.SetValue({kTestString});
+  p_.pathlist_prop_.SetValue({kTestObjectPathUpdate});
+  p_.uint8list_prop_.SetValue({1});
+}
+
+TEST_F(ExportedPropertySetTest, UpdateToSameValue) {
+  EXPECT_CALL(p_, PropertyUpdated(kTestInterface1, kBoolPropName,
+                                  &p_.bool_prop_)).Times(1);
+  p_.bool_prop_.SetValue(true);
+  p_.bool_prop_.SetValue(true);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllNoArgs) {
+  dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                               dbus::kPropertiesGetAll);
+  AssertGetAllReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllInvalidInterface) {
+  dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                               dbus::kPropertiesGetAll);
+  dbus::MessageWriter writer(&method_call);
+  writer.AppendString("org.chromium.BadInterface");
+  AssertGetAllReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllExtraArgs) {
+  dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                               dbus::kPropertiesGetAll);
+  dbus::MessageWriter writer(&method_call);
+  writer.AppendString(kTestInterface1);
+  writer.AppendString(kTestInterface1);
+  AssertGetAllReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllCorrectness) {
+  dbus::MethodCall method_call(
+      dbus::kPropertiesInterface, dbus::kPropertiesGetAll);
+  method_call.SetSerial(123);
+  dbus::MessageWriter writer(&method_call);
+  writer.AppendString(kTestInterface2);
+  auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+                                    base::Unretained(this));
+  p_.CallHandleGetAll(&method_call, response_sender);
+  dbus::MessageReader response_reader(last_response_.get());
+  dbus::MessageReader dict_reader(nullptr);
+  dbus::MessageReader entry_reader(nullptr);
+  ASSERT_TRUE(response_reader.PopArray(&dict_reader));
+  ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader));
+  std::string property_name;
+  ASSERT_TRUE(entry_reader.PopString(&property_name));
+  uint16 value16;
+  int32 value32;
+  if (property_name.compare(kUint16PropName) == 0) {
+    ASSERT_TRUE(entry_reader.PopVariantOfUint16(&value16));
+    ASSERT_FALSE(entry_reader.HasMoreData());
+    ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader));
+    ASSERT_TRUE(entry_reader.PopString(&property_name));
+    ASSERT_EQ(property_name.compare(kInt32PropName), 0);
+    ASSERT_TRUE(entry_reader.PopVariantOfInt32(&value32));
+  } else {
+    ASSERT_EQ(property_name.compare(kInt32PropName), 0);
+    ASSERT_TRUE(entry_reader.PopVariantOfInt32(&value32));
+    ASSERT_FALSE(entry_reader.HasMoreData());
+    ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader));
+    ASSERT_TRUE(entry_reader.PopString(&property_name));
+    ASSERT_EQ(property_name.compare(kUint16PropName), 0);
+    ASSERT_TRUE(entry_reader.PopVariantOfUint16(&value16));
+  }
+  ASSERT_FALSE(entry_reader.HasMoreData());
+  ASSERT_FALSE(dict_reader.HasMoreData());
+  ASSERT_FALSE(response_reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetNoArgs) {
+  dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                               dbus::kPropertiesGet);
+  AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetInvalidInterface) {
+  dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                               dbus::kPropertiesGet);
+  dbus::MessageWriter writer(&method_call);
+  writer.AppendString("org.chromium.BadInterface");
+  writer.AppendString(kInt16PropName);
+  AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetBadPropertyName) {
+  dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                               dbus::kPropertiesGet);
+  dbus::MessageWriter writer(&method_call);
+  writer.AppendString(kTestInterface1);
+  writer.AppendString("IAmNotAProperty");
+  AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetPropIfMismatch) {
+  dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                               dbus::kPropertiesGet);
+  dbus::MessageWriter writer(&method_call);
+  writer.AppendString(kTestInterface1);
+  writer.AppendString(kStringPropName);
+  AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetNoPropertyName) {
+  dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                               dbus::kPropertiesGet);
+  dbus::MessageWriter writer(&method_call);
+  writer.AppendString(kTestInterface1);
+  AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetExtraArgs) {
+  dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                               dbus::kPropertiesGet);
+  dbus::MessageWriter writer(&method_call);
+  writer.AppendString(kTestInterface1);
+  writer.AppendString(kBoolPropName);
+  writer.AppendString("Extra param");
+  AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithBool) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface1, kBoolPropName);
+  dbus::MessageReader reader(response.get());
+  bool value;
+  ASSERT_TRUE(reader.PopVariantOfBool(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint8) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface1, kUint8PropName);
+  dbus::MessageReader reader(response.get());
+  uint8 value;
+  ASSERT_TRUE(reader.PopVariantOfByte(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt16) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface1, kInt16PropName);
+  dbus::MessageReader reader(response.get());
+  int16 value;
+  ASSERT_TRUE(reader.PopVariantOfInt16(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint16) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface2, kUint16PropName);
+  dbus::MessageReader reader(response.get());
+  uint16 value;
+  ASSERT_TRUE(reader.PopVariantOfUint16(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt32) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface2, kInt32PropName);
+  dbus::MessageReader reader(response.get());
+  int32 value;
+  ASSERT_TRUE(reader.PopVariantOfInt32(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint32) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface3, kUint32PropName);
+  dbus::MessageReader reader(response.get());
+  uint32 value;
+  ASSERT_TRUE(reader.PopVariantOfUint32(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt64) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface3, kInt64PropName);
+  dbus::MessageReader reader(response.get());
+  int64 value;
+  ASSERT_TRUE(reader.PopVariantOfInt64(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint64) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface3, kUint64PropName);
+  dbus::MessageReader reader(response.get());
+  uint64 value;
+  ASSERT_TRUE(reader.PopVariantOfUint64(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithDouble) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface3, kDoublePropName);
+  dbus::MessageReader reader(response.get());
+  double value;
+  ASSERT_TRUE(reader.PopVariantOfDouble(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithString) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface3, kStringPropName);
+  dbus::MessageReader reader(response.get());
+  std::string value;
+  ASSERT_TRUE(reader.PopVariantOfString(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithPath) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface3, kPathPropName);
+  dbus::MessageReader reader(response.get());
+  dbus::ObjectPath value;
+  ASSERT_TRUE(reader.PopVariantOfObjectPath(&value));
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithStringList) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface3, kStringListPropName);
+  dbus::MessageReader reader(response.get());
+  dbus::MessageReader variant_reader(nullptr);
+  std::vector<std::string> value;
+  ASSERT_TRUE(reader.PopVariant(&variant_reader));
+  ASSERT_TRUE(variant_reader.PopArrayOfStrings(&value));
+  ASSERT_FALSE(variant_reader.HasMoreData());
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithPathList) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface3, kPathListPropName);
+  dbus::MessageReader reader(response.get());
+  dbus::MessageReader variant_reader(nullptr);
+  std::vector<dbus::ObjectPath> value;
+  ASSERT_TRUE(reader.PopVariant(&variant_reader));
+  ASSERT_TRUE(variant_reader.PopArrayOfObjectPaths(&value));
+  ASSERT_FALSE(variant_reader.HasMoreData());
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint8List) {
+  scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+      kTestInterface3, kPathListPropName);
+  dbus::MessageReader reader(response.get());
+  dbus::MessageReader variant_reader(nullptr);
+  uint8* buffer;
+  size_t buffer_len;
+  ASSERT_TRUE(reader.PopVariant(&variant_reader));
+  // |buffer| remains under the control of the MessageReader.
+  ASSERT_TRUE(variant_reader.PopArrayOfBytes(&buffer, &buffer_len));
+  ASSERT_FALSE(variant_reader.HasMoreData());
+  ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, SetFailsGracefully) {
+  dbus::MethodCall method_call(dbus::kPropertiesInterface,
+                               dbus::kPropertiesSet);
+  method_call.SetSerial(123);
+  auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+                                    base::Unretained(this));
+  p_.CallHandleSet(&method_call, response_sender);
+  ASSERT_TRUE(
+      dynamic_cast<dbus::ErrorResponse*>(last_response_.get()) != nullptr);
+}
+
+}  // namespace dbus_utils
+
+}  // namespace buffet