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/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_