Merge branch 'merge-cromo-rewrite' into merge-cromo

BUG=chromium:370267

Change-Id: I090189be488624a3a6bcb4b85e119bdbdda5d998
diff --git a/buffet/HACKING b/buffet/HACKING
new file mode 100644
index 0000000..5bab79e
--- /dev/null
+++ b/buffet/HACKING
@@ -0,0 +1,24 @@
+Some common workflows for developing with buffet:
+
+# Tell portage that you'd like to make local changes to Buffet:
+cros_workon start --board=${BOARD} platform2
+
+# Edit files in platform2/buffet/
+vim ...
+
+# Compile and install those changes into the chroot:
+USE=buffet emerge-<board> platform2
+
+# Compile and run buffet unittests
+USE=buffet P2_TEST_FILTER="buffet::*" FEATURES=test emerge-<board> platform2
+
+# Deploy the most recently built version of buffet to a DUT:
+cros deploy --board=${BOARD} <remote host> platform2
+
+#To enable additional debug logging in buffet daemon, run it as:
+# buffet --v=<level>, where <level> is verbosity level of debug info:
+#  1 - enable additional tracing of internal object construction and destruction
+#  2 - add tracing of request and response data sent over HTTP (beware of
+#      privacy concerns).
+#  3 - enable low-level CURL tracing for HTTP communication.
+buffet --v=2
diff --git a/buffet/README b/buffet/README
new file mode 100644
index 0000000..9dc1e87
--- /dev/null
+++ b/buffet/README
@@ -0,0 +1,6 @@
+// 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.
+
+This directory contains the a Brillo service for registering a device and
+sending/receiving remote commands.
diff --git a/buffet/any.cc b/buffet/any.cc
new file mode 100644
index 0000000..9ce0ce2
--- /dev/null
+++ b/buffet/any.cc
@@ -0,0 +1,51 @@
+// 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/any.h"
+
+#include <algorithm>
+
+namespace buffet {
+
+Any::Any(const Any& rhs) : data_buffer_(rhs.data_buffer_) {
+}
+
+Any::~Any() {
+}
+
+Any& Any::operator=(const Any& rhs) {
+  data_buffer_ = rhs.data_buffer_;
+  return *this;
+}
+
+const std::type_info& Any::GetType() const {
+  if (!IsEmpty())
+    return data_buffer_.GetDataPtr()->GetType();
+
+  struct NullType {};  // Special helper type representing an empty variant.
+  return typeid(NullType);
+}
+
+void Any::Swap(Any& other) {
+  std::swap(data_buffer_, other.data_buffer_);
+}
+
+bool Any::IsEmpty() const {
+  return data_buffer_.IsEmpty();
+}
+
+void Any::Clear() {
+  data_buffer_.Clear();
+}
+
+bool Any::IsConvertibleToInteger() const {
+  return !IsEmpty() && data_buffer_.GetDataPtr()->IsConvertibleToInteger();
+}
+
+intmax_t Any::GetAsInteger() const {
+  CHECK(!IsEmpty()) << "Must not be called on an empty Any";
+  return data_buffer_.GetDataPtr()->GetAsInteger();
+}
+
+}  // namespace buffet
diff --git a/buffet/any.h b/buffet/any.h
new file mode 100644
index 0000000..5ab249c
--- /dev/null
+++ b/buffet/any.h
@@ -0,0 +1,174 @@
+// 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.
+
+// This is an implementation of a "true" variant class in C++.
+// The buffet::Any class can hold any C++ type, but both the setter and
+// getter sites need to know the actual type of data.
+// Note that C-style arrays when stored in Any are reduced to simple
+// data pointers. Any will not copy a contents of the array.
+//    const int data[] = [1,2,3];
+//    Any v(data);  // stores const int*, effectively "Any v(&data[0]);"
+
+// buffet::Any is a value type. Which means, the data is copied into it
+// and Any owns it. The owned object (stored by value) will be destroyed
+// when Any is cleared or reassigned. The contained value type must be
+// copy-constructible. You can also store pointers and references to objects.
+// Storing pointers is trivial. In order to store a reference, you can
+// use helper functions std::ref() and std::cref() to create non-const and
+// const references respectively. In such a case, the type of contained data
+// will be std::reference_wrapper<T>. See 'References' unit tests in
+// any_unittest.cc for examples.
+
+#ifndef BUFFET_ANY_H_
+#define BUFFET_ANY_H_
+
+#include "buffet/any_internal_impl.h"
+
+#include <algorithm>
+
+namespace buffet {
+
+class Any final {
+ public:
+  Any() = default;
+  // Standard copy constructor. This is a value-class container
+  // that must be copy-constructible and movable. The copy constructors
+  // should not be marked as explicit.
+  Any(const Any& rhs);
+  // Typed constructor that stores a value of type T in the Any.
+  template<class T>
+  Any(T value) {        // NOLINT(runtime/explicit)
+    data_buffer_.Assign(std::move(value));
+  }
+
+  // Not declaring the destructor as virtual since this is a sealed class
+  // and there is no need to introduce a virtual table to it.
+  ~Any();
+
+  // Assignment operators.
+  Any& operator=(const Any& rhs);
+  template<class T>
+  Any& operator=(T value) {
+    data_buffer_.Assign(std::move(value));
+    return *this;
+  }
+
+  // Checks if the given type DestType can be obtained from the Any.
+  // For example, to check if Any has a 'double' value in it:
+  //  any.IsTypeCompatible<double>()
+  template<typename DestType>
+  bool IsTypeCompatible() const {
+    // Make sure the requested type DestType conforms to the storage
+    // requirements of Any. We always store the data by value, which means we
+    // strip away any references as well as cv-qualifiers. So, if the user
+    // stores "const int&", we actually store just an "int".
+    // When calling IsTypeCompatible, we need to do a similar "type cleansing"
+    // to make sure the requested type matches the type of data actually stored,
+    // so this "canonical" type is used for type checking below.
+    using CanonicalDestType = typename std::decay<DestType>::type;
+    const std::type_info& ContainedTypeId = GetType();
+    if (typeid(CanonicalDestType) == ContainedTypeId)
+      return true;
+
+    if (!std::is_pointer<CanonicalDestType>::value)
+      return false;
+
+    // If asking for a const pointer from a variant containing non-const
+    // pointer, still satisfy the request. So, we need to remove the pointer
+    // specification first, then strip the const/volatile qualifiers, then
+    // re-add the pointer back, so "const int*" would become "int*".
+    using NonPointer = typename std::remove_pointer<CanonicalDestType>::type;
+    using CanonicalDestTypeNoConst = typename std::add_pointer<
+        typename std::remove_const<NonPointer>::type>::type;
+    using CanonicalDestTypeNoVolatile = typename std::add_pointer<
+        typename std::remove_volatile<NonPointer>::type>::type;
+    using CanonicalDestTypeNoConstOrVolatile = typename std::add_pointer<
+        typename std::remove_cv<NonPointer>::type>::type;
+
+    return typeid(CanonicalDestTypeNoConst) == ContainedTypeId ||
+           typeid(CanonicalDestTypeNoVolatile) == ContainedTypeId ||
+           typeid(CanonicalDestTypeNoConstOrVolatile) == ContainedTypeId;
+  }
+
+  // Returns immutable data contained in Any.
+  // Aborts if Any doesn't contain a value of type T, or trivially
+  // convertible to/compatible with it.
+  template<typename T>
+  const T& Get() const {
+    CHECK(IsTypeCompatible<T>()) << "Requesting value of type "
+                                 << typeid(T).name()
+                                 << " from variant containing "
+                                 << GetType().name();
+    return data_buffer_.GetData<T>();
+  }
+
+  // Returns a pointer to mutable value of type T contained within Any.
+  // No data copying is made, the data pointed to is still owned by Any.
+  // If Any doesn't contain a value of type T, or trivially
+  // convertible/compatible to/with it, then it returns nullptr.
+  template<typename T>
+  T* GetPtr() {
+    if (!IsTypeCompatible<T>())
+      return nullptr;
+    return &(data_buffer_.GetData<T>());
+  }
+
+  // Returns immutable data contained in Any.
+  // If the Any doesn't contain a compatible value, the provided default
+  // |def_val| is returned instead.
+  template<typename T>
+  const T& TryGet(typename std::decay<T>::type const& def_val) const {
+    if (!IsTypeCompatible<T>())
+      return def_val;
+    return data_buffer_.GetData<T>();
+  }
+
+  // A convenience specialization of the above function where the default
+  // value of type T is returned in case the underlying Get() fails.
+  template<typename T>
+  const T& TryGet() const {
+    return TryGet<T>(typename std::decay<T>::type());
+  }
+
+
+  // Returns the type information about the contained data. For most cases,
+  // instead of using this function, you should be calling IsTypeCompatible<>().
+  const std::type_info& GetType() const;
+  // Swaps the value of this object with that of |other|.
+  void Swap(Any& other);
+  // Checks if Any is empty, that is, not containing a value of any type.
+  bool IsEmpty() const;
+  // Clears the Any and destroys any contained object. Makes it empty.
+  void Clear();
+  // Checks if Any contains a type convertible to integer.
+  // Any type that match std::is_integral<T> and std::is_enum<T> is accepted.
+  // That includes signed and unsigned char, short, int, long, etc as well as
+  // 'bool' and enumerated types.
+  // For 'integer' type, you can call GetAsInteger to do implicit type
+  // conversion to intmax_t.
+  bool IsConvertibleToInteger() const;
+  // For integral types and enums contained in the Any, get the integer value
+  // of data. This is a useful function to obtain an integer value when
+  // any can possibly have unspecified integer, such as 'short', 'unsigned long'
+  // and so on.
+  intmax_t GetAsInteger() const;
+
+ private:
+  // The data buffer for contained object.
+  internal_details::Buffer data_buffer_;
+};
+
+}  // namespace buffet
+
+namespace std {
+
+// Specialize std::swap() algorithm for buffet::Any class.
+inline void swap(buffet::Any& lhs, buffet::Any& rhs) {
+  lhs.Swap(rhs);
+}
+
+}  // namespace std
+
+#endif  // BUFFET_ANY_H_
+
diff --git a/buffet/any_internal_impl.h b/buffet/any_internal_impl.h
new file mode 100644
index 0000000..cbf0bb8
--- /dev/null
+++ b/buffet/any_internal_impl.h
@@ -0,0 +1,215 @@
+// 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.
+
+// Internal implementation of buffet::Any class.
+
+#ifndef BUFFET_ANY_INTERNAL_IMPL_H_
+#define BUFFET_ANY_INTERNAL_IMPL_H_
+
+#include <type_traits>
+#include <typeinfo>
+#include <utility>
+
+#include <base/logging.h>
+
+namespace buffet {
+
+namespace internal_details {
+
+// An extension to std::is_convertible to allow conversion from an enum to
+// an integral type which std::is_convertible does not indicate as supported.
+template <typename From, typename To>
+struct IsConvertible : public std::integral_constant<bool,
+    std::is_convertible<From, To>::value ||
+    (std::is_enum<From>::value && std::is_integral<To>::value)> {
+};
+// TryConvert is a helper function that does a safe compile-time conditional
+// type cast between data types that may not be always convertible.
+// From and To are the source and destination types.
+// The function returns true if conversion was possible/successful.
+template <typename From, typename To>
+inline typename std::enable_if<IsConvertible<From, To>::value, bool>::type
+TryConvert(const From& in, To* out) {
+  *out = static_cast<To>(in);
+  return true;
+}
+template <typename From, typename To>
+inline typename std::enable_if<!IsConvertible<From, To>::value, bool>::type
+TryConvert(const From& in, To* out) {
+  return false;
+}
+
+struct Buffer;  // Forward declaration of data buffer container.
+
+// Abstract base class for contained variant data.
+struct Data {
+  virtual ~Data() {}
+  // Returns the type information for the contained data.
+  virtual const std::type_info& GetType() const = 0;
+  // Copies the contained data to the output |buffer|.
+  virtual void CopyTo(Buffer* buffer) const = 0;
+  // Checks if the contained data is an integer type (not necessarily an 'int').
+  virtual bool IsConvertibleToInteger() const = 0;
+  // Gets the contained integral value as an integer.
+  virtual intmax_t GetAsInteger() const = 0;
+};
+
+// Concrete implementation of variant data of type T.
+template<typename T>
+struct TypedData : public Data {
+  explicit TypedData(const T& value) : value_(value) {}
+
+  virtual const std::type_info& GetType() const override { return typeid(T); }
+  virtual void CopyTo(Buffer* buffer) const override;
+  virtual bool IsConvertibleToInteger() const override {
+    return std::is_integral<T>::value || std::is_enum<T>::value;
+  }
+  virtual intmax_t GetAsInteger() const override {
+    intmax_t int_val = 0;
+    bool converted = TryConvert(value_, &int_val);
+    CHECK(converted) << "Unable to convert value of type " << typeid(T).name()
+                     << " to integer";
+    return int_val;
+  }
+  // Special method to copy data of the same type
+  // without reallocating the buffer.
+  void FastAssign(const T& source) { value_ = source; }
+
+  T value_;
+};
+
+// Buffer class that stores the contained variant data.
+// To improve performance and reduce memory fragmentation, small variants
+// are stored in pre-allocated memory buffers that are part of the Any class.
+// If the memory requirements are larger than the set limit or the type is
+// non-trivially copyable, then the contained class is allocated in a separate
+// memory block and the pointer to that memory is contained within this memory
+// buffer class.
+class Buffer {
+ public:
+  enum StorageType { kExternal, kContained };
+  Buffer() : external_ptr_(nullptr), storage_(kExternal) {}
+  ~Buffer() {
+    Clear();
+  }
+
+  Buffer(const Buffer& rhs) : Buffer() {
+    rhs.CopyTo(this);
+  }
+  Buffer& operator=(const Buffer& rhs) {
+    rhs.CopyTo(this);
+    return *this;
+  }
+
+  // Returns the underlying pointer to contained data. Uses either the pointer
+  // or the raw data depending on |storage_| type.
+  inline Data* GetDataPtr() {
+    return (storage_ == kExternal) ?
+        external_ptr_ : reinterpret_cast<Data*>(contained_buffer_);
+  }
+  inline const Data* GetDataPtr() const {
+    return (storage_ == kExternal) ?
+        external_ptr_ : reinterpret_cast<const Data*>(contained_buffer_);
+  }
+
+  // Destroys the contained object (and frees memory if needed).
+  void Clear() {
+    Data* data = GetDataPtr();
+    if (storage_ == kExternal) {
+      delete data;
+    } else {
+      // Call the destructor manually, since the object was constructed inline
+      // in the pre-allocated buffer. We still need to call the destructor
+      // to free any associated resources, but we can't call delete |data| here.
+      data->~Data();
+    }
+    external_ptr_ = nullptr;
+    storage_ = kExternal;
+  }
+
+  // Stores a value of type T.
+  template<typename T>
+  void Assign(T value) {
+    using Type = typename std::decay<T>::type;
+    using DataType = TypedData<Type>;
+    Data* ptr = GetDataPtr();
+    if (ptr && ptr->GetType() == typeid(Type)) {
+      // We assign the data to the variant container, which already
+      // has the data of the same type. Do fast copy with no memory
+      // reallocation.
+      DataType* typed_ptr = static_cast<DataType*>(ptr);
+      typed_ptr->FastAssign(value);
+    } else {
+      Clear();
+      // TODO(avakulenko): [see crbug.com/379833]
+      // Unfortunately, GCC doesn't support std::is_trivially_copyable<T> yet,
+      // so using std::is_trivial instead, which is a bit more restrictive.
+      // Once GCC has support for is_trivially_copyable, update the following.
+      if (!std::is_trivial<Type>::value ||
+          sizeof(DataType) > sizeof(contained_buffer_)) {
+        // If it is too big or not trivially copyable, allocate it separately.
+        external_ptr_ = new DataType(value);
+        storage_ = kExternal;
+      } else {
+        // Otherwise just use the pre-allocated buffer.
+        DataType* address = reinterpret_cast<DataType*>(contained_buffer_);
+        // Make sure we still call the copy constructor.
+        // Call the constructor manually by using placement 'new'.
+        new (address) DataType(value);
+        storage_ = kContained;
+      }
+    }
+  }
+
+  // Helper methods to retrieve a reference to contained data.
+  // These assume that type checking has already been performed by Any
+  // so the type cast is valid and will succeed.
+  template<typename T>
+  const T& GetData() const {
+    using DataType = internal_details::TypedData<typename std::decay<T>::type>;
+    return static_cast<const DataType*>(GetDataPtr())->value_;
+  }
+  template<typename T>
+  T& GetData() {
+    using DataType = internal_details::TypedData<typename std::decay<T>::type>;
+    return static_cast<DataType*>(GetDataPtr())->value_;
+  }
+
+  // Returns true if the buffer has no contained data.
+  bool IsEmpty() const {
+    return (storage_ == kExternal && external_ptr_ == nullptr);
+  }
+
+  // Copies the data from the current buffer into the |destination|.
+  void CopyTo(Buffer* destination) const {
+    if (IsEmpty()) {
+      destination->Clear();
+    } else {
+      GetDataPtr()->CopyTo(destination);
+    }
+  }
+
+  union {
+    // |external_ptr_| is a pointer to a larger object allocated in
+    // a separate memory block.
+    Data* external_ptr_;
+    // |contained_buffer_| is a pre-allocated buffer for smaller/simple objects.
+    // Pre-allocate enough memory to store objects as big as "double".
+    unsigned char contained_buffer_[sizeof(TypedData<double>)];
+  };
+  // Depending on a value of |storage_|, either |external_ptr_| or
+  // |contained_buffer_| above is used to get a pointer to memory containing
+  // the variant data.
+  StorageType storage_;  // Declare after the union to eliminate member padding.
+};
+
+template<typename T>
+void TypedData<T>::CopyTo(Buffer* buffer) const { buffer->Assign(value_); }
+
+}  // namespace internal_details
+
+}  // namespace buffet
+
+#endif  // BUFFET_ANY_INTERNAL_IMPL_H_
+
diff --git a/buffet/any_internal_impl_unittest.cc b/buffet/any_internal_impl_unittest.cc
new file mode 100644
index 0000000..fbc56fc
--- /dev/null
+++ b/buffet/any_internal_impl_unittest.cc
@@ -0,0 +1,114 @@
+// 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 <string>
+
+#include <gtest/gtest.h>
+
+#include "buffet/any.h"
+
+using buffet::internal_details::Buffer;
+
+TEST(Buffer, Empty) {
+  Buffer buffer;
+  EXPECT_TRUE(buffer.IsEmpty());
+  EXPECT_EQ(Buffer::kExternal, buffer.storage_);
+  EXPECT_EQ(nullptr, buffer.GetDataPtr());
+}
+
+TEST(Buffer, Store_Int) {
+  Buffer buffer;
+  buffer.Assign(2);
+  EXPECT_FALSE(buffer.IsEmpty());
+  EXPECT_EQ(Buffer::kContained, buffer.storage_);
+  EXPECT_EQ(typeid(int), buffer.GetDataPtr()->GetType());
+}
+
+TEST(Buffer, Store_Double) {
+  Buffer buffer;
+  buffer.Assign(2.3);
+  EXPECT_FALSE(buffer.IsEmpty());
+  EXPECT_EQ(Buffer::kContained, buffer.storage_);
+  EXPECT_EQ(typeid(double), buffer.GetDataPtr()->GetType());
+}
+
+TEST(Buffer, Store_Pointers) {
+  Buffer buffer;
+  // nullptr
+  buffer.Assign(nullptr);
+  EXPECT_FALSE(buffer.IsEmpty());
+  EXPECT_EQ(Buffer::kContained, buffer.storage_);
+  EXPECT_EQ(typeid(nullptr_t), buffer.GetDataPtr()->GetType());
+
+  // char *
+  buffer.Assign("abcd");
+  EXPECT_FALSE(buffer.IsEmpty());
+  EXPECT_EQ(Buffer::kContained, buffer.storage_);
+  EXPECT_EQ(typeid(const char*), buffer.GetDataPtr()->GetType());
+
+  // pointer to non-trivial object
+  class NonTrivial {
+   public:
+    virtual ~NonTrivial() {}
+  } non_trivial;
+  buffer.Assign(&non_trivial);
+  EXPECT_FALSE(buffer.IsEmpty());
+  EXPECT_EQ(Buffer::kContained, buffer.storage_);
+  EXPECT_EQ(typeid(NonTrivial*), buffer.GetDataPtr()->GetType());
+}
+
+TEST(Buffer, Store_NonTrivialObjects) {
+  class NonTrivial {
+   public:
+    virtual ~NonTrivial() {}
+  } non_trivial;
+  Buffer buffer;
+  buffer.Assign(non_trivial);
+  EXPECT_FALSE(buffer.IsEmpty());
+  EXPECT_EQ(Buffer::kExternal, buffer.storage_);
+  EXPECT_EQ(typeid(NonTrivial), buffer.GetDataPtr()->GetType());
+}
+
+TEST(Buffer, Store_Objects) {
+  Buffer buffer;
+
+  struct Small {
+    double d;
+  } small = {};
+  buffer.Assign(small);
+  EXPECT_FALSE(buffer.IsEmpty());
+  EXPECT_EQ(Buffer::kContained, buffer.storage_);
+  EXPECT_EQ(typeid(Small), buffer.GetDataPtr()->GetType());
+
+  struct Large {
+    char c[10];
+  } large = {};
+  buffer.Assign(large);
+  EXPECT_FALSE(buffer.IsEmpty());
+  EXPECT_EQ(Buffer::kExternal, buffer.storage_);
+  EXPECT_EQ(typeid(Large), buffer.GetDataPtr()->GetType());
+}
+
+TEST(Buffer, Copy) {
+  Buffer buffer1;
+  Buffer buffer2;
+
+  buffer1.Assign(30);
+  buffer1.CopyTo(&buffer2);
+  EXPECT_FALSE(buffer1.IsEmpty());
+  EXPECT_FALSE(buffer2.IsEmpty());
+  EXPECT_EQ(typeid(int), buffer1.GetDataPtr()->GetType());
+  EXPECT_EQ(typeid(int), buffer2.GetDataPtr()->GetType());
+  EXPECT_EQ(30, buffer1.GetData<int>());
+  EXPECT_EQ(30, buffer2.GetData<int>());
+
+  buffer1.Assign(std::string("abc"));
+  buffer1.CopyTo(&buffer2);
+  EXPECT_FALSE(buffer1.IsEmpty());
+  EXPECT_FALSE(buffer2.IsEmpty());
+  EXPECT_EQ(typeid(std::string), buffer1.GetDataPtr()->GetType());
+  EXPECT_EQ(typeid(std::string), buffer2.GetDataPtr()->GetType());
+  EXPECT_EQ("abc", buffer1.GetData<std::string>());
+  EXPECT_EQ("abc", buffer2.GetData<std::string>());
+}
diff --git a/buffet/any_unittest.cc b/buffet/any_unittest.cc
new file mode 100644
index 0000000..eaf3ce1
--- /dev/null
+++ b/buffet/any_unittest.cc
@@ -0,0 +1,245 @@
+// 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 <algorithm>
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "buffet/any.h"
+
+using buffet::Any;
+
+TEST(Any, Empty) {
+  Any val;
+  EXPECT_TRUE(val.IsEmpty());
+
+  Any val2 = val;
+  EXPECT_TRUE(val.IsEmpty());
+  EXPECT_TRUE(val2.IsEmpty());
+}
+
+TEST(Any, SimpleTypes) {
+  Any val(20);
+  EXPECT_FALSE(val.IsEmpty());
+  EXPECT_TRUE(val.IsTypeCompatible<int>());
+  EXPECT_EQ(20, val.Get<int>());
+
+  Any val2(3.1415926);
+  EXPECT_FALSE(val2.IsEmpty());
+  EXPECT_TRUE(val2.IsTypeCompatible<double>());
+  EXPECT_FALSE(val2.IsTypeCompatible<int>());
+  EXPECT_DOUBLE_EQ(3.1415926, val2.Get<double>());
+
+  Any val3(std::string("blah"));
+  EXPECT_TRUE(val3.IsTypeCompatible<std::string>());
+  EXPECT_EQ("blah", val3.Get<std::string>());
+}
+
+TEST(Any, Clear) {
+  Any val('x');
+  EXPECT_FALSE(val.IsEmpty());
+  EXPECT_EQ('x', val.Get<char>());
+
+  val.Clear();
+  EXPECT_TRUE(val.IsEmpty());
+}
+
+TEST(Any, Assignments) {
+  Any val(20);
+  EXPECT_EQ(20, val.Get<int>());
+
+  val = 3.1415926;
+  EXPECT_FALSE(val.IsEmpty());
+  EXPECT_TRUE(val.IsTypeCompatible<double>());
+  EXPECT_DOUBLE_EQ(3.1415926, val.Get<double>());
+
+  val = std::string("blah");
+  EXPECT_EQ("blah", val.Get<std::string>());
+
+  Any val2;
+  EXPECT_TRUE(val2.IsEmpty());
+  val2 = val;
+  EXPECT_FALSE(val.IsEmpty());
+  EXPECT_FALSE(val2.IsEmpty());
+  EXPECT_EQ("blah", val.Get<std::string>());
+  EXPECT_EQ("blah", val2.Get<std::string>());
+  val.Clear();
+  EXPECT_TRUE(val.IsEmpty());
+  EXPECT_EQ("blah", val2.Get<std::string>());
+  val2.Clear();
+  EXPECT_TRUE(val2.IsEmpty());
+
+  val = std::vector<int>{100, 20, 3};
+  auto v = val.Get<std::vector<int>>();
+  EXPECT_EQ(100, v[0]);
+  EXPECT_EQ(20, v[1]);
+  EXPECT_EQ(3, v[2]);
+}
+
+TEST(Any, Enums) {
+  enum class Dummy { foo, bar, baz };
+  Any val(Dummy::bar);
+  EXPECT_FALSE(val.IsEmpty());
+  EXPECT_TRUE(val.IsConvertibleToInteger());
+  EXPECT_EQ(Dummy::bar, val.Get<Dummy>());
+  EXPECT_EQ(1, val.GetAsInteger());
+
+  val = Dummy::baz;
+  EXPECT_EQ(2, val.GetAsInteger());
+
+  val = Dummy::foo;
+  EXPECT_EQ(0, val.GetAsInteger());
+}
+
+TEST(Any, Integers) {
+  Any val(14);
+  EXPECT_TRUE(val.IsConvertibleToInteger());
+  EXPECT_EQ(14, val.Get<int>());
+  EXPECT_EQ(14, val.GetAsInteger());
+
+  val = '\x40';
+  EXPECT_TRUE(val.IsConvertibleToInteger());
+  EXPECT_EQ(64, val.Get<char>());
+  EXPECT_EQ(64, val.GetAsInteger());
+
+  val = static_cast<uint16_t>(65535);
+  EXPECT_TRUE(val.IsConvertibleToInteger());
+  EXPECT_EQ(65535, val.Get<uint16_t>());
+  EXPECT_EQ(65535, val.GetAsInteger());
+
+  val = static_cast<uint64_t>(0xFFFFFFFFFFFFFFFFULL);
+  EXPECT_TRUE(val.IsConvertibleToInteger());
+  EXPECT_EQ(0xFFFFFFFFFFFFFFFFULL, val.Get<uint64_t>());
+  EXPECT_EQ(-1, val.GetAsInteger());
+
+  val = "abc";
+  EXPECT_FALSE(val.IsConvertibleToInteger());
+
+  int a = 5;
+  val = &a;
+  EXPECT_FALSE(val.IsConvertibleToInteger());
+}
+
+TEST(Any, Pointers) {
+  Any val("abc");  // const char*
+  EXPECT_FALSE(val.IsTypeCompatible<char*>());
+  EXPECT_TRUE(val.IsTypeCompatible<const char*>());
+  EXPECT_FALSE(val.IsTypeCompatible<volatile char*>());
+  EXPECT_TRUE(val.IsTypeCompatible<volatile const char*>());
+  EXPECT_STREQ("abc", val.Get<const char*>());
+
+  int a = 10;
+  val = &a;
+  EXPECT_TRUE(val.IsTypeCompatible<int*>());
+  EXPECT_TRUE(val.IsTypeCompatible<const int*>());
+  EXPECT_TRUE(val.IsTypeCompatible<volatile int*>());
+  EXPECT_TRUE(val.IsTypeCompatible<volatile const int*>());
+  EXPECT_EQ(10, *val.Get<const int*>());
+  *val.Get<int*>() = 3;
+  EXPECT_EQ(3, a);
+}
+
+TEST(Any, Arrays) {
+  // The following test are here to validate the array-to-pointer decay rules.
+  // Since Any does not store the contents of a C-style array, just a pointer
+  // to the data, putting array data into Any could be dangerous.
+  // Make sure the array's lifetime exceeds that of an Any containing the
+  // pointer to the array data.
+  // If you want to store the array with data, use corresponding value types
+  // such as std::vector or a struct containing C-style array as a member.
+
+  int int_array[] = {1, 2, 3};  // int*
+  Any val = int_array;
+  EXPECT_TRUE(val.IsTypeCompatible<int*>());
+  EXPECT_TRUE(val.IsTypeCompatible<const int*>());
+  EXPECT_TRUE(val.IsTypeCompatible<int[]>());
+  EXPECT_TRUE(val.IsTypeCompatible<const int[]>());
+  EXPECT_EQ(3, val.Get<int*>()[2]);
+
+  const int const_int_array[] = {10, 20, 30};  // const int*
+  val = const_int_array;
+  EXPECT_FALSE(val.IsTypeCompatible<int*>());
+  EXPECT_TRUE(val.IsTypeCompatible<const int*>());
+  EXPECT_FALSE(val.IsTypeCompatible<int[]>());
+  EXPECT_TRUE(val.IsTypeCompatible<const int[]>());
+  EXPECT_EQ(30, val.Get<const int*>()[2]);
+}
+
+TEST(Any, References) {
+  // Passing references to object via Any might be error-prone or the
+  // semantics could be unfamiliar to other developers. In many cases,
+  // using pointers instead of references are more conventional and easier
+  // to understand. Even though the cases of passing references are quite
+  // explicit on both storing and retrieving ends, you might want to
+  // use pointers instead anyway.
+
+  int a = 5;
+  Any val(std::ref(a));  // int&
+  EXPECT_EQ(5, val.Get<std::reference_wrapper<int>>().get());
+  val.Get<std::reference_wrapper<int>>().get() = 7;
+  EXPECT_EQ(7, val.Get<std::reference_wrapper<int>>().get());
+  EXPECT_EQ(7, a);
+
+  Any val2(std::cref(a));  // const int&
+  EXPECT_EQ(7, val2.Get<std::reference_wrapper<const int>>().get());
+
+  a = 10;
+  EXPECT_EQ(10, val.Get<std::reference_wrapper<int>>().get());
+  EXPECT_EQ(10, val2.Get<std::reference_wrapper<const int>>().get());
+}
+
+TEST(Any, CustomTypes) {
+  struct Person {
+    std::string name;
+    int age;
+  };
+  Any val(Person{"Jack", 40});
+  Any val2 = val;
+  EXPECT_EQ("Jack", val.Get<Person>().name);
+  val.GetPtr<Person>()->name = "Joe";
+  val.GetPtr<Person>()->age /= 2;
+  EXPECT_EQ("Joe", val.Get<Person>().name);
+  EXPECT_EQ(20, val.Get<Person>().age);
+  EXPECT_EQ("Jack", val2.Get<Person>().name);
+  EXPECT_EQ(40, val2.Get<Person>().age);
+}
+
+TEST(Any, Swap) {
+  Any val(12);
+  Any val2(2.7);
+  EXPECT_EQ(12, val.Get<int>());
+  EXPECT_EQ(2.7, val2.Get<double>());
+
+  val.Swap(val2);
+  EXPECT_EQ(2.7, val.Get<double>());
+  EXPECT_EQ(12, val2.Get<int>());
+
+  std::swap(val, val2);
+  EXPECT_EQ(12, val.Get<int>());
+  EXPECT_EQ(2.7, val2.Get<double>());
+}
+
+TEST(Any, TypeMismatch) {
+  Any val(12);
+  EXPECT_DEATH(val.Get<double>(),
+               "Requesting value of type \\w+ from variant containing \\w+");
+
+  val = std::string("123");
+  EXPECT_DEATH(val.GetAsInteger(),
+               "Unable to convert value of type \\w+ to integer");
+
+  Any empty;
+  EXPECT_DEATH(empty.GetAsInteger(), "Must not be called on an empty Any");
+}
+
+TEST(Any, TryGet) {
+  Any val(12);
+  Any empty;
+  EXPECT_EQ("dummy", val.TryGet<std::string>("dummy"));
+  EXPECT_EQ(12, val.TryGet<int>(17));
+  EXPECT_EQ(17, empty.TryGet<int>(17));
+}
diff --git a/buffet/async_event_sequencer.cc b/buffet/async_event_sequencer.cc
new file mode 100644
index 0000000..b90b552
--- /dev/null
+++ b/buffet/async_event_sequencer.cc
@@ -0,0 +1,112 @@
+// 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/async_event_sequencer.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+AsyncEventSequencer::AsyncEventSequencer() { }
+AsyncEventSequencer::~AsyncEventSequencer() { }
+
+AsyncEventSequencer::Handler AsyncEventSequencer::GetHandler(
+    const std::string& descriptive_message, bool failure_is_fatal) {
+  CHECK(!started_) << "Cannot create handlers after OnAllTasksCompletedCall()";
+  int unique_registration_id = ++registration_counter_;
+  outstanding_registrations_.insert(unique_registration_id);
+  return base::Bind(&AsyncEventSequencer::HandleFinish, this,
+                    unique_registration_id, descriptive_message,
+                    failure_is_fatal);
+}
+
+AsyncEventSequencer::ExportHandler AsyncEventSequencer::GetExportHandler(
+        const std::string& interface_name, const std::string& method_name,
+        const std::string& descriptive_message, bool failure_is_fatal) {
+  auto finish_handler = GetHandler(descriptive_message, failure_is_fatal);
+  return base::Bind(&AsyncEventSequencer::HandleDBusMethodExported, this,
+                    finish_handler,
+                    interface_name,
+                    method_name);
+}
+
+void AsyncEventSequencer::OnAllTasksCompletedCall(
+    std::vector<CompletionAction> actions) {
+  CHECK(!started_) << "OnAllTasksCompletedCall called twice!";
+  started_ = true;
+  completion_actions_.assign(actions.begin(), actions.end());
+  // All of our callbacks might have been called already.
+  PossiblyRunCompletionActions();
+}
+
+namespace {
+void IgnoreSuccess(const AsyncEventSequencer::CompletionTask& task,
+                   bool /*success*/) { task.Run(); }
+}  // namespace
+
+AsyncEventSequencer::CompletionAction AsyncEventSequencer::WrapCompletionTask(
+    const CompletionTask& task) {
+  return base::Bind(&IgnoreSuccess, task);
+}
+
+void AsyncEventSequencer::HandleFinish(int registration_number,
+                                       const std::string& error_message,
+                                       bool failure_is_fatal, bool success) {
+  RetireRegistration(registration_number);
+  CheckForFailure(failure_is_fatal, success, error_message);
+  PossiblyRunCompletionActions();
+}
+
+void AsyncEventSequencer::HandleDBusMethodExported(
+    const AsyncEventSequencer::Handler& finish_handler,
+    const std::string& expected_interface_name,
+    const std::string& expected_method_name,
+    const std::string& actual_interface_name,
+    const std::string& actual_method_name, bool success) {
+  CHECK_EQ(expected_method_name, actual_method_name)
+      << "Exported DBus method '" << actual_method_name << "' "
+      << "but expected '" << expected_method_name << "'";
+  CHECK_EQ(expected_interface_name, actual_interface_name)
+      << "Exported method DBus interface '" << actual_interface_name << "' "
+      << "but expected '" << expected_interface_name << "'";
+  finish_handler.Run(success);
+}
+
+
+void AsyncEventSequencer::RetireRegistration(int registration_number) {
+  const size_t handlers_retired = outstanding_registrations_.erase(
+      registration_number);
+  CHECK_EQ(1, handlers_retired)
+      << "Tried to retire invalid handler " << registration_number << ")";
+}
+
+void AsyncEventSequencer::CheckForFailure(bool failure_is_fatal, bool success,
+                                          const std::string& error_message) {
+  if (failure_is_fatal) {
+    CHECK(success) << error_message;
+  }
+  if (!success) {
+    LOG(ERROR) << error_message;
+    had_failures_ = true;
+  }
+}
+
+void AsyncEventSequencer::PossiblyRunCompletionActions() {
+  if (!started_ || !outstanding_registrations_.empty()) {
+    // Don't run completion actions if we have any outstanding
+    // Handlers outstanding or if any more handlers might
+    // be scheduled in the future.
+    return;
+  }
+  for (const auto& completion_action : completion_actions_) {
+    // Should this be put on the message loop or run directly?
+    completion_action.Run(!had_failures_);
+  }
+  // Discard our references to those actions.
+  completion_actions_.clear();
+}
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
diff --git a/buffet/async_event_sequencer.h b/buffet/async_event_sequencer.h
new file mode 100644
index 0000000..c16325b
--- /dev/null
+++ b/buffet/async_event_sequencer.h
@@ -0,0 +1,105 @@
+// 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_ASYNC_EVENT_SEQUENCER_H_
+#define BUFFET_ASYNC_EVENT_SEQUENCER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/basictypes.h>
+#include <base/memory/ref_counted.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+// A helper class for coordinating the multiple async tasks.  A consumer
+// may grab any number of callbacks via Get*Handler() and schedule a list
+// of completion actions to take.  When all handlers obtained via Get*Handler()
+// have been called, the AsyncEventSequencer will call its CompletionActions.
+//
+// Usage:
+//
+// void Init(const base::Callback<void(bool success)> cb) {
+//   scoped_refptr<AsyncEventSequencer> sequencer(
+//       new AsyncEventSequencer());
+//   one_delegate_needing_init_.Init(sequencer->GetHandler(
+//       "my delegate failed to init", false));
+//   dbus_init_delegate_.Init(sequencer->GetExportHandler(
+//       "org.test.Interface", "ExposedMethodName",
+//       "another delegate is flaky", false));
+//   sequencer->OnAllTasksCompletedCall({cb});
+// }
+class AsyncEventSequencer : public base::RefCounted<AsyncEventSequencer> {
+ public:
+  typedef base::Callback<void(bool success)> Handler;
+  typedef base::Callback<void (const std::string& interface_name,
+                               const std::string& method_name,
+                               bool success)> ExportHandler;
+  typedef base::Callback<void(bool all_succeeded)> CompletionAction;
+  typedef base::Callback<void(void)> CompletionTask;
+
+  AsyncEventSequencer();
+
+  // Get a Finished handler callback.  Each callback is "unique" in the sense
+  // that subsequent calls to GetHandler() will create new handlers
+  // which will need to be called before completion actions are run.
+  Handler GetHandler(const std::string& descriptive_message,
+                     bool failure_is_fatal);
+
+  // Like GetHandler except with a signature tailored to
+  // ExportedObject's ExportMethod callback requirements.  Will also assert
+  // that the passed interface/method names from ExportedObject are correct.
+  ExportHandler GetExportHandler(
+      const std::string& interface_name, const std::string& method_name,
+      const std::string& descriptive_message, bool failure_is_fatal);
+
+  // Once all handlers obtained via GetHandler have run,
+  // we'll run each CompletionAction, then discard our references.
+  // No more handlers may be obtained after this call.
+  void OnAllTasksCompletedCall(std::vector<CompletionAction> actions);
+
+  // Wrap a CompletionTask with a function that discards the result.
+  // This CompletionTask retains no references to the AsyncEventSequencer.
+  CompletionAction WrapCompletionTask(const CompletionTask& task);
+
+ private:
+  // We'll partially bind this function before giving it back via
+  // GetHandler.  Note that the returned callbacks have
+  // references to *this, which gives us the neat property that we'll
+  // destroy *this only when all our callbacks have been destroyed.
+  void HandleFinish(int registration_number, const std::string& error_message,
+                    bool failure_is_fatal, bool success);
+  // Similar to HandleFinish.
+  void HandleDBusMethodExported(
+      const Handler& finish_handler,
+      const std::string& expected_interface_name,
+      const std::string& expected_method_name,
+      const std::string& actual_interface_name,
+      const std::string& actual_method_name,
+      bool success);
+  void RetireRegistration(int registration_number);
+  void CheckForFailure(bool failure_is_fatal, bool success,
+                       const std::string& error_message);
+  void PossiblyRunCompletionActions();
+
+  bool started_{false};
+  int registration_counter_{0};
+  std::set<int> outstanding_registrations_;
+  std::vector<CompletionAction> completion_actions_;
+  bool had_failures_{false};
+  // Ref counted objects have private destructors.
+  ~AsyncEventSequencer();
+  friend class base::RefCounted<AsyncEventSequencer>;
+  DISALLOW_COPY_AND_ASSIGN(AsyncEventSequencer);
+};
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
+
+#endif  // BUFFET_ASYNC_EVENT_SEQUENCER_H_
diff --git a/buffet/async_event_sequencer_unittest.cc b/buffet/async_event_sequencer_unittest.cc
new file mode 100644
index 0000000..d95ff56
--- /dev/null
+++ b/buffet/async_event_sequencer_unittest.cc
@@ -0,0 +1,96 @@
+// 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/async_event_sequencer.h"
+
+#include <base/bind_helpers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+
+const char kTestInterface[] = "org.test.if";
+const char kTestMethod1[] = "TestMethod1";
+const char kTestMethod2[] = "TestMethod2";
+
+}  // namespace
+
+class AsyncEventSequencerTest : public ::testing::Test {
+ public:
+  MOCK_METHOD1(HandleCompletion, void(bool all_succeeded));
+
+  void SetUp() {
+      aec_ = new AsyncEventSequencer();
+      cb_ = base::Bind(&AsyncEventSequencerTest::HandleCompletion,
+                       base::Unretained(this));
+  }
+
+  scoped_refptr<AsyncEventSequencer> aec_;
+  AsyncEventSequencer::CompletionAction cb_;
+};
+
+TEST_F(AsyncEventSequencerTest, WaitForCompletionActions) {
+  auto finished_handler = aec_->GetHandler("handler failed", false);
+  finished_handler.Run(true);
+  EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+  aec_->OnAllTasksCompletedCall({cb_});
+}
+
+TEST_F(AsyncEventSequencerTest, MultiInitActionsSucceed) {
+  auto finished_handler1 = aec_->GetHandler("handler failed", false);
+  auto finished_handler2 = aec_->GetHandler("handler failed", false);
+  aec_->OnAllTasksCompletedCall({cb_});
+  finished_handler1.Run(true);
+  EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+  finished_handler2.Run(true);
+}
+
+TEST_F(AsyncEventSequencerTest, SomeInitActionsFail) {
+  auto finished_handler1 = aec_->GetHandler("handler failed", false);
+  auto finished_handler2 = aec_->GetHandler("handler failed", false);
+  aec_->OnAllTasksCompletedCall({cb_});
+  finished_handler1.Run(false);
+  EXPECT_CALL(*this, HandleCompletion(false)).Times(1);
+  finished_handler2.Run(true);
+}
+
+TEST_F(AsyncEventSequencerTest, MultiDBusActionsSucceed) {
+  auto handler1 =  aec_->GetExportHandler(
+      kTestInterface, kTestMethod1, "method export failed", false);
+  auto handler2 =  aec_->GetExportHandler(
+      kTestInterface, kTestMethod2, "method export failed", false);
+  aec_->OnAllTasksCompletedCall({cb_});
+  handler1.Run(kTestInterface, kTestMethod1, true);
+  EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+  handler2.Run(kTestInterface, kTestMethod2, true);
+}
+
+TEST_F(AsyncEventSequencerTest, SomeDBusActionsFail) {
+  auto handler1 =  aec_->GetExportHandler(
+      kTestInterface, kTestMethod1, "method export failed", false);
+  auto handler2 =  aec_->GetExportHandler(
+      kTestInterface, kTestMethod2, "method export failed", false);
+  aec_->OnAllTasksCompletedCall({cb_});
+  handler1.Run(kTestInterface, kTestMethod1, true);
+  EXPECT_CALL(*this, HandleCompletion(false)).Times(1);
+  handler2.Run(kTestInterface, kTestMethod2, false);
+}
+
+TEST_F(AsyncEventSequencerTest, MixedActions) {
+  auto handler1 =  aec_->GetExportHandler(
+      kTestInterface, kTestMethod1, "method export failed", false);
+  auto handler2 = aec_->GetHandler("handler failed", false);
+  aec_->OnAllTasksCompletedCall({cb_});
+  handler1.Run(kTestInterface, kTestMethod1, true);
+  EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+  handler2.Run(true);
+}
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
diff --git a/buffet/bind_lambda.h b/buffet/bind_lambda.h
new file mode 100644
index 0000000..0172ab3
--- /dev/null
+++ b/buffet/bind_lambda.h
@@ -0,0 +1,65 @@
+// 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_BIND_LAMBDA_H_
+#define BUFFET_BIND_LAMBDA_H_
+
+#include <base/bind.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// This file is an extension to base/bind_internal.h and adds a RunnableAdapter
+// class specialization that wraps a functor (including lambda objects), so
+// they can be used in base::Callback/base::Bind constructs.
+// By including this file you will gain the ability to write expressions like:
+//    base::Callback<int(int)> callback = base::Bind([](int value) {
+//      return value * value;
+//    });
+////////////////////////////////////////////////////////////////////////////////
+namespace base {
+namespace internal {
+
+// LambdaAdapter is a helper class that specializes on different function call
+// signatures and provides the RunType and Run() method required by
+// RunnableAdapter<> class.
+template <typename Lambda, typename Sig>
+class LambdaAdapter;
+
+// R(...)
+template <typename Lambda, typename R, typename... Args>
+class LambdaAdapter<Lambda, R(Lambda::*)(Args... args)> {
+ public:
+  typedef R(RunType)(Args...);
+  LambdaAdapter(Lambda lambda) : lambda_(lambda) {}
+  R Run(Args... args) { return lambda_(args...); }
+
+ private:
+  Lambda lambda_;
+};
+
+// R(...) const
+template <typename Lambda, typename R, typename... Args>
+class LambdaAdapter<Lambda, R(Lambda::*)(Args... args) const> {
+ public:
+  typedef R(RunType)(Args...);
+  LambdaAdapter(Lambda lambda) : lambda_(lambda) {}
+  R Run(Args... args) { return lambda_(args...); }
+
+ private:
+  Lambda lambda_;
+};
+
+template <typename Lambda>
+class RunnableAdapter : public LambdaAdapter<Lambda,
+                                             decltype(&Lambda::operator())> {
+ public:
+  explicit RunnableAdapter(Lambda lambda) :
+      LambdaAdapter<Lambda, decltype(&Lambda::operator())>(lambda) {
+  }
+};
+
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BUFFET_BIND_LAMBDA_H_
diff --git a/buffet/buffet.conf b/buffet/buffet.conf
new file mode 100644
index 0000000..66cd9ed
--- /dev/null
+++ b/buffet/buffet.conf
@@ -0,0 +1,16 @@
+# 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.
+
+description     "Brillo Buffet Service"
+author          "chromium-os-dev@chromium.org"
+
+start on starting system-services
+stop on stopping system-services
+respawn
+
+pre-start script
+  mkdir -p /var/lib/buffet
+end script
+
+exec buffet
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp
new file mode 100644
index 0000000..17a85bc
--- /dev/null
+++ b/buffet/buffet.gyp
@@ -0,0 +1,101 @@
+{
+  'target_defaults': {
+    'variables': {
+      'deps': [
+        'dbus-1',
+        'libchrome-<(libbase_ver)',
+        'libchrome-test-<(libbase_ver)',
+        'libcurl',
+        'libmetrics-<(libbase_ver)',
+      ],
+    },
+    'link_settings': {
+      'libraries': [
+        '-lbase-dbus_test_support-<(libbase_ver)',
+      ],
+    },
+    'cflags_cc': [
+      '-std=gnu++11',
+    ],
+  },
+  'targets': [
+    {
+      'target_name': 'buffet_common',
+      'type': 'static_library',
+      'sources': [
+        'any.cc',
+        'async_event_sequencer.cc',
+        'commands/object_schema.cc',
+        'commands/prop_constraints.cc',
+        'commands/prop_types.cc',
+        'commands/prop_values.cc',
+        'commands/schema_constants.cc',
+        'commands/schema_utils.cc',
+        'data_encoding.cc',
+        'dbus_constants.cc',
+        'dbus_utils.cc',
+        'device_registration_info.cc',
+        'error.cc',
+        'exported_object_manager.cc',
+        'exported_property_set.cc',
+        'http_request.cc',
+        'http_connection_curl.cc',
+        'http_transport_curl.cc',
+        'http_utils.cc',
+        'manager.cc',
+        'mime_utils.cc',
+        'storage_impls.cc',
+        'string_utils.cc',
+        'url_utils.cc'
+      ],
+    },
+    {
+      'target_name': 'buffet',
+      'type': 'executable',
+      'sources': [
+        'main.cc',
+      ],
+      'dependencies': [
+        'buffet_common',
+      ],
+    },
+    {
+      'target_name': 'buffet_client',
+      'type': 'executable',
+      'sources': [
+        'buffet_client.cc',
+        'dbus_constants.cc',
+      ],
+      'dependencies': [
+        'buffet_common',
+      ],
+    },
+    {
+      'target_name': 'buffet_testrunner',
+      'type': 'executable',
+      'dependencies': [
+        'buffet_common',
+      ],
+      'includes': ['../common-mk/common_test.gypi'],
+      'sources': [
+        'any_unittest.cc',
+        'any_internal_impl_unittest.cc',
+        'async_event_sequencer_unittest.cc',
+        'buffet_testrunner.cc',
+        'commands/object_schema_unittest.cc',
+        'commands/schema_utils_unittest.cc',
+        'data_encoding_unittest.cc',
+        'device_registration_info_unittest.cc',
+        'error_unittest.cc',
+        'exported_object_manager_unittest.cc',
+        'exported_property_set_unittest.cc',
+        'http_connection_fake.cc',
+        'http_transport_fake.cc',
+        'http_utils_unittest.cc',
+        'mime_utils_unittest.cc',
+        'string_utils_unittest.cc',
+        'url_utils_unittest.cc'
+      ],
+    },
+  ],
+}
diff --git a/buffet/buffet_client.cc b/buffet/buffet_client.cc
new file mode 100644
index 0000000..11f4ffc
--- /dev/null
+++ b/buffet/buffet_client.cc
@@ -0,0 +1,311 @@
+// 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 <iostream>  // NOLINT(readability/streams)
+#include <string>
+#include <sysexits.h>
+
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/values.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_proxy.h>
+#include <dbus/object_manager.h>
+#include <dbus/values_util.h>
+
+#include "buffet/dbus_constants.h"
+#include "buffet/data_encoding.h"
+
+using namespace buffet::dbus_constants;  // NOLINT(build/namespaces)
+
+namespace {
+static const int default_timeout_ms = 1000;
+
+void usage() {
+  std::cerr << "Possible commands:" << std::endl;
+  std::cerr << "  " << kManagerTestMethod << std::endl;
+  std::cerr << "  " << kManagerCheckDeviceRegistered << std::endl;
+  std::cerr << "  " << kManagerGetDeviceInfo << std::endl;
+  std::cerr << "  " << kManagerStartRegisterDevice
+                    << " param1 = val1&param2 = val2..." << std::endl;
+  std::cerr << "  " << kManagerFinishRegisterDevice
+                    << " device_id" << std::endl;
+  std::cerr << "  " << kManagerUpdateStateMethod << std::endl;
+  std::cerr << "  " << dbus::kObjectManagerGetManagedObjects << std::endl;
+}
+
+class BuffetHelperProxy {
+ public:
+  int Init() {
+    dbus::Bus::Options options;
+    options.bus_type = dbus::Bus::SYSTEM;
+    bus_ = new dbus::Bus(options);
+    manager_proxy_ = bus_->GetObjectProxy(
+        kServiceName,
+        dbus::ObjectPath(kManagerServicePath));
+    root_proxy_ = bus_->GetObjectProxy(
+        kServiceName,
+        dbus::ObjectPath(kRootServicePath));
+    return EX_OK;
+  }
+
+  int CallTestMethod(const CommandLine::StringVector& args) {
+    dbus::MethodCall method_call(kManagerInterface, kManagerTestMethod);
+    scoped_ptr<dbus::Response> response(
+        manager_proxy_->CallMethodAndBlock(&method_call, default_timeout_ms));
+    if (!response) {
+      std::cout << "Failed to receive a response." << std::endl;
+      return EX_UNAVAILABLE;
+    }
+    std::cout << "Received a response." << std::endl;
+    return EX_OK;
+  }
+
+  int CallManagerCheckDeviceRegistered(const CommandLine::StringVector& args) {
+    if (!args.empty()) {
+      std::cerr << "Invalid number of arguments for "
+                << "Manager." << kManagerCheckDeviceRegistered << std::endl;
+      usage();
+      return EX_USAGE;
+    }
+    dbus::MethodCall method_call(
+        kManagerInterface, kManagerCheckDeviceRegistered);
+
+    scoped_ptr<dbus::Response> response(
+        manager_proxy_->CallMethodAndBlock(&method_call, default_timeout_ms));
+    if (!response) {
+      std::cout << "Failed to receive a response." << std::endl;
+      return EX_UNAVAILABLE;
+    }
+
+    dbus::MessageReader reader(response.get());
+    std::string device_id;
+    if (!reader.PopString(&device_id)) {
+      std::cout << "No device ID in response." << std::endl;
+      return EX_SOFTWARE;
+    }
+
+    std::cout << "Device ID: "
+              << (device_id.empty() ? std::string("<unregistered>") : device_id)
+              << std::endl;
+    return EX_OK;
+  }
+
+  int CallManagerGetDeviceInfo(const CommandLine::StringVector& args) {
+    if (!args.empty()) {
+      std::cerr << "Invalid number of arguments for "
+                << "Manager." << kManagerGetDeviceInfo << std::endl;
+      usage();
+      return EX_USAGE;
+    }
+    dbus::MethodCall method_call(
+        kManagerInterface, kManagerGetDeviceInfo);
+
+    scoped_ptr<dbus::Response> response(
+        manager_proxy_->CallMethodAndBlock(&method_call, default_timeout_ms));
+    if (!response) {
+      std::cout << "Failed to receive a response." << std::endl;
+      return EX_UNAVAILABLE;
+    }
+
+    dbus::MessageReader reader(response.get());
+    std::string device_info;
+    if (!reader.PopString(&device_info)) {
+      std::cout << "No device info in response." << std::endl;
+      return EX_SOFTWARE;
+    }
+
+    std::cout << "Device Info: "
+      << (device_info.empty() ? std::string("<unregistered>") : device_info)
+      << std::endl;
+    return EX_OK;
+  }
+
+  int CallManagerStartRegisterDevice(const CommandLine::StringVector& args) {
+    if (args.size() > 1) {
+      std::cerr << "Invalid number of arguments for "
+                << "Manager." << kManagerStartRegisterDevice << std::endl;
+      usage();
+      return EX_USAGE;
+    }
+    std::map<std::string, std::shared_ptr<base::Value>> params;
+
+    if (!args.empty()) {
+      auto key_values = buffet::data_encoding::WebParamsDecode(args.front());
+      for (const auto& pair : key_values) {
+        params.insert(std::make_pair(
+          pair.first, std::shared_ptr<base::Value>(
+              base::Value::CreateStringValue(pair.second))));
+      }
+    }
+
+    dbus::MethodCall method_call(
+        kManagerInterface, kManagerStartRegisterDevice);
+    dbus::MessageWriter writer(&method_call);
+    dbus::MessageWriter dict_writer(nullptr);
+    writer.OpenArray("{sv}", &dict_writer);
+    for (const auto& pair : params) {
+      dbus::MessageWriter dict_entry_writer(nullptr);
+      dict_writer.OpenDictEntry(&dict_entry_writer);
+      dict_entry_writer.AppendString(pair.first);
+      dbus::AppendBasicTypeValueDataAsVariant(&dict_entry_writer,
+                                              *pair.second.get());
+      dict_writer.CloseContainer(&dict_entry_writer);
+    }
+    writer.CloseContainer(&dict_writer);
+
+    static const int timeout_ms = 3000;
+    scoped_ptr<dbus::Response> response(
+        manager_proxy_->CallMethodAndBlock(&method_call, timeout_ms));
+    if (!response) {
+      std::cout << "Failed to receive a response." << std::endl;
+      return EX_UNAVAILABLE;
+    }
+
+    dbus::MessageReader reader(response.get());
+    std::string info;
+    if (!reader.PopString(&info)) {
+      std::cout << "No valid response." << std::endl;
+      return EX_SOFTWARE;
+    }
+
+    std::cout << "Registration started: " << info << std::endl;
+    return EX_OK;
+  }
+
+  int CallManagerFinishRegisterDevice(const CommandLine::StringVector& args) {
+    if (args.size() > 1) {
+      std::cerr << "Invalid number of arguments for "
+                << "Manager." << kManagerFinishRegisterDevice << std::endl;
+      usage();
+      return EX_USAGE;
+    }
+    dbus::MethodCall method_call(
+        kManagerInterface, kManagerFinishRegisterDevice);
+    dbus::MessageWriter writer(&method_call);
+    std::string user_auth_code;
+    if (!args.empty()) { user_auth_code = args.front(); }
+    writer.AppendString(user_auth_code);
+    static const int timeout_ms = 10000;
+    scoped_ptr<dbus::Response> response(
+        manager_proxy_->CallMethodAndBlock(&method_call, timeout_ms));
+    if (!response) {
+      std::cout << "Failed to receive a response." << std::endl;
+      return EX_UNAVAILABLE;
+    }
+
+    dbus::MessageReader reader(response.get());
+    std::string device_id;
+    if (!reader.PopString(&device_id)) {
+      std::cout << "No device ID in response." << std::endl;
+      return EX_SOFTWARE;
+    }
+
+    std::cout << "Device ID is "
+              << (device_id.empty() ? std::string("<unregistered>") : device_id)
+              << std::endl;
+    return EX_OK;
+  }
+
+  int CallManagerUpdateState(const CommandLine::StringVector& args) {
+    if (args.size() != 1) {
+      std::cerr << "Invalid number of arguments for "
+                << "Manager." << kManagerUpdateStateMethod << std::endl;
+      usage();
+      return EX_USAGE;
+    }
+    dbus::MethodCall method_call(
+        kManagerInterface, kManagerUpdateStateMethod);
+    dbus::MessageWriter writer(&method_call);
+    writer.AppendString(args.front());
+    scoped_ptr<dbus::Response> response(
+        manager_proxy_->CallMethodAndBlock(&method_call, default_timeout_ms));
+    if (!response) {
+      std::cout << "Failed to receive a response." << std::endl;
+      return EX_UNAVAILABLE;
+    }
+    return EX_OK;
+  }
+
+  int CallRootGetManagedObjects(const CommandLine::StringVector& args) {
+    if (!args.empty()) {
+      std::cerr << "Invalid number of arguments for "
+                << dbus::kObjectManagerGetManagedObjects << std::endl;
+      usage();
+      return EX_USAGE;
+    }
+    dbus::MethodCall method_call(
+        dbus::kObjectManagerInterface, dbus::kObjectManagerGetManagedObjects);
+    scoped_ptr<dbus::Response> response(
+        root_proxy_->CallMethodAndBlock(&method_call, default_timeout_ms));
+    if (!response) {
+      std::cout << "Failed to receive a response." << std::endl;
+      return EX_UNAVAILABLE;
+    }
+    std::cout << response->ToString() << std::endl;
+    return EX_OK;
+  }
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  dbus::ObjectProxy* manager_proxy_{nullptr};
+  dbus::ObjectProxy* root_proxy_{nullptr};
+};
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  CommandLine::Init(argc, argv);
+  CommandLine* cl = CommandLine::ForCurrentProcess();
+  CommandLine::StringVector args = cl->GetArgs();
+  if (args.empty()) {
+    usage();
+    return EX_USAGE;
+  }
+
+  // Pop the command off of the args list.
+  std::string command = args.front();
+  args.erase(args.begin());
+  int err = EX_USAGE;
+  BuffetHelperProxy helper;
+  err = helper.Init();
+  if (err) {
+    std::cerr << "Error initializing proxies." << std::endl;
+    return err;
+  }
+
+  if (command.compare(kManagerTestMethod) == 0) {
+    err = helper.CallTestMethod(args);
+  } else if (command.compare(kManagerCheckDeviceRegistered) == 0 ||
+             command.compare("cr") == 0) {
+    err = helper.CallManagerCheckDeviceRegistered(args);
+  } else if (command.compare(kManagerGetDeviceInfo) == 0 ||
+             command.compare("di") == 0) {
+    err = helper.CallManagerGetDeviceInfo(args);
+  } else if (command.compare(kManagerStartRegisterDevice) == 0 ||
+             command.compare("sr") == 0) {
+    err = helper.CallManagerStartRegisterDevice(args);
+  } else if (command.compare(kManagerFinishRegisterDevice) == 0 ||
+             command.compare("fr") == 0) {
+    err = helper.CallManagerFinishRegisterDevice(args);
+  } else if (command.compare(kManagerUpdateStateMethod) == 0 ||
+             command.compare("us") == 0) {
+    err = helper.CallManagerUpdateState(args);
+  } else if (command.compare(dbus::kObjectManagerGetManagedObjects) == 0) {
+    err = helper.CallRootGetManagedObjects(args);
+  } else {
+    std::cerr << "Unknown command: " << command << std::endl;
+    usage();
+  }
+
+  if (err) {
+    std::cerr << "Done, with errors." << std::endl;
+  } else {
+    std::cout << "Done." << std::endl;
+  }
+  return err;
+}
diff --git a/buffet/buffet_testrunner.cc b/buffet/buffet_testrunner.cc
new file mode 100644
index 0000000..575f952
--- /dev/null
+++ b/buffet/buffet_testrunner.cc
@@ -0,0 +1,12 @@
+// Copyright (c) 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 <base/at_exit.h>
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+  base::AtExitManager exit_manager;
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/buffet/commands/object_schema.cc b/buffet/commands/object_schema.cc
new file mode 100644
index 0000000..b978ec9
--- /dev/null
+++ b/buffet/commands/object_schema.cc
@@ -0,0 +1,258 @@
+// 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/commands/object_schema.h"
+
+#include <algorithm>
+#include <limits>
+
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+#include <base/values.h>
+
+#include "buffet/commands/prop_types.h"
+#include "buffet/commands/prop_values.h"
+#include "buffet/commands/schema_constants.h"
+
+namespace buffet {
+
+void ObjectSchema::AddProp(const std::string& name,
+                           std::shared_ptr<PropType> prop) {
+  properties_[name] = prop;
+}
+
+const PropType* ObjectSchema::GetProp(const std::string& name) const {
+  auto p = properties_.find(name);
+  return p != properties_.end() ? p->second.get() : nullptr;
+}
+
+std::unique_ptr<base::DictionaryValue> ObjectSchema::ToJson(
+    bool full_schema, ErrorPtr* error) const {
+  std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
+  for (const auto& pair : properties_) {
+    auto PropDef = pair.second->ToJson(full_schema, error);
+    if (!PropDef)
+      return std::unique_ptr<base::DictionaryValue>();
+    value->SetWithoutPathExpansion(pair.first, PropDef.release());
+  }
+  return value;
+}
+
+bool ObjectSchema::FromJson(const base::DictionaryValue* value,
+                            const ObjectSchema* object_schema,
+                            ErrorPtr* error) {
+  Properties properties;
+  base::DictionaryValue::Iterator iter(*value);
+  while (!iter.IsAtEnd()) {
+    std::string name = iter.key();
+    const PropType* base_schema =
+        object_schema ? object_schema->GetProp(iter.key()) : nullptr;
+    if (!PropFromJson(iter.key(), iter.value(), base_schema, &properties,
+                      error))
+      return false;
+    iter.Advance();
+  }
+  properties_ = std::move(properties);
+  return true;
+}
+
+static std::unique_ptr<PropType> CreatePropType(const std::string& type_name,
+                                                const std::string& prop_name,
+                                                ErrorPtr* error) {
+  std::unique_ptr<PropType> prop;
+  ValueType type;
+  if (PropType::GetTypeFromTypeString(type_name, &type))
+    prop = PropType::Create(type);
+  if (!prop) {
+    Error::AddToPrintf(error, commands::errors::kDomain,
+                       commands::errors::kUnknownType,
+                       "Unknown type %s for parameter %s",
+                       type_name.c_str(), prop_name.c_str());
+  }
+  return prop;
+}
+
+static bool ErrorInvalidTypeInfo(const std::string& prop_name,
+                                 ErrorPtr* error) {
+  Error::AddToPrintf(error, commands::errors::kDomain,
+                     commands::errors::kNoTypeInfo,
+                     "Unable to determine parameter type for %s",
+                     prop_name.c_str());
+  return false;
+}
+
+bool ObjectSchema::PropFromJson(const std::string& prop_name,
+                                const base::Value& value,
+                                const PropType* base_schema,
+                                Properties* properties,
+                                ErrorPtr* error) const {
+  if (value.IsType(base::Value::TYPE_STRING)) {
+    // A string value is a short-hand object specification and provides
+    // the parameter type.
+    return PropFromJsonString(prop_name, value, base_schema, properties,
+                              error);
+  } else if (value.IsType(base::Value::TYPE_LIST)) {
+    // One of the enumerated types.
+    return PropFromJsonArray(prop_name, value, base_schema, properties,
+                             error);
+  } else if (value.IsType(base::Value::TYPE_DICTIONARY)) {
+    // Full parameter definition.
+    return PropFromJsonObject(prop_name, value, base_schema, properties,
+                              error);
+  }
+  Error::AddToPrintf(error, commands::errors::kDomain,
+                     commands::errors::kInvalidPropDef,
+                     "Invalid parameter definition for %s", prop_name.c_str());
+  return false;
+}
+
+bool ObjectSchema::PropFromJsonString(const std::string& prop_name,
+                                      const base::Value& value,
+                                      const PropType* base_schema,
+                                      Properties* properties,
+                                      ErrorPtr* error) const {
+  std::string type_name;
+  CHECK(value.GetAsString(&type_name)) << "Unable to get string value";
+  std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error);
+  if (!prop)
+    return false;
+  base::DictionaryValue empty;
+  if (!prop->FromJson(&empty, base_schema, error))
+    return false;
+  properties->insert(std::make_pair(prop_name, std::move(prop)));
+  return true;
+}
+
+static std::string DetectArrayType(const base::ListValue* list,
+                                   const PropType* base_schema) {
+  std::string type_name;
+  if (base_schema) {
+    type_name = base_schema->GetTypeAsString();
+  } else if (list->GetSize() > 0) {
+    const base::Value* first_element = nullptr;
+    if (list->Get(0, &first_element)) {
+      switch (first_element->GetType()) {
+      case base::Value::TYPE_BOOLEAN:
+        type_name = PropType::GetTypeStringFromType(ValueType::Boolean);
+        break;
+      case base::Value::TYPE_INTEGER:
+        type_name = PropType::GetTypeStringFromType(ValueType::Int);
+        break;
+      case base::Value::TYPE_DOUBLE:
+        type_name = PropType::GetTypeStringFromType(ValueType::Double);
+        break;
+      case base::Value::TYPE_STRING:
+        type_name = PropType::GetTypeStringFromType(ValueType::String);
+        break;
+      case base::Value::TYPE_DICTIONARY:
+        type_name = PropType::GetTypeStringFromType(ValueType::Object);
+        break;
+      default:
+        // The rest are unsupported.
+        break;
+      }
+    }
+  }
+  return type_name;
+}
+
+bool ObjectSchema::PropFromJsonArray(const std::string& prop_name,
+                                     const base::Value& value,
+                                     const PropType* base_schema,
+                                     Properties* properties,
+                                     ErrorPtr* error) const {
+  const base::ListValue* list = nullptr;
+  CHECK(value.GetAsList(&list)) << "Unable to get array value";
+  std::string type_name = DetectArrayType(list, base_schema);
+  if (type_name.empty())
+    return ErrorInvalidTypeInfo(prop_name, error);
+  std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error);
+  if (!prop)
+    return false;
+  base::DictionaryValue array_object;
+  array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum,
+                                       list->DeepCopy());
+  if (!prop->FromJson(&array_object, base_schema, error))
+    return false;
+  properties->insert(std::make_pair(prop_name, std::move(prop)));
+  return true;
+}
+
+static std::string DetectObjectType(const base::DictionaryValue* dict,
+                                    const PropType* base_schema) {
+  bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) ||
+                     dict->HasKey(commands::attributes::kNumeric_Max);
+
+  // Here we are trying to "detect the type and read in the object based on
+  // the deduced type". Later, we'll verify that this detected type matches
+  // the expectation of the base schema, if applicable, to make sure we are not
+  // changing the expected type. This makes the vendor-side (re)definition of
+  // standard and custom commands behave exactly the same.
+  // The only problem with this approach was the double-vs-int types.
+  // If the type is meant to be a double we want to allow its definition as
+  // "min:0, max:0" instead of just forcing it to be only "min:0.0, max:0.0".
+  // If we have "minimum" or "maximum", and we have a Double schema object,
+  // treat this object as a Double (even if both min and max are integers).
+  if (has_min_max && base_schema && base_schema->GetType() == ValueType::Double)
+    return PropType::GetTypeStringFromType(ValueType::Double);
+
+  // If we have at least one "minimum" or "maximum" that is Double,
+  // it's a Double.
+  const base::Value* value = nullptr;
+  if (dict->Get(commands::attributes::kNumeric_Min, &value) &&
+      value->IsType(base::Value::TYPE_DOUBLE))
+    return PropType::GetTypeStringFromType(ValueType::Double);
+  if (dict->Get(commands::attributes::kNumeric_Max, &value) &&
+      value->IsType(base::Value::TYPE_DOUBLE))
+    return PropType::GetTypeStringFromType(ValueType::Double);
+
+  // If we have "minimum" or "maximum", it's an Integer.
+  if (has_min_max)
+    return PropType::GetTypeStringFromType(ValueType::Int);
+
+  // If we have "minLength" or "maxLength", it's a String.
+  if (dict->HasKey(commands::attributes::kString_MinLength) ||
+      dict->HasKey(commands::attributes::kString_MaxLength))
+    return PropType::GetTypeStringFromType(ValueType::String);
+
+  // If we have "properties", it's an object.
+  if (dict->HasKey(commands::attributes::kObject_Properties))
+    return PropType::GetTypeStringFromType(ValueType::Object);
+
+  // If we have "enum", it's an array. Detect type from array elements.
+  const base::ListValue* list = nullptr;
+  if (dict->GetListWithoutPathExpansion(
+      commands::attributes::kOneOf_Enum, &list))
+    return DetectArrayType(list, base_schema);
+
+  return std::string();
+}
+
+bool ObjectSchema::PropFromJsonObject(const std::string& prop_name,
+                                      const base::Value& value,
+                                      const PropType* base_schema,
+                                      Properties* properties,
+                                      ErrorPtr* error) const {
+  const base::DictionaryValue* dict = nullptr;
+  CHECK(value.GetAsDictionary(&dict)) << "Unable to get dictionary value";
+  std::string type_name;
+  if (dict->HasKey(commands::attributes::kType)) {
+    if (!dict->GetString(commands::attributes::kType, &type_name))
+      return ErrorInvalidTypeInfo(prop_name, error);
+  } else {
+    type_name = DetectObjectType(dict, base_schema);
+  }
+  if (type_name.empty()) {
+    if (!base_schema)
+      return ErrorInvalidTypeInfo(prop_name, error);
+    type_name = base_schema->GetTypeAsString();
+  }
+  std::unique_ptr<PropType> prop = CreatePropType(type_name, prop_name, error);
+  if (!prop || !prop->FromJson(dict, base_schema, error))
+    return false;
+  properties->insert(std::make_pair(prop_name, std::move(prop)));
+  return true;
+}
+
+}  // namespace buffet
diff --git a/buffet/commands/object_schema.h b/buffet/commands/object_schema.h
new file mode 100644
index 0000000..d6f5069
--- /dev/null
+++ b/buffet/commands/object_schema.h
@@ -0,0 +1,100 @@
+// 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_COMMANDS_OBJECT_SCHEMA_H_
+#define BUFFET_COMMANDS_OBJECT_SCHEMA_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "buffet/error.h"
+
+namespace base {
+class Value;
+class DictionaryValue;
+}  // namespace base
+
+namespace buffet {
+
+class PropType;
+
+// ObjectSchema is a class representing an object definition in GCD command
+// schema. This could represent a GCD command definition, but also it can be
+// used when defining custom object types for command properties such as
+// output media type (paper) for print command. The schema definition for
+// these type of object description is the same.
+class ObjectSchema final {
+ public:
+  // Properties is a string-to-PropType map representing a list of
+  // properties defined for a command/object. The key is the parameter
+  // name and the value is the parameter type definition object.
+  using Properties = std::map<std::string, std::shared_ptr<PropType>>;
+
+  // Declaring default and copy constructors to document the copyable
+  // nature of this class. Using the default implementation for them though.
+  ObjectSchema() = default;
+  ObjectSchema(const ObjectSchema& rhs) = default;
+  ObjectSchema& operator=(const ObjectSchema& rhs) = default;
+
+  // Add a new parameter definition.
+  void AddProp(const std::string& name, std::shared_ptr<PropType> prop);
+  // Finds parameter type definition by name. Returns nullptr if not found.
+  const PropType* GetProp(const std::string& name) const;
+  // Gets the list of all the properties defined.
+  const Properties& GetProps() const { return properties_; }
+
+  // Specify whether extra properties are allowed on objects described by
+  // this schema. When validating a value of an object type, we can
+  // make sure that the value has only the properties explicitly defined by
+  // the schema and no other (custom) properties are allowed.
+  // This is to support JSON Schema's "additionalProperties" specification.
+  bool GetExtraPropertiesAllowed() const { return extra_properties_allowed_; }
+  void SetExtraPropertiesAllowed(bool allowed) {
+    extra_properties_allowed_ = allowed;
+  }
+
+  // Saves the object schema to JSON. When |full_schema| is set to true,
+  // then all properties and constraints are saved, otherwise, only
+  // the overridden (not inherited) ones are saved.
+  std::unique_ptr<base::DictionaryValue> ToJson(bool full_schema,
+                                                ErrorPtr* error) const;
+  // Loads the object schema from JSON. If |object_schema| is not nullptr, it is
+  // used as a base schema to inherit omitted properties and constraints from.
+  bool FromJson(const base::DictionaryValue* value,
+                const ObjectSchema* object_schema, ErrorPtr* error);
+
+ private:
+  // Internal helper method to load individual parameter type definitions.
+  bool PropFromJson(const std::string& prop_name,
+                    const base::Value& value,
+                    const PropType* base_schema,
+                    Properties* properties, ErrorPtr* error) const;
+  // Helper function in case the parameter is defined as JSON string like this:
+  //   "prop":"..."
+  bool PropFromJsonString(const std::string& prop_name,
+                          const base::Value& value,
+                          const PropType* base_schema,
+                          Properties* properties, ErrorPtr* error) const;
+  // Helper function in case the parameter is defined as JSON array like this:
+  //   "prop":[...]
+  bool PropFromJsonArray(const std::string& prop_name,
+                         const base::Value& value,
+                         const PropType* base_schema,
+                         Properties* properties, ErrorPtr* error) const;
+  // Helper function in case the parameter is defined as JSON object like this:
+  //   "prop":{...}
+  bool PropFromJsonObject(const std::string& prop_name,
+                          const base::Value& value,
+                          const PropType* base_schema,
+                          Properties* properties, ErrorPtr* error) const;
+
+  // Internal parameter type definition map.
+  Properties properties_;
+  bool extra_properties_allowed_{false};
+};
+
+}  // namespace buffet
+
+#endif  // BUFFET_COMMANDS_OBJECT_SCHEMA_H_
diff --git a/buffet/commands/object_schema_unittest.cc b/buffet/commands/object_schema_unittest.cc
new file mode 100644
index 0000000..64f4600
--- /dev/null
+++ b/buffet/commands/object_schema_unittest.cc
@@ -0,0 +1,877 @@
+// 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 <algorithm>
+#include <limits>
+#include <memory>
+#include <vector>
+
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "buffet/commands/object_schema.h"
+#include "buffet/commands/prop_types.h"
+
+namespace {
+// Helper method to create base::Value from a string as a smart pointer.
+// For ease of definition in C++ code, double-quotes in the source definition
+// are replaced with apostrophes.
+std::unique_ptr<base::Value> CreateValue(const char* json) {
+  std::string json2(json);
+  // Convert apostrophes to double-quotes so JSONReader can parse the string.
+  std::replace(json2.begin(), json2.end(), '\'', '"');
+  return std::unique_ptr<base::Value>(base::JSONReader::Read(json2));
+}
+
+// Helper method to create a JSON dictionary object from a string.
+std::unique_ptr<base::DictionaryValue> CreateDictionaryValue(const char* json) {
+  std::string json2(json);
+  std::replace(json2.begin(), json2.end(), '\'', '"');
+  base::Value* value = base::JSONReader::Read(json2);
+  base::DictionaryValue* dict;
+  value->GetAsDictionary(&dict);
+  return std::unique_ptr<base::DictionaryValue>(dict);
+}
+
+// Converts a JSON value to a string. It also converts double-quotes to
+// apostrophes for easy comparisons in C++ source code.
+std::string ValueToString(const base::Value* value) {
+  std::string json;
+  base::JSONWriter::Write(value, &json);
+  std::replace(json.begin(), json.end(), '"', '\'');
+  return json;
+}
+
+}  // namespace
+
+TEST(CommandSchema, IntPropType_Empty) {
+  buffet::IntPropType prop;
+  EXPECT_TRUE(prop.GetConstraints().empty());
+  EXPECT_FALSE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+}
+
+TEST(CommandSchema, IntPropType_Types) {
+  buffet::IntPropType prop;
+  EXPECT_EQ(nullptr, prop.GetBoolean());
+  EXPECT_EQ(&prop, prop.GetInt());
+  EXPECT_EQ(nullptr, prop.GetDouble());
+  EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
+}
+
+TEST(CommandSchema, IntPropType_ToJson) {
+  buffet::IntPropType prop;
+  EXPECT_EQ("'integer'", ValueToString(prop.ToJson(false, nullptr).get()));
+  EXPECT_EQ("{'type':'integer'}",
+            ValueToString(prop.ToJson(true, nullptr).get()));
+  buffet::IntPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_EQ("{}", ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'minimum':3}").get(),
+                  &prop, nullptr);
+  EXPECT_EQ("{'minimum':3}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'maximum':-7}").get(),
+                  &prop, nullptr);
+  EXPECT_EQ("{'maximum':-7}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':5}").get(),
+                  &prop, nullptr);
+  EXPECT_EQ("{'maximum':5,'minimum':0}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'enum':[1,2,3]}").get(), &prop,
+                  nullptr);
+  EXPECT_EQ("[1,2,3]",
+            ValueToString(param2.ToJson(false, nullptr).get()));
+}
+
+TEST(CommandSchema, IntPropType_FromJson) {
+  buffet::IntPropType prop;
+  prop.AddMinMaxConstraint(2, 8);
+  buffet::IntPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_FALSE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(2, prop.GetMinValue());
+  EXPECT_EQ(8, prop.GetMaxValue());
+  prop.AddMinMaxConstraint(-2, 30);
+  param2.FromJson(CreateDictionaryValue("{'minimum':7}").get(),
+                  &prop, nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(7, param2.GetMinValue());
+  EXPECT_EQ(30, param2.GetMaxValue());
+  param2.FromJson(CreateDictionaryValue("{'maximum':17}").get(),
+                  &prop, nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(-2, param2.GetMinValue());
+  EXPECT_EQ(17, param2.GetMaxValue());
+  param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':6}").get(),
+                  &prop, nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(0, param2.GetMinValue());
+  EXPECT_EQ(6, param2.GetMaxValue());
+}
+
+TEST(CommandSchema, IntPropType_Validate) {
+  buffet::IntPropType prop;
+  prop.AddMinMaxConstraint(2, 4);
+  buffet::ErrorPtr error;
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("-1").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("0").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("1").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("2").get(), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("3").get(), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("4").get(), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("5").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("true").get(), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("3.0").get(), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("'3'").get(), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, BoolPropType_Empty) {
+  buffet::BooleanPropType prop;
+  EXPECT_TRUE(prop.GetConstraints().empty());
+  EXPECT_FALSE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+}
+
+TEST(CommandSchema, BoolPropType_Types) {
+  buffet::BooleanPropType prop;
+  EXPECT_EQ(nullptr, prop.GetInt());
+  EXPECT_EQ(&prop, prop.GetBoolean());
+  EXPECT_EQ(nullptr, prop.GetDouble());
+  EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
+}
+
+TEST(CommandSchema, BoolPropType_ToJson) {
+  buffet::BooleanPropType prop;
+  EXPECT_EQ("'boolean'", ValueToString(prop.ToJson(false, nullptr).get()));
+  EXPECT_EQ("{'type':'boolean'}",
+            ValueToString(prop.ToJson(true, nullptr).get()));
+  buffet::BooleanPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_EQ("{}", ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'enum':[true,false]}").get(), &prop,
+                  nullptr);
+  EXPECT_EQ("[true,false]", ValueToString(param2.ToJson(false, nullptr).get()));
+  EXPECT_EQ("{'enum':[true,false],'type':'boolean'}",
+            ValueToString(param2.ToJson(true, nullptr).get()));
+}
+
+TEST(CommandSchema, BoolPropType_FromJson) {
+  buffet::BooleanPropType prop;
+  prop.FromJson(CreateDictionaryValue("{'enum':[true]}").get(), &prop,
+                nullptr);
+  buffet::BooleanPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_FALSE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(std::vector<bool>{true}, prop.GetOneOfValues());
+}
+
+TEST(CommandSchema, BoolPropType_Validate) {
+  buffet::BooleanPropType prop;
+  prop.FromJson(CreateDictionaryValue("{'enum':[true]}").get(), &prop,
+                nullptr);
+  buffet::ErrorPtr error;
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("false").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("true").get(), &error));
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("1").get(), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("3.0").get(), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("'3'").get(), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, DoublePropType_Empty) {
+  buffet::DoublePropType prop;
+  EXPECT_DOUBLE_EQ(std::numeric_limits<double>::lowest(), prop.GetMinValue());
+  EXPECT_DOUBLE_EQ((std::numeric_limits<double>::max)(), prop.GetMaxValue());
+  EXPECT_FALSE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+}
+
+TEST(CommandSchema, DoublePropType_Types) {
+  buffet::DoublePropType prop;
+  EXPECT_EQ(nullptr, prop.GetInt());
+  EXPECT_EQ(nullptr, prop.GetBoolean());
+  EXPECT_EQ(&prop, prop.GetDouble());
+  EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
+}
+
+TEST(CommandSchema, DoublePropType_ToJson) {
+  buffet::DoublePropType prop;
+  EXPECT_EQ("'number'", ValueToString(prop.ToJson(false, nullptr).get()));
+  EXPECT_EQ("{'type':'number'}",
+            ValueToString(prop.ToJson(true, nullptr).get()));
+  buffet::DoublePropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_EQ("{}", ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'minimum':3}").get(), &prop,
+                  nullptr);
+  EXPECT_EQ("{'minimum':3.0}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'maximum':-7}").get(), &prop,
+                  nullptr);
+  EXPECT_EQ("{'maximum':-7.0}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':5}").get(),
+                  &prop, nullptr);
+  EXPECT_EQ("{'maximum':5.0,'minimum':0.0}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
+}
+
+TEST(CommandSchema, DoublePropType_FromJson) {
+  buffet::DoublePropType prop;
+  prop.AddMinMaxConstraint(2.5, 8.7);
+  buffet::DoublePropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_FALSE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_DOUBLE_EQ(2.5, prop.GetMinValue());
+  EXPECT_DOUBLE_EQ(8.7, prop.GetMaxValue());
+  prop.AddMinMaxConstraint(-2.2, 30.4);
+  param2.FromJson(CreateDictionaryValue("{'minimum':7}").get(), &prop,
+                  nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_DOUBLE_EQ(7.0, param2.GetMinValue());
+  EXPECT_DOUBLE_EQ(30.4, param2.GetMaxValue());
+  param2.FromJson(CreateDictionaryValue("{'maximum':17.2}").get(), &prop,
+                  nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_DOUBLE_EQ(-2.2, param2.GetMinValue());
+  EXPECT_DOUBLE_EQ(17.2, param2.GetMaxValue());
+  param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':6.1}").get(),
+                  &prop, nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_DOUBLE_EQ(0.0, param2.GetMinValue());
+  EXPECT_DOUBLE_EQ(6.1, param2.GetMaxValue());
+}
+
+TEST(CommandSchema, DoublePropType_Validate) {
+  buffet::DoublePropType prop;
+  prop.AddMinMaxConstraint(-1.2, 1.3);
+  buffet::ErrorPtr error;
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("-2").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("-1.3").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("-1.2").get(), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("0.0").get(), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("1.3").get(), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("1.31").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("true").get(), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("'0.0'").get(), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, StringPropType_Empty) {
+  buffet::StringPropType prop;
+  EXPECT_EQ(0, prop.GetMinLength());
+  EXPECT_EQ((std::numeric_limits<int>::max)(), prop.GetMaxLength());
+  EXPECT_FALSE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+}
+
+TEST(CommandSchema, StringPropType_Types) {
+  buffet::StringPropType prop;
+  EXPECT_EQ(nullptr, prop.GetInt());
+  EXPECT_EQ(nullptr, prop.GetBoolean());
+  EXPECT_EQ(nullptr, prop.GetDouble());
+  EXPECT_EQ(&prop, prop.GetString());
+  EXPECT_EQ(nullptr, prop.GetObject());
+}
+
+TEST(CommandSchema, StringPropType_ToJson) {
+  buffet::StringPropType prop;
+  EXPECT_EQ("'string'", ValueToString(prop.ToJson(false, nullptr).get()));
+  EXPECT_EQ("{'type':'string'}",
+            ValueToString(prop.ToJson(true, nullptr).get()));
+  buffet::StringPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_EQ("{}", ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'minLength':3}").get(), &prop,
+                  nullptr);
+  EXPECT_EQ("{'minLength':3}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'maxLength':7}").get(), &prop,
+                  nullptr);
+  EXPECT_EQ("{'maxLength':7}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
+  param2.FromJson(CreateDictionaryValue("{'minLength':0,'maxLength':5}").get(),
+                  &prop, nullptr);
+  EXPECT_EQ("{'maxLength':5,'minLength':0}",
+            ValueToString(param2.ToJson(false, nullptr).get()));
+}
+
+TEST(CommandSchema, StringPropType_FromJson) {
+  buffet::StringPropType prop;
+  prop.AddLengthConstraint(2, 8);
+  buffet::StringPropType param2;
+  param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_FALSE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(2, prop.GetMinLength());
+  EXPECT_EQ(8, prop.GetMaxLength());
+  prop.AddLengthConstraint(3, 5);
+  param2.FromJson(CreateDictionaryValue("{'minLength':4}").get(), &prop,
+                  nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(4, param2.GetMinLength());
+  EXPECT_EQ(5, param2.GetMaxLength());
+  param2.FromJson(CreateDictionaryValue("{'maxLength':8}").get(), &prop,
+                  nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(3, param2.GetMinLength());
+  EXPECT_EQ(8, param2.GetMaxLength());
+  param2.FromJson(CreateDictionaryValue(
+      "{'minLength':1,'maxLength':7}").get(), &prop, nullptr);
+  EXPECT_TRUE(param2.HasOverriddenAttributes());
+  EXPECT_TRUE(param2.IsBasedOnSchema());
+  EXPECT_EQ(1, param2.GetMinLength());
+  EXPECT_EQ(7, param2.GetMaxLength());
+}
+
+TEST(CommandSchema, StringPropType_Validate) {
+  buffet::StringPropType prop;
+  prop.AddLengthConstraint(1, 3);
+  buffet::ErrorPtr error;
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("''").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  prop.AddLengthConstraint(2, 3);
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("''").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("'a'").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("'ab'").get(), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("'abc'").get(), &error));
+  EXPECT_EQ(nullptr, error.get());
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("'abcd'").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+
+  prop.FromJson(CreateDictionaryValue("{'enum':['abc','def','xyz!!']}").get(),
+                nullptr, &error);
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("'abc'").get(), &error));
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("'def'").get(), &error));
+  EXPECT_TRUE(prop.ValidateValue(CreateValue("'xyz!!'").get(), &error));
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("'xyz'").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, ObjectPropType_Empty) {
+  buffet::ObjectPropType prop;
+  EXPECT_TRUE(prop.HasOverriddenAttributes());
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+}
+
+TEST(CommandSchema, ObjectPropType_Types) {
+  buffet::ObjectPropType prop;
+  EXPECT_EQ(nullptr, prop.GetInt());
+  EXPECT_EQ(nullptr, prop.GetBoolean());
+  EXPECT_EQ(nullptr, prop.GetDouble());
+  EXPECT_EQ(nullptr, prop.GetString());
+  EXPECT_EQ(&prop, prop.GetObject());
+}
+
+TEST(CommandSchema, ObjectPropType_ToJson) {
+  buffet::ObjectPropType prop;
+  EXPECT_EQ("{'properties':{}}",
+            ValueToString(prop.ToJson(false, nullptr).get()));
+  EXPECT_EQ("{'properties':{},'type':'object'}",
+            ValueToString(prop.ToJson(true, nullptr).get()));
+  EXPECT_FALSE(prop.IsBasedOnSchema());
+  buffet::ObjectPropType prop2;
+  prop2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+  EXPECT_EQ("{}", ValueToString(prop2.ToJson(false, nullptr).get()));
+  EXPECT_TRUE(prop2.IsBasedOnSchema());
+
+  auto schema = std::make_shared<buffet::ObjectSchema>();
+  schema->AddProp("expires", std::make_shared<buffet::IntPropType>());
+  auto pw = std::make_shared<buffet::StringPropType>();
+  pw->AddLengthConstraint(6, 100);
+  schema->AddProp("password", pw);
+  prop2.SetObjectSchema(schema);
+  EXPECT_EQ("{'properties':{'expires':'integer',"
+            "'password':{'maxLength':100,'minLength':6}}}",
+            ValueToString(prop2.ToJson(false, nullptr).get()));
+  EXPECT_EQ("{'properties':{'expires':{'type':'integer'},"
+            "'password':{'maxLength':100,'minLength':6,'type':'string'}},"
+            "'type':'object'}",
+            ValueToString(prop2.ToJson(true, nullptr).get()));
+}
+
+TEST(CommandSchema, ObjectPropType_FromJson) {
+  buffet::ObjectPropType base_prop;
+  EXPECT_TRUE(base_prop.FromJson(CreateDictionaryValue(
+      "{'properties':{'name':'string','age':'integer'}}").get(), nullptr,
+      nullptr));
+  auto schema = base_prop.GetObjectSchemaPtr();
+  const buffet::PropType* prop = schema->GetProp("name");
+  EXPECT_EQ(buffet::ValueType::String, prop->GetType());
+  prop = schema->GetProp("age");
+  EXPECT_EQ(buffet::ValueType::Int, prop->GetType());
+}
+
+TEST(CommandSchema, ObjectPropType_Validate) {
+  buffet::ObjectPropType prop;
+  prop.FromJson(CreateDictionaryValue(
+      "{'properties':{'expires':'integer',"
+      "'password':{'maxLength':100,'minLength':6}}}").get(), nullptr,
+      nullptr);
+  buffet::ErrorPtr error;
+  EXPECT_TRUE(prop.ValidateValue(CreateValue(
+      "{'expires':10,'password':'abcdef'}").get(), &error));
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue(
+      "{'expires':10}").get(), &error));
+  EXPECT_EQ("parameter_missing", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue(
+      "{'password':'abcdef'}").get(), &error));
+  EXPECT_EQ("parameter_missing", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue(
+      "{'expires':10,'password':'abcde'}").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetFirstError()->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue("2").get(), &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue(
+      "{'expires':10,'password':'abcdef','retry':true}").get(), &error));
+  EXPECT_EQ("unexpected_parameter", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchema, ObjectPropType_Validate_Enum) {
+  buffet::ObjectPropType prop;
+  EXPECT_TRUE(prop.FromJson(CreateDictionaryValue(
+      "{'properties':{'width':'integer','height':'integer'},"
+      "'enum':[{'width':10,'height':20},{'width':100,'height':200}]}").get(),
+      nullptr, nullptr));
+  buffet::ErrorPtr error;
+  EXPECT_TRUE(prop.ValidateValue(CreateValue(
+      "{'height':20,'width':10}").get(), &error));
+  error.reset();
+
+  EXPECT_TRUE(prop.ValidateValue(CreateValue(
+      "{'height':200,'width':100}").get(), &error));
+  error.reset();
+
+  EXPECT_FALSE(prop.ValidateValue(CreateValue(
+      "{'height':12,'width':10}").get(), &error));
+  EXPECT_EQ("out_of_range", error->GetCode());
+  error.reset();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeName) {
+  buffet::ObjectSchema schema;
+  const char* schema_str = "{"
+  "'param1':'integer',"
+  "'param2':'number',"
+  "'param3':'string'"
+  "}";
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                              nullptr));
+  EXPECT_EQ(buffet::ValueType::Int, schema.GetProp("param1")->GetType());
+  EXPECT_EQ(buffet::ValueType::Double, schema.GetProp("param2")->GetType());
+  EXPECT_EQ(buffet::ValueType::String, schema.GetProp("param3")->GetType());
+  EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString());
+  EXPECT_EQ(nullptr, schema.GetProp("param4"));
+
+  int min_int = (std::numeric_limits<int>::min)();
+  int max_int = (std::numeric_limits<int>::max)();
+  double min_dbl = (std::numeric_limits<double>::lowest)();
+  double max_dbl = (std::numeric_limits<double>::max)();
+  EXPECT_EQ(min_int, schema.GetProp("param1")->GetInt()->GetMinValue());
+  EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue());
+  EXPECT_EQ(min_dbl, schema.GetProp("param2")->GetDouble()->GetMinValue());
+  EXPECT_EQ(max_dbl, schema.GetProp("param2")->GetDouble()->GetMaxValue());
+  EXPECT_EQ(0, schema.GetProp("param3")->GetString()->GetMinLength());
+  EXPECT_EQ(max_int, schema.GetProp("param3")->GetString()->GetMaxLength());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Full_TypeName) {
+  buffet::ObjectSchema schema;
+  const char* schema_str = "{"
+  "'param1':{'type':'integer'},"
+  "'param2':{'type':'number'},"
+  "'param3':{'type':'string'}"
+  "}";
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                              nullptr));
+  EXPECT_EQ(buffet::ValueType::Int, schema.GetProp("param1")->GetType());
+  EXPECT_EQ(buffet::ValueType::Double, schema.GetProp("param2")->GetType());
+  EXPECT_EQ(buffet::ValueType::String, schema.GetProp("param3")->GetType());
+  EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString());
+  EXPECT_EQ(nullptr, schema.GetProp("param4"));
+
+  int min_int = (std::numeric_limits<int>::min)();
+  int max_int = (std::numeric_limits<int>::max)();
+  double min_dbl = (std::numeric_limits<double>::lowest)();
+  double max_dbl = (std::numeric_limits<double>::max)();
+  EXPECT_EQ(min_int, schema.GetProp("param1")->GetInt()->GetMinValue());
+  EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue());
+  EXPECT_EQ(min_dbl, schema.GetProp("param2")->GetDouble()->GetMinValue());
+  EXPECT_EQ(max_dbl, schema.GetProp("param2")->GetDouble()->GetMaxValue());
+  EXPECT_EQ(0, schema.GetProp("param3")->GetString()->GetMinLength());
+  EXPECT_EQ(max_int, schema.GetProp("param3")->GetString()->GetMaxLength());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeDeduction_Scalar) {
+  buffet::ObjectSchema schema;
+  const char* schema_str = "{"
+  "'param1' :{'minimum':2},"
+  "'param2' :{'maximum':10},"
+  "'param3' :{'maximum':8, 'minimum':2},"
+  "'param4' :{'minimum':2.1},"
+  "'param5' :{'maximum':10.1},"
+  "'param6' :{'maximum':8.1, 'minimum':3.1},"
+  "'param7' :{'maximum':8, 'minimum':3.1},"
+  "'param8' :{'maximum':8.1, 'minimum':3},"
+  "'param9' :{'minLength':2},"
+  "'param10':{'maxLength':10},"
+  "'param11':{'maxLength':8, 'minLength':3}"
+  "}";
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                              nullptr));
+  EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param2")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param3")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param4")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param6")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param7")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param9")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param10")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param11")->GetTypeAsString());
+
+  int min_int = (std::numeric_limits<int>::min)();
+  int max_int = (std::numeric_limits<int>::max)();
+  double min_dbl = (std::numeric_limits<double>::lowest)();
+  double max_dbl = (std::numeric_limits<double>::max)();
+  EXPECT_EQ(2, schema.GetProp("param1")->GetInt()->GetMinValue());
+  EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue());
+  EXPECT_EQ(min_int, schema.GetProp("param2")->GetInt()->GetMinValue());
+  EXPECT_EQ(10, schema.GetProp("param2")->GetInt()->GetMaxValue());
+  EXPECT_EQ(2, schema.GetProp("param3")->GetInt()->GetMinValue());
+  EXPECT_EQ(8, schema.GetProp("param3")->GetInt()->GetMaxValue());
+  EXPECT_DOUBLE_EQ(2.1, schema.GetProp("param4")->GetDouble()->GetMinValue());
+  EXPECT_DOUBLE_EQ(max_dbl,
+                   schema.GetProp("param4")->GetDouble()->GetMaxValue());
+  EXPECT_DOUBLE_EQ(min_dbl,
+                   schema.GetProp("param5")->GetDouble()->GetMinValue());
+  EXPECT_DOUBLE_EQ(10.1, schema.GetProp("param5")->GetDouble()->GetMaxValue());
+  EXPECT_DOUBLE_EQ(3.1, schema.GetProp("param6")->GetDouble()->GetMinValue());
+  EXPECT_DOUBLE_EQ(8.1, schema.GetProp("param6")->GetDouble()->GetMaxValue());
+  EXPECT_DOUBLE_EQ(3.1, schema.GetProp("param7")->GetDouble()->GetMinValue());
+  EXPECT_DOUBLE_EQ(8.0, schema.GetProp("param7")->GetDouble()->GetMaxValue());
+  EXPECT_DOUBLE_EQ(3.0, schema.GetProp("param8")->GetDouble()->GetMinValue());
+  EXPECT_DOUBLE_EQ(8.1, schema.GetProp("param8")->GetDouble()->GetMaxValue());
+  EXPECT_EQ(2, schema.GetProp("param9")->GetString()->GetMinLength());
+  EXPECT_EQ(max_int, schema.GetProp("param9")->GetString()->GetMaxLength());
+  EXPECT_EQ(0, schema.GetProp("param10")->GetString()->GetMinLength());
+  EXPECT_EQ(10, schema.GetProp("param10")->GetString()->GetMaxLength());
+  EXPECT_EQ(3, schema.GetProp("param11")->GetString()->GetMinLength());
+  EXPECT_EQ(8, schema.GetProp("param11")->GetString()->GetMaxLength());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeDeduction_Array) {
+  buffet::ObjectSchema schema;
+  const char* schema_str = "{"
+  "'param1' :[0,1,2,3],"
+  "'param2' :[0.0,1.1,2.2],"
+  "'param3' :['id1', 'id2'],"
+  "'param4' :{'enum':[1,2,3]},"
+  "'param5' :{'enum':[-1.1,2.2,3]},"
+  "'param6' :{'enum':['id0', 'id1']},"
+  "'param7' :{'type':'integer', 'enum':[1,2,3]},"
+  "'param8' :{'type':'number',  'enum':[1,2,3]},"
+  "'param9' :{'type':'number',  'enum':[]},"
+  "'param10':{'type':'integer', 'enum':[]}"
+  "}";
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                              nullptr));
+  EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param4")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString());
+  EXPECT_EQ("string", schema.GetProp("param6")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param7")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString());
+  EXPECT_EQ("number", schema.GetProp("param9")->GetTypeAsString());
+  EXPECT_EQ("integer", schema.GetProp("param10")->GetTypeAsString());
+
+  EXPECT_EQ(4, schema.GetProp("param1")->GetInt()->GetOneOfValues().size());
+  EXPECT_EQ(3, schema.GetProp("param2")->GetDouble()->GetOneOfValues().size());
+  EXPECT_EQ(2, schema.GetProp("param3")->GetString()->GetOneOfValues().size());
+  EXPECT_EQ(3, schema.GetProp("param4")->GetInt()->GetOneOfValues().size());
+  EXPECT_EQ(3, schema.GetProp("param5")->GetDouble()->GetOneOfValues().size());
+  EXPECT_EQ(2, schema.GetProp("param6")->GetString()->GetOneOfValues().size());
+  EXPECT_EQ(3, schema.GetProp("param7")->GetInt()->GetOneOfValues().size());
+  EXPECT_EQ(3, schema.GetProp("param8")->GetDouble()->GetOneOfValues().size());
+  EXPECT_EQ(0, schema.GetProp("param9")->GetDouble()->GetOneOfValues().size());
+  EXPECT_EQ(0, schema.GetProp("param10")->GetInt()->GetOneOfValues().size());
+
+  EXPECT_EQ(std::vector<int>({0, 1, 2, 3}),
+            schema.GetProp("param1")->GetInt()->GetOneOfValues());
+  EXPECT_EQ(std::vector<double>({0.0, 1.1, 2.2}),
+            schema.GetProp("param2")->GetDouble()->GetOneOfValues());
+  EXPECT_EQ(std::vector<std::string>({"id1", "id2"}),
+            schema.GetProp("param3")->GetString()->GetOneOfValues());
+
+  EXPECT_EQ(std::vector<int>({1, 2, 3}),
+            schema.GetProp("param4")->GetInt()->GetOneOfValues());
+  EXPECT_EQ(std::vector<double>({-1.1, 2.2, 3.0}),
+            schema.GetProp("param5")->GetDouble()->GetOneOfValues());
+  EXPECT_EQ(std::vector<std::string>({"id0", "id1"}),
+            schema.GetProp("param6")->GetString()->GetOneOfValues());
+  EXPECT_EQ(std::vector<int>({1, 2, 3}),
+            schema.GetProp("param7")->GetInt()->GetOneOfValues());
+  EXPECT_EQ(std::vector<double>({1.0, 2.0, 3.0}),
+            schema.GetProp("param8")->GetDouble()->GetOneOfValues());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Inheritance) {
+  const char* base_schema_str = "{"
+  "'param0' :{'minimum':1, 'maximum':5},"
+  "'param1' :{'minimum':1, 'maximum':5},"
+  "'param2' :{'minimum':1, 'maximum':5},"
+  "'param3' :{'minimum':1, 'maximum':5},"
+  "'param4' :{'minimum':1, 'maximum':5},"
+  "'param5' :{'minimum':1.1, 'maximum':5.5},"
+  "'param6' :{'minimum':1.1, 'maximum':5.5},"
+  "'param7' :{'minimum':1.1, 'maximum':5.5},"
+  "'param8' :{'minimum':1.1, 'maximum':5.5},"
+  "'param9' :{'minLength':1, 'maxLength':5},"
+  "'param10':{'minLength':1, 'maxLength':5},"
+  "'param11':{'minLength':1, 'maxLength':5},"
+  "'param12':{'minLength':1, 'maxLength':5},"
+  "'param13':[1,2,3],"
+  "'param14':[1,2,3],"
+  "'param15':[1.1,2.2,3.3],"
+  "'param16':[1.1,2.2,3.3],"
+  "'param17':['id1', 'id2'],"
+  "'param18':['id1', 'id2'],"
+  "'param19':{'minimum':1, 'maximum':5}"
+  "}";
+  buffet::ObjectSchema base_schema;
+  EXPECT_TRUE(base_schema.FromJson(CreateDictionaryValue(base_schema_str).get(),
+                                   nullptr, nullptr));
+  const char* schema_str = "{"
+  "'param1' :{},"
+  "'param2' :{'minimum':2},"
+  "'param3' :{'maximum':9},"
+  "'param4' :{'minimum':2, 'maximum':9},"
+  "'param5' :{},"
+  "'param6' :{'minimum':2.2},"
+  "'param7' :{'maximum':9.9},"
+  "'param8' :{'minimum':2.2, 'maximum':9.9},"
+  "'param9' :{},"
+  "'param10':{'minLength':3},"
+  "'param11':{'maxLength':8},"
+  "'param12':{'minLength':3, 'maxLength':8},"
+  "'param13':{},"
+  "'param14':[1,2,3,4],"
+  "'param15':{},"
+  "'param16':[1.1,2.2,3.3,4.4],"
+  "'param17':{},"
+  "'param18':['id1', 'id3'],"
+  "'param19':{}"
+  "}";
+  buffet::ObjectSchema schema;
+  EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(),
+                              &base_schema, nullptr));
+  EXPECT_EQ(nullptr, schema.GetProp("param0"));
+  EXPECT_NE(nullptr, schema.GetProp("param1"));
+  EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+  EXPECT_EQ(1, schema.GetProp("param1")->GetInt()->GetMinValue());
+  EXPECT_EQ(5, schema.GetProp("param1")->GetInt()->GetMaxValue());
+  EXPECT_EQ("integer", schema.GetProp("param2")->GetTypeAsString());
+  EXPECT_EQ(2, schema.GetProp("param2")->GetInt()->GetMinValue());
+  EXPECT_EQ(5, schema.GetProp("param2")->GetInt()->GetMaxValue());
+  EXPECT_EQ("integer", schema.GetProp("param3")->GetTypeAsString());
+  EXPECT_EQ(1, schema.GetProp("param3")->GetInt()->GetMinValue());
+  EXPECT_EQ(9, schema.GetProp("param3")->GetInt()->GetMaxValue());
+  EXPECT_EQ("integer", schema.GetProp("param4")->GetTypeAsString());
+  EXPECT_EQ(2, schema.GetProp("param4")->GetInt()->GetMinValue());
+  EXPECT_EQ(9, schema.GetProp("param4")->GetInt()->GetMaxValue());
+  EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString());
+  EXPECT_EQ(1.1, schema.GetProp("param5")->GetDouble()->GetMinValue());
+  EXPECT_EQ(5.5, schema.GetProp("param5")->GetDouble()->GetMaxValue());
+  EXPECT_EQ("number", schema.GetProp("param6")->GetTypeAsString());
+  EXPECT_EQ(2.2, schema.GetProp("param6")->GetDouble()->GetMinValue());
+  EXPECT_EQ(5.5, schema.GetProp("param6")->GetDouble()->GetMaxValue());
+  EXPECT_EQ("number", schema.GetProp("param7")->GetTypeAsString());
+  EXPECT_EQ(1.1, schema.GetProp("param7")->GetDouble()->GetMinValue());
+  EXPECT_EQ(9.9, schema.GetProp("param7")->GetDouble()->GetMaxValue());
+  EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString());
+  EXPECT_EQ(2.2, schema.GetProp("param8")->GetDouble()->GetMinValue());
+  EXPECT_EQ(9.9, schema.GetProp("param8")->GetDouble()->GetMaxValue());
+  EXPECT_EQ("string", schema.GetProp("param9")->GetTypeAsString());
+  EXPECT_EQ(1, schema.GetProp("param9")->GetString()->GetMinLength());
+  EXPECT_EQ(5, schema.GetProp("param9")->GetString()->GetMaxLength());
+  EXPECT_EQ("string", schema.GetProp("param10")->GetTypeAsString());
+  EXPECT_EQ(3, schema.GetProp("param10")->GetString()->GetMinLength());
+  EXPECT_EQ(5, schema.GetProp("param10")->GetString()->GetMaxLength());
+  EXPECT_EQ("string", schema.GetProp("param11")->GetTypeAsString());
+  EXPECT_EQ(1, schema.GetProp("param11")->GetString()->GetMinLength());
+  EXPECT_EQ(8, schema.GetProp("param11")->GetString()->GetMaxLength());
+  EXPECT_EQ("string", schema.GetProp("param12")->GetTypeAsString());
+  EXPECT_EQ(3, schema.GetProp("param12")->GetString()->GetMinLength());
+  EXPECT_EQ(8, schema.GetProp("param12")->GetString()->GetMaxLength());
+  EXPECT_EQ("integer", schema.GetProp("param13")->GetTypeAsString());
+  EXPECT_EQ(std::vector<int>({1, 2, 3}),
+            schema.GetProp("param13")->GetInt()->GetOneOfValues());
+  EXPECT_EQ("integer", schema.GetProp("param14")->GetTypeAsString());
+  EXPECT_EQ(std::vector<int>({1, 2, 3, 4}),
+            schema.GetProp("param14")->GetInt()->GetOneOfValues());
+  EXPECT_EQ("number", schema.GetProp("param15")->GetTypeAsString());
+  EXPECT_EQ(std::vector<double>({1.1, 2.2, 3.3}),
+            schema.GetProp("param15")->GetDouble()->GetOneOfValues());
+  EXPECT_EQ("number", schema.GetProp("param16")->GetTypeAsString());
+  EXPECT_EQ(std::vector<double>({1.1, 2.2, 3.3, 4.4}),
+            schema.GetProp("param16")->GetDouble()->GetOneOfValues());
+  EXPECT_EQ("string", schema.GetProp("param17")->GetTypeAsString());
+  EXPECT_EQ(std::vector<std::string>({"id1", "id2"}),
+            schema.GetProp("param17")->GetString()->GetOneOfValues());
+  EXPECT_EQ("string", schema.GetProp("param18")->GetTypeAsString());
+  EXPECT_EQ(std::vector<std::string>({"id1", "id3"}),
+            schema.GetProp("param18")->GetString()->GetOneOfValues());
+  EXPECT_EQ("integer", schema.GetProp("param19")->GetTypeAsString());
+  EXPECT_EQ(1, schema.GetProp("param19")->GetInt()->GetMinValue());
+  EXPECT_EQ(5, schema.GetProp("param19")->GetInt()->GetMaxValue());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_BaseSchema_Failures) {
+  buffet::ObjectSchema schema;
+  buffet::ErrorPtr error;
+  const char* schema_str = "{"
+  "'param1':{}"
+  "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str = "{"
+  "'param1':{'type':'foo'}"
+  "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("unknown_type", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str = "{"
+  "'param1':[]"
+  "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str = "{"
+  "'param1':{'minimum':'foo'}"
+  "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str = "{"
+  "'param1':[1,2.2]"
+  "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str = "{"
+  "'param1':{'minimum':1, 'enum':[1,2,3]}"  // can't have min/max & enum.
+  "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("unexpected_parameter", error->GetFirstError()->GetCode());
+  error.reset();
+
+  schema_str = "{"
+  "'param1':{'maximum':1, 'blah':2}"  // 'blah' is unexpected.
+  "}";
+  EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+                               &error));
+  EXPECT_EQ("unexpected_parameter", error->GetFirstError()->GetCode());
+  error.reset();
+}
+
diff --git a/buffet/commands/prop_constraints.cc b/buffet/commands/prop_constraints.cc
new file mode 100644
index 0000000..5ea9314
--- /dev/null
+++ b/buffet/commands/prop_constraints.cc
@@ -0,0 +1,128 @@
+// 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/commands/prop_constraints.h"
+#include "buffet/commands/prop_values.h"
+#include "buffet/commands/schema_constants.h"
+#include "buffet/string_utils.h"
+
+namespace buffet {
+
+// Constraint ----------------------------------------------------------------
+Constraint::~Constraint() {}
+
+bool Constraint::ReportErrorLessThan(
+    ErrorPtr* error, const std::string& val, const std::string& limit) {
+  Error::AddToPrintf(error, commands::errors::kDomain,
+                     commands::errors::kOutOfRange,
+                     "Value %s is out of range. It must not be less than %s",
+                     val.c_str(), limit.c_str());
+  return false;
+}
+
+bool Constraint::ReportErrorGreaterThan(
+    ErrorPtr* error, const std::string& val, const std::string& limit) {
+  Error::AddToPrintf(error, commands::errors::kDomain,
+                     commands::errors::kOutOfRange,
+                     "Value %s is out of range. It must not be greater than %s",
+                     val.c_str(), limit.c_str());
+  return false;
+}
+
+bool Constraint::ReportErrorNotOneOf(
+    ErrorPtr* error, const std::string& val,
+    const std::vector<std::string>& values) {
+  Error::AddToPrintf(error, commands::errors::kDomain,
+                     commands::errors::kOutOfRange,
+                     "Value %s is invalid. Expected one of [%s]",
+                     val.c_str(), string_utils::Join(',', values).c_str());
+  return false;
+}
+
+bool Constraint::AddToJsonDict(base::DictionaryValue* dict,
+                               bool overridden_only,
+                               ErrorPtr* error) const {
+  if (!overridden_only || HasOverriddenAttributes()) {
+    auto value = ToJson(error);
+    if (!value)
+      return false;
+    dict->SetWithoutPathExpansion(GetDictKey(), value.release());
+  }
+  return true;
+}
+
+// ConstraintStringLength -----------------------------------------------------
+ConstraintStringLength::ConstraintStringLength(
+    const InheritableAttribute<int>& limit) : limit_(limit) {}
+ConstraintStringLength::ConstraintStringLength(int limit) : limit_(limit) {}
+
+bool ConstraintStringLength::HasOverriddenAttributes() const {
+  return !limit_.is_inherited;
+}
+
+std::unique_ptr<base::Value> ConstraintStringLength::ToJson(
+    ErrorPtr* error) const {
+  return TypedValueToJson(limit_.value, error);
+}
+
+// ConstraintStringLengthMin --------------------------------------------------
+ConstraintStringLengthMin::ConstraintStringLengthMin(
+    const InheritableAttribute<int>& limit) : ConstraintStringLength(limit) {}
+ConstraintStringLengthMin::ConstraintStringLengthMin(int limit)
+    : ConstraintStringLength(limit) {}
+
+bool ConstraintStringLengthMin::Validate(const PropValue& value,
+                                         ErrorPtr* error) const {
+  CHECK(value.GetString()) << "Expecting a string value for this constraint";
+  const std::string& str = value.GetString()->GetValue();
+  int length = static_cast<int>(str.size());
+  if (length < limit_.value) {
+    if (limit_.value == 1) {
+      Error::AddTo(error, commands::errors::kDomain,
+                   commands::errors::kOutOfRange, "String must not be empty");
+    } else {
+      Error::AddToPrintf(error, commands::errors::kDomain,
+                         commands::errors::kOutOfRange,
+                         "String must be at least %d characters long, "
+                         "actual length of string '%s' is %d", limit_.value,
+                         str.c_str(), length);
+    }
+    return false;
+  }
+  return true;
+}
+
+std::shared_ptr<Constraint>
+    ConstraintStringLengthMin::CloneAsInherited() const {
+  return std::make_shared<ConstraintStringLengthMin>(limit_.value);
+}
+
+// ConstraintStringLengthMax --------------------------------------------------
+ConstraintStringLengthMax::ConstraintStringLengthMax(
+    const InheritableAttribute<int>& limit) : ConstraintStringLength(limit) {}
+ConstraintStringLengthMax::ConstraintStringLengthMax(int limit)
+    : ConstraintStringLength(limit) {}
+
+bool ConstraintStringLengthMax::Validate(const PropValue& value,
+                                         ErrorPtr* error) const {
+  CHECK(value.GetString()) << "Expecting a string value for this constraint";
+  const std::string& str = value.GetString()->GetValue();
+  int length = static_cast<int>(str.size());
+  if (length > limit_.value) {
+    Error::AddToPrintf(error, commands::errors::kDomain,
+                       commands::errors::kOutOfRange,
+                       "String must be no more than %d character(s) long, "
+                       "actual length of string '%s' is %d", limit_.value,
+                       str.c_str(), length);
+    return false;
+  }
+  return true;
+}
+
+std::shared_ptr<Constraint>
+    ConstraintStringLengthMax::CloneAsInherited() const {
+  return std::make_shared<ConstraintStringLengthMax>(limit_.value);
+}
+
+}  // namespace buffet
diff --git a/buffet/commands/prop_constraints.h b/buffet/commands/prop_constraints.h
new file mode 100644
index 0000000..da43e86
--- /dev/null
+++ b/buffet/commands/prop_constraints.h
@@ -0,0 +1,317 @@
+// 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_COMMANDS_PROP_CONSTRAINTS_H_
+#define BUFFET_COMMANDS_PROP_CONSTRAINTS_H_
+
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <base/basictypes.h>
+#include <base/values.h>
+
+#include "buffet/commands/prop_values.h"
+#include "buffet/commands/schema_constants.h"
+#include "buffet/commands/schema_utils.h"
+#include "buffet/error.h"
+#include "buffet/string_utils.h"
+
+namespace buffet {
+
+enum class ConstraintType {
+  Min,
+  Max,
+  StringLengthMin,
+  StringLengthMax,
+  OneOf
+};
+
+// Abstract base class for all parameter constraints. Many constraints are
+// type-dependent. Thus, a numeric parameter could have "minimum" and/or
+// "maximum" constraints specified. Some constraints, such as "OneOf" apply to
+// any data type.
+class Constraint {
+ public:
+  Constraint() = default;
+  virtual ~Constraint();
+
+  // Gets the constraint type.
+  virtual ConstraintType GetType() const = 0;
+  // Checks if any of the constraint properties/attributes are overridden
+  // from their base schema definition. If the constraint is inherited, then
+  // it will not be written to JSON when saving partial schema.
+  virtual bool HasOverriddenAttributes() const = 0;
+  // Validates a parameter against the constraint. Returns true if parameter
+  // value satisfies the constraint, otherwise fills the optional |error| with
+  // the details for the failure.
+  virtual bool Validate(const PropValue& value, ErrorPtr* error) const = 0;
+  // Makes a copy of the constraint object, marking all the attributes
+  // as inherited from the original definition.
+  virtual std::shared_ptr<Constraint> CloneAsInherited() const = 0;
+  // Saves the constraint into the specified JSON |dict| object, representing
+  // the object schema. If |overridden_only| is set to true, then the
+  // inherited constraints will not be added to the schema object.
+  virtual bool AddToJsonDict(base::DictionaryValue* dict, bool overridden_only,
+                             ErrorPtr* error) const;
+  // Saves the value of constraint to JSON value. E.g., if the numeric
+  // constraint was defined as {"minimum":20} this will create a JSON value
+  // of 20. The current design implies that each constraint has one value
+  // only. If this assumption changes, this interface needs to be updated
+  // accordingly.
+  virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const = 0;
+  // Overloaded by the concrete class implementation, it should return the
+  // JSON object property name to store the constraint's value as.
+  // E.g., if the numeric constraint was defined as {"minimum":20} this
+  // method should return "minimum".
+  virtual const char* GetDictKey() const = 0;
+
+ protected:
+  // Static helper methods to format common constraint validation errors.
+  // They fill the |error| object with specific error message.
+  // Since these functions could be used by constraint objects for various
+  // data types, the values used in validation are expected to be
+  // send as strings already.
+  static bool ReportErrorLessThan(ErrorPtr* error, const std::string& val,
+                                  const std::string& limit);
+  static bool ReportErrorGreaterThan(ErrorPtr* error, const std::string& val,
+                                     const std::string& limit);
+
+  static bool ReportErrorNotOneOf(ErrorPtr* error, const std::string& val,
+                                  const std::vector<std::string>& values);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Constraint);
+};
+
+// ConstraintMinMaxBase is a base class for numeric Minimum and Maximum
+// constraints.
+template<typename T>
+class ConstraintMinMaxBase : public Constraint {
+ public:
+  explicit ConstraintMinMaxBase(const InheritableAttribute<T>& limit)
+      : limit_(limit) {}
+  explicit ConstraintMinMaxBase(const T& limit)
+      : limit_(limit) {}
+
+  // Implementation of Constraint::HasOverriddenAttributes().
+  virtual bool HasOverriddenAttributes() const override {
+    return !limit_.is_inherited;
+  }
+
+  // Implementation of Constraint::ToJson().
+  virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override {
+    return TypedValueToJson(limit_.value, error);
+  }
+
+  // Stores the upper/lower value limit for maximum/minimum constraint.
+  // |limit_.is_inherited| indicates whether the constraint is inherited
+  // from base schema or overridden.
+  InheritableAttribute<T> limit_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintMinMaxBase);
+};
+
+// Implementation of Minimum value constraint for Integer/Double types.
+template<typename T>
+class ConstraintMin : public ConstraintMinMaxBase<T> {
+ public:
+  explicit ConstraintMin(const InheritableAttribute<T>& limit)
+      : ConstraintMinMaxBase<T>(limit) {}
+  explicit ConstraintMin(const T& limit)
+      : ConstraintMinMaxBase<T>(limit) {}
+
+  // Implementation of Constraint::GetType().
+  virtual ConstraintType GetType() const { return ConstraintType::Min; }
+
+  // Implementation of Constraint::Validate().
+  virtual bool Validate(const PropValue& value,
+                        ErrorPtr* error) const override {
+    T v = value.GetValueAsAny().Get<T>();
+    if (v < this->limit_.value)
+      return this->ReportErrorLessThan(
+          error, string_utils::ToString(v),
+          string_utils::ToString(this->limit_.value));
+    return true;
+  }
+
+  // Implementation of Constraint::CloneAsInherited().
+  virtual std::shared_ptr<Constraint> CloneAsInherited() const override {
+    return std::make_shared<ConstraintMin>(this->limit_.value);
+  }
+
+  // Implementation of Constraint::GetDictKey().
+  virtual const char* GetDictKey() const override {
+    return commands::attributes::kNumeric_Min;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintMin);
+};
+
+// Implementation of Maximum value constraint for Integer/Double types.
+template<typename T>
+class ConstraintMax : public ConstraintMinMaxBase<T> {
+ public:
+  explicit ConstraintMax(const InheritableAttribute<T>& limit)
+      : ConstraintMinMaxBase<T>(limit) {}
+  explicit ConstraintMax(const T& limit)
+      : ConstraintMinMaxBase<T>(limit) {}
+
+  // Implementation of Constraint::GetType().
+  virtual ConstraintType GetType() const { return ConstraintType::Max; }
+
+  // Implementation of Constraint::Validate().
+  virtual bool Validate(const PropValue& value,
+                        ErrorPtr* error) const override {
+    T v = value.GetValueAsAny().Get<T>();
+    if (v > this->limit_.value)
+      return this->ReportErrorGreaterThan(
+          error, string_utils::ToString(v),
+          string_utils::ToString(this->limit_.value));
+    return true;
+  }
+
+  // Implementation of Constraint::CloneAsInherited().
+  virtual std::shared_ptr<Constraint> CloneAsInherited() const override {
+    return std::make_shared<ConstraintMax>(this->limit_.value);
+  }
+
+  // Implementation of Constraint::GetDictKey().
+  virtual const char* GetDictKey() const override {
+    return commands::attributes::kNumeric_Max;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintMax);
+};
+
+// ConstraintStringLength is a base class for Minimum/Maximum string length
+// constraints, similar to ConstraintMinMaxBase of numeric types.
+class ConstraintStringLength : public Constraint {
+ public:
+  explicit ConstraintStringLength(const InheritableAttribute<int>& limit);
+  explicit ConstraintStringLength(int limit);
+
+  // Implementation of Constraint::HasOverriddenAttributes().
+  virtual bool HasOverriddenAttributes() const override;
+  // Implementation of Constraint::ToJson().
+  virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override;
+
+  // Stores the upper/lower value limit for string length constraint.
+  // |limit_.is_inherited| indicates whether the constraint is inherited
+  // from base schema or overridden.
+  InheritableAttribute<int> limit_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintStringLength);
+};
+
+// Implementation of Minimum string length constraint.
+class ConstraintStringLengthMin : public ConstraintStringLength {
+ public:
+  explicit ConstraintStringLengthMin(const InheritableAttribute<int>& limit);
+  explicit ConstraintStringLengthMin(int limit);
+  // Implementation of Constraint::GetType().
+  virtual ConstraintType GetType() const override {
+    return ConstraintType::StringLengthMin;
+  }
+  // Implementation of Constraint::Validate().
+  virtual bool Validate(const PropValue& value, ErrorPtr* error) const override;
+  // Implementation of Constraint::CloneAsInherited().
+  virtual std::shared_ptr<Constraint> CloneAsInherited() const override;
+  // Implementation of Constraint::GetDictKey().
+  virtual const char* GetDictKey() const override {
+    return commands::attributes::kString_MinLength;
+  }
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintStringLengthMin);
+};
+
+// Implementation of Maximum string length constraint.
+class ConstraintStringLengthMax : public ConstraintStringLength {
+ public:
+  explicit ConstraintStringLengthMax(const InheritableAttribute<int>& limit);
+  explicit ConstraintStringLengthMax(int limit);
+  // Implementation of Constraint::GetType().
+  virtual ConstraintType GetType() const override {
+    return ConstraintType::StringLengthMax;
+  }
+  // Implementation of Constraint::Validate().
+  virtual bool Validate(const PropValue& value, ErrorPtr* error) const override;
+  // Implementation of Constraint::CloneAsInherited().
+  virtual std::shared_ptr<Constraint> CloneAsInherited() const override;
+  // Implementation of Constraint::GetDictKey().
+  virtual const char* GetDictKey() const override {
+    return commands::attributes::kString_MaxLength;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintStringLengthMax);
+};
+
+// Implementation of OneOf constraint for different data types.
+template<typename T>
+class ConstraintOneOf : public Constraint {
+ public:
+  explicit ConstraintOneOf(const InheritableAttribute<std::vector<T>>& set)
+      : set_(set) {}
+  explicit ConstraintOneOf(const std::vector<T>& set)
+      : set_(set) {}
+
+  // Implementation of Constraint::GetType().
+  virtual ConstraintType GetType() const override {
+    return ConstraintType::OneOf;
+  }
+
+  // Implementation of Constraint::HasOverriddenAttributes().
+  virtual bool HasOverriddenAttributes() const override {
+    return !set_.is_inherited;
+  }
+
+  // Implementation of Constraint::Validate().
+  virtual bool Validate(const PropValue& value,
+                        ErrorPtr* error) const override {
+    using string_utils::ToString;
+    T v = value.GetValueAsAny().Get<T>();
+    for (const auto& item : set_.value) {
+      if (CompareValue(v, item))
+        return true;
+    }
+    std::vector<std::string> values;
+    values.reserve(set_.value.size());
+    for (const auto& item : set_.value) {
+      values.push_back(ToString(item));
+    }
+    return ReportErrorNotOneOf(error, ToString(v), values);
+  }
+
+  // Implementation of Constraint::CloneAsInherited().
+  virtual std::shared_ptr<Constraint> CloneAsInherited() const override {
+    return std::make_shared<ConstraintOneOf>(set_.value);
+  }
+
+  // Implementation of Constraint::ToJson().
+  virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override {
+    return TypedValueToJson(set_.value, error);
+  }
+
+  // Implementation of Constraint::GetDictKey().
+  virtual const char* GetDictKey() const override {
+    return commands::attributes::kOneOf_Enum;
+  }
+
+  // Stores the list of acceptable values for the parameter.
+  // |set_.is_inherited| indicates whether the constraint is inherited
+  // from base schema or overridden.
+  InheritableAttribute<std::vector<T>> set_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstraintOneOf);
+};
+
+}  // namespace buffet
+
+#endif  // BUFFET_COMMANDS_PROP_CONSTRAINTS_H_
diff --git a/buffet/commands/prop_types.cc b/buffet/commands/prop_types.cc
new file mode 100644
index 0000000..9644eac
--- /dev/null
+++ b/buffet/commands/prop_types.cc
@@ -0,0 +1,432 @@
+// 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/commands/prop_types.h"
+
+#include <algorithm>
+#include <limits>
+#include <set>
+
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+#include <base/values.h>
+
+#include "buffet/commands/prop_values.h"
+#include "buffet/commands/object_schema.h"
+#include "buffet/commands/schema_constants.h"
+#include "buffet/string_utils.h"
+
+namespace buffet {
+
+// PropType -------------------------------------------------------------------
+PropType::PropType() {
+}
+
+PropType::~PropType() {
+}
+
+std::string PropType::GetTypeAsString() const {
+  return GetTypeStringFromType(GetType());
+}
+
+bool PropType::HasOverriddenAttributes() const {
+  for (const auto& pair : constraints_) {
+    if (pair.second->HasOverriddenAttributes())
+      return true;
+  }
+  return false;
+}
+
+std::unique_ptr<base::Value> PropType::ToJson(bool full_schema,
+                                              ErrorPtr* error) const {
+  if (!full_schema && !HasOverriddenAttributes()) {
+    if (based_on_schema_)
+      return std::unique_ptr<base::Value>(new base::DictionaryValue);
+    return TypedValueToJson(GetTypeAsString(), error);
+  }
+
+  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+  if (full_schema) {
+    // If we are asked for full_schema definition, then we need to output every
+    // parameter property, including the "type", and any constraints.
+    // So, we write the "type" only if asked for full schema.
+    // Otherwise we will be able to infer the parameter type based on
+    // the constraints and their types.
+    // That is, the following JSONs could possibly define a parameter:
+    // {'type':'integer'} -> explicit "integer" with no constraints
+    // {'minimum':10} -> no type specified, but since we have "minimum"
+    //                   and 10 is integer, than this is an integer
+    //                   parameter with min constraint.
+    // {'enum':[1,2,3]} -> integer with OneOf constraint.
+    // And so is this: [1,2,3] -> an array of ints assume it's an "int" enum.
+    dict->SetString(commands::attributes::kType, GetTypeAsString());
+  }
+
+  if (!full_schema && constraints_.size() == 1) {
+    // If we are not asked for full schema, and we have only one constraint
+    // which is OneOf, we short-circuit the whole thing and return just
+    // the array [1,2,3] instead of an object with "enum" property like:
+    // {'enum':[1,2,3]}
+    auto p = constraints_.find(ConstraintType::OneOf);
+    if (p != constraints_.end()) {
+      return p->second->ToJson(error);
+    }
+  }
+
+  for (const auto& pair : constraints_) {
+    if (!pair.second->AddToJsonDict(dict.get(), !full_schema, error))
+      return std::unique_ptr<base::Value>();
+  }
+  return std::unique_ptr<base::Value>(dict.release());
+}
+
+bool PropType::FromJson(const base::DictionaryValue* value,
+                        const PropType* base_schema, ErrorPtr* error) {
+  if (base_schema && base_schema->GetType() != GetType()) {
+    Error::AddToPrintf(error, commands::errors::kDomain,
+                       commands::errors::kPropTypeChanged,
+                       "Redefining a command of type %s as %s",
+                       base_schema->GetTypeAsString().c_str(),
+                       GetTypeAsString().c_str());
+    return false;
+  }
+  based_on_schema_ = (base_schema != nullptr);
+  constraints_.clear();
+  std::set<std::string> processed_keys{commands::attributes::kType};
+  if (!ObjectSchemaFromJson(value, base_schema, &processed_keys, error))
+    return false;
+  if (base_schema) {
+    for (const auto& pair : base_schema->GetConstraints()) {
+      std::shared_ptr<Constraint> inherited(pair.second->CloneAsInherited());
+      constraints_.insert(std::make_pair(pair.first, inherited));
+    }
+  }
+  if (!ConstraintsFromJson(value, &processed_keys, error))
+    return false;
+
+  // Now make sure there are no unexpected/unknown keys in the property schema
+  // definition object.
+  base::DictionaryValue::Iterator iter(*value);
+  while (!iter.IsAtEnd()) {
+    std::string key = iter.key();
+    if (processed_keys.find(key) == processed_keys.end()) {
+      Error::AddToPrintf(error, commands::errors::kDomain,
+                         commands::errors::kUnknownProperty,
+                         "Unexpected property '%s'", key.c_str());
+      return false;
+    }
+    iter.Advance();
+  }
+
+  return true;
+}
+
+void PropType::AddConstraint(std::shared_ptr<Constraint> constraint) {
+  constraints_[constraint->GetType()] = constraint;
+}
+
+void PropType::RemoveConstraint(ConstraintType constraint_type) {
+  constraints_.erase(constraint_type);
+}
+
+const Constraint* PropType::GetConstraint(
+    ConstraintType constraint_type) const {
+  auto p = constraints_.find(constraint_type);
+  return p != constraints_.end() ? p->second.get() : nullptr;
+}
+
+Constraint* PropType::GetConstraint(ConstraintType constraint_type) {
+  auto p = constraints_.find(constraint_type);
+  return p != constraints_.end() ? p->second.get() : nullptr;
+}
+
+bool PropType::ValidateValue(const base::Value* value, ErrorPtr* error) const {
+  std::shared_ptr<PropValue> val = CreateValue();
+  CHECK(val) << "Failed to create value object";
+  return val->FromJson(value, error) && ValidateConstraints(*val, error);
+}
+
+bool PropType::ValidateConstraints(const PropValue& value,
+                                   ErrorPtr* error) const {
+  for (const auto& pair : constraints_) {
+    if (!pair.second->Validate(value, error))
+      return false;
+  }
+  return true;
+}
+
+const PropType::TypeMap& PropType::GetTypeMap() {
+  static TypeMap map = {
+    {ValueType::Int,         "integer"},
+    {ValueType::Double,      "number"},
+    {ValueType::String,      "string"},
+    {ValueType::Boolean,     "boolean"},
+    {ValueType::Object,      "object"},
+  };
+  return map;
+}
+
+std::string PropType::GetTypeStringFromType(ValueType type) {
+  for (const auto& pair : GetTypeMap()) {
+    if (pair.first == type)
+      return pair.second;
+  }
+  LOG(FATAL) << "Type map is missing a type";
+  return std::string();
+}
+
+bool PropType::GetTypeFromTypeString(const std::string& name, ValueType* type) {
+  for (const auto& pair : GetTypeMap()) {
+    if (pair.second == name) {
+      *type = pair.first;
+      return true;
+    }
+  }
+  return false;
+}
+
+std::unique_ptr<PropType> PropType::Create(ValueType type) {
+  PropType* prop = nullptr;
+  switch (type) {
+  case buffet::ValueType::Int:
+    prop = new IntPropType;
+    break;
+  case buffet::ValueType::Double:
+    prop = new DoublePropType;
+    break;
+  case buffet::ValueType::String:
+    prop = new StringPropType;
+    break;
+  case buffet::ValueType::Boolean:
+    prop = new BooleanPropType;
+    break;
+  case buffet::ValueType::Object:
+    prop = new ObjectPropType;
+    break;
+  }
+  return std::unique_ptr<PropType>(prop);
+}
+
+template<typename T>
+static std::shared_ptr<Constraint> LoadOneOfConstraint(
+    const base::DictionaryValue* value, const ObjectSchema* object_schema,
+    ErrorPtr* error) {
+  const base::ListValue* list = nullptr;
+  if (!value->GetListWithoutPathExpansion(commands::attributes::kOneOf_Enum,
+                                          &list)) {
+    Error::AddTo(error, commands::errors::kDomain,
+                 commands::errors::kTypeMismatch, "Expecting an array");
+    return std::shared_ptr<Constraint>();
+  }
+  std::vector<T> set;
+  set.reserve(list->GetSize());
+  for (const base::Value* item : *list) {
+    T val{};
+    if (!TypedValueFromJson(item, object_schema, &val, error))
+      return std::shared_ptr<Constraint>();
+    set.push_back(val);
+  }
+  InheritableAttribute<std::vector<T>> val(set, false);
+  return std::make_shared<ConstraintOneOf<T>>(val);
+}
+
+template<class ConstraintClass, typename T>
+static std::shared_ptr<Constraint> LoadMinMaxConstraint(
+    const char* dict_key, const base::DictionaryValue* value,
+    const ObjectSchema* object_schema, ErrorPtr* error) {
+  InheritableAttribute<T> limit;
+
+  const base::Value* src_val = nullptr;
+  CHECK(value->Get(dict_key, &src_val)) << "Unable to get min/max constraints";
+  if (!TypedValueFromJson(src_val, object_schema, &limit.value, error))
+    return std::shared_ptr<Constraint>();
+  limit.is_inherited = false;
+
+  return std::make_shared<ConstraintClass>(limit);
+}
+
+// PropTypeBase ----------------------------------------------------------------
+
+template<class Derived, class Value, typename T>
+bool PropTypeBase<Derived, Value, T>::ConstraintsFromJson(
+    const base::DictionaryValue* value, std::set<std::string>* processed_keys,
+    ErrorPtr* error) {
+  if (!PropType::ConstraintsFromJson(value, processed_keys, error))
+    return false;
+
+  if (value->HasKey(commands::attributes::kOneOf_Enum)) {
+    auto constraint = LoadOneOfConstraint<T>(value, this->GetObjectSchemaPtr(),
+                                             error);
+    if (!constraint)
+      return false;
+    this->AddConstraint(constraint);
+    this->RemoveConstraint(ConstraintType::Min);
+    this->RemoveConstraint(ConstraintType::Max);
+    processed_keys->insert(commands::attributes::kOneOf_Enum);
+  }
+
+  return true;
+}
+
+// NumericPropTypeBase ---------------------------------------------------------
+
+template<class Derived, class Value, typename T>
+bool NumericPropTypeBase<Derived, Value, T>::ConstraintsFromJson(
+    const base::DictionaryValue* value, std::set<std::string>* processed_keys,
+    ErrorPtr* error) {
+  if (!_Base::ConstraintsFromJson(value, processed_keys, error))
+    return false;
+
+  if (processed_keys->find(commands::attributes::kOneOf_Enum) ==
+      processed_keys->end()) {
+    // Process min/max constraints only if "enum" constraint wasn't already
+    // specified.
+    if (value->HasKey(commands::attributes::kNumeric_Min)) {
+      auto constraint = LoadMinMaxConstraint<ConstraintMin<T>, T>(
+          commands::attributes::kNumeric_Min, value, this->GetObjectSchemaPtr(),
+          error);
+      if (!constraint)
+        return false;
+      this->AddConstraint(constraint);
+      this->RemoveConstraint(ConstraintType::OneOf);
+      processed_keys->insert(commands::attributes::kNumeric_Min);
+    }
+    if (value->HasKey(commands::attributes::kNumeric_Max)) {
+      auto constraint = LoadMinMaxConstraint<ConstraintMax<T>, T>(
+          commands::attributes::kNumeric_Max, value, this->GetObjectSchemaPtr(),
+          error);
+      if (!constraint)
+        return false;
+      this->AddConstraint(constraint);
+      this->RemoveConstraint(ConstraintType::OneOf);
+      processed_keys->insert(commands::attributes::kNumeric_Max);
+    }
+  }
+
+  return true;
+}
+
+// StringPropType -------------------------------------------------------------
+
+bool StringPropType::ConstraintsFromJson(
+    const base::DictionaryValue* value, std::set<std::string>* processed_keys,
+    ErrorPtr* error) {
+  if (!_Base::ConstraintsFromJson(value, processed_keys, error))
+    return false;
+
+  if (processed_keys->find(commands::attributes::kOneOf_Enum) ==
+      processed_keys->end()) {
+    // Process min/max constraints only if "enum" constraint wasn't already
+    // specified.
+    if (value->HasKey(commands::attributes::kString_MinLength)) {
+      auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMin, int>(
+          commands::attributes::kString_MinLength, value, GetObjectSchemaPtr(),
+          error);
+      if (!constraint)
+        return false;
+      AddConstraint(constraint);
+      RemoveConstraint(ConstraintType::OneOf);
+      processed_keys->insert(commands::attributes::kString_MinLength);
+    }
+    if (value->HasKey(commands::attributes::kString_MaxLength)) {
+      auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMax, int>(
+          commands::attributes::kString_MaxLength, value, GetObjectSchemaPtr(),
+          error);
+      if (!constraint)
+        return false;
+      AddConstraint(constraint);
+      RemoveConstraint(ConstraintType::OneOf);
+      processed_keys->insert(commands::attributes::kString_MaxLength);
+    }
+  }
+  return true;
+}
+
+void StringPropType::AddLengthConstraint(int min_len, int max_len) {
+  InheritableAttribute<int> min_attr(min_len, false);
+  InheritableAttribute<int> max_attr(max_len, false);
+  AddConstraint(std::make_shared<ConstraintStringLengthMin>(min_attr));
+  AddConstraint(std::make_shared<ConstraintStringLengthMax>(max_attr));
+}
+
+int StringPropType::GetMinLength() const {
+  auto slc = static_cast<const ConstraintStringLength*>(
+      GetConstraint(ConstraintType::StringLengthMin));
+  return slc ? slc->limit_.value : 0;
+}
+
+int StringPropType::GetMaxLength() const {
+  auto slc = static_cast<const ConstraintStringLength*>(
+      GetConstraint(ConstraintType::StringLengthMax));
+  return slc ? slc->limit_.value : std::numeric_limits<int>::max();
+}
+
+// ObjectPropType -------------------------------------------------------------
+
+ObjectPropType::ObjectPropType()
+    : object_schema_(std::make_shared<ObjectSchema>(), false) {}
+
+bool ObjectPropType::HasOverriddenAttributes() const {
+  return PropType::HasOverriddenAttributes() ||
+         !object_schema_.is_inherited;
+}
+
+std::unique_ptr<base::Value> ObjectPropType::ToJson(bool full_schema,
+                                                    ErrorPtr* error) const {
+  std::unique_ptr<base::Value> value = PropType::ToJson(full_schema, error);
+  if (value) {
+    base::DictionaryValue* dict = nullptr;
+    CHECK(value->GetAsDictionary(&dict)) << "Expecting a JSON object";
+    if (!object_schema_.is_inherited || full_schema) {
+      auto object_schema = object_schema_.value->ToJson(full_schema, error);
+      if (!object_schema) {
+        value.reset();
+        return value;
+      }
+      dict->SetWithoutPathExpansion(commands::attributes::kObject_Properties,
+                                    object_schema.release());
+    }
+  }
+  return value;
+}
+
+bool ObjectPropType::ObjectSchemaFromJson(
+    const base::DictionaryValue* value, const PropType* base_schema,
+    std::set<std::string>* processed_keys, ErrorPtr* error) {
+  if (!_Base::ObjectSchemaFromJson(value, base_schema, processed_keys, error))
+    return false;
+
+  using commands::attributes::kObject_Properties;
+
+  std::shared_ptr<const ObjectSchema> base_object_schema;
+  if (base_schema)
+    base_object_schema = base_schema->GetObject()->GetObjectSchema();
+
+  const base::DictionaryValue* props = nullptr;
+  if (value->GetDictionaryWithoutPathExpansion(kObject_Properties, &props)) {
+    processed_keys->insert(kObject_Properties);
+    auto object_schema = std::make_shared<ObjectSchema>();
+    if (!object_schema->FromJson(props, base_object_schema.get(), error)) {
+      Error::AddTo(error, commands::errors::kDomain,
+                   commands::errors::kInvalidObjectSchema,
+                   "Error parsing object property schema");
+      return false;
+    }
+    object_schema_.value = object_schema;
+    object_schema_.is_inherited = false;
+  } else if (base_object_schema) {
+    object_schema_.value = base_object_schema;
+    object_schema_.is_inherited = true;
+  } else {
+    Error::AddToPrintf(error, commands::errors::kDomain,
+                       commands::errors::kInvalidObjectSchema,
+                       "Object type definition must include the object schema "
+                       "('%s' field not found)", kObject_Properties);
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace buffet
diff --git a/buffet/commands/prop_types.h b/buffet/commands/prop_types.h
new file mode 100644
index 0000000..016d2d5
--- /dev/null
+++ b/buffet/commands/prop_types.h
@@ -0,0 +1,309 @@
+// 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_COMMANDS_PROP_TYPES_H_
+#define BUFFET_COMMANDS_PROP_TYPES_H_
+
+#include <limits>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "buffet/commands/prop_constraints.h"
+#include "buffet/commands/prop_values.h"
+#include "buffet/error.h"
+
+namespace buffet {
+
+class IntPropType;
+class DoublePropType;
+class StringPropType;
+class BooleanPropType;
+class ObjectPropType;
+
+// PropType is a base class for all parameter type definition objects.
+// Property definitions of a particular type will derive from this class and
+// provide type-specific implementations.
+class PropType {
+ public:
+  // ConstraintMap is a type alias for a map containing parameter
+  // constraints. It is implemented as a map for fast look-ups of constraints
+  // of particular type. Also it is expected to have at most one constraint
+  // of each type (e.g. it makes no sense to impose two "minimum" constraints
+  // onto a numeric parameter).
+  using ConstraintMap = std::map<ConstraintType,
+                                 std::shared_ptr<Constraint>>;
+
+  PropType();
+  virtual ~PropType();
+
+  // Gets the parameter type as an enumeration.
+  virtual ValueType GetType() const = 0;
+  // Gets the parameter type as a string.
+  std::string GetTypeAsString() const;
+  // Returns true if this parameter definition inherits a type
+  // definition from a base object schema.
+  bool IsBasedOnSchema() const { return based_on_schema_; }
+  // Returns a default value specified for the type, or nullptr if no default
+  // is available.
+  const PropValue* GetDefaultValue() const { return default_.value.get(); }
+  // Gets the constraints specified for the parameter, if any.
+  const ConstraintMap& GetConstraints() const {
+    return constraints_;
+  }
+  // Checks if any of the type attributes were overridden from the base
+  // schema definition. If this type does not inherit from a base schema,
+  // this method returns true.
+  // An attribute could be the value of any of the constraints, default
+  // value of a parameter or any other data that may be specified in
+  // parameter type definition in and can be inherited from the base schema.
+  virtual bool HasOverriddenAttributes() const;
+
+  // Type conversion methods. Used in lieu of RTTI and dynamic_cast<>.
+  virtual IntPropType* GetInt() { return nullptr; }
+  virtual IntPropType const* GetInt() const { return nullptr; }
+  virtual DoublePropType* GetDouble() { return nullptr; }
+  virtual DoublePropType const* GetDouble() const { return nullptr; }
+  virtual StringPropType* GetString() { return nullptr; }
+  virtual StringPropType const* GetString() const { return nullptr; }
+  virtual BooleanPropType* GetBoolean() { return nullptr; }
+  virtual BooleanPropType const* GetBoolean() const { return nullptr; }
+  virtual ObjectPropType* GetObject() { return nullptr; }
+  virtual ObjectPropType const* GetObject() const { return nullptr; }
+
+  // Makes a full copy of this type definition.
+  virtual std::shared_ptr<PropType> Clone() const = 0;
+  // Creates an instance of associated value object, using the parameter
+  // type as a factory class.
+  virtual std::shared_ptr<PropValue> CreateValue() const = 0;
+  virtual std::shared_ptr<PropValue> CreateValue(const Any& val) const = 0;
+
+  // Saves the parameter type definition as a JSON object.
+  // If |full_schema| is set to true, the full type definition is saved,
+  // otherwise only the overridden properties and attributes from the base
+  // schema is saved. That is, inherited properties and attributes are not
+  // saved.
+  // If it fails, returns "nullptr" and fills in the |error| with additional
+  // error information.
+  virtual std::unique_ptr<base::Value> ToJson(bool full_schema,
+                                              ErrorPtr* error) const;
+  // Parses an JSON parameter type definition. Optional |base_schema| may
+  // specify the base schema type definition this type should be based upon.
+  // If not specified (nullptr), the parameter type is assumed to be a full
+  // definition and any omitted required properties are treated as an error.
+  // Returns true on success, otherwise fills in the |error| with additional
+  // error information.
+  virtual bool FromJson(const base::DictionaryValue* value,
+                        const PropType* base_schema, ErrorPtr* error);
+  // Helper function to load object schema from JSON.
+  virtual bool ObjectSchemaFromJson(const base::DictionaryValue* value,
+                                    const PropType* base_schema,
+                                    std::set<std::string>* processed_keys,
+                                    ErrorPtr* error) {
+    return true;
+  }
+  // Helper function to load type-specific constraints from JSON.
+  virtual bool ConstraintsFromJson(const base::DictionaryValue* value,
+                                   std::set<std::string>* processed_keys,
+                                   ErrorPtr* error) {
+    return true;
+  }
+
+  // Validates a JSON value for the parameter type to make sure it satisfies
+  // the parameter type definition including any specified constraints.
+  // Returns false if the |value| does not meet the requirements of the type
+  // definition and returns additional information about the failure via
+  // the |error| parameter.
+  bool ValidateValue(const base::Value* value, ErrorPtr* error) const;
+
+  // Additional helper static methods to help with converting a type enum
+  // value into a string and back.
+  using TypeMap = std::vector<std::pair<ValueType, std::string>>;
+  // Returns a list of value types and corresponding type names.
+  static const TypeMap& GetTypeMap();
+  // Gets the type name string for the given type.
+  static std::string GetTypeStringFromType(ValueType type);
+  // Finds the type for the given type name. Returns true on success.
+  static bool GetTypeFromTypeString(const std::string& name, ValueType* type);
+
+  // Creates an instance of PropType-derived class for the specified
+  // parameter type.
+  static std::unique_ptr<PropType> Create(ValueType type);
+
+  // Adds a constraint to the type definition.
+  void AddConstraint(std::shared_ptr<Constraint> constraint);
+  // Removes a constraint of given type, if it exists.
+  void RemoveConstraint(ConstraintType constraint_type);
+
+  // Finds a constraint of given type. Returns nullptr if not found.
+  const Constraint* GetConstraint(ConstraintType constraint_type) const;
+  Constraint* GetConstraint(ConstraintType constraint_type);
+
+  // Returns a schema for Object-type parameter. This will be nullptr for
+  // every type but Object.
+  const ObjectSchema* GetObjectSchemaPtr() const {
+    return GetObjectSchema().get();
+  }
+  virtual std::shared_ptr<const ObjectSchema> GetObjectSchema() const {
+    return std::shared_ptr<const ObjectSchema>();
+  }
+
+  // Validates the given value against all the constraints.
+  bool ValidateConstraints(const PropValue& value, ErrorPtr* error) const;
+
+ protected:
+  // Specifies if this parameter definition is derived from a base
+  // object schema.
+  bool based_on_schema_ = false;
+  // A list of constraints specified for the parameter.
+  ConstraintMap constraints_;
+  // The default value specified for the parameter, if any. If the default
+  // value is present, the parameter is treated as optional and the default
+  // value is used if the parameter value is omitted when sending a command.
+  // Otherwise the parameter is treated as required and, if it is omitted,
+  // this is treated as an error.
+  InheritableAttribute<std::shared_ptr<PropValue>> default_;
+};
+
+// Base class for all the derived concrete implementations of property
+// type classes. Provides implementations for common methods of PropType base.
+template<class Derived, class Value, typename T>
+class PropTypeBase : public PropType {
+ public:
+  virtual ValueType GetType() const override { return GetValueType<T>(); }
+  virtual std::shared_ptr<PropType> Clone() const override {
+    return std::make_shared<Derived>(*static_cast<const Derived*>(this));
+  }
+  virtual std::shared_ptr<PropValue> CreateValue() const override {
+    return std::make_shared<Value>(this);
+  }
+  virtual std::shared_ptr<PropValue> CreateValue(const Any& v) const override {
+    auto value = std::make_shared<Value>(this);
+    value->SetValue(v.Get<T>());
+    return std::move(value);
+  }
+  virtual bool ConstraintsFromJson(const base::DictionaryValue* value,
+                                   std::set<std::string>* processed_keys,
+                                   ErrorPtr* error) override;
+
+  // Helper method to obtain a vector of OneOf constraint values.
+  std::vector<T> GetOneOfValues() const {
+    auto ofc = static_cast<const ConstraintOneOf<T>*>(
+        this->GetConstraint(ConstraintType::OneOf));
+    return ofc ? ofc->set_.value : std::vector<T>();
+  }
+};
+
+// Helper base class for Int and Double parameter types.
+template<class Derived, class Value, typename T>
+class NumericPropTypeBase : public PropTypeBase<Derived, Value, T> {
+ public:
+  using _Base = PropTypeBase<Derived, Value, T>;
+  virtual bool ConstraintsFromJson(const base::DictionaryValue* value,
+                                   std::set<std::string>* processed_keys,
+                                   ErrorPtr* error) override;
+
+  // Helper method to set and obtain a min/max constraint values.
+  // Used mostly for unit testing.
+  void AddMinMaxConstraint(T min_value, T max_value) {
+    InheritableAttribute<T> min_attr(min_value, false);
+    InheritableAttribute<T> max_attr(max_value, false);
+    this->AddConstraint(std::make_shared<ConstraintMin<T>>(min_attr));
+    this->AddConstraint(std::make_shared<ConstraintMax<T>>(max_attr));
+  }
+  T GetMinValue() const {
+    auto mmc = static_cast<const ConstraintMin<T>*>(
+        this->GetConstraint(ConstraintType::Min));
+    return mmc ? mmc->limit_.value : std::numeric_limits<T>::lowest();
+  }
+  T GetMaxValue() const {
+    auto mmc = static_cast<const ConstraintMax<T>*>(
+        this->GetConstraint(ConstraintType::Max));
+    return mmc ? mmc->limit_.value : (std::numeric_limits<T>::max)();
+  }
+};
+
+// Property definition of Integer type.
+class IntPropType : public NumericPropTypeBase<IntPropType, IntValue, int> {
+ public:
+  // Overrides from the PropType base class.
+  virtual IntPropType* GetInt() override { return this; }
+  virtual IntPropType const* GetInt() const override { return this; }
+};
+
+// Property definition of Number type.
+class DoublePropType
+    : public NumericPropTypeBase<DoublePropType, DoubleValue, double> {
+ public:
+  // Overrides from the PropType base class.
+  virtual DoublePropType* GetDouble() override { return this; }
+  virtual DoublePropType const* GetDouble() const override { return this; }
+};
+
+// Property definition of String type.
+class StringPropType
+    : public PropTypeBase<StringPropType, StringValue, std::string> {
+ public:
+  using _Base = PropTypeBase<StringPropType, StringValue, std::string>;
+  // Overrides from the PropType base class.
+  virtual StringPropType* GetString() override { return this; }
+  virtual StringPropType const* GetString() const override { return this; }
+
+  virtual bool ConstraintsFromJson(const base::DictionaryValue* value,
+                                   std::set<std::string>* processed_keys,
+                                   ErrorPtr* error) override;
+
+  // Helper methods to add and inspect simple constraints.
+  // Used mostly for unit testing.
+  void AddLengthConstraint(int min_len, int max_len);
+  int GetMinLength() const;
+  int GetMaxLength() const;
+};
+
+// Property definition of Boolean type.
+class BooleanPropType
+    : public PropTypeBase<BooleanPropType, BooleanValue, bool> {
+ public:
+  // Overrides from the PropType base class.
+  virtual BooleanPropType* GetBoolean() override { return this; }
+  virtual BooleanPropType const* GetBoolean() const override { return this; }
+};
+
+// Parameter definition of Object type.
+class ObjectPropType
+    : public PropTypeBase<ObjectPropType, ObjectValue, native_types::Object> {
+ public:
+  using _Base = PropTypeBase<ObjectPropType, ObjectValue, native_types::Object>;
+  ObjectPropType();
+
+  // Overrides from the ParamType base class.
+  virtual bool HasOverriddenAttributes() const override;
+
+  virtual ObjectPropType* GetObject() override { return this; }
+  virtual ObjectPropType const* GetObject() const override { return this; }
+
+  virtual std::unique_ptr<base::Value> ToJson(bool full_schema,
+                                              ErrorPtr* error) const override;
+  virtual bool ObjectSchemaFromJson(const base::DictionaryValue* value,
+                                    const PropType* base_schema,
+                                    std::set<std::string>* processed_keys,
+                                    ErrorPtr* error) override;
+
+  virtual std::shared_ptr<const ObjectSchema> GetObjectSchema() const override {
+    return object_schema_.value;
+  }
+  void SetObjectSchema(const std::shared_ptr<const ObjectSchema>& schema) {
+    object_schema_.value = schema;
+    object_schema_.is_inherited = false;
+  }
+
+ private:
+  InheritableAttribute<std::shared_ptr<const ObjectSchema>> object_schema_;
+};
+}  // namespace buffet
+
+#endif  // BUFFET_COMMANDS_PROP_TYPES_H_
diff --git a/buffet/commands/prop_values.cc b/buffet/commands/prop_values.cc
new file mode 100644
index 0000000..0c98a20
--- /dev/null
+++ b/buffet/commands/prop_values.cc
@@ -0,0 +1,12 @@
+// 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.
+
+// TODO(avakulenko) Remove this file by Aug 1, 2014 if nothing ends up here...
+
+#include "buffet/commands/prop_values.h"
+
+namespace buffet {
+
+
+}  // namespace buffet
diff --git a/buffet/commands/prop_values.h b/buffet/commands/prop_values.h
new file mode 100644
index 0000000..adaa91d
--- /dev/null
+++ b/buffet/commands/prop_values.h
@@ -0,0 +1,200 @@
+// 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_COMMANDS_PROP_VALUES_H_
+#define BUFFET_COMMANDS_PROP_VALUES_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "buffet/any.h"
+#include "buffet/commands/schema_utils.h"
+#include "buffet/error.h"
+
+namespace base {
+class Value;
+class DictionaryValue;
+}  // namespace base
+
+namespace buffet {
+
+// Enumeration to indicate supported command parameter types.
+enum class ValueType {
+  Int,
+  Double,
+  String,
+  Boolean,
+  Object
+};
+
+class PropValue;
+class IntValue;
+class DoubleValue;
+class StringValue;
+class BooleanValue;
+class ObjectValue;
+
+class PropType;
+
+// Helper methods to get the parameter type enum value for the given
+// native C++ data representation.
+// The generic GetValueType<T>() is undefined, however particular
+// type specializations return the appropriate ValueType.
+template<typename T> ValueType GetValueType();  // Undefined.
+template<>
+inline ValueType GetValueType<int>() { return ValueType::Int; }
+template<>
+inline ValueType GetValueType<double>() { return ValueType::Double; }
+template<>
+inline ValueType GetValueType<std::string>() { return ValueType::String; }
+template<>
+inline ValueType GetValueType<bool>() { return ValueType::Boolean; }
+template<>
+inline ValueType GetValueType<native_types::Object>() {
+  return ValueType::Object;
+}
+
+// The base class for property values.
+// Concrete value classes of various types will be derived from this base.
+// A property value is the actual command parameter value (or a concrete value
+// that can be used in constraints and presets). The PropValue is mostly
+// just parsed content of base::Value when a command is dispatched, however
+// it does have some additional functionality:
+//   - it has a reference to the type definition (PropType) which is used
+//     when validating the value, especially for "object" types.
+//   - it can be compared with another instances of values of the same type.
+//     This is used to validate the values against "enum"/"one of" constraints.
+class PropValue {
+ public:
+  explicit PropValue(const PropType* type)
+      : type_(type) {}
+  virtual ~PropValue() = default;
+
+  // Gets the type of the value.
+  virtual ValueType GetType() const = 0;
+
+  // Type conversion methods. Used in lieu of RTTI and dynamic_cast<>.
+  virtual IntValue* GetInt() { return nullptr; }
+  virtual IntValue const* GetInt() const { return nullptr; }
+  virtual DoubleValue* GetDouble() { return nullptr; }
+  virtual DoubleValue const* GetDouble() const { return nullptr; }
+  virtual StringValue* GetString() { return nullptr; }
+  virtual StringValue const* GetString() const { return nullptr; }
+  virtual BooleanValue* GetBoolean() { return nullptr; }
+  virtual BooleanValue const* GetBoolean() const { return nullptr; }
+  virtual ObjectValue* GetObject() { return nullptr; }
+  virtual ObjectValue const* GetObject() const { return nullptr; }
+
+  // Makes a full copy of this value class.
+  virtual std::shared_ptr<PropValue> Clone() const = 0;
+
+  // Saves the value as a JSON object.
+  // If it fails, returns nullptr value and fills in the details for the
+  // failure in the |error| parameter.
+  virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const = 0;
+  // Parses a value from JSON.
+  // If it fails, it returns false and provides additional information
+  // via the |error| parameter.
+  virtual bool FromJson(const base::Value* value,
+                        ErrorPtr* error) = 0;
+
+  // Returns the contained C++ value as Any.
+  virtual Any GetValueAsAny() const = 0;
+
+  // Return the type definition of this value.
+  const PropType* GetPropType() const { return type_; }
+  // Compares two values and returns true if they are equal.
+  virtual bool IsEqual(const PropValue* value) const = 0;
+
+ protected:
+  const PropType* type_;  // weak pointer
+};
+
+// A helper template base class for implementing simple (non-Object) value
+// classes.
+template<typename Derived, typename T>
+class TypedValueBase : public PropValue {
+ public:
+  // To help refer to this base class from derived classes, define _Base to
+  // be this class.
+  using _Base = TypedValueBase<Derived, T>;
+  // Expose the non-default constructor of the base class.
+  using PropValue::PropValue;
+
+  // Overrides from PropValue base class.
+  virtual ValueType GetType() const override { return GetValueType<T>(); }
+  virtual std::shared_ptr<PropValue> Clone() const override {
+    return std::make_shared<Derived>(*static_cast<const Derived*>(this));
+  }
+
+  virtual std::unique_ptr<base::Value> ToJson(ErrorPtr* error) const override {
+    return TypedValueToJson(value_, error);
+  }
+
+  virtual bool FromJson(const base::Value* value,
+                        ErrorPtr* error) override {
+    return TypedValueFromJson(value, GetPropType()->GetObjectSchemaPtr(),
+                              &value_, error);
+  }
+
+  virtual bool IsEqual(const PropValue* value) const override {
+    if (GetType() != value->GetType())
+      return false;
+    const _Base* value_base = static_cast<const _Base*>(value);
+    return CompareValue(GetValue(), value_base->GetValue());
+  }
+
+  // Helper methods to get and set the C++ representation of the value.
+  virtual Any GetValueAsAny() const override { return value_; }
+  const T& GetValue() const { return value_; }
+  void SetValue(T value) { value_ = std::move(value); }
+
+ protected:
+  T value_{};  // The value of the parameter in C++ data representation.
+};
+
+// Value of type Integer.
+class IntValue final : public TypedValueBase<IntValue, int> {
+ public:
+  using _Base::_Base;  // Expose the custom constructor of the base class.
+  virtual IntValue* GetInt() override { return this; }
+  virtual IntValue const* GetInt() const override { return this; }
+};
+
+// Value of type Number.
+class DoubleValue final : public TypedValueBase<DoubleValue, double> {
+ public:
+  using _Base::_Base;  // Expose the custom constructor of the base class.
+  virtual DoubleValue* GetDouble() override { return this; }
+  virtual DoubleValue const* GetDouble() const override { return this; }
+};
+
+// Value of type String.
+class StringValue final : public TypedValueBase<StringValue, std::string> {
+ public:
+  using _Base::_Base;  // Expose the custom constructor of the base class.
+  virtual StringValue* GetString() override { return this; }
+  virtual StringValue const* GetString() const override { return this; }
+};
+
+// Value of type Boolean.
+class BooleanValue final : public TypedValueBase<BooleanValue, bool> {
+ public:
+  using _Base::_Base;  // Expose the custom constructor of the base class.
+  virtual BooleanValue* GetBoolean() override { return this; }
+  virtual BooleanValue const* GetBoolean() const override { return this; }
+};
+
+// Value of type Object.
+class ObjectValue final : public TypedValueBase<ObjectValue,
+                                                native_types::Object> {
+ public:
+  using _Base::_Base;  // Expose the custom constructor of the base class.
+  virtual ObjectValue* GetObject() override { return this; }
+  virtual ObjectValue const* GetObject() const override { return this; }
+};
+}  // namespace buffet
+
+#endif  // BUFFET_COMMANDS_PROP_VALUES_H_
diff --git a/buffet/commands/schema_constants.cc b/buffet/commands/schema_constants.cc
new file mode 100644
index 0000000..d8ec689
--- /dev/null
+++ b/buffet/commands/schema_constants.cc
@@ -0,0 +1,43 @@
+// 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/commands/schema_constants.h"
+
+namespace buffet {
+namespace commands {
+
+namespace errors {
+const char kDomain[] = "command_schema";
+
+const char kOutOfRange[] = "out_of_range";
+const char kTypeMismatch[] = "type_mismatch";
+const char kPropTypeChanged[] = "param_type_changed";
+const char kUnknownType[] = "unknown_type";
+const char kInvalidPropDef[] = "invalid_parameter_definition";
+const char kInvalidPropValue[] = "invalid_parameter_value";
+const char kNoTypeInfo[] = "no_type_info";
+const char kPropertyMissing[] = "parameter_missing";
+const char kUnknownProperty[] = "unexpected_parameter";
+const char kInvalidObjectSchema[] = "invalid_object_schema";
+}  // namespace errors
+
+namespace attributes {
+const char kType[] = "type";
+const char kDisplayName[] = "displayName";
+
+const char kNumeric_Min[] = "minimum";
+const char kNumeric_Max[] = "maximum";
+
+const char kString_MinLength[] = "minLength";
+const char kString_MaxLength[] = "maxLength";
+
+const char kOneOf_Enum[] = "enum";
+const char kOneOf_Metadata[] = "metadata";
+const char kOneOf_MetaSchema[] = "schema";
+
+const char kObject_Properties[] = "properties";
+}  // namespace attributes
+
+}  // namespace commands
+}  // namespace buffet
diff --git a/buffet/commands/schema_constants.h b/buffet/commands/schema_constants.h
new file mode 100644
index 0000000..087f53b
--- /dev/null
+++ b/buffet/commands/schema_constants.h
@@ -0,0 +1,49 @@
+// 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_COMMANDS_SCHEMA_CONSTANTS_H_
+#define BUFFET_COMMANDS_SCHEMA_CONSTANTS_H_
+
+namespace buffet {
+namespace commands {
+
+namespace errors {
+// Error domain for command schema description.
+extern const char kDomain[];
+
+// Common command definition error codes.
+extern const char kOutOfRange[];
+extern const char kTypeMismatch[];
+extern const char kPropTypeChanged[];
+extern const char kUnknownType[];
+extern const char kInvalidPropDef[];
+extern const char kInvalidPropValue[];
+extern const char kNoTypeInfo[];
+extern const char kPropertyMissing[];
+extern const char kUnknownProperty[];
+extern const char kInvalidObjectSchema[];
+}  // namespace errors
+
+namespace attributes {
+// Command description JSON schema attributes.
+extern const char kType[];
+extern const char kDisplayName[];
+
+extern const char kNumeric_Min[];
+extern const char kNumeric_Max[];
+
+extern const char kString_MinLength[];
+extern const char kString_MaxLength[];
+
+extern const char kOneOf_Enum[];
+extern const char kOneOf_Metadata[];
+extern const char kOneOf_MetaSchema[];
+
+extern const char kObject_Properties[];
+}  // namespace attributes
+
+}  // namespace commands
+}  // namespace buffet
+
+#endif  // BUFFET_COMMANDS_SCHEMA_CONSTANTS_H_
diff --git a/buffet/commands/schema_utils.cc b/buffet/commands/schema_utils.cc
new file mode 100644
index 0000000..3c6ae57
--- /dev/null
+++ b/buffet/commands/schema_utils.cc
@@ -0,0 +1,191 @@
+// 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/commands/schema_utils.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include <base/json/json_writer.h>
+
+#include "buffet/commands/object_schema.h"
+#include "buffet/commands/prop_types.h"
+#include "buffet/commands/prop_values.h"
+
+namespace buffet {
+namespace {
+// Helper function to report "type mismatch" errors when parsing JSON.
+void ReportJsonTypeMismatch(const base::Value* value_in,
+                            const std::string& expected_type,
+                            ErrorPtr* error) {
+  std::string value_as_string;
+  base::JSONWriter::Write(value_in, &value_as_string);
+  Error::AddToPrintf(error, commands::errors::kDomain,
+                     commands::errors::kTypeMismatch,
+                     "Unable to convert value %s into %s",
+                     value_as_string.c_str(), expected_type.c_str());
+}
+
+// Template version of ReportJsonTypeMismatch that deduces the type of expected
+// data from the value_out parameter passed to particular overload of
+// TypedValueFromJson() function. Always returns false.
+template<typename T>
+bool ReportUnexpectedJson(const base::Value* value_in, T*, ErrorPtr* error) {
+  ReportJsonTypeMismatch(value_in,
+                         PropType::GetTypeStringFromType(GetValueType<T>()),
+                         error);
+  return false;
+}
+
+bool ErrorMissingProperty(ErrorPtr* error, const char* param_name) {
+  Error::AddToPrintf(error, commands::errors::kDomain,
+                     commands::errors::kPropertyMissing,
+                     "Required parameter missing: %s", param_name);
+  return false;
+}
+}  // namespace
+
+// Specializations of TypedValueToJson<T>() for supported C++ types.
+std::unique_ptr<base::Value> TypedValueToJson(bool value, ErrorPtr* error) {
+  return std::unique_ptr<base::Value>(base::Value::CreateBooleanValue(value));
+}
+
+std::unique_ptr<base::Value> TypedValueToJson(int value, ErrorPtr* error) {
+  return std::unique_ptr<base::Value>(base::Value::CreateIntegerValue(value));
+}
+
+std::unique_ptr<base::Value> TypedValueToJson(double value, ErrorPtr* error) {
+  return std::unique_ptr<base::Value>(base::Value::CreateDoubleValue(value));
+}
+
+std::unique_ptr<base::Value> TypedValueToJson(const std::string& value,
+                                              ErrorPtr* error) {
+  return std::unique_ptr<base::Value>(base::Value::CreateStringValue(value));
+}
+
+std::unique_ptr<base::Value> TypedValueToJson(const native_types::Object& value,
+                                              ErrorPtr* error) {
+  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+  for (const auto& pair : value) {
+    auto prop_value = pair.second->ToJson(error);
+    if (!prop_value)
+      return prop_value;
+    dict->SetWithoutPathExpansion(pair.first, prop_value.release());
+  }
+  return std::move(dict);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const ObjectSchema* object_schema,
+                        bool* value_out, ErrorPtr* error) {
+  return value_in->GetAsBoolean(value_out) ||
+         ReportUnexpectedJson(value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const ObjectSchema* object_schema,
+                        int* value_out, ErrorPtr* error) {
+  return value_in->GetAsInteger(value_out) ||
+         ReportUnexpectedJson(value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const ObjectSchema* object_schema,
+                        double* value_out, ErrorPtr* error) {
+  return value_in->GetAsDouble(value_out) ||
+         ReportUnexpectedJson(value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const ObjectSchema* object_schema,
+                        std::string* value_out, ErrorPtr* error) {
+  return value_in->GetAsString(value_out) ||
+         ReportUnexpectedJson(value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+                        const ObjectSchema* object_schema,
+                        native_types::Object* value_out, ErrorPtr* error) {
+  const base::DictionaryValue* dict = nullptr;
+  if (!value_in->GetAsDictionary(&dict))
+    return ReportUnexpectedJson(value_in, value_out, error);
+
+  CHECK(object_schema) << "Object schema must be provided";
+
+  std::set<std::string> keys_processed;
+  for (const auto& pair : object_schema->GetProps()) {
+    const PropValue* def_value = pair.second->GetDefaultValue();
+    if (dict->HasKey(pair.first)) {
+      std::shared_ptr<PropValue> value = pair.second->CreateValue();
+      const base::Value* param_value = nullptr;
+      CHECK(dict->GetWithoutPathExpansion(pair.first, &param_value))
+          << "Unable to get parameter";
+      if (!value->FromJson(param_value, error))
+        return false;
+      value_out->insert(std::make_pair(pair.first, std::move(value)));
+    } else if (def_value) {
+      std::shared_ptr<PropValue> value = def_value->Clone();
+      value_out->insert(std::make_pair(pair.first, std::move(value)));
+    } else {
+      return ErrorMissingProperty(error, pair.first.c_str());
+    }
+    keys_processed.insert(pair.first);
+  }
+
+  // Just for sanity, make sure that we processed all the necessary properties
+  // and there weren't any extra (unknown) ones specified. If so, ignore
+  // them, but log as warnings...
+  base::DictionaryValue::Iterator iter(*dict);
+  while (!iter.IsAtEnd()) {
+    std::string key = iter.key();
+    if (keys_processed.find(key) == keys_processed.end() &&
+        !object_schema->GetExtraPropertiesAllowed()) {
+      Error::AddToPrintf(error, commands::errors::kDomain,
+                         commands::errors::kUnknownProperty,
+                         "Unrecognized parameter '%s'", key.c_str());
+      return false;
+    }
+    iter.Advance();
+  }
+
+  // Now go over all property values and validate them.
+  for (const auto& pair : *value_out) {
+    const PropType* prop_type = pair.second->GetPropType();
+    CHECK(prop_type) << "Value property type must be available";
+    if (!prop_type->ValidateConstraints(*pair.second, error)) {
+      Error::AddToPrintf(error, commands::errors::kDomain,
+                         commands::errors::kInvalidPropValue,
+                         "Invalid parameter value for property '%s'",
+                         pair.first.c_str());
+      return false;
+    }
+  }
+  return true;
+}
+
+// Compares two sets of key-value pairs from two Objects.
+static bool obj_cmp(const native_types::Object::value_type& v1,
+                    const native_types::Object::value_type& v2) {
+  return (v1.first == v2.first) && v1.second->IsEqual(v2.second.get());
+}
+
+bool operator==(const native_types::Object& obj1,
+                const native_types::Object& obj2) {
+  if (obj1.size() != obj2.size())
+    return false;
+
+  auto pair = std::mismatch(obj1.begin(), obj1.end(), obj2.begin(), obj_cmp);
+  return pair == std::make_pair(obj1.end(), obj2.end());
+}
+
+std::string ToString(const native_types::Object& obj) {
+  auto val = TypedValueToJson(obj, nullptr);
+  std::string str;
+  base::JSONWriter::Write(val.get(), &str);
+  return str;
+}
+
+
+}  // namespace buffet
diff --git a/buffet/commands/schema_utils.h b/buffet/commands/schema_utils.h
new file mode 100644
index 0000000..ff9d461
--- /dev/null
+++ b/buffet/commands/schema_utils.h
@@ -0,0 +1,118 @@
+// 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_COMMANDS_SCHEMA_UTILS_H_
+#define BUFFET_COMMANDS_SCHEMA_UTILS_H_
+
+#include <limits>
+#include <map>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <base/values.h>
+
+#include <buffet/error.h>
+
+namespace buffet {
+
+class PropValue;
+class ObjectSchema;
+
+namespace native_types {
+// C++ representation of object values.
+using Object = std::map<std::string, std::shared_ptr<PropValue>>;
+}  // namespace native_types
+// Converts an object to string.
+std::string ToString(const native_types::Object& obj);
+
+// InheritableAttribute class is used for specifying various command parameter
+// attributes that can be inherited from a base (parent) schema.
+// The |value| still specifies the actual attribute values, whether it
+// is inherited or overridden, while |is_inherited| can be used to identify
+// if the attribute was inherited (true) or overridden (false).
+template<typename T>
+class InheritableAttribute {
+ public:
+  InheritableAttribute() = default;
+  explicit InheritableAttribute(T val)
+      : value(std::move(val)), is_inherited(true) {}
+  InheritableAttribute(T val, bool inherited)
+      : value(std::move(val)), is_inherited(inherited) {}
+  T value{};
+  bool is_inherited{true};
+};
+
+// A bunch of helper function to create base::Value for specific C++ classes,
+// including vectors of types. These are used in template classes below
+// to simplify specialization logic.
+std::unique_ptr<base::Value> TypedValueToJson(bool value, ErrorPtr* error);
+std::unique_ptr<base::Value> TypedValueToJson(int value, ErrorPtr* error);
+std::unique_ptr<base::Value> TypedValueToJson(double value, ErrorPtr* error);
+std::unique_ptr<base::Value> TypedValueToJson(const std::string& value,
+                                              ErrorPtr* error);
+std::unique_ptr<base::Value> TypedValueToJson(const native_types::Object& value,
+                                              ErrorPtr* error);
+template<typename T>
+std::unique_ptr<base::Value> TypedValueToJson(const std::vector<T>& values,
+                                              ErrorPtr* error) {
+  std::unique_ptr<base::ListValue> list(new base::ListValue);
+  for (const auto& v : values) {
+    auto json = TypedValueToJson(v, error);
+    if (!json)
+      return std::unique_ptr<base::Value>();
+    list->Append(json.release());
+  }
+  return std::move(list);
+}
+
+// Similarly to CreateTypedValue() function above, the following overloaded
+// helper methods allow to extract specific C++ data types from base::Value.
+// Also used in template classes below to simplify specialization logic.
+bool TypedValueFromJson(const base::Value* value_in,
+                        const ObjectSchema* object_schema,
+                        bool* value_out, ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+                        const ObjectSchema* object_schema,
+                        int* value_out, ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+                        const ObjectSchema* object_schema,
+                        double* value_out, ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+                        const ObjectSchema* object_schema,
+                        std::string* value_out, ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+                        const ObjectSchema* object_schema,
+                        native_types::Object* value_out, ErrorPtr* error);
+
+bool operator==(const native_types::Object& obj1,
+                const native_types::Object& obj2);
+
+// CompareValue is a helper function to help with implementing EqualsTo operator
+// for various data types. For most scalar types it is using operator==(),
+// however, for floating point values, rounding errors in binary representation
+// of IEEE floats/doubles can cause straight == comparison to fail for seemingly
+// equivalent values. For these, use approximate comparison with the error
+// margin equal to the epsilon value defined for the corresponding data type.
+// This is used when looking up values for implementation of OneOf constraints
+// which should work reliably for floating points also ("number" type).
+
+// Compare exact types using ==.
+template<typename T>
+inline typename std::enable_if<!std::is_floating_point<T>::value, bool>::type
+CompareValue(const T& v1, const T& v2) {
+  return v1 == v2;
+}
+
+// Compare non-exact types (such as double) using precision margin (epsilon).
+template<typename T>
+inline typename std::enable_if<std::is_floating_point<T>::value, bool>::type
+CompareValue(const T& v1, const T& v2) {
+  return std::abs(v1 - v2) <= std::numeric_limits<T>::epsilon();
+}
+
+}  // namespace buffet
+
+#endif  // BUFFET_COMMANDS_SCHEMA_UTILS_H_
diff --git a/buffet/commands/schema_utils_unittest.cc b/buffet/commands/schema_utils_unittest.cc
new file mode 100644
index 0000000..6918186
--- /dev/null
+++ b/buffet/commands/schema_utils_unittest.cc
@@ -0,0 +1,210 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "buffet/commands/object_schema.h"
+#include "buffet/commands/prop_types.h"
+#include "buffet/commands/prop_values.h"
+#include "buffet/commands/schema_utils.h"
+
+namespace {
+// Helper method to create base::Value from a string as a smart pointer.
+// For ease of definition in C++ code, double-quotes in the source definition
+// are replaced with apostrophes.
+std::unique_ptr<base::Value> CreateValue(const char* json) {
+  std::string json2(json);
+  // Convert apostrophes to double-quotes so JSONReader can parse the string.
+  std::replace(json2.begin(), json2.end(), '\'', '"');
+  return std::unique_ptr<base::Value>(base::JSONReader::Read(json2));
+}
+
+// Helper method to create a JSON dictionary object from a string.
+std::unique_ptr<base::DictionaryValue> CreateDictionaryValue(const char* json) {
+  std::string json2(json);
+  std::replace(json2.begin(), json2.end(), '\'', '"');
+  base::Value* value = base::JSONReader::Read(json2);
+  base::DictionaryValue* dict;
+  value->GetAsDictionary(&dict);
+  return std::unique_ptr<base::DictionaryValue>(dict);
+}
+
+// Converts a JSON value to a string. It also converts double-quotes to
+// apostrophes for easy comparisons in C++ source code.
+std::string ValueToString(const base::Value* value) {
+  std::string json;
+  base::JSONWriter::Write(value, &json);
+  std::replace(json.begin(), json.end(), '"', '\'');
+  return json;
+}
+
+}  // namespace
+
+TEST(CommandSchemaUtils, TypedValueToJson_Scalar) {
+  EXPECT_EQ("true",
+            ValueToString(buffet::TypedValueToJson(true, nullptr).get()));
+  EXPECT_EQ("false",
+            ValueToString(buffet::TypedValueToJson(false, nullptr).get()));
+
+  EXPECT_EQ("0", ValueToString(buffet::TypedValueToJson(0, nullptr).get()));
+  EXPECT_EQ("-10", ValueToString(buffet::TypedValueToJson(-10, nullptr).get()));
+  EXPECT_EQ("20", ValueToString(buffet::TypedValueToJson(20, nullptr).get()));
+
+  EXPECT_EQ("0.0", ValueToString(buffet::TypedValueToJson(0.0, nullptr).get()));
+  EXPECT_EQ("1.2", ValueToString(buffet::TypedValueToJson(1.2, nullptr).get()));
+
+  EXPECT_EQ("'abc'",
+            ValueToString(buffet::TypedValueToJson(std::string("abc"),
+                                                   nullptr).get()));
+
+  std::vector<bool> bool_array{true, false};
+  EXPECT_EQ("[true,false]",
+            ValueToString(buffet::TypedValueToJson(bool_array, nullptr).get()));
+
+  std::vector<int> int_array{1, 2, 5};
+  EXPECT_EQ("[1,2,5]",
+            ValueToString(buffet::TypedValueToJson(int_array, nullptr).get()));
+
+  std::vector<double> dbl_array{1.1, 2.2};
+  EXPECT_EQ("[1.1,2.2]",
+            ValueToString(buffet::TypedValueToJson(dbl_array, nullptr).get()));
+
+  std::vector<std::string> str_array{"a", "bc"};
+  EXPECT_EQ("['a','bc']",
+            ValueToString(buffet::TypedValueToJson(str_array, nullptr).get()));
+}
+
+TEST(CommandSchemaUtils, TypedValueToJson_Object) {
+  buffet::IntPropType int_type;
+  buffet::native_types::Object object;
+
+  object.insert(std::make_pair("width", int_type.CreateValue(640)));
+  object.insert(std::make_pair("height", int_type.CreateValue(480)));
+  EXPECT_EQ("{'height':480,'width':640}",
+            ValueToString(buffet::TypedValueToJson(object, nullptr).get()));
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Bool) {
+  bool value;
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("true").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_TRUE(value);
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("false").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_FALSE(value);
+
+  buffet::ErrorPtr error;
+  EXPECT_FALSE(buffet::TypedValueFromJson(CreateValue("0").get(), nullptr,
+                                          &value, &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Int) {
+  int value;
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("0").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_EQ(0, value);
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("23").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_EQ(23, value);
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("-1234").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_EQ(-1234, value);
+
+  buffet::ErrorPtr error;
+  EXPECT_FALSE(buffet::TypedValueFromJson(CreateValue("'abc'").get(), nullptr,
+                                          &value, &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Double) {
+  double value;
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("0").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_DOUBLE_EQ(0.0, value);
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("0.0").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_DOUBLE_EQ(0.0, value);
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("23").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_EQ(23.0, value);
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("23.1").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_EQ(23.1, value);
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("-1.23E+02").get(),
+                                         nullptr, &value, nullptr));
+  EXPECT_EQ(-123.0, value);
+
+  buffet::ErrorPtr error;
+  EXPECT_FALSE(buffet::TypedValueFromJson(CreateValue("'abc'").get(), nullptr,
+                                          &value, &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_String) {
+  std::string value;
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("''").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_EQ("", value);
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("'23'").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_EQ("23", value);
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(CreateValue("'abc'").get(), nullptr,
+                                         &value, nullptr));
+  EXPECT_EQ("abc", value);
+
+  buffet::ErrorPtr error;
+  EXPECT_FALSE(buffet::TypedValueFromJson(CreateValue("12").get(), nullptr,
+                                          &value, &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Object) {
+  buffet::native_types::Object value;
+  buffet::ObjectSchema schema;
+
+  auto age_prop = std::make_shared<buffet::IntPropType>();
+  age_prop->AddMinMaxConstraint(0, 150);
+  schema.AddProp("age", age_prop);
+
+  auto name_prop = std::make_shared<buffet::StringPropType>();
+  name_prop->AddLengthConstraint(1, 30);
+  schema.AddProp("name", name_prop);
+
+  EXPECT_TRUE(buffet::TypedValueFromJson(
+      CreateValue("{'age':20,'name':'Bob'}").get(), &schema, &value, nullptr));
+  buffet::native_types::Object value2;
+  value2.insert(std::make_pair("age", age_prop->CreateValue(20)));
+  value2.insert(std::make_pair("name",
+                               name_prop->CreateValue(std::string("Bob"))));
+  EXPECT_EQ(value2, value);
+
+  buffet::ErrorPtr error;
+  EXPECT_FALSE(buffet::TypedValueFromJson(CreateValue("'abc'").get(), nullptr,
+                                          &value, &error));
+  EXPECT_EQ("type_mismatch", error->GetCode());
+  error.reset();
+}
diff --git a/buffet/data_encoding.cc b/buffet/data_encoding.cc
new file mode 100644
index 0000000..f2de937
--- /dev/null
+++ b/buffet/data_encoding.cc
@@ -0,0 +1,101 @@
+// 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/data_encoding.h"
+
+#include <base/strings/stringprintf.h>
+#include <string.h>
+
+#include "buffet/string_utils.h"
+
+namespace {
+
+inline int HexToDec(int hex) {
+  int dec = -1;
+  if (hex >= '0' && hex <= '9') {
+    dec = hex - '0';
+  } else if (hex >= 'A' && hex <= 'F') {
+    dec = hex - 'A' + 10;
+  } else if (hex >= 'a' && hex <= 'f') {
+    dec = hex - 'a' + 10;
+  }
+  return dec;
+}
+
+}  // namespace
+
+/////////////////////////////////////////////////////////////////////////
+namespace buffet {
+namespace data_encoding {
+
+std::string UrlEncode(const char* data, bool encodeSpaceAsPlus) {
+  std::string result;
+
+  while (*data) {
+    char c = *data++;
+    // According to RFC3986 (http://www.faqs.org/rfcs/rfc3986.html),
+    // section 2.3. - Unreserved Characters
+    if ((c >= '0' && c <= '9') ||
+        (c >= 'A' && c <= 'Z') ||
+        (c >= 'a' && c <= 'z') ||
+        c == '-' || c == '.' || c == '_' || c == '~') {
+      result += c;
+    } else if (c == ' ' && encodeSpaceAsPlus) {
+      // For historical reasons, some URLs have spaces encoded as '+',
+      // this also applies to form data encoded as
+      // 'application/x-www-form-urlencoded'
+      result += '+';
+    } else {
+      base::StringAppendF(&result, "%%%02X",
+                          static_cast<unsigned char>(c));  // Encode as %NN
+    }
+  }
+  return result;
+}
+
+std::string UrlDecode(const char* data) {
+  std::string result;
+  while (*data) {
+    char c = *data++;
+    int part1 = 0, part2 = 0;
+    // HexToDec would return -1 even for character 0 (end of string),
+    // so it is safe to access data[0] and data[1] without overrunning the buf.
+    if (c == '%' &&
+        (part1 = HexToDec(data[0])) >= 0 && (part2 = HexToDec(data[1])) >= 0) {
+      c = static_cast<char>((part1 << 4) | part2);
+      data += 2;
+    } else if (c == '+') {
+      c = ' ';
+    }
+    result += c;
+  }
+  return result;
+}
+
+std::string WebParamsEncode(const WebParamList& params,
+                            bool encodeSpaceAsPlus) {
+  std::vector<std::string> pairs;
+  pairs.reserve(params.size());
+  for (const auto& p : params) {
+    std::string key = UrlEncode(p.first.c_str(), encodeSpaceAsPlus);
+    std::string value = UrlEncode(p.second.c_str(), encodeSpaceAsPlus);
+    pairs.push_back(string_utils::Join('=', key, value));
+  }
+
+  return string_utils::Join('&', pairs);
+}
+
+WebParamList WebParamsDecode(const std::string& data) {
+  WebParamList result;
+  std::vector<std::string> params = string_utils::Split(data, '&');
+  for (const auto& p : params) {
+    auto pair = string_utils::SplitAtFirst(p, '=');
+    result.emplace_back(UrlDecode(pair.first.c_str()),
+                        UrlDecode(pair.second.c_str()));
+  }
+  return result;
+}
+
+}  // namespace data_encoding
+}  // namespace buffet
diff --git a/buffet/data_encoding.h b/buffet/data_encoding.h
new file mode 100644
index 0000000..e493e36
--- /dev/null
+++ b/buffet/data_encoding.h
@@ -0,0 +1,46 @@
+// 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_DATA_ENCODING_H_
+#define BUFFET_DATA_ENCODING_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace buffet {
+namespace data_encoding {
+
+typedef std::vector<std::pair<std::string, std::string>> WebParamList;
+
+// Encode/escape string to be used in the query portion of a URL.
+// If |encodeSpaceAsPlus| is set to true, spaces are encoded as '+' instead
+// of "%20"
+std::string UrlEncode(const char* data, bool encodeSpaceAsPlus);
+
+inline std::string UrlEncode(const char* data) {
+  return UrlEncode(data, true);
+}
+
+// Decodes/unescapes a URL. Replaces all %XX sequences with actual characters.
+// Also replaces '+' with spaces.
+std::string UrlDecode(const char* data);
+
+// Converts a list of key-value pairs into a string compatible with
+// 'application/x-www-form-urlencoded' content encoding.
+std::string WebParamsEncode(const WebParamList& params, bool encodeSpaceAsPlus);
+
+inline std::string WebParamsEncode(const WebParamList& params) {
+  return WebParamsEncode(params, true);
+}
+
+// Parses a string of '&'-delimited key-value pairs (separated by '=') and
+// encoded in a way compatible with 'application/x-www-form-urlencoded'
+// content encoding.
+WebParamList WebParamsDecode(const std::string& data);
+
+}  // namespace data_encoding
+}  // namespace buffet
+
+#endif  // BUFFET_DATA_ENCODING_H_
diff --git a/buffet/data_encoding_unittest.cc b/buffet/data_encoding_unittest.cc
new file mode 100644
index 0000000..ed6295c
--- /dev/null
+++ b/buffet/data_encoding_unittest.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2011 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/data_encoding.h"
+
+#include <gtest/gtest.h>
+
+using namespace buffet::data_encoding;  // NOLINT(build/namespaces)
+
+TEST(data_encoding, UrlEncoding) {
+  std::string test = "\"http://sample/path/0014.html \"";
+  std::string encoded = UrlEncode(test.c_str());
+  EXPECT_EQ("%22http%3A%2F%2Fsample%2Fpath%2F0014.html+%22",
+            encoded);
+  EXPECT_EQ(test, UrlDecode(encoded.c_str()));
+
+  test = "\"http://sample/path/0014.html \"";
+  encoded = UrlEncode(test.c_str(), false);
+  EXPECT_EQ("%22http%3A%2F%2Fsample%2Fpath%2F0014.html%20%22",
+            encoded);
+  EXPECT_EQ(test, UrlDecode(encoded.c_str()));
+}
+
+TEST(data_encoding, WebParamsEncoding) {
+  std::string encoded = WebParamsEncode({{"q", "test"},
+                                         {"path", "/usr/bin"},
+                                         {"#", "%"}});
+  EXPECT_EQ("q=test&path=%2Fusr%2Fbin&%23=%25", encoded);
+
+  auto params = WebParamsDecode(encoded);
+  EXPECT_EQ(3, params.size());
+  EXPECT_EQ("q", params[0].first);
+  EXPECT_EQ("test", params[0].second);
+  EXPECT_EQ("path", params[1].first);
+  EXPECT_EQ("/usr/bin", params[1].second);
+  EXPECT_EQ("#", params[2].first);
+  EXPECT_EQ("%", params[2].second);
+}
diff --git a/buffet/dbus/org.chromium.Buffet.conf b/buffet/dbus/org.chromium.Buffet.conf
new file mode 100644
index 0000000..0f6ea4d
--- /dev/null
+++ b/buffet/dbus/org.chromium.Buffet.conf
@@ -0,0 +1,13 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+  <policy user="root">
+    <allow own="org.chromium.Buffet" />
+    <allow send_destination="org.chromium.Buffet" />
+  </policy>
+
+  <policy context="default">
+    <allow send_destination="org.chromium.Buffet" />
+  </policy>
+</busconfig>
\ No newline at end of file
diff --git a/buffet/dbus_constants.cc b/buffet/dbus_constants.cc
new file mode 100644
index 0000000..d8c70ba
--- /dev/null
+++ b/buffet/dbus_constants.cc
@@ -0,0 +1,27 @@
+// 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/dbus_constants.h"
+
+namespace buffet {
+
+namespace dbus_constants {
+
+const char kServiceName[] = "org.chromium.Buffet";
+
+const char kRootServicePath[] = "/org/chromium/Buffet";
+
+const char kManagerInterface[] = "org.chromium.Buffet.Manager";
+const char kManagerServicePath[] = "/org/chromium/Buffet/Manager";
+
+const char kManagerCheckDeviceRegistered[]  = "CheckDeviceRegistered";
+const char kManagerGetDeviceInfo[]          = "GetDeviceInfo";
+const char kManagerStartRegisterDevice[]    = "StartRegisterDevice";
+const char kManagerFinishRegisterDevice[]   = "FinishRegisterDevice";
+const char kManagerUpdateStateMethod[]      = "UpdateState";
+const char kManagerTestMethod[]             = "TestMethod";
+
+}  // namespace dbus_constants
+
+}  // namespace buffet
diff --git a/buffet/dbus_constants.h b/buffet/dbus_constants.h
new file mode 100644
index 0000000..b7e9239
--- /dev/null
+++ b/buffet/dbus_constants.h
@@ -0,0 +1,34 @@
+// 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_DBUS_CONSTANTS_H_
+#define BUFFET_DBUS_CONSTANTS_H_
+
+namespace buffet {
+
+namespace dbus_constants {
+
+// The service name claimed by the Buffet daemon.
+extern const char kServiceName[];
+
+// The object at this path implements the ObjectManager interface.
+extern const char kRootServicePath[];
+
+// Interface implemented by the object at kManagerServicePath.
+extern const char kManagerInterface[];
+extern const char kManagerServicePath[];
+
+// Methods exposed as part of kManagerInterface.
+extern const char kManagerCheckDeviceRegistered[];
+extern const char kManagerGetDeviceInfo[];
+extern const char kManagerStartRegisterDevice[];
+extern const char kManagerFinishRegisterDevice[];
+extern const char kManagerUpdateStateMethod[];
+extern const char kManagerTestMethod[];
+
+}  // namespace dbus_constants
+
+}  // namespace buffet
+
+#endif  // BUFFET_DBUS_CONSTANTS_H_
diff --git a/buffet/dbus_utils.cc b/buffet/dbus_utils.cc
new file mode 100644
index 0000000..58f0b26
--- /dev/null
+++ b/buffet/dbus_utils.cc
@@ -0,0 +1,63 @@
+// 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/dbus_utils.h"
+
+#include <base/logging.h>
+#include <base/bind.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+
+// Passes |method_call| to |handler| and passes the response to
+// |response_sender|. If |handler| returns NULL, an empty response is created
+// and sent.
+void HandleSynchronousDBusMethodCall(
+    base::Callback<scoped_ptr<dbus::Response>(dbus::MethodCall*)> handler,
+    dbus::MethodCall* method_call,
+    dbus::ExportedObject::ResponseSender response_sender) {
+  auto response = handler.Run(method_call);
+  if (!response)
+    response = dbus::Response::FromMethodCall(method_call);
+
+  response_sender.Run(response.Pass());
+}
+
+}  // namespace
+
+scoped_ptr<dbus::Response> GetBadArgsError(dbus::MethodCall* method_call,
+                                           const std::string& message) {
+  LOG(ERROR) << "Error while handling DBus call: " << message;
+  scoped_ptr<dbus::ErrorResponse> resp(dbus::ErrorResponse::FromMethodCall(
+      method_call, "org.freedesktop.DBus.Error.InvalidArgs", message));
+  return scoped_ptr<dbus::Response>(resp.release());
+}
+
+scoped_ptr<dbus::Response> GetDBusError(dbus::MethodCall* method_call,
+                                        const Error* error) {
+  std::string message;
+  while (error) {
+    // Format error string as "domain/code:message".
+    if (!message.empty())
+      message += ';';
+    message += error->GetDomain() + '/' + error->GetCode() + ':' +
+               error->GetMessage();
+    error = error->GetInnerError();
+  }
+  scoped_ptr<dbus::ErrorResponse> resp(dbus::ErrorResponse::FromMethodCall(
+    method_call, "org.freedesktop.DBus.Error.Failed", message));
+  return scoped_ptr<dbus::Response>(resp.release());
+}
+
+dbus::ExportedObject::MethodCallCallback GetExportableDBusMethod(
+    base::Callback<scoped_ptr<dbus::Response>(dbus::MethodCall*)> handler) {
+  return base::Bind(&HandleSynchronousDBusMethodCall, handler);
+}
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
diff --git a/buffet/dbus_utils.h b/buffet/dbus_utils.h
new file mode 100644
index 0000000..6314531
--- /dev/null
+++ b/buffet/dbus_utils.h
@@ -0,0 +1,35 @@
+// 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_DBUS_UTILS_H_
+#define BUFFET_DBUS_UTILS_H_
+
+#include <string>
+
+#include <base/memory/scoped_ptr.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.h>
+
+#include "buffet/error.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+scoped_ptr<dbus::Response> GetBadArgsError(dbus::MethodCall* method_call,
+                                           const std::string& message);
+
+scoped_ptr<dbus::Response> GetDBusError(dbus::MethodCall* method_call,
+                                        const Error* error);
+
+
+dbus::ExportedObject::MethodCallCallback GetExportableDBusMethod(
+    base::Callback<scoped_ptr<dbus::Response>(dbus::MethodCall*)> handler);
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
+
+#endif  // BUFFET_DBUS_UTILS_H_
+
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc
new file mode 100644
index 0000000..5447726
--- /dev/null
+++ b/buffet/device_registration_info.cc
@@ -0,0 +1,513 @@
+// 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/device_registration_info.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <base/json/json_writer.h>
+#include <base/values.h>
+
+#include "buffet/data_encoding.h"
+#include "buffet/device_registration_storage_keys.h"
+#include "buffet/storage_impls.h"
+#include "buffet/http_transport_curl.h"
+#include "buffet/http_utils.h"
+#include "buffet/mime_utils.h"
+#include "buffet/string_utils.h"
+#include "buffet/url_utils.h"
+
+const char buffet::kErrorDomainOAuth2[] = "oauth2";
+const char buffet::kErrorDomainGCD[] = "gcd";
+const char buffet::kErrorDomainGCDServer[] = "gcd_server";
+const char buffet::kErrorDomainBuffet[] = "buffet";
+
+namespace buffet {
+namespace storage_keys {
+
+// Persistent keys
+const char kClientId[]      = "client_id";
+const char kClientSecret[]  = "client_secret";
+const char kApiKey[]        = "api_key";
+const char kRefreshToken[]  = "refresh_token";
+const char kDeviceId[]      = "device_id";
+const char kOAuthURL[]      = "oauth_url";
+const char kServiceURL[]    = "service_url";
+const char kRobotAccount[]  = "robot_account";
+// Transient keys
+const char kDeviceKind[]    = "device_kind";
+const char kSystemName[]    = "system_name";
+const char kDisplayName[]   = "display_name";
+
+}  // namespace storage_keys
+}  // namespace buffet
+
+namespace {
+
+const base::FilePath::CharType kDeviceInfoFilePath[] =
+    FILE_PATH_LITERAL("/var/lib/buffet/device_reg_info");
+
+bool GetParamValue(
+    const std::map<std::string, std::shared_ptr<base::Value>>& params,
+    const std::string& param_name,
+    std::string* param_value) {
+  auto p = params.find(param_name);
+  if (p == params.end())
+    return false;
+
+  return p->second->GetAsString(param_value);
+}
+
+std::pair<std::string, std::string> BuildAuthHeader(
+    const std::string& access_token_type,
+    const std::string& access_token) {
+  std::string authorization =
+      buffet::string_utils::Join(' ', access_token_type, access_token);
+  return {buffet::http::request_header::kAuthorization, authorization};
+}
+
+std::unique_ptr<base::DictionaryValue> ParseOAuthResponse(
+    const buffet::http::Response* response, buffet::ErrorPtr* error) {
+  int code = 0;
+  auto resp = buffet::http::ParseJsonResponse(response, &code, error);
+  if (resp && code >= buffet::http::status_code::BadRequest) {
+    if (error) {
+      std::string error_code, error_message;
+      if (resp->GetString("error", &error_code) &&
+          resp->GetString("error_description", &error_message)) {
+        buffet::Error::AddTo(error, buffet::kErrorDomainOAuth2, error_code,
+                             error_message);
+      } else {
+        buffet::Error::AddTo(error, buffet::kErrorDomainOAuth2,
+                             "unexpected_response", "Unexpected OAuth error");
+      }
+    }
+    return std::unique_ptr<base::DictionaryValue>();
+  }
+  return resp;
+}
+
+inline void SetUnexpectedError(buffet::ErrorPtr* error) {
+  buffet::Error::AddTo(error, buffet::kErrorDomainGCD, "unexpected_response",
+                       "Unexpected GCD error");
+}
+
+void ParseGCDError(const base::DictionaryValue* json, buffet::ErrorPtr* error) {
+  if (!error)
+    return;
+
+  const base::Value* list_value = nullptr;
+  const base::ListValue* error_list = nullptr;
+  if (!json->Get("error.errors", &list_value) ||
+      !list_value->GetAsList(&error_list)) {
+    SetUnexpectedError(error);
+    return;
+  }
+
+  for (size_t i = 0; i < error_list->GetSize(); i++) {
+    const base::Value* error_value = nullptr;
+    const base::DictionaryValue* error_object = nullptr;
+    if (!error_list->Get(i, &error_value) ||
+        !error_value->GetAsDictionary(&error_object)) {
+      SetUnexpectedError(error);
+      continue;
+    }
+    std::string error_code, error_message;
+    if (error_object->GetString("reason", &error_code) &&
+        error_object->GetString("message", &error_message)) {
+      buffet::Error::AddTo(error, buffet::kErrorDomainGCDServer,
+                           error_code, error_message);
+    } else {
+      SetUnexpectedError(error);
+    }
+  }
+}
+
+std::string BuildURL(const std::string& url,
+                     const std::vector<std::string>& subpaths,
+                     const buffet::data_encoding::WebParamList& params) {
+  std::string result = buffet::url::CombineMultiple(url, subpaths);
+  return buffet::url::AppendQueryParams(result, params);
+}
+
+}  // anonymous namespace
+
+namespace buffet {
+
+DeviceRegistrationInfo::DeviceRegistrationInfo()
+    : transport_(new http::curl::Transport()),
+      // TODO(avakulenko): Figure out security implications of storing
+      // this data unencrypted.
+      storage_(new FileStorage(base::FilePath(kDeviceInfoFilePath))) {
+}
+
+DeviceRegistrationInfo::DeviceRegistrationInfo(
+    std::shared_ptr<http::Transport> transport,
+    std::shared_ptr<StorageInterface> storage) : transport_(transport),
+                                                 storage_(storage) {
+}
+
+std::pair<std::string, std::string>
+    DeviceRegistrationInfo::GetAuthorizationHeader() const {
+  return BuildAuthHeader("Bearer", access_token_);
+}
+
+std::string DeviceRegistrationInfo::GetServiceURL(
+    const std::string& subpath,
+    const data_encoding::WebParamList& params) const {
+  return BuildURL(service_url_, {subpath}, params);
+}
+
+std::string DeviceRegistrationInfo::GetDeviceURL(
+    const std::string& subpath,
+    const data_encoding::WebParamList& params) const {
+  CHECK(!device_id_.empty()) << "Must have a valid device ID";
+  return BuildURL(service_url_, {"devices", device_id_, subpath}, params);
+}
+
+std::string DeviceRegistrationInfo::GetOAuthURL(
+    const std::string& subpath,
+    const data_encoding::WebParamList& params) const {
+  return BuildURL(oauth_url_, {subpath}, params);
+}
+
+std::string DeviceRegistrationInfo::GetDeviceId(ErrorPtr* error) {
+  return CheckRegistration(error) ? device_id_ : std::string();
+}
+
+bool DeviceRegistrationInfo::Load() {
+  auto value = storage_->Load();
+  const base::DictionaryValue* dict = nullptr;
+  if (!value || !value->GetAsDictionary(&dict))
+    return false;
+
+  // Get the values into temp variables first to make sure we can get
+  // all the data correctly before changing the state of this object.
+  std::string client_id;
+  if (!dict->GetString(storage_keys::kClientId, &client_id))
+    return false;
+  std::string client_secret;
+  if (!dict->GetString(storage_keys::kClientSecret, &client_secret))
+    return false;
+  std::string api_key;
+  if (!dict->GetString(storage_keys::kApiKey, &api_key))
+    return false;
+  std::string refresh_token;
+  if (!dict->GetString(storage_keys::kRefreshToken, &refresh_token))
+    return false;
+  std::string device_id;
+  if (!dict->GetString(storage_keys::kDeviceId, &device_id))
+    return false;
+  std::string oauth_url;
+  if (!dict->GetString(storage_keys::kOAuthURL, &oauth_url))
+    return false;
+  std::string service_url;
+  if (!dict->GetString(storage_keys::kServiceURL, &service_url))
+    return false;
+  std::string device_robot_account;
+  if (!dict->GetString(storage_keys::kRobotAccount, &device_robot_account))
+    return false;
+
+  client_id_            = client_id;
+  client_secret_        = client_secret;
+  api_key_              = api_key;
+  refresh_token_        = refresh_token;
+  device_id_            = device_id;
+  oauth_url_            = oauth_url;
+  service_url_          = service_url;
+  device_robot_account_ = device_robot_account;
+  return true;
+}
+
+bool DeviceRegistrationInfo::Save() const {
+  base::DictionaryValue dict;
+  dict.SetString(storage_keys::kClientId,     client_id_);
+  dict.SetString(storage_keys::kClientSecret, client_secret_);
+  dict.SetString(storage_keys::kApiKey,       api_key_);
+  dict.SetString(storage_keys::kRefreshToken, refresh_token_);
+  dict.SetString(storage_keys::kDeviceId,     device_id_);
+  dict.SetString(storage_keys::kOAuthURL,     oauth_url_);
+  dict.SetString(storage_keys::kServiceURL,   service_url_);
+  dict.SetString(storage_keys::kRobotAccount, device_robot_account_);
+  return storage_->Save(&dict);
+}
+
+bool DeviceRegistrationInfo::CheckRegistration(ErrorPtr* error) {
+  LOG(INFO) << "Checking device registration record.";
+  if (refresh_token_.empty() ||
+      device_id_.empty() ||
+      device_robot_account_.empty()) {
+    LOG(INFO) << "No valid device registration record found.";
+    Error::AddTo(error, kErrorDomainGCD, "device_not_registered",
+                 "No valid device registration record found");
+    return false;
+  }
+
+  LOG(INFO) << "Device registration record found.";
+  return ValidateAndRefreshAccessToken(error);
+}
+
+bool DeviceRegistrationInfo::ValidateAndRefreshAccessToken(ErrorPtr* error) {
+  LOG(INFO) << "Checking access token expiration.";
+  if (!access_token_.empty() &&
+      !access_token_expiration_.is_null() &&
+      access_token_expiration_ > base::Time::Now()) {
+    LOG(INFO) << "Access token is still valid.";
+    return true;
+  }
+
+  auto response = http::PostFormData(GetOAuthURL("token"), {
+    {"refresh_token", refresh_token_},
+    {"client_id", client_id_},
+    {"client_secret", client_secret_},
+    {"grant_type", "refresh_token"},
+  }, transport_, error);
+  if (!response)
+    return false;
+
+  auto json = ParseOAuthResponse(response.get(), error);
+  if (!json)
+    return false;
+
+  int expires_in = 0;
+  if (!json->GetString("access_token", &access_token_) ||
+      !json->GetInteger("expires_in", &expires_in) ||
+      access_token_.empty() ||
+      expires_in <= 0) {
+    LOG(ERROR) << "Access token unavailable.";
+    Error::AddTo(error, kErrorDomainOAuth2, "unexpected_server_response",
+                 "Access token unavailable");
+    return false;
+  }
+
+  access_token_expiration_ = base::Time::Now() +
+                             base::TimeDelta::FromSeconds(expires_in);
+
+  LOG(INFO) << "Access token is refreshed for additional " << expires_in
+            << " seconds.";
+  return true;
+}
+
+std::unique_ptr<base::Value> DeviceRegistrationInfo::GetDeviceInfo(
+    ErrorPtr* error) {
+  if (!CheckRegistration(error))
+    return std::unique_ptr<base::Value>();
+
+  auto response = http::Get(GetDeviceURL(),
+                            {GetAuthorizationHeader()}, transport_, error);
+  int status_code = 0;
+  std::unique_ptr<base::DictionaryValue> json =
+      http::ParseJsonResponse(response.get(), &status_code, error);
+  if (json) {
+    if (status_code >= http::status_code::BadRequest) {
+      LOG(WARNING) << "Failed to retrieve the device info. Response code = "
+                   << status_code;
+      ParseGCDError(json.get(), error);
+      return std::unique_ptr<base::Value>();
+    }
+  }
+  return std::unique_ptr<base::Value>(json.release());
+}
+
+bool CheckParam(const std::string& param_name,
+                const std::string& param_value,
+                ErrorPtr* error) {
+  if (!param_value.empty())
+    return true;
+
+  Error::AddToPrintf(error, kErrorDomainBuffet, "missing_parameter",
+                     "Parameter %s not specified", param_name.c_str());
+  return false;
+}
+
+std::string DeviceRegistrationInfo::StartRegistration(
+    const std::map<std::string, std::shared_ptr<base::Value>>& params,
+    ErrorPtr* error) {
+  GetParamValue(params, storage_keys::kClientId, &client_id_);
+  GetParamValue(params, storage_keys::kClientSecret, &client_secret_);
+  GetParamValue(params, storage_keys::kApiKey, &api_key_);
+  GetParamValue(params, storage_keys::kDeviceId, &device_id_);
+  GetParamValue(params, storage_keys::kDeviceKind, &device_kind_);
+  GetParamValue(params, storage_keys::kSystemName, &system_name_);
+  GetParamValue(params, storage_keys::kDisplayName, &display_name_);
+  GetParamValue(params, storage_keys::kOAuthURL, &oauth_url_);
+  GetParamValue(params, storage_keys::kServiceURL, &service_url_);
+
+  if (!CheckParam(storage_keys::kClientId, client_id_, error))
+    return std::string();
+  if (!CheckParam(storage_keys::kClientSecret, client_secret_, error))
+    return std::string();
+  if (!CheckParam(storage_keys::kApiKey, api_key_, error))
+    return std::string();
+  if (!CheckParam(storage_keys::kDeviceKind, device_kind_, error))
+    return std::string();
+  if (!CheckParam(storage_keys::kSystemName, system_name_, error))
+    return std::string();
+  if (!CheckParam(storage_keys::kOAuthURL, oauth_url_, error))
+    return std::string();
+  if (!CheckParam(storage_keys::kServiceURL, service_url_, error))
+    return std::string();
+
+  std::vector<std::pair<std::string, std::vector<std::string>>> commands = {
+    {"SetDeviceConfiguration", {"data"}}
+  };
+
+  base::DictionaryValue req_json;
+  base::ListValue* set_device_configuration_params = new base::ListValue;
+  base::DictionaryValue* param1 = new base::DictionaryValue;
+  param1->SetString("name", "data");
+  set_device_configuration_params->Append(param1);
+
+  base::ListValue* vendor_commands = new base::ListValue;
+  for (const auto& pair : commands) {
+    base::ListValue* params = new base::ListValue;
+    for (const auto& param_name : pair.second) {
+      base::DictionaryValue* param = new base::DictionaryValue;
+      param->SetString("name", param_name);
+      params->Append(param);
+    }
+    base::DictionaryValue* command = new base::DictionaryValue;
+    command->SetString("name", pair.first);
+    command->Set("parameter", params);
+    vendor_commands->Append(command);
+  }
+
+  req_json.SetString("oauthClientId", client_id_);
+  req_json.SetString("deviceDraft.deviceKind", device_kind_);
+  req_json.SetString("deviceDraft.systemName", system_name_);
+  req_json.SetString("deviceDraft.displayName", display_name_);
+  req_json.SetString("deviceDraft.channel.supportedType", "xmpp");
+  req_json.Set("deviceDraft.commands.base.vendorCommands", vendor_commands);
+
+  std::string url = GetServiceURL("registrationTickets", {{"key", api_key_}});
+  auto resp_json = http::ParseJsonResponse(
+      http::PostJson(url, &req_json, transport_, error).get(), nullptr, error);
+  if (!resp_json)
+    return std::string();
+
+  if (!resp_json->GetString("id", &ticket_id_)) {
+    Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
+                 "Device ID missing");
+    return std::string();
+  }
+
+  std::string auth_url = GetOAuthURL("auth", {
+    {"scope", "https://www.googleapis.com/auth/clouddevices"},
+    {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
+    {"response_type", "code"},
+    {"client_id", client_id_}
+  });
+
+  base::DictionaryValue json;
+  json.SetString("ticket_id", ticket_id_);
+  json.SetString("auth_url", auth_url);
+
+  std::string ret;
+  base::JSONWriter::Write(&json, &ret);
+  return ret;
+}
+
+bool DeviceRegistrationInfo::FinishRegistration(
+    const std::string& user_auth_code, ErrorPtr* error) {
+  if (ticket_id_.empty()) {
+    LOG(ERROR) << "Finish registration without ticket ID";
+    Error::AddTo(error, kErrorDomainBuffet, "registration_not_started",
+                 "Device registration not started");
+    return false;
+  }
+
+  std::string url = GetServiceURL("registrationTickets/" + ticket_id_);
+  std::unique_ptr<http::Response> response;
+  if (!user_auth_code.empty()) {
+    response = http::PostFormData(GetOAuthURL("token"), {
+      {"code", user_auth_code},
+      {"client_id", client_id_},
+      {"client_secret", client_secret_},
+      {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
+      {"grant_type", "authorization_code"}
+    }, transport_, error);
+    if (!response)
+      return false;
+
+    auto json_resp = ParseOAuthResponse(response.get(), error);
+    if (!json_resp)
+      return false;
+
+    std::string user_access_token;
+    std::string token_type;
+    if (!json_resp->GetString("access_token", &user_access_token) ||
+        !json_resp->GetString("token_type", &token_type)) {
+      Error::AddTo(error, kErrorDomainOAuth2, "unexpected_response",
+                   "User access_token is missing in response");
+      return false;
+    }
+
+    base::DictionaryValue user_info;
+    user_info.SetString("userEmail", "me");
+    response = http::PatchJson(
+        url, &user_info, {BuildAuthHeader(token_type, user_access_token)},
+        transport_, error);
+
+    auto json = http::ParseJsonResponse(response.get(), nullptr, error);
+    if (!json)
+      return false;
+  }
+
+  std::string auth_code;
+  url += "/finalize?key=" + api_key_;
+  LOG(INFO) << "Sending request to: " << url;
+  response = http::PostBinary(url, nullptr, 0, transport_, error);
+  if (!response)
+    return false;
+  auto json_resp = http::ParseJsonResponse(response.get(), nullptr, error);
+  if (!json_resp)
+    return false;
+  if (!response->IsSuccessful()) {
+    ParseGCDError(json_resp.get(), error);
+    return false;
+  }
+  if (!json_resp->GetString("robotAccountEmail", &device_robot_account_) ||
+      !json_resp->GetString("robotAccountAuthorizationCode", &auth_code) ||
+      !json_resp->GetString("deviceDraft.id", &device_id_)) {
+    Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
+                 "Device account missing in response");
+    return false;
+  }
+
+  // Now get access_token and refresh_token
+  response = http::PostFormData(GetOAuthURL("token"), {
+    {"code", auth_code},
+    {"client_id", client_id_},
+    {"client_secret", client_secret_},
+    {"redirect_uri", "oob"},
+    {"scope", "https://www.googleapis.com/auth/clouddevices"},
+    {"grant_type", "authorization_code"}
+  }, transport_, error);
+  if (!response)
+    return false;
+
+  json_resp = ParseOAuthResponse(response.get(), error);
+  int expires_in = 0;
+  if (!json_resp ||
+      !json_resp->GetString("access_token", &access_token_) ||
+      !json_resp->GetString("refresh_token", &refresh_token_) ||
+      !json_resp->GetInteger("expires_in", &expires_in) ||
+      access_token_.empty() ||
+      refresh_token_.empty() ||
+      expires_in <= 0) {
+    Error::AddTo(error, kErrorDomainGCD, "unexpected_response",
+                  "Device access_token missing in response");
+    return false;
+  }
+
+  access_token_expiration_ = base::Time::Now() +
+                             base::TimeDelta::FromSeconds(expires_in);
+
+  Save();
+  return true;
+}
+
+}  // namespace buffet
diff --git a/buffet/device_registration_info.h b/buffet/device_registration_info.h
new file mode 100644
index 0000000..da0d409
--- /dev/null
+++ b/buffet/device_registration_info.h
@@ -0,0 +1,146 @@
+// 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_DEVICE_REGISTRATION_INFO_H_
+#define BUFFET_DEVICE_REGISTRATION_INFO_H_
+
+#include <string>
+#include <map>
+#include <memory>
+#include <utility>
+
+#include <base/basictypes.h>
+#include <base/time/time.h>
+
+#include "buffet/data_encoding.h"
+#include "buffet/error.h"
+#include "buffet/http_transport.h"
+#include "buffet/storage_interface.h"
+
+namespace base {
+  class Value;
+}  // namespace base
+
+namespace buffet {
+
+extern const char kErrorDomainOAuth2[];
+extern const char kErrorDomainGCD[];
+extern const char kErrorDomainGCDServer[];
+extern const char kErrorDomainBuffet[];
+
+// The DeviceRegistrationInfo class represents device registration information.
+class DeviceRegistrationInfo {
+ public:
+  // This is a helper class for unit testing.
+  class TestHelper;
+  // Default-constructed uses CURL HTTP transport.
+  DeviceRegistrationInfo();
+  // This constructor allows to pass in a custom HTTP transport
+  // (mainly for testing).
+  DeviceRegistrationInfo(std::shared_ptr<http::Transport> transport,
+                         std::shared_ptr<StorageInterface> storage);
+
+  // Returns the authorization HTTP header that can be used to talk
+  // to GCD server for authenticated device communication.
+  // Make sure ValidateAndRefreshAccessToken() is called before this call.
+  std::pair<std::string, std::string> GetAuthorizationHeader() const;
+
+  // Returns the GCD service request URL. If |subpath| is specified, it is
+  // appended to the base URL which is normally
+  //    https://www.googleapis.com/clouddevices/v1/".
+  // If |params| are specified, each key-value pair is formatted using
+  // data_encoding::WebParamsEncode() and appended to URL as a query
+  // string.
+  // So, calling:
+  //    GetServiceURL("ticket", {{"key","apiKey"}})
+  // will return something like:
+  //    https://www.googleapis.com/clouddevices/v1/ticket?key=apiKey
+  std::string GetServiceURL(
+      const std::string& subpath = {},
+      const data_encoding::WebParamList& params = {}) const;
+
+  // Returns a service URL to access the registered device on GCD server.
+  // The base URL used to construct the full URL looks like this:
+  //    https://www.googleapis.com/clouddevices/v1/devices/<device_id>/
+  std::string GetDeviceURL(
+    const std::string& subpath = {},
+    const data_encoding::WebParamList& params = {}) const;
+
+  // Similar to GetServiceURL, GetOAuthURL() returns a URL of OAuth 2.0 server.
+  // The base URL used is https://accounts.google.com/o/oauth2/.
+  std::string GetOAuthURL(
+    const std::string& subpath = {},
+    const data_encoding::WebParamList& params = {}) const;
+
+  // Returns the registered device ID (GUID) or empty string if failed
+  std::string GetDeviceId(ErrorPtr* error);
+
+  // Loads the device registration information from cache.
+  bool Load();
+
+  // Checks for the valid device registration as well as refreshes
+  // the device access token, if available.
+  bool CheckRegistration(ErrorPtr* error);
+
+  // Gets the full device description JSON object, or nullptr if
+  // the device is not registered or communication failure.
+  std::unique_ptr<base::Value> GetDeviceInfo(ErrorPtr* error);
+
+  // Starts device registration procedure. |params| are a list of
+  // key-value pairs of device information, such as client_id, client_secret,
+  // and so on. If a particular key-value pair is omitted, a default value
+  // is used when possible. Returns a device claim ID on success.
+  std::string StartRegistration(
+    const std::map<std::string, std::shared_ptr<base::Value>>& params,
+    ErrorPtr* error);
+
+  // Finalizes the device registration. If |user_auth_code| is provided, then
+  // the device record is populated with user email on user's behalf. Otherwise
+  // the user is responsible to issue a PATCH request to provide a valid
+  // email address before calling FinishRegistration.
+  bool FinishRegistration(const std::string& user_auth_code,
+                          ErrorPtr* error);
+
+ private:
+  // Saves the device registration to cache.
+  bool Save() const;
+
+  // Makes sure the access token is available and up-to-date.
+  bool ValidateAndRefreshAccessToken(ErrorPtr* error);
+
+  // Persistent data. Some of default values for testing purposes are used.
+  // TODO(avakulenko): remove these default values in the future.
+  // http://crbug.com/364692
+  std::string client_id_ =
+    "583509257718-lnmeofvjef3b1tm33sbjmckfnumfvn8j.apps.googleusercontent.com";
+  std::string client_secret_ = "6fzZwQhgnsHhvYYvvFdpv5SD";
+  std::string api_key_ = "AIzaSyAp7KVig5m9g4LWWKr79mTS8sXWfUU6w9g";
+  std::string refresh_token_;
+  std::string device_id_;
+  std::string device_robot_account_;
+  std::string oauth_url_ = "https://accounts.google.com/o/oauth2/";
+  std::string service_url_ =
+    "https://www-googleapis-staging.sandbox.google.com/"
+    "clouddevices/v1/";
+
+  // Transient data
+  std::string access_token_;
+  base::Time access_token_expiration_;
+  std::string ticket_id_;
+  std::string device_kind_ = "vendor";
+  std::string system_name_ = "coffee_pot";
+  std::string display_name_ = "Coffee Pot";
+
+  // HTTP transport used for communications.
+  std::shared_ptr<http::Transport> transport_;
+  // Serialization interface to save and load device registration info.
+  std::shared_ptr<StorageInterface> storage_;
+
+  friend class TestHelper;
+  DISALLOW_COPY_AND_ASSIGN(DeviceRegistrationInfo);
+};
+
+}  // namespace buffet
+
+#endif  // BUFFET_DEVICE_REGISTRATION_INFO_H_
diff --git a/buffet/device_registration_info_unittest.cc b/buffet/device_registration_info_unittest.cc
new file mode 100644
index 0000000..5108e35
--- /dev/null
+++ b/buffet/device_registration_info_unittest.cc
@@ -0,0 +1,392 @@
+// 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 <base/json/json_reader.h>
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "buffet/bind_lambda.h"
+#include "buffet/device_registration_info.h"
+#include "buffet/device_registration_storage_keys.h"
+#include "buffet/http_request.h"
+#include "buffet/http_transport_fake.h"
+#include "buffet/mime_utils.h"
+#include "buffet/storage_impls.h"
+
+using namespace buffet;          // NOLINT(build/namespaces)
+using namespace buffet::http;    // NOLINT(build/namespaces)
+
+namespace {
+
+namespace test_data {
+
+const char kServiceURL[]           = "http://gcd.server.com/";
+const char kOAuthURL[]             = "http://oauth.server.com/";
+const char kApiKey[]               = "GOadRdTf9FERf0k4w6EFOof56fUJ3kFDdFL3d7f";
+const char kClientId[]             = "123543821385-sfjkjshdkjhfk234sdfsdfkskd"
+                                     "fkjh7f.apps.googleusercontent.com";
+const char kClientSecret[]         = "5sdGdGlfolGlrFKfdFlgP6FG";
+const char kDeviceId[]             = "4a7ea2d1-b331-1e1f-b206-e863c7635196";
+const char kClaimTicketId[]        = "RTcUE";
+const char kAccessToken[]          = "ya29.1.AADtN_V-dLUM-sVZ0qVjG9Dxm5NgdS9J"
+                                     "Mx_JLUqhC9bED_YFjzHZtYt65ZzXCS35NMAeaVZ"
+                                     "Dei530-w0yE2urpQ";
+const char kRefreshToken[]         = "1/zQmxR6PKNvhcxf9SjXUrCjcmCrcqRKXctc6cp"
+                                     "1nI-GQ";
+const char kRobotAccountAuthCode[] = "4/Mf_ujEhPejVhOq-OxW9F5cSOnWzx."
+                                     "YgciVjTYGscRshQV0ieZDAqiTIjMigI";
+const char kRobotAccountEmail[]    = "6ed0b3f54f9bd619b942f4ad2441c252@"
+                                     "clouddevices.gserviceaccount.com";
+const char kUserAccountAuthCode[]  = "2/sd_GD1TGFKpJOLJ34-0g5fK0fflp.GlT"
+                                     "I0F5g7hNtFgj5HFGOf8FlGK9eflO";
+const char kUserAccessToken[]      = "sd56.4.FGDjG_F-gFGF-dFG6gGOG9Dxm5NgdS9"
+                                     "JMx_JLUqhC9bED_YFjLKjlkjLKJlkjLKjlKJea"
+                                     "VZDei530-w0yE2urpQ";
+const char kUserRefreshToken[]     = "1/zQLKjlKJlkLkLKjLkjLKjLkjLjLkjl0ftc6"
+                                     "cp1nI-GQ";
+
+}  // namespace test_data
+
+// Fill in the storage with default environment information (URLs, etc).
+void InitDefaultStorage(base::DictionaryValue* data) {
+  data->SetString(storage_keys::kClientId, test_data::kClientId);
+  data->SetString(storage_keys::kClientSecret, test_data::kClientSecret);
+  data->SetString(storage_keys::kApiKey, test_data::kApiKey);
+  data->SetString(storage_keys::kRefreshToken, "");
+  data->SetString(storage_keys::kDeviceId, "");
+  data->SetString(storage_keys::kOAuthURL, test_data::kOAuthURL);
+  data->SetString(storage_keys::kServiceURL, test_data::kServiceURL);
+  data->SetString(storage_keys::kRobotAccount, "");
+}
+
+// Add the test device registration information.
+void SetDefaultDeviceRegistration(base::DictionaryValue* data) {
+  data->SetString(storage_keys::kRefreshToken, test_data::kRefreshToken);
+  data->SetString(storage_keys::kDeviceId, test_data::kDeviceId);
+  data->SetString(storage_keys::kRobotAccount, test_data::kRobotAccountEmail);
+}
+
+void OAuth2Handler(const fake::ServerRequest& request,
+                  fake::ServerResponse* response) {
+  base::DictionaryValue json;
+  if (request.GetFormField("grant_type") == "refresh_token") {
+    // Refresh device access token.
+    EXPECT_EQ(test_data::kRefreshToken, request.GetFormField("refresh_token"));
+    EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id"));
+    EXPECT_EQ(test_data::kClientSecret, request.GetFormField("client_secret"));
+    json.SetString("access_token", test_data::kAccessToken);
+  } else if (request.GetFormField("grant_type") == "authorization_code") {
+    // Obtain access token.
+    std::string code = request.GetFormField("code");
+    if (code == test_data::kUserAccountAuthCode) {
+      // Get user access token.
+      EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id"));
+      EXPECT_EQ(test_data::kClientSecret,
+                request.GetFormField("client_secret"));
+      EXPECT_EQ("urn:ietf:wg:oauth:2.0:oob",
+                request.GetFormField("redirect_uri"));
+      json.SetString("access_token", test_data::kUserAccessToken);
+      json.SetString("token_type", "Bearer");
+      json.SetString("refresh_token", test_data::kUserRefreshToken);
+    } else if (code == test_data::kRobotAccountAuthCode) {
+      // Get device access token.
+      EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id"));
+      EXPECT_EQ(test_data::kClientSecret,
+                request.GetFormField("client_secret"));
+      EXPECT_EQ("oob", request.GetFormField("redirect_uri"));
+      EXPECT_EQ("https://www.googleapis.com/auth/clouddevices",
+                request.GetFormField("scope"));
+      json.SetString("access_token", test_data::kAccessToken);
+      json.SetString("token_type", "Bearer");
+      json.SetString("refresh_token", test_data::kRefreshToken);
+    } else {
+      FAIL() << "Unexpected authorization code";
+    }
+  } else {
+    FAIL() << "Unexpected grant type";
+  }
+  json.SetInteger("expires_in", 3600);
+  response->ReplyJson(status_code::Ok, &json);
+}
+
+void DeviceInfoHandler(const fake::ServerRequest& request,
+                       fake::ServerResponse* response) {
+  std::string auth = "Bearer ";
+  auth += test_data::kAccessToken;
+  EXPECT_EQ(auth, request.GetHeader(http::request_header::kAuthorization));
+  response->ReplyJson(status_code::Ok, {
+    {"channel.supportedType", "xmpp"},
+    {"deviceKind", "vendor"},
+    {"id", test_data::kDeviceId},
+    {"kind", "clouddevices#device"},
+  });
+}
+
+void FinalizeTicketHandler(const fake::ServerRequest& request,
+                           fake::ServerResponse* response) {
+  EXPECT_EQ(test_data::kApiKey, request.GetFormField("key"));
+  EXPECT_TRUE(request.GetData().empty());
+
+  response->ReplyJson(status_code::Ok, {
+    {"id", test_data::kClaimTicketId},
+    {"kind", "clouddevices#registrationTicket"},
+    {"oauthClientId", test_data::kClientId},
+    {"userEmail", "user@email.com"},
+    {"deviceDraft.id", test_data::kDeviceId},
+    {"deviceDraft.kind", "clouddevices#device"},
+    {"deviceDraft.channel.supportedType", "xmpp"},
+    {"robotAccountEmail", test_data::kRobotAccountEmail},
+    {"robotAccountAuthorizationCode", test_data::kRobotAccountAuthCode},
+  });
+}
+
+}  // anonymous namespace
+
+// This is a helper class that allows the unit tests to set the private
+// member DeviceRegistrationInfo::ticket_id_, since TestHelper is declared
+// as a friend to DeviceRegistrationInfo.
+class DeviceRegistrationInfo::TestHelper {
+ public:
+  static void SetTestTicketId(DeviceRegistrationInfo* info) {
+    info->ticket_id_ = test_data::kClaimTicketId;
+  }
+};
+
+class DeviceRegistrationInfoTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() override {
+    InitDefaultStorage(&data);
+    storage = std::make_shared<MemStorage>();
+    storage->Save(&data);
+    transport = std::make_shared<fake::Transport>();
+    dev_reg = std::unique_ptr<DeviceRegistrationInfo>(
+        new DeviceRegistrationInfo(transport, storage));
+  }
+
+  base::DictionaryValue data;
+  std::shared_ptr<MemStorage> storage;
+  std::shared_ptr<fake::Transport> transport;
+  std::unique_ptr<DeviceRegistrationInfo> dev_reg;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(DeviceRegistrationInfoTest, GetServiceURL) {
+  EXPECT_TRUE(dev_reg->Load());
+  EXPECT_EQ(test_data::kServiceURL, dev_reg->GetServiceURL());
+  std::string url = test_data::kServiceURL;
+  url += "registrationTickets";
+  EXPECT_EQ(url, dev_reg->GetServiceURL("registrationTickets"));
+  url += "?key=";
+  url += test_data::kApiKey;
+  EXPECT_EQ(url, dev_reg->GetServiceURL("registrationTickets", {
+    {"key", test_data::kApiKey}
+  }));
+  url += "&restart=true";
+  EXPECT_EQ(url, dev_reg->GetServiceURL("registrationTickets", {
+    {"key", test_data::kApiKey},
+    {"restart", "true"},
+  }));
+}
+
+TEST_F(DeviceRegistrationInfoTest, GetOAuthURL) {
+  EXPECT_TRUE(dev_reg->Load());
+  EXPECT_EQ(test_data::kOAuthURL, dev_reg->GetOAuthURL());
+  std::string url = test_data::kOAuthURL;
+  url += "auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fclouddevices&";
+  url += "redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&";
+  url += "response_type=code&";
+  url += "client_id=";
+  url += test_data::kClientId;
+  EXPECT_EQ(url, dev_reg->GetOAuthURL("auth", {
+    {"scope", "https://www.googleapis.com/auth/clouddevices"},
+    {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
+    {"response_type", "code"},
+    {"client_id", test_data::kClientId}
+  }));
+}
+
+TEST_F(DeviceRegistrationInfoTest, CheckRegistration) {
+  EXPECT_TRUE(dev_reg->Load());
+  EXPECT_FALSE(dev_reg->CheckRegistration(nullptr));
+  EXPECT_EQ(0, transport->GetRequestCount());
+
+  SetDefaultDeviceRegistration(&data);
+  storage->Save(&data);
+  EXPECT_TRUE(dev_reg->Load());
+
+  transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
+                        base::Bind(OAuth2Handler));
+  transport->ResetRequestCount();
+  EXPECT_TRUE(dev_reg->CheckRegistration(nullptr));
+  EXPECT_EQ(1, transport->GetRequestCount());
+}
+
+TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) {
+  SetDefaultDeviceRegistration(&data);
+  storage->Save(&data);
+  EXPECT_TRUE(dev_reg->Load());
+
+  transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
+                        base::Bind(OAuth2Handler));
+  transport->AddHandler(dev_reg->GetDeviceURL(), request_type::kGet,
+                        base::Bind(DeviceInfoHandler));
+  transport->ResetRequestCount();
+  auto device_info = dev_reg->GetDeviceInfo(nullptr);
+  EXPECT_EQ(2, transport->GetRequestCount());
+  EXPECT_NE(nullptr, device_info.get());
+  base::DictionaryValue* dict = nullptr;
+  EXPECT_TRUE(device_info->GetAsDictionary(&dict));
+  std::string id;
+  EXPECT_TRUE(dict->GetString("id", &id));
+  EXPECT_EQ(test_data::kDeviceId, id);
+}
+
+TEST_F(DeviceRegistrationInfoTest, GetDeviceId) {
+  SetDefaultDeviceRegistration(&data);
+  storage->Save(&data);
+  EXPECT_TRUE(dev_reg->Load());
+
+  transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
+                        base::Bind(OAuth2Handler));
+  transport->AddHandler(dev_reg->GetDeviceURL(), request_type::kGet,
+                        base::Bind(DeviceInfoHandler));
+  std::string id = dev_reg->GetDeviceId(nullptr);
+  EXPECT_EQ(test_data::kDeviceId, id);
+}
+
+TEST_F(DeviceRegistrationInfoTest, StartRegistration) {
+  EXPECT_TRUE(dev_reg->Load());
+
+  auto create_ticket = [](const fake::ServerRequest& request,
+                          fake::ServerResponse* response) {
+    EXPECT_EQ(test_data::kApiKey, request.GetFormField("key"));
+    auto json = request.GetDataAsJson();
+    EXPECT_NE(nullptr, json.get());
+    std::string value;
+    EXPECT_TRUE(json->GetString("deviceDraft.channel.supportedType", &value));
+    EXPECT_EQ("xmpp", value);
+    EXPECT_TRUE(json->GetString("oauthClientId", &value));
+    EXPECT_EQ(test_data::kClientId, value);
+    EXPECT_TRUE(json->GetString("deviceDraft.deviceKind", &value));
+    EXPECT_EQ("vendor", value);
+
+    base::DictionaryValue json_resp;
+    json_resp.SetString("id", test_data::kClaimTicketId);
+    json_resp.SetString("kind", "clouddevices#registrationTicket");
+    json_resp.SetString("oauthClientId", test_data::kClientId);
+    base::DictionaryValue* device_draft = nullptr;
+    EXPECT_TRUE(json->GetDictionary("deviceDraft", &device_draft));
+    device_draft = device_draft->DeepCopy();
+    device_draft->SetString("id", test_data::kDeviceId);
+    device_draft->SetString("kind", "clouddevices#device");
+    json_resp.Set("deviceDraft", device_draft);
+
+    response->ReplyJson(status_code::Ok, &json_resp);
+  };
+
+  transport->AddHandler(dev_reg->GetServiceURL("registrationTickets"),
+                        request_type::kPost,
+                        base::Bind(create_ticket));
+  std::map<std::string, std::shared_ptr<base::Value>> params;
+  std::string json_resp = dev_reg->StartRegistration(params, nullptr);
+  auto json = std::unique_ptr<base::Value>(base::JSONReader::Read(json_resp));
+  EXPECT_NE(nullptr, json.get());
+  base::DictionaryValue* dict = nullptr;
+  EXPECT_TRUE(json->GetAsDictionary(&dict));
+  std::string value;
+  EXPECT_TRUE(dict->GetString("ticket_id", &value));
+  EXPECT_EQ(test_data::kClaimTicketId, value);
+}
+
+TEST_F(DeviceRegistrationInfoTest, FinishRegistration_NoAuth) {
+  // Test finalizing ticket with no user authorization token.
+  // This assumes that a client would patch in their email separately.
+  EXPECT_TRUE(dev_reg->Load());
+
+  // General ticket finalization handler.
+  std::string ticket_url =
+      dev_reg->GetServiceURL("registrationTickets/" +
+                             std::string(test_data::kClaimTicketId));
+  transport->AddHandler(ticket_url + "/finalize", request_type::kPost,
+                        base::Bind(FinalizeTicketHandler));
+
+  transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
+                        base::Bind(OAuth2Handler));
+
+  storage->reset_save_count();
+  DeviceRegistrationInfo::TestHelper::SetTestTicketId(dev_reg.get());
+  EXPECT_TRUE(dev_reg->FinishRegistration("", nullptr));
+  EXPECT_EQ(1, storage->save_count());  // The device info must have been saved.
+  EXPECT_EQ(2, transport->GetRequestCount());
+
+  // Validate the device info saved to storage...
+  auto storage_data = storage->Load();
+  base::DictionaryValue* dict = nullptr;
+  EXPECT_TRUE(storage_data->GetAsDictionary(&dict));
+  std::string value;
+  EXPECT_TRUE(dict->GetString(storage_keys::kApiKey, &value));
+  EXPECT_EQ(test_data::kApiKey, value);
+  EXPECT_TRUE(dict->GetString(storage_keys::kClientId, &value));
+  EXPECT_EQ(test_data::kClientId, value);
+  EXPECT_TRUE(dict->GetString(storage_keys::kClientSecret, &value));
+  EXPECT_EQ(test_data::kClientSecret, value);
+  EXPECT_TRUE(dict->GetString(storage_keys::kDeviceId, &value));
+  EXPECT_EQ(test_data::kDeviceId, value);
+  EXPECT_TRUE(dict->GetString(storage_keys::kOAuthURL, &value));
+  EXPECT_EQ(test_data::kOAuthURL, value);
+  EXPECT_TRUE(dict->GetString(storage_keys::kRefreshToken, &value));
+  EXPECT_EQ(test_data::kRefreshToken, value);
+  EXPECT_TRUE(dict->GetString(storage_keys::kRobotAccount, &value));
+  EXPECT_EQ(test_data::kRobotAccountEmail, value);
+  EXPECT_TRUE(dict->GetString(storage_keys::kServiceURL, &value));
+  EXPECT_EQ(test_data::kServiceURL, value);
+}
+
+TEST_F(DeviceRegistrationInfoTest, FinishRegistration_Auth) {
+  // Test finalizing ticket with user authorization token.
+  EXPECT_TRUE(dev_reg->Load());
+
+  // General ticket finalization handler.
+  std::string ticket_url =
+      dev_reg->GetServiceURL("registrationTickets/" +
+                             std::string(test_data::kClaimTicketId));
+  transport->AddHandler(ticket_url + "/finalize", request_type::kPost,
+                        base::Bind(FinalizeTicketHandler));
+
+  transport->AddHandler(dev_reg->GetOAuthURL("token"), request_type::kPost,
+                        base::Bind(OAuth2Handler));
+
+  // Handle patching in the user email onto the device record.
+  auto email_patch_handler = [](const fake::ServerRequest& request,
+                                fake::ServerResponse* response) {
+    std::string auth_header = "Bearer ";
+    auth_header += test_data::kUserAccessToken;
+    EXPECT_EQ(auth_header,
+              request.GetHeader(http::request_header::kAuthorization));
+    auto json = request.GetDataAsJson();
+    EXPECT_NE(nullptr, json.get());
+    std::string value;
+    EXPECT_TRUE(json->GetString("userEmail", &value));
+    EXPECT_EQ("me", value);
+
+    response->ReplyJson(status_code::Ok, {
+      {"id", test_data::kClaimTicketId},
+      {"kind", "clouddevices#registrationTicket"},
+      {"oauthClientId", test_data::kClientId},
+      {"userEmail", "user@email.com"},
+      {"deviceDraft.id", test_data::kDeviceId},
+      {"deviceDraft.kind", "clouddevices#device"},
+      {"deviceDraft.channel.supportedType", "xmpp"},
+    });
+  };
+  transport->AddHandler(ticket_url, request_type::kPatch,
+                        base::Bind(email_patch_handler));
+
+  storage->reset_save_count();
+  DeviceRegistrationInfo::TestHelper::SetTestTicketId(dev_reg.get());
+  EXPECT_TRUE(dev_reg->FinishRegistration(test_data::kUserAccountAuthCode,
+                                          nullptr));
+  EXPECT_EQ(1, storage->save_count());  // The device info must have been saved.
+  EXPECT_EQ(4, transport->GetRequestCount());
+}
diff --git a/buffet/device_registration_storage_keys.h b/buffet/device_registration_storage_keys.h
new file mode 100644
index 0000000..a6c9239
--- /dev/null
+++ b/buffet/device_registration_storage_keys.h
@@ -0,0 +1,31 @@
+// 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_DEVICE_REGISTRATION_STORAGE_KEYS_H_
+#define BUFFET_DEVICE_REGISTRATION_STORAGE_KEYS_H_
+
+// These are the keys used to identify specific device registration information
+// being saved to a storage. Used mostly internally by DeviceRegistrationInfo
+// but also exposed so that tests can access them.
+namespace buffet {
+namespace storage_keys {
+
+// Persistent keys
+extern const char kClientId[];
+extern const char kClientSecret[];
+extern const char kApiKey[];
+extern const char kRefreshToken[];
+extern const char kDeviceId[];
+extern const char kOAuthURL[];
+extern const char kServiceURL[];
+extern const char kRobotAccount[];
+// Transient keys
+extern const char kDeviceKind[];
+extern const char kSystemName[];
+extern const char kDisplayName[];
+
+}  // namespace storage_keys
+}  // namespace buffet
+
+#endif  // BUFFET_DEVICE_REGISTRATION_STORAGE_KEYS_H_
diff --git a/buffet/error.cc b/buffet/error.cc
new file mode 100644
index 0000000..db5543d
--- /dev/null
+++ b/buffet/error.cc
@@ -0,0 +1,80 @@
+// 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/error.h"
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+using buffet::Error;
+using buffet::ErrorPtr;
+
+ErrorPtr Error::Create(const std::string& domain,
+                       const std::string& code,
+                       const std::string& message) {
+  return Create(domain, code, message, ErrorPtr());
+}
+
+ErrorPtr Error::Create(const std::string& domain,
+                       const std::string& code,
+                       const std::string& message,
+                       ErrorPtr inner_error) {
+  LOG(ERROR) << "Error::Create: Domain=" << domain
+             << ", Code=" << code << ", Message=" << message;
+  return ErrorPtr(new Error(domain, code, message, std::move(inner_error)));
+}
+
+void Error::AddTo(ErrorPtr* error, const std::string& domain,
+                  const std::string& code, const std::string& message) {
+  if (error) {
+    *error = Create(domain, code, message, std::move(*error));
+  } else {
+    // Create already logs the error, but if |error| is nullptr,
+    // we still want to log the error...
+    LOG(ERROR) << "Error::Create: Domain=" << domain
+               << ", Code=" << code << ", Message=" << message;
+  }
+}
+
+void Error::AddToPrintf(ErrorPtr* error, const std::string& domain,
+                        const std::string& code, const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  std::string message = base::StringPrintV(format, ap);
+  va_end(ap);
+  AddTo(error, domain, code, message);
+}
+
+bool Error::HasDomain(const std::string& domain) const {
+  const Error* err = this;
+  while (err) {
+    if (err->GetDomain() == domain)
+      return true;
+    err = err->GetInnerError();
+  }
+  return false;
+}
+
+bool Error::HasError(const std::string& domain, const std::string& code) const {
+  const Error* err = this;
+  while (err) {
+    if (err->GetDomain() == domain && err->GetCode() == code)
+      return true;
+    err = err->GetInnerError();
+  }
+  return false;
+}
+
+const Error* Error::GetFirstError() const {
+  const Error* err = this;
+  while (err->GetInnerError())
+    err = err->GetInnerError();
+  return err;
+}
+
+Error::Error(const std::string& domain, const std::string& code,
+             const std::string& message, ErrorPtr inner_error) :
+    domain_(domain), code_(code), message_(message),
+    inner_error_(std::move(inner_error)) {
+}
diff --git a/buffet/error.h b/buffet/error.h
new file mode 100644
index 0000000..4e90c2b
--- /dev/null
+++ b/buffet/error.h
@@ -0,0 +1,80 @@
+// 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_ERROR_H_
+#define BUFFET_ERROR_H_
+
+#include <memory>
+#include <string>
+
+#include <base/basictypes.h>
+
+namespace buffet {
+
+class Error;  // Forward declaration.
+
+typedef std::unique_ptr<Error> ErrorPtr;
+
+class Error {
+ public:
+  virtual ~Error() = default;
+
+  // Creates an instance of Error class.
+  static ErrorPtr Create(const std::string& domain, const std::string& code,
+                         const std::string& message);
+  static ErrorPtr Create(const std::string& domain, const std::string& code,
+                         const std::string& message, ErrorPtr inner_error);
+  // If |error| is not nullptr, creates another instance of Error class,
+  // initializes it with specified arguments and adds it to the head of
+  // the error chain pointed to by |error|.
+  static void AddTo(ErrorPtr* error, const std::string& domain,
+                    const std::string& code, const std::string& message);
+  // Same as the Error::AddTo above, but allows to pass in a printf-like
+  // format string and optional parameters to format the error message.
+  static void AddToPrintf(ErrorPtr* error, const std::string& domain,
+                          const std::string& code,
+                          const char* format, ...) PRINTF_FORMAT(4, 5);
+
+  // Returns the error domain, code and message
+  const std::string& GetDomain() const { return domain_; }
+  const std::string& GetCode() const { return code_; }
+  const std::string& GetMessage() const { return message_; }
+
+  // Checks if this or any of the inner error in the chain has the specified
+  // error domain.
+  bool HasDomain(const std::string& domain) const;
+  // Checks if this or any of the inner error in the chain matches the specified
+  // error domain and code.
+  bool HasError(const std::string& domain, const std::string& code) const;
+
+  // Gets a pointer to the inner error, if present. Returns nullptr otherwise.
+  const Error* GetInnerError() const { return inner_error_.get(); }
+
+  // Gets a pointer to the first error occurred.
+  // Returns itself if no inner error are available.
+  const Error* GetFirstError() const;
+
+ protected:
+  // Constructor is protected since this object is supposed to be
+  // created via the Create factory methods.
+  Error(const std::string& domain, const std::string& code,
+        const std::string& message, ErrorPtr inner_error);
+
+  // Error domain. The domain defines the scopes for error codes.
+  // Two errors with the same code but different domains are different errors.
+  std::string domain_;
+  // Error code. A unique error code identifier within the given domain.
+  std::string code_;
+  // Human-readable error message.
+  std::string message_;
+  // Pointer to inner error, if any. This forms a chain of errors.
+  ErrorPtr inner_error_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Error);
+};
+
+}  // namespace buffet
+
+#endif  // BUFFET_ERROR_H_
diff --git a/buffet/error_unittest.cc b/buffet/error_unittest.cc
new file mode 100644
index 0000000..cf943ad
--- /dev/null
+++ b/buffet/error_unittest.cc
@@ -0,0 +1,56 @@
+// 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 <gtest/gtest.h>
+#include <base/files/file_path.h>
+
+#include "buffet/error.h"
+
+using buffet::Error;
+
+namespace {
+
+buffet::ErrorPtr GenerateNetworkError() {
+  return Error::Create("network", "not_found", "Resource not found");
+}
+
+buffet::ErrorPtr GenerateHttpError() {
+  auto inner = GenerateNetworkError();
+  return Error::Create("HTTP", "404", "Not found", std::move(inner));
+}
+
+}  // namespace
+
+TEST(Error, Single) {
+  auto err = GenerateNetworkError();
+  EXPECT_EQ("network", err->GetDomain());
+  EXPECT_EQ("not_found", err->GetCode());
+  EXPECT_EQ("Resource not found", err->GetMessage());
+  EXPECT_EQ(nullptr, err->GetInnerError());
+  EXPECT_TRUE(err->HasDomain("network"));
+  EXPECT_FALSE(err->HasDomain("HTTP"));
+  EXPECT_FALSE(err->HasDomain("foo"));
+  EXPECT_TRUE(err->HasError("network", "not_found"));
+  EXPECT_FALSE(err->HasError("network", "404"));
+  EXPECT_FALSE(err->HasError("HTTP", "404"));
+  EXPECT_FALSE(err->HasError("HTTP", "not_found"));
+  EXPECT_FALSE(err->HasError("foo", "bar"));
+}
+
+TEST(Error, Nested) {
+  auto err = GenerateHttpError();
+  EXPECT_EQ("HTTP", err->GetDomain());
+  EXPECT_EQ("404", err->GetCode());
+  EXPECT_EQ("Not found", err->GetMessage());
+  EXPECT_NE(nullptr, err->GetInnerError());
+  EXPECT_EQ("network", err->GetInnerError()->GetDomain());
+  EXPECT_TRUE(err->HasDomain("network"));
+  EXPECT_TRUE(err->HasDomain("HTTP"));
+  EXPECT_FALSE(err->HasDomain("foo"));
+  EXPECT_TRUE(err->HasError("network", "not_found"));
+  EXPECT_FALSE(err->HasError("network", "404"));
+  EXPECT_TRUE(err->HasError("HTTP", "404"));
+  EXPECT_FALSE(err->HasError("HTTP", "not_found"));
+  EXPECT_FALSE(err->HasError("foo", "bar"));
+}
diff --git a/buffet/exported_object_manager.cc b/buffet/exported_object_manager.cc
new file mode 100644
index 0000000..c1403ad
--- /dev/null
+++ b/buffet/exported_object_manager.cc
@@ -0,0 +1,133 @@
+// 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_object_manager.h"
+
+#include <dbus/object_manager.h>
+
+#include "buffet/async_event_sequencer.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+ExportedObjectManager::ExportedObjectManager(scoped_refptr<dbus::Bus> bus,
+                                             const dbus::ObjectPath& path)
+    : bus_(bus), exported_object_(bus->GetExportedObject(path)) {}
+
+void ExportedObjectManager::Init(const OnInitFinish& cb) {
+  bus_->AssertOnOriginThread();
+  scoped_refptr<dbus_utils::AsyncEventSequencer> sequencer(
+      new dbus_utils::AsyncEventSequencer());
+  exported_object_->ExportMethod(
+      dbus::kObjectManagerInterface,
+      dbus::kObjectManagerGetManagedObjects,
+      base::Bind(&ExportedObjectManager::HandleGetManagedObjects,
+                 AsWeakPtr()),
+      sequencer->GetExportHandler(
+          dbus::kObjectManagerInterface,
+          dbus::kObjectManagerGetManagedObjects,
+          "Failed exporting GetManagedObjects method of ObjectManager",
+          false));
+  sequencer->OnAllTasksCompletedCall({cb});
+}
+
+void ExportedObjectManager::ClaimInterface(
+    const dbus::ObjectPath& path,
+    const std::string& interface_name,
+    const PropertyWriter& property_writer) {
+  bus_->AssertOnOriginThread();
+  // We're sending signals that look like:
+  //   org.freedesktop.DBus.ObjectManager.InterfacesAdded (
+  //       OBJPATH object_path,
+  //       DICT<STRING,DICT<STRING,VARIANT>> interfaces_and_properties);
+  dbus::Signal signal(dbus::kObjectManagerInterface,
+                      dbus::kObjectManagerInterfacesAdded);
+  dbus::MessageWriter signal_writer(&signal);
+  dbus::MessageWriter all_interfaces(&signal);
+  dbus::MessageWriter each_interface(&signal);
+  signal_writer.AppendObjectPath(path);
+  signal_writer.OpenArray("{sa{sv}}", &all_interfaces);
+  all_interfaces.OpenDictEntry(&each_interface);
+  each_interface.AppendString(interface_name);
+  property_writer.Run(&each_interface);
+  all_interfaces.CloseContainer(&each_interface);
+  signal_writer.CloseContainer(&all_interfaces);
+  exported_object_->SendSignal(&signal);
+  registered_objects_[path][interface_name] = property_writer;
+}
+
+void ExportedObjectManager::ReleaseInterface(
+    const dbus::ObjectPath& path, const std::string& interface_name) {
+  bus_->AssertOnOriginThread();
+  auto interfaces_for_path_itr = registered_objects_.find(path);
+  CHECK(interfaces_for_path_itr != registered_objects_.end())
+      << "Attempting to signal interface removal for path " << path.value()
+      << " which was never registered.";
+  auto interfaces_for_path = interfaces_for_path_itr->second;
+  auto property_for_interface_itr = interfaces_for_path.find(interface_name);
+  CHECK(property_for_interface_itr != interfaces_for_path.end())
+      << "Attempted to remove interface " << interface_name << " from "
+      << path.value() << ", but this interface was never registered.";
+  interfaces_for_path.erase(interface_name);
+  if (interfaces_for_path.size() < 1) {
+    registered_objects_.erase(path);
+  }
+  // We're sending signals that look like:
+  //   org.freedesktop.DBus.ObjectManager.InterfacesRemoved (
+  //       OBJPATH object_path, ARRAY<STRING> interfaces);
+  dbus::Signal signal(dbus::kObjectManagerInterface,
+                      dbus::kObjectManagerInterfacesRemoved);
+  dbus::MessageWriter signal_writer(&signal);
+  signal_writer.AppendObjectPath(path);
+  dbus::MessageWriter interface_writer(nullptr);
+  signal_writer.OpenArray("s", &interface_writer);
+  interface_writer.AppendString(interface_name);
+  signal_writer.CloseContainer(&interface_writer);
+  exported_object_->SendSignal(&signal);
+}
+
+void ExportedObjectManager::HandleGetManagedObjects(
+    dbus::MethodCall* method_call,
+    dbus::ExportedObject::ResponseSender response_sender) const {
+  // Implements the GetManagedObjects method:
+  //
+  // org.freedesktop.DBus.ObjectManager.GetManagedObjects (
+  //     out DICT<OBJPATH,
+  //              DICT<STRING,
+  //                   DICT<STRING,VARIANT>>> )
+  bus_->AssertOnOriginThread();
+  scoped_ptr<dbus::Response> response(
+      dbus::Response::FromMethodCall(method_call));
+  dbus::MessageWriter response_writer(response.get());
+  dbus::MessageWriter all_object_paths(nullptr);
+  dbus::MessageWriter each_object_path(nullptr);
+  dbus::MessageWriter all_interfaces(nullptr);
+  dbus::MessageWriter each_interface(nullptr);
+
+  response_writer.OpenArray("{oa{sa{sv}}}", &all_object_paths);
+  for (const auto path_pair : registered_objects_) {
+    const dbus::ObjectPath& path = path_pair.first;
+    const InterfaceProperties& interface2properties = path_pair.second;
+    all_object_paths.OpenDictEntry(&each_object_path);
+    each_object_path.AppendObjectPath(path);
+    each_object_path.OpenArray("{sa{sv}}", &all_interfaces);
+    for (const auto interface : interface2properties) {
+      const std::string& interface_name = interface.first;
+      const PropertyWriter& property_writer = interface.second;
+      all_interfaces.OpenDictEntry(&each_interface);
+      each_interface.AppendString(interface_name);
+      property_writer.Run(&each_interface);
+      all_interfaces.CloseContainer(&each_interface);
+    }
+    each_object_path.CloseContainer(&all_interfaces);
+    all_object_paths.CloseContainer(&each_object_path);
+  }
+  response_writer.CloseContainer(&all_object_paths);
+  response_sender.Run(response.Pass());
+}
+
+}  //  namespace dbus_utils
+
+}  //  namespace buffet
diff --git a/buffet/exported_object_manager.h b/buffet/exported_object_manager.h
new file mode 100644
index 0000000..d483db0
--- /dev/null
+++ b/buffet/exported_object_manager.h
@@ -0,0 +1,117 @@
+// 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_OBJECT_MANAGER_H_
+#define BUFFET_EXPORTED_OBJECT_MANAGER_H_
+
+#include <map>
+#include <string>
+
+#include <base/memory/weak_ptr.h>
+#include <dbus/bus.h>
+#include <dbus/exported_object.h>
+#include <dbus/object_path.h>
+
+#include "buffet/exported_property_set.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+// ExportedObjectManager is a delegate that implements the
+// org.freedesktop.DBus.ObjectManager interface on behalf of another
+// object. It handles sending signals when new interfaces are added.
+//
+// This class is very similar to the ExportedPropertySet class, except that
+// it allows objects to expose an object manager interface rather than the
+// properties interface.
+//
+//  Example usage:
+//
+//   class ExampleObjectManager {
+//    public:
+//     ExampleObjectManager(dbus::Bus* bus)
+//         : object_manager_(bus, "/my/objects/path") { }
+//
+//     void Init(const OnInitFinish& cb) { object_manager_.Init(cb); }
+//     void ClaimInterface(const dbus::ObjectPath& path,
+//                         const std::string& interface_name,
+//                         const PropertyWriter& writer) {
+//       object_manager_->ClaimInterface(...);
+//     }
+//     void ReleaseInterface(const dbus::ObjectPath& path,
+//                           const std::string& interface_name) {
+//       object_manager_->ReleaseInterface(...);
+//     }
+//
+//    private:
+//     ExportedObjectManager object_manager_;
+//   };
+//
+//   class MyObjectClaimingAnInterface {
+//    public:
+//     MyObjectClaimingAnInterface(ExampleObjectManager* object_manager)
+//       : object_manager_(object_manager) {}
+//
+//     void OnInitFinish(bool success) {
+//       if (!success) { /* handle that */ }
+//       object_manager_->ClaimInterface(
+//           my_path_, my_interface_, my_properties_.GetWriter());
+//     }
+//
+//    private:
+//     struct Properties : public ExportedPropertySet {
+//      public:
+//       /* Lots of interesting properties. */
+//     };
+//
+//     Properties my_properties_;
+//     ExampleObjectManager* object_manager_;
+//   };
+class ExportedObjectManager
+    : public base::SupportsWeakPtr<ExportedObjectManager> {
+ public:
+  // Writes a dictionary of property name to property value variants to writer.
+  typedef base::Callback<void(dbus::MessageWriter* writer)> PropertyWriter;
+  typedef base::Callback<void(bool success)> OnInitFinish;
+  typedef std::map<std::string, PropertyWriter> InterfaceProperties;
+
+  ExportedObjectManager(scoped_refptr<dbus::Bus> bus,
+                        const dbus::ObjectPath& path);
+
+  // Registers methods implementing the ObjectManager interface on the object
+  // exported on the path given in the constructor. Must be called on the
+  // origin thread.
+  void Init(const OnInitFinish& cb);
+
+  // Trigger a signal that |path| has added an interface |interface_name|
+  // with properties as given by |writer|.
+  void ClaimInterface(const dbus::ObjectPath& path,
+                      const std::string& interface_name,
+                      const PropertyWriter& writer);
+
+  // Trigger a signal that |path| has removed an interface |interface_name|.
+  void ReleaseInterface(const dbus::ObjectPath& path,
+                        const std::string& interface_name);
+
+ private:
+  void HandleGetManagedObjects(
+      dbus::MethodCall* method_call,
+      dbus::ExportedObject::ResponseSender response_sender) const;
+
+  // Both |bus_| and |exported_object_| outlive *this.
+  dbus::Bus* const bus_;
+  dbus::ExportedObject* const exported_object_;
+  // Tracks all objects currently known to the ExportedObjectManager.
+  std::map<dbus::ObjectPath, InterfaceProperties> registered_objects_;
+
+  friend class ExportedObjectManagerTest;
+  DISALLOW_COPY_AND_ASSIGN(ExportedObjectManager);
+};
+
+}  //  namespace dbus_utils
+
+}  //  namespace buffet
+
+#endif  // BUFFET_EXPORTED_OBJECT_MANAGER_H_
diff --git a/buffet/exported_object_manager_unittest.cc b/buffet/exported_object_manager_unittest.cc
new file mode 100644
index 0000000..696b76f
--- /dev/null
+++ b/buffet/exported_object_manager_unittest.cc
@@ -0,0 +1,205 @@
+// 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_object_manager.h"
+
+#include <base/bind.h>
+#include <dbus/mock_bus.h>
+#include <dbus/mock_exported_object.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <gtest/gtest.h>
+
+using ::testing::AnyNumber;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::_;
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+
+const dbus::ObjectPath kTestPath(std::string("/test/om_path"));
+const dbus::ObjectPath kClaimedTestPath(std::string("/test/claimed_path"));
+const std::string kClaimedInterface("claimed.interface");
+const std::string kTestPropertyName("PropertyName");
+const std::string kTestPropertyValue("PropertyValue");
+
+void WriteTestPropertyDict(dbus::MessageWriter* writer) {
+  dbus::MessageWriter all_properties(nullptr);
+  dbus::MessageWriter each_property(nullptr);
+  writer->OpenArray("{sv}", &all_properties);
+  all_properties.OpenDictEntry(&each_property);
+  each_property.AppendString(kTestPropertyName);
+  each_property.AppendVariantOfString(kTestPropertyValue);
+  all_properties.CloseContainer(&each_property);
+  writer->CloseContainer(&all_properties);
+}
+
+void ReadTestPropertyDict(dbus::MessageReader* reader) {
+  dbus::MessageReader all_properties(nullptr);
+  dbus::MessageReader each_property(nullptr);
+  ASSERT_TRUE(reader->PopArray(&all_properties));
+  ASSERT_TRUE(all_properties.PopDictEntry(&each_property));
+  std::string property_name;
+  std::string property_value;
+  ASSERT_TRUE(each_property.PopString(&property_name));
+  ASSERT_TRUE(each_property.PopVariantOfString(&property_value));
+  EXPECT_FALSE(each_property.HasMoreData());
+  EXPECT_FALSE(all_properties.HasMoreData());
+  EXPECT_EQ(property_name, kTestPropertyName);
+  EXPECT_EQ(property_value, kTestPropertyValue);
+}
+
+void VerifyInterfaceClaimSignal(dbus::Signal* signal) {
+  EXPECT_EQ(signal->GetInterface(),
+            std::string(dbus::kObjectManagerInterface));
+  EXPECT_EQ(signal->GetMember(),
+            std::string(dbus::kObjectManagerInterfacesAdded));
+  //   org.freedesktop.DBus.ObjectManager.InterfacesAdded (
+  //       OBJPATH object_path,
+  //       DICT<STRING,DICT<STRING,VARIANT>> interfaces_and_properties);
+  dbus::MessageReader reader(signal);
+  dbus::MessageReader all_interfaces(nullptr);
+  dbus::MessageReader each_interface(nullptr);
+  dbus::ObjectPath path;
+  ASSERT_TRUE(reader.PopObjectPath(&path));
+  ASSERT_TRUE(reader.PopArray(&all_interfaces));
+  ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface));
+  std::string interface_name;
+  ASSERT_TRUE(each_interface.PopString(&interface_name));
+  ReadTestPropertyDict(&each_interface);
+  EXPECT_FALSE(each_interface.HasMoreData());
+  EXPECT_FALSE(all_interfaces.HasMoreData());
+  EXPECT_FALSE(reader.HasMoreData());
+  EXPECT_EQ(interface_name, kClaimedInterface);
+  EXPECT_EQ(path, kClaimedTestPath);
+}
+
+void VerifyInterfaceDropSignal(dbus::Signal* signal) {
+  EXPECT_EQ(signal->GetInterface(),
+            std::string(dbus::kObjectManagerInterface));
+  EXPECT_EQ(signal->GetMember(),
+            std::string(dbus::kObjectManagerInterfacesRemoved));
+  //   org.freedesktop.DBus.ObjectManager.InterfacesRemoved (
+  //       OBJPATH object_path, ARRAY<STRING> interfaces);
+  dbus::MessageReader reader(signal);
+  dbus::MessageReader each_interface(nullptr);
+  dbus::ObjectPath path;
+  ASSERT_TRUE(reader.PopObjectPath(&path));
+  ASSERT_TRUE(reader.PopArray(&each_interface));
+  std::string interface_name;
+  ASSERT_TRUE(each_interface.PopString(&interface_name));
+  EXPECT_FALSE(each_interface.HasMoreData());
+  EXPECT_FALSE(reader.HasMoreData());
+  EXPECT_EQ(interface_name, kClaimedInterface);
+  EXPECT_EQ(path, kClaimedTestPath);
+}
+
+}  // namespace
+
+class ExportedObjectManagerTest: public ::testing::Test {
+ public:
+  virtual void SetUp() {
+    dbus::Bus::Options options;
+    options.bus_type = dbus::Bus::SYSTEM;
+    bus_ = new dbus::MockBus(options);
+    // By default, don't worry about threading assertions.
+    EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber());
+    EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber());
+    // Use a mock exported object.
+    mock_exported_object_ = new dbus::MockExportedObject(
+        bus_.get(), kTestPath);
+    EXPECT_CALL(*bus_, GetExportedObject(kTestPath))
+        .Times(1).WillOnce(Return(mock_exported_object_.get()));
+    om_.reset(new ExportedObjectManager(bus_.get(), kTestPath));
+    property_writer_ = base::Bind(&WriteTestPropertyDict);
+    response_storer_ = base::Bind(&ExportedObjectManagerTest::StoreResponse,
+                                  base::Unretained(this));
+  }
+
+  void StoreResponse(scoped_ptr<dbus::Response> method_response) {
+    last_response_.reset(method_response.release());
+  }
+
+  void CallHandleGetManagedObjects(
+      dbus::MethodCall* method_call,
+      dbus::ExportedObject::ResponseSender sender) {
+    om_->HandleGetManagedObjects(method_call, response_storer_);
+  }
+
+  scoped_refptr<dbus::MockBus> bus_;
+  scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
+  scoped_ptr<ExportedObjectManager> om_;
+  ExportedObjectManager::PropertyWriter property_writer_;
+  dbus::ExportedObject::ResponseSender response_storer_;
+  scoped_ptr<dbus::Response> last_response_;
+};
+
+TEST_F(ExportedObjectManagerTest, ClaimInterfaceSendsSignals) {
+  EXPECT_CALL(*mock_exported_object_, SendSignal(_))
+      .Times(1).WillOnce(Invoke(&VerifyInterfaceClaimSignal));
+  om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_);
+}
+
+TEST_F(ExportedObjectManagerTest, ReleaseInterfaceSendsSignals) {
+  InSequence dummy;
+  EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1);
+  EXPECT_CALL(*mock_exported_object_, SendSignal(_))
+      .Times(1).WillOnce(Invoke(&VerifyInterfaceDropSignal));
+  om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_);
+  om_->ReleaseInterface(kClaimedTestPath, kClaimedInterface);
+}
+
+TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseEmptyCorrectness) {
+  dbus::MethodCall method_call(dbus::kObjectManagerInterface,
+                               dbus::kObjectManagerGetManagedObjects);
+  method_call.SetSerial(123);
+  CallHandleGetManagedObjects(&method_call, response_storer_);
+  dbus::MessageReader reader(last_response_.get());
+  dbus::MessageReader all_paths(nullptr);
+  ASSERT_TRUE(reader.PopArray(&all_paths));
+  EXPECT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseCorrectness) {
+  // org.freedesktop.DBus.ObjectManager.GetManagedObjects (
+  //     out DICT<OBJPATH,
+  //              DICT<STRING,
+  //                   DICT<STRING,VARIANT>>> )
+  dbus::MethodCall method_call(dbus::kObjectManagerInterface,
+                               dbus::kObjectManagerGetManagedObjects);
+  method_call.SetSerial(123);
+  EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1);
+  om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_);
+  CallHandleGetManagedObjects(&method_call, response_storer_);
+  dbus::MessageReader reader(last_response_.get());
+  dbus::MessageReader all_paths(nullptr);
+  dbus::MessageReader each_path(nullptr);
+  dbus::MessageReader all_interfaces(nullptr);
+  dbus::MessageReader each_interface(nullptr);
+  ASSERT_TRUE(reader.PopArray(&all_paths));
+  ASSERT_TRUE(all_paths.PopDictEntry(&each_path));
+  dbus::ObjectPath path;
+  ASSERT_TRUE(each_path.PopObjectPath(&path));
+  ASSERT_TRUE(each_path.PopArray(&all_interfaces));
+  ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface));
+  std::string interface_name;
+  ASSERT_TRUE(each_interface.PopString(&interface_name));
+  ReadTestPropertyDict(&each_interface);
+  EXPECT_FALSE(each_interface.HasMoreData());
+  EXPECT_FALSE(all_interfaces.HasMoreData());
+  EXPECT_FALSE(each_path.HasMoreData());
+  EXPECT_FALSE(all_paths.HasMoreData());
+  EXPECT_FALSE(reader.HasMoreData());
+  EXPECT_EQ(path, kClaimedTestPath);
+  EXPECT_EQ(interface_name, kClaimedInterface);
+}
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
diff --git a/buffet/exported_property_set.cc b/buffet/exported_property_set.cc
new file mode 100644
index 0000000..b1b5633
--- /dev/null
+++ b/buffet/exported_property_set.cc
@@ -0,0 +1,338 @@
+// 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/bus.h>  // For kPropertyInterface
+#include <dbus/property.h>  // For kPropertyInterface
+
+#include "buffet/async_event_sequencer.h"
+#include "buffet/dbus_utils.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+const char kExportFailedMessage[] = "Failed to register DBus method.";
+}  // namespace
+
+ExportedPropertySet::ExportedPropertySet(dbus::Bus* bus,
+                                         const dbus::ObjectPath& path)
+    : bus_(bus), exported_object_(bus->GetExportedObject(path)),
+      weak_ptr_factory_(this) { }
+
+void ExportedPropertySet::Init(const OnInitFinish& cb) {
+  bus_->AssertOnOriginThread();
+  scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+  exported_object_->ExportMethod(
+      dbus::kPropertiesInterface, dbus::kPropertiesGetAll,
+      base::Bind(&ExportedPropertySet::HandleGetAll,
+                 weak_ptr_factory_.GetWeakPtr()),
+      sequencer->GetExportHandler(
+          dbus::kPropertiesInterface, dbus::kPropertiesGetAll,
+          kExportFailedMessage, false));
+  exported_object_->ExportMethod(
+      dbus::kPropertiesInterface, dbus::kPropertiesGet,
+      base::Bind(&ExportedPropertySet::HandleGet,
+                 weak_ptr_factory_.GetWeakPtr()),
+      sequencer->GetExportHandler(
+          dbus::kPropertiesInterface, dbus::kPropertiesGet,
+          kExportFailedMessage, false));
+  exported_object_->ExportMethod(
+      dbus::kPropertiesInterface, dbus::kPropertiesSet,
+      base::Bind(&ExportedPropertySet::HandleSet,
+                 weak_ptr_factory_.GetWeakPtr()),
+      sequencer->GetExportHandler(
+          dbus::kPropertiesInterface, dbus::kPropertiesSet,
+          kExportFailedMessage, false));
+  sequencer->OnAllTasksCompletedCall({cb});
+}
+
+ExportedPropertySet::PropertyWriter ExportedPropertySet::GetPropertyWriter(
+    const std::string& interface) {
+  return base::Bind(&ExportedPropertySet::WritePropertiesDictToMessage,
+                    weak_ptr_factory_.GetWeakPtr(),
+                    interface);
+}
+
+void ExportedPropertySet::RegisterProperty(
+    const std::string& interface_name,
+    const std::string& property_name,
+    ExportedPropertyBase* exported_property) {
+  bus_->AssertOnOriginThread();
+  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) {
+  bus_->AssertOnOriginThread();
+  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;
+  }
+  scoped_ptr<dbus::Response> response(
+      dbus::Response::FromMethodCall(method_call));
+  dbus::MessageWriter resp_writer(response.get());
+  WritePropertiesDictToMessage(interface_name, &resp_writer);
+  response_sender.Run(response.Pass());
+}
+
+void ExportedPropertySet::WritePropertiesDictToMessage(
+    const std::string& interface_name,
+    dbus::MessageWriter* writer) {
+  dbus::MessageWriter dict_writer(nullptr);
+  writer->OpenArray("{sv}", &dict_writer);
+  auto property_map_itr = properties_.find(interface_name);
+  if (property_map_itr != properties_.end()) {
+    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);
+    }
+  } else {
+    LOG(WARNING) << "No properties found for interface interface_name";
+  }
+  writer->CloseContainer(&dict_writer);
+}
+
+void ExportedPropertySet::HandleGet(
+    dbus::MethodCall* method_call,
+    dbus::ExportedObject::ResponseSender response_sender) {
+  bus_->AssertOnOriginThread();
+  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) {
+  bus_->AssertOnOriginThread();
+  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) {
+  bus_->AssertOnOriginThread();
+  dbus::Signal signal(dbus::kPropertiesInterface, dbus::kPropertiesChanged);
+  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.
+  writer.OpenArray("s", &array_writer);
+  writer.CloseContainer(&array_writer);
+  // 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);
+}
+
+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) const {
+  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..d91415f
--- /dev/null
+++ b/buffet/exported_property_set.h
@@ -0,0 +1,200 @@
+// 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 <vector>
+
+#include <base/memory/weak_ptr.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.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) const = 0;
+};
+
+class ExportedPropertySet {
+ public:
+  typedef base::Callback<void(bool success)> OnInitFinish;
+  typedef base::Callback<void(dbus::MessageWriter* writer)> PropertyWriter;
+
+  ExportedPropertySet(dbus::Bus* bus, const dbus::ObjectPath& path);
+  virtual ~ExportedPropertySet() = default;
+
+  // Claims the method associated with the org.freedesktop.DBus.Properties
+  // interface.  This needs to be done after all properties are initialized to
+  // appropriate values.  This method will call |cb| when all methods
+  // are exported to the DBus object.  |cb| will be called on the origin
+  // thread.
+  void Init(const OnInitFinish& cb);
+
+  // Return a callback that knows how to write this property set's properties
+  // to a message.  This writer retains a weak pointer to this, and must
+  // only be invoked on the same thread as the rest of ExportedPropertySet.
+  PropertyWriter GetPropertyWriter(const std::string& interface);
+
+ protected:
+  void RegisterProperty(const std::string& interface_name,
+                        const std::string& property_name,
+                        ExportedPropertyBase* exported_property);
+
+ private:
+  // Used to write the dictionary of string->variant to a message.
+  // This dictionary represents the property name/value pairs for the
+  // given interface.
+  void WritePropertiesDictToMessage(const std::string& interface_name,
+                                    dbus::MessageWriter* writer);
+  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);
+  void HandlePropertyUpdated(const std::string& interface,
+                             const std::string& name,
+                             const ExportedPropertyBase* property);
+
+  dbus::Bus* bus_;
+  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) const 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..ce8a3fe
--- /dev/null
+++ b/buffet/exported_property_set_unittest.cc
@@ -0,0 +1,506 @@
+// 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 <dbus/mock_bus.h>
+#include <dbus/mock_exported_object.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::AnyNumber;
+using ::testing::Return;
+using ::testing::Invoke;
+using ::testing::_;
+
+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 kMethodsExportedOnPath(std::string("/export"));
+const dbus::ObjectPath kTestObjectPathInit(std::string("/path_init"));
+const dbus::ObjectPath kTestObjectPathUpdate(std::string("/path_update"));
+
+}  // namespace
+
+class ExportedPropertySetTest : public ::testing::Test {
+ public:
+  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(dbus::Bus* bus, const dbus::ObjectPath& path)
+        : ExportedPropertySet(bus, path) {
+      // 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);
+    }
+  };
+
+  virtual void SetUp() {
+    dbus::Bus::Options options;
+    options.bus_type = dbus::Bus::SYSTEM;
+    bus_ = new dbus::MockBus(options);
+    // By default, don't worry about threading assertions.
+    EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber());
+    EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber());
+    // Use a mock exported object.
+    mock_exported_object_ = new dbus::MockExportedObject(
+        bus_.get(), kMethodsExportedOnPath);
+    EXPECT_CALL(*bus_, GetExportedObject(kMethodsExportedOnPath))
+        .Times(1).WillOnce(Return(mock_exported_object_.get()));
+    p_.reset(new Properties(bus_.get(), kMethodsExportedOnPath));
+  }
+
+  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_;
+  scoped_refptr<dbus::MockBus> bus_;
+  scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
+  scoped_ptr<Properties> p_;
+};
+
+TEST_F(ExportedPropertySetTest, UpdateNotifications) {
+  EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(14);
+  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(*mock_exported_object_, SendSignal(_)).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);
+  method_call.SetSerial(123);
+  dbus::MessageWriter writer(&method_call);
+  writer.AppendString("org.chromium.BadInterface");
+  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);
+  ASSERT_TRUE(response_reader.PopArray(&dict_reader));
+  // The response should just be a an empty array, since there are no properties
+  // on this interface.  The spec doesn't say much about error conditions here,
+  // so I'm going to assume this is a valid implementation.
+  ASSERT_FALSE(dict_reader.HasMoreData());
+  ASSERT_FALSE(response_reader.HasMoreData());
+}
+
+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);
+  const 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 {
+
+void VerifySignal(dbus::Signal* signal) {
+  ASSERT_NE(signal, nullptr);
+  std::string interface_name;
+  std::string property_name;
+  uint8 value;
+  dbus::MessageReader reader(signal);
+  dbus::MessageReader array_reader(signal);
+  dbus::MessageReader dict_reader(signal);
+  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());
+  ASSERT_TRUE(reader.HasMoreData());
+  // Read the (empty) list of invalidated property names.
+  ASSERT_TRUE(reader.PopArray(&array_reader));
+  ASSERT_FALSE(array_reader.HasMoreData());
+  ASSERT_FALSE(reader.HasMoreData());
+  ASSERT_EQ(value, 57);
+  ASSERT_EQ(property_name, std::string(kUint8PropName));
+  ASSERT_EQ(interface_name, std::string(kTestInterface1));
+}
+
+}  // namespace
+
+TEST_F(ExportedPropertySetTest, SignalsAreParsable) {
+  EXPECT_CALL(*mock_exported_object_, SendSignal(_))
+      .Times(1).WillOnce(Invoke(&VerifySignal));
+  p_->uint8_prop_.SetValue(57);
+}
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
diff --git a/buffet/http_connection.h b/buffet/http_connection.h
new file mode 100644
index 0000000..ab67905
--- /dev/null
+++ b/buffet/http_connection.h
@@ -0,0 +1,84 @@
+// 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_HTTP_CONNECTION_H_
+#define BUFFET_HTTP_CONNECTION_H_
+
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/error.h"
+#include "buffet/http_transport.h"
+
+namespace buffet {
+namespace http {
+
+///////////////////////////////////////////////////////////////////////////////
+// Connection class is the base class for HTTP communication session.
+// It abstracts the implementation of underlying transport library (ex libcurl).
+// When the Connection-derived class is constructed, it is pre-set up with
+// basic initialization information necessary to initiate the server request
+// connection (such as the URL, request method, etc - see
+// Transport::CreateConnection() for more details). But most implementations
+// would not probably initiate the physical connection until SendHeaders
+// is called.
+// You normally shouldn't worry about using this class directly.
+// http::Request and http::Response classes use it for communication.
+///////////////////////////////////////////////////////////////////////////////
+class Connection {
+ public:
+  explicit Connection(std::shared_ptr<Transport> transport)
+      : transport_(transport) {}
+  virtual ~Connection() = default;
+
+  // Called by http::Request to initiate the connection with the server.
+  // This normally opens the socket and sends the request headers.
+  virtual bool SendHeaders(const HeaderList& headers, ErrorPtr* error) = 0;
+  // If needed, this function can be called to send the request body data.
+  // This function can be called repeatedly until all data is sent.
+  virtual bool WriteRequestData(const void* data, size_t size,
+                                ErrorPtr* error) = 0;
+  // This function is called when all the data is sent off and it's time
+  // to receive the response data.
+  virtual bool FinishRequest(ErrorPtr* error) = 0;
+
+  // Returns the HTTP status code (e.g. 200 for success).
+  virtual int GetResponseStatusCode() const = 0;
+  // Returns the status text (e.g. for error 403 it could be "NOT AUTHORIZED").
+  virtual std::string GetResponseStatusText() const = 0;
+  // Returns the HTTP protocol version (e.g. "HTTP/1.1").
+  virtual std::string GetProtocolVersion() const = 0;
+  // Returns the value of particular response header, or empty string if the
+  // headers wasn't received.
+  virtual std::string GetResponseHeader(
+      const std::string& header_name) const = 0;
+  // Returns the response data size, if known. For chunked (streaming)
+  // transmission this might not be known until all the data is sent.
+  // In this case GetResponseDataSize() will return 0.
+  virtual uint64_t GetResponseDataSize() const = 0;
+  // This function is called to read a block of response data.
+  // It needs to be called repeatedly until it returns false or |size_read| is
+  // set to 0. |data| is the destination buffer to read the data into.
+  // |buffer_size| is the size of the buffer (amount of data to read).
+  // |read_size| is the amount of data actually read, which could be less than
+  // the size requested or 0 if there is no more data available.
+  virtual bool ReadResponseData(void* data, size_t buffer_size,
+                                size_t* size_read, ErrorPtr* error) = 0;
+
+ protected:
+  // |transport_| is mainly used to keep the object alive as long as the
+  // connection exists. But some implementations of Connection could use
+  // the Transport-derived class for their own needs as well.
+  std::shared_ptr<Transport> transport_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Connection);
+};
+
+}  // namespace http
+}  // namespace buffet
+
+#endif  // BUFFET_HTTP_CONNECTION_H_
diff --git a/buffet/http_connection_curl.cc b/buffet/http_connection_curl.cc
new file mode 100644
index 0000000..ebdd3cc
--- /dev/null
+++ b/buffet/http_connection_curl.cc
@@ -0,0 +1,226 @@
+// 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/http_connection_curl.h"
+
+#include <base/logging.h>
+
+#include "buffet/http_request.h"
+#include "buffet/http_transport_curl.h"
+#include "buffet/string_utils.h"
+
+namespace buffet {
+namespace http {
+namespace curl {
+
+static int curl_trace(CURL *handle, curl_infotype type,
+                      char *data, size_t size, void *userp) {
+  std::string msg(data, size);
+
+  switch (type) {
+  case CURLINFO_TEXT:
+    VLOG(3) << "== Info: " << msg;
+    break;
+  case CURLINFO_HEADER_OUT:
+    VLOG(3) << "=> Send headers:\n" << msg;
+    break;
+  case CURLINFO_DATA_OUT:
+    VLOG(3) << "=> Send data:\n" << msg;
+    break;
+  case CURLINFO_SSL_DATA_OUT:
+    VLOG(3) << "=> Send SSL data" << msg;
+    break;
+  case CURLINFO_HEADER_IN:
+    VLOG(3) << "<= Recv header: " << msg;
+    break;
+  case CURLINFO_DATA_IN:
+    VLOG(3) << "<= Recv data:\n" << msg;
+    break;
+  case CURLINFO_SSL_DATA_IN:
+    VLOG(3) << "<= Recv SSL data" << msg;
+    break;
+  default:
+    break;
+  }
+  return 0;
+}
+
+Connection::Connection(CURL* curl_handle, const std::string& method,
+                       std::shared_ptr<http::Transport> transport) :
+    http::Connection(transport), method_(method), curl_handle_(curl_handle) {
+  VLOG(1) << "curl::Connection created: " << method_;
+}
+
+Connection::~Connection() {
+  VLOG(1) << "curl::Connection destroyed";
+}
+
+bool Connection::SendHeaders(const HeaderList& headers, ErrorPtr* error) {
+  headers_.insert(headers.begin(), headers.end());
+  return true;
+}
+
+bool Connection::WriteRequestData(const void* data, size_t size,
+                                  ErrorPtr* error) {
+  if (size > 0) {
+    auto data_ptr = reinterpret_cast<const unsigned char*>(data);
+    request_data_.insert(request_data_.end(), data_ptr, data_ptr + size);
+  }
+  return true;
+}
+
+bool Connection::FinishRequest(ErrorPtr* error) {
+  if (VLOG_IS_ON(3)) {
+    curl_easy_setopt(curl_handle_, CURLOPT_DEBUGFUNCTION, curl_trace);
+    curl_easy_setopt(curl_handle_, CURLOPT_VERBOSE, 1L);
+  }
+
+  // Set up HTTP request data.
+  if (method_ == request_type::kPut) {
+    curl_easy_setopt(curl_handle_, CURLOPT_INFILESIZE_LARGE,
+                      curl_off_t(request_data_.size()));
+  } else {
+    curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE,
+                      curl_off_t(request_data_.size()));
+  }
+  if (!request_data_.empty()) {
+    curl_easy_setopt(curl_handle_,
+                     CURLOPT_READFUNCTION, &Connection::read_callback);
+    curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this);
+    VLOG(2) << "Raw request data: "
+        << std::string(reinterpret_cast<const char*>(request_data_.data()),
+                       request_data_.size());
+  }
+
+  curl_slist* header_list = nullptr;
+  if (!headers_.empty()) {
+    for (auto pair : headers_) {
+      std::string header = string_utils::Join(": ", pair.first, pair.second);
+      VLOG(2) << "Request header: " << header;
+      header_list = curl_slist_append(header_list, header.c_str());
+    }
+    curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER, header_list);
+  }
+
+  headers_.clear();
+
+  // Set up HTTP response data.
+  if (method_ != request_type::kHead) {
+    curl_easy_setopt(curl_handle_,
+                     CURLOPT_WRITEFUNCTION, &Connection::write_callback);
+    curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this);
+  }
+
+  // HTTP response headers
+  curl_easy_setopt(curl_handle_,
+                   CURLOPT_HEADERFUNCTION, &Connection::header_callback);
+  curl_easy_setopt(curl_handle_, CURLOPT_HEADERDATA, this);
+
+  CURLcode ret = curl_easy_perform(curl_handle_);
+  if (header_list)
+    curl_slist_free_all(header_list);
+  if (ret != CURLE_OK) {
+    Error::AddTo(error, http::curl::kErrorDomain, string_utils::ToString(ret),
+                 curl_easy_strerror(ret));
+  } else {
+    LOG(INFO) << "Response: " << GetResponseStatusCode() << " ("
+      << GetResponseStatusText() << ")";
+    VLOG(2) << "Response data (" << response_data_.size() << "): "
+        << std::string(reinterpret_cast<const char*>(response_data_.data()),
+                       response_data_.size());
+  }
+  return (ret == CURLE_OK);
+}
+
+int Connection::GetResponseStatusCode() const {
+  long status_code = 0;  // NOLINT(runtime/int) - curl expects a long here.
+  curl_easy_getinfo(curl_handle_, CURLINFO_RESPONSE_CODE, &status_code);
+  return status_code;
+}
+
+std::string Connection::GetResponseStatusText() const {
+  return status_text_;
+}
+
+std::string Connection::GetProtocolVersion() const {
+  return protocol_version_;
+}
+
+std::string Connection::GetResponseHeader(
+    const std::string& header_name) const {
+  auto p = headers_.find(header_name);
+  return p != headers_.end() ? p->second : std::string();
+}
+
+uint64_t Connection::GetResponseDataSize() const {
+  return response_data_.size();
+}
+
+bool Connection::ReadResponseData(void* data, size_t buffer_size,
+                                  size_t* size_read, ErrorPtr* error) {
+  size_t size_to_read = response_data_.size() - response_data_ptr_;
+  if (size_to_read > buffer_size)
+    size_to_read = buffer_size;
+  memcpy(data, response_data_.data() + response_data_ptr_, size_to_read);
+  if (size_read)
+    *size_read = size_to_read;
+  response_data_ptr_ += size_to_read;
+  return true;
+}
+
+size_t Connection::write_callback(char* ptr, size_t size,
+                                  size_t num, void* data) {
+  Connection* me = reinterpret_cast<Connection*>(data);
+  size_t data_len = size * num;
+  me->response_data_.insert(me->response_data_.end(), ptr, ptr + data_len);
+  return data_len;
+}
+
+size_t Connection::read_callback(char* ptr, size_t size,
+                                 size_t num, void* data) {
+  Connection* me = reinterpret_cast<Connection*>(data);
+  size_t data_len = size * num;
+
+  if (me->request_data_ptr_ >= me->request_data_.size())
+    return 0;
+
+  if (me->request_data_ptr_ + data_len > me->request_data_.size())
+    data_len = me->request_data_.size() - me->request_data_ptr_;
+
+  memcpy(ptr, me->request_data_.data() + me->request_data_ptr_, data_len);
+  me->request_data_ptr_ += data_len;
+
+  return data_len;
+}
+
+size_t Connection::header_callback(char* ptr, size_t size,
+                                   size_t num, void* data) {
+  Connection* me = reinterpret_cast<Connection*>(data);
+  size_t hdr_len = size * num;
+  std::string header(ptr, hdr_len);
+  // Remove newlines at the end of header line.
+  while (!header.empty() && (header.back() == '\r' || header.back() == '\n')) {
+    header.pop_back();
+  }
+
+  VLOG(2) << "Response header: " << header;
+
+  if (!me->status_text_set_) {
+    // First header - response code as "HTTP/1.1 200 OK".
+    // Need to extract the OK part
+    auto pair = string_utils::SplitAtFirst(header, ' ');
+    me->protocol_version_ = pair.first;
+    me->status_text_ = string_utils::SplitAtFirst(pair.second, ' ').second;
+    me->status_text_set_ = true;
+  } else {
+    auto pair = string_utils::SplitAtFirst(header, ':');
+    if (!pair.second.empty())
+      me->headers_.insert(pair);
+  }
+  return hdr_len;
+}
+
+}  // namespace curl
+}  // namespace http
+}  // namespace buffet
diff --git a/buffet/http_connection_curl.h b/buffet/http_connection_curl.h
new file mode 100644
index 0000000..9a82e26
--- /dev/null
+++ b/buffet/http_connection_curl.h
@@ -0,0 +1,86 @@
+// 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_HTTP_CONNECTION_CURL_H_
+#define BUFFET_HTTP_CONNECTION_CURL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+#include <curl/curl.h>
+
+#include "buffet/http_connection.h"
+
+namespace buffet {
+namespace http {
+namespace curl {
+
+// This is a libcurl-based implementation of http::Connection.
+class Connection : public http::Connection {
+ public:
+  Connection(CURL* curl_handle, const std::string& method,
+             std::shared_ptr<http::Transport> transport);
+  virtual ~Connection();
+
+  // Overrides from http::Connection.
+  // See http_connection.h for description of these methods.
+  virtual bool SendHeaders(const HeaderList& headers, ErrorPtr* error) override;
+  virtual bool WriteRequestData(const void* data, size_t size,
+                                ErrorPtr* error) override;
+  virtual bool FinishRequest(ErrorPtr* error) override;
+
+  virtual int GetResponseStatusCode() const override;
+  virtual std::string GetResponseStatusText() const override;
+  virtual std::string GetProtocolVersion() const override;
+  virtual std::string GetResponseHeader(
+     const std::string& header_name) const override;
+  virtual uint64_t GetResponseDataSize() const override;
+  virtual bool ReadResponseData(void* data, size_t buffer_size,
+                                size_t* size_read, ErrorPtr* error) override;
+
+ protected:
+  // Write data callback. Used by CURL when receiving response data.
+  static size_t write_callback(char* ptr, size_t size, size_t num, void* data);
+  // Read data callback. Used by CURL when sending request body data.
+  static size_t read_callback(char* ptr, size_t size, size_t num, void* data);
+  // Write header data callback. Used by CURL when receiving response headers.
+  static size_t header_callback(char* ptr, size_t size, size_t num, void* data);
+
+  // HTTP request verb, such as "GET", "POST", "PUT", ...
+  std::string method_;
+
+  // Binary data for request body.
+  std::vector<unsigned char> request_data_;
+  // Read pointer for request data. Used when streaming data to the server.
+  size_t request_data_ptr_ = 0;
+
+  // Received response data.
+  std::vector<unsigned char> response_data_;
+  size_t response_data_ptr_ = 0;
+
+  // List of optional request headers provided by the caller.
+  // After request has been sent, contains the received response headers.
+  std::map<std::string, std::string> headers_;
+
+  // HTTP protocol version, such as HTTP/1.1
+  std::string protocol_version_;
+  // Response status text, such as "OK" for 200, or "Forbidden" for 403
+  std::string status_text_;
+  // Flag used when parsing response headers to separate the response status
+  // from the rest of response headers.
+  bool status_text_set_ = false;
+
+  CURL* curl_handle_ = nullptr;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Connection);
+};
+
+}  // namespace curl
+}  // namespace http
+}  // namespace buffet
+
+#endif  // BUFFET_HTTP_CONNECTION_CURL_H_
diff --git a/buffet/http_connection_fake.cc b/buffet/http_connection_fake.cc
new file mode 100644
index 0000000..383f6d4
--- /dev/null
+++ b/buffet/http_connection_fake.cc
@@ -0,0 +1,94 @@
+// 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/http_connection_fake.h"
+
+#include <base/logging.h>
+
+#include "buffet/http_request.h"
+#include "buffet/mime_utils.h"
+#include "buffet/string_utils.h"
+
+namespace buffet {
+namespace http {
+namespace fake {
+
+Connection::Connection(const std::string& url, const std::string& method,
+                       std::shared_ptr<http::Transport> transport) :
+    http::Connection(transport), request_(url, method) {
+  VLOG(1) << "fake::Connection created: " << method;
+}
+
+Connection::~Connection() {
+  VLOG(1) << "fake::Connection destroyed";
+}
+
+bool Connection::SendHeaders(const HeaderList& headers, ErrorPtr* error) {
+  request_.AddHeaders(headers);
+  return true;
+}
+
+bool Connection::WriteRequestData(const void* data, size_t size,
+                                  ErrorPtr* error) {
+  request_.AddData(data, size);
+  return true;
+}
+
+bool Connection::FinishRequest(ErrorPtr* error) {
+  request_.AddHeaders({{request_header::kContentLength,
+                      string_utils::ToString(request_.GetData().size())}});
+  fake::Transport* transport = static_cast<fake::Transport*>(transport_.get());
+  CHECK(transport) << "Expecting a fake transport";
+  auto handler = transport->GetHandler(request_.GetURL(), request_.GetMethod());
+  if (handler.is_null()) {
+    LOG(ERROR) << "Received unexpected " << request_.GetMethod()
+               << " request at " << request_.GetURL();
+    response_.ReplyText(status_code::NotFound,
+                        "<html><body>Not found</body></html>",
+                        mime::text::kHtml);
+  } else {
+    handler.Run(request_, &response_);
+  }
+  return true;
+}
+
+int Connection::GetResponseStatusCode() const {
+  return response_.GetStatusCode();
+}
+
+std::string Connection::GetResponseStatusText() const {
+  return response_.GetStatusText();
+}
+
+std::string Connection::GetProtocolVersion() const {
+  return response_.GetProtocolVersion();
+}
+
+std::string Connection::GetResponseHeader(
+    const std::string& header_name) const {
+  return response_.GetHeader(header_name);
+}
+
+uint64_t Connection::GetResponseDataSize() const {
+  // HEAD requests must not return body.
+  return (request_.GetMethod() != request_type::kHead) ?
+      response_.GetData().size() : 0;
+}
+
+bool Connection::ReadResponseData(void* data, size_t buffer_size,
+                                  size_t* size_read, ErrorPtr* error) {
+  size_t size_to_read = GetResponseDataSize() - response_data_ptr_;
+  if (size_to_read > buffer_size)
+    size_to_read = buffer_size;
+  if (size_to_read > 0)
+    memcpy(data, response_.GetData().data() + response_data_ptr_, size_to_read);
+  if (size_read)
+    *size_read = size_to_read;
+  response_data_ptr_ += size_to_read;
+  return true;
+}
+
+}  // namespace fake
+}  // namespace http
+}  // namespace buffet
diff --git a/buffet/http_connection_fake.h b/buffet/http_connection_fake.h
new file mode 100644
index 0000000..57bf016
--- /dev/null
+++ b/buffet/http_connection_fake.h
@@ -0,0 +1,62 @@
+// 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_HTTP_CONNECTION_FAKE_H_
+#define BUFFET_HTTP_CONNECTION_FAKE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/http_connection.h"
+#include "buffet/http_transport_fake.h"
+
+namespace buffet {
+namespace http {
+namespace fake {
+
+// This is a fake implementation of http::Connection for unit testing.
+class Connection : public http::Connection {
+ public:
+  Connection(const std::string& url, const std::string& method,
+             std::shared_ptr<http::Transport> transport);
+  virtual ~Connection();
+
+  // Overrides from http::Connection.
+  // See http_connection.h for description of these methods.
+  virtual bool SendHeaders(const HeaderList& headers, ErrorPtr* error) override;
+  virtual bool WriteRequestData(const void* data, size_t size,
+                                ErrorPtr* error) override;
+  virtual bool FinishRequest(ErrorPtr* error) override;
+
+  virtual int GetResponseStatusCode() const override;
+  virtual std::string GetResponseStatusText() const override;
+  virtual std::string GetProtocolVersion() const override;
+  virtual std::string GetResponseHeader(
+     const std::string& header_name) const override;
+  virtual uint64_t GetResponseDataSize() const override;
+  virtual bool ReadResponseData(void* data, size_t buffer_size,
+                                size_t* size_read, ErrorPtr* error) override;
+
+ private:
+  // Request and response objects passed to the user-provided request handler
+  // callback. The request object contains all the request information.
+  // The response object is the server response that is created by
+  // the handler in response to the request.
+  ServerRequest request_;
+  ServerResponse response_;
+
+  // Internal read data pointer needed for ReadResponseData() implementation.
+  size_t response_data_ptr_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(Connection);
+};
+
+}  // namespace fake
+}  // namespace http
+}  // namespace buffet
+
+#endif  // BUFFET_HTTP_CONNECTION_FAKE_H_
diff --git a/buffet/http_request.cc b/buffet/http_request.cc
new file mode 100644
index 0000000..c08e797
--- /dev/null
+++ b/buffet/http_request.cc
@@ -0,0 +1,297 @@
+// 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/http_request.h"
+
+#include <base/logging.h>
+
+#include "buffet/http_connection_curl.h"
+#include "buffet/http_transport_curl.h"
+#include "buffet/map_utils.h"
+#include "buffet/mime_utils.h"
+#include "buffet/string_utils.h"
+
+namespace buffet {
+namespace http {
+
+// request_type
+const char request_type::kOptions[]               = "OPTIONS";
+const char request_type::kGet[]                   = "GET";
+const char request_type::kHead[]                  = "HEAD";
+const char request_type::kPost[]                  = "POST";
+const char request_type::kPut[]                   = "PUT";
+const char request_type::kPatch[]                 = "PATCH";
+const char request_type::kDelete[]                = "DELETE";
+const char request_type::kTrace[]                 = "TRACE";
+const char request_type::kConnect[]               = "CONNECT";
+const char request_type::kCopy[]                  = "COPY";
+const char request_type::kMove[]                  = "MOVE";
+
+// request_header
+const char request_header::kAccept[]              = "Accept";
+const char request_header::kAcceptCharset[]       = "Accept-Charset";
+const char request_header::kAcceptEncoding[]      = "Accept-Encoding";
+const char request_header::kAcceptLanguage[]      = "Accept-Language";
+const char request_header::kAllow[]               = "Allow";
+const char request_header::kAuthorization[]       = "Authorization";
+const char request_header::kCacheControl[]        = "Cache-Control";
+const char request_header::kConnection[]          = "Connection";
+const char request_header::kContentEncoding[]     = "Content-Encoding";
+const char request_header::kContentLanguage[]     = "Content-Language";
+const char request_header::kContentLength[]       = "Content-Length";
+const char request_header::kContentLocation[]     = "Content-Location";
+const char request_header::kContentMd5[]          = "Content-MD5";
+const char request_header::kContentRange[]        = "Content-Range";
+const char request_header::kContentType[]         = "Content-Type";
+const char request_header::kCookie[]              = "Cookie";
+const char request_header::kDate[]                = "Date";
+const char request_header::kExpect[]              = "Expect";
+const char request_header::kExpires[]             = "Expires";
+const char request_header::kFrom[]                = "From";
+const char request_header::kHost[]                = "Host";
+const char request_header::kIfMatch[]             = "If-Match";
+const char request_header::kIfModifiedSince[]     = "If-Modified-Since";
+const char request_header::kIfNoneMatch[]         = "If-None-Match";
+const char request_header::kIfRange[]             = "If-Range";
+const char request_header::kIfUnmodifiedSince[]   = "If-Unmodified-Since";
+const char request_header::kLastModified[]        = "Last-Modified";
+const char request_header::kMaxForwards[]         = "Max-Forwards";
+const char request_header::kPragma[]              = "Pragma";
+const char request_header::kProxyAuthorization[]  = "Proxy-Authorization";
+const char request_header::kRange[]               = "Range";
+const char request_header::kReferer[]             = "Referer";
+const char request_header::kTE[]                  = "TE";
+const char request_header::kTrailer[]             = "Trailer";
+const char request_header::kTransferEncoding[]    = "Transfer-Encoding";
+const char request_header::kUpgrade[]             = "Upgrade";
+const char request_header::kUserAgent[]           = "User-Agent";
+const char request_header::kVia[]                 = "Via";
+const char request_header::kWarning[]             = "Warning";
+
+// response_header
+const char response_header::kAcceptRanges[]       = "Accept-Ranges";
+const char response_header::kAge[]                = "Age";
+const char response_header::kAllow[]              = "Allow";
+const char response_header::kCacheControl[]       = "Cache-Control";
+const char response_header::kConnection[]         = "Connection";
+const char response_header::kContentEncoding[]    = "Content-Encoding";
+const char response_header::kContentLanguage[]    = "Content-Language";
+const char response_header::kContentLength[]      = "Content-Length";
+const char response_header::kContentLocation[]    = "Content-Location";
+const char response_header::kContentMd5[]         = "Content-MD5";
+const char response_header::kContentRange[]       = "Content-Range";
+const char response_header::kContentType[]        = "Content-Type";
+const char response_header::kDate[]               = "Date";
+const char response_header::kETag[]               = "ETag";
+const char response_header::kExpires[]            = "Expires";
+const char response_header::kLastModified[]       = "Last-Modified";
+const char response_header::kLocation[]           = "Location";
+const char response_header::kPragma[]             = "Pragma";
+const char response_header::kProxyAuthenticate[]  = "Proxy-Authenticate";
+const char response_header::kRetryAfter[]         = "Retry-After";
+const char response_header::kServer[]             = "Server";
+const char response_header::kSetCookie[]          = "Set-Cookie";
+const char response_header::kTrailer[]            = "Trailer";
+const char response_header::kTransferEncoding[]   = "Transfer-Encoding";
+const char response_header::kUpgrade[]            = "Upgrade";
+const char response_header::kVary[]               = "Vary";
+const char response_header::kVia[]                = "Via";
+const char response_header::kWarning[]            = "Warning";
+const char response_header::kWwwAuthenticate[]    = "WWW-Authenticate";
+
+// ***********************************************************
+// ********************** Request Class **********************
+// ***********************************************************
+Request::Request(const std::string& url, const char* method,
+                 std::shared_ptr<Transport> transport) :
+    transport_(transport), request_url_(url), method_(method) {
+  VLOG(1) << "http::Request created";
+  if (!transport_)
+    transport_.reset(new http::curl::Transport());
+}
+
+Request::~Request() {
+  VLOG(1) << "http::Request destroyed";
+}
+
+void Request::AddRange(int64_t bytes) {
+  if (bytes < 0) {
+    ranges_.emplace_back(Request::range_value_omitted, -bytes);
+  } else {
+    ranges_.emplace_back(bytes, Request::range_value_omitted);
+  }
+}
+
+void Request::AddRange(uint64_t from_byte, uint64_t to_byte) {
+  ranges_.emplace_back(from_byte, to_byte);
+}
+
+std::unique_ptr<Response> Request::GetResponse(ErrorPtr* error) {
+  if (!SendRequestIfNeeded(error) || !connection_->FinishRequest(error))
+    return std::unique_ptr<Response>();
+  std::unique_ptr<Response> response(new Response(std::move(connection_)));
+  transport_.reset();  // Indicate that the response has been received
+  return response;
+}
+
+void Request::SetAccept(const char* accept_mime_types) {
+  accept_ = accept_mime_types;
+}
+
+std::string Request::GetAccept() const {
+  return accept_;
+}
+
+void Request::SetContentType(const char* contentType) {
+  content_type_ = contentType;
+}
+
+std::string Request::GetContentType() const {
+  return content_type_;
+}
+
+void Request::AddHeader(const char* header, const char* value) {
+  headers_[header] = value;
+}
+
+void Request::AddHeaders(const HeaderList& headers) {
+  headers_.insert(headers.begin(), headers.end());
+}
+
+bool Request::AddRequestBody(const void* data, size_t size, ErrorPtr* error) {
+  if (!SendRequestIfNeeded(error))
+    return false;
+  return connection_->WriteRequestData(data, size, error);
+}
+
+void Request::SetReferer(const char* referer) {
+  referer_ = referer;
+}
+
+std::string Request::GetReferer() const {
+  return referer_;
+}
+
+void Request::SetUserAgent(const char* user_agent) {
+  user_agent_ = user_agent;
+}
+
+std::string Request::GetUserAgent() const {
+  return user_agent_;
+}
+
+bool Request::SendRequestIfNeeded(ErrorPtr* error) {
+  if (transport_) {
+    if (!connection_) {
+      http::HeaderList headers = MapToVector(headers_);
+      std::vector<std::string> ranges;
+      if (method_ != request_type::kHead) {
+        ranges.reserve(ranges_.size());
+        for (auto p : ranges_) {
+          if (p.first != range_value_omitted ||
+              p.second != range_value_omitted) {
+            std::string range;
+            if (p.first != range_value_omitted) {
+              range = string_utils::ToString(p.first);
+            }
+            range += '-';
+            if (p.second != range_value_omitted) {
+              range += string_utils::ToString(p.second);
+            }
+            ranges.push_back(range);
+          }
+        }
+      }
+      if (!ranges.empty())
+        headers.emplace_back(request_header::kRange,
+                             "bytes=" + string_utils::Join(',', ranges));
+
+      headers.emplace_back(request_header::kAccept, GetAccept());
+      if (method_ != request_type::kGet && method_ != request_type::kHead) {
+        if (!content_type_.empty())
+          headers.emplace_back(request_header::kContentType, content_type_);
+      }
+      connection_ = transport_->CreateConnection(transport_, request_url_,
+                                                 method_, headers,
+                                                 user_agent_, referer_,
+                                                 error);
+    }
+
+    if (connection_)
+      return true;
+  } else {
+    Error::AddTo(error, http::curl::kErrorDomain,
+                 "request_already_received", "HTTP response already received");
+  }
+  return false;
+}
+
+// ************************************************************
+// ********************** Response Class **********************
+// ************************************************************
+Response::Response(std::unique_ptr<Connection> connection)
+    : connection_(std::move(connection)) {
+  VLOG(1) << "http::Response created";
+  // Response object doesn't have streaming interface for response data (yet),
+  // so read the data into a buffer and cache it.
+  if (connection_) {
+    size_t size = static_cast<size_t>(connection_->GetResponseDataSize());
+    response_data_.reserve(size);
+    unsigned char buffer[1024];
+    size_t read = 0;
+    while (connection_->ReadResponseData(buffer, sizeof(buffer),
+                                         &read, nullptr) && read > 0) {
+      response_data_.insert(response_data_.end(), buffer, buffer + read);
+    }
+  }
+}
+
+Response::~Response() {
+  VLOG(1) << "http::Response destroyed";
+}
+
+bool Response::IsSuccessful() const {
+  int code = GetStatusCode();
+  return code >= status_code::Continue && code < status_code::BadRequest;
+}
+
+int Response::GetStatusCode() const {
+  if (!connection_)
+    return -1;
+
+  return connection_->GetResponseStatusCode();
+}
+
+std::string Response::GetStatusText() const {
+  if (!connection_)
+    return std::string();
+
+  return connection_->GetResponseStatusText();
+}
+
+std::string Response::GetContentType() const {
+  return GetHeader(response_header::kContentType);
+}
+
+const std::vector<unsigned char>& Response::GetData() const {
+  return response_data_;
+}
+
+std::string Response::GetDataAsString() const {
+  if (response_data_.empty())
+    return std::string();
+
+  const char* data_buf = reinterpret_cast<const char*>(response_data_.data());
+  return std::string(data_buf, data_buf + response_data_.size());
+}
+
+std::string Response::GetHeader(const char* header_name) const {
+  if (connection_)
+    return connection_->GetResponseHeader(header_name);
+
+  return std::string();
+}
+
+}  // namespace http
+}  // namespace buffet
diff --git a/buffet/http_request.h b/buffet/http_request.h
new file mode 100644
index 0000000..e5b9710
--- /dev/null
+++ b/buffet/http_request.h
@@ -0,0 +1,352 @@
+// 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_HTTP_REQUEST_H_
+#define BUFFET_HTTP_REQUEST_H_
+
+#include <limits>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/http_connection.h"
+#include "buffet/http_transport.h"
+#include "buffet/error.h"
+
+namespace buffet {
+namespace http {
+
+// HTTP request verbs
+namespace request_type {
+  extern const char kOptions[];
+  extern const char kGet[];
+  extern const char kHead[];
+  extern const char kPost[];
+  extern const char kPut[];
+  extern const char kPatch[];  // Not a standard HTTP/1.1 request method
+  extern const char kDelete[];
+  extern const char kTrace[];
+  extern const char kConnect[];
+  extern const char kCopy[];   // Not a standard HTTP/1.1 request method
+  extern const char kMove[];   // Not a standard HTTP/1.1 request method
+}  // namespace request_type
+
+// HTTP request header names
+namespace request_header {
+  extern const char kAccept[];
+  extern const char kAcceptCharset[];
+  extern const char kAcceptEncoding[];
+  extern const char kAcceptLanguage[];
+  extern const char kAllow[];
+  extern const char kAuthorization[];
+  extern const char kCacheControl[];
+  extern const char kConnection[];
+  extern const char kContentEncoding[];
+  extern const char kContentLanguage[];
+  extern const char kContentLength[];
+  extern const char kContentLocation[];
+  extern const char kContentMd5[];
+  extern const char kContentRange[];
+  extern const char kContentType[];
+  extern const char kCookie[];
+  extern const char kDate[];
+  extern const char kExpect[];
+  extern const char kExpires[];
+  extern const char kFrom[];
+  extern const char kHost[];
+  extern const char kIfMatch[];
+  extern const char kIfModifiedSince[];
+  extern const char kIfNoneMatch[];
+  extern const char kIfRange[];
+  extern const char kIfUnmodifiedSince[];
+  extern const char kLastModified[];
+  extern const char kMaxForwards[];
+  extern const char kPragma[];
+  extern const char kProxyAuthorization[];
+  extern const char kRange[];
+  extern const char kReferer[];
+  extern const char kTE[];
+  extern const char kTrailer[];
+  extern const char kTransferEncoding[];
+  extern const char kUpgrade[];
+  extern const char kUserAgent[];
+  extern const char kVia[];
+  extern const char kWarning[];
+}  // namespace request_header
+
+// HTTP response header names
+namespace response_header {
+  extern const char kAcceptRanges[];
+  extern const char kAge[];
+  extern const char kAllow[];
+  extern const char kCacheControl[];
+  extern const char kConnection[];
+  extern const char kContentEncoding[];
+  extern const char kContentLanguage[];
+  extern const char kContentLength[];
+  extern const char kContentLocation[];
+  extern const char kContentMd5[];
+  extern const char kContentRange[];
+  extern const char kContentType[];
+  extern const char kDate[];
+  extern const char kETag[];
+  extern const char kExpires[];
+  extern const char kLastModified[];
+  extern const char kLocation[];
+  extern const char kPragma[];
+  extern const char kProxyAuthenticate[];
+  extern const char kRetryAfter[];
+  extern const char kServer[];
+  extern const char kSetCookie[];
+  extern const char kTrailer[];
+  extern const char kTransferEncoding[];
+  extern const char kUpgrade[];
+  extern const char kVary[];
+  extern const char kVia[];
+  extern const char kWarning[];
+  extern const char kWwwAuthenticate[];
+}  // namespace response_header
+
+// HTTP request status (error) codes
+namespace status_code {
+  // OK to continue with request
+  static const int Continue = 100;
+  // Server has switched protocols in upgrade header
+  static const int SwitchProtocols = 101;
+
+  // Request completed
+  static const int Ok = 200;
+  // Object created, reason = new URI
+  static const int Created = 201;
+  // Async completion (TBS)
+  static const int Accepted = 202;
+  // Partial completion
+  static const int Partial = 203;
+  // No info to return
+  static const int NoContent = 204;
+  // Request completed, but clear form
+  static const int ResetContent = 205;
+  // Partial GET fulfilled
+  static const int PartialContent = 206;
+
+  // Server couldn't decide what to return
+  static const int Ambiguous = 300;
+  // Object permanently moved
+  static const int Moved = 301;
+  // Object temporarily moved
+  static const int Redirect = 302;
+  // Redirection w/ new access method
+  static const int RedirectMethod = 303;
+  // If-Modified-Since was not modified
+  static const int NotModified = 304;
+  // Redirection to proxy, location header specifies proxy to use
+  static const int UseProxy = 305;
+  // HTTP/1.1: keep same verb
+  static const int RedirectKeepVerb = 307;
+
+  // Invalid syntax
+  static const int BadRequest = 400;
+  // Access denied
+  static const int Denied = 401;
+  // Payment required
+  static const int PaymentRequired = 402;
+  // Request forbidden
+  static const int Forbidden = 403;
+  // Object not found
+  static const int NotFound = 404;
+  // Method is not allowed
+  static const int BadMethod = 405;
+  // No response acceptable to client found
+  static const int NoneAcceptable = 406;
+  // Proxy authentication required
+  static const int ProxyAuthRequired = 407;
+  // Server timed out waiting for request
+  static const int RequestTimeout = 408;
+  // User should resubmit with more info
+  static const int Conflict = 409;
+  // The resource is no longer available
+  static const int Gone = 410;
+  // The server refused to accept request w/o a length
+  static const int LengthRequired = 411;
+  // Precondition given in request failed
+  static const int PrecondionFailed = 412;
+  // Request entity was too large
+  static const int RequestTooLarge = 413;
+  // Request URI too long
+  static const int UriTooLong = 414;
+  // Unsupported media type
+  static const int UnsupportedMedia = 415;
+  // Retry after doing the appropriate action.
+  static const int RetryWith = 449;
+
+  // Internal server error
+  static const int InternalServerError = 500;
+  // Request not supported
+  static const int NotSupported = 501;
+  // Error response received from gateway
+  static const int BadGateway = 502;
+  // Temporarily overloaded
+  static const int ServiceUnavailable = 503;
+  // Timed out waiting for gateway
+  static const int GatewayTimeout = 504;
+  // HTTP version not supported
+  static const int VersionNotSupported = 505;
+}  // namespace status_code
+
+class Response;  // Just a forward declaration.
+
+///////////////////////////////////////////////////////////////////////////////
+// Request class is the main object used to set up and initiate an HTTP
+// communication session. It is used to specify the HTTP request method,
+// request URL and many optional parameters (such as HTTP headers, user agent,
+// referer URL and so on.
+//
+// Once everything is setup, GetResponse() method is used to send the request
+// and obtain the server response. The returned Response object can be
+// used to inspect the response code, HTTP headers and/or response body.
+///////////////////////////////////////////////////////////////////////////////
+class Request {
+ public:
+  // The main constructor. |url| specifies the remote host address/path
+  // to send the request to. |method| is the HTTP request verb and
+  // |transport| is the HTTP transport implementation for server communications.
+  Request(const std::string& url, const char* method,
+          std::shared_ptr<Transport> transport);
+  ~Request();
+
+  // Gets/Sets "Accept:" header value. The default value is "*/*" if not set.
+  void SetAccept(const char* accept_mime_types);
+  std::string GetAccept() const;
+
+  // Gets/Sets "Content-Type:" header value
+  void SetContentType(const char* content_type);
+  std::string GetContentType() const;
+
+  // Adds additional HTTP request header
+  void AddHeader(const char* header, const char* value);
+  void AddHeaders(const HeaderList& headers);
+
+  // Removes HTTP request header
+  void RemoveHeader(const char* header);
+
+  // Adds a request body. This is not to be used with GET method
+  bool AddRequestBody(const void* data, size_t size, ErrorPtr* error);
+
+  // Makes a request for a subrange of data. Specifies a partial range with
+  // either from beginning of the data to the specified offset (if |bytes| is
+  // negative) or from the specified offset to the end of data (if |bytes| is
+  // positive).
+  // All individual ranges will be sent as part of "Range:" HTTP request header.
+  void AddRange(int64_t bytes);
+
+  // Makes a request for a subrange of data. Specifies a full range with
+  // start and end bytes from the beginning of the requested data.
+  // All individual ranges will be sent as part of "Range:" HTTP request header.
+  void AddRange(uint64_t from_byte, uint64_t to_byte);
+
+  // Returns the request URL
+  std::string GetRequestURL() const;
+
+  // Gets/Sets a request referer URL (sent as "Referer:" request header).
+  void SetReferer(const char* referer);
+  std::string GetReferer() const;
+
+  // Gets/Sets a user agent string (sent as "User-Agent:" request header).
+  void SetUserAgent(const char* user_agent);
+  std::string GetUserAgent() const;
+
+  // Sends the request to the server and returns the response object.
+  // In case the server couldn't be reached for whatever reason, returns
+  // empty unique_ptr (null). In such a case, the additional error information
+  // can be returned through the optional supplied |error| parameter.
+  std::unique_ptr<Response> GetResponse(ErrorPtr* error);
+
+ private:
+  // Helper function to create an http::Connection and send off request headers.
+  bool SendRequestIfNeeded(ErrorPtr* error);
+
+  // Implementation that provides particular HTTP transport.
+  std::shared_ptr<Transport> transport_;
+
+  // An established connection for adding request body. This connection
+  // is maintained by the request object after the headers have been
+  // sent and before the response is requested.
+  std::unique_ptr<Connection> connection_;
+
+  // Full request URL, such as "http://www.host.com/path/to/object"
+  std::string request_url_;
+  // HTTP request verb, such as "GET", "POST", "PUT", ...
+  std::string method_;
+
+  // Referrer URL, if any. Sent to the server via "Referer: " header.
+  std::string referer_;
+  // User agent string, if any. Sent to the server via "User-Agent: " header.
+  std::string user_agent_;
+  // Content type of the request body data.
+  // Sent to the server via "Content-Type: " header.
+  std::string content_type_;
+  // List of acceptable response data types.
+  // Sent to the server via "Accept: " header.
+  std::string accept_ = "*/*";
+
+  // List of optional request headers provided by the caller.
+  // After request has been sent, contains the received response headers.
+  std::map<std::string, std::string> headers_;
+  // List of optional data ranges to request partial content from the server.
+  // Sent to the server as "Range: " header.
+  std::vector<std::pair<uint64_t, uint64_t>> ranges_;
+
+  // range_value_omitted is used in |ranges_| list to indicate omitted value.
+  // E.g. range (10,range_value_omitted) represents bytes from 10 to the end
+  // of the data stream.
+  const uint64_t range_value_omitted = std::numeric_limits<uint64_t>::max();
+
+  DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Response class is returned from Request::GetResponse() and is a way
+// to get to response status, error codes, response HTTP headers and response
+// data (body) if available.
+///////////////////////////////////////////////////////////////////////////////
+class Response {
+ public:
+  explicit Response(std::unique_ptr<Connection> connection);
+  ~Response();
+
+  // Returns true if server returned a success code (status code below 400).
+  bool IsSuccessful() const;
+
+  // Returns the HTTP status code (e.g. 200 for success)
+  int GetStatusCode() const;
+
+  // Returns the status text (e.g. for error 403 it could be "NOT AUTHORIZED").
+  std::string GetStatusText() const;
+
+  // Returns the content type of the response data.
+  std::string GetContentType() const;
+
+  // Returns response data as a byte array
+  const std::vector<unsigned char>& GetData() const;
+
+  // Returns response data as a string
+  std::string GetDataAsString() const;
+
+  // Returns a value of a given response HTTP header.
+  std::string GetHeader(const char* header_name) const;
+
+ private:
+  std::unique_ptr<Connection> connection_;
+  std::vector<unsigned char> response_data_;
+  DISALLOW_COPY_AND_ASSIGN(Response);
+};
+
+}  // namespace http
+}  // namespace buffet
+
+#endif  // BUFFET_HTTP_REQUEST_H_
diff --git a/buffet/http_transport.h b/buffet/http_transport.h
new file mode 100644
index 0000000..e26ee77
--- /dev/null
+++ b/buffet/http_transport.h
@@ -0,0 +1,54 @@
+// 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_HTTP_TRANSPORT_H_
+#define BUFFET_HTTP_TRANSPORT_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/error.h"
+
+namespace buffet {
+namespace http {
+
+typedef std::vector<std::pair<std::string, std::string>> HeaderList;
+
+class Request;
+class Connection;
+
+///////////////////////////////////////////////////////////////////////////////
+// Transport is a base class for specific implementation of HTTP communication.
+// This class (and its underlying implementation) is used by http::Request and
+// http::Response classes to provide HTTP functionality to the clients.
+///////////////////////////////////////////////////////////////////////////////
+class Transport {
+ public:
+  Transport() = default;
+  virtual ~Transport() = default;
+
+  // Creates a connection object and initializes it with the specified data.
+  // |transport| is a shared pointer to this transport object instance,
+  // used to maintain the object alive as long as the connection exists.
+  virtual std::unique_ptr<Connection> CreateConnection(
+      std::shared_ptr<Transport> transport,
+      const std::string& url,
+      const std::string& method,
+      const HeaderList& headers,
+      const std::string& user_agent,
+      const std::string& referer,
+      ErrorPtr* error) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Transport);
+};
+
+}  // namespace http
+}  // namespace buffet
+
+#endif  // BUFFET_HTTP_TRANSPORT_H_
diff --git a/buffet/http_transport_curl.cc b/buffet/http_transport_curl.cc
new file mode 100644
index 0000000..7ef914b
--- /dev/null
+++ b/buffet/http_transport_curl.cc
@@ -0,0 +1,81 @@
+// 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/http_transport_curl.h"
+
+#include <base/logging.h>
+
+#include "buffet/http_connection_curl.h"
+#include "buffet/http_request.h"
+
+namespace buffet {
+namespace http {
+namespace curl {
+
+const char kErrorDomain[] = "http_transport";
+
+Transport::Transport() {
+  VLOG(1) << "curl::Transport created";
+}
+
+Transport::~Transport() {
+  VLOG(1) << "curl::Transport destroyed";
+}
+
+std::unique_ptr<http::Connection> Transport::CreateConnection(
+    std::shared_ptr<http::Transport> transport,
+    const std::string& url,
+    const std::string& method,
+    const HeaderList& headers,
+    const std::string& user_agent,
+    const std::string& referer,
+    ErrorPtr* error) {
+  CURL* curl_handle = curl_easy_init();
+  if (!curl_handle) {
+    LOG(ERROR) << "Failed to initialize CURL";
+    Error::AddTo(error, http::curl::kErrorDomain, "curl_init_failed",
+                 "Failed to initialize CURL");
+    return std::unique_ptr<http::Connection>();
+  }
+
+  LOG(INFO) << "Sending a " << method << " request to " << url;
+  curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
+
+  if (!user_agent.empty()) {
+    curl_easy_setopt(curl_handle,
+                     CURLOPT_USERAGENT, user_agent.c_str());
+  }
+
+  if (!referer.empty()) {
+    curl_easy_setopt(curl_handle,
+                     CURLOPT_REFERER, referer.c_str());
+  }
+
+  // Setup HTTP request method and optional request body.
+  if (method ==  request_type::kGet) {
+    curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 1L);
+  } else if (method == request_type::kHead) {
+    curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1L);
+  } else if (method == request_type::kPut) {
+    curl_easy_setopt(curl_handle, CURLOPT_UPLOAD, 1L);
+  } else {
+    // POST and custom request methods
+    curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
+    curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, nullptr);
+    if (method != request_type::kPost)
+      curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, method.c_str());
+  }
+
+  std::unique_ptr<http::Connection> connection(
+      new http::curl::Connection(curl_handle, method, transport));
+  CHECK(connection) << "Unable to create Connection object";
+  if (!connection->SendHeaders(headers, error)) {
+    connection.reset();
+  }
+  return connection;
+}
+
+}  // namespace curl
+}  // namespace http
+}  // namespace buffet
diff --git a/buffet/http_transport_curl.h b/buffet/http_transport_curl.h
new file mode 100644
index 0000000..ddb24ce
--- /dev/null
+++ b/buffet/http_transport_curl.h
@@ -0,0 +1,47 @@
+// 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_HTTP_TRANSPORT_CURL_H_
+#define BUFFET_HTTP_TRANSPORT_CURL_H_
+
+#include <string>
+
+#include "buffet/http_transport.h"
+
+namespace buffet {
+namespace http {
+namespace curl {
+
+extern const char kErrorDomain[];
+
+///////////////////////////////////////////////////////////////////////////////
+// An implementation of http::Transport that uses libcurl for
+// HTTP communications. This class (as http::Transport base)
+// is used by http::Request and http::Response classes to provide HTTP
+// functionality to the clients.
+// See http_transport.h for more details.
+///////////////////////////////////////////////////////////////////////////////
+class Transport : public http::Transport {
+ public:
+  Transport();
+  virtual ~Transport();
+
+  virtual std::unique_ptr<http::Connection> CreateConnection(
+      std::shared_ptr<http::Transport> transport,
+      const std::string& url,
+      const std::string& method,
+      const HeaderList& headers,
+      const std::string& user_agent,
+      const std::string& referer,
+      ErrorPtr* error) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Transport);
+};
+
+}  // namespace curl
+}  // namespace http
+}  // namespace buffet
+
+#endif  // BUFFET_HTTP_TRANSPORT_CURL_H_
diff --git a/buffet/http_transport_fake.cc b/buffet/http_transport_fake.cc
new file mode 100644
index 0000000..471de11
--- /dev/null
+++ b/buffet/http_transport_fake.cc
@@ -0,0 +1,261 @@
+// 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/http_transport_fake.h"
+
+#include <utility>
+
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+
+#include "buffet/bind_lambda.h"
+#include "buffet/http_connection_fake.h"
+#include "buffet/http_request.h"
+#include "buffet/mime_utils.h"
+#include "buffet/string_utils.h"
+#include "buffet/url_utils.h"
+
+namespace buffet {
+
+using http::fake::Transport;
+using http::fake::ServerRequestResponseBase;
+using http::fake::ServerRequest;
+using http::fake::ServerResponse;
+
+Transport::Transport() {
+  VLOG(1) << "fake::Transport created";
+}
+
+Transport::~Transport() {
+  VLOG(1) << "fake::Transport destroyed";
+}
+
+std::unique_ptr<http::Connection> Transport::CreateConnection(
+    std::shared_ptr<http::Transport> transport,
+    const std::string& url,
+    const std::string& method,
+    const HeaderList& headers,
+    const std::string& user_agent,
+    const std::string& referer,
+    ErrorPtr* error) {
+  HeaderList headers_copy = headers;
+  if (!user_agent.empty()) {
+    headers_copy.push_back(std::make_pair(http::request_header::kUserAgent,
+                                          user_agent));
+  }
+  if (!referer.empty()) {
+    headers_copy.push_back(std::make_pair(http::request_header::kReferer,
+                                          referer));
+  }
+  std::unique_ptr<http::Connection> connection(
+      new http::fake::Connection(url, method, transport));
+  CHECK(connection) << "Unable to create Connection object";
+  if (!connection->SendHeaders(headers_copy, error))
+    connection.reset();
+  request_count_++;
+  return connection;
+}
+
+static inline std::string GetHandlerMapKey(const std::string& url,
+                                           const std::string& method) {
+  return method + ":" + url;
+}
+
+void Transport::AddHandler(const std::string& url, const std::string& method,
+                           const HandlerCallback& handler) {
+  handlers_.insert(std::make_pair(GetHandlerMapKey(url, method), handler));
+}
+
+void Transport::AddSimpleReplyHandler(const std::string& url,
+                                      const std::string& method,
+                                      int status_code,
+                                      const std::string& reply_text,
+                                      const std::string& mime_type) {
+  auto handler = [status_code, reply_text, mime_type](
+      const ServerRequest& request, ServerResponse* response) {
+    response->ReplyText(status_code, reply_text, mime_type.c_str());
+  };
+  AddHandler(url, method, base::Bind(handler));
+}
+
+Transport::HandlerCallback Transport::GetHandler(
+    const std::string& url, const std::string& method) const {
+  // First try the exact combination of URL/Method
+  auto p = handlers_.find(GetHandlerMapKey(url, method));
+  if (p != handlers_.end())
+    return p->second;
+  // If not found, try URL/*
+  p = handlers_.find(GetHandlerMapKey(url, "*"));
+  if (p != handlers_.end())
+    return p->second;
+  // If still not found, try */method
+  p = handlers_.find(GetHandlerMapKey("*", method));
+  if (p != handlers_.end())
+    return p->second;
+  // Finally, try */*
+  p = handlers_.find(GetHandlerMapKey("*", "*"));
+  return (p != handlers_.end()) ? p->second : HandlerCallback();
+}
+
+void ServerRequestResponseBase::AddData(const void* data, size_t data_size) {
+  auto bytes = reinterpret_cast<const unsigned char*>(data);
+  data_.insert(data_.end(), bytes, bytes + data_size);
+}
+
+std::string ServerRequestResponseBase::GetDataAsString() const {
+  if (data_.empty())
+    return std::string();
+  auto chars = reinterpret_cast<const char*>(data_.data());
+  return std::string(chars, data_.size());
+}
+
+std::unique_ptr<base::DictionaryValue>
+    ServerRequestResponseBase::GetDataAsJson() const {
+  if (mime::RemoveParameters(GetHeader(request_header::kContentType)) ==
+      mime::application::kJson) {
+    auto value = base::JSONReader::Read(GetDataAsString());
+    if (value) {
+      base::DictionaryValue* dict = nullptr;
+      if (value->GetAsDictionary(&dict)) {
+        return std::unique_ptr<base::DictionaryValue>(dict);
+      } else {
+        delete value;
+      }
+    }
+  }
+  return std::unique_ptr<base::DictionaryValue>();
+}
+
+void ServerRequestResponseBase::AddHeaders(const HeaderList& headers) {
+  for (const auto& pair : headers) {
+    if (pair.second.empty())
+      headers_.erase(pair.first);
+    else
+      headers_.insert(pair);
+  }
+}
+
+std::string ServerRequestResponseBase::GetHeader(
+    const std::string& header_name) const {
+  auto p = headers_.find(header_name);
+  return p != headers_.end() ? p->second : std::string();
+}
+
+ServerRequest::ServerRequest(const std::string& url,
+                             const std::string& method) : method_(method) {
+  auto params = url::GetQueryStringParameters(url);
+  url_ = url::RemoveQueryString(url, true);
+  form_fields_.insert(params.begin(), params.end());
+}
+
+std::string ServerRequest::GetFormField(const std::string& field_name) const {
+  if (!form_fields_parsed_) {
+    std::string mime_type = mime::RemoveParameters(
+        GetHeader(request_header::kContentType));
+    if (mime_type == mime::application::kWwwFormUrlEncoded &&
+        !GetData().empty()) {
+      auto fields = data_encoding::WebParamsDecode(GetDataAsString());
+      form_fields_.insert(fields.begin(), fields.end());
+    }
+    form_fields_parsed_ = true;
+  }
+  auto p = form_fields_.find(field_name);
+  return p != form_fields_.end() ? p->second : std::string();
+}
+
+void ServerResponse::Reply(int status_code, const void* data, size_t data_size,
+                           const char* mime_type) {
+  data_.clear();
+  status_code_ = status_code;
+  AddData(data, data_size);
+  AddHeaders({
+    {response_header::kContentLength, string_utils::ToString(data_size)},
+    {response_header::kContentType, mime_type}
+  });
+}
+
+void ServerResponse::ReplyText(int status_code, const std::string& text,
+                               const char* mime_type) {
+  Reply(status_code, text.data(), text.size(), mime_type);
+}
+
+void ServerResponse::ReplyJson(int status_code, const base::Value* json) {
+  std::string text;
+  base::JSONWriter::WriteWithOptions(json,
+                                     base::JSONWriter::OPTIONS_PRETTY_PRINT,
+                                     &text);
+  std::string mime_type = mime::AppendParameter(mime::application::kJson,
+                                                mime::parameters::kCharset,
+                                                "utf-8");
+  ReplyText(status_code, text, mime_type.c_str());
+}
+
+void ServerResponse::ReplyJson(int status_code,
+                               const http::FormFieldList& fields) {
+  base::DictionaryValue json;
+  for (const auto& pair : fields) {
+    json.SetString(pair.first, pair.second);
+  }
+  ReplyJson(status_code, &json);
+}
+
+std::string ServerResponse::GetStatusText() const {
+  static std::vector<std::pair<int, const char*>> status_text_map = {
+    {100, "Continue"},
+    {101, "Switching Protocols"},
+    {102, "Processing"},
+    {200, "OK"},
+    {201, "Created"},
+    {202, "Accepted"},
+    {203, "Non-Authoritative Information"},
+    {204, "No Content"},
+    {205, "Reset Content"},
+    {206, "Partial Content"},
+    {207, "Multi-Status"},
+    {208, "Already Reported"},
+    {226, "IM Used"},
+    {300, "Multiple Choices"},
+    {301, "Moved Permanently"},
+    {302, "Found"},
+    {303, "See Other"},
+    {304, "Not Modified"},
+    {305, "Use Proxy"},
+    {306, "Switch Proxy"},
+    {307, "Temporary Redirect"},
+    {308, "Permanent Redirect"},
+    {400, "Bad Request"},
+    {401, "Unauthorized"},
+    {402, "Payment Required"},
+    {403, "Forbidden"},
+    {404, "Not Found"},
+    {405, "Method Not Allowed"},
+    {406, "Not Acceptable"},
+    {407, "Proxy Authentication Required"},
+    {408, "Request Timeout"},
+    {409, "Conflict"},
+    {410, "Gone"},
+    {411, "Length Required"},
+    {412, "Precondition Failed"},
+    {413, "Request Entity Too Large"},
+    {414, "Request - URI Too Long"},
+    {415, "Unsupported Media Type"},
+    {429, "Too Many Requests"},
+    {431, "Request Header Fields Too Large"},
+    {500, "Internal Server Error"},
+    {501, "Not Implemented"},
+    {502, "Bad Gateway"},
+    {503, "Service Unavailable"},
+    {504, "Gateway Timeout"},
+    {505, "HTTP Version Not Supported"},
+  };
+
+  for (const auto& pair : status_text_map) {
+    if (pair.first == status_code_)
+      return pair.second;
+  }
+  return std::string();
+}
+
+}  // namespace buffet
diff --git a/buffet/http_transport_fake.h b/buffet/http_transport_fake.h
new file mode 100644
index 0000000..7905f3e
--- /dev/null
+++ b/buffet/http_transport_fake.h
@@ -0,0 +1,223 @@
+// 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_HTTP_TRANSPORT_FAKE_H_
+#define BUFFET_HTTP_TRANSPORT_FAKE_H_
+
+#include <map>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/values.h>
+
+#include "buffet/http_transport.h"
+#include "buffet/http_utils.h"
+
+namespace buffet {
+namespace http {
+namespace fake {
+
+class ServerRequest;
+class ServerResponse;
+class Connection;
+
+///////////////////////////////////////////////////////////////////////////////
+// A fake implementation of http::Transport that simulates HTTP communication
+// with a server.
+///////////////////////////////////////////////////////////////////////////////
+class Transport : public http::Transport {
+ public:
+  Transport();
+  virtual ~Transport();
+
+  // Server handler callback signature.
+  typedef base::Callback<void(const ServerRequest&, ServerResponse*)>
+      HandlerCallback;
+
+  // This method allows the test code to provide a callback to handle requests
+  // for specific URL/HTTP-verb combination. When a specific |method| request
+  // is made on the given |url|, the |handler| will be invoked and all the
+  // request data will be filled in the |ServerRequest| parameter. Any server
+  // response should be returned through the |ServerResponse| parameter.
+  // Either |method| or |url| (or both) can be specified as "*" to handle
+  // any requests. So, ("http://localhost","*") will handle any request type
+  // on that URL and ("*","GET") will handle any GET requests.
+  // The lookup starts with the most specific data pair to the catch-all (*,*).
+  void AddHandler(const std::string& url, const std::string& method,
+                  const HandlerCallback& handler);
+  // Simple version of AddHandler. AddSimpleReplyHandler just returns the
+  // specified text response of given MIME type.
+  void AddSimpleReplyHandler(const std::string& url,
+                             const std::string& method,
+                             int status_code,
+                             const std::string& reply_text,
+                             const std::string& mime_type);
+  // Retrieve a handler for specific |url| and request |method|.
+  HandlerCallback GetHandler(const std::string& url,
+                             const std::string& method) const;
+
+  // For tests that want to assert on the number of HTTP requests sent,
+  // these methods can be used to do just that.
+  int GetRequestCount() const { return request_count_; }
+  void ResetRequestCount() { request_count_ = 0; }
+
+  // Overload from http::Transport
+  virtual std::unique_ptr<http::Connection> CreateConnection(
+      std::shared_ptr<http::Transport> transport,
+      const std::string& url,
+      const std::string& method,
+      const HeaderList& headers,
+      const std::string& user_agent,
+      const std::string& referer,
+      ErrorPtr* error) override;
+
+ private:
+  // A list of user-supplied request handlers.
+  std::map<std::string, HandlerCallback> handlers_;
+  // Counter incremented each time a request is made.
+  int request_count_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(Transport);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// A base class for ServerRequest and ServerResponse. It provides common
+// functionality to work with request/response HTTP headers and data.
+///////////////////////////////////////////////////////////////////////////////
+class ServerRequestResponseBase {
+ public:
+  ServerRequestResponseBase() = default;
+
+  // Add/retrieve request/response body data.
+  void AddData(const void* data, size_t data_size);
+  const std::vector<unsigned char>& GetData() const { return data_; }
+  std::string GetDataAsString() const;
+  std::unique_ptr<base::DictionaryValue> GetDataAsJson() const;
+
+  // Add/retrieve request/response HTTP headers.
+  void AddHeaders(const HeaderList& headers);
+  std::string GetHeader(const std::string& header_name) const;
+  const std::map<std::string, std::string>& GetHeaders() const {
+    return headers_;
+  }
+
+ protected:
+  // Data buffer.
+  std::vector<unsigned char> data_;
+  // Header map.
+  std::map<std::string, std::string> headers_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ServerRequestResponseBase);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// A container class that encapsulates all the HTTP server request information.
+///////////////////////////////////////////////////////////////////////////////
+class ServerRequest : public ServerRequestResponseBase {
+ public:
+  ServerRequest(const std::string& url, const std::string& method);
+
+  // Get the actual request URL. Does not include the query string or fragment.
+  const std::string& GetURL() const { return url_; }
+  // Get the request method.
+  const std::string& GetMethod() const { return method_; }
+  // Get the POST/GET request parameters. These are parsed query string
+  // parameters from the URL. In addition, for POST requests with
+  // application/x-www-form-urlencoded content type, the request body is also
+  // parsed and individual fields can be accessed through this method.
+  std::string GetFormField(const std::string& field_name) const;
+
+ private:
+  // Request URL (without query string or URL fragment).
+  std::string url_;
+  // Request method
+  std::string method_;
+  // List of available request data form fields.
+  mutable std::map<std::string, std::string> form_fields_;
+  // Flag used on first request to GetFormField to parse the body of HTTP POST
+  // request with application/x-www-form-urlencoded content.
+  mutable bool form_fields_parsed_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(ServerRequest);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// A container class that encapsulates all the HTTP server response information.
+// The request handler will use this class to provide a response to the caller.
+// Call the Reply() or the appropriate ReplyNNN() specialization to provide
+// the response data. Additional calls to AddHeaders() can be made to provide
+// custom response headers. The Reply-methods will already provide the
+// following response headers:
+//    Content-Length
+//    Content-Type
+///////////////////////////////////////////////////////////////////////////////
+class ServerResponse : public ServerRequestResponseBase {
+ public:
+  ServerResponse() = default;
+
+  // Generic reply method.
+  void Reply(int status_code, const void* data, size_t data_size,
+             const char* mime_type);
+  // Reply with text body.
+  void ReplyText(int status_code, const std::string& text,
+                 const char* mime_type);
+  // Reply with JSON object. The content type will be "application/json".
+  void ReplyJson(int status_code, const base::Value* json);
+  // Special form for JSON response for simple objects that have a flat
+  // list of key-value pairs of string type.
+  void ReplyJson(int status_code, const FormFieldList& fields);
+
+  // Specialized overload to send the binary data as an array of simple
+  // data elements. Only trivial data types (scalars, POD structures, etc)
+  // can be used.
+  template<typename T>
+  void Reply(int status_code, const std::vector<T>& data,
+             const char* mime_type) {
+    // Make sure T doesn't have virtual functions, custom constructors, etc.
+    static_assert(std::is_trivial<T>::value, "Only simple data is supported");
+    Reply(status_code, data.data(), data.size() * sizeof(T), mime_type);
+  }
+
+  // Specialized overload to send the binary data.
+  // Only trivial data types (scalars, POD structures, etc) can be used.
+  template<typename T>
+  void Reply(int status_code, const T& data, const char* mime_type) {
+    // Make sure T doesn't have virtual functions, custom constructors, etc.
+    static_assert(std::is_trivial<T>::value, "Only simple data is supported");
+    Reply(status_code, &data, sizeof(T), mime_type);
+  }
+
+  // For handlers that want to simulate versions of HTTP protocol other
+  // than HTTP/1.1, call this method with the custom version string,
+  // for example "HTTP/1.0".
+  void SetProtocolVersion(const std::string& protocol_version) {
+    protocol_version_ = protocol_version;
+  }
+
+ protected:
+  // These methods are helpers to implement corresponding functionality
+  // of fake::Connection.
+  friend class Connection;
+  // Helper for fake::Connection::GetResponseStatusCode().
+  int GetStatusCode() const { return status_code_; }
+  // Helper for fake::Connection::GetResponseStatusText().
+  std::string GetStatusText() const;
+  // Helper for fake::Connection::GetProtocolVersion().
+  std::string GetProtocolVersion() const { return protocol_version_; }
+
+ private:
+  int status_code_ = 0;
+  std::string protocol_version_ = "HTTP/1.1";
+
+  DISALLOW_COPY_AND_ASSIGN(ServerResponse);
+};
+
+}  // namespace fake
+}  // namespace http
+}  // namespace buffet
+
+#endif  // BUFFET_HTTP_TRANSPORT_FAKE_H_
diff --git a/buffet/http_utils.cc b/buffet/http_utils.cc
new file mode 100644
index 0000000..f7f1190
--- /dev/null
+++ b/buffet/http_utils.cc
@@ -0,0 +1,167 @@
+// 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/http_utils.h"
+
+#include <algorithm>
+
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/values.h>
+
+#include "buffet/mime_utils.h"
+#include "buffet/data_encoding.h"
+
+namespace buffet {
+namespace http {
+
+const char kErrorDomainJSON[] = "json_parser";
+
+std::unique_ptr<Response> Get(const std::string& url,
+                              const HeaderList& headers,
+                              std::shared_ptr<Transport> transport,
+                              ErrorPtr* error) {
+  return SendRequest(request_type::kGet, url, nullptr, 0, nullptr,
+                     headers, transport, error);
+}
+
+std::string GetAsString(const std::string& url,
+                        const HeaderList& headers,
+                        std::shared_ptr<Transport> transport,
+                        ErrorPtr* error) {
+  auto resp = Get(url, headers, transport, error);
+  return resp ? resp->GetDataAsString() : std::string();
+}
+
+std::unique_ptr<Response> Head(const std::string& url,
+                               std::shared_ptr<Transport> transport,
+                               ErrorPtr* error) {
+  Request request(url, request_type::kHead, transport);
+  return request.GetResponse(error);
+}
+
+std::unique_ptr<Response> PostText(const std::string& url,
+                                   const char* data,
+                                   const char* mime_type,
+                                   const HeaderList& headers,
+                                   std::shared_ptr<Transport> transport,
+                                   ErrorPtr* error) {
+  if (mime_type == nullptr) {
+    mime_type = mime::application::kWwwFormUrlEncoded;
+  }
+
+  return PostBinary(url, data, strlen(data), mime_type, headers, transport,
+                    error);
+}
+
+std::unique_ptr<Response> SendRequest(const char * method,
+                                      const std::string& url,
+                                      const void* data,
+                                      size_t data_size,
+                                      const char* mime_type,
+                                      const HeaderList& headers,
+                                      std::shared_ptr<Transport> transport,
+                                      ErrorPtr* error) {
+  Request request(url, method, transport);
+  request.AddHeaders(headers);
+  if (data_size > 0) {
+    if (mime_type == nullptr) {
+      mime_type = mime::application::kOctet_stream;
+    }
+    request.SetContentType(mime_type);
+    if (!request.AddRequestBody(data, data_size, error))
+      return std::unique_ptr<Response>();
+  }
+  return request.GetResponse(error);
+}
+
+std::unique_ptr<Response> PostBinary(const std::string & url, const void* data,
+                                     size_t data_size, const char* mime_type,
+                                     const HeaderList& headers,
+                                     std::shared_ptr<Transport> transport,
+                                     ErrorPtr* error) {
+  return SendRequest(request_type::kPost, url,
+                     data, data_size, mime_type, headers, transport, error);
+}
+
+std::unique_ptr<Response> PostFormData(const std::string& url,
+                                       const FormFieldList& data,
+                                       const HeaderList& headers,
+                                       std::shared_ptr<Transport> transport,
+                                       ErrorPtr* error) {
+  std::string encoded_data = data_encoding::WebParamsEncode(data);
+  return PostBinary(url, encoded_data.c_str(), encoded_data.size(),
+                    mime::application::kWwwFormUrlEncoded,
+                    headers, transport, error);
+}
+
+
+std::unique_ptr<Response> PostJson(const std::string& url,
+                                   const base::Value* json,
+                                   const HeaderList& headers,
+                                   std::shared_ptr<Transport> transport,
+                                   ErrorPtr* error) {
+  std::string data;
+  if (json)
+    base::JSONWriter::Write(json, &data);
+  std::string mime_type = mime::AppendParameter(mime::application::kJson,
+                                                mime::parameters::kCharset,
+                                                "utf-8");
+  return PostBinary(url, data.c_str(), data.size(),
+                    mime_type.c_str(), headers, transport, error);
+}
+
+std::unique_ptr<Response> PatchJson(const std::string& url,
+                                    const base::Value* json,
+                                    const HeaderList& headers,
+                                    std::shared_ptr<Transport> transport,
+                                    ErrorPtr* error) {
+  std::string data;
+  if (json)
+    base::JSONWriter::Write(json, &data);
+  std::string mime_type = mime::AppendParameter(mime::application::kJson,
+                                                mime::parameters::kCharset,
+                                                "utf-8");
+  return SendRequest(request_type::kPatch, url, data.c_str(), data.size(),
+                     mime_type.c_str(), headers, transport, error);
+}
+
+std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
+    const Response* response, int* status_code, ErrorPtr* error) {
+  if (!response)
+    return std::unique_ptr<base::DictionaryValue>();
+
+  if (status_code)
+    *status_code = response->GetStatusCode();
+
+  // Make sure we have a correct content type. Do not try to parse
+  // binary files, or HTML output. Limit to application/json and text/plain.
+  auto content_type = mime::RemoveParameters(response->GetContentType());
+  if (content_type != mime::application::kJson &&
+      content_type != mime::text::kPlain) {
+    Error::AddTo(error, kErrorDomainJSON, "non_json_content_type",
+                 "Unexpected response content type: " + content_type);
+    return std::unique_ptr<base::DictionaryValue>();
+  }
+
+  std::string json = response->GetDataAsString();
+  std::string error_message;
+  base::Value* value = base::JSONReader::ReadAndReturnError(
+      json, base::JSON_PARSE_RFC, nullptr, &error_message);
+  if (!value) {
+    Error::AddTo(error, kErrorDomainJSON, "json_parse_error", error_message);
+    return std::unique_ptr<base::DictionaryValue>();
+  }
+  base::DictionaryValue* dict_value = nullptr;
+  if (!value->GetAsDictionary(&dict_value)) {
+    delete value;
+    Error::AddTo(error, kErrorDomainJSON, "json_object_error",
+                 "Response is not a valid JSON object");
+    return std::unique_ptr<base::DictionaryValue>();
+  }
+  return std::unique_ptr<base::DictionaryValue>(dict_value);
+}
+
+}  // namespace http
+}  // namespace buffet
diff --git a/buffet/http_utils.h b/buffet/http_utils.h
new file mode 100644
index 0000000..20e255b
--- /dev/null
+++ b/buffet/http_utils.h
@@ -0,0 +1,181 @@
+// 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_HTTP_UTILS_H_
+#define BUFFET_HTTP_UTILS_H_
+
+#include "buffet/error.h"
+#include "buffet/http_request.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace base {
+  class Value;
+  class DictionaryValue;
+}  // namespace base
+
+namespace buffet {
+namespace http {
+
+extern const char kErrorDomainJSON[];
+
+typedef std::vector<std::pair<std::string, std::string>> FormFieldList;
+
+////////////////////////////////////////////////////////////////////////////////
+// The following are simple utility helper functions for common HTTP operations
+// that use http::Request object behind the scenes and set it up accordingly.
+//
+// For more advanced functionality you need to use Request/Response objects
+// directly.
+////////////////////////////////////////////////////////////////////////////////
+
+// Performs a generic HTTP request with binary data. Success status,
+// returned data and additional information (such as returned HTTP headers)
+// can be obtained from the returned Response object.
+// If data MIME type is not specified, "application/octet-stream" is assumed.
+std::unique_ptr<Response> SendRequest(
+    const char* method, const std::string& url,
+    const void* data, size_t data_size, const char* mime_type,
+    const HeaderList& headers, std::shared_ptr<Transport> transport,
+    ErrorPtr* error);
+
+// Performs a simple GET request and returns the data as a string.
+std::string GetAsString(const std::string& url, const HeaderList& headers,
+                        std::shared_ptr<Transport> transport,
+                        ErrorPtr* error);
+inline std::string GetAsString(const std::string& url,
+                               std::shared_ptr<Transport> transport,
+                               ErrorPtr* error) {
+  return GetAsString(url, HeaderList(), transport, error);
+}
+
+// Performs a GET request. Success status, returned data and additional
+// information (such as returned HTTP headers) can be obtained from
+// the returned Response object.
+std::unique_ptr<Response> Get(const std::string& url,
+                              const HeaderList& headers,
+                              std::shared_ptr<Transport> transport,
+                              ErrorPtr* error);
+inline std::unique_ptr<Response> Get(
+    const std::string& url, std::shared_ptr<Transport> transport,
+    ErrorPtr* error) {
+  return Get(url, HeaderList(), transport, error);
+}
+
+// Performs a HEAD request. Success status and additional
+// information (such as returned HTTP headers) can be obtained from
+// the returned Response object.
+std::unique_ptr<Response> Head(const std::string& url,
+                               std::shared_ptr<Transport> transport,
+                               ErrorPtr* error);
+
+// Performs a POST request with binary data. Success status, returned data
+// and additional information (such as returned HTTP headers) can be obtained
+// from the returned Response object.
+// If data MIME type is not specified, "application/octet-stream" is assumed
+std::unique_ptr<Response> PostBinary(const std::string& url,
+                                     const void* data,
+                                     size_t data_size,
+                                     const char* mime_type,
+                                     const HeaderList& headers,
+                                     std::shared_ptr<Transport> transport,
+                                     ErrorPtr* error);
+
+inline std::unique_ptr<Response> PostBinary(
+    const std::string& url, const void* data, size_t data_size,
+    const char* mime_type, std::shared_ptr<Transport> transport,
+    ErrorPtr* error) {
+  return PostBinary(url, data, data_size, mime_type, HeaderList(), transport,
+                    error);
+}
+
+inline std::unique_ptr<Response> PostBinary(
+    const std::string& url, const void* data, size_t data_size,
+    std::shared_ptr<Transport> transport, ErrorPtr* error) {
+  return PostBinary(url, data, data_size, nullptr, transport, error);
+}
+
+// Performs a POST request with text data. Success status, returned data
+// and additional information (such as returned HTTP headers) can be obtained
+// from the returned Response object.
+// If data MIME type is not specified, "application/x-www-form-urlencoded"
+// is assumed.
+std::unique_ptr<Response> PostText(const std::string& url,
+                                   const char* data,
+                                   const char* mime_type,
+                                   const HeaderList& headers,
+                                   std::shared_ptr<Transport> transport,
+                                   ErrorPtr* error);
+
+inline std::unique_ptr<Response> PostText(
+    const std::string& url, const char* data, const char* mime_type,
+    std::shared_ptr<Transport> transport, ErrorPtr* error) {
+  return PostText(url, data, mime_type, HeaderList(), transport, error);
+}
+
+inline std::unique_ptr<Response> PostText(
+    const std::string& url, const char* data,
+    std::shared_ptr<Transport> transport, ErrorPtr* error) {
+  return PostText(url, data, nullptr, transport, error);
+}
+
+// Performs a POST request with form data. Success status, returned data
+// and additional information (such as returned HTTP headers) can be obtained
+// from the returned Response object. The form data is a list of key/value
+// pairs. The data is posed as "application/x-www-form-urlencoded".
+std::unique_ptr<Response> PostFormData(
+    const std::string& url, const FormFieldList& data,
+    const HeaderList& headers, std::shared_ptr<Transport> transport,
+    ErrorPtr* error);
+
+inline std::unique_ptr<Response> PostFormData(
+    const std::string& url, const FormFieldList& data,
+    std::shared_ptr<Transport> transport, ErrorPtr* error) {
+  return PostFormData(url, data, HeaderList(), transport, error);
+}
+
+// Performs a POST request with JSON data. Success status, returned data
+// and additional information (such as returned HTTP headers) can be obtained
+// from the returned Response object. If a JSON response is expected,
+// use ParseJsonResponse() method on the returned Response object.
+std::unique_ptr<Response> PostJson(const std::string& url,
+                                   const base::Value* json,
+                                   const HeaderList& headers,
+                                   std::shared_ptr<Transport> transport,
+                                   ErrorPtr* error);
+
+inline std::unique_ptr<Response> PostJson(
+    const std::string& url, const base::Value* json,
+    std::shared_ptr<Transport> transport, ErrorPtr* error) {
+  return PostJson(url, json, HeaderList(), transport, error);
+}
+
+// Performs a PATCH request with JSON data. Success status, returned data
+// and additional information (such as returned HTTP headers) can be obtained
+// from the returned Response object. If a JSON response is expected,
+// use ParseJsonResponse() method on the returned Response object.
+std::unique_ptr<Response> PatchJson(const std::string& url,
+                                    const base::Value* json,
+                                    const HeaderList& headers,
+                                    std::shared_ptr<Transport> transport,
+                                    ErrorPtr* error);
+
+inline std::unique_ptr<Response> PatchJson(
+    const std::string& url, const base::Value* json,
+    std::shared_ptr<Transport> transport, ErrorPtr* error) {
+  return PatchJson(url, json, HeaderList(), transport, error);
+}
+
+// Given an http::Response object, parse the body data into Json object.
+// Returns null if failed. Optional |error| can be passed in to
+// get the extended error information as to why the parse failed.
+std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
+    const Response* response, int* status_code, ErrorPtr* error);
+
+}  // namespace http
+}  // namespace buffet
+
+#endif  // BUFFET_HTTP_UTILS_H_
diff --git a/buffet/http_utils_unittest.cc b/buffet/http_utils_unittest.cc
new file mode 100644
index 0000000..882752b
--- /dev/null
+++ b/buffet/http_utils_unittest.cc
@@ -0,0 +1,337 @@
+// 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 <string>
+#include <vector>
+
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "buffet/bind_lambda.h"
+#include "buffet/http_utils.h"
+#include "buffet/http_transport_fake.h"
+#include "buffet/mime_utils.h"
+#include "buffet/string_utils.h"
+#include "buffet/url_utils.h"
+
+using namespace buffet;        // NOLINT(build/namespaces)
+using namespace buffet::http;  // NOLINT(build/namespaces)
+
+static const char kFakeUrl[] = "http://localhost";
+static const char kEchoUrl[] = "http://localhost/echo";
+static const char kMethodEchoUrl[] = "http://localhost/echo/method";
+
+///////////////////// Generic helper request handlers /////////////////////////
+// Returns the request data back with the same content type.
+static void EchoDataHandler(const fake::ServerRequest& request,
+                            fake::ServerResponse* response) {
+  response->Reply(status_code::Ok, request.GetData(),
+                  request.GetHeader(request_header::kContentType).c_str());
+}
+
+// Returns the request method as a plain text response.
+static void EchoMethodHandler(const fake::ServerRequest& request,
+                              fake::ServerResponse* response) {
+  response->ReplyText(status_code::Ok, request.GetMethod(), mime::text::kPlain);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+TEST(HttpUtils, SendRequest_BinaryData) {
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kEchoUrl, request_type::kPost,
+                        base::Bind(EchoDataHandler));
+
+  // Test binary data round-tripping.
+  std::vector<unsigned char> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
+  auto response = http::SendRequest(request_type::kPost, kEchoUrl,
+                                    custom_data.data(), custom_data.size(),
+                                    mime::application::kOctet_stream,
+                                    HeaderList(), transport, nullptr);
+  EXPECT_TRUE(response->IsSuccessful());
+  EXPECT_EQ(mime::application::kOctet_stream, response->GetContentType());
+  EXPECT_EQ(custom_data.size(), response->GetData().size());
+  EXPECT_EQ(custom_data, response->GetData());
+}
+
+TEST(HttpUtils, SendRequest_Post) {
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+
+  // Test binary data round-tripping.
+  std::vector<unsigned char> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
+
+  // Check the correct HTTP method used.
+  auto response = http::SendRequest(request_type::kPost, kMethodEchoUrl,
+                                    custom_data.data(), custom_data.size(),
+                                    mime::application::kOctet_stream,
+                                    HeaderList(), transport, nullptr);
+  EXPECT_TRUE(response->IsSuccessful());
+  EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+  EXPECT_EQ(request_type::kPost, response->GetDataAsString());
+}
+
+TEST(HttpUtils, SendRequest_Get) {
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+
+  auto response = http::SendRequest(request_type::kGet, kMethodEchoUrl,
+                                    nullptr, 0, nullptr,
+                                    HeaderList(), transport, nullptr);
+  EXPECT_TRUE(response->IsSuccessful());
+  EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+  EXPECT_EQ(request_type::kGet, response->GetDataAsString());
+}
+
+TEST(HttpUtils, SendRequest_Put) {
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+
+  auto response = http::SendRequest(request_type::kPut, kMethodEchoUrl,
+                                    nullptr, 0, nullptr,
+                                    HeaderList(), transport, nullptr);
+  EXPECT_TRUE(response->IsSuccessful());
+  EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+  EXPECT_EQ(request_type::kPut, response->GetDataAsString());
+}
+
+TEST(HttpUtils, SendRequest_NotFound) {
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  // Test failed response (URL not found).
+  auto response = http::SendRequest(request_type::kGet, "http://blah.com",
+                                    nullptr, 0, nullptr,
+                                    HeaderList(), transport, nullptr);
+  EXPECT_FALSE(response->IsSuccessful());
+  EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
+}
+
+TEST(HttpUtils, SendRequest_Headers) {
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+
+  static const char json_echo_url[] = "http://localhost/echo/json";
+  auto JsonEchoHandler = [](const fake::ServerRequest& request,
+                            fake::ServerResponse* response) {
+    base::DictionaryValue json;
+    json.SetString("method", request.GetMethod());
+    json.SetString("data", request.GetDataAsString());
+    for (const auto& pair : request.GetHeaders()) {
+      json.SetString("header." + pair.first, pair.second);
+    }
+    response->ReplyJson(status_code::Ok, &json);
+  };
+  transport->AddHandler(json_echo_url, "*",
+                        base::Bind(JsonEchoHandler));
+  auto response = http::SendRequest(
+      request_type::kPost, json_echo_url, "abcd", 4,
+      mime::application::kOctet_stream, {
+        {request_header::kCookie, "flavor=vanilla"},
+        {request_header::kIfMatch, "*"},
+      }, transport, nullptr);
+  EXPECT_TRUE(response->IsSuccessful());
+  EXPECT_EQ(mime::application::kJson,
+            mime::RemoveParameters(response->GetContentType()));
+  auto json = ParseJsonResponse(response.get(), nullptr, nullptr);
+  std::string value;
+  EXPECT_TRUE(json->GetString("method", &value));
+  EXPECT_EQ(request_type::kPost, value);
+  EXPECT_TRUE(json->GetString("data", &value));
+  EXPECT_EQ("abcd", value);
+  EXPECT_TRUE(json->GetString("header.Cookie", &value));
+  EXPECT_EQ("flavor=vanilla", value);
+  EXPECT_TRUE(json->GetString("header.Content-Type", &value));
+  EXPECT_EQ(mime::application::kOctet_stream, value);
+  EXPECT_TRUE(json->GetString("header.Content-Length", &value));
+  EXPECT_EQ("4", value);
+  EXPECT_TRUE(json->GetString("header.If-Match", &value));
+  EXPECT_EQ("*", value);
+}
+
+TEST(HttpUtils, Get) {
+  // Sends back the "?test=..." portion of URL.
+  // So if we do GET "http://localhost?test=blah", this handler responds
+  // with "blah" as text/plain.
+  auto GetHandler = [](const fake::ServerRequest& request,
+                       fake::ServerResponse* response) {
+    EXPECT_EQ(request_type::kGet, request.GetMethod());
+    EXPECT_EQ("0", request.GetHeader(request_header::kContentLength));
+    EXPECT_EQ("", request.GetHeader(request_header::kContentType));
+    response->ReplyText(status_code::Ok, request.GetFormField("test"),
+                        mime::text::kPlain);
+  };
+
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kFakeUrl, request_type::kGet, base::Bind(GetHandler));
+  transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+
+  // Make sure Get/GetAsString actually do the GET request
+  auto response = http::Get(kMethodEchoUrl, transport, nullptr);
+  EXPECT_TRUE(response->IsSuccessful());
+  EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+  EXPECT_EQ(request_type::kGet, response->GetDataAsString());
+  EXPECT_EQ(request_type::kGet,
+            http::GetAsString(kMethodEchoUrl, transport, nullptr));
+
+  for (std::string data : {"blah", "some data", ""}) {
+    std::string url = url::AppendQueryParam(kFakeUrl, "test", data);
+    EXPECT_EQ(data, http::GetAsString(url, transport, nullptr));
+  }
+}
+
+TEST(HttpUtils, Head) {
+  auto HeadHandler = [](const fake::ServerRequest& request,
+                        fake::ServerResponse* response) {
+    EXPECT_EQ(request_type::kHead, request.GetMethod());
+    EXPECT_EQ("0", request.GetHeader(request_header::kContentLength));
+    EXPECT_EQ("", request.GetHeader(request_header::kContentType));
+    response->ReplyText(status_code::Ok, "blah",
+                        mime::text::kPlain);
+  };
+
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kFakeUrl, request_type::kHead, base::Bind(HeadHandler));
+
+  auto response = http::Head(kFakeUrl, transport, nullptr);
+  EXPECT_TRUE(response->IsSuccessful());
+  EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+  EXPECT_EQ("", response->GetDataAsString());  // Must not have actual body.
+  EXPECT_EQ("4", response->GetHeader(request_header::kContentLength));
+}
+
+TEST(HttpUtils, PostBinary) {
+  auto Handler = [](const fake::ServerRequest& request,
+                    fake::ServerResponse* response) {
+    EXPECT_EQ(request_type::kPost, request.GetMethod());
+    EXPECT_EQ("256", request.GetHeader(request_header::kContentLength));
+    EXPECT_EQ(mime::application::kOctet_stream,
+              request.GetHeader(request_header::kContentType));
+    const auto& data = request.GetData();
+    EXPECT_EQ(256, data.size());
+
+    // Sum up all the bytes.
+    int sum = std::accumulate(data.begin(), data.end(), 0);
+    EXPECT_EQ(32640, sum);  // sum(i, i => [0, 255]) = 32640.
+    response->ReplyText(status_code::Ok, "", mime::text::kPlain);
+  };
+
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(Handler));
+
+  /// Fill the data buffer with bytes from 0x00 to 0xFF.
+  std::vector<unsigned char> data(256);
+  std::iota(data.begin(), data.end(), 0);
+
+  auto response = http::PostBinary(kFakeUrl, data.data(), data.size(),
+                                   transport, nullptr);
+  EXPECT_TRUE(response->IsSuccessful());
+}
+
+TEST(HttpUtils, PostText) {
+  std::string fake_data = "Some data";
+  auto PostHandler = [fake_data](const fake::ServerRequest& request,
+                                 fake::ServerResponse* response) {
+    EXPECT_EQ(request_type::kPost, request.GetMethod());
+    EXPECT_EQ(fake_data.size(),
+              std::stoul(request.GetHeader(request_header::kContentLength)));
+    EXPECT_EQ(mime::text::kPlain,
+              request.GetHeader(request_header::kContentType));
+    response->ReplyText(status_code::Ok, request.GetDataAsString(),
+                       mime::text::kPlain);
+  };
+
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(PostHandler));
+
+  auto response = http::PostText(kFakeUrl, fake_data.c_str(),
+                                 mime::text::kPlain, transport, nullptr);
+  EXPECT_TRUE(response->IsSuccessful());
+  EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+  EXPECT_EQ(fake_data, response->GetDataAsString());
+}
+
+TEST(HttpUtils, PostFormData) {
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kFakeUrl, request_type::kPost,
+                        base::Bind(EchoDataHandler));
+
+  auto response = http::PostFormData(kFakeUrl, {
+                      {"key", "value"},
+                      {"field", "field value"},
+                  }, transport, nullptr);
+  EXPECT_TRUE(response->IsSuccessful());
+  EXPECT_EQ(mime::application::kWwwFormUrlEncoded, response->GetContentType());
+  EXPECT_EQ("key=value&field=field+value", response->GetDataAsString());
+}
+
+TEST(HttpUtils, PostPatchJson) {
+  auto JsonHandler = [](const fake::ServerRequest& request,
+                        fake::ServerResponse* response) {
+    auto mime_type = mime::RemoveParameters(
+        request.GetHeader(request_header::kContentType));
+    EXPECT_EQ(mime::application::kJson, mime_type);
+    response->ReplyJson(status_code::Ok, {
+      {"method", request.GetMethod()},
+      {"data", request.GetDataAsString()},
+    });
+  };
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kFakeUrl, "*", base::Bind(JsonHandler));
+
+  base::DictionaryValue json;
+  json.SetString("key1", "val1");
+  json.SetString("key2", "val2");
+  std::string value;
+
+  // Test POST
+  auto response = http::PostJson(kFakeUrl, &json, transport, nullptr);
+  auto resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr);
+  EXPECT_NE(nullptr, resp_json.get());
+  EXPECT_TRUE(resp_json->GetString("method", &value));
+  EXPECT_EQ(request_type::kPost, value);
+  EXPECT_TRUE(resp_json->GetString("data", &value));
+  EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value);
+
+  // Test PATCH
+  response = http::PatchJson(kFakeUrl, &json, transport, nullptr);
+  resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr);
+  EXPECT_NE(nullptr, resp_json.get());
+  EXPECT_TRUE(resp_json->GetString("method", &value));
+  EXPECT_EQ(request_type::kPatch, value);
+  EXPECT_TRUE(resp_json->GetString("data", &value));
+  EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value);
+}
+
+TEST(HttpUtils, ParseJsonResponse) {
+  auto JsonHandler = [](const fake::ServerRequest& request,
+                        fake::ServerResponse* response) {
+    int status_code = std::stoi(request.GetFormField("code"));
+    response->ReplyJson(status_code, {{"data", request.GetFormField("value")}});
+  };
+  std::shared_ptr<fake::Transport> transport(new fake::Transport);
+  transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(JsonHandler));
+
+  // Test valid JSON responses (with success or error codes).
+  for (auto item : {"200;data", "400;wrong", "500;Internal Server error"}) {
+    auto pair = string_utils::SplitAtFirst(item, ';');
+    auto response = http::PostFormData(kFakeUrl, {
+                      {"code", pair.first},
+                      {"value", pair.second},
+                    }, transport, nullptr);
+    int code = 0;
+    auto json = http::ParseJsonResponse(response.get(), &code, nullptr);
+    EXPECT_NE(nullptr, json.get());
+    std::string value;
+    EXPECT_TRUE(json->GetString("data", &value));
+    EXPECT_EQ(pair.first, string_utils::ToString(code));
+    EXPECT_EQ(pair.second, value);
+  }
+
+  // Test invalid (non-JSON) response.
+  auto response = http::Get("http://bad.url", transport, nullptr);
+  EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
+  EXPECT_EQ(mime::text::kHtml, response->GetContentType());
+  int code = 0;
+  auto json = http::ParseJsonResponse(response.get(), &code, nullptr);
+  EXPECT_EQ(nullptr, json.get());
+  EXPECT_EQ(status_code::NotFound, code);
+}
+
diff --git a/buffet/main.cc b/buffet/main.cc
new file mode 100644
index 0000000..43ba4ab
--- /dev/null
+++ b/buffet/main.cc
@@ -0,0 +1,141 @@
+// 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 <string>
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/file_util.h>
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <dbus/bus.h>
+#include <sysexits.h>
+
+#include "buffet/async_event_sequencer.h"
+#include "buffet/dbus_constants.h"
+#include "buffet/exported_object_manager.h"
+#include "buffet/manager.h"
+
+using buffet::dbus_utils::AsyncEventSequencer;
+using buffet::dbus_utils::ExportedObjectManager;
+
+namespace {
+
+static const char kLogRoot[] = "logroot";
+static const char kHelp[] = "help";
+static const char kDefaultLogRoot[] = "/var/log";
+
+// The help message shown if help flag is passed to the program.
+static const char kHelpMessage[] = "\n"
+    "Available Switches: \n"
+    "  --logroot=/path/to/logroot\n"
+    "    Specifies parent directory to put buffet logs in.\n";
+
+// Returns |utime| as a string
+std::string GetTimeAsString(time_t utime) {
+  struct tm tm;
+  CHECK_EQ(localtime_r(&utime, &tm), &tm);
+  char str[16];
+  CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15UL);
+  return std::string(str);
+}
+
+// Sets up a symlink to point to log file.
+void SetupLogSymlink(const std::string& symlink_path,
+                     const std::string& log_path) {
+  base::DeleteFile(base::FilePath(symlink_path), true);
+  if (symlink(log_path.c_str(), symlink_path.c_str()) == -1) {
+    LOG(ERROR) << "Unable to create symlink " << symlink_path
+               << " pointing at " << log_path;
+  }
+}
+
+// Creates new log file based on timestamp in |log_root|/buffet.
+std::string SetupLogFile(const std::string& log_root) {
+  const auto log_symlink = log_root + "/buffet.log";
+  const auto logs_dir = log_root + "/buffet";
+  const auto log_path =
+      base::StringPrintf("%s/buffet.%s",
+                         logs_dir.c_str(),
+                         GetTimeAsString(::time(NULL)).c_str());
+  mkdir(logs_dir.c_str(), 0755);
+  SetupLogSymlink(log_symlink, log_path);
+  return log_symlink;
+}
+
+// Sets up logging for buffet.
+void SetupLogging(const std::string& log_root) {
+  const auto log_file = SetupLogFile(log_root);
+  logging::LoggingSettings settings;
+  settings.logging_dest = logging::LOG_TO_ALL;
+  settings.log_file = log_file.c_str();
+  settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+  settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+  logging::InitLogging(settings);
+}
+
+void TakeServiceOwnership(scoped_refptr<dbus::Bus> bus, bool success) {
+  // Success should always be true since we've said that failures are
+  // fatal.
+  CHECK(success) << "Init of one or more objects has failed.";
+  CHECK(bus->RequestOwnershipAndBlock(buffet::dbus_constants::kServiceName,
+                                      dbus::Bus::REQUIRE_PRIMARY))
+      << "Unable to take ownership of " << buffet::dbus_constants::kServiceName;
+}
+
+void EnterMainLoop(base::MessageLoopForIO* message_loop,
+                   scoped_refptr<dbus::Bus> bus) {
+  scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+  ExportedObjectManager object_manager(
+      bus, dbus::ObjectPath(buffet::dbus_constants::kRootServicePath));
+  buffet::Manager manager(bus, object_manager.AsWeakPtr());
+  object_manager.Init(
+      sequencer->GetHandler("ObjectManager.Init() failed.", true));
+  manager.Init(sequencer->GetHandler("Manager.Init() failed.", true));
+  sequencer->OnAllTasksCompletedCall(
+      {base::Bind(&TakeServiceOwnership, bus)});
+  // Release our handle on the sequencer so that it gets deleted after
+  // both callbacks return.
+  sequencer = nullptr;
+  LOG(INFO) << "Entering mainloop.";
+  message_loop->Run();
+}
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+  // Parse the args and check for extra args.
+  CommandLine::Init(argc, argv);
+  CommandLine* cl = CommandLine::ForCurrentProcess();
+
+  if (cl->HasSwitch(kHelp)) {
+    LOG(INFO) << kHelpMessage;
+    return EX_USAGE;
+  }
+
+  std::string log_root = std::string(kDefaultLogRoot);
+  if (cl->HasSwitch(kLogRoot)) {
+    log_root = cl->GetSwitchValueASCII(kLogRoot);
+  }
+
+  SetupLogging(log_root);
+
+  base::AtExitManager at_exit_manager;
+  base::MessageLoopForIO message_loop;
+
+  dbus::Bus::Options options;
+  // TODO(sosa): Should this be on the system bus?
+  options.bus_type = dbus::Bus::SYSTEM;
+  scoped_refptr<dbus::Bus> bus(new dbus::Bus(options));
+  CHECK(bus->Connect());
+  // Our top level objects expect the bus to exist in a connected state for
+  // the duration of their lifetimes.
+  EnterMainLoop(&message_loop, bus);
+  bus->ShutdownAndBlock();
+
+  return EX_OK;
+}
diff --git a/buffet/manager.cc b/buffet/manager.cc
new file mode 100644
index 0000000..3639803
--- /dev/null
+++ b/buffet/manager.cc
@@ -0,0 +1,301 @@
+// 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/manager.h"
+
+#include <map>
+#include <string>
+
+#include <base/bind.h>
+#include <base/bind_helpers.h>
+#include <base/json/json_writer.h>
+#include <dbus/bus.h>
+#include <dbus/object_path.h>
+#include <dbus/values_util.h>
+
+#include "buffet/async_event_sequencer.h"
+#include "buffet/dbus_constants.h"
+#include "buffet/dbus_utils.h"
+#include "buffet/error.h"
+#include "buffet/exported_object_manager.h"
+
+using buffet::dbus_utils::AsyncEventSequencer;
+using buffet::dbus_utils::GetBadArgsError;
+using buffet::dbus_utils::GetDBusError;
+
+namespace buffet {
+
+Manager::Manager(
+    scoped_refptr<dbus::Bus> bus,
+    base::WeakPtr<dbus_utils::ExportedObjectManager> object_manager)
+    : bus_(bus),
+      exported_object_(bus->GetExportedObject(
+          dbus::ObjectPath(dbus_constants::kManagerServicePath))),
+      object_manager_(object_manager) { }
+
+Manager::~Manager() {
+  object_manager_->ReleaseInterface(
+      dbus::ObjectPath(dbus_constants::kManagerServicePath),
+      dbus_constants::kManagerInterface);
+  // Prevent the properties object from making calls to the exported object.
+  properties_.reset(nullptr);
+  // Unregister ourselves from the Bus.  This prevents the bus from calling
+  // our callbacks in between the Manager's death and the bus unregistering
+  // our exported object on shutdown.  Unretained makes no promises of memory
+  // management.
+  exported_object_->Unregister();
+  exported_object_ = nullptr;
+}
+
+void Manager::Init(const OnInitFinish& cb) {
+  scoped_refptr<AsyncEventSequencer> sequencer(
+      new AsyncEventSequencer());
+  exported_object_->ExportMethod(
+      dbus_constants::kManagerInterface,
+      dbus_constants::kManagerCheckDeviceRegistered,
+      dbus_utils::GetExportableDBusMethod(
+          base::Bind(&Manager::HandleCheckDeviceRegistered,
+          base::Unretained(this))),
+      sequencer->GetExportHandler(
+          dbus_constants::kManagerInterface,
+          dbus_constants::kManagerCheckDeviceRegistered,
+          "Failed exporting CheckDeviceRegistered method",
+          true));
+  exported_object_->ExportMethod(
+      dbus_constants::kManagerInterface,
+      dbus_constants::kManagerGetDeviceInfo,
+      dbus_utils::GetExportableDBusMethod(
+          base::Bind(&Manager::HandleGetDeviceInfo,
+          base::Unretained(this))),
+      sequencer->GetExportHandler(
+          dbus_constants::kManagerInterface,
+          dbus_constants::kManagerGetDeviceInfo,
+          "Failed exporting GetDeviceInfo method",
+          true));
+  exported_object_->ExportMethod(
+      dbus_constants::kManagerInterface,
+      dbus_constants::kManagerStartRegisterDevice,
+      dbus_utils::GetExportableDBusMethod(
+          base::Bind(&Manager::HandleStartRegisterDevice,
+          base::Unretained(this))),
+      sequencer->GetExportHandler(
+          dbus_constants::kManagerInterface,
+          dbus_constants::kManagerStartRegisterDevice,
+          "Failed exporting StartRegisterDevice method",
+          true));
+  exported_object_->ExportMethod(
+      dbus_constants::kManagerInterface,
+      dbus_constants::kManagerFinishRegisterDevice,
+      dbus_utils::GetExportableDBusMethod(
+          base::Bind(&Manager::HandleFinishRegisterDevice,
+          base::Unretained(this))),
+      sequencer->GetExportHandler(
+          dbus_constants::kManagerInterface,
+          dbus_constants::kManagerFinishRegisterDevice,
+          "Failed exporting FinishRegisterDevice method",
+          true));
+  exported_object_->ExportMethod(
+      dbus_constants::kManagerInterface,
+      dbus_constants::kManagerUpdateStateMethod,
+      dbus_utils::GetExportableDBusMethod(
+          base::Bind(&Manager::HandleUpdateState,
+          base::Unretained(this))),
+      sequencer->GetExportHandler(
+          dbus_constants::kManagerInterface,
+          dbus_constants::kManagerUpdateStateMethod,
+          "Failed exporting UpdateState method",
+          true));
+  exported_object_->ExportMethod(
+      dbus_constants::kManagerInterface, dbus_constants::kManagerTestMethod,
+      dbus_utils::GetExportableDBusMethod(
+          base::Bind(&Manager::HandleTestMethod, base::Unretained(this))),
+      sequencer->GetExportHandler(
+          dbus_constants::kManagerInterface, dbus_constants::kManagerTestMethod,
+          "Failed exporting TestMethod method",
+          true));
+  properties_.reset(new Properties(bus_));
+  // TODO(wiley): Initialize all properties appropriately before claiming
+  //              the properties interface.
+  properties_->state_.SetValue("{}");
+  properties_->Init(
+      sequencer->GetHandler("Manager properties export failed.", true));
+  auto claim_interface_task = sequencer->WrapCompletionTask(
+      base::Bind(&dbus_utils::ExportedObjectManager::ClaimInterface,
+                 object_manager_->AsWeakPtr(),
+                 dbus::ObjectPath(dbus_constants::kManagerServicePath),
+                 dbus_constants::kManagerInterface,
+                 properties_->GetPropertyWriter(
+                     dbus_constants::kManagerInterface)));
+  sequencer->OnAllTasksCompletedCall({claim_interface_task, cb});
+  device_info_.Load();
+}
+
+scoped_ptr<dbus::Response> Manager::HandleCheckDeviceRegistered(
+    dbus::MethodCall* method_call) {
+  // Read the parameters to the method.
+  dbus::MessageReader reader(method_call);
+  if (reader.HasMoreData()) {
+    return GetBadArgsError(method_call,
+                           "Too many parameters to CheckDeviceRegistered");
+  }
+
+  LOG(INFO) << "Received call to Manager.CheckDeviceRegistered()";
+
+  buffet::ErrorPtr error;
+  bool registered = device_info_.CheckRegistration(&error);
+  // If it fails due to any reason other than 'device not registered',
+  // treat it as a real error and report it to the caller.
+  if (!registered &&
+      !error->HasError(kErrorDomainGCD, "device_not_registered")) {
+    return GetDBusError(method_call, error.get());
+  }
+
+  std::string device_id;
+  if (registered) {
+    device_id = device_info_.GetDeviceId(&error);
+    if (device_id.empty())
+      return GetDBusError(method_call, error.get());
+  }
+  // Send back our response.
+  scoped_ptr<dbus::Response> response(
+      dbus::Response::FromMethodCall(method_call));
+  dbus::MessageWriter writer(response.get());
+  writer.AppendString(device_id);
+  return response.Pass();
+}
+
+scoped_ptr<dbus::Response> Manager::HandleGetDeviceInfo(
+    dbus::MethodCall* method_call) {
+  // Read the parameters to the method.
+  dbus::MessageReader reader(method_call);
+  if (reader.HasMoreData()) {
+    return GetBadArgsError(method_call,
+                           "Too many parameters to GetDeviceInfo");
+  }
+
+  LOG(INFO) << "Received call to Manager.GetDeviceInfo()";
+
+  std::string device_info_str;
+  buffet::ErrorPtr error;
+  auto device_info = device_info_.GetDeviceInfo(&error);
+  if (!device_info)
+    return GetDBusError(method_call, error.get());
+
+  base::JSONWriter::Write(device_info.get(), &device_info_str);
+
+  // Send back our response.
+  scoped_ptr<dbus::Response> response(
+      dbus::Response::FromMethodCall(method_call));
+  dbus::MessageWriter writer(response.get());
+  writer.AppendString(device_info_str);
+  return response.Pass();
+}
+
+scoped_ptr<dbus::Response> Manager::HandleStartRegisterDevice(
+    dbus::MethodCall* method_call) {
+  // Read the parameters to the method.
+  dbus::MessageReader reader(method_call);
+  if (!reader.HasMoreData()) {
+    return GetBadArgsError(method_call, "No parameters to StartRegisterDevice");
+  }
+
+  dbus::MessageReader array_reader(nullptr);
+  if (!reader.PopArray(&array_reader))
+    return GetBadArgsError(method_call, "Failed to read the parameter array");
+  std::map<std::string, std::shared_ptr<base::Value>> params;
+  while (array_reader.HasMoreData()) {
+    dbus::MessageReader dict_entry_reader(nullptr);
+    if (!array_reader.PopDictEntry(&dict_entry_reader))
+      return GetBadArgsError(method_call, "Failed to get a call parameter");
+    std::string key;
+    if (!dict_entry_reader.PopString(&key))
+      return GetBadArgsError(method_call, "Failed to read parameter key");
+    base::Value* value = dbus::PopDataAsValue(&dict_entry_reader);
+    if (!value)
+      return GetBadArgsError(method_call, "Failed to read parameter value");
+    params.insert(std::make_pair(key, std::shared_ptr<base::Value>(value)));
+  }
+  if (reader.HasMoreData())
+    return GetBadArgsError(method_call,
+                           "Too many parameters to StartRegisterDevice");
+
+  LOG(INFO) << "Received call to Manager.StartRegisterDevice()";
+
+  buffet::ErrorPtr error;
+  std::string id = device_info_.StartRegistration(params, &error);
+  if (id.empty())
+    return GetDBusError(method_call, error.get());
+
+  // Send back our response.
+  scoped_ptr<dbus::Response> response(
+      dbus::Response::FromMethodCall(method_call));
+  dbus::MessageWriter writer(response.get());
+  writer.AppendString(id);
+  return response.Pass();
+}
+
+scoped_ptr<dbus::Response> Manager::HandleFinishRegisterDevice(
+  dbus::MethodCall* method_call) {
+  // Read the parameters to the method.
+  dbus::MessageReader reader(method_call);
+  if (!reader.HasMoreData()) {
+    return GetBadArgsError(method_call,
+                           "No parameters to FinishRegisterDevice");
+  }
+  std::string user_auth_code;
+  if (!reader.PopString(&user_auth_code)) {
+    return GetBadArgsError(method_call, "Failed to read UserAuthCode");
+  }
+  if (reader.HasMoreData()) {
+    return GetBadArgsError(method_call,
+                           "Too many parameters to FinishRegisterDevice");
+  }
+
+  LOG(INFO) << "Received call to Manager.FinishRegisterDevice()";
+  buffet::ErrorPtr error;
+  if (!device_info_.FinishRegistration(user_auth_code, &error))
+    return GetDBusError(method_call, error.get());
+
+  std::string device_id = device_info_.GetDeviceId(&error);
+  if (device_id.empty())
+    return GetDBusError(method_call, error.get());
+
+  // Send back our response.
+  scoped_ptr<dbus::Response> response(
+    dbus::Response::FromMethodCall(method_call));
+  dbus::MessageWriter writer(response.get());
+  writer.AppendString(device_id);
+  return response.Pass();
+}
+
+scoped_ptr<dbus::Response> Manager::HandleUpdateState(
+    dbus::MethodCall *method_call) {
+  // Read the parameters to the method.
+  dbus::MessageReader reader(method_call);
+  if (!reader.HasMoreData()) {
+    return GetBadArgsError(method_call, "No parameters to UpdateState");
+  }
+  std::string json_state_fragment;
+  if (!reader.PopString(&json_state_fragment)) {
+    return GetBadArgsError(method_call, "Failed to read json_state_fragment");
+  }
+  if (reader.HasMoreData()) {
+    return GetBadArgsError(method_call, "Too many parameters to UpdateState");
+  }
+
+  LOG(INFO) << "Received call to Manager.UpdateState()";
+  // TODO(wiley): Merge json state blobs intelligently.
+  properties_->state_.SetValue(json_state_fragment);
+
+  // Send back our response.
+  return dbus::Response::FromMethodCall(method_call);
+}
+
+scoped_ptr<dbus::Response> Manager::HandleTestMethod(
+    dbus::MethodCall* method_call) {
+  LOG(INFO) << "Received call to test method.";
+  return scoped_ptr<dbus::Response>();
+}
+
+}  // namespace buffet
diff --git a/buffet/manager.h b/buffet/manager.h
new file mode 100644
index 0000000..4498c50
--- /dev/null
+++ b/buffet/manager.h
@@ -0,0 +1,83 @@
+// 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_MANAGER_H_
+#define BUFFET_MANAGER_H_
+
+#include <memory>
+#include <string>
+
+#include <base/basictypes.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/memory/weak_ptr.h>
+#include <base/values.h>
+#include <dbus/message.h>
+#include <dbus/object_path.h>
+
+#include "buffet/dbus_constants.h"
+#include "buffet/exported_property_set.h"
+#include "buffet/device_registration_info.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+class ExportedObjectManager;
+}  // namespace dbus_utils
+
+// The Manager is responsible for global state of Buffet.  It exposes
+// interfaces which affect the entire device such as device registration and
+// device state.
+class Manager {
+ public:
+  typedef base::Callback<void(bool success)> OnInitFinish;
+
+  Manager(scoped_refptr<dbus::Bus> bus,
+          base::WeakPtr<dbus_utils::ExportedObjectManager> object_manager);
+  ~Manager();
+  void Init(const OnInitFinish& cb);
+
+ private:
+  struct Properties: public dbus_utils::ExportedPropertySet {
+   public:
+    dbus_utils::ExportedProperty<std::string> state_;
+    explicit Properties(dbus::Bus* bus)
+        : dbus_utils::ExportedPropertySet(
+              bus, dbus::ObjectPath(dbus_constants::kManagerServicePath)) {
+      RegisterProperty(dbus_constants::kManagerInterface, "State", &state_);
+    }
+    virtual ~Properties() {}
+  };
+
+  // Handles calls to org.chromium.Buffet.Manager.CheckDeviceRegistered().
+  scoped_ptr<dbus::Response> HandleCheckDeviceRegistered(
+      dbus::MethodCall* method_call);
+  // Handles calls to org.chromium.Buffet.Manager.GetDeviceInfo().
+  scoped_ptr<dbus::Response> HandleGetDeviceInfo(
+      dbus::MethodCall* method_call);
+  // Handles calls to org.chromium.Buffet.Manager.StartRegisterDevice().
+  scoped_ptr<dbus::Response> HandleStartRegisterDevice(
+      dbus::MethodCall* method_call);
+  // Handles calls to org.chromium.Buffet.Manager.FinishRegisterDevice().
+  scoped_ptr<dbus::Response> HandleFinishRegisterDevice(
+      dbus::MethodCall* method_call);
+  // Handles calls to org.chromium.Buffet.Manager.UpdateState().
+  scoped_ptr<dbus::Response> HandleUpdateState(
+      dbus::MethodCall* method_call);
+  // Handles calls to org.chromium.Buffet.Manager.Test()
+  scoped_ptr<::dbus::Response> HandleTestMethod(
+      ::dbus::MethodCall* method_call);
+
+  scoped_refptr<dbus::Bus> bus_;
+  dbus::ExportedObject* exported_object_;  // weak; owned by the Bus object.
+  base::WeakPtr<dbus_utils::ExportedObjectManager> object_manager_;
+  scoped_ptr<Properties> properties_;
+
+  DeviceRegistrationInfo device_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(Manager);
+};
+
+}  // namespace buffet
+
+#endif  // BUFFET_MANAGER_H_
diff --git a/buffet/map_utils.h b/buffet/map_utils.h
new file mode 100644
index 0000000..f5e3f30
--- /dev/null
+++ b/buffet/map_utils.h
@@ -0,0 +1,47 @@
+// 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_MAP_UTILS_H_
+#define BUFFET_MAP_UTILS_H_
+
+#include <map>
+#include <utility>
+#include <vector>
+
+namespace buffet {
+
+// Given an STL map returns a vector containing all keys from the map
+template<typename T>
+std::vector<typename T::key_type> GetMapKeys(const T& map) {
+  std::vector<typename T::key_type> keys;
+  keys.reserve(map.size());
+  for (const auto& pair : map)
+    keys.push_back(pair.first);
+  return keys;
+}
+
+// Given an STL map returns a vector containing all values from the map
+template<typename T>
+std::vector<typename T::mapped_type> GetMapValues(const T& map) {
+  std::vector<typename T::mapped_type> values;
+  values.reserve(map.size());
+  for (const auto& pair : map)
+    values.push_back(pair.second);
+  return values;
+}
+
+// Given an STL map returns a vector of key-value pairs from the map
+template<typename T>
+std::vector<std::pair<typename T::key_type,
+                      typename T::mapped_type>> MapToVector(const T& map) {
+  std::vector<std::pair<typename T::key_type, typename T::mapped_type>> vector;
+  vector.reserve(map.size());
+  for (const auto& pair : map)
+    vector.push_back(pair);
+  return vector;
+}
+
+}  // namespace buffet
+
+#endif  // BUFFET_MAP_UTILS_H_
diff --git a/buffet/mime_utils.cc b/buffet/mime_utils.cc
new file mode 100644
index 0000000..dc57497
--- /dev/null
+++ b/buffet/mime_utils.cc
@@ -0,0 +1,156 @@
+// 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/mime_utils.h"
+
+#include <algorithm>
+#include <base/strings/string_util.h>
+
+#include "buffet/string_utils.h"
+
+namespace buffet {
+
+// ***************************************************************************
+// ******************************* MIME types ********************************
+// ***************************************************************************
+const char mime::types::kApplication[]             = "application";
+const char mime::types::kAudio[]                   = "audio";
+const char mime::types::kImage[]                   = "image";
+const char mime::types::kMessage[]                 = "message";
+const char mime::types::kMultipart[]               = "multipart";
+const char mime::types::kText[]                    = "text";
+const char mime::types::kVideo[]                   = "video";
+
+const char mime::parameters::kCharset[]            = "charset";
+
+const char mime::image::kJpeg[]                    = "image/jpeg";
+const char mime::image::kPng[]                     = "image/png";
+const char mime::image::kBmp[]                     = "image/bmp";
+const char mime::image::kTiff[]                    = "image/tiff";
+const char mime::image::kGif[]                     = "image/gif";
+
+const char mime::text::kPlain[]                    = "text/plain";
+const char mime::text::kHtml[]                     = "text/html";
+const char mime::text::kXml[]                      = "text/xml";
+
+const char mime::application::kOctet_stream[]      = "application/octet-stream";
+const char mime::application::kJson[]              = "application/json";
+const char mime::application::kWwwFormUrlEncoded[] =
+    "application/x-www-form-urlencoded";
+
+// ***************************************************************************
+// **************************** Utility Functions ****************************
+// ***************************************************************************
+static std::string EncodeParam(const std::string& param) {
+  // If the string contains one of "tspecials" characters as
+  // specified in RFC 1521, enclose it in quotes.
+  if (param.find_first_of("()<>@,;:\\\"/[]?=") != std::string::npos) {
+    return '"' + param + '"';
+  }
+  return param;
+}
+
+static std::string DecodeParam(const std::string& param) {
+  if (param.size() > 1 && param.front() == '"' && param.back() == '"') {
+    return param.substr(1, param.size() - 2);
+  }
+  return param;
+}
+
+// ***************************************************************************
+// ******************** Main MIME manipulation functions *********************
+// ***************************************************************************
+
+bool mime::Split(const std::string& mime_string,
+                 std::string* type, std::string* subtype,
+                 mime::Parameters* parameters) {
+  std::vector<std::string> parts = string_utils::Split(mime_string, ';');
+  if (parts.empty())
+    return false;
+
+  if (!mime::Split(parts.front(), type, subtype))
+    return false;
+
+  if (parameters) {
+    parameters->clear();
+    parameters->reserve(parts.size() - 1);
+    for (size_t i = 1; i < parts.size(); i++) {
+      auto pair = string_utils::SplitAtFirst(parts[i], '=');
+      pair.second = DecodeParam(pair.second);
+      parameters->push_back(pair);
+    }
+  }
+  return true;
+}
+
+bool mime::Split(const std::string& mime_string,
+                 std::string* type, std::string* subtype) {
+  std::string mime = mime::RemoveParameters(mime_string);
+  auto types = string_utils::SplitAtFirst(mime, '/');
+
+  if (type)
+    *type = types.first;
+
+  if (subtype)
+    *subtype = types.second;
+
+  return !types.first.empty() && !types.second.empty();
+}
+
+std::string mime::Combine(const std::string& type, const std::string& subtype,
+                          const mime::Parameters& parameters) {
+  std::vector<std::string> parts;
+  parts.push_back(string_utils::Join('/', type, subtype));
+  for (const auto& pair : parameters) {
+    parts.push_back(string_utils::Join('=', pair.first,
+                                       EncodeParam(pair.second)));
+  }
+  return string_utils::Join("; ", parts);
+}
+
+std::string mime::GetType(const std::string& mime_string) {
+  std::string mime = mime::RemoveParameters(mime_string);
+  return string_utils::SplitAtFirst(mime, '/').first;
+}
+
+std::string mime::GetSubtype(const std::string& mime_string) {
+  std::string mime = mime::RemoveParameters(mime_string);
+  return string_utils::SplitAtFirst(mime, '/').second;
+}
+
+mime::Parameters mime::GetParameters(const std::string& mime_string) {
+  std::string type;
+  std::string subtype;
+  mime::Parameters parameters;
+
+  if (mime::Split(mime_string, &type, &subtype, &parameters))
+    return std::move(parameters);
+
+  return mime::Parameters();
+}
+
+std::string mime::RemoveParameters(const std::string& mime_string) {
+  return string_utils::SplitAtFirst(mime_string, ';').first;
+}
+
+std::string mime::AppendParameter(const std::string& mime_string,
+                                  const std::string& paramName,
+                                  const std::string& paramValue) {
+  std::string mime(mime_string);
+  mime += "; ";
+  mime += string_utils::Join('=', paramName, EncodeParam(paramValue));
+  return mime;
+}
+
+std::string mime::GetParameterValue(const std::string& mime_string,
+                                    const std::string& paramName) {
+  mime::Parameters params = mime::GetParameters(mime_string);
+  for (const auto& pair : params) {
+    if (base::strcasecmp(pair.first.c_str(), paramName.c_str()) == 0)
+      return pair.second;
+  }
+  return std::string();
+}
+
+}  // namespace buffet
diff --git a/buffet/mime_utils.h b/buffet/mime_utils.h
new file mode 100644
index 0000000..999b677
--- /dev/null
+++ b/buffet/mime_utils.h
@@ -0,0 +1,105 @@
+// 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_MIME_UTILS_H_
+#define BUFFET_MIME_UTILS_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/basictypes.h>
+
+namespace buffet {
+
+namespace mime {
+
+namespace types {
+  // Main MIME type categories
+  extern const char kApplication[];        // application
+  extern const char kAudio[];              // audio
+  extern const char kImage[];              // image
+  extern const char kMessage[];            // message
+  extern const char kMultipart[];          // multipart
+  extern const char kText[];               // test
+  extern const char kVideo[];              // video
+}
+
+namespace parameters {
+  // Common MIME parameters
+  extern const char kCharset[];            // charset=...
+}
+
+namespace image {
+  // Common image MIME types
+  extern const char kJpeg[];               // image/jpeg
+  extern const char kPng[];                // image/png
+  extern const char kBmp[];                // image/bmp
+  extern const char kTiff[];               // image/tiff
+  extern const char kGif[];                // image/gif
+}
+
+namespace text {
+  // Common text MIME types
+  extern const char kPlain[];              // text/plain
+  extern const char kHtml[];               // text/html
+  extern const char kXml[];                // text/xml
+}
+
+namespace application {
+  // Common application MIME types
+  extern const char kOctet_stream[];       // application/octet-stream
+  extern const char kJson[];               // application/json
+  extern const char kWwwFormUrlEncoded[];  // application/x-www-form-urlencoded
+}
+
+typedef std::vector<std::pair<std::string, std::string>> Parameters;
+
+// Combine a MIME type, subtype and parameters into a MIME string.
+// e.g. Combine("text", "plain", {{"charset", "utf-8"}}) will give:
+//      "text/plain; charset=utf-8"
+std::string Combine(const std::string& type, const std::string& subtype,
+                    const Parameters& parameters = {}) WARN_UNUSED_RESULT;
+
+// Splits a MIME string into type and subtype.
+// "text/plain;charset=utf-8" => ("text", "plain")
+bool Split(const std::string& mime_string,
+           std::string* type, std::string* subtype);
+
+// Splits a MIME string into type, subtype, and parameters.
+// "text/plain;charset=utf-8" => ("text", "plain", {{"charset","utf-8"}})
+bool Split(const std::string& mime_string,
+           std::string* type, std::string* subtype, Parameters* parameters);
+
+// Returns the MIME type from MIME string.
+// "text/plain;charset=utf-8" => "text"
+std::string GetType(const std::string& mime_string);
+
+// Returns the MIME sub-type from MIME string.
+// "text/plain;charset=utf-8" => "plain"
+std::string GetSubtype(const std::string& mime_string);
+
+// Returns the MIME parameters from MIME string.
+// "text/plain;charset=utf-8" => {{"charset","utf-8"}}
+Parameters GetParameters(const std::string& mime_string);
+
+// Removes parameters from a MIME string
+// "text/plain;charset=utf-8" => "text/plain"
+std::string RemoveParameters(const std::string& mime_string) WARN_UNUSED_RESULT;
+
+// Appends a parameter to a MIME string.
+// "text/plain" => "text/plain; charset=utf-8"
+std::string AppendParameter(const std::string& mime_string,
+                            const std::string& paramName,
+                            const std::string& paramValue) WARN_UNUSED_RESULT;
+
+// Returns the value of a parameter on a MIME string (empty string if missing).
+// ("text/plain;charset=utf-8","charset") => "utf-8"
+std::string GetParameterValue(const std::string& mime_string,
+                              const std::string& paramName);
+
+}  // namespace mime
+}  // namespace buffet
+
+#endif  // BUFFET_MIME_UTILS_H_
diff --git a/buffet/mime_utils_unittest.cc b/buffet/mime_utils_unittest.cc
new file mode 100644
index 0000000..a705dfa
--- /dev/null
+++ b/buffet/mime_utils_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 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/mime_utils.h"
+
+#include <gtest/gtest.h>
+
+using namespace buffet;  // NOLINT(build/namespaces)
+
+TEST(MimeUtils, Combine) {
+  std::string mime_string = mime::Combine(mime::types::kText, "xml");
+  EXPECT_EQ(mime::text::kXml, mime_string);
+  EXPECT_EQ("application/json; charset=utf-8",
+            mime::Combine(mime::types::kApplication, "json",
+                          {{"charset", "utf-8"}}));
+}
+
+TEST(MimeUtils, Split) {
+  std::string s1, s2;
+  EXPECT_TRUE(mime::Split(mime::image::kJpeg, &s1, &s2));
+  EXPECT_EQ(mime::types::kImage, s1);
+  EXPECT_EQ("jpeg", s2);
+
+  mime::Parameters parameters;
+  EXPECT_TRUE(mime::Split("application/json;charset=utf-8",
+    &s1, &s2, &parameters));
+  EXPECT_EQ(mime::types::kApplication, s1);
+  EXPECT_EQ("json", s2);
+  EXPECT_EQ(mime::application::kJson, mime::Combine(s1, s2));
+  EXPECT_EQ(1, parameters.size());
+  EXPECT_EQ(mime::parameters::kCharset, parameters.front().first);
+  EXPECT_EQ("utf-8", parameters.front().second);
+  EXPECT_EQ("application/json; charset=utf-8",
+            mime::Combine(s1, s2, parameters));
+}
+
+TEST(MimeUtils, ExtractParts) {
+  mime::Parameters parameters;
+
+  EXPECT_EQ(mime::types::kText, mime::GetType(mime::text::kPlain));
+  EXPECT_EQ("plain", mime::GetSubtype(mime::text::kPlain));
+
+  parameters = mime::GetParameters("text/plain; charset=iso-8859-1;foo=bar");
+  EXPECT_EQ(2, parameters.size());
+  EXPECT_EQ(mime::parameters::kCharset, parameters[0].first);
+  EXPECT_EQ("iso-8859-1", parameters[0].second);
+  EXPECT_EQ("foo", parameters[1].first);
+  EXPECT_EQ("bar", parameters[1].second);
+}
+
+TEST(MimeUtils, AppendRemoveParams) {
+  std::string mime_string = mime::AppendParameter(mime::text::kXml,
+                                                  mime::parameters::kCharset,
+                                                  "utf-8");
+  EXPECT_EQ("text/xml; charset=utf-8", mime_string);
+  mime_string = mime::AppendParameter(mime_string, "foo", "bar");
+  EXPECT_EQ("text/xml; charset=utf-8; foo=bar", mime_string);
+  EXPECT_EQ("utf-8", mime::GetParameterValue(mime_string,
+                                             mime::parameters::kCharset));
+  EXPECT_EQ("bar", mime::GetParameterValue(mime_string, "foo"));
+  EXPECT_EQ("", mime::GetParameterValue(mime_string, "baz"));
+  mime_string = mime::RemoveParameters(mime_string);
+  EXPECT_EQ(mime::text::kXml, mime_string);
+}
diff --git a/buffet/storage_impls.cc b/buffet/storage_impls.cc
new file mode 100644
index 0000000..d95538a
--- /dev/null
+++ b/buffet/storage_impls.cc
@@ -0,0 +1,44 @@
+// 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/storage_impls.h"
+
+#include <string>
+
+#include <base/files/important_file_writer.h>
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+
+namespace buffet {
+
+FileStorage::FileStorage(const base::FilePath& file_path)
+    : file_path_(file_path) { }
+
+std::unique_ptr<base::Value> FileStorage::Load() {
+  std::string json;
+  if (!base::ReadFileToString(file_path_, &json))
+    return std::unique_ptr<base::Value>();
+
+  return std::unique_ptr<base::Value>(base::JSONReader::Read(json));
+}
+
+bool FileStorage::Save(const base::Value* config) {
+  std::string json;
+  base::JSONWriter::WriteWithOptions(
+      config, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+  return base::ImportantFileWriter::WriteFileAtomically(file_path_, json);
+}
+
+
+std::unique_ptr<base::Value> MemStorage::Load() {
+  return std::unique_ptr<base::Value>(cache_->DeepCopy());
+}
+
+bool MemStorage::Save(const base::Value* config) {
+  cache_.reset(config->DeepCopy());
+  ++save_count_;
+  return true;
+}
+
+}  // namespace buffet
diff --git a/buffet/storage_impls.h b/buffet/storage_impls.h
new file mode 100644
index 0000000..6e69084
--- /dev/null
+++ b/buffet/storage_impls.h
@@ -0,0 +1,47 @@
+// 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_STORAGE_IMPLS_H_
+#define BUFFET_STORAGE_IMPLS_H_
+
+#include <base/basictypes.h>
+#include <base/file_util.h>
+#include <base/values.h>
+
+#include "buffet/storage_interface.h"
+
+namespace buffet {
+
+// Persists the given Value to an atomically written file.
+class FileStorage : public StorageInterface {
+ public:
+  explicit FileStorage(const base::FilePath& file_path);
+  virtual ~FileStorage() = default;
+  virtual std::unique_ptr<base::Value> Load() override;
+  virtual bool Save(const base::Value* config) override;
+
+ private:
+  base::FilePath file_path_;
+  DISALLOW_COPY_AND_ASSIGN(FileStorage);
+};
+
+// StorageInterface for testing. Just stores the values in memory.
+class MemStorage : public StorageInterface {
+ public:
+  MemStorage() = default;
+  virtual ~MemStorage() = default;
+  virtual std::unique_ptr<base::Value> Load() override;
+  virtual bool Save(const base::Value* config) override;
+  int save_count() { return save_count_; }
+  void reset_save_count() { save_count_ = 0; }
+
+ private:
+  int save_count_ = 0;
+  std::unique_ptr<base::Value> cache_;
+  DISALLOW_COPY_AND_ASSIGN(MemStorage);
+};
+
+}  // namespace buffet
+
+#endif  // BUFFET_STORAGE_IMPLS_H_
diff --git a/buffet/storage_interface.h b/buffet/storage_interface.h
new file mode 100644
index 0000000..26d71c1
--- /dev/null
+++ b/buffet/storage_interface.h
@@ -0,0 +1,32 @@
+// 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_STORAGE_INTERFACE_H_
+#define BUFFET_STORAGE_INTERFACE_H_
+
+#include <memory>
+
+#include <base/values.h>
+
+namespace buffet {
+
+// We need to persist data in a couple places, and it is convenient to hide
+// the details of this storage behind an interface for test purposes.
+class StorageInterface {
+ public:
+  // Load the device registration configuration from storage.
+  // If it fails (e.g. the storage container [file?] doesn't exist), then
+  // it returns empty unique_ptr (aka nullptr).
+  virtual std::unique_ptr<base::Value> Load() = 0;
+
+  // Save the device registration configuration to storage.
+  // If saved successfully, returns true. Could fail when writing to
+  // physical storage like file system for various reasons (out of disk space,
+  // access permissions, etc).
+  virtual bool Save(const base::Value* config) = 0;
+};
+
+}  // namespace buffet
+
+#endif  // BUFFET_STORAGE_INTERFACE_H_
diff --git a/buffet/string_utils.cc b/buffet/string_utils.cc
new file mode 100644
index 0000000..8916e96
--- /dev/null
+++ b/buffet/string_utils.cc
@@ -0,0 +1,99 @@
+// 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/string_utils.h"
+
+#include <algorithm>
+#include <string.h>
+#include <utility>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+namespace buffet {
+namespace string_utils {
+
+std::vector<std::string> Split(const std::string& str,
+                               char delimiter,
+                               bool trim_whitespaces,
+                               bool purge_empty_strings) {
+  std::vector<std::string> tokens;
+  if (delimiter == 0)
+    return tokens;
+
+  const char* sz = str.c_str();
+  if (sz) {
+    const char* szNext = strchr(sz, delimiter);
+    while (szNext) {
+      if (szNext != sz || !purge_empty_strings)
+        tokens.emplace_back(sz, szNext - sz);
+      sz = szNext + 1;
+      szNext = strchr(sz, delimiter);
+    }
+    if (*sz != 0 || !purge_empty_strings)
+      tokens.emplace_back(sz);
+  }
+
+  if (trim_whitespaces) {
+    std::for_each(tokens.begin(), tokens.end(),
+                  [](std::string& str) {
+      base::TrimWhitespaceASCII(str, base::TRIM_ALL, &str); });
+  }
+
+  return tokens;
+}
+
+std::pair<std::string, std::string> SplitAtFirst(const std::string& str,
+                                                 char delimiter,
+                                                 bool trim_whitespaces) {
+  std::pair<std::string, std::string> pair;
+  if (delimiter == 0)
+    return pair;
+
+  const char* sz = str.c_str();
+  const char* szNext = strchr(sz, delimiter);
+  if (szNext) {
+    pair.first = std::string(sz, szNext);
+    pair.second = std::string(szNext + 1);
+  } else {
+    pair.first = str;
+  }
+
+  if (trim_whitespaces) {
+    base::TrimWhitespaceASCII(pair.first, base::TRIM_ALL, &pair.first);
+    base::TrimWhitespaceASCII(pair.second, base::TRIM_ALL, &pair.second);
+  }
+
+  return pair;
+}
+
+std::string Join(char delimiter, const std::vector<std::string>& strings) {
+  return JoinString(strings, delimiter);
+}
+
+std::string Join(const std::string& delimiter,
+                 const std::vector<std::string>& strings) {
+  return JoinString(strings, delimiter);
+}
+
+std::string Join(char delimiter,
+                 const std::string& str1, const std::string& str2) {
+  return str1 + delimiter + str2;
+}
+
+std::string Join(const std::string& delimiter,
+                 const std::string& str1, const std::string& str2) {
+  return str1 + delimiter + str2;
+}
+
+std::string ToString(double value) {
+  return base::StringPrintf("%g", value);
+}
+
+std::string ToString(bool value) {
+  return value ? "true" : "false";
+}
+
+}  // namespace string_utils
+}  // namespace buffet
diff --git a/buffet/string_utils.h b/buffet/string_utils.h
new file mode 100644
index 0000000..e29675b
--- /dev/null
+++ b/buffet/string_utils.h
@@ -0,0 +1,56 @@
+// 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_STRING_UTILS_H_
+#define BUFFET_STRING_UTILS_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace buffet {
+namespace string_utils {
+
+// Treats the string as a delimited list of substrings and returns the array
+// of original elements of the list.
+// By default, empty elements from the original string are omitted and
+// each element has all whitespaces trimmed off.
+std::vector<std::string> Split(const std::string& str,
+                               char delimiter,
+                               bool trim_whitespaces = true,
+                               bool purge_empty_strings = true);
+
+// Splits the string into two pieces at the first position of the specified
+// delimiter. By default, each part has all whitespaces trimmed off.
+std::pair<std::string, std::string> SplitAtFirst(const std::string& str,
+                                                 char delimiter,
+                                                 bool trim_whitespaces = true);
+
+// Joins an array of strings into a single string separated by |delimiter|.
+std::string Join(char delimiter, const std::vector<std::string>& strings);
+std::string Join(const std::string& delimiter,
+                 const std::vector<std::string>& strings);
+std::string Join(char delimiter,
+                 const std::string& str1, const std::string& str2);
+std::string Join(const std::string& delimiter,
+                 const std::string& str1, const std::string& str2);
+
+// string_utils::ToString() is a helper function to convert any scalar type
+// to a string. In most cases, it redirects the call to std::to_string with
+// two exceptions: for std::string itself and for double and bool.
+template<typename T>
+inline std::string ToString(T value) { return std::to_string(value); }
+// Having the following overload is handy for templates where the type
+// of template parameter isn't known and could be a string itself.
+inline std::string ToString(std::string value) { return value; }
+// We overload this for double because std::to_string(double) uses %f to
+// format the value and I would like to use a shorter %g format instead.
+std::string ToString(double value);
+// And the bool to be converted as true/false instead of 1/0.
+std::string ToString(bool value);
+
+}  // namespace string_utils
+}  // namespace buffet
+
+#endif  // BUFFET_STRING_UTILS_H_
diff --git a/buffet/string_utils_unittest.cc b/buffet/string_utils_unittest.cc
new file mode 100644
index 0000000..4a021f4
--- /dev/null
+++ b/buffet/string_utils_unittest.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 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/string_utils.h"
+
+#include <gtest/gtest.h>
+
+using namespace buffet;  // NOLINT(build/namespaces)
+
+TEST(StringUtils, Split) {
+  std::vector<std::string> parts;
+
+  parts = string_utils::Split(",a,bc , d,,e,", ',', true, true);
+  EXPECT_EQ(4, parts.size());
+  EXPECT_EQ("a", parts[0]);
+  EXPECT_EQ("bc", parts[1]);
+  EXPECT_EQ("d", parts[2]);
+  EXPECT_EQ("e", parts[3]);
+
+  parts = string_utils::Split(",a,bc , d,,e,", ',', false, true);
+  EXPECT_EQ(4, parts.size());
+  EXPECT_EQ("a", parts[0]);
+  EXPECT_EQ("bc ", parts[1]);
+  EXPECT_EQ(" d", parts[2]);
+  EXPECT_EQ("e", parts[3]);
+
+  parts = string_utils::Split(",a,bc , d,,e,", ',', true, false);
+  EXPECT_EQ(7, parts.size());
+  EXPECT_EQ("", parts[0]);
+  EXPECT_EQ("a", parts[1]);
+  EXPECT_EQ("bc", parts[2]);
+  EXPECT_EQ("d", parts[3]);
+  EXPECT_EQ("", parts[4]);
+  EXPECT_EQ("e", parts[5]);
+  EXPECT_EQ("", parts[6]);
+
+  parts = string_utils::Split(",a,bc , d,,e,", ',', false, false);
+  EXPECT_EQ(7, parts.size());
+  EXPECT_EQ("", parts[0]);
+  EXPECT_EQ("a", parts[1]);
+  EXPECT_EQ("bc ", parts[2]);
+  EXPECT_EQ(" d", parts[3]);
+  EXPECT_EQ("", parts[4]);
+  EXPECT_EQ("e", parts[5]);
+  EXPECT_EQ("", parts[6]);
+}
+
+TEST(StringUtils, SplitAtFirst) {
+  std::pair<std::string, std::string> pair;
+
+  pair = string_utils::SplitAtFirst(" 123 : 4 : 56 : 789 ", ':', true);
+  EXPECT_EQ("123", pair.first);
+  EXPECT_EQ("4 : 56 : 789", pair.second);
+
+  pair = string_utils::SplitAtFirst(" 123 : 4 : 56 : 789 ", ':', false);
+  EXPECT_EQ(" 123 ", pair.first);
+  EXPECT_EQ(" 4 : 56 : 789 ", pair.second);
+
+  pair = string_utils::SplitAtFirst("", '=');
+  EXPECT_EQ("", pair.first);
+  EXPECT_EQ("", pair.second);
+
+  pair = string_utils::SplitAtFirst("=", '=');
+  EXPECT_EQ("", pair.first);
+  EXPECT_EQ("", pair.second);
+
+  pair = string_utils::SplitAtFirst("a=", '=');
+  EXPECT_EQ("a", pair.first);
+  EXPECT_EQ("", pair.second);
+
+  pair = string_utils::SplitAtFirst("abc=", '=');
+  EXPECT_EQ("abc", pair.first);
+  EXPECT_EQ("", pair.second);
+
+  pair = string_utils::SplitAtFirst("=a", '=');
+  EXPECT_EQ("", pair.first);
+  EXPECT_EQ("a", pair.second);
+
+  pair = string_utils::SplitAtFirst("=abc=", '=');
+  EXPECT_EQ("", pair.first);
+  EXPECT_EQ("abc=", pair.second);
+
+  pair = string_utils::SplitAtFirst("abc", '=');
+  EXPECT_EQ("abc", pair.first);
+  EXPECT_EQ("", pair.second);
+}
+
+TEST(StringUtils, Join_Char) {
+  EXPECT_EQ("", string_utils::Join(',', {}));
+  EXPECT_EQ("abc", string_utils::Join(',', {"abc"}));
+  EXPECT_EQ("abc,defg", string_utils::Join(',', {"abc", "defg"}));
+  EXPECT_EQ("1:2:3", string_utils::Join(':', {"1", "2", "3"}));
+  EXPECT_EQ("192.168.0.1", string_utils::Join('.', {"192", "168", "0", "1"}));
+  EXPECT_EQ("ff02::1", string_utils::Join(':', {"ff02", "", "1"}));
+}
+
+TEST(StringUtils, Join_String) {
+  EXPECT_EQ("", string_utils::Join(",", {}));
+  EXPECT_EQ("abc", string_utils::Join(",", {"abc"}));
+  EXPECT_EQ("abc,defg", string_utils::Join(",", {"abc", "defg"}));
+  EXPECT_EQ("1 : 2 : 3", string_utils::Join(" : ", {"1", "2", "3"}));
+  EXPECT_EQ("123", string_utils::Join("", {"1", "2", "3"}));
+}
+
+TEST(StringUtils, Join_Pair) {
+  EXPECT_EQ("ab,cd", string_utils::Join(',', "ab", "cd"));
+  EXPECT_EQ("key = value", string_utils::Join(" = ", "key", "value"));
+}
diff --git a/buffet/url_utils.cc b/buffet/url_utils.cc
new file mode 100644
index 0000000..3fcdb48
--- /dev/null
+++ b/buffet/url_utils.cc
@@ -0,0 +1,167 @@
+// 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/url_utils.h"
+
+#include <algorithm>
+
+namespace {
+// Given a URL string, determine where the query string starts and ends.
+// URLs have schema, domain and path (along with possible user name, password
+// and port number which are of no interest for us here) which could optionally
+// have a query string that is separated from the path by '?'. Finally, the URL
+// could also have a '#'-separated URL fragment which is usually used by the
+// browser as a bookmark element. So, for example:
+//    http://server.com/path/to/object?k=v&foo=bar#fragment
+// Here:
+//    http://server.com/path/to/object - is the URL of the object,
+//    ?k=v&foo=bar                     - URL query string
+//    #fragment                        - URL fragment string
+// If |exclude_fragment| is true, the function returns the start character and
+// the length of the query string alone. If it is false, the query string length
+// will include both the query string and the fragment.
+bool GetQueryStringPos(const std::string& url, bool exclude_fragment,
+                       size_t* query_pos, size_t* query_len) {
+  size_t query_start = url.find_first_of("?#");
+  if (query_start == std::string::npos) {
+    *query_pos = url.size();
+    if (query_len)
+      *query_len = 0;
+    return false;
+  }
+
+  *query_pos = query_start;
+  if (query_len) {
+    size_t query_end = url.size();
+
+    if (exclude_fragment) {
+      if (url[query_start] == '?') {
+        size_t pos_fragment = url.find('#', query_start);
+        if (pos_fragment != std::string::npos)
+          query_end = pos_fragment;
+      } else {
+        query_end = query_start;
+      }
+    }
+    *query_len = query_end - query_start;
+  }
+  return true;
+}
+}  // anonymous namespace
+
+namespace buffet {
+
+std::string url::TrimOffQueryString(std::string* url) {
+  size_t query_pos;
+  if (!GetQueryStringPos(*url, false, &query_pos, nullptr))
+    return std::string();
+  std::string query_string = url->substr(query_pos);
+  url->resize(query_pos);
+  return query_string;
+}
+
+std::string url::Combine(
+    const std::string& url, const std::string& subpath) {
+  return CombineMultiple(url, {subpath});
+}
+
+std::string url::CombineMultiple(
+    const std::string& url, const std::vector<std::string>& parts) {
+  std::string result = url;
+  if (!parts.empty()) {
+    std::string query_string = TrimOffQueryString(&result);
+    for (const auto& part : parts) {
+      if (!part.empty()) {
+        if (!result.empty() && result.back() != '/')
+          result += '/';
+        size_t non_slash_pos = part.find_first_not_of('/');
+        if (non_slash_pos != std::string::npos)
+          result += part.substr(non_slash_pos);
+      }
+    }
+    result += query_string;
+  }
+  return result;
+}
+
+std::string url::GetQueryString(
+    const std::string& url, bool remove_fragment) {
+  std::string query_string;
+  size_t query_pos, query_len;
+  if (GetQueryStringPos(url, remove_fragment, &query_pos, &query_len)) {
+    query_string = url.substr(query_pos, query_len);
+  }
+  return query_string;
+}
+
+data_encoding::WebParamList url::GetQueryStringParameters(
+    const std::string& url) {
+  // Extract the query string and remove the leading '?'.
+  std::string query_string = GetQueryString(url, true);
+  if (!query_string.empty() && query_string.front() == '?')
+    query_string.erase(query_string.begin());
+  return data_encoding::WebParamsDecode(query_string);
+}
+
+std::string url::GetQueryStringValue(
+    const std::string& url, const std::string& name) {
+  return GetQueryStringValue(GetQueryStringParameters(url), name);
+}
+
+std::string url::GetQueryStringValue(
+    const data_encoding::WebParamList& params,
+    const std::string& name) {
+  for (const auto& pair : params) {
+    if (name.compare(pair.first) == 0)
+      return pair.second;
+  }
+  return std::string();
+}
+
+std::string url::RemoveQueryString(
+    const std::string& url, bool remove_fragment_too) {
+  size_t query_pos, query_len;
+  if (!GetQueryStringPos(url, !remove_fragment_too, &query_pos, &query_len))
+    return url;
+  std::string result = url.substr(0, query_pos);
+  size_t fragment_pos = query_pos + query_len;
+  if (fragment_pos < url.size()) {
+    result += url.substr(fragment_pos);
+  }
+  return result;
+}
+
+std::string url::AppendQueryParam(
+    const std::string& url, const std::string& name, const std::string& value) {
+  return AppendQueryParams(url, {{name, value}});
+}
+
+std::string url::AppendQueryParams(
+    const std::string& url,
+    const data_encoding::WebParamList& params) {
+  if (params.empty())
+    return url;
+  size_t query_pos, query_len;
+  GetQueryStringPos(url, true, &query_pos, &query_len);
+  size_t fragment_pos = query_pos + query_len;
+  std::string result = url.substr(0, fragment_pos);
+  if (query_len == 0) {
+    result += '?';
+  } else if (query_len > 1) {
+    result += '&';
+  }
+  result += data_encoding::WebParamsEncode(params);
+  if (fragment_pos < url.size()) {
+    result += url.substr(fragment_pos);
+  }
+  return result;
+}
+
+bool url::HasQueryString(const std::string& url) {
+  size_t query_pos, query_len;
+  GetQueryStringPos(url, true, &query_pos, &query_len);
+  return (query_len > 0);
+}
+
+}  // namespace buffet
diff --git a/buffet/url_utils.h b/buffet/url_utils.h
new file mode 100644
index 0000000..d9bce25
--- /dev/null
+++ b/buffet/url_utils.h
@@ -0,0 +1,77 @@
+// 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_URL_UTILS_H_
+#define BUFFET_URL_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/data_encoding.h"
+
+namespace buffet {
+
+namespace url {
+
+// Appends a subpath to url and delimiting then with '/' if the path doesn't
+// end with it already. Also handles URLs with query parameters/fragment.
+std::string Combine(const std::string& url,
+                    const std::string& subpath) WARN_UNUSED_RESULT;
+std::string CombineMultiple(
+    const std::string& url,
+    const std::vector<std::string>& parts) WARN_UNUSED_RESULT;
+
+// Removes the query string/fragment from |url| and returns the query string.
+// This method actually modifies |url|. So, if you call it on this:
+//    http://www.test.org/?foo=bar
+// it will modify |url| to "http://www.test.org/" and return "?foo=bar"
+std::string TrimOffQueryString(std::string* url);
+
+// Returns the query string, if available.
+// For example, for the following URL:
+//    http://server.com/path/to/object?k=v&foo=bar#fragment
+// Here:
+//    http://server.com/path/to/object - is the URL of the object,
+//    ?k=v&foo=bar                     - URL query string
+//    #fragment                        - URL fragment string
+// If |remove_fragment| is true, the function returns the query string without
+// the fragment. Otherwise the fragment is included as part of the result.
+std::string GetQueryString(const std::string& url, bool remove_fragment);
+
+// Parses the query string into a set of key-value pairs.
+data_encoding::WebParamList GetQueryStringParameters(const std::string& url);
+
+// Returns a value of the specified query parameter, or empty string if missing.
+std::string GetQueryStringValue(const std::string& url,
+                                const std::string& name);
+std::string GetQueryStringValue(const data_encoding::WebParamList& params,
+                                const std::string& name);
+
+// Removes the query string and/or a fragment part from URL.
+// If |remove_fragment| is specified, the fragment is also removed.
+// For example:
+//    http://server.com/path/to/object?k=v&foo=bar#fragment
+// true  -> http://server.com/path/to/object
+// false -> http://server.com/path/to/object#fragment
+std::string RemoveQueryString(const std::string& url,
+                              bool remove_fragment) WARN_UNUSED_RESULT;
+
+// Appends a single query parameter to the URL.
+std::string AppendQueryParam(const std::string& url,
+                             const std::string& name,
+                             const std::string& value) WARN_UNUSED_RESULT;
+// Appends a list of query parameters to the URL.
+std::string AppendQueryParams(
+    const std::string& url,
+    const data_encoding::WebParamList& params) WARN_UNUSED_RESULT;
+
+// Checks if the URL has query parameters.
+bool HasQueryString(const std::string& url);
+
+}  // namespace url
+}  // namespace buffet
+
+#endif  // BUFFET_URL_UTILS_H_
diff --git a/buffet/url_utils_unittest.cc b/buffet/url_utils_unittest.cc
new file mode 100644
index 0000000..7f4826d
--- /dev/null
+++ b/buffet/url_utils_unittest.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 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/url_utils.h"
+
+#include <gtest/gtest.h>
+
+using namespace buffet;  // NOLINT(build/namespaces)
+
+TEST(UrlUtils, Combine) {
+  EXPECT_EQ("http://sample.org/path",
+            url::Combine("http://sample.org", "path"));
+  EXPECT_EQ("http://sample.org/path",
+            url::Combine("http://sample.org/", "path"));
+  EXPECT_EQ("path1/path2", url::Combine("", "path1/path2"));
+  EXPECT_EQ("path1/path2", url::Combine("path1", "path2"));
+  EXPECT_EQ("http://sample.org",
+            url::Combine("http://sample.org", ""));
+  EXPECT_EQ("http://sample.org/path",
+            url::Combine("http://sample.org/", "/path"));
+  EXPECT_EQ("http://sample.org/path",
+            url::Combine("http://sample.org", "//////path"));
+  EXPECT_EQ("http://sample.org/",
+            url::Combine("http://sample.org", "///"));
+  EXPECT_EQ("http://sample.org/obj/path1/path2",
+            url::Combine("http://sample.org/obj", "path1/path2"));
+  EXPECT_EQ("http://sample.org/obj/path1/path2#tag",
+            url::Combine("http://sample.org/obj#tag", "path1/path2"));
+  EXPECT_EQ("http://sample.org/obj/path1/path2?k1=v1&k2=v2",
+            url::Combine("http://sample.org/obj?k1=v1&k2=v2", "path1/path2"));
+  EXPECT_EQ("http://sample.org/obj/path1/path2?k1=v1#k2=v2",
+            url::Combine("http://sample.org/obj/?k1=v1#k2=v2", "path1/path2"));
+  EXPECT_EQ("http://sample.org/obj/path1/path2#tag?",
+            url::Combine("http://sample.org/obj#tag?", "path1/path2"));
+  EXPECT_EQ("path1/path2", url::CombineMultiple("", {"path1", "path2"}));
+  EXPECT_EQ("http://sample.org/obj/part1/part2",
+            url::CombineMultiple("http://sample.org",
+                                 {"obj", "", "/part1/", "part2"}));
+}
+
+TEST(UrlUtils, GetQueryString) {
+  EXPECT_EQ("", url::GetQueryString("http://sample.org", false));
+  EXPECT_EQ("", url::GetQueryString("http://sample.org", true));
+  EXPECT_EQ("", url::GetQueryString("", false));
+  EXPECT_EQ("", url::GetQueryString("", true));
+
+  EXPECT_EQ("?q=v&b=2#tag?2",
+            url::GetQueryString("http://s.com/?q=v&b=2#tag?2", false));
+  EXPECT_EQ("?q=v&b=2",
+            url::GetQueryString("http://s.com/?q=v&b=2#tag?2", true));
+
+  EXPECT_EQ("#tag?a=2",
+            url::GetQueryString("http://s.com/#tag?a=2", false));
+  EXPECT_EQ("",
+            url::GetQueryString("http://s.com/#tag?a=2", true));
+
+  EXPECT_EQ("?a=2&b=2",
+            url::GetQueryString("?a=2&b=2", false));
+  EXPECT_EQ("?a=2&b=2",
+            url::GetQueryString("?a=2&b=2", true));
+
+  EXPECT_EQ("#s#?d#?f?#s?#d",
+            url::GetQueryString("#s#?d#?f?#s?#d", false));
+  EXPECT_EQ("",
+            url::GetQueryString("#s#?d#?f?#s?#d", true));
+}
+
+TEST(UrlUtils, GetQueryStringParameters) {
+  auto params = url::GetQueryStringParameters(
+    "http://sample.org/path?k=v&&%3Dkey%3D=val%26&r#blah");
+
+  EXPECT_EQ(3, params.size());
+  EXPECT_EQ("k", params[0].first);
+  EXPECT_EQ("v", params[0].second);
+  EXPECT_EQ("=key=", params[1].first);
+  EXPECT_EQ("val&", params[1].second);
+  EXPECT_EQ("r", params[2].first);
+  EXPECT_EQ("", params[2].second);
+}
+
+TEST(UrlUtils, GetQueryStringValue) {
+  std::string url = "http://url?key1=val1&&key2=val2";
+  EXPECT_EQ("val1", url::GetQueryStringValue(url, "key1"));
+  EXPECT_EQ("val2", url::GetQueryStringValue(url, "key2"));
+  EXPECT_EQ("", url::GetQueryStringValue(url, "key3"));
+
+  auto params = url::GetQueryStringParameters(url);
+  EXPECT_EQ("val1", url::GetQueryStringValue(params, "key1"));
+  EXPECT_EQ("val2", url::GetQueryStringValue(params, "key2"));
+  EXPECT_EQ("", url::GetQueryStringValue(params, "key3"));
+}
+
+TEST(UrlUtils, TrimOffQueryString) {
+  std::string url = "http://url?key1=val1&key2=val2#fragment";
+  std::string query = url::TrimOffQueryString(&url);
+  EXPECT_EQ("http://url", url);
+  EXPECT_EQ("?key1=val1&key2=val2#fragment", query);
+
+  url = "http://url#fragment";
+  query = url::TrimOffQueryString(&url);
+  EXPECT_EQ("http://url", url);
+  EXPECT_EQ("#fragment", query);
+
+  url = "http://url";
+  query = url::TrimOffQueryString(&url);
+  EXPECT_EQ("http://url", url);
+  EXPECT_EQ("", query);
+}
+
+TEST(UrlUtils, RemoveQueryString) {
+  std::string url = "http://url?key1=val1&key2=val2#fragment";
+  EXPECT_EQ("http://url", url::RemoveQueryString(url, true));
+  EXPECT_EQ("http://url#fragment", url::RemoveQueryString(url, false));
+}
+
+TEST(UrlUtils, AppendQueryParam) {
+  std::string url = "http://server.com/path";
+  url = url::AppendQueryParam(url, "param", "value");
+  EXPECT_EQ("http://server.com/path?param=value", url);
+  url = url::AppendQueryParam(url, "param2", "v");
+  EXPECT_EQ("http://server.com/path?param=value&param2=v", url);
+
+  url = "http://server.com/path#fragment";
+  url = url::AppendQueryParam(url, "param", "value");
+  EXPECT_EQ("http://server.com/path?param=value#fragment", url);
+  url = url::AppendQueryParam(url, "param2", "v");
+  EXPECT_EQ("http://server.com/path?param=value&param2=v#fragment", url);
+
+  url = url::AppendQueryParam("http://server.com/path?", "param", "value");
+  EXPECT_EQ("http://server.com/path?param=value", url);
+}
+
+TEST(UrlUtils, AppendQueryParams) {
+  std::string url = "http://server.com/path";
+  url = url::AppendQueryParams(url, {});
+  EXPECT_EQ("http://server.com/path", url);
+  url = url::AppendQueryParams(url, {{"param", "value"}, {"q", "="}});
+  EXPECT_EQ("http://server.com/path?param=value&q=%3D", url);
+  url += "#fr?";
+  url = url::AppendQueryParams(url, {{"p", "1"}, {"s&", "\n"}});
+  EXPECT_EQ("http://server.com/path?param=value&q=%3D&p=1&s%26=%0A#fr?", url);
+}
+
+TEST(UrlUtils, HasQueryString) {
+  EXPECT_FALSE(url::HasQueryString("http://server.com/path"));
+  EXPECT_FALSE(url::HasQueryString("http://server.com/path#blah?v=1"));
+  EXPECT_TRUE(url::HasQueryString("http://server.com/path?v=1#blah"));
+  EXPECT_TRUE(url::HasQueryString("http://server.com/path?v=1"));
+  EXPECT_FALSE(url::HasQueryString(""));
+  EXPECT_TRUE(url::HasQueryString("?ss"));
+}