Project import
diff --git a/libbrillo/.clang-format b/libbrillo/.clang-format
new file mode 120000
index 0000000..f412743
--- /dev/null
+++ b/libbrillo/.clang-format
@@ -0,0 +1 @@
+../../build/tools/brillo-clang-format
\ No newline at end of file
diff --git a/libbrillo/Android.mk b/libbrillo/Android.mk
new file mode 100644
index 0000000..c7d2a29
--- /dev/null
+++ b/libbrillo/Android.mk
@@ -0,0 +1,408 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default values for the USE flags. Override these USE flags from your product
+# by setting BRILLO_USE_* values. Note that we define local variables like
+# local_use_* to prevent leaking our default setting for other packages.
+local_use_dbus := $(if $(BRILLO_USE_DBUS),$(BRILLO_USE_DBUS),1)
+
+LOCAL_PATH := $(call my-dir)
+
+libbrillo_cpp_extension := .cc
+libbrillo_core_sources := \
+ brillo/backoff_entry.cc \
+ brillo/data_encoding.cc \
+ brillo/errors/error.cc \
+ brillo/errors/error_codes.cc \
+ brillo/flag_helper.cc \
+ brillo/key_value_store.cc \
+ brillo/message_loops/base_message_loop.cc \
+ brillo/message_loops/message_loop.cc \
+ brillo/message_loops/message_loop_utils.cc \
+ brillo/mime_utils.cc \
+ brillo/osrelease_reader.cc \
+ brillo/process.cc \
+ brillo/process_information.cc \
+ brillo/secure_blob.cc \
+ brillo/strings/string_utils.cc \
+ brillo/syslog_logging.cc \
+ brillo/type_name_undecorate.cc \
+ brillo/url_utils.cc \
+ brillo/userdb_utils.cc \
+ brillo/value_conversion.cc \
+
+libbrillo_linux_sources := \
+ brillo/asynchronous_signal_handler.cc \
+ brillo/daemons/daemon.cc \
+ brillo/file_utils.cc \
+ brillo/process_reaper.cc \
+
+libbrillo_binder_sources := \
+ brillo/binder_watcher.cc \
+
+libbrillo_dbus_sources := \
+ brillo/any.cc \
+ brillo/daemons/dbus_daemon.cc \
+ brillo/dbus/async_event_sequencer.cc \
+ brillo/dbus/data_serialization.cc \
+ brillo/dbus/dbus_connection.cc \
+ brillo/dbus/dbus_method_invoker.cc \
+ brillo/dbus/dbus_method_response.cc \
+ brillo/dbus/dbus_object.cc \
+ brillo/dbus/dbus_service_watcher.cc \
+ brillo/dbus/dbus_signal.cc \
+ brillo/dbus/exported_object_manager.cc \
+ brillo/dbus/exported_property_set.cc \
+ brillo/dbus/utils.cc \
+
+libbrillo_http_sources := \
+ brillo/http/curl_api.cc \
+ brillo/http/http_connection_curl.cc \
+ brillo/http/http_form_data.cc \
+ brillo/http/http_request.cc \
+ brillo/http/http_transport.cc \
+ brillo/http/http_transport_curl.cc \
+ brillo/http/http_utils.cc \
+
+libbrillo_policy_sources := \
+ policy/device_policy.cc \
+ policy/libpolicy.cc \
+
+libbrillo_stream_sources := \
+ brillo/streams/file_stream.cc \
+ brillo/streams/input_stream_set.cc \
+ brillo/streams/memory_containers.cc \
+ brillo/streams/memory_stream.cc \
+ brillo/streams/openssl_stream_bio.cc \
+ brillo/streams/stream.cc \
+ brillo/streams/stream_errors.cc \
+ brillo/streams/stream_utils.cc \
+ brillo/streams/tls_stream.cc \
+
+libbrillo_test_helpers_sources := \
+ brillo/http/http_connection_fake.cc \
+ brillo/http/http_transport_fake.cc \
+ brillo/message_loops/fake_message_loop.cc \
+ brillo/streams/fake_stream.cc \
+ brillo/unittest_utils.cc \
+
+libbrillo_test_sources := \
+ brillo/asynchronous_signal_handler_unittest.cc \
+ brillo/backoff_entry_unittest.cc \
+ brillo/data_encoding_unittest.cc \
+ brillo/errors/error_codes_unittest.cc \
+ brillo/errors/error_unittest.cc \
+ brillo/file_utils_unittest.cc \
+ brillo/flag_helper_unittest.cc \
+ brillo/http/http_connection_curl_unittest.cc \
+ brillo/http/http_form_data_unittest.cc \
+ brillo/http/http_request_unittest.cc \
+ brillo/http/http_transport_curl_unittest.cc \
+ brillo/http/http_utils_unittest.cc \
+ brillo/key_value_store_unittest.cc \
+ brillo/map_utils_unittest.cc \
+ brillo/message_loops/base_message_loop_unittest.cc \
+ brillo/message_loops/fake_message_loop_unittest.cc \
+ brillo/mime_utils_unittest.cc \
+ brillo/osrelease_reader_unittest.cc \
+ brillo/process_reaper_unittest.cc \
+ brillo/process_unittest.cc \
+ brillo/secure_blob_unittest.cc \
+ brillo/streams/fake_stream_unittest.cc \
+ brillo/streams/file_stream_unittest.cc \
+ brillo/streams/input_stream_set_unittest.cc \
+ brillo/streams/memory_containers_unittest.cc \
+ brillo/streams/memory_stream_unittest.cc \
+ brillo/streams/openssl_stream_bio_unittests.cc \
+ brillo/streams/stream_unittest.cc \
+ brillo/streams/stream_utils_unittest.cc \
+ brillo/strings/string_utils_unittest.cc \
+ brillo/type_name_undecorate_unittest.cc \
+ brillo/unittest_utils.cc \
+ brillo/url_utils_unittest.cc \
+ brillo/value_conversion_unittest.cc \
+
+libbrillo_dbus_test_sources := \
+ brillo/any_unittest.cc \
+ brillo/any_internal_impl_unittest.cc \
+ brillo/dbus/async_event_sequencer_unittest.cc \
+ brillo/dbus/data_serialization_unittest.cc \
+ brillo/dbus/dbus_method_invoker_unittest.cc \
+ brillo/dbus/dbus_object_unittest.cc \
+ brillo/dbus/dbus_param_reader_unittest.cc \
+ brillo/dbus/dbus_param_writer_unittest.cc \
+ brillo/dbus/dbus_signal_handler_unittest.cc \
+ brillo/dbus/exported_object_manager_unittest.cc \
+ brillo/dbus/exported_property_set_unittest.cc \
+ brillo/variant_dictionary_unittest.cc \
+
+libbrillo_CFLAGS := \
+ -Wall \
+ -Werror \
+ -DUSE_DBUS=$(local_use_dbus)
+libbrillo_CPPFLAGS :=
+libbrillo_includes :=
+libbrillo_shared_libraries := libchrome
+
+# Shared library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo
+LOCAL_SRC_FILES := $(libbrillo_core_sources) $(libbrillo_linux_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries)
+LOCAL_STATIC_LIBRARIES := libmodpb64 libgtest_prod
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_SHARED_LIBRARY)
+
+# Shared binder library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo-binder
+LOCAL_SRC_FILES := $(libbrillo_binder_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_SHARED_LIBRARIES := \
+ $(libbrillo_shared_libraries) \
+ libbinder \
+ libbrillo \
+ libutils
+LOCAL_STATIC_LIBRARIES := libgtest_prod
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_SHARED_LIBRARY)
+
+ifeq ($(local_use_dbus),1)
+
+# Shared dbus library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo-dbus
+LOCAL_SRC_FILES := $(libbrillo_dbus_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries) libbrillo \
+ libchrome-dbus libdbus
+LOCAL_STATIC_LIBRARIES := libgtest_prod
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) external/dbus
+include $(BUILD_SHARED_LIBRARY)
+
+endif # local_use_dbus == 1
+
+# Shared minijail library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo-minijail
+LOCAL_SRC_FILES := brillo/minijail/minijail.cc \
+
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries) libbrillo \
+ libminijail
+LOCAL_STATIC_LIBRARIES := libgtest_prod
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_SHARED_LIBRARY)
+
+# Shared stream library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo-stream
+LOCAL_SRC_FILES := $(libbrillo_stream_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries) libbrillo \
+ libcrypto libssl
+LOCAL_STATIC_LIBRARIES := libgtest_prod
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_SHARED_LIBRARY)
+
+# Shared http library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo-http
+LOCAL_SRC_FILES := $(libbrillo_http_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries) libbrillo \
+ libbrillo-stream libcurl
+LOCAL_STATIC_LIBRARIES := libgtest_prod
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_SHARED_LIBRARY)
+
+# Shared policy library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo-policy
+LOCAL_SRC_FILES := $(libbrillo_policy_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries)
+LOCAL_STATIC_LIBRARIES := libgtest_prod
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_SHARED_LIBRARY)
+
+# Static library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo
+LOCAL_SRC_FILES := $(libbrillo_core_sources) $(libbrillo_linux_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_STATIC_LIBRARIES := $(libbrillo_shared_libraries) libmodpb64 libgtest_prod
+LOCAL_EXPORT_STATIC_LIBRARY_HEADERS := $(LOCAL_STATIC_LIBRARIES)
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_STATIC_LIBRARY)
+
+# Static stream library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo-stream
+LOCAL_SRC_FILES := $(libbrillo_stream_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_STATIC_LIBRARIES := $(libbrillo_shared_libraries) libbrillo \
+ libcrypto libssl libgtest_prod
+LOCAL_EXPORT_STATIC_LIBRARY_HEADERS := $(LOCAL_STATIC_LIBRARIES)
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_STATIC_LIBRARY)
+
+# Static test-helpers library for target
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo-test-helpers
+LOCAL_SRC_FILES := $(libbrillo_test_helpers_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_STATIC_LIBRARIES := libgtest libgmock
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries) libbrillo libcurl \
+ libbrillo-http libbrillo-stream libcrypto
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS) -Wno-sign-compare
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_STATIC_LIBRARY)
+
+# Shared library for host
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo
+LOCAL_SRC_FILES := $(libbrillo_core_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries)
+LOCAL_STATIC_LIBRARIES := libmodpb64 libgtest_prod
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := \
+ -D__ANDROID_HOST__ \
+ $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_HOST_SHARED_LIBRARY)
+
+ifeq ($(HOST_OS),linux)
+
+# Shared stream library for host
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo-stream
+LOCAL_SRC_FILES := $(libbrillo_stream_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries) libbrillo \
+ libcrypto libssl
+LOCAL_STATIC_LIBRARIES := libgtest_prod
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_HOST_SHARED_LIBRARY)
+
+# Shared http library for host
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo-http
+LOCAL_SRC_FILES := $(libbrillo_http_sources)
+LOCAL_C_INCLUDES := $(libbrillo_includes)
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries) libbrillo \
+ libbrillo-stream libcurl-host
+LOCAL_STATIC_LIBRARIES := libgtest_prod
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS)
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+include $(BUILD_HOST_SHARED_LIBRARY)
+
+endif # HOST_OS == linux
+
+# Unit tests.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_CPP_EXTENSION := $(libbrillo_cpp_extension)
+LOCAL_MODULE := libbrillo_test
+LOCAL_MODULE_CLASS := NATIVE_TESTS
+generated_sources_dir := $(call local-generated-sources-dir)
+LOCAL_SRC_FILES := $(libbrillo_test_sources)
+LOCAL_C_INCLUDES := \
+ $(libbrillo_includes) \
+
+LOCAL_STATIC_LIBRARIES := libgtest libchrome_test_helpers \
+ libbrillo-test-helpers libgmock libBionicGtestMain
+LOCAL_SHARED_LIBRARIES := $(libbrillo_shared_libraries) libbrillo libcurl \
+ libbrillo-http libbrillo-stream libcrypto
+ifeq ($(local_use_dbus),1)
+LOCAL_SRC_FILES += $(libbrillo_dbus_test_sources)
+LOCAL_STATIC_LIBRARIES += libchrome_dbus_test_helpers
+LOCAL_SHARED_LIBRARIES += libbrillo-dbus libchrome-dbus libdbus
+endif # local_use_dbus == 1
+LOCAL_CFLAGS := $(libbrillo_CFLAGS)
+LOCAL_CPPFLAGS := $(libbrillo_CPPFLAGS) -Wno-sign-compare
+LOCAL_CLANG := true
+include $(BUILD_NATIVE_TEST)
+
+# Run unit tests on target
+# ========================================================
+# We su shell because process tests try setting "illegal"
+# uid/gids and expecting failures, but root can legally
+# set those to any value.
+runtargettests: libbrillo_test
+ adb sync
+ adb shell su shell /data/nativetest/libbrillo_test/libbrillo_test
diff --git a/libbrillo/MODULE_LICENSE_BSD b/libbrillo/MODULE_LICENSE_BSD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libbrillo/MODULE_LICENSE_BSD
diff --git a/libbrillo/NOTICE b/libbrillo/NOTICE
new file mode 100644
index 0000000..b9e779f
--- /dev/null
+++ b/libbrillo/NOTICE
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/libbrillo/OWNERS b/libbrillo/OWNERS
new file mode 100644
index 0000000..8a548e2
--- /dev/null
+++ b/libbrillo/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+avakulenko@chromium.org
+derat@chromium.org
+vapier@chromium.org
diff --git a/libbrillo/brillo/any.cc b/libbrillo/brillo/any.cc
new file mode 100644
index 0000000..f84badf
--- /dev/null
+++ b/libbrillo/brillo/any.cc
@@ -0,0 +1,79 @@
+// 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 <brillo/any.h>
+
+#include <algorithm>
+
+namespace brillo {
+
+Any::Any() {
+}
+
+Any::Any(const Any& rhs) : data_buffer_(rhs.data_buffer_) {
+}
+
+// NOLINTNEXTLINE(build/c++11)
+Any::Any(Any&& rhs) : data_buffer_(std::move(rhs.data_buffer_)) {
+}
+
+Any::~Any() {
+}
+
+Any& Any::operator=(const Any& rhs) {
+ data_buffer_ = rhs.data_buffer_;
+ return *this;
+}
+
+// NOLINTNEXTLINE(build/c++11)
+Any& Any::operator=(Any&& rhs) {
+ data_buffer_ = std::move(rhs.data_buffer_);
+ return *this;
+}
+
+bool Any::operator==(const Any& rhs) const {
+ // Make sure both objects contain data of the same type.
+ if (strcmp(GetTypeTagInternal(), rhs.GetTypeTagInternal()) != 0)
+ return false;
+
+ if (IsEmpty())
+ return true;
+
+ return data_buffer_.GetDataPtr()->CompareEqual(rhs.data_buffer_.GetDataPtr());
+}
+
+const char* Any::GetTypeTagInternal() const {
+ if (!IsEmpty())
+ return data_buffer_.GetDataPtr()->GetTypeTag();
+
+ return "";
+}
+
+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();
+}
+
+void Any::AppendToDBusMessageWriter(dbus::MessageWriter* writer) const {
+ CHECK(!IsEmpty()) << "Must not be called on an empty Any";
+ data_buffer_.GetDataPtr()->AppendToDBusMessage(writer);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/any.h b/libbrillo/brillo/any.h
new file mode 100644
index 0000000..51016b5
--- /dev/null
+++ b/libbrillo/brillo/any.h
@@ -0,0 +1,214 @@
+// 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 brillo::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]);"
+
+// brillo::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 LIBBRILLO_BRILLO_ANY_H_
+#define LIBBRILLO_BRILLO_ANY_H_
+
+#include <brillo/any_internal_impl.h>
+
+#include <algorithm>
+
+#include <brillo/brillo_export.h>
+#include <brillo/type_name_undecorate.h>
+
+namespace dbus {
+class MessageWriter;
+} // namespace dbus
+
+namespace brillo {
+
+class BRILLO_EXPORT Any final {
+ public:
+ Any(); // Do not inline to hide internal_details::Buffer from export table.
+ // Standard copy/move constructors. 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);
+ Any(Any&& rhs); // NOLINT(build/c++11)
+ // Typed constructor that stores a value of type T in the Any.
+ template<class T>
+ inline 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);
+ Any& operator=(Any&& rhs); // NOLINT(build/c++11)
+ template<class T>
+ inline Any& operator=(T value) {
+ data_buffer_.Assign(std::move(value));
+ return *this;
+ }
+
+ // Compares the contents of two Any objects for equality. Note that the
+ // contained type must be equality-comparable (must have operator== defined).
+ // If operator==() is not available for contained type, comparison operation
+ // always returns false (as if the data were different).
+ bool operator==(const Any& rhs) const;
+ inline bool operator!=(const Any& rhs) const { return !operator==(rhs); }
+
+ // 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 char* contained_type = GetTypeTagInternal();
+ if (strcmp(GetTypeTag<CanonicalDestType>(), contained_type) == 0)
+ 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;
+ if (strcmp(GetTypeTag<CanonicalDestTypeNoConst>(), contained_type) == 0)
+ return true;
+
+ using CanonicalDestTypeNoVolatile = typename std::add_pointer<
+ typename std::remove_volatile<NonPointer>::type>::type;
+ if (strcmp(GetTypeTag<CanonicalDestTypeNoVolatile>(), contained_type) == 0)
+ return true;
+
+ using CanonicalDestTypeNoConstOrVolatile = typename std::add_pointer<
+ typename std::remove_cv<NonPointer>::type>::type;
+ return strcmp(GetTypeTag<CanonicalDestTypeNoConstOrVolatile>(),
+ contained_type) == 0;
+ }
+
+ // 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 '" << brillo::GetUndecoratedTypeName<T>()
+ << "' from variant containing '" << GetUndecoratedTypeName()
+ << "'";
+ return data_buffer_.GetData<T>();
+ }
+
+ // Returns a copy of data in Any and returns true when that data is
+ // compatible with T. Returns false if contained data is incompatible.
+ template<typename T>
+ bool GetValue(T* value) const {
+ if (!IsTypeCompatible<T>()) {
+ return false;
+ }
+ *value = Get<T>();
+ return true;
+ }
+
+ // 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 a copy of the data contained in Any.
+ // If the Any doesn't contain a compatible value, the provided default
+ // |def_val| is returned instead.
+ template<typename T>
+ 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>
+ T TryGet() const {
+ return TryGet<T>(typename std::decay<T>::type());
+ }
+
+ // Returns the undecorated name of the type contained within Any.
+ inline std::string GetUndecoratedTypeName() const {
+ return GetUndecoratedTypeNameForTag(GetTypeTagInternal());
+ }
+ // 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;
+ // Writes the contained data to D-Bus message writer, if the appropriate
+ // serialization method for contained data of the given type is provided
+ // (an appropriate specialization of AppendValueToWriter<T>() is available).
+ // Returns false if the Any is empty or if there is no serialization method
+ // defined for the contained data.
+ void AppendToDBusMessageWriter(dbus::MessageWriter* writer) const;
+
+ private:
+ // Returns a pointer to a static buffer containing type tag (sort of a type
+ // name) of the contained value.
+ const char* GetTypeTagInternal() const;
+
+ // The data buffer for contained object.
+ internal_details::Buffer data_buffer_;
+};
+
+} // namespace brillo
+
+namespace std {
+
+// Specialize std::swap() algorithm for brillo::Any class.
+inline void swap(brillo::Any& lhs, brillo::Any& rhs) {
+ lhs.Swap(rhs);
+}
+
+} // namespace std
+
+#endif // LIBBRILLO_BRILLO_ANY_H_
diff --git a/libbrillo/brillo/any_internal_impl.h b/libbrillo/brillo/any_internal_impl.h
new file mode 100644
index 0000000..9309f5d
--- /dev/null
+++ b/libbrillo/brillo/any_internal_impl.h
@@ -0,0 +1,374 @@
+// 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 brillo::Any class.
+
+#ifndef LIBBRILLO_BRILLO_ANY_INTERNAL_IMPL_H_
+#define LIBBRILLO_BRILLO_ANY_INTERNAL_IMPL_H_
+
+#include <type_traits>
+#include <typeinfo>
+#include <utility>
+
+#include <base/logging.h>
+#include <brillo/dbus/data_serialization.h>
+#include <brillo/type_name_undecorate.h>
+
+namespace brillo {
+
+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;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Provide a way to compare values of unspecified types without compiler errors
+// when no operator==() is provided for a given type. This is important to
+// allow Any class to have operator==(), yet still allowing arbitrary types
+// (not necessarily comparable) to be placed inside Any without resulting in
+// compile-time error.
+//
+// We achieve this in two ways. First, we provide a IsEqualityComparable<T>
+// class that can be used in compile-time conditions to determine if there is
+// operator==() defined that takes values of type T (or which can be implicitly
+// converted to type T). Secondly, this allows us to specialize a helper
+// compare function EqCompare<T>(v1, v2) to use operator==() for types that
+// are comparable, and just return false for those that are not.
+//
+// IsEqualityComparableHelper<T> is a helper class for implementing an
+// an STL-compatible IsEqualityComparable<T> containing a Boolean member |value|
+// which evaluates to true for comparable types and false otherwise.
+template<typename T>
+struct IsEqualityComparableHelper {
+ struct IntWrapper {
+ // A special structure that provides a constructor that takes an int.
+ // This way, an int argument passed to a function will be favored over
+ // IntWrapper when both overloads are provided.
+ // Also this constructor must NOT be explicit.
+ // NOLINTNEXTLINE(runtime/explicit)
+ // NOLINT: Allow implicit conversion from int.
+ IntWrapper(int /* dummy */) {} // do nothing, NOLINT
+ };
+
+ // Here is an obscure trick to determine if a type U has operator==().
+ // We are providing two function prototypes for TriggerFunction. One that
+ // takes an argument of type IntWrapper (which is implicitly convertible from
+ // an int), and returns an std::false_type. This is a fall-back mechanism.
+ template<typename U>
+ static std::false_type TriggerFunction(IntWrapper dummy);
+
+ // The second overload of TriggerFunction takes an int (explicitly) and
+ // returns std::true_type. If both overloads are available, this one will be
+ // chosen when referencing it as TriggerFunction(0), since it is a better
+ // (more specific) match.
+ //
+ // However this overload is available only for types that support operator==.
+ // This is achieved by employing SFINAE mechanism inside a template function
+ // overload that refers to operator==() for two values of types U&. This is
+ // used inside decltype(), so no actual code is executed. If the types
+ // are not comparable, reference to "==" would fail and the compiler will
+ // simply ignore this overload due to SFIANE.
+ //
+ // The final little trick used here is the reliance on operator comma inside
+ // the decltype() expression. The result of the expression is always
+ // std::true_type(). The expression on the left of comma is just evaluated and
+ // discarded. If it evaluates successfully (i.e. the type has operator==), the
+ // return value of the function is set to be std::true_value. If it fails,
+ // the whole function prototype is discarded and is not available in the
+ // IsEqualityComparableHelper<T> class.
+ //
+ // Here we use std::declval<U&>() to make sure we have operator==() that takes
+ // lvalue references to type U which is not necessarily default-constructible.
+ template<typename U>
+ static decltype((std::declval<U&>() == std::declval<U&>()), std::true_type())
+ TriggerFunction(int dummy);
+
+ // Finally, use the return type of the overload of TriggerFunction that
+ // matches the argument (int) to be aliased to type |type|. If T is
+ // comparable, there will be two overloads and the more specific (int) will
+ // be chosen which returns std::true_value. If the type is non-comparable,
+ // there will be only one version of TriggerFunction available which
+ // returns std::false_value.
+ using type = decltype(TriggerFunction<T>(0));
+};
+
+// IsEqualityComparable<T> is simply a class that derives from either
+// std::true_value, if type T is comparable, or from std::false_value, if the
+// type is non-comparable. We just use |type| alias from
+// IsEqualityComparableHelper<T> as the base class.
+template<typename T>
+struct IsEqualityComparable : IsEqualityComparableHelper<T>::type {};
+
+// EqCompare() overload for non-comparable types. Always returns false.
+template<typename T>
+inline typename std::enable_if<!IsEqualityComparable<T>::value, bool>::type
+EqCompare(const T& /* v1 */, const T& /* v2 */) {
+ return false;
+}
+
+// EqCompare overload for comparable types. Calls operator==(v1, v2) to compare.
+template<typename T>
+inline typename std::enable_if<IsEqualityComparable<T>::value, bool>::type
+EqCompare(const T& v1, const T& v2) {
+ return (v1 == v2);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+class Buffer; // Forward declaration of data buffer container.
+
+// Abstract base class for contained variant data.
+struct Data {
+ virtual ~Data() {}
+ // Returns the type tag (name) for the contained data.
+ virtual const char* GetTypeTag() const = 0;
+ // Copies the contained data to the output |buffer|.
+ virtual void CopyTo(Buffer* buffer) const = 0;
+ // Moves the contained data to the output |buffer|.
+ virtual void MoveTo(Buffer* buffer) = 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;
+ // Writes the contained value to the D-Bus message buffer.
+ virtual void AppendToDBusMessage(dbus::MessageWriter* writer) const = 0;
+ // Compares if the two data containers have objects of the same value.
+ virtual bool CompareEqual(const Data* other_data) const = 0;
+};
+
+// Concrete implementation of variant data of type T.
+template<typename T>
+struct TypedData : public Data {
+ explicit TypedData(const T& value) : value_(value) {}
+ // NOLINTNEXTLINE(build/c++11)
+ explicit TypedData(T&& value) : value_(std::move(value)) {}
+
+ const char* GetTypeTag() const override { return brillo::GetTypeTag<T>(); }
+ void CopyTo(Buffer* buffer) const override;
+ void MoveTo(Buffer* buffer) override;
+ bool IsConvertibleToInteger() const override {
+ return std::is_integral<T>::value || std::is_enum<T>::value;
+ }
+ intmax_t GetAsInteger() const override {
+ intmax_t int_val = 0;
+ bool converted = TryConvert(value_, &int_val);
+ CHECK(converted) << "Unable to convert value of type '"
+ << GetUndecoratedTypeName<T>() << "' to integer";
+ return int_val;
+ }
+
+ template<typename U>
+ static typename std::enable_if<dbus_utils::IsTypeSupported<U>::value>::type
+ AppendValueHelper(dbus::MessageWriter* writer, const U& value) {
+ brillo::dbus_utils::AppendValueToWriterAsVariant(writer, value);
+ }
+ template<typename U>
+ static typename std::enable_if<!dbus_utils::IsTypeSupported<U>::value>::type
+ AppendValueHelper(dbus::MessageWriter* /* writer */, const U& /* value */) {
+ LOG(FATAL) << "Type '" << GetUndecoratedTypeName<U>()
+ << "' is not supported by D-Bus";
+ }
+
+ void AppendToDBusMessage(dbus::MessageWriter* writer) const override {
+ return AppendValueHelper(writer, value_);
+ }
+
+ bool CompareEqual(const Data* other_data) const override {
+ return EqCompare<T>(value_,
+ static_cast<const TypedData<T>*>(other_data)->value_);
+ }
+
+ // Special methods to copy/move data of the same type
+ // without reallocating the buffer.
+ void FastAssign(const T& source) { value_ = source; }
+ // NOLINTNEXTLINE(build/c++11)
+ void FastAssign(T&& source) { value_ = std::move(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 final {
+ public:
+ enum StorageType { kExternal, kContained };
+ Buffer() : external_ptr_(nullptr), storage_(kExternal) {}
+ ~Buffer() { Clear(); }
+
+ Buffer(const Buffer& rhs) : Buffer() { rhs.CopyTo(this); }
+ // NOLINTNEXTLINE(build/c++11)
+ Buffer(Buffer&& rhs) : Buffer() { rhs.MoveTo(this); }
+ Buffer& operator=(const Buffer& rhs) {
+ rhs.CopyTo(this);
+ return *this;
+ }
+ // NOLINTNEXTLINE(build/c++11)
+ Buffer& operator=(Buffer&& rhs) {
+ rhs.MoveTo(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) { // NOLINT(build/c++11)
+ using Type = typename std::decay<T>::type;
+ using DataType = TypedData<Type>;
+ Data* ptr = GetDataPtr();
+ if (ptr && strcmp(ptr->GetTypeTag(), GetTypeTag<Type>()) == 0) {
+ // We assign the data to the variant container, which already
+ // has the data of the same type. Do fast copy/move with no memory
+ // reallocation.
+ DataType* typed_ptr = static_cast<DataType*>(ptr);
+ // NOLINTNEXTLINE(build/c++11)
+ typed_ptr->FastAssign(std::forward<T>(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.
+ // NOLINTNEXTLINE(build/c++11)
+ external_ptr_ = new DataType(std::forward<T>(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/move constructor.
+ // Call the constructor manually by using placement 'new'.
+ // NOLINTNEXTLINE(build/c++11)
+ new (address) DataType(std::forward<T>(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);
+ }
+ }
+
+ // Moves the data from the current buffer into the |destination|.
+ void MoveTo(Buffer* destination) {
+ if (IsEmpty()) {
+ destination->Clear();
+ } else {
+ if (storage_ == kExternal) {
+ destination->Clear();
+ destination->storage_ = kExternal;
+ destination->external_ptr_ = external_ptr_;
+ external_ptr_ = nullptr;
+ } else {
+ GetDataPtr()->MoveTo(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_);
+}
+template <typename T>
+void TypedData<T>::MoveTo(Buffer* buffer) {
+ buffer->Assign(std::move(value_));
+}
+
+} // namespace internal_details
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_ANY_INTERNAL_IMPL_H_
diff --git a/libbrillo/brillo/any_internal_impl_unittest.cc b/libbrillo/brillo/any_internal_impl_unittest.cc
new file mode 100644
index 0000000..6f7f512
--- /dev/null
+++ b/libbrillo/brillo/any_internal_impl_unittest.cc
@@ -0,0 +1,143 @@
+// 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 <brillo/any.h>
+#include <gtest/gtest.h>
+
+using brillo::internal_details::Buffer;
+using brillo::GetTypeTag;
+
+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_STREQ(GetTypeTag<int>(), buffer.GetDataPtr()->GetTypeTag());
+}
+
+TEST(Buffer, Store_Double) {
+ Buffer buffer;
+ buffer.Assign(2.3);
+ EXPECT_FALSE(buffer.IsEmpty());
+ EXPECT_EQ(Buffer::kContained, buffer.storage_);
+ EXPECT_STREQ(GetTypeTag<double>(), buffer.GetDataPtr()->GetTypeTag());
+}
+
+TEST(Buffer, Store_Pointers) {
+ Buffer buffer;
+ // nullptr
+ buffer.Assign(nullptr);
+ EXPECT_FALSE(buffer.IsEmpty());
+ EXPECT_EQ(Buffer::kContained, buffer.storage_);
+ EXPECT_STREQ(GetTypeTag<std::nullptr_t>(),
+ buffer.GetDataPtr()->GetTypeTag());
+
+ // char *
+ buffer.Assign("abcd");
+ EXPECT_FALSE(buffer.IsEmpty());
+ EXPECT_EQ(Buffer::kContained, buffer.storage_);
+ EXPECT_STREQ(GetTypeTag<const char*>(), buffer.GetDataPtr()->GetTypeTag());
+
+ // 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_STREQ(GetTypeTag<NonTrivial*>(), buffer.GetDataPtr()->GetTypeTag());
+}
+
+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_STREQ(GetTypeTag<NonTrivial>(), buffer.GetDataPtr()->GetTypeTag());
+}
+
+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_STREQ(GetTypeTag<Small>(), buffer.GetDataPtr()->GetTypeTag());
+
+ struct Large {
+ char c[20];
+ } large = {};
+ buffer.Assign(large);
+ EXPECT_FALSE(buffer.IsEmpty());
+ EXPECT_EQ(Buffer::kExternal, buffer.storage_);
+ EXPECT_STREQ(GetTypeTag<Large>(), buffer.GetDataPtr()->GetTypeTag());
+}
+
+TEST(Buffer, Copy) {
+ Buffer buffer1;
+ Buffer buffer2;
+
+ buffer1.Assign(30);
+ buffer1.CopyTo(&buffer2);
+ EXPECT_FALSE(buffer1.IsEmpty());
+ EXPECT_FALSE(buffer2.IsEmpty());
+ EXPECT_STREQ(GetTypeTag<int>(), buffer1.GetDataPtr()->GetTypeTag());
+ EXPECT_STREQ(GetTypeTag<int>(), buffer2.GetDataPtr()->GetTypeTag());
+ 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_STREQ(GetTypeTag<std::string>(), buffer1.GetDataPtr()->GetTypeTag());
+ EXPECT_STREQ(GetTypeTag<std::string>(), buffer2.GetDataPtr()->GetTypeTag());
+ EXPECT_EQ("abc", buffer1.GetData<std::string>());
+ EXPECT_EQ("abc", buffer2.GetData<std::string>());
+}
+
+TEST(Buffer, Move) {
+ // Move operations essentially leave the source object in a state that is
+ // guaranteed to be safe for reuse or destruction. There is no other explicit
+ // guarantees on the exact state of the source after move (e.g. that the
+ // source Any will be Empty after the move is complete).
+ Buffer buffer1;
+ Buffer buffer2;
+
+ buffer1.Assign(30);
+ buffer1.MoveTo(&buffer2);
+ // Contained types aren't flushed, so the source Any doesn't become empty.
+ // The contained value is just moved, but for scalars this just copies
+ // the data and any retains the actual type.
+ EXPECT_FALSE(buffer1.IsEmpty());
+ EXPECT_FALSE(buffer2.IsEmpty());
+ EXPECT_STREQ(GetTypeTag<int>(), buffer2.GetDataPtr()->GetTypeTag());
+ EXPECT_EQ(30, buffer2.GetData<int>());
+
+ buffer1.Assign(std::string("abc"));
+ buffer1.MoveTo(&buffer2);
+ // External types are moved by just moving the pointer value from src to dest.
+ // This will make the source object effectively "Empty".
+ EXPECT_TRUE(buffer1.IsEmpty());
+ EXPECT_FALSE(buffer2.IsEmpty());
+ EXPECT_STREQ(GetTypeTag<std::string>(), buffer2.GetDataPtr()->GetTypeTag());
+ EXPECT_EQ("abc", buffer2.GetData<std::string>());
+}
diff --git a/libbrillo/brillo/any_unittest.cc b/libbrillo/brillo/any_unittest.cc
new file mode 100644
index 0000000..db89884
--- /dev/null
+++ b/libbrillo/brillo/any_unittest.cc
@@ -0,0 +1,323 @@
+// 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 <brillo/any.h>
+#include <gtest/gtest.h>
+
+using brillo::Any;
+
+TEST(Any, Empty) {
+ Any val;
+ EXPECT_TRUE(val.IsEmpty());
+
+ Any val2 = val;
+ EXPECT_TRUE(val.IsEmpty());
+ EXPECT_TRUE(val2.IsEmpty());
+
+ Any val3 = std::move(val);
+ EXPECT_TRUE(val.IsEmpty());
+ EXPECT_TRUE(val3.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]);
+
+ val2 = std::move(val);
+ EXPECT_TRUE(val.IsEmpty());
+ EXPECT_TRUE(val2.IsTypeCompatible<std::vector<int>>());
+ EXPECT_EQ(3, val2.Get<std::vector<int>>().size());
+
+ val = val2;
+ EXPECT_TRUE(val.IsTypeCompatible<std::vector<int>>());
+ EXPECT_TRUE(val2.IsTypeCompatible<std::vector<int>>());
+ EXPECT_EQ(3, val.Get<std::vector<int>>().size());
+ EXPECT_EQ(3, val2.Get<std::vector<int>>().size());
+}
+
+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 'double' from variant containing "
+ "'int'");
+
+ val = std::string("123");
+ EXPECT_DEATH(val.GetAsInteger(),
+ "Unable to convert value of type 'std::.*' 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));
+}
+
+TEST(Any, Compare_Int) {
+ Any int1{12};
+ Any int2{12};
+ Any int3{20};
+ EXPECT_EQ(int1, int2);
+ EXPECT_NE(int2, int3);
+}
+
+TEST(Any, Compare_String) {
+ Any str1{std::string{"foo"}};
+ Any str2{std::string{"foo"}};
+ Any str3{std::string{"bar"}};
+ EXPECT_EQ(str1, str2);
+ EXPECT_NE(str2, str3);
+}
+
+TEST(Any, Compare_Array) {
+ Any vec1{std::vector<int>{1, 2}};
+ Any vec2{std::vector<int>{1, 2}};
+ Any vec3{std::vector<int>{1, 2, 3}};
+ EXPECT_EQ(vec1, vec2);
+ EXPECT_NE(vec2, vec3);
+}
+
+TEST(Any, Compare_Empty) {
+ Any empty1;
+ Any empty2;
+ Any int1{1};
+ EXPECT_EQ(empty1, empty2);
+ EXPECT_NE(int1, empty1);
+ EXPECT_NE(empty2, int1);
+}
+
+TEST(Any, Compare_NonComparable) {
+ struct Person {
+ std::string name;
+ int age;
+ };
+ Any person1(Person{"Jack", 40});
+ Any person2 = person1;
+ Any person3(Person{"Jill", 20});
+ EXPECT_NE(person1, person2);
+ EXPECT_NE(person1, person3);
+ EXPECT_NE(person2, person3);
+}
+
+TEST(Any, GetUndecoratedTypeName) {
+ Any val;
+ EXPECT_TRUE(val.GetUndecoratedTypeName().empty());
+
+ val = 1;
+ EXPECT_EQ(brillo::GetUndecoratedTypeName<int>(),
+ val.GetUndecoratedTypeName());
+
+ val = 3.1415926;
+ EXPECT_EQ(brillo::GetUndecoratedTypeName<double>(),
+ val.GetUndecoratedTypeName());
+
+ val = std::string("blah");
+ EXPECT_EQ(brillo::GetUndecoratedTypeName<std::string>(),
+ val.GetUndecoratedTypeName());
+}
diff --git a/libbrillo/brillo/asynchronous_signal_handler.cc b/libbrillo/brillo/asynchronous_signal_handler.cc
new file mode 100644
index 0000000..b8ec529
--- /dev/null
+++ b/libbrillo/brillo/asynchronous_signal_handler.cc
@@ -0,0 +1,108 @@
+// 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 "brillo/asynchronous_signal_handler.h"
+
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+#include <base/posix/eintr_wrapper.h>
+
+namespace {
+const int kInvalidDescriptor = -1;
+} // namespace
+
+namespace brillo {
+
+AsynchronousSignalHandler::AsynchronousSignalHandler()
+ : descriptor_(kInvalidDescriptor) {
+ CHECK_EQ(sigemptyset(&signal_mask_), 0) << "Failed to initialize signal mask";
+ CHECK_EQ(sigemptyset(&saved_signal_mask_), 0)
+ << "Failed to initialize signal mask";
+}
+
+AsynchronousSignalHandler::~AsynchronousSignalHandler() {
+ if (descriptor_ != kInvalidDescriptor) {
+ MessageLoop::current()->CancelTask(fd_watcher_task_);
+
+ if (IGNORE_EINTR(close(descriptor_)) != 0)
+ PLOG(WARNING) << "Failed to close file descriptor";
+
+ descriptor_ = kInvalidDescriptor;
+ CHECK_EQ(0, sigprocmask(SIG_SETMASK, &saved_signal_mask_, nullptr));
+ }
+}
+
+void AsynchronousSignalHandler::Init() {
+ CHECK_EQ(kInvalidDescriptor, descriptor_);
+ CHECK_EQ(0, sigprocmask(SIG_BLOCK, &signal_mask_, &saved_signal_mask_));
+ descriptor_ =
+ signalfd(descriptor_, &signal_mask_, SFD_CLOEXEC | SFD_NONBLOCK);
+ CHECK_NE(kInvalidDescriptor, descriptor_);
+ fd_watcher_task_ = MessageLoop::current()->WatchFileDescriptor(
+ FROM_HERE,
+ descriptor_,
+ MessageLoop::WatchMode::kWatchRead,
+ true,
+ base::Bind(&AsynchronousSignalHandler::OnFileCanReadWithoutBlocking,
+ base::Unretained(this)));
+ CHECK(fd_watcher_task_ != MessageLoop::kTaskIdNull)
+ << "Watching shutdown pipe failed.";
+}
+
+void AsynchronousSignalHandler::RegisterHandler(int signal,
+ const SignalHandler& callback) {
+ registered_callbacks_[signal] = callback;
+ CHECK_EQ(0, sigaddset(&signal_mask_, signal));
+ UpdateSignals();
+}
+
+void AsynchronousSignalHandler::UnregisterHandler(int signal) {
+ Callbacks::iterator callback_it = registered_callbacks_.find(signal);
+ if (callback_it != registered_callbacks_.end()) {
+ registered_callbacks_.erase(callback_it);
+ ResetSignal(signal);
+ }
+}
+
+void AsynchronousSignalHandler::OnFileCanReadWithoutBlocking() {
+ struct signalfd_siginfo info;
+ while (base::ReadFromFD(descriptor_,
+ reinterpret_cast<char*>(&info), sizeof(info))) {
+ int signal = info.ssi_signo;
+ Callbacks::iterator callback_it = registered_callbacks_.find(signal);
+ if (callback_it == registered_callbacks_.end()) {
+ LOG(WARNING) << "Unable to find a signal handler for signal: " << signal;
+ // Can happen if a signal has been called multiple time, and the callback
+ // asked to be unregistered the first time.
+ continue;
+ }
+ const SignalHandler& callback = callback_it->second;
+ bool must_unregister = callback.Run(info);
+ if (must_unregister) {
+ UnregisterHandler(signal);
+ }
+ }
+}
+
+void AsynchronousSignalHandler::ResetSignal(int signal) {
+ CHECK_EQ(0, sigdelset(&signal_mask_, signal));
+ UpdateSignals();
+}
+
+void AsynchronousSignalHandler::UpdateSignals() {
+ if (descriptor_ != kInvalidDescriptor) {
+ CHECK_EQ(0, sigprocmask(SIG_SETMASK, &saved_signal_mask_, nullptr));
+ CHECK_EQ(0, sigprocmask(SIG_BLOCK, &signal_mask_, nullptr));
+ CHECK_EQ(descriptor_,
+ signalfd(descriptor_, &signal_mask_, SFD_CLOEXEC | SFD_NONBLOCK));
+ }
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/asynchronous_signal_handler.h b/libbrillo/brillo/asynchronous_signal_handler.h
new file mode 100644
index 0000000..ceae1ff
--- /dev/null
+++ b/libbrillo/brillo/asynchronous_signal_handler.h
@@ -0,0 +1,74 @@
+// 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 LIBBRILLO_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_H_
+#define LIBBRILLO_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_H_
+
+#include <signal.h>
+#include <sys/signalfd.h>
+
+#include <map>
+
+#include <base/callback.h>
+#include <base/compiler_specific.h>
+#include <base/macros.h>
+#include <base/message_loop/message_loop.h>
+#include <brillo/asynchronous_signal_handler_interface.h>
+#include <brillo/brillo_export.h>
+#include <brillo/message_loops/message_loop.h>
+
+namespace brillo {
+// Sets up signal handlers for registered signals, and converts signal receipt
+// into a write on a pipe. Watches that pipe for data and, when some appears,
+// execute the associated callback.
+class BRILLO_EXPORT AsynchronousSignalHandler final :
+ public AsynchronousSignalHandlerInterface {
+ public:
+ AsynchronousSignalHandler();
+ ~AsynchronousSignalHandler() override;
+
+ using AsynchronousSignalHandlerInterface::SignalHandler;
+
+ // Initialize the handler.
+ void Init();
+
+ // AsynchronousSignalHandlerInterface overrides.
+ void RegisterHandler(int signal, const SignalHandler& callback) override;
+ void UnregisterHandler(int signal) override;
+
+ private:
+ // Called from the main loop when we can read from |descriptor_|, indicated
+ // that a signal was processed.
+ void OnFileCanReadWithoutBlocking();
+
+ // Controller used to manage watching of signalling pipe.
+ MessageLoop::TaskId fd_watcher_task_{MessageLoop::kTaskIdNull};
+
+ // The registered callbacks.
+ typedef std::map<int, SignalHandler> Callbacks;
+ Callbacks registered_callbacks_;
+
+ // File descriptor for accepting signals indicated by |signal_mask_|.
+ int descriptor_;
+
+ // A set of signals to be handled after the dispatcher is running.
+ sigset_t signal_mask_;
+
+ // A copy of the signal mask before the dispatcher starts, which will be
+ // used to restore to the original state when the dispatcher stops.
+ sigset_t saved_signal_mask_;
+
+ // Resets the given signal to its default behavior. Doesn't touch
+ // |registered_callbacks_|.
+ BRILLO_PRIVATE void ResetSignal(int signal);
+
+ // Updates the set of signals that this handler listens to.
+ BRILLO_PRIVATE void UpdateSignals();
+
+ DISALLOW_COPY_AND_ASSIGN(AsynchronousSignalHandler);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_H_
diff --git a/libbrillo/brillo/asynchronous_signal_handler_interface.h b/libbrillo/brillo/asynchronous_signal_handler_interface.h
new file mode 100644
index 0000000..ef0012d
--- /dev/null
+++ b/libbrillo/brillo/asynchronous_signal_handler_interface.h
@@ -0,0 +1,42 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_INTERFACE_H_
+#define LIBBRILLO_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_INTERFACE_H_
+
+#include <sys/signalfd.h>
+
+#include <base/callback.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+// Sets up signal handlers for registered signals, and converts signal receipt
+// into a write on a pipe. Watches that pipe for data and, when some appears,
+// execute the associated callback.
+class BRILLO_EXPORT AsynchronousSignalHandlerInterface {
+ public:
+ virtual ~AsynchronousSignalHandlerInterface() = default;
+
+ // The callback called when a signal is received.
+ using SignalHandler = base::Callback<bool(const struct signalfd_siginfo&)>;
+
+ // Register a new handler for the given |signal|, replacing any previously
+ // registered handler. |callback| will be called on the thread the
+ // |AsynchronousSignalHandlerInterface| implementation is bound to when a
+ // signal is received. The received |signalfd_siginfo| will be passed to
+ // |callback|. |callback| must returns |true| if the signal handler must be
+ // unregistered, and |false| otherwise. Due to an implementation detail, you
+ // cannot set any sigaction flags you might be accustomed to using. This might
+ // matter if you hoped to use SA_NOCLDSTOP to avoid getting a SIGCHLD when a
+ // child process receives a SIGSTOP.
+ virtual void RegisterHandler(int signal, const SignalHandler& callback) = 0;
+
+ // Unregister a previously registered handler for the given |signal|.
+ virtual void UnregisterHandler(int signal) = 0;
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_ASYNCHRONOUS_SIGNAL_HANDLER_INTERFACE_H_
diff --git a/libbrillo/brillo/asynchronous_signal_handler_unittest.cc b/libbrillo/brillo/asynchronous_signal_handler_unittest.cc
new file mode 100644
index 0000000..ec3b061
--- /dev/null
+++ b/libbrillo/brillo/asynchronous_signal_handler_unittest.cc
@@ -0,0 +1,138 @@
+// 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 "brillo/asynchronous_signal_handler.h"
+
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include <base/bind.h>
+#include <base/macros.h>
+#include <base/message_loop/message_loop.h>
+#include <base/run_loop.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+class AsynchronousSignalHandlerTest : public ::testing::Test {
+ public:
+ AsynchronousSignalHandlerTest() {}
+ virtual ~AsynchronousSignalHandlerTest() {}
+
+ virtual void SetUp() {
+ brillo_loop_.SetAsCurrent();
+ handler_.Init();
+ }
+
+ virtual void TearDown() {}
+
+ bool RecordInfoAndQuit(bool response, const struct signalfd_siginfo& info) {
+ infos_.push_back(info);
+ brillo_loop_.PostTask(FROM_HERE, brillo_loop_.QuitClosure());
+ return response;
+ }
+
+ protected:
+ base::MessageLoopForIO base_loop_;
+ BaseMessageLoop brillo_loop_{&base_loop_};
+ std::vector<struct signalfd_siginfo> infos_;
+ AsynchronousSignalHandler handler_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AsynchronousSignalHandlerTest);
+};
+
+TEST_F(AsynchronousSignalHandlerTest, CheckTerm) {
+ handler_.RegisterHandler(
+ SIGTERM,
+ base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit,
+ base::Unretained(this),
+ true));
+ EXPECT_EQ(0, infos_.size());
+ EXPECT_EQ(0, kill(getpid(), SIGTERM));
+
+ // Spin the message loop.
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, infos_.size());
+ EXPECT_EQ(SIGTERM, infos_[0].ssi_signo);
+}
+
+TEST_F(AsynchronousSignalHandlerTest, CheckSignalUnregistration) {
+ handler_.RegisterHandler(
+ SIGCHLD,
+ base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit,
+ base::Unretained(this),
+ true));
+ EXPECT_EQ(0, infos_.size());
+ EXPECT_EQ(0, kill(getpid(), SIGCHLD));
+
+ // Spin the message loop.
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, infos_.size());
+ EXPECT_EQ(SIGCHLD, infos_[0].ssi_signo);
+
+ EXPECT_EQ(0, kill(getpid(), SIGCHLD));
+
+ // Run the loop with a timeout, as no message are expected.
+ brillo_loop_.PostDelayedTask(FROM_HERE,
+ base::Bind(&MessageLoop::BreakLoop,
+ base::Unretained(&brillo_loop_)),
+ base::TimeDelta::FromMilliseconds(10));
+ MessageLoop::current()->Run();
+
+ // The signal handle should have been unregistered. No new message are
+ // expected.
+ EXPECT_EQ(1, infos_.size());
+}
+
+TEST_F(AsynchronousSignalHandlerTest, CheckMultipleSignal) {
+ const uint8_t NB_SIGNALS = 5;
+ handler_.RegisterHandler(
+ SIGCHLD,
+ base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit,
+ base::Unretained(this),
+ false));
+ EXPECT_EQ(0, infos_.size());
+ for (int i = 0; i < NB_SIGNALS; ++i) {
+ EXPECT_EQ(0, kill(getpid(), SIGCHLD));
+
+ // Spin the message loop.
+ MessageLoop::current()->Run();
+ }
+
+ ASSERT_EQ(NB_SIGNALS, infos_.size());
+ for (int i = 0; i < NB_SIGNALS; ++i) {
+ EXPECT_EQ(SIGCHLD, infos_[i].ssi_signo);
+ }
+}
+
+TEST_F(AsynchronousSignalHandlerTest, CheckChld) {
+ handler_.RegisterHandler(
+ SIGCHLD,
+ base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit,
+ base::Unretained(this),
+ false));
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ _Exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(0, infos_.size());
+ // Spin the message loop.
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, infos_.size());
+ EXPECT_EQ(SIGCHLD, infos_[0].ssi_signo);
+ EXPECT_EQ(child_pid, infos_[0].ssi_pid);
+ EXPECT_EQ(static_cast<int>(CLD_EXITED), infos_[0].ssi_code);
+ EXPECT_EQ(EXIT_SUCCESS, infos_[0].ssi_status);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/backoff_entry.cc b/libbrillo/brillo/backoff_entry.cc
new file mode 100644
index 0000000..8b36287
--- /dev/null
+++ b/libbrillo/brillo/backoff_entry.cc
@@ -0,0 +1,167 @@
+// Copyright 2015 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 <brillo/backoff_entry.h>
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include <base/logging.h>
+#include <base/numerics/safe_math.h>
+#include <base/rand_util.h>
+
+namespace brillo {
+
+BackoffEntry::BackoffEntry(const BackoffEntry::Policy* const policy)
+ : policy_(policy) {
+ DCHECK(policy_);
+ Reset();
+}
+
+void BackoffEntry::InformOfRequest(bool succeeded) {
+ if (!succeeded) {
+ ++failure_count_;
+ exponential_backoff_release_time_ = CalculateReleaseTime();
+ } else {
+ // We slowly decay the number of times delayed instead of
+ // resetting it to 0 in order to stay stable if we receive
+ // successes interleaved between lots of failures. Note that in
+ // the normal case, the calculated release time (in the next
+ // statement) will be in the past once the method returns.
+ if (failure_count_ > 0)
+ --failure_count_;
+
+ // The reason why we are not just cutting the release time to
+ // ImplGetTimeNow() is on the one hand, it would unset a release
+ // time set by SetCustomReleaseTime and on the other we would like
+ // to push every request up to our "horizon" when dealing with
+ // multiple in-flight requests. Ex: If we send three requests and
+ // we receive 2 failures and 1 success. The success that follows
+ // those failures will not reset the release time, further
+ // requests will then need to wait the delay caused by the 2
+ // failures.
+ base::TimeDelta delay;
+ if (policy_->always_use_initial_delay)
+ delay = base::TimeDelta::FromMilliseconds(policy_->initial_delay_ms);
+ exponential_backoff_release_time_ = std::max(
+ ImplGetTimeNow() + delay, exponential_backoff_release_time_);
+ }
+}
+
+bool BackoffEntry::ShouldRejectRequest() const {
+ return exponential_backoff_release_time_ > ImplGetTimeNow();
+}
+
+base::TimeDelta BackoffEntry::GetTimeUntilRelease() const {
+ base::TimeTicks now = ImplGetTimeNow();
+ if (exponential_backoff_release_time_ <= now)
+ return base::TimeDelta();
+ return exponential_backoff_release_time_ - now;
+}
+
+base::TimeTicks BackoffEntry::GetReleaseTime() const {
+ return exponential_backoff_release_time_;
+}
+
+void BackoffEntry::SetCustomReleaseTime(const base::TimeTicks& release_time) {
+ exponential_backoff_release_time_ = release_time;
+}
+
+bool BackoffEntry::CanDiscard() const {
+ if (policy_->entry_lifetime_ms == -1)
+ return false;
+
+ base::TimeTicks now = ImplGetTimeNow();
+
+ int64_t unused_since_ms =
+ (now - exponential_backoff_release_time_).InMilliseconds();
+
+ // Release time is further than now, we are managing it.
+ if (unused_since_ms < 0)
+ return false;
+
+ if (failure_count_ > 0) {
+ // Need to keep track of failures until maximum back-off period
+ // has passed (since further failures can add to back-off).
+ return unused_since_ms >= std::max(policy_->maximum_backoff_ms,
+ policy_->entry_lifetime_ms);
+ }
+
+ // Otherwise, consider the entry is outdated if it hasn't been used for the
+ // specified lifetime period.
+ return unused_since_ms >= policy_->entry_lifetime_ms;
+}
+
+void BackoffEntry::Reset() {
+ failure_count_ = 0;
+
+ // We leave exponential_backoff_release_time_ unset, meaning 0. We could
+ // initialize to ImplGetTimeNow() but because it's a virtual method it's
+ // not safe to call in the constructor (and the constructor calls Reset()).
+ // The effects are the same, i.e. ShouldRejectRequest() will return false
+ // right after Reset().
+ exponential_backoff_release_time_ = base::TimeTicks();
+}
+
+base::TimeTicks BackoffEntry::ImplGetTimeNow() const {
+ return base::TimeTicks::Now();
+}
+
+base::TimeTicks BackoffEntry::CalculateReleaseTime() const {
+ int effective_failure_count =
+ std::max(0, failure_count_ - policy_->num_errors_to_ignore);
+
+ // If always_use_initial_delay is true, it's equivalent to
+ // the effective_failure_count always being one greater than when it's false.
+ if (policy_->always_use_initial_delay)
+ ++effective_failure_count;
+
+ if (effective_failure_count == 0) {
+ // Never reduce previously set release horizon, e.g. due to Retry-After
+ // header.
+ return std::max(ImplGetTimeNow(), exponential_backoff_release_time_);
+ }
+
+ // The delay is calculated with this formula:
+ // delay = initial_backoff * multiply_factor^(
+ // effective_failure_count - 1) * Uniform(1 - jitter_factor, 1]
+ // Note: if the failure count is too high, |delay_ms| will become infinity
+ // after the exponential calculation, and then NaN after the jitter is
+ // accounted for. Both cases are handled by using CheckedNumeric<int64_t> to
+ // perform the conversion to integers.
+ double delay_ms = policy_->initial_delay_ms;
+ delay_ms *= pow(policy_->multiply_factor, effective_failure_count - 1);
+ delay_ms -= base::RandDouble() * policy_->jitter_factor * delay_ms;
+
+ // Do overflow checking in microseconds, the internal unit of TimeTicks.
+ const int64_t kTimeTicksNowUs =
+ (ImplGetTimeNow() - base::TimeTicks()).InMicroseconds();
+ base::internal::CheckedNumeric<int64_t> calculated_release_time_us =
+ delay_ms + 0.5;
+ calculated_release_time_us *= base::Time::kMicrosecondsPerMillisecond;
+ calculated_release_time_us += kTimeTicksNowUs;
+
+ const int64_t kMaxTime = std::numeric_limits<int64_t>::max();
+ base::internal::CheckedNumeric<int64_t> maximum_release_time_us = kMaxTime;
+ if (policy_->maximum_backoff_ms >= 0) {
+ maximum_release_time_us = policy_->maximum_backoff_ms;
+ maximum_release_time_us *= base::Time::kMicrosecondsPerMillisecond;
+ maximum_release_time_us += kTimeTicksNowUs;
+ }
+
+ // Decide between maximum release time and calculated release time, accounting
+ // for overflow with both.
+ int64_t release_time_us = std::min(
+ calculated_release_time_us.ValueOrDefault(kMaxTime),
+ maximum_release_time_us.ValueOrDefault(kMaxTime));
+
+ // Never reduce previously set release horizon, e.g. due to Retry-After
+ // header.
+ return std::max(
+ base::TimeTicks() + base::TimeDelta::FromMicroseconds(release_time_us),
+ exponential_backoff_release_time_);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/backoff_entry.h b/libbrillo/brillo/backoff_entry.h
new file mode 100644
index 0000000..82cc953
--- /dev/null
+++ b/libbrillo/brillo/backoff_entry.h
@@ -0,0 +1,115 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_BACKOFF_ENTRY_H_
+#define LIBBRILLO_BRILLO_BACKOFF_ENTRY_H_
+
+#include <base/time/time.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+// Provides the core logic needed for randomized exponential back-off
+// on requests to a given resource, given a back-off policy.
+//
+// This class is largely taken from net/base/backoff_entry.h from Chromium.
+// TODO(avakulenko): Consider packaging portions of Chrome's //net functionality
+// into the current libchrome library.
+class BRILLO_EXPORT BackoffEntry {
+ public:
+ // The set of parameters that define a back-off policy.
+ struct Policy {
+ // Number of initial errors (in sequence) to ignore before applying
+ // exponential back-off rules.
+ int num_errors_to_ignore;
+
+ // Initial delay. The interpretation of this value depends on
+ // always_use_initial_delay. It's either how long we wait between
+ // requests before backoff starts, or how much we delay the first request
+ // after backoff starts.
+ int initial_delay_ms;
+
+ // Factor by which the waiting time will be multiplied.
+ double multiply_factor;
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ double jitter_factor;
+
+ // Maximum amount of time we are willing to delay our request, -1
+ // for no maximum.
+ int64_t maximum_backoff_ms;
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ int64_t entry_lifetime_ms;
+
+ // If true, we always use a delay of initial_delay_ms, even before
+ // we've seen num_errors_to_ignore errors. Otherwise, initial_delay_ms
+ // is the first delay once we start exponential backoff.
+ //
+ // So if we're ignoring 1 error, we'll see (N, N, Nm, Nm^2, ...) if true,
+ // and (0, 0, N, Nm, ...) when false, where N is initial_backoff_ms and
+ // m is multiply_factor, assuming we've already seen one success.
+ bool always_use_initial_delay;
+ };
+
+ // Lifetime of policy must enclose lifetime of BackoffEntry. The
+ // pointer must be valid but is not dereferenced during construction.
+ explicit BackoffEntry(const Policy* const policy);
+ virtual ~BackoffEntry() = default;
+
+ // Inform this item that a request for the network resource it is
+ // tracking was made, and whether it failed or succeeded.
+ void InformOfRequest(bool succeeded);
+
+ // Returns true if a request for the resource this item tracks should
+ // be rejected at the present time due to exponential back-off policy.
+ bool ShouldRejectRequest() const;
+
+ // Returns the absolute time after which this entry (given its present
+ // state) will no longer reject requests.
+ base::TimeTicks GetReleaseTime() const;
+
+ // Returns the time until a request can be sent.
+ base::TimeDelta GetTimeUntilRelease() const;
+
+ // Causes this object reject requests until the specified absolute time.
+ // This can be used to e.g. implement support for a Retry-After header.
+ void SetCustomReleaseTime(const base::TimeTicks& release_time);
+
+ // Returns true if this object has no significant state (i.e. you could
+ // just as well start with a fresh BackoffEntry object), and hasn't
+ // had for Policy::entry_lifetime_ms.
+ bool CanDiscard() const;
+
+ // Resets this entry to a fresh (as if just constructed) state.
+ void Reset();
+
+ // Returns the failure count for this entry.
+ int failure_count() const { return failure_count_; }
+
+ protected:
+ // Equivalent to TimeTicks::Now(), virtual so unit tests can override.
+ virtual base::TimeTicks ImplGetTimeNow() const;
+
+ private:
+ // Calculates when requests should again be allowed through.
+ base::TimeTicks CalculateReleaseTime() const;
+
+ // Timestamp calculated by the exponential back-off algorithm at which we are
+ // allowed to start sending requests again.
+ base::TimeTicks exponential_backoff_release_time_;
+
+ // Counts request errors; decremented on success.
+ int failure_count_;
+
+ const Policy* const policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackoffEntry);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_BACKOFF_ENTRY_H_
diff --git a/libbrillo/brillo/backoff_entry_unittest.cc b/libbrillo/brillo/backoff_entry_unittest.cc
new file mode 100644
index 0000000..dcfa0b2
--- /dev/null
+++ b/libbrillo/brillo/backoff_entry_unittest.cc
@@ -0,0 +1,311 @@
+// Copyright 2015 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 <brillo/backoff_entry.h>
+#include <gtest/gtest.h>
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace brillo {
+
+BackoffEntry::Policy base_policy = { 0, 1000, 2.0, 0.0, 20000, 2000, false };
+
+class TestBackoffEntry : public BackoffEntry {
+ public:
+ explicit TestBackoffEntry(const Policy* const policy)
+ : BackoffEntry(policy),
+ now_(TimeTicks()) {
+ // Work around initialization in constructor not picking up
+ // fake time.
+ SetCustomReleaseTime(TimeTicks());
+ }
+
+ ~TestBackoffEntry() override {}
+
+ TimeTicks ImplGetTimeNow() const override { return now_; }
+
+ void set_now(const TimeTicks& now) {
+ now_ = now;
+ }
+
+ private:
+ TimeTicks now_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestBackoffEntry);
+};
+
+TEST(BackoffEntryTest, BaseTest) {
+ TestBackoffEntry entry(&base_policy);
+ EXPECT_FALSE(entry.ShouldRejectRequest());
+ EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease());
+
+ entry.InformOfRequest(false);
+ EXPECT_TRUE(entry.ShouldRejectRequest());
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+}
+
+TEST(BackoffEntryTest, CanDiscardNeverExpires) {
+ BackoffEntry::Policy never_expires_policy = base_policy;
+ never_expires_policy.entry_lifetime_ms = -1;
+ TestBackoffEntry never_expires(&never_expires_policy);
+ EXPECT_FALSE(never_expires.CanDiscard());
+ never_expires.set_now(TimeTicks() + TimeDelta::FromDays(100));
+ EXPECT_FALSE(never_expires.CanDiscard());
+}
+
+TEST(BackoffEntryTest, CanDiscard) {
+ TestBackoffEntry entry(&base_policy);
+ // Because lifetime is non-zero, we shouldn't be able to discard yet.
+ EXPECT_FALSE(entry.CanDiscard());
+
+ // Test the "being used" case.
+ entry.InformOfRequest(false);
+ EXPECT_FALSE(entry.CanDiscard());
+
+ // Test the case where there are errors but we can time out.
+ entry.set_now(
+ entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1));
+ EXPECT_FALSE(entry.CanDiscard());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(
+ base_policy.maximum_backoff_ms + 1));
+ EXPECT_TRUE(entry.CanDiscard());
+
+ // Test the final case (no errors, dependent only on specified lifetime).
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(
+ base_policy.entry_lifetime_ms - 1));
+ entry.InformOfRequest(true);
+ EXPECT_FALSE(entry.CanDiscard());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(
+ base_policy.entry_lifetime_ms));
+ EXPECT_TRUE(entry.CanDiscard());
+}
+
+TEST(BackoffEntryTest, CanDiscardAlwaysDelay) {
+ BackoffEntry::Policy always_delay_policy = base_policy;
+ always_delay_policy.always_use_initial_delay = true;
+ always_delay_policy.entry_lifetime_ms = 0;
+
+ TestBackoffEntry entry(&always_delay_policy);
+
+ // Because lifetime is non-zero, we shouldn't be able to discard yet.
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000));
+ EXPECT_TRUE(entry.CanDiscard());
+
+ // Even with no failures, we wait until the delay before we allow discard.
+ entry.InformOfRequest(true);
+ EXPECT_FALSE(entry.CanDiscard());
+
+ // Wait until the delay expires, and we can discard the entry again.
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1000));
+ EXPECT_TRUE(entry.CanDiscard());
+}
+
+TEST(BackoffEntryTest, CanDiscardNotStored) {
+ BackoffEntry::Policy no_store_policy = base_policy;
+ no_store_policy.entry_lifetime_ms = 0;
+ TestBackoffEntry not_stored(&no_store_policy);
+ EXPECT_TRUE(not_stored.CanDiscard());
+}
+
+TEST(BackoffEntryTest, ShouldIgnoreFirstTwo) {
+ BackoffEntry::Policy lenient_policy = base_policy;
+ lenient_policy.num_errors_to_ignore = 2;
+
+ BackoffEntry entry(&lenient_policy);
+
+ entry.InformOfRequest(false);
+ EXPECT_FALSE(entry.ShouldRejectRequest());
+
+ entry.InformOfRequest(false);
+ EXPECT_FALSE(entry.ShouldRejectRequest());
+
+ entry.InformOfRequest(false);
+ EXPECT_TRUE(entry.ShouldRejectRequest());
+}
+
+TEST(BackoffEntryTest, ReleaseTimeCalculation) {
+ TestBackoffEntry entry(&base_policy);
+
+ // With zero errors, should return "now".
+ TimeTicks result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow(), result);
+
+ // 1 error.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(1000), result);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // 2 errors.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(2000), result);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+
+ // 3 errors.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease());
+
+ // 6 errors (to check it doesn't pass maximum).
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(
+ entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(20000), result);
+}
+
+TEST(BackoffEntryTest, ReleaseTimeCalculationAlwaysDelay) {
+ BackoffEntry::Policy always_delay_policy = base_policy;
+ always_delay_policy.always_use_initial_delay = true;
+ always_delay_policy.num_errors_to_ignore = 2;
+
+ TestBackoffEntry entry(&always_delay_policy);
+
+ // With previous requests, should return "now".
+ TimeTicks result = entry.GetReleaseTime();
+ EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease());
+
+ // 1 error.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // 2 errors.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // 3 errors, exponential backoff starts.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+
+ // 4 errors.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease());
+
+ // 8 errors (to check it doesn't pass maximum).
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(TimeDelta::FromMilliseconds(20000), entry.GetTimeUntilRelease());
+}
+
+TEST(BackoffEntryTest, ReleaseTimeCalculationWithJitter) {
+ for (int i = 0; i < 10; ++i) {
+ BackoffEntry::Policy jittery_policy = base_policy;
+ jittery_policy.jitter_factor = 0.2;
+
+ TestBackoffEntry entry(&jittery_policy);
+
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ TimeTicks result = entry.GetReleaseTime();
+ EXPECT_LE(
+ entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(3200), result);
+ EXPECT_GE(
+ entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result);
+ }
+}
+
+TEST(BackoffEntryTest, FailureThenSuccess) {
+ TestBackoffEntry entry(&base_policy);
+
+ // Failure count 1, establishes horizon.
+ entry.InformOfRequest(false);
+ TimeTicks release_time = entry.GetReleaseTime();
+ EXPECT_EQ(TimeTicks() + TimeDelta::FromMilliseconds(1000), release_time);
+
+ // Success, failure count 0, should not advance past
+ // the horizon that was already set.
+ entry.set_now(release_time - TimeDelta::FromMilliseconds(200));
+ entry.InformOfRequest(true);
+ EXPECT_EQ(release_time, entry.GetReleaseTime());
+
+ // Failure, failure count 1.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(release_time + TimeDelta::FromMilliseconds(800),
+ entry.GetReleaseTime());
+}
+
+TEST(BackoffEntryTest, FailureThenSuccessAlwaysDelay) {
+ BackoffEntry::Policy always_delay_policy = base_policy;
+ always_delay_policy.always_use_initial_delay = true;
+ always_delay_policy.num_errors_to_ignore = 1;
+
+ TestBackoffEntry entry(&always_delay_policy);
+
+ // Failure count 1.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // Failure count 2.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000));
+
+ // Success. We should go back to the original delay.
+ entry.InformOfRequest(true);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // Failure count reaches 2 again. We should increase the delay once more.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000));
+}
+
+TEST(BackoffEntryTest, RetainCustomHorizon) {
+ TestBackoffEntry custom(&base_policy);
+ TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3);
+ custom.SetCustomReleaseTime(custom_horizon);
+ custom.InformOfRequest(false);
+ custom.InformOfRequest(true);
+ custom.set_now(TimeTicks() + TimeDelta::FromDays(2));
+ custom.InformOfRequest(false);
+ custom.InformOfRequest(true);
+ EXPECT_EQ(custom_horizon, custom.GetReleaseTime());
+
+ // Now check that once we are at or past the custom horizon,
+ // we get normal behavior.
+ custom.set_now(TimeTicks() + TimeDelta::FromDays(3));
+ custom.InformOfRequest(false);
+ EXPECT_EQ(
+ TimeTicks() + TimeDelta::FromDays(3) + TimeDelta::FromMilliseconds(1000),
+ custom.GetReleaseTime());
+}
+
+TEST(BackoffEntryTest, RetainCustomHorizonWhenInitialErrorsIgnored) {
+ // Regression test for a bug discovered during code review.
+ BackoffEntry::Policy lenient_policy = base_policy;
+ lenient_policy.num_errors_to_ignore = 1;
+ TestBackoffEntry custom(&lenient_policy);
+ TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3);
+ custom.SetCustomReleaseTime(custom_horizon);
+ custom.InformOfRequest(false); // This must not reset the horizon.
+ EXPECT_EQ(custom_horizon, custom.GetReleaseTime());
+}
+
+TEST(BackoffEntryTest, OverflowProtection) {
+ BackoffEntry::Policy large_multiply_policy = base_policy;
+ large_multiply_policy.multiply_factor = 256;
+ TestBackoffEntry custom(&large_multiply_policy);
+
+ // Trigger enough failures such that more than 11 bits of exponent are used
+ // to represent the exponential backoff intermediate values. Given a multiply
+ // factor of 256 (2^8), 129 iterations is enough: 2^(8*(129-1)) = 2^1024.
+ for (int i = 0; i < 129; ++i) {
+ custom.set_now(custom.ImplGetTimeNow() + custom.GetTimeUntilRelease());
+ custom.InformOfRequest(false);
+ ASSERT_TRUE(custom.ShouldRejectRequest());
+ }
+
+ // Max delay should still be respected.
+ EXPECT_EQ(20000, custom.GetTimeUntilRelease().InMilliseconds());
+}
+
+} // namespace
diff --git a/libbrillo/brillo/bind_lambda.h b/libbrillo/brillo/bind_lambda.h
new file mode 100644
index 0000000..50ac095
--- /dev/null
+++ b/libbrillo/brillo/bind_lambda.h
@@ -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.
+
+#ifndef LIBBRILLO_BRILLO_BIND_LAMBDA_H_
+#define LIBBRILLO_BRILLO_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...);
+ explicit LambdaAdapter(Lambda lambda) : lambda_(lambda) {}
+ R Run(Args... args) { return lambda_(std::forward<Args>(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...);
+ explicit LambdaAdapter(Lambda lambda) : lambda_(lambda) {}
+ R Run(Args... args) { return lambda_(std::forward<Args>(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 // LIBBRILLO_BRILLO_BIND_LAMBDA_H_
diff --git a/libbrillo/brillo/binder_watcher.cc b/libbrillo/brillo/binder_watcher.cc
new file mode 100644
index 0000000..9752204
--- /dev/null
+++ b/libbrillo/brillo/binder_watcher.cc
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <brillo/binder_watcher.h>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+
+using android::IPCThreadState;
+using android::ProcessState;
+
+namespace {
+// Called from the message loop whenever the binder file descriptor is ready.
+void OnBinderReadReady() {
+ IPCThreadState::self()->handlePolledCommands();
+}
+} // namespace
+
+namespace brillo {
+
+BinderWatcher::BinderWatcher(MessageLoop* message_loop)
+ : message_loop_(message_loop) {}
+
+BinderWatcher::BinderWatcher() : message_loop_(nullptr) {}
+
+BinderWatcher::~BinderWatcher() {
+ if (task_id_ != MessageLoop::kTaskIdNull)
+ message_loop_->CancelTask(task_id_);
+}
+
+bool BinderWatcher::Init() {
+ if (!message_loop_)
+ message_loop_ = MessageLoop::current();
+ if (!message_loop_) {
+ LOG(ERROR) << "Must initialize a brillo::MessageLoop to use BinderWatcher";
+ return false;
+ }
+
+ int binder_fd = -1;
+ ProcessState::self()->setThreadPoolMaxThreadCount(0);
+ IPCThreadState::self()->disableBackgroundScheduling(true);
+ int err = IPCThreadState::self()->setupPolling(&binder_fd);
+ if (err != 0) {
+ LOG(ERROR) << "Error setting up binder polling: "
+ << logging::SystemErrorCodeToString(err);
+ return false;
+ }
+ if (binder_fd < 0) {
+ LOG(ERROR) << "Invalid binder FD " << binder_fd;
+ return false;
+ }
+ VLOG(1) << "Got binder FD " << binder_fd;
+
+ task_id_ = message_loop_->WatchFileDescriptor(
+ FROM_HERE,
+ binder_fd,
+ MessageLoop::kWatchRead,
+ true /* persistent */,
+ base::Bind(&OnBinderReadReady));
+ if (task_id_ == MessageLoop::kTaskIdNull) {
+ LOG(ERROR) << "Failed to watch binder FD";
+ return false;
+ }
+ return true;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/binder_watcher.h b/libbrillo/brillo/binder_watcher.h
new file mode 100644
index 0000000..ece999d
--- /dev/null
+++ b/libbrillo/brillo/binder_watcher.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LIBBRILLO_BRILLO_BINDER_WATCHER_H_
+#define LIBBRILLO_BRILLO_BINDER_WATCHER_H_
+
+#include <base/macros.h>
+#include <brillo/message_loops/message_loop.h>
+
+namespace brillo {
+
+// Bridge between libbinder and brillo::MessageLoop. Construct at startup to
+// make the message loop watch for binder events and pass them to libbinder.
+class BinderWatcher final {
+ public:
+ // Construct the BinderWatcher using the passed |message_loop| if not null or
+ // the current MessageLoop otherwise.
+ explicit BinderWatcher(MessageLoop* message_loop);
+ BinderWatcher();
+ ~BinderWatcher();
+
+ // Initializes the object, returning true on success.
+ bool Init();
+
+ private:
+ MessageLoop::TaskId task_id_{MessageLoop::kTaskIdNull};
+ MessageLoop* message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(BinderWatcher);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_BINDER_WATCHER_H_
diff --git a/libbrillo/brillo/brillo_export.h b/libbrillo/brillo/brillo_export.h
new file mode 100644
index 0000000..0e1f834
--- /dev/null
+++ b/libbrillo/brillo/brillo_export.h
@@ -0,0 +1,60 @@
+// 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 LIBBRILLO_BRILLO_BRILLO_EXPORT_H_
+#define LIBBRILLO_BRILLO_BRILLO_EXPORT_H_
+
+// Use BRILLO_EXPORT attribute to decorate your classes, methods and variables
+// that need to be exported out of libbrillo. By default, any symbol not
+// explicitly marked with BRILLO_EXPORT attribute is not exported.
+
+// Put BRILLO_EXPORT in front of methods or variables and in between the
+// class and the tag name:
+/*
+
+BRILLO_EXPORT void foo();
+
+class BRILLO_EXPORT Bar {
+ public:
+ void baz(); // Exported since it is a member of an exported class.
+};
+
+*/
+
+// Exporting a class automatically exports all of its members. However there are
+// no export entries for non-static member variables since they are not accessed
+// directly, but rather through "this" pointer. Class methods, type information,
+// virtual table (if any), and static member variables are exported.
+
+// Finally, template functions and template members of a class may not be
+// inlined by the compiler automatically and the out-of-line version will not
+// be exported and fail to link. Marking those inline explicitly might help.
+// Alternatively, exporting specific instantiation of the template could be
+// used with "extern template" and combining this with BRILLO_EXPORT.
+#define BRILLO_EXPORT __attribute__((__visibility__("default")))
+
+// On occasion you might need to disable exporting a particular symbol if
+// you don't want the clients to see it. For example, you can explicitly
+// hide a member of an exported class:
+/*
+
+class BRILLO_EXPORT Foo {
+ public:
+ void bar(); // Exported since it is a member of an exported class.
+
+ private:
+ BRILLO_PRIVATE void baz(); // Explicitly removed from export table.
+};
+
+*/
+
+// Note that even though a class may have a private member it doesn't mean
+// that it must not be exported, since the compiler might still need it.
+// For example, an inline public method calling a private method will not link
+// if that private method is not exported.
+// So be careful with hiding members if you don't want to deal with obscure
+// linker errors.
+#define BRILLO_PRIVATE __attribute__((__visibility__("hidden")))
+
+#endif // LIBBRILLO_BRILLO_BRILLO_EXPORT_H_
diff --git a/libbrillo/brillo/cryptohome.cc b/libbrillo/brillo/cryptohome.cc
new file mode 100644
index 0000000..49a4a88
--- /dev/null
+++ b/libbrillo/brillo/cryptohome.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "brillo/cryptohome.h"
+
+#include <openssl/sha.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+
+using base::FilePath;
+
+namespace brillo {
+namespace cryptohome {
+namespace home {
+
+const char kGuestUserName[] = "$guest";
+
+static char g_user_home_prefix[PATH_MAX] = "/home/user/";
+static char g_root_home_prefix[PATH_MAX] = "/home/root/";
+static char g_system_salt_path[PATH_MAX] = "/home/.shadow/salt";
+
+static std::string* salt = nullptr;
+
+static bool EnsureSystemSaltIsLoaded() {
+ if (salt && !salt->empty())
+ return true;
+ FilePath salt_path(g_system_salt_path);
+ int64_t file_size;
+ if (!base::GetFileSize(salt_path, &file_size)) {
+ PLOG(ERROR) << "Could not get size of system salt: " << g_system_salt_path;
+ return false;
+ }
+ if (file_size > static_cast<int64_t>(std::numeric_limits<int>::max())) {
+ LOG(ERROR) << "System salt too large: " << file_size;
+ return false;
+ }
+ std::vector<char> buf;
+ buf.resize(file_size);
+ unsigned int data_read = base::ReadFile(salt_path, buf.data(), file_size);
+ if (data_read != file_size) {
+ PLOG(ERROR) << "Could not read entire file: " << data_read
+ << " != " << file_size;
+ return false;
+ }
+
+ if (!salt)
+ salt = new std::string();
+ salt->assign(buf.data(), file_size);
+ return true;
+}
+
+std::string SanitizeUserName(const std::string& username) {
+ if (!EnsureSystemSaltIsLoaded())
+ return std::string();
+
+ unsigned char binmd[SHA_DIGEST_LENGTH];
+ std::string lowercase(username);
+ std::transform(
+ lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower);
+ SHA_CTX ctx;
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, salt->data(), salt->size());
+ SHA1_Update(&ctx, lowercase.data(), lowercase.size());
+ SHA1_Final(binmd, &ctx);
+ std::string final = base::HexEncode(binmd, sizeof(binmd));
+ // Stay compatible with CryptoLib::HexEncodeToBuffer()
+ std::transform(final.begin(), final.end(), final.begin(), ::tolower);
+ return final;
+}
+
+FilePath GetUserPathPrefix() {
+ return FilePath(g_user_home_prefix);
+}
+
+FilePath GetRootPathPrefix() {
+ return FilePath(g_root_home_prefix);
+}
+
+FilePath GetHashedUserPath(const std::string& hashed_username) {
+ return FilePath(
+ base::StringPrintf("%s%s", g_user_home_prefix, hashed_username.c_str()));
+}
+
+FilePath GetUserPath(const std::string& username) {
+ if (!EnsureSystemSaltIsLoaded())
+ return FilePath("");
+ return GetHashedUserPath(SanitizeUserName(username));
+}
+
+FilePath GetRootPath(const std::string& username) {
+ if (!EnsureSystemSaltIsLoaded())
+ return FilePath("");
+ return FilePath(base::StringPrintf(
+ "%s%s", g_root_home_prefix, SanitizeUserName(username).c_str()));
+}
+
+FilePath GetDaemonPath(const std::string& username, const std::string& daemon) {
+ if (!EnsureSystemSaltIsLoaded())
+ return FilePath("");
+ return GetRootPath(username).Append(daemon);
+}
+
+bool IsSanitizedUserName(const std::string& sanitized) {
+ std::vector<uint8_t> bytes;
+ return (sanitized.length() == 2 * SHA_DIGEST_LENGTH) &&
+ base::HexStringToBytes(sanitized, &bytes);
+}
+
+void SetUserHomePrefix(const std::string& prefix) {
+ if (prefix.length() < sizeof(g_user_home_prefix)) {
+ snprintf(
+ g_user_home_prefix, sizeof(g_user_home_prefix), "%s", prefix.c_str());
+ }
+}
+
+void SetRootHomePrefix(const std::string& prefix) {
+ if (prefix.length() < sizeof(g_root_home_prefix)) {
+ snprintf(
+ g_root_home_prefix, sizeof(g_root_home_prefix), "%s", prefix.c_str());
+ }
+}
+
+std::string* GetSystemSalt() {
+ return salt;
+}
+
+void SetSystemSalt(std::string* value) {
+ salt = value;
+}
+
+} // namespace home
+} // namespace cryptohome
+} // namespace brillo
diff --git a/libbrillo/brillo/cryptohome.h b/libbrillo/brillo/cryptohome.h
new file mode 100644
index 0000000..caca31b
--- /dev/null
+++ b/libbrillo/brillo/cryptohome.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_BRILLO_CRYPTOHOME_H_
+#define LIBBRILLO_BRILLO_CRYPTOHOME_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+namespace cryptohome {
+namespace home {
+
+BRILLO_EXPORT extern const char kGuestUserName[];
+
+// Returns the common prefix under which the mount points for user homes are
+// created.
+BRILLO_EXPORT base::FilePath GetUserPathPrefix();
+
+// Returns the common prefix under which the mount points for root homes are
+// created.
+BRILLO_EXPORT base::FilePath GetRootPathPrefix();
+
+// Returns the path at which the user home for |username| will be mounted.
+// Returns "" for failures.
+BRILLO_EXPORT base::FilePath GetUserPath(const std::string& username);
+
+// Returns the path at which the user home for |hashed_username| will be
+// mounted. Useful when you already have the username hashed.
+// Returns "" for failures.
+BRILLO_EXPORT base::FilePath GetHashedUserPath(
+ const std::string& hashed_username);
+
+// Returns the path at which the root home for |username| will be mounted.
+// Returns "" for failures.
+BRILLO_EXPORT base::FilePath GetRootPath(const std::string& username);
+
+// Returns the path at which the daemon |daemon| should store per-user data.
+BRILLO_EXPORT base::FilePath GetDaemonPath(const std::string& username,
+ const std::string& daemon);
+
+// Checks whether |sanitized| has the format of a sanitized username.
+BRILLO_EXPORT bool IsSanitizedUserName(const std::string& sanitized);
+
+// Returns a sanitized form of |username|. For x != y, SanitizeUserName(x) !=
+// SanitizeUserName(y).
+BRILLO_EXPORT std::string SanitizeUserName(const std::string& username);
+
+// Overrides the common prefix under which the mount points for user homes are
+// created. This is used for testing only.
+BRILLO_EXPORT void SetUserHomePrefix(const std::string& prefix);
+
+// Overrides the common prefix under which the mount points for root homes are
+// created. This is used for testing only.
+BRILLO_EXPORT void SetRootHomePrefix(const std::string& prefix);
+
+// Overrides the contents of the system salt.
+// salt should be non-NULL and non-empty when attempting to avoid filesystem
+// usage in tests.
+// Note:
+// (1) Never mix usage with SetSystemSaltPath().
+// (2) Ownership of the pointer stays with the caller.
+BRILLO_EXPORT void SetSystemSalt(std::string* salt);
+
+// Returns the system salt.
+BRILLO_EXPORT std::string* GetSystemSalt();
+
+} // namespace home
+} // namespace cryptohome
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_CRYPTOHOME_H_
diff --git a/libbrillo/brillo/daemons/daemon.cc b/libbrillo/brillo/daemons/daemon.cc
new file mode 100644
index 0000000..7c7e996
--- /dev/null
+++ b/libbrillo/brillo/daemons/daemon.cc
@@ -0,0 +1,92 @@
+// 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 <brillo/daemons/daemon.h>
+
+#include <sysexits.h>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/run_loop.h>
+
+namespace brillo {
+
+Daemon::Daemon() : exit_code_{EX_OK} {
+ message_loop_.SetAsCurrent();
+}
+
+Daemon::~Daemon() {
+}
+
+int Daemon::Run() {
+ int exit_code = OnInit();
+ if (exit_code != EX_OK)
+ return exit_code;
+
+ message_loop_.Run();
+
+ OnShutdown(&exit_code_);
+
+ // base::RunLoop::QuitClosure() causes the message loop to quit
+ // immediately, even if pending tasks are still queued.
+ // Run a secondary loop to make sure all those are processed.
+ // This becomes important when working with D-Bus since dbus::Bus does
+ // a bunch of clean-up tasks asynchronously when shutting down.
+ while (message_loop_.RunOnce(false /* may_block */)) {}
+
+ return exit_code_;
+}
+
+void Daemon::Quit() { QuitWithExitCode(EX_OK); }
+
+void Daemon::QuitWithExitCode(int exit_code) {
+ exit_code_ = exit_code;
+ message_loop_.PostTask(FROM_HERE, QuitClosure());
+}
+
+void Daemon::RegisterHandler(
+ int signal,
+ const AsynchronousSignalHandlerInterface::SignalHandler& callback) {
+ async_signal_handler_.RegisterHandler(signal, callback);
+}
+
+void Daemon::UnregisterHandler(int signal) {
+ async_signal_handler_.UnregisterHandler(signal);
+}
+
+int Daemon::OnInit() {
+ async_signal_handler_.Init();
+ for (int signal : {SIGTERM, SIGINT}) {
+ async_signal_handler_.RegisterHandler(
+ signal, base::Bind(&Daemon::Shutdown, base::Unretained(this)));
+ }
+ async_signal_handler_.RegisterHandler(
+ SIGHUP, base::Bind(&Daemon::Restart, base::Unretained(this)));
+ return EX_OK;
+}
+
+void Daemon::OnShutdown(int* /* exit_code */) {
+ // Do nothing.
+}
+
+bool Daemon::OnRestart() {
+ // Not handled.
+ return false; // Returning false will shut down the daemon instead.
+}
+
+bool Daemon::Shutdown(const signalfd_siginfo& /* info */) {
+ Quit();
+ return true; // Unregister the signal handler.
+}
+
+bool Daemon::Restart(const signalfd_siginfo& /* info */) {
+ if (OnRestart())
+ return false; // Keep listening to the signal.
+ Quit();
+ return true; // Unregister the signal handler.
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/daemons/daemon.h b/libbrillo/brillo/daemons/daemon.h
new file mode 100644
index 0000000..8a11f0f
--- /dev/null
+++ b/libbrillo/brillo/daemons/daemon.h
@@ -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.
+
+#ifndef LIBBRILLO_BRILLO_DAEMONS_DAEMON_H_
+#define LIBBRILLO_BRILLO_DAEMONS_DAEMON_H_
+
+#include <string>
+
+#include <base/at_exit.h>
+#include <base/macros.h>
+#include <base/message_loop/message_loop.h>
+#include <brillo/asynchronous_signal_handler.h>
+#include <brillo/brillo_export.h>
+#include <brillo/message_loops/base_message_loop.h>
+
+struct signalfd_siginfo;
+
+namespace brillo {
+
+// Daemon is a simple base class for system daemons. It provides a lot
+// of useful facilities such as a message loop, handling of SIGTERM, SIGINT, and
+// SIGHUP system signals.
+// You can use this class directly to implement your daemon or you can
+// specialize it by creating your own class and deriving it from
+// brillo::Daemon. Override some of the virtual methods provide to fine-tune
+// its behavior to suit your daemon's needs.
+class BRILLO_EXPORT Daemon : public AsynchronousSignalHandlerInterface {
+ public:
+ Daemon();
+ virtual ~Daemon();
+
+ // Performs proper initialization of the daemon and runs the message loop.
+ // Blocks until the daemon is finished. The return value is the error
+ // code that should be returned from daemon's main(). Returns EX_OK (0) on
+ // success.
+ virtual int Run();
+
+ // Can be used by call-backs to trigger shut-down of a running message loop.
+ // Calls QuiteWithExitCode(EX_OK);
+ // WARNING: This method (as well as QuitWithExitCode) can only be called when
+ // the message loop is running (that is, during Daemon::Run() call). Calling
+ // these methods before (e.g. during OnInit()) or after (e.g in OnShutdown())
+ // will lead to abnormal process termination.
+ void Quit();
+
+ // |exit_code| is the status code to be returned when the daemon process
+ // quits. See the warning for Quit() above regarding the allowed scope for
+ // this method.
+ void QuitWithExitCode(int exit_code);
+
+ // AsynchronousSignalHandlerInterface overrides.
+ // Register/unregister custom signal handlers for the daemon. The semantics
+ // are identical to AsynchronousSignalHandler::RegisterHandler and
+ // AsynchronousSignalHandler::UnregisterHandler, except that handlers for
+ // SIGTERM, SIGINT, and SIGHUP cannot be modified.
+ void RegisterHandler(
+ int signal, const
+ AsynchronousSignalHandlerInterface::SignalHandler& callback) override;
+ void UnregisterHandler(int signal) override;
+
+ protected:
+ // Overload to provide your own initialization code that should happen just
+ // before running the message loop. Return EX_OK (0) on success or any other
+ // non-zero error codes. If an error is returned, the message loop execution
+ // is aborted and Daemon::Run() exits early.
+ // When overloading, make sure you call the base implementation of OnInit().
+ virtual int OnInit();
+ // Called when the message loops exits and before Daemon::Run() returns.
+ // Overload to clean up the data that was set up during OnInit().
+ // |return_code| contains the current error code that will be returned from
+ // Run(). You can override this value with your own error code if needed.
+ // When overloading, make sure you call the base implementation of
+ // OnShutdown().
+ virtual void OnShutdown(int* exit_code);
+ // Called when the SIGHUP signal is received. In response to this call, your
+ // daemon could reset/reload the configuration and re-initialize its state
+ // as if the process has been reloaded.
+ // Return true if the signal was processed successfully and the daemon
+ // reset its configuration. Returning false will force the daemon to
+ // quit (and subsequently relaunched by an upstart job, if one is configured).
+ // The default implementation just returns false (unhandled), which terminates
+ // the daemon, so do not call the base implementation of OnRestart() from
+ // your overload.
+ virtual bool OnRestart();
+
+ // Returns a delegate to Quit() method in the base::RunLoop instance.
+ base::Closure QuitClosure() const {
+ return message_loop_.QuitClosure();
+ }
+
+ private:
+ // Called when SIGTERM/SIGINT signals are received.
+ bool Shutdown(const signalfd_siginfo& info);
+ // Called when SIGHUP signal is received.
+ bool Restart(const signalfd_siginfo& info);
+
+ // |at_exit_manager_| must be first to make sure it is initialized before
+ // other members, especially the |message_loop_|.
+ base::AtExitManager at_exit_manager_;
+ // The brillo wrapper for the base message loop.
+ BaseMessageLoop message_loop_;
+ // A helper to dispatch signal handlers asynchronously, so that the main
+ // system signal handler returns as soon as possible.
+ AsynchronousSignalHandler async_signal_handler_;
+ // Process exit code specified in QuitWithExitCode() method call.
+ int exit_code_;
+
+ DISALLOW_COPY_AND_ASSIGN(Daemon);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DAEMONS_DAEMON_H_
diff --git a/libbrillo/brillo/daemons/dbus_daemon.cc b/libbrillo/brillo/daemons/dbus_daemon.cc
new file mode 100644
index 0000000..a48b288
--- /dev/null
+++ b/libbrillo/brillo/daemons/dbus_daemon.cc
@@ -0,0 +1,82 @@
+// 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 <brillo/daemons/dbus_daemon.h>
+
+#include <sysexits.h>
+
+#include <base/bind.h>
+#include <brillo/dbus/async_event_sequencer.h>
+#include <brillo/dbus/exported_object_manager.h>
+
+using brillo::dbus_utils::AsyncEventSequencer;
+using brillo::dbus_utils::ExportedObjectManager;
+
+namespace brillo {
+
+DBusDaemon::DBusDaemon() {
+}
+
+int DBusDaemon::OnInit() {
+ int exit_code = Daemon::OnInit();
+ if (exit_code != EX_OK)
+ return exit_code;
+
+ bus_ = dbus_connection_.Connect();
+ CHECK(bus_);
+
+ return exit_code;
+}
+
+DBusServiceDaemon::DBusServiceDaemon(const std::string& service_name)
+ : service_name_(service_name) {
+}
+
+DBusServiceDaemon::DBusServiceDaemon(
+ const std::string& service_name,
+ const dbus::ObjectPath& object_manager_path)
+ : service_name_(service_name), object_manager_path_(object_manager_path) {
+}
+
+DBusServiceDaemon::DBusServiceDaemon(const std::string& service_name,
+ base::StringPiece object_manager_path)
+ : DBusServiceDaemon(service_name,
+ dbus::ObjectPath(object_manager_path.as_string())) {
+}
+
+int DBusServiceDaemon::OnInit() {
+ int exit_code = DBusDaemon::OnInit();
+ if (exit_code != EX_OK)
+ return exit_code;
+
+ scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+ if (object_manager_path_.IsValid()) {
+ object_manager_.reset(
+ new ExportedObjectManager(bus_, object_manager_path_));
+ object_manager_->RegisterAsync(
+ sequencer->GetHandler("ObjectManager.RegisterAsync() failed.", true));
+ }
+ RegisterDBusObjectsAsync(sequencer.get());
+ sequencer->OnAllTasksCompletedCall({
+ base::Bind(&DBusServiceDaemon::TakeServiceOwnership,
+ base::Unretained(this))
+ });
+ return EX_OK;
+}
+
+void DBusServiceDaemon::RegisterDBusObjectsAsync(
+ dbus_utils::AsyncEventSequencer* /* sequencer */) {
+ // Do nothing here.
+ // Overload this method to export custom D-Bus objects at daemon startup.
+}
+
+void DBusServiceDaemon::TakeServiceOwnership(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(service_name_,
+ dbus::Bus::REQUIRE_PRIMARY))
+ << "Unable to take ownership of " << service_name_;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/daemons/dbus_daemon.h b/libbrillo/brillo/daemons/dbus_daemon.h
new file mode 100644
index 0000000..25ce306
--- /dev/null
+++ b/libbrillo/brillo/daemons/dbus_daemon.h
@@ -0,0 +1,93 @@
+// 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 LIBBRILLO_BRILLO_DAEMONS_DBUS_DAEMON_H_
+#define LIBBRILLO_BRILLO_DAEMONS_DBUS_DAEMON_H_
+
+#include <memory>
+#include <string>
+
+#include <base/strings/string_piece.h>
+#include <base/memory/ref_counted.h>
+#include <brillo/brillo_export.h>
+#include <brillo/daemons/daemon.h>
+#include <brillo/dbus/dbus_connection.h>
+#include <brillo/dbus/exported_object_manager.h>
+#include <dbus/bus.h>
+
+namespace brillo {
+
+namespace dbus_utils {
+class AsyncEventSequencer;
+} // namespace dbus_utils
+
+// DBusDaemon adds D-Bus support to Daemon.
+// Derive your daemon from this class if you want D-Bus client services in your
+// daemon (consuming other D-Bus objects). Currently uses a SYSTEM bus.
+class BRILLO_EXPORT DBusDaemon : public Daemon {
+ public:
+ DBusDaemon();
+ ~DBusDaemon() override = default;
+
+ protected:
+ // Calls the base OnInit() and then instantiates dbus::Bus and establishes
+ // a D-Bus connection.
+ int OnInit() override;
+
+ // A reference to the |dbus_connection_| bus object often used by derived
+ // classes.
+ scoped_refptr<dbus::Bus> bus_;
+
+ private:
+ DBusConnection dbus_connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(DBusDaemon);
+};
+
+// DBusServiceDaemon adds D-Bus service support to DBusDaemon.
+// Derive your daemon from this class if your daemon exposes D-Bus objects.
+// Provides an ExportedObjectManager to announce your object/interface creation
+// and destruction.
+class BRILLO_EXPORT DBusServiceDaemon : public DBusDaemon {
+ public:
+ // Constructs the daemon.
+ // |service_name| is the name of D-Bus service provided by the daemon.
+ // |object_manager_path_| is a well-known D-Bus object path for
+ // ExportedObjectManager object.
+ // If |object_manager_path_| is not specified, then ExportedObjectManager is
+ // not created and is not available as part of the D-Bus service.
+ explicit DBusServiceDaemon(const std::string& service_name);
+ DBusServiceDaemon(const std::string& service_name,
+ const dbus::ObjectPath& object_manager_path);
+ DBusServiceDaemon(const std::string& service_name,
+ base::StringPiece object_manager_path);
+
+ protected:
+ // OnInit() overload exporting D-Bus objects. Exports the contained
+ // ExportedObjectManager object and calls RegisterDBusObjectsAsync() to let
+ // you provide additional D-Bus objects.
+ int OnInit() override;
+
+ // Overload this method to export your custom D-Bus objects at startup.
+ // Objects exported in this way will finish exporting before we claim the
+ // daemon's service name on DBus.
+ virtual void RegisterDBusObjectsAsync(
+ dbus_utils::AsyncEventSequencer* sequencer);
+
+ std::string service_name_;
+ dbus::ObjectPath object_manager_path_;
+ std::unique_ptr<dbus_utils::ExportedObjectManager> object_manager_;
+
+ private:
+ // A callback that will be called when all the D-Bus objects/interfaces are
+ // exported successfully and the daemon is ready to claim the D-Bus service
+ // ownership.
+ void TakeServiceOwnership(bool success);
+
+ DISALLOW_COPY_AND_ASSIGN(DBusServiceDaemon);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DAEMONS_DBUS_DAEMON_H_
diff --git a/libbrillo/brillo/data_encoding.cc b/libbrillo/brillo/data_encoding.cc
new file mode 100644
index 0000000..f3e95f8
--- /dev/null
+++ b/libbrillo/brillo/data_encoding.cc
@@ -0,0 +1,154 @@
+// 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 <brillo/data_encoding.h>
+#include <modp_b64/modp_b64.h>
+
+#include <memory>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/strings/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;
+}
+
+// Helper for Base64Encode() and Base64EncodeWrapLines().
+std::string Base64EncodeHelper(const void* data, size_t size) {
+ std::vector<char> buffer;
+ buffer.resize(modp_b64_encode_len(size));
+ size_t out_size = modp_b64_encode(buffer.data(),
+ static_cast<const char*>(data),
+ size);
+ return std::string{buffer.begin(), buffer.begin() + out_size};
+}
+
+} // namespace
+
+/////////////////////////////////////////////////////////////////////////
+namespace brillo {
+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(brillo::string_utils::Join("=", key, value));
+ }
+
+ return brillo::string_utils::Join("&", pairs);
+}
+
+WebParamList WebParamsDecode(const std::string& data) {
+ WebParamList result;
+ std::vector<std::string> params = brillo::string_utils::Split(data, "&");
+ for (const auto& p : params) {
+ auto pair = brillo::string_utils::SplitAtFirst(p, "=");
+ result.emplace_back(UrlDecode(pair.first.c_str()),
+ UrlDecode(pair.second.c_str()));
+ }
+ return result;
+}
+
+std::string Base64Encode(const void* data, size_t size) {
+ return Base64EncodeHelper(data, size);
+}
+
+std::string Base64EncodeWrapLines(const void* data, size_t size) {
+ std::string unwrapped = Base64EncodeHelper(data, size);
+ std::string wrapped;
+
+ for (size_t i = 0; i < unwrapped.size(); i += 64) {
+ wrapped.append(unwrapped, i, 64);
+ wrapped.append("\n");
+ }
+ return wrapped;
+}
+
+bool Base64Decode(const std::string& input, brillo::Blob* output) {
+ std::string temp_buffer;
+ const std::string* data = &input;
+ if (input.find_first_of("\r\n") != std::string::npos) {
+ base::ReplaceChars(input, "\n", "", &temp_buffer);
+ base::ReplaceChars(temp_buffer, "\r", "", &temp_buffer);
+ data = &temp_buffer;
+ }
+ // base64 decoded data has 25% fewer bytes than the original (since every
+ // 3 source octets are encoded as 4 characters in base64).
+ // modp_b64_decode_len provides an upper estimate of the size of the output
+ // data.
+ output->resize(modp_b64_decode_len(data->size()));
+
+ size_t size_read = modp_b64_decode(reinterpret_cast<char*>(output->data()),
+ data->data(), data->size());
+ if (size_read == MODP_B64_ERROR) {
+ output->resize(0);
+ return false;
+ }
+ output->resize(size_read);
+
+ return true;
+}
+
+} // namespace data_encoding
+} // namespace brillo
diff --git a/libbrillo/brillo/data_encoding.h b/libbrillo/brillo/data_encoding.h
new file mode 100644
index 0000000..95d266c
--- /dev/null
+++ b/libbrillo/brillo/data_encoding.h
@@ -0,0 +1,82 @@
+// 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 LIBBRILLO_BRILLO_DATA_ENCODING_H_
+#define LIBBRILLO_BRILLO_DATA_ENCODING_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <brillo/brillo_export.h>
+#include <brillo/secure_blob.h>
+
+namespace brillo {
+namespace data_encoding {
+
+using WebParamList = std::vector<std::pair<std::string, std::string>>;
+
+// 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"
+BRILLO_EXPORT 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.
+BRILLO_EXPORT 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.
+BRILLO_EXPORT 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.
+BRILLO_EXPORT WebParamList WebParamsDecode(const std::string& data);
+
+// Encodes binary data using base64-encoding.
+BRILLO_EXPORT std::string Base64Encode(const void* data, size_t size);
+
+// Encodes binary data using base64-encoding and wraps lines at 64 character
+// boundary using LF as required by PEM (RFC 1421) specification.
+BRILLO_EXPORT std::string Base64EncodeWrapLines(const void* data, size_t size);
+
+// Decodes the input string from Base64.
+BRILLO_EXPORT bool Base64Decode(const std::string& input, brillo::Blob* output);
+
+// Helper wrappers to use std::string and brillo::Blob as binary data
+// containers.
+inline std::string Base64Encode(const brillo::Blob& input) {
+ return Base64Encode(input.data(), input.size());
+}
+inline std::string Base64EncodeWrapLines(const brillo::Blob& input) {
+ return Base64EncodeWrapLines(input.data(), input.size());
+}
+inline std::string Base64Encode(const std::string& input) {
+ return Base64Encode(input.data(), input.size());
+}
+inline std::string Base64EncodeWrapLines(const std::string& input) {
+ return Base64EncodeWrapLines(input.data(), input.size());
+}
+inline bool Base64Decode(const std::string& input, std::string* output) {
+ brillo::Blob blob;
+ if (!Base64Decode(input, &blob))
+ return false;
+ *output = std::string{blob.begin(), blob.end()};
+ return true;
+}
+
+} // namespace data_encoding
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DATA_ENCODING_H_
diff --git a/libbrillo/brillo/data_encoding_unittest.cc b/libbrillo/brillo/data_encoding_unittest.cc
new file mode 100644
index 0000000..cb73da6
--- /dev/null
+++ b/libbrillo/brillo/data_encoding_unittest.cc
@@ -0,0 +1,147 @@
+// 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 <brillo/data_encoding.h>
+
+#include <algorithm>
+#include <numeric>
+
+#include <gtest/gtest.h>
+
+namespace brillo {
+namespace data_encoding {
+
+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);
+}
+
+TEST(data_encoding, Base64Encode) {
+ const std::string text1 = "hello world";
+ const std::string encoded1 = "aGVsbG8gd29ybGQ=";
+
+ const std::string text2 =
+ "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque "
+ "molestie commodo. Viverra tincidunt integer erat ipsum, integer "
+ "molestie, arcu in, sit mauris ac a sed sit etiam.";
+ const std::string encoded2 =
+ "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBhbGlxdWF"
+ "tLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRpbmNpZHVudCBpbn"
+ "RlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFyY3UgaW4sIHNpdCBtYXVya"
+ "XMgYWMgYSBzZWQgc2l0IGV0aWFtLg==";
+
+ brillo::Blob data3(256);
+ std::iota(data3.begin(), data3.end(), 0); // Fills the buffer with 0x00-0xFF.
+ const std::string encoded3 =
+ "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ"
+ "1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaW"
+ "prbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en"
+ "6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU"
+ "1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
+
+ EXPECT_EQ(encoded1, Base64Encode(text1));
+ EXPECT_EQ(encoded2, Base64Encode(text2));
+ EXPECT_EQ(encoded3, Base64Encode(data3));
+}
+
+TEST(data_encoding, Base64EncodeWrapLines) {
+ const std::string text1 = "hello world";
+ const std::string encoded1 = "aGVsbG8gd29ybGQ=\n";
+
+ const std::string text2 =
+ "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque "
+ "molestie commodo. Viverra tincidunt integer erat ipsum, integer "
+ "molestie, arcu in, sit mauris ac a sed sit etiam.";
+ const std::string encoded2 =
+ "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBh\n"
+ "bGlxdWFtLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRp\n"
+ "bmNpZHVudCBpbnRlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFy\n"
+ "Y3UgaW4sIHNpdCBtYXVyaXMgYWMgYSBzZWQgc2l0IGV0aWFtLg==\n";
+
+ brillo::Blob data3(256);
+ std::iota(data3.begin(), data3.end(), 0); // Fills the buffer with 0x00-0xFF.
+ const std::string encoded3 =
+ "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v\n"
+ "MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f\n"
+ "YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P\n"
+ "kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/\n"
+ "wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v\n"
+ "8PHy8/T19vf4+fr7/P3+/w==\n";
+
+ EXPECT_EQ(encoded1, Base64EncodeWrapLines(text1));
+ EXPECT_EQ(encoded2, Base64EncodeWrapLines(text2));
+ EXPECT_EQ(encoded3, Base64EncodeWrapLines(data3));
+}
+
+TEST(data_encoding, Base64Decode) {
+ const std::string encoded1 = "dGVzdCBzdHJpbmc=";
+
+ const std::string encoded2 =
+ "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBh\n"
+ "bGlxdWFtLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRp\r\n"
+ "bmNpZHVudCBpbnRlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFy\r"
+ "Y3UgaW4sIHNpdCBtYXVyaXMgYWMgYSBzZWQgc2l0IGV0aWFt\n"
+ "Lg==\n\n\n";
+ const std::string decoded2 =
+ "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque "
+ "molestie commodo. Viverra tincidunt integer erat ipsum, integer "
+ "molestie, arcu in, sit mauris ac a sed sit etiam.";
+
+ const std::string encoded3 =
+ "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ"
+ "1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaW"
+ "prbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en"
+ "6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU"
+ "1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
+ brillo::Blob decoded3(256);
+ std::iota(decoded3.begin(), decoded3.end(), 0); // Fill with 0x00..0xFF.
+
+ std::string decoded;
+ EXPECT_TRUE(Base64Decode(encoded1, &decoded));
+ EXPECT_EQ("test string", decoded);
+
+ EXPECT_TRUE(Base64Decode(encoded2, &decoded));
+ EXPECT_EQ(decoded2, decoded);
+
+ brillo::Blob decoded_blob;
+ EXPECT_TRUE(Base64Decode(encoded3, &decoded_blob));
+ EXPECT_EQ(decoded3, decoded_blob);
+
+ EXPECT_FALSE(Base64Decode("A", &decoded_blob));
+ EXPECT_TRUE(decoded_blob.empty());
+
+ EXPECT_TRUE(Base64Decode("/w==", &decoded_blob));
+ EXPECT_EQ((brillo::Blob{0xFF}), decoded_blob);
+
+ EXPECT_TRUE(Base64Decode("//8=", &decoded_blob));
+ EXPECT_EQ((brillo::Blob{0xFF, 0xFF}), decoded_blob);
+
+ EXPECT_FALSE(Base64Decode("AAECAwQFB,cI", &decoded_blob));
+ EXPECT_TRUE(decoded_blob.empty());
+}
+
+} // namespace data_encoding
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/async_event_sequencer.cc b/libbrillo/brillo/dbus/async_event_sequencer.cc
new file mode 100644
index 0000000..8861e21
--- /dev/null
+++ b/libbrillo/brillo/dbus/async_event_sequencer.cc
@@ -0,0 +1,131 @@
+// 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 <brillo/dbus/async_event_sequencer.h>
+
+namespace brillo {
+
+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();
+}
+void DoNothing(bool /* success */) {
+}
+} // namespace
+
+AsyncEventSequencer::CompletionAction AsyncEventSequencer::WrapCompletionTask(
+ const CompletionTask& task) {
+ return base::Bind(&IgnoreSuccess, task);
+}
+
+AsyncEventSequencer::CompletionAction
+AsyncEventSequencer::GetDefaultCompletionAction() {
+ return base::Bind(&DoNothing);
+}
+
+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(1U, 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 brillo
diff --git a/libbrillo/brillo/dbus/async_event_sequencer.h b/libbrillo/brillo/dbus/async_event_sequencer.h
new file mode 100644
index 0000000..c817b55
--- /dev/null
+++ b/libbrillo/brillo/dbus/async_event_sequencer.h
@@ -0,0 +1,113 @@
+// 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 LIBBRILLO_BRILLO_DBUS_ASYNC_EVENT_SEQUENCER_H_
+#define LIBBRILLO_BRILLO_DBUS_ASYNC_EVENT_SEQUENCER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+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 BRILLO_EXPORT AsyncEventSequencer
+ : public base::RefCounted<AsyncEventSequencer> {
+ public:
+ using Handler = base::Callback<void(bool success)>;
+ using ExportHandler = base::Callback<void(const std::string& interface_name,
+ const std::string& method_name,
+ bool success)>;
+ using CompletionAction = base::Callback<void(bool all_succeeded)>;
+ using CompletionTask = base::Callback<void(void)>;
+
+ 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.
+ static CompletionAction WrapCompletionTask(const CompletionTask& task);
+ // Create a default CompletionAction that doesn't do anything when called.
+ static CompletionAction GetDefaultCompletionAction();
+
+ 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.
+ BRILLO_PRIVATE void HandleFinish(int registration_number,
+ const std::string& error_message,
+ bool failure_is_fatal,
+ bool success);
+ // Similar to HandleFinish.
+ BRILLO_PRIVATE 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);
+ BRILLO_PRIVATE void RetireRegistration(int registration_number);
+ BRILLO_PRIVATE void CheckForFailure(bool failure_is_fatal,
+ bool success,
+ const std::string& error_message);
+ BRILLO_PRIVATE 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 brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_ASYNC_EVENT_SEQUENCER_H_
diff --git a/libbrillo/brillo/dbus/async_event_sequencer_unittest.cc b/libbrillo/brillo/dbus/async_event_sequencer_unittest.cc
new file mode 100644
index 0000000..5f4c0e2
--- /dev/null
+++ b/libbrillo/brillo/dbus/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 <brillo/dbus/async_event_sequencer.h>
+
+#include <base/bind_helpers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+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 brillo
diff --git a/libbrillo/brillo/dbus/data_serialization.cc b/libbrillo/brillo/dbus/data_serialization.cc
new file mode 100644
index 0000000..86d1c63
--- /dev/null
+++ b/libbrillo/brillo/dbus/data_serialization.cc
@@ -0,0 +1,324 @@
+// 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 <brillo/dbus/data_serialization.h>
+
+#include <base/logging.h>
+#include <brillo/any.h>
+#include <brillo/variant_dictionary.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+void AppendValueToWriter(dbus::MessageWriter* writer, bool value) {
+ writer->AppendBool(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer, uint8_t value) {
+ writer->AppendByte(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer, int16_t value) {
+ writer->AppendInt16(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer, uint16_t value) {
+ writer->AppendUint16(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer, int32_t value) {
+ writer->AppendInt32(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer, uint32_t value) {
+ writer->AppendUint32(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer, int64_t value) {
+ writer->AppendInt64(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer, uint64_t value) {
+ writer->AppendUint64(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer, double value) {
+ writer->AppendDouble(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer,
+ const std::string& value) {
+ writer->AppendString(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer, const char* value) {
+ AppendValueToWriter(writer, std::string(value));
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer,
+ const dbus::ObjectPath& value) {
+ writer->AppendObjectPath(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer,
+ const dbus::FileDescriptor& value) {
+ writer->AppendFileDescriptor(value);
+}
+
+void AppendValueToWriter(dbus::MessageWriter* writer,
+ const brillo::Any& value) {
+ value.AppendToDBusMessageWriter(writer);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PopValueFromReader(dbus::MessageReader* reader, bool* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopBool(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader, uint8_t* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopByte(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader, int16_t* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopInt16(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader, uint16_t* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopUint16(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader, int32_t* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopInt32(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader, uint32_t* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopUint32(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader, int64_t* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopInt64(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader, uint64_t* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopUint64(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader, double* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopDouble(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader, std::string* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopString(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader, dbus::ObjectPath* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ return details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopObjectPath(value);
+}
+
+bool PopValueFromReader(dbus::MessageReader* reader,
+ dbus::FileDescriptor* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ bool ok = details::DescendIntoVariantIfPresent(&reader, &variant_reader) &&
+ reader->PopFileDescriptor(value);
+ if (ok)
+ value->CheckValidity();
+ return ok;
+}
+
+namespace {
+
+// Helper methods for PopValueFromReader(dbus::MessageReader*, Any*)
+// implementation. Pops a value of particular type from |reader| and assigns
+// it to |value| of type Any.
+template<typename T>
+bool PopTypedValueFromReader(dbus::MessageReader* reader,
+ brillo::Any* value) {
+ T data{};
+ if (!PopValueFromReader(reader, &data))
+ return false;
+ *value = std::move(data);
+ return true;
+}
+
+// std::vector<T> overload.
+template<typename T>
+bool PopTypedArrayFromReader(dbus::MessageReader* reader,
+ brillo::Any* value) {
+ return PopTypedValueFromReader<std::vector<T>>(reader, value);
+}
+
+// std::map<KEY, VALUE> overload.
+template<typename KEY, typename VALUE>
+bool PopTypedMapFromReader(dbus::MessageReader* reader, brillo::Any* value) {
+ return PopTypedValueFromReader<std::map<KEY, VALUE>>(reader, value);
+}
+
+// Helper methods for reading common ARRAY signatures into a Variant.
+// Note that only common types are supported. If an additional specific
+// type signature is required, feel free to add support for it.
+bool PopArrayValueFromReader(dbus::MessageReader* reader,
+ brillo::Any* value) {
+ std::string signature = reader->GetDataSignature();
+ if (signature == "ab")
+ return PopTypedArrayFromReader<bool>(reader, value);
+ else if (signature == "ay")
+ return PopTypedArrayFromReader<uint8_t>(reader, value);
+ else if (signature == "an")
+ return PopTypedArrayFromReader<int16_t>(reader, value);
+ else if (signature == "aq")
+ return PopTypedArrayFromReader<uint16_t>(reader, value);
+ else if (signature == "ai")
+ return PopTypedArrayFromReader<int32_t>(reader, value);
+ else if (signature == "au")
+ return PopTypedArrayFromReader<uint32_t>(reader, value);
+ else if (signature == "ax")
+ return PopTypedArrayFromReader<int64_t>(reader, value);
+ else if (signature == "at")
+ return PopTypedArrayFromReader<uint64_t>(reader, value);
+ else if (signature == "ad")
+ return PopTypedArrayFromReader<double>(reader, value);
+ else if (signature == "as")
+ return PopTypedArrayFromReader<std::string>(reader, value);
+ else if (signature == "ao")
+ return PopTypedArrayFromReader<dbus::ObjectPath>(reader, value);
+ else if (signature == "av")
+ return PopTypedArrayFromReader<brillo::Any>(reader, value);
+ else if (signature == "a{ss}")
+ return PopTypedMapFromReader<std::string, std::string>(reader, value);
+ else if (signature == "a{sv}")
+ return PopTypedValueFromReader<brillo::VariantDictionary>(reader, value);
+ else if (signature == "aa{ss}")
+ return PopTypedArrayFromReader<
+ std::map<std::string, std::string>>(reader, value);
+ else if (signature == "aa{sv}")
+ return PopTypedArrayFromReader<brillo::VariantDictionary>(reader, value);
+ else if (signature == "a{sa{ss}}")
+ return PopTypedMapFromReader<
+ std::string, std::map<std::string, std::string>>(reader, value);
+ else if (signature == "a{sa{sv}}")
+ return PopTypedMapFromReader<
+ std::string, brillo::VariantDictionary>(reader, value);
+ else if (signature == "a{say}")
+ return PopTypedMapFromReader<
+ std::string, std::vector<uint8_t>>(reader, value);
+ else if (signature == "a{uv}")
+ return PopTypedMapFromReader<uint32_t, brillo::Any>(reader, value);
+ else if (signature == "a(su)")
+ return PopTypedArrayFromReader<
+ std::tuple<std::string, uint32_t>>(reader, value);
+ else if (signature == "a{uu}")
+ return PopTypedMapFromReader<uint32_t, uint32_t>(reader, value);
+ else if (signature == "a(uu)")
+ return PopTypedArrayFromReader<
+ std::tuple<uint32_t, uint32_t>>(reader, value);
+
+ // When a use case for particular array signature is found, feel free
+ // to add handing for it here.
+ LOG(ERROR) << "Variant de-serialization of array containing data of "
+ << "type '" << signature << "' is not yet supported";
+ return false;
+}
+
+// Helper methods for reading common STRUCT signatures into a Variant.
+// Note that only common types are supported. If an additional specific
+// type signature is required, feel free to add support for it.
+bool PopStructValueFromReader(dbus::MessageReader* reader,
+ brillo::Any* value) {
+ std::string signature = reader->GetDataSignature();
+ if (signature == "(ii)")
+ return PopTypedValueFromReader<std::tuple<int, int>>(reader, value);
+ else if (signature == "(ss)")
+ return PopTypedValueFromReader<std::tuple<std::string, std::string>>(reader,
+ value);
+ else if (signature == "(ub)")
+ return PopTypedValueFromReader<std::tuple<uint32_t, bool>>(reader, value);
+ else if (signature == "(uu)")
+ return PopTypedValueFromReader<std::tuple<uint32_t, uint32_t>>(reader,
+ value);
+
+ // When a use case for particular struct signature is found, feel free
+ // to add handing for it here.
+ LOG(ERROR) << "Variant de-serialization of structs of type '" << signature
+ << "' is not yet supported";
+ return false;
+}
+
+} // anonymous namespace
+
+bool PopValueFromReader(dbus::MessageReader* reader, brillo::Any* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader))
+ return false;
+
+ switch (reader->GetDataType()) {
+ case dbus::Message::BYTE:
+ return PopTypedValueFromReader<uint8_t>(reader, value);
+ case dbus::Message::BOOL:
+ return PopTypedValueFromReader<bool>(reader, value);
+ case dbus::Message::INT16:
+ return PopTypedValueFromReader<int16_t>(reader, value);
+ case dbus::Message::UINT16:
+ return PopTypedValueFromReader<uint16_t>(reader, value);
+ case dbus::Message::INT32:
+ return PopTypedValueFromReader<int32_t>(reader, value);
+ case dbus::Message::UINT32:
+ return PopTypedValueFromReader<uint32_t>(reader, value);
+ case dbus::Message::INT64:
+ return PopTypedValueFromReader<int64_t>(reader, value);
+ case dbus::Message::UINT64:
+ return PopTypedValueFromReader<uint64_t>(reader, value);
+ case dbus::Message::DOUBLE:
+ return PopTypedValueFromReader<double>(reader, value);
+ case dbus::Message::STRING:
+ return PopTypedValueFromReader<std::string>(reader, value);
+ case dbus::Message::OBJECT_PATH:
+ return PopTypedValueFromReader<dbus::ObjectPath>(reader, value);
+ case dbus::Message::ARRAY:
+ return PopArrayValueFromReader(reader, value);
+ case dbus::Message::STRUCT:
+ return PopStructValueFromReader(reader, value);
+ case dbus::Message::DICT_ENTRY:
+ LOG(ERROR) << "Variant of DICT_ENTRY is invalid";
+ return false;
+ case dbus::Message::VARIANT:
+ LOG(ERROR) << "Variant containing a variant is invalid";
+ return false;
+ case dbus::Message::UNIX_FD:
+ CHECK(dbus::IsDBusTypeUnixFdSupported()) << "UNIX_FD data not supported";
+ // dbus::FileDescriptor is not a copyable type. Cannot be returned via
+ // brillo::Any. Fail here.
+ LOG(ERROR) << "Cannot return FileDescriptor via Any";
+ return false;
+ default:
+ LOG(FATAL) << "Unknown D-Bus data type: " << variant_reader.GetDataType();
+ return false;
+ }
+ return true;
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/data_serialization.h b/libbrillo/brillo/dbus/data_serialization.h
new file mode 100644
index 0000000..7d10fe5
--- /dev/null
+++ b/libbrillo/brillo/dbus/data_serialization.h
@@ -0,0 +1,851 @@
+// 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 LIBBRILLO_BRILLO_DBUS_DATA_SERIALIZATION_H_
+#define LIBBRILLO_BRILLO_DBUS_DATA_SERIALIZATION_H_
+
+// The main functionality provided by this header file is methods to serialize
+// native C++ data over D-Bus. This includes three major parts:
+// - Methods to get the D-Bus signature for a given C++ type:
+// std::string GetDBusSignature<T>();
+// - Methods to write arbitrary C++ data to D-Bus MessageWriter:
+// void AppendValueToWriter(dbus::MessageWriter* writer, const T& value);
+// void AppendValueToWriterAsVariant(dbus::MessageWriter*, const T&);
+// - Methods to read arbitrary C++ data from D-Bus MessageReader:
+// bool PopValueFromReader(dbus::MessageReader* reader, T* value);
+// bool PopVariantValueFromReader(dbus::MessageReader* reader, T* value);
+//
+// There are a number of overloads to handle C++ equivalents of basic D-Bus
+// types:
+// D-Bus Type | D-Bus Signature | Native C++ type
+// --------------------------------------------------
+// BYTE | y | uint8_t
+// BOOL | b | bool
+// INT16 | n | int16_t
+// UINT16 | q | uint16_t
+// INT32 | i | int32_t (int)
+// UINT32 | u | uint32_t (unsigned)
+// INT64 | x | int64_t
+// UINT64 | t | uint64_t
+// DOUBLE | d | double
+// STRING | s | std::string
+// OBJECT_PATH | o | dbus::ObjectPath
+// ARRAY | aT | std::vector<T>
+// STRUCT | (UV) | std::pair<U,V>
+// | (UVW...) | std::tuple<U,V,W,...>
+// DICT | a{KV} | std::map<K,V>
+// VARIANT | v | brillo::Any
+// UNIX_FD | h | dbus::FileDescriptor
+// SIGNATURE | g | (unsupported)
+//
+// Additional overloads/specialization can be provided for custom types.
+// In order to do that, provide overloads of AppendValueToWriter() and
+// PopValueFromReader() functions in brillo::dbus_utils namespace for the
+// CustomType. As well as a template specialization of DBusType<> for the same
+// CustomType. This specialization must provide three static functions:
+// - static std::string GetSignature();
+// - static void Write(dbus::MessageWriter* writer, const CustomType& value);
+// - static bool Read(dbus::MessageReader* reader, CustomType* value);
+// See an example in DBusUtils.CustomStruct unit test in
+// brillo/dbus/data_serialization_unittest.cc.
+
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <brillo/brillo_export.h>
+#include <brillo/type_name_undecorate.h>
+#include <dbus/message.h>
+
+namespace google {
+namespace protobuf {
+class MessageLite;
+} // namespace protobuf
+} // namespace google
+
+namespace brillo {
+
+// Forward-declare only. Can't include any.h right now because it needs
+// AppendValueToWriter() declared below.
+class Any;
+
+namespace dbus_utils {
+
+// Base class for DBusType<T> for T not supported by D-Bus. This used to
+// implement IsTypeSupported<> below.
+struct Unsupported {};
+
+// Generic definition of DBusType<T> which will be specialized for particular
+// types later.
+// The second template parameter is used only in SFINAE situations to resolve
+// class hierarchy chains for protobuf-derived classes. This type is defaulted
+// to be 'void' in all other cases and simply ignored.
+// See DBusType specialization for google::protobuf::MessageLite below for more
+// detailed information.
+template<typename T, typename = void>
+struct DBusType : public Unsupported {};
+
+// A helper type trait to determine if all of the types listed in Types... are
+// supported by D-Bus. This is a generic forward-declaration which will be
+// specialized for different type combinations.
+template<typename... Types>
+struct IsTypeSupported;
+
+// Both T and the Types... must be supported for the complete set to be
+// supported.
+template<typename T, typename... Types>
+struct IsTypeSupported<T, Types...>
+ : public std::integral_constant<
+ bool,
+ IsTypeSupported<T>::value && IsTypeSupported<Types...>::value> {};
+
+// For a single type T, check if DBusType<T> derives from Unsupported.
+// If it does, then the type is not supported by the D-Bus.
+template<typename T>
+struct IsTypeSupported<T>
+ : public std::integral_constant<
+ bool,
+ !std::is_base_of<Unsupported, DBusType<T>>::value> {};
+
+// Empty set is not supported.
+template<>
+struct IsTypeSupported<> : public std::false_type {};
+
+//----------------------------------------------------------------------------
+// AppendValueToWriter<T>(dbus::MessageWriter* writer, const T& value)
+// Write the |value| of type T to D-Bus message.
+// Explicitly delete the overloads for scalar types that are not supported by
+// D-Bus.
+void AppendValueToWriter(dbus::MessageWriter* writer, char value) = delete;
+void AppendValueToWriter(dbus::MessageWriter* writer, float value) = delete;
+
+//----------------------------------------------------------------------------
+// PopValueFromReader<T>(dbus::MessageWriter* writer, T* value)
+// Reads the |value| of type T from D-Bus message.
+// Explicitly delete the overloads for scalar types that are not supported by
+// D-Bus.
+void PopValueFromReader(dbus::MessageReader* reader, char* value) = delete;
+void PopValueFromReader(dbus::MessageReader* reader, float* value) = delete;
+
+//----------------------------------------------------------------------------
+// Get D-Bus data signature from C++ data types.
+// Specializations of a generic GetDBusSignature<T>() provide signature strings
+// for native C++ types. This function is available only for type supported
+// by D-Bus.
+template<typename T>
+inline typename std::enable_if<IsTypeSupported<T>::value, std::string>::type
+GetDBusSignature() {
+ return DBusType<T>::GetSignature();
+}
+
+namespace details {
+// Helper method used by the many overloads of PopValueFromReader().
+// If the current value in the reader is of Variant type, the method descends
+// into the Variant and updates the |*reader_ref| with the transient
+// |variant_reader| MessageReader instance passed in.
+// Returns false if it fails to descend into the Variant.
+inline bool DescendIntoVariantIfPresent(dbus::MessageReader** reader_ref,
+ dbus::MessageReader* variant_reader) {
+ if ((*reader_ref)->GetDataType() != dbus::Message::VARIANT)
+ return true;
+ if (!(*reader_ref)->PopVariant(variant_reader))
+ return false;
+ *reader_ref = variant_reader;
+ return true;
+}
+
+// Helper method to format the type string of an array.
+// Essentially it adds "a" in front of |element_signature|.
+inline std::string GetArrayDBusSignature(const std::string& element_signature) {
+ return DBUS_TYPE_ARRAY_AS_STRING + element_signature;
+}
+
+// Helper method to get a signature string for DICT_ENTRY.
+// Returns "{KV}", where "K" and "V" are the type signatures for types
+// KEY/VALUE. For example, GetDBusDictEntryType<std::string, int>() would return
+// "{si}".
+template<typename KEY, typename VALUE>
+inline std::string GetDBusDictEntryType() {
+ return DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING +
+ GetDBusSignature<KEY>() + GetDBusSignature<VALUE>() +
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING;
+}
+
+} // namespace details
+
+//=============================================================================
+// Specializations/overloads for AppendValueToWriter, PopValueFromReader and
+// DBusType<T> for various C++ types that can be serialized over D-Bus.
+
+// bool -----------------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ bool value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ bool* value);
+
+template<>
+struct DBusType<bool> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_BOOLEAN_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer, bool value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, bool* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// uint8_t --------------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ uint8_t value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ uint8_t* value);
+
+template<>
+struct DBusType<uint8_t> {
+ inline static std::string GetSignature() { return DBUS_TYPE_BYTE_AS_STRING; }
+ inline static void Write(dbus::MessageWriter* writer, uint8_t value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, uint8_t* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// int16_t --------------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ int16_t value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ int16_t* value);
+
+template<>
+struct DBusType<int16_t> {
+ inline static std::string GetSignature() { return DBUS_TYPE_INT16_AS_STRING; }
+ inline static void Write(dbus::MessageWriter* writer, int16_t value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, int16_t* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// uint16_t -------------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ uint16_t value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ uint16_t* value);
+
+template<>
+struct DBusType<uint16_t> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_UINT16_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer, uint16_t value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, uint16_t* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// int32_t --------------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ int32_t value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ int32_t* value);
+
+template<>
+struct DBusType<int32_t> {
+ inline static std::string GetSignature() { return DBUS_TYPE_INT32_AS_STRING; }
+ inline static void Write(dbus::MessageWriter* writer, int32_t value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, int32_t* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// uint32_t -------------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ uint32_t value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ uint32_t* value);
+
+template<>
+struct DBusType<uint32_t> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_UINT32_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer, uint32_t value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, uint32_t* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// int64_t --------------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ int64_t value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ int64_t* value);
+
+template<>
+struct DBusType<int64_t> {
+ inline static std::string GetSignature() { return DBUS_TYPE_INT64_AS_STRING; }
+ inline static void Write(dbus::MessageWriter* writer, int64_t value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, int64_t* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// uint64_t -------------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ uint64_t value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ uint64_t* value);
+
+template<>
+struct DBusType<uint64_t> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_UINT64_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer, uint64_t value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, uint64_t* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// double ---------------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ double value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ double* value);
+
+template<>
+struct DBusType<double> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_DOUBLE_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer, double value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, double* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// std::string ----------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ const std::string& value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ std::string* value);
+
+template<>
+struct DBusType<std::string> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_STRING_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer,
+ const std::string& value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, std::string* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// const char*
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ const char* value);
+
+template<>
+struct DBusType<const char*> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_STRING_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer, const char* value) {
+ AppendValueToWriter(writer, value);
+ }
+};
+
+// const char[]
+template<>
+struct DBusType<const char[]> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_STRING_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer, const char* value) {
+ AppendValueToWriter(writer, value);
+ }
+};
+
+// dbus::ObjectPath -----------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ const dbus::ObjectPath& value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ dbus::ObjectPath* value);
+
+template<>
+struct DBusType<dbus::ObjectPath> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_OBJECT_PATH_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer,
+ const dbus::ObjectPath& value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader,
+ dbus::ObjectPath* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// dbus::FileDescriptor -------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ const dbus::FileDescriptor& value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ dbus::FileDescriptor* value);
+
+template<>
+struct DBusType<dbus::FileDescriptor> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_UNIX_FD_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer,
+ const dbus::FileDescriptor& value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader,
+ dbus::FileDescriptor* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// brillo::Any --------------------------------------------------------------
+BRILLO_EXPORT void AppendValueToWriter(dbus::MessageWriter* writer,
+ const brillo::Any& value);
+BRILLO_EXPORT bool PopValueFromReader(dbus::MessageReader* reader,
+ brillo::Any* value);
+
+template<>
+struct DBusType<brillo::Any> {
+ inline static std::string GetSignature() {
+ return DBUS_TYPE_VARIANT_AS_STRING;
+ }
+ inline static void Write(dbus::MessageWriter* writer,
+ const brillo::Any& value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, brillo::Any* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// std::vector = D-Bus ARRAY. -------------------------------------------------
+template<typename T, typename ALLOC>
+typename std::enable_if<IsTypeSupported<T>::value>::type AppendValueToWriter(
+ dbus::MessageWriter* writer,
+ const std::vector<T, ALLOC>& value) {
+ dbus::MessageWriter array_writer(nullptr);
+ writer->OpenArray(GetDBusSignature<T>(), &array_writer);
+ for (const auto& element : value) {
+ // Use DBusType<T>::Write() instead of AppendValueToWriter() to delay
+ // binding to AppendValueToWriter() to the point of instantiation of this
+ // template.
+ DBusType<T>::Write(&array_writer, element);
+ }
+ writer->CloseContainer(&array_writer);
+}
+
+template<typename T, typename ALLOC>
+typename std::enable_if<IsTypeSupported<T>::value, bool>::type
+PopValueFromReader(dbus::MessageReader* reader, std::vector<T, ALLOC>* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ dbus::MessageReader array_reader(nullptr);
+ if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) ||
+ !reader->PopArray(&array_reader))
+ return false;
+ value->clear();
+ while (array_reader.HasMoreData()) {
+ T data;
+ // Use DBusType<T>::Read() instead of PopValueFromReader() to delay
+ // binding to PopValueFromReader() to the point of instantiation of this
+ // template.
+ if (!DBusType<T>::Read(&array_reader, &data))
+ return false;
+ value->push_back(std::move(data));
+ }
+ return true;
+}
+
+namespace details {
+// DBusArrayType<> is a helper base class for DBusType<vector<T>> that provides
+// GetSignature/Write/Read methods for T types that are supported by D-Bus
+// and not having those methods for types that are not supported by D-Bus.
+template<bool inner_type_supported, typename T, typename ALLOC>
+struct DBusArrayType {
+ // Returns "aT", where "T" is the signature string for type T.
+ inline static std::string GetSignature() {
+ return GetArrayDBusSignature(GetDBusSignature<T>());
+ }
+ inline static void Write(dbus::MessageWriter* writer,
+ const std::vector<T, ALLOC>& value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader,
+ std::vector<T, ALLOC>* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// Explicit specialization for unsupported type T.
+template<typename T, typename ALLOC>
+struct DBusArrayType<false, T, ALLOC> : public Unsupported {};
+
+} // namespace details
+
+template<typename T, typename ALLOC>
+struct DBusType<std::vector<T, ALLOC>>
+ : public details::DBusArrayType<IsTypeSupported<T>::value, T, ALLOC> {};
+
+// std::pair = D-Bus STRUCT with two elements. --------------------------------
+namespace details {
+
+// Helper class to get a D-Bus signature of a list of types.
+// For example, TupleTraits<int32_t, bool, std::string>::GetSignature() will
+// return "ibs".
+template<typename... Types>
+struct TupleTraits;
+
+template<typename FirstType, typename... RestOfTypes>
+struct TupleTraits<FirstType, RestOfTypes...> {
+ static std::string GetSignature() {
+ return GetDBusSignature<FirstType>() +
+ TupleTraits<RestOfTypes...>::GetSignature();
+ }
+};
+
+template<>
+struct TupleTraits<> {
+ static std::string GetSignature() { return std::string{}; }
+};
+
+} // namespace details
+
+template<typename... Types>
+inline std::string GetStructDBusSignature() {
+ // Returns "(T...)", where "T..." is the signature strings for types T...
+ return DBUS_STRUCT_BEGIN_CHAR_AS_STRING +
+ details::TupleTraits<Types...>::GetSignature() +
+ DBUS_STRUCT_END_CHAR_AS_STRING;
+}
+
+template<typename U, typename V>
+typename std::enable_if<IsTypeSupported<U, V>::value>::type AppendValueToWriter(
+ dbus::MessageWriter* writer,
+ const std::pair<U, V>& value) {
+ dbus::MessageWriter struct_writer(nullptr);
+ writer->OpenStruct(&struct_writer);
+ // Use DBusType<T>::Write() instead of AppendValueToWriter() to delay
+ // binding to AppendValueToWriter() to the point of instantiation of this
+ // template.
+ DBusType<U>::Write(&struct_writer, value.first);
+ DBusType<V>::Write(&struct_writer, value.second);
+ writer->CloseContainer(&struct_writer);
+}
+
+template<typename U, typename V>
+typename std::enable_if<IsTypeSupported<U, V>::value, bool>::type
+PopValueFromReader(dbus::MessageReader* reader, std::pair<U, V>* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ dbus::MessageReader struct_reader(nullptr);
+ if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) ||
+ !reader->PopStruct(&struct_reader))
+ return false;
+ // Use DBusType<T>::Read() instead of PopValueFromReader() to delay
+ // binding to PopValueFromReader() to the point of instantiation of this
+ // template.
+ return DBusType<U>::Read(&struct_reader, &value->first) &&
+ DBusType<V>::Read(&struct_reader, &value->second);
+}
+
+namespace details {
+
+// DBusArrayType<> is a helper base class for DBusType<pair<U, V>> that provides
+// GetSignature/Write/Read methods for types that are supported by D-Bus
+// and not having those methods for types that are not supported by D-Bus.
+template<bool inner_type_supported, typename U, typename V>
+struct DBusPairType {
+ // Returns "(UV)", where "U" and "V" are the signature strings for types U, V.
+ inline static std::string GetSignature() {
+ return GetStructDBusSignature<U, V>();
+ }
+ inline static void Write(dbus::MessageWriter* writer,
+ const std::pair<U, V>& value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, std::pair<U, V>* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// Either U, or V, or both are not supported by D-Bus.
+template<typename U, typename V>
+struct DBusPairType<false, U, V> : public Unsupported {};
+
+} // namespace details
+
+template<typename U, typename V>
+struct DBusType<std::pair<U, V>>
+ : public details::DBusPairType<IsTypeSupported<U, V>::value, U, V> {};
+
+// std::tuple = D-Bus STRUCT with arbitrary number of members. ----------------
+namespace details {
+
+// TupleIterator<I, N, T...> is a helper class to iterate over all the elements
+// of a tuple<T...> from index I to N. TupleIterator<>::Read and ::Write methods
+// are called for each element of the tuple and iteration continues until I == N
+// in which case the specialization for I==N below stops the recursion.
+template<size_t I, size_t N, typename... T>
+struct TupleIterator {
+ // Tuple is just a convenience alias to a tuple containing elements of type T.
+ using Tuple = std::tuple<T...>;
+ // ValueType is the type of the element at index I.
+ using ValueType = typename std::tuple_element<I, Tuple>::type;
+
+ // Write the tuple element at index I to D-Bus message.
+ static void Write(dbus::MessageWriter* writer, const Tuple& value) {
+ // Use DBusType<T>::Write() instead of AppendValueToWriter() to delay
+ // binding to AppendValueToWriter() to the point of instantiation of this
+ // template.
+ DBusType<ValueType>::Write(writer, std::get<I>(value));
+ TupleIterator<I + 1, N, T...>::Write(writer, value);
+ }
+
+ // Read the tuple element at index I from D-Bus message.
+ static bool Read(dbus::MessageReader* reader, Tuple* value) {
+ // Use DBusType<T>::Read() instead of PopValueFromReader() to delay
+ // binding to PopValueFromReader() to the point of instantiation of this
+ // template.
+ return DBusType<ValueType>::Read(reader, &std::get<I>(*value)) &&
+ TupleIterator<I + 1, N, T...>::Read(reader, value);
+ }
+};
+
+// Specialization to end the iteration when the index reaches the last element.
+template<size_t N, typename... T>
+struct TupleIterator<N, N, T...> {
+ using Tuple = std::tuple<T...>;
+ static void Write(dbus::MessageWriter* /* writer */,
+ const Tuple& /* value */) {}
+ static bool Read(dbus::MessageReader* /* reader */,
+ Tuple* /* value */) { return true; }
+};
+
+} // namespace details
+
+template<typename... T>
+typename std::enable_if<IsTypeSupported<T...>::value>::type AppendValueToWriter(
+ dbus::MessageWriter* writer,
+ const std::tuple<T...>& value) {
+ dbus::MessageWriter struct_writer(nullptr);
+ writer->OpenStruct(&struct_writer);
+ details::TupleIterator<0, sizeof...(T), T...>::Write(&struct_writer, value);
+ writer->CloseContainer(&struct_writer);
+}
+
+template<typename... T>
+typename std::enable_if<IsTypeSupported<T...>::value, bool>::type
+PopValueFromReader(dbus::MessageReader* reader, std::tuple<T...>* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ dbus::MessageReader struct_reader(nullptr);
+ if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) ||
+ !reader->PopStruct(&struct_reader))
+ return false;
+ return details::TupleIterator<0, sizeof...(T), T...>::Read(&struct_reader,
+ value);
+}
+
+namespace details {
+
+// DBusTupleType<> is a helper base class for DBusType<tuple<T...>> that
+// provides GetSignature/Write/Read methods for types that are supported by
+// D-Bus and not having those methods for types that are not supported by D-Bus.
+template<bool inner_type_supported, typename... T>
+struct DBusTupleType {
+ // Returns "(T...)", where "T..." are the signature strings for types T...
+ inline static std::string GetSignature() {
+ return GetStructDBusSignature<T...>();
+ }
+ inline static void Write(dbus::MessageWriter* writer,
+ const std::tuple<T...>& value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader,
+ std::tuple<T...>* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// Some/all of types T... are not supported by D-Bus.
+template<typename... T>
+struct DBusTupleType<false, T...> : public Unsupported {};
+
+} // namespace details
+
+template<typename... T>
+struct DBusType<std::tuple<T...>>
+ : public details::DBusTupleType<IsTypeSupported<T...>::value, T...> {};
+
+// std::map = D-Bus ARRAY of DICT_ENTRY. --------------------------------------
+template<typename KEY, typename VALUE, typename PRED, typename ALLOC>
+typename std::enable_if<IsTypeSupported<KEY, VALUE>::value>::type
+AppendValueToWriter(dbus::MessageWriter* writer,
+ const std::map<KEY, VALUE, PRED, ALLOC>& value) {
+ dbus::MessageWriter dict_writer(nullptr);
+ writer->OpenArray(details::GetDBusDictEntryType<KEY, VALUE>(), &dict_writer);
+ for (const auto& pair : value) {
+ dbus::MessageWriter entry_writer(nullptr);
+ dict_writer.OpenDictEntry(&entry_writer);
+ // Use DBusType<T>::Write() instead of AppendValueToWriter() to delay
+ // binding to AppendValueToWriter() to the point of instantiation of this
+ // template.
+ DBusType<KEY>::Write(&entry_writer, pair.first);
+ DBusType<VALUE>::Write(&entry_writer, pair.second);
+ dict_writer.CloseContainer(&entry_writer);
+ }
+ writer->CloseContainer(&dict_writer);
+}
+
+template<typename KEY, typename VALUE, typename PRED, typename ALLOC>
+typename std::enable_if<IsTypeSupported<KEY, VALUE>::value, bool>::type
+PopValueFromReader(dbus::MessageReader* reader,
+ std::map<KEY, VALUE, PRED, ALLOC>* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ dbus::MessageReader array_reader(nullptr);
+ if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) ||
+ !reader->PopArray(&array_reader))
+ return false;
+ value->clear();
+ while (array_reader.HasMoreData()) {
+ dbus::MessageReader dict_entry_reader(nullptr);
+ if (!array_reader.PopDictEntry(&dict_entry_reader))
+ return false;
+ KEY key;
+ VALUE data;
+ // Use DBusType<T>::Read() instead of PopValueFromReader() to delay
+ // binding to PopValueFromReader() to the point of instantiation of this
+ // template.
+ if (!DBusType<KEY>::Read(&dict_entry_reader, &key) ||
+ !DBusType<VALUE>::Read(&dict_entry_reader, &data))
+ return false;
+ value->emplace(std::move(key), std::move(data));
+ }
+ return true;
+}
+
+namespace details {
+
+// DBusArrayType<> is a helper base class for DBusType<map<K, V>> that provides
+// GetSignature/Write/Read methods for T types that are supported by D-Bus
+// and not having those methods for types that are not supported by D-Bus.
+template<bool inner_types_supported,
+ typename KEY,
+ typename VALUE,
+ typename PRED,
+ typename ALLOC>
+struct DBusMapType {
+ // Returns "a{KV}", where "K" and "V" are the signature strings for types
+ // KEY/VALUE.
+ inline static std::string GetSignature() {
+ return GetArrayDBusSignature(GetDBusDictEntryType<KEY, VALUE>());
+ }
+ inline static void Write(dbus::MessageWriter* writer,
+ const std::map<KEY, VALUE, PRED, ALLOC>& value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader,
+ std::map<KEY, VALUE, PRED, ALLOC>* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+// Types KEY, VALUE or both are not supported by D-Bus.
+template<typename KEY, typename VALUE, typename PRED, typename ALLOC>
+struct DBusMapType<false, KEY, VALUE, PRED, ALLOC> : public Unsupported {};
+
+} // namespace details
+
+template<typename KEY, typename VALUE, typename PRED, typename ALLOC>
+struct DBusType<std::map<KEY, VALUE, PRED, ALLOC>>
+ : public details::DBusMapType<IsTypeSupported<KEY, VALUE>::value,
+ KEY,
+ VALUE,
+ PRED,
+ ALLOC> {};
+
+//----------------------------------------------------------------------------
+// AppendValueToWriterAsVariant<T>(dbus::MessageWriter* writer, const T& value)
+// Write the |value| of type T to D-Bus message as a VARIANT.
+// This overload is provided only if T is supported by D-Bus.
+template<typename T>
+typename std::enable_if<IsTypeSupported<T>::value>::type
+AppendValueToWriterAsVariant(dbus::MessageWriter* writer, const T& value) {
+ std::string data_type = GetDBusSignature<T>();
+ dbus::MessageWriter variant_writer(nullptr);
+ writer->OpenVariant(data_type, &variant_writer);
+ // Use DBusType<T>::Write() instead of AppendValueToWriter() to delay
+ // binding to AppendValueToWriter() to the point of instantiation of this
+ // template.
+ DBusType<T>::Write(&variant_writer, value);
+ writer->CloseContainer(&variant_writer);
+}
+
+// Special case: do not allow to write a Variant containing a Variant.
+// Just redirect to normal AppendValueToWriter().
+inline void AppendValueToWriterAsVariant(dbus::MessageWriter* writer,
+ const brillo::Any& value) {
+ return AppendValueToWriter(writer, value);
+}
+
+//----------------------------------------------------------------------------
+// PopVariantValueFromReader<T>(dbus::MessageWriter* writer, T* value)
+// Reads a Variant containing the |value| of type T from D-Bus message.
+// Note that the generic PopValueFromReader<T>(...) can do this too.
+// This method is provided for two reasons:
+// 1. For API symmetry with AppendValueToWriter/AppendValueToWriterAsVariant.
+// 2. To be used when it is important to assert that the data was sent
+// specifically as a Variant.
+// This overload is provided only if T is supported by D-Bus.
+template<typename T>
+typename std::enable_if<IsTypeSupported<T>::value, bool>::type
+PopVariantValueFromReader(dbus::MessageReader* reader, T* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ if (!reader->PopVariant(&variant_reader))
+ return false;
+ // Use DBusType<T>::Read() instead of PopValueFromReader() to delay
+ // binding to PopValueFromReader() to the point of instantiation of this
+ // template.
+ return DBusType<T>::Read(&variant_reader, value);
+}
+
+// Special handling of request to read a Variant of Variant.
+inline bool PopVariantValueFromReader(dbus::MessageReader* reader, Any* value) {
+ return PopValueFromReader(reader, value);
+}
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DATA_SERIALIZATION_H_
diff --git a/libbrillo/brillo/dbus/data_serialization_unittest.cc b/libbrillo/brillo/dbus/data_serialization_unittest.cc
new file mode 100644
index 0000000..1acae60
--- /dev/null
+++ b/libbrillo/brillo/dbus/data_serialization_unittest.cc
@@ -0,0 +1,753 @@
+// 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 <brillo/dbus/data_serialization.h>
+
+#include <limits>
+
+#include <brillo/variant_dictionary.h>
+#include <gtest/gtest.h>
+
+using dbus::FileDescriptor;
+using dbus::Message;
+using dbus::MessageReader;
+using dbus::MessageWriter;
+using dbus::ObjectPath;
+using dbus::Response;
+
+namespace brillo {
+namespace dbus_utils {
+
+TEST(DBusUtils, Supported_BasicTypes) {
+ EXPECT_TRUE(IsTypeSupported<bool>::value);
+ EXPECT_TRUE(IsTypeSupported<uint8_t>::value);
+ EXPECT_TRUE(IsTypeSupported<int16_t>::value);
+ EXPECT_TRUE(IsTypeSupported<uint16_t>::value);
+ EXPECT_TRUE(IsTypeSupported<int32_t>::value);
+ EXPECT_TRUE(IsTypeSupported<uint32_t>::value);
+ EXPECT_TRUE(IsTypeSupported<int64_t>::value);
+ EXPECT_TRUE(IsTypeSupported<uint64_t>::value);
+ EXPECT_TRUE(IsTypeSupported<double>::value);
+ EXPECT_TRUE(IsTypeSupported<std::string>::value);
+ EXPECT_TRUE(IsTypeSupported<ObjectPath>::value);
+ EXPECT_TRUE(IsTypeSupported<FileDescriptor>::value);
+ EXPECT_TRUE(IsTypeSupported<Any>::value);
+}
+
+TEST(DBusUtils, Unsupported_BasicTypes) {
+ EXPECT_FALSE(IsTypeSupported<char>::value);
+ EXPECT_FALSE(IsTypeSupported<float>::value);
+}
+
+TEST(DBusUtils, Supported_ComplexTypes) {
+ EXPECT_TRUE(IsTypeSupported<std::vector<bool>>::value);
+ EXPECT_TRUE(IsTypeSupported<std::vector<uint8_t>>::value);
+ EXPECT_TRUE((IsTypeSupported<std::pair<int16_t, double>>::value));
+ EXPECT_TRUE(
+ (IsTypeSupported<std::map<uint16_t, std::vector<int64_t>>>::value));
+ EXPECT_TRUE((IsTypeSupported<std::tuple<bool, double, int32_t>>::value));
+}
+
+TEST(DBusUtils, Unsupported_ComplexTypes) {
+ EXPECT_FALSE(IsTypeSupported<std::vector<char>>::value);
+ EXPECT_FALSE((IsTypeSupported<std::pair<int16_t, float>>::value));
+ EXPECT_FALSE((IsTypeSupported<std::pair<char, int32_t>>::value));
+ EXPECT_FALSE((IsTypeSupported<std::map<int16_t, float>>::value));
+ EXPECT_FALSE((IsTypeSupported<std::map<char, int32_t>>::value));
+ EXPECT_FALSE((IsTypeSupported<std::tuple<bool, char, int32_t>>::value));
+}
+
+TEST(DBusUtils, Supported_TypeSet) {
+ EXPECT_TRUE((IsTypeSupported<int32_t, double, std::string>::value));
+ EXPECT_TRUE((IsTypeSupported<bool, std::vector<int32_t>, uint8_t>::value));
+}
+
+TEST(DBusUtils, Unupported_TypeSet) {
+ EXPECT_FALSE((IsTypeSupported<int32_t, double, std::string, char>::value));
+ EXPECT_FALSE(
+ (IsTypeSupported<bool, std::pair<std::vector<float>, uint8_t>>::value));
+ EXPECT_FALSE((IsTypeSupported<char, double, std::string, int16_t>::value));
+ EXPECT_FALSE((IsTypeSupported<char, std::vector<float>, float>::value));
+}
+
+TEST(DBusUtils, Signatures_BasicTypes) {
+ EXPECT_EQ("b", GetDBusSignature<bool>());
+ EXPECT_EQ("y", GetDBusSignature<uint8_t>());
+ EXPECT_EQ("n", GetDBusSignature<int16_t>());
+ EXPECT_EQ("q", GetDBusSignature<uint16_t>());
+ EXPECT_EQ("i", GetDBusSignature<int32_t>());
+ EXPECT_EQ("u", GetDBusSignature<uint32_t>());
+ EXPECT_EQ("x", GetDBusSignature<int64_t>());
+ EXPECT_EQ("t", GetDBusSignature<uint64_t>());
+ EXPECT_EQ("d", GetDBusSignature<double>());
+ EXPECT_EQ("s", GetDBusSignature<std::string>());
+ EXPECT_EQ("o", GetDBusSignature<ObjectPath>());
+ EXPECT_EQ("h", GetDBusSignature<FileDescriptor>());
+ EXPECT_EQ("v", GetDBusSignature<Any>());
+}
+
+TEST(DBusUtils, Signatures_Arrays) {
+ EXPECT_EQ("ab", GetDBusSignature<std::vector<bool>>());
+ EXPECT_EQ("ay", GetDBusSignature<std::vector<uint8_t>>());
+ EXPECT_EQ("an", GetDBusSignature<std::vector<int16_t>>());
+ EXPECT_EQ("aq", GetDBusSignature<std::vector<uint16_t>>());
+ EXPECT_EQ("ai", GetDBusSignature<std::vector<int32_t>>());
+ EXPECT_EQ("au", GetDBusSignature<std::vector<uint32_t>>());
+ EXPECT_EQ("ax", GetDBusSignature<std::vector<int64_t>>());
+ EXPECT_EQ("at", GetDBusSignature<std::vector<uint64_t>>());
+ EXPECT_EQ("ad", GetDBusSignature<std::vector<double>>());
+ EXPECT_EQ("as", GetDBusSignature<std::vector<std::string>>());
+ EXPECT_EQ("ao", GetDBusSignature<std::vector<ObjectPath>>());
+ EXPECT_EQ("ah", GetDBusSignature<std::vector<FileDescriptor>>());
+ EXPECT_EQ("av", GetDBusSignature<std::vector<Any>>());
+ EXPECT_EQ("a(is)",
+ (GetDBusSignature<std::vector<std::pair<int, std::string>>>()));
+ EXPECT_EQ("aad", GetDBusSignature<std::vector<std::vector<double>>>());
+}
+
+TEST(DBusUtils, Signatures_Maps) {
+ EXPECT_EQ("a{sb}", (GetDBusSignature<std::map<std::string, bool>>()));
+ EXPECT_EQ("a{ss}", (GetDBusSignature<std::map<std::string, std::string>>()));
+ EXPECT_EQ("a{sv}", (GetDBusSignature<std::map<std::string, Any>>()));
+ EXPECT_EQ("a{id}", (GetDBusSignature<std::map<int, double>>()));
+ EXPECT_EQ(
+ "a{ia{ss}}",
+ (GetDBusSignature<std::map<int, std::map<std::string, std::string>>>()));
+}
+
+TEST(DBusUtils, Signatures_Pairs) {
+ EXPECT_EQ("(sb)", (GetDBusSignature<std::pair<std::string, bool>>()));
+ EXPECT_EQ("(sv)", (GetDBusSignature<std::pair<std::string, Any>>()));
+ EXPECT_EQ("(id)", (GetDBusSignature<std::pair<int, double>>()));
+}
+
+TEST(DBusUtils, Signatures_Tuples) {
+ EXPECT_EQ("(i)", (GetDBusSignature<std::tuple<int>>()));
+ EXPECT_EQ("(sv)", (GetDBusSignature<std::tuple<std::string, Any>>()));
+ EXPECT_EQ("(id(si))",
+ (GetDBusSignature<
+ std::tuple<int, double, std::tuple<std::string, int>>>()));
+}
+
+// Test that a byte can be properly written and read. We only have this
+// test for byte, as repeating this for other basic types is too redundant.
+TEST(DBusUtils, AppendAndPopByte) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, uint8_t{123});
+ EXPECT_EQ("y", message->GetSignature());
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(reader.HasMoreData()); // Should have data to read.
+ EXPECT_EQ(Message::BYTE, reader.GetDataType());
+
+ bool bool_value = false;
+ // Should fail as the type is not bool here.
+ EXPECT_FALSE(PopValueFromReader(&reader, &bool_value));
+
+ uint8_t byte_value = 0;
+ EXPECT_TRUE(PopValueFromReader(&reader, &byte_value));
+ EXPECT_EQ(123, byte_value); // Should match with the input.
+ EXPECT_FALSE(reader.HasMoreData()); // Should not have more data to read.
+
+ // Try to get another byte. Should fail.
+ EXPECT_FALSE(PopValueFromReader(&reader, &byte_value));
+}
+
+// Check all basic types can be properly written and read.
+TEST(DBusUtils, AppendAndPopBasicDataTypes) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+
+ // Append 0, true, 2, 3, 4, 5, 6, 7, 8.0, "string", "/object/path".
+ AppendValueToWriter(&writer, uint8_t{0});
+ AppendValueToWriter(&writer, bool{true});
+ AppendValueToWriter(&writer, int16_t{2});
+ AppendValueToWriter(&writer, uint16_t{3});
+ AppendValueToWriter(&writer, int32_t{4});
+ AppendValueToWriter(&writer, uint32_t{5});
+ AppendValueToWriter(&writer, int64_t{6});
+ AppendValueToWriter(&writer, uint64_t{7});
+ AppendValueToWriter(&writer, double{8.0});
+ AppendValueToWriter(&writer, std::string{"string"});
+ AppendValueToWriter(&writer, ObjectPath{"/object/path"});
+
+ EXPECT_EQ("ybnqiuxtdso", message->GetSignature());
+
+ uint8_t byte_value = 0;
+ bool bool_value = false;
+ int16_t int16_value = 0;
+ uint16_t uint16_value = 0;
+ int32_t int32_value = 0;
+ uint32_t uint32_value = 0;
+ int64_t int64_value = 0;
+ uint64_t uint64_value = 0;
+ double double_value = 0;
+ std::string string_value;
+ ObjectPath object_path_value;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(reader.HasMoreData());
+ EXPECT_TRUE(PopValueFromReader(&reader, &byte_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &bool_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &int16_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &uint16_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &int32_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &uint32_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &int64_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &uint64_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &double_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &string_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &object_path_value));
+ EXPECT_FALSE(reader.HasMoreData());
+
+ // 0, true, 2, 3, 4, 5, 6, 7, 8, "string", "/object/path" should be returned.
+ EXPECT_EQ(0, byte_value);
+ EXPECT_TRUE(bool_value);
+ EXPECT_EQ(2, int16_value);
+ EXPECT_EQ(3U, uint16_value);
+ EXPECT_EQ(4, int32_value);
+ EXPECT_EQ(5U, uint32_value);
+ EXPECT_EQ(6, int64_value);
+ EXPECT_EQ(7U, uint64_value);
+ EXPECT_DOUBLE_EQ(8.0, double_value);
+ EXPECT_EQ("string", string_value);
+ EXPECT_EQ(ObjectPath{"/object/path"}, object_path_value);
+}
+
+// Check all basic types can be properly written and read.
+TEST(DBusUtils, AppendAndPopFileDescriptor) {
+ if (!dbus::IsDBusTypeUnixFdSupported()) {
+ LOG(WARNING) << "FD passing is not supported";
+ return;
+ }
+
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+
+ // Append stdout.
+ FileDescriptor temp(1);
+ // Descriptor should not be valid until checked.
+ EXPECT_FALSE(temp.is_valid());
+ // NB: thread IO requirements not relevant for unit tests.
+ temp.CheckValidity();
+ EXPECT_TRUE(temp.is_valid());
+ AppendValueToWriter(&writer, temp);
+
+ EXPECT_EQ("h", message->GetSignature());
+
+ FileDescriptor fd_value;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(reader.HasMoreData());
+ EXPECT_TRUE(PopValueFromReader(&reader, &fd_value));
+ EXPECT_FALSE(reader.HasMoreData());
+ // Descriptor is automatically checked for validity as part of
+ // PopValueFromReader() call.
+ EXPECT_TRUE(fd_value.is_valid());
+}
+
+// Check all variant types can be properly written and read.
+TEST(DBusUtils, AppendAndPopVariantDataTypes) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+
+ // Append 10, false, 12, 13, 14, 15, 16, 17, 18.5, "data", "/obj/path".
+ AppendValueToWriterAsVariant(&writer, uint8_t{10});
+ AppendValueToWriterAsVariant(&writer, bool{false});
+ AppendValueToWriterAsVariant(&writer, int16_t{12});
+ AppendValueToWriterAsVariant(&writer, uint16_t{13});
+ AppendValueToWriterAsVariant(&writer, int32_t{14});
+ AppendValueToWriterAsVariant(&writer, uint32_t{15});
+ AppendValueToWriterAsVariant(&writer, int64_t{16});
+ AppendValueToWriterAsVariant(&writer, uint64_t{17});
+ AppendValueToWriterAsVariant(&writer, double{18.5});
+ AppendValueToWriterAsVariant(&writer, std::string{"data"});
+ AppendValueToWriterAsVariant(&writer, ObjectPath{"/obj/path"});
+ AppendValueToWriterAsVariant(&writer, Any{17});
+ AppendValueToWriterAsVariant(&writer,
+ Any{std::vector<std::vector<int>>{{6, 7}}});
+
+ EXPECT_EQ("vvvvvvvvvvvvv", message->GetSignature());
+
+ uint8_t byte_value = 0;
+ bool bool_value = true;
+ int16_t int16_value = 0;
+ uint16_t uint16_value = 0;
+ int32_t int32_value = 0;
+ uint32_t uint32_value = 0;
+ int64_t int64_value = 0;
+ uint64_t uint64_value = 0;
+ double double_value = 0;
+ std::string string_value;
+ ObjectPath object_path_value;
+ Any any_value;
+ Any any_vector_vector;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(reader.HasMoreData());
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &byte_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &bool_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &int16_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &uint16_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &int32_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &uint32_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &int64_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &uint64_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &double_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &string_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &object_path_value));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &any_value));
+ // Not implemented.
+ EXPECT_FALSE(PopVariantValueFromReader(&reader, &any_vector_vector));
+ EXPECT_FALSE(reader.HasMoreData());
+
+ EXPECT_EQ(10, byte_value);
+ EXPECT_FALSE(bool_value);
+ EXPECT_EQ(12, int16_value);
+ EXPECT_EQ(13U, uint16_value);
+ EXPECT_EQ(14, int32_value);
+ EXPECT_EQ(15U, uint32_value);
+ EXPECT_EQ(16, int64_value);
+ EXPECT_EQ(17U, uint64_value);
+ EXPECT_DOUBLE_EQ(18.5, double_value);
+ EXPECT_EQ("data", string_value);
+ EXPECT_EQ(ObjectPath{"/obj/path"}, object_path_value);
+ EXPECT_EQ(17, any_value.Get<int>());
+ EXPECT_TRUE(any_vector_vector.IsEmpty());
+}
+
+TEST(DBusUtils, AppendAndPopBasicAny) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+
+ // Append 10, true, 12, 13, 14, 15, 16, 17, 18.5, "data", "/obj/path".
+ AppendValueToWriter(&writer, Any(uint8_t{10}));
+ AppendValueToWriter(&writer, Any(bool{true}));
+ AppendValueToWriter(&writer, Any(int16_t{12}));
+ AppendValueToWriter(&writer, Any(uint16_t{13}));
+ AppendValueToWriter(&writer, Any(int32_t{14}));
+ AppendValueToWriter(&writer, Any(uint32_t{15}));
+ AppendValueToWriter(&writer, Any(int64_t{16}));
+ AppendValueToWriter(&writer, Any(uint64_t{17}));
+ AppendValueToWriter(&writer, Any(double{18.5}));
+ AppendValueToWriter(&writer, Any(std::string{"data"}));
+ AppendValueToWriter(&writer, Any(ObjectPath{"/obj/path"}));
+ EXPECT_EQ("vvvvvvvvvvv", message->GetSignature());
+
+ Any byte_value;
+ Any bool_value;
+ Any int16_value;
+ Any uint16_value;
+ Any int32_value;
+ Any uint32_value;
+ Any int64_value;
+ Any uint64_value;
+ Any double_value;
+ Any string_value;
+ Any object_path_value;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(reader.HasMoreData());
+ EXPECT_TRUE(PopValueFromReader(&reader, &byte_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &bool_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &int16_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &uint16_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &int32_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &uint32_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &int64_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &uint64_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &double_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &string_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &object_path_value));
+ EXPECT_FALSE(reader.HasMoreData());
+
+ // Must be: 10, true, 12, 13, 14, 15, 16, 17, 18.5, "data", "/obj/path".
+ EXPECT_EQ(10, byte_value.Get<uint8_t>());
+ EXPECT_TRUE(bool_value.Get<bool>());
+ EXPECT_EQ(12, int16_value.Get<int16_t>());
+ EXPECT_EQ(13U, uint16_value.Get<uint16_t>());
+ EXPECT_EQ(14, int32_value.Get<int32_t>());
+ EXPECT_EQ(15U, uint32_value.Get<uint32_t>());
+ EXPECT_EQ(16, int64_value.Get<int64_t>());
+ EXPECT_EQ(17U, uint64_value.Get<uint64_t>());
+ EXPECT_DOUBLE_EQ(18.5, double_value.Get<double>());
+ EXPECT_EQ("data", string_value.Get<std::string>());
+ EXPECT_EQ(ObjectPath{"/obj/path"}, object_path_value.Get<ObjectPath>());
+}
+
+TEST(DBusUtils, ArrayOfBytes) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::vector<uint8_t> bytes{1, 2, 3};
+ AppendValueToWriter(&writer, bytes);
+
+ EXPECT_EQ("ay", message->GetSignature());
+
+ MessageReader reader(message.get());
+ std::vector<uint8_t> bytes_out;
+ EXPECT_TRUE(PopValueFromReader(&reader, &bytes_out));
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(bytes, bytes_out);
+}
+
+TEST(DBusUtils, ArrayOfBytes_Empty) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::vector<uint8_t> bytes;
+ AppendValueToWriter(&writer, bytes);
+
+ EXPECT_EQ("ay", message->GetSignature());
+
+ MessageReader reader(message.get());
+ std::vector<uint8_t> bytes_out;
+ EXPECT_TRUE(PopValueFromReader(&reader, &bytes_out));
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(bytes, bytes_out);
+}
+
+TEST(DBusUtils, ArrayOfStrings) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::vector<std::string> strings{"foo", "bar", "baz"};
+ AppendValueToWriter(&writer, strings);
+
+ EXPECT_EQ("as", message->GetSignature());
+
+ MessageReader reader(message.get());
+ std::vector<std::string> strings_out;
+ EXPECT_TRUE(PopValueFromReader(&reader, &strings_out));
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(strings, strings_out);
+}
+
+TEST(DBusUtils, ArrayOfInt64) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::vector<int64_t> values{-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5,
+ std::numeric_limits<int64_t>::min(),
+ std::numeric_limits<int64_t>::max()};
+ AppendValueToWriter(&writer, values);
+
+ EXPECT_EQ("ax", message->GetSignature());
+
+ MessageReader reader(message.get());
+ std::vector<int64_t> values_out;
+ EXPECT_TRUE(PopValueFromReader(&reader, &values_out));
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(values, values_out);
+}
+
+TEST(DBusUtils, ArrayOfObjectPaths) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::vector<ObjectPath> object_paths{
+ ObjectPath("/object/path/1"),
+ ObjectPath("/object/path/2"),
+ ObjectPath("/object/path/3"),
+ };
+ AppendValueToWriter(&writer, object_paths);
+
+ EXPECT_EQ("ao", message->GetSignature());
+
+ MessageReader reader(message.get());
+ std::vector<ObjectPath> object_paths_out;
+ EXPECT_TRUE(PopValueFromReader(&reader, &object_paths_out));
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(object_paths, object_paths_out);
+}
+
+TEST(DBusUtils, ArraysAsVariant) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::vector<int> int_array{1, 2, 3};
+ std::vector<std::string> str_array{"foo", "bar", "baz"};
+ std::vector<double> dbl_array_empty{};
+ std::map<std::string, std::string> dict_ss{{"k1", "v1"}, {"k2", "v2"}};
+ VariantDictionary dict_sv{{"k1", 1}, {"k2", "v2"}};
+ AppendValueToWriterAsVariant(&writer, int_array);
+ AppendValueToWriterAsVariant(&writer, str_array);
+ AppendValueToWriterAsVariant(&writer, dbl_array_empty);
+ AppendValueToWriterAsVariant(&writer, dict_ss);
+ AppendValueToWriterAsVariant(&writer, dict_sv);
+
+ EXPECT_EQ("vvvvv", message->GetSignature());
+
+ Any int_array_out;
+ Any str_array_out;
+ Any dbl_array_out;
+ Any dict_ss_out;
+ Any dict_sv_out;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &int_array_out));
+ EXPECT_TRUE(PopValueFromReader(&reader, &str_array_out));
+ EXPECT_TRUE(PopValueFromReader(&reader, &dbl_array_out));
+ EXPECT_TRUE(PopValueFromReader(&reader, &dict_ss_out));
+ EXPECT_TRUE(PopValueFromReader(&reader, &dict_sv_out));
+ EXPECT_FALSE(reader.HasMoreData());
+
+ EXPECT_EQ(int_array, int_array_out.Get<std::vector<int>>());
+ EXPECT_EQ(str_array, str_array_out.Get<std::vector<std::string>>());
+ EXPECT_EQ(dbl_array_empty, dbl_array_out.Get<std::vector<double>>());
+ EXPECT_EQ(dict_ss, (dict_ss_out.Get<std::map<std::string, std::string>>()));
+ EXPECT_EQ(dict_sv["k1"].Get<int>(),
+ dict_sv_out.Get<VariantDictionary>().at("k1").Get<int>());
+ EXPECT_EQ(dict_sv["k2"].Get<const char*>(),
+ dict_sv_out.Get<VariantDictionary>().at("k2").Get<std::string>());
+}
+
+TEST(DBusUtils, VariantDictionary) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ VariantDictionary values{
+ {"key1", uint8_t{10}},
+ {"key2", bool{true}},
+ {"key3", int16_t{12}},
+ {"key4", uint16_t{13}},
+ {"key5", int32_t{14}},
+ {"key6", uint32_t{15}},
+ {"key7", int64_t{16}},
+ {"key8", uint64_t{17}},
+ {"key9", double{18.5}},
+ {"keyA", std::string{"data"}},
+ {"keyB", ObjectPath{"/obj/path"}},
+ };
+ AppendValueToWriter(&writer, values);
+
+ EXPECT_EQ("a{sv}", message->GetSignature());
+
+ MessageReader reader(message.get());
+ VariantDictionary values_out;
+ EXPECT_TRUE(PopValueFromReader(&reader, &values_out));
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(values.size(), values_out.size());
+ EXPECT_EQ(values["key1"].Get<uint8_t>(), values_out["key1"].Get<uint8_t>());
+ EXPECT_EQ(values["key2"].Get<bool>(), values_out["key2"].Get<bool>());
+ EXPECT_EQ(values["key3"].Get<int16_t>(), values_out["key3"].Get<int16_t>());
+ EXPECT_EQ(values["key4"].Get<uint16_t>(), values_out["key4"].Get<uint16_t>());
+ EXPECT_EQ(values["key5"].Get<int32_t>(), values_out["key5"].Get<int32_t>());
+ EXPECT_EQ(values["key6"].Get<uint32_t>(), values_out["key6"].Get<uint32_t>());
+ EXPECT_EQ(values["key7"].Get<int64_t>(), values_out["key7"].Get<int64_t>());
+ EXPECT_EQ(values["key8"].Get<uint64_t>(), values_out["key8"].Get<uint64_t>());
+ EXPECT_EQ(values["key9"].Get<double>(), values_out["key9"].Get<double>());
+ EXPECT_EQ(values["keyA"].Get<std::string>(),
+ values_out["keyA"].Get<std::string>());
+ EXPECT_EQ(values["keyB"].Get<ObjectPath>(),
+ values_out["keyB"].Get<ObjectPath>());
+}
+
+TEST(DBusUtils, StringToStringMap) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::map<std::string, std::string> values{
+ {"key1", "value1"},
+ {"key2", "value2"},
+ {"key3", "value3"},
+ {"key4", "value4"},
+ {"key5", "value5"},
+ };
+ AppendValueToWriter(&writer, values);
+
+ EXPECT_EQ("a{ss}", message->GetSignature());
+
+ MessageReader reader(message.get());
+ std::map<std::string, std::string> values_out;
+ EXPECT_TRUE(PopValueFromReader(&reader, &values_out));
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(values, values_out);
+}
+
+TEST(DBusUtils, Pair) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::pair<std::string, int> struct1{"value2", 3};
+ AppendValueToWriter(&writer, struct1);
+ std::pair<int, std::pair<int, int>> struct2{1, {2, 3}};
+ AppendValueToWriter(&writer, struct2);
+
+ EXPECT_EQ("(si)(i(ii))", message->GetSignature());
+
+ std::pair<std::string, int> struct1_out;
+ std::pair<int, std::pair<int, int>> struct2_out;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &struct1_out));
+ EXPECT_TRUE(PopValueFromReader(&reader, &struct2_out));
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(struct1, struct1_out);
+ EXPECT_EQ(struct2, struct2_out);
+}
+
+TEST(DBusUtils, Tuple) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::tuple<std::string, int> struct1{"value2", 3};
+ AppendValueToWriter(&writer, struct1);
+ std::tuple<int, std::string, std::vector<std::pair<int, int>>> struct2{
+ 1, "a", {{2, 3}}
+ };
+ AppendValueToWriter(&writer, struct2);
+
+ EXPECT_EQ("(si)(isa(ii))", message->GetSignature());
+
+ std::tuple<std::string, int> struct1_out;
+ std::tuple<int, std::string, std::vector<std::pair<int, int>>> struct2_out;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &struct1_out));
+ EXPECT_TRUE(PopValueFromReader(&reader, &struct2_out));
+ EXPECT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(struct1, struct1_out);
+ EXPECT_EQ(struct2, struct2_out);
+}
+
+TEST(DBusUtils, ReinterpretVariant) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::vector<std::string> str_array{"foo", "bar", "baz"};
+ std::map<std::string, std::string> dict_ss{{"k1", "v1"}, {"k2", "v2"}};
+ VariantDictionary dict_sv{{"k1", "v1"}, {"k2", "v2"}};
+ AppendValueToWriterAsVariant(&writer, 123);
+ AppendValueToWriterAsVariant(&writer, str_array);
+ AppendValueToWriterAsVariant(&writer, 1.7);
+ AppendValueToWriterAsVariant(&writer, dict_ss);
+ AppendValueToWriter(&writer, dict_sv);
+
+ EXPECT_EQ("vvvva{sv}", message->GetSignature());
+
+ int int_out = 0;
+ std::vector<std::string> str_array_out;
+ double dbl_out = 0.0;
+ std::map<std::string, std::string> dict_ss_out;
+ std::map<std::string, std::string> dict_ss_out2;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &int_out));
+ EXPECT_TRUE(PopValueFromReader(&reader, &str_array_out));
+ EXPECT_TRUE(PopValueFromReader(&reader, &dbl_out));
+ EXPECT_TRUE(PopValueFromReader(&reader, &dict_ss_out));
+ EXPECT_TRUE(PopValueFromReader(&reader,
+ &dict_ss_out2)); // Read "a{sv}" as "a{ss}".
+ EXPECT_FALSE(reader.HasMoreData());
+
+ EXPECT_EQ(123, int_out);
+ EXPECT_EQ(str_array, str_array_out);
+ EXPECT_DOUBLE_EQ(1.7, dbl_out);
+ EXPECT_EQ(dict_ss, dict_ss_out);
+ EXPECT_EQ(dict_ss, dict_ss_out2);
+}
+
+// Test handling of custom data types.
+struct Person {
+ std::string first_name;
+ std::string last_name;
+ int age;
+ // Provide == operator so we can easily compare arrays of Person.
+ bool operator==(const Person& rhs) const {
+ return first_name == rhs.first_name && last_name == rhs.last_name &&
+ age == rhs.age;
+ }
+};
+
+// Overload AppendValueToWriter() for "Person" structure.
+void AppendValueToWriter(dbus::MessageWriter* writer, const Person& value) {
+ dbus::MessageWriter struct_writer(nullptr);
+ writer->OpenStruct(&struct_writer);
+ AppendValueToWriter(&struct_writer, value.first_name);
+ AppendValueToWriter(&struct_writer, value.last_name);
+ AppendValueToWriter(&struct_writer, value.age);
+ writer->CloseContainer(&struct_writer);
+}
+
+// Overload PopValueFromReader() for "Person" structure.
+bool PopValueFromReader(dbus::MessageReader* reader, Person* value) {
+ dbus::MessageReader variant_reader(nullptr);
+ dbus::MessageReader struct_reader(nullptr);
+ if (!details::DescendIntoVariantIfPresent(&reader, &variant_reader) ||
+ !reader->PopStruct(&struct_reader))
+ return false;
+ return PopValueFromReader(&struct_reader, &value->first_name) &&
+ PopValueFromReader(&struct_reader, &value->last_name) &&
+ PopValueFromReader(&struct_reader, &value->age);
+}
+
+// Specialize DBusType<T> for "Person" structure.
+template<>
+struct DBusType<Person> {
+ inline static std::string GetSignature() {
+ return GetStructDBusSignature<std::string, std::string, int>();
+ }
+ inline static void Write(dbus::MessageWriter* writer, const Person& value) {
+ AppendValueToWriter(writer, value);
+ }
+ inline static bool Read(dbus::MessageReader* reader, Person* value) {
+ return PopValueFromReader(reader, value);
+ }
+};
+
+TEST(DBusUtils, CustomStruct) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::vector<Person> people{{"John", "Doe", 32}, {"Jane", "Smith", 48}};
+ AppendValueToWriter(&writer, people);
+ AppendValueToWriterAsVariant(&writer, people);
+ AppendValueToWriterAsVariant(&writer, people);
+
+ EXPECT_EQ("a(ssi)vv", message->GetSignature());
+
+ std::vector<Person> people_out1;
+ std::vector<Person> people_out2;
+ std::vector<Person> people_out3;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &people_out1));
+ EXPECT_TRUE(PopValueFromReader(&reader, &people_out2));
+ EXPECT_TRUE(PopVariantValueFromReader(&reader, &people_out3));
+ EXPECT_FALSE(reader.HasMoreData());
+
+ EXPECT_EQ(people, people_out1);
+ EXPECT_EQ(people, people_out2);
+ EXPECT_EQ(people, people_out3);
+}
+
+TEST(DBusUtils, CustomStructInComplexTypes) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ std::vector<Person> people{{"John", "Doe", 32}, {"Jane", "Smith", 48}};
+ std::vector<std::map<int, Person>> data{
+ {
+ {1, Person{"John", "Doe", 32}},
+ {2, Person{"Jane", "Smith", 48}},
+ }
+ };
+ AppendValueToWriter(&writer, data);
+
+ EXPECT_EQ("aa{i(ssi)}", message->GetSignature());
+
+ std::vector<std::map<int, Person>> data_out;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &data_out));
+ EXPECT_FALSE(reader.HasMoreData());
+
+ EXPECT_EQ(data, data_out);
+}
+
+TEST(DBusUtils, EmptyVariant) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ EXPECT_DEATH(AppendValueToWriter(&writer, Any{}),
+ "Must not be called on an empty Any");
+}
+
+TEST(DBusUtils, IncompatibleVariant) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ EXPECT_DEATH(AppendValueToWriter(&writer, Any{2.2f}),
+ "Type 'float' is not supported by D-Bus");
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_connection.cc b/libbrillo/brillo/dbus/dbus_connection.cc
new file mode 100644
index 0000000..b60cf44
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_connection.cc
@@ -0,0 +1,58 @@
+// Copyright 2016 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 <brillo/dbus/dbus_connection.h>
+
+#include <sysexits.h>
+
+#include <base/bind.h>
+#include <brillo/dbus/async_event_sequencer.h>
+#include <brillo/dbus/exported_object_manager.h>
+
+using brillo::dbus_utils::AsyncEventSequencer;
+using brillo::dbus_utils::ExportedObjectManager;
+
+namespace brillo {
+
+DBusConnection::DBusConnection() {
+}
+
+DBusConnection::~DBusConnection() {
+ if (bus_)
+ bus_->ShutdownAndBlock();
+}
+
+scoped_refptr<dbus::Bus> DBusConnection::Connect() {
+ return ConnectWithTimeout(base::TimeDelta());
+}
+
+scoped_refptr<dbus::Bus> DBusConnection::ConnectWithTimeout(
+ base::TimeDelta timeout) {
+ if (bus_)
+ return bus_;
+
+ base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
+
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SYSTEM;
+
+ scoped_refptr<dbus::Bus> bus = new dbus::Bus(options);
+
+ do {
+ if (bus->Connect()) {
+ bus_ = bus;
+ return bus_;
+ }
+ LOG(WARNING) << "Failed to get system bus.";
+ // Wait 1 second to prevent trashing the device while waiting for the
+ // dbus-daemon to start.
+ sleep(1);
+ } while (base::TimeTicks::Now() < deadline);
+
+ LOG(ERROR) << "Failed to get system bus after " << timeout.InSeconds()
+ << " seconds.";
+ return nullptr;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_connection.h b/libbrillo/brillo/dbus/dbus_connection.h
new file mode 100644
index 0000000..aecf434
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_connection.h
@@ -0,0 +1,40 @@
+// Copyright 2016 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 LIBBRILLO_BRILLO_DBUS_DBUS_CONNECTION_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_CONNECTION_H_
+
+#include <base/memory/ref_counted.h>
+#include <base/time/time.h>
+#include <dbus/bus.h>
+
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+// DBusConnection adds D-Bus support to Daemon.
+class BRILLO_EXPORT DBusConnection final {
+ public:
+ DBusConnection();
+ ~DBusConnection();
+
+ // Instantiates dbus::Bus and establishes a D-Bus connection. Returns a
+ // reference to the connected bus, or an empty pointer in case of error.
+ scoped_refptr<dbus::Bus> Connect();
+
+ // Instantiates dbus::Bus and tries to establish a D-Bus connection for up to
+ // |timeout|. If the connection can't be established after the timeout, fails
+ // returning an empty pointer.
+ scoped_refptr<dbus::Bus> ConnectWithTimeout(base::TimeDelta timeout);
+
+ private:
+ scoped_refptr<dbus::Bus> bus_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DBusConnection);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DAEMONS_DBUS_DAEMON_H_
diff --git a/libbrillo/brillo/dbus/dbus_method_invoker.cc b/libbrillo/brillo/dbus/dbus_method_invoker.cc
new file mode 100644
index 0000000..94002c2
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_method_invoker.cc
@@ -0,0 +1,23 @@
+// 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 <brillo/dbus/dbus_method_invoker.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+void TranslateErrorResponse(const AsyncErrorCallback& callback,
+ dbus::ErrorResponse* resp) {
+ if (!callback.is_null()) {
+ ErrorPtr error;
+ dbus::MessageReader reader(resp);
+ std::string error_message;
+ if (ExtractMessageParameters(&reader, &error, &error_message))
+ AddDBusError(&error, resp->GetErrorName(), error_message);
+ callback.Run(error.get());
+ }
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_method_invoker.h b/libbrillo/brillo/dbus/dbus_method_invoker.h
new file mode 100644
index 0000000..f3d0e6b
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_method_invoker.h
@@ -0,0 +1,324 @@
+// 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 file provides a way to call D-Bus methods on objects in remote processes
+// as if they were native C++ function calls.
+
+// CallMethodAndBlock (along with CallMethodAndBlockWithTimeout) lets you call
+// a D-Bus method synchronously and pass all the required parameters as C++
+// function arguments. CallMethodAndBlock relies on automatic C++ to D-Bus data
+// serialization implemented in brillo/dbus/data_serialization.h.
+// CallMethodAndBlock invokes the D-Bus method and returns the Response.
+
+// The method call response should be parsed with ExtractMethodCallResults().
+// The method takes an optional list of pointers to the expected return values
+// of the D-Bus method.
+
+// CallMethod and CallMethodWithTimeout are similar to CallMethodAndBlock but
+// make the calls asynchronously. They take two callbacks: one for successful
+// method invocation and the second is for error conditions.
+
+// Here is an example of synchronous calls:
+// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus:
+
+// using brillo::dbus_utils::CallMethodAndBlock;
+// using brillo::dbus_utils::ExtractMethodCallResults;
+//
+// brillo::ErrorPtr error;
+// auto resp = CallMethodAndBlock(obj,
+// "org.chromium.MyService.MyInterface",
+// "MyMethod",
+// &error,
+// 2, 8.7);
+// std::string return_value;
+// if (resp && ExtractMethodCallResults(resp.get(), &error, &return_value)) {
+// // Use the |return_value|.
+// } else {
+// // An error occurred. Use |error| to get details.
+// }
+
+// And here is how to call D-Bus methods asynchronously:
+// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus:
+
+// using brillo::dbus_utils::CallMethod;
+// using brillo::dbus_utils::ExtractMethodCallResults;
+//
+// void OnSuccess(const std::string& return_value) {
+// // Use the |return_value|.
+// }
+//
+// void OnError(brillo::Error* error) {
+// // An error occurred. Use |error| to get details.
+// }
+//
+// brillo::dbus_utils::CallMethod(obj,
+// "org.chromium.MyService.MyInterface",
+// "MyMethod",
+// base::Bind(OnSuccess),
+// base::Bind(OnError),
+// 2, 8.7);
+
+#ifndef LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_
+
+#include <memory>
+#include <string>
+#include <tuple>
+
+#include <base/bind.h>
+#include <brillo/dbus/dbus_param_reader.h>
+#include <brillo/dbus/dbus_param_writer.h>
+#include <brillo/dbus/utils.h>
+#include <brillo/errors/error.h>
+#include <brillo/errors/error_codes.h>
+#include <brillo/brillo_export.h>
+#include <dbus/file_descriptor.h>
+#include <dbus/message.h>
+#include <dbus/object_proxy.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+// A helper method to dispatch a blocking D-Bus method call. Can specify
+// zero or more method call arguments in |args| which will be sent over D-Bus.
+// This method sends a D-Bus message and blocks for a time period specified
+// in |timeout_ms| while waiting for a reply. The time out is in milliseconds or
+// -1 (DBUS_TIMEOUT_USE_DEFAULT) for default, or DBUS_TIMEOUT_INFINITE for no
+// timeout. If a timeout occurs, the response object contains an error object
+// with DBUS_ERROR_NO_REPLY error code (those constants come from libdbus
+// [dbus/dbus.h]).
+// Returns a dbus::Response object on success. On failure, returns nullptr and
+// fills in additional error details into the |error| object.
+template<typename... Args>
+inline std::unique_ptr<dbus::Response> CallMethodAndBlockWithTimeout(
+ int timeout_ms,
+ dbus::ObjectProxy* object,
+ const std::string& interface_name,
+ const std::string& method_name,
+ ErrorPtr* error,
+ const Args&... args) {
+ dbus::MethodCall method_call(interface_name, method_name);
+ // Add method arguments to the message buffer.
+ dbus::MessageWriter writer(&method_call);
+ DBusParamWriter::Append(&writer, args...);
+ dbus::ScopedDBusError dbus_error;
+ auto response = object->CallMethodAndBlockWithErrorDetails(
+ &method_call, timeout_ms, &dbus_error);
+ if (!response) {
+ if (dbus_error.is_set()) {
+ Error::AddTo(error,
+ FROM_HERE,
+ errors::dbus::kDomain,
+ dbus_error.name(),
+ dbus_error.message());
+ } else {
+ Error::AddToPrintf(error,
+ FROM_HERE,
+ errors::dbus::kDomain,
+ DBUS_ERROR_FAILED,
+ "Failed to call D-Bus method: %s.%s",
+ interface_name.c_str(),
+ method_name.c_str());
+ }
+ }
+ return response;
+}
+
+// Same as CallMethodAndBlockWithTimeout() but uses a default timeout value.
+template<typename... Args>
+inline std::unique_ptr<dbus::Response> CallMethodAndBlock(
+ dbus::ObjectProxy* object,
+ const std::string& interface_name,
+ const std::string& method_name,
+ ErrorPtr* error,
+ const Args&... args) {
+ return CallMethodAndBlockWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+ object,
+ interface_name,
+ method_name,
+ error,
+ args...);
+}
+
+namespace internal {
+// In order to support non-copyable dbus::FileDescriptor, we have this
+// internal::HackMove() helper function that does really nothing for normal
+// types but uses Pass() for file descriptors so we can move them out from
+// the temporaries created inside DBusParamReader<...>::Invoke().
+// If only libchrome supported real rvalues so we can just do std::move() and
+// be done with it.
+template <typename T>
+inline const T& HackMove(const T& val) {
+ return val;
+}
+
+// Even though |val| here is passed as const&, the actual value is created
+// inside DBusParamReader<...>::Invoke() and is temporary in nature, so it is
+// safe to move the file descriptor out of |val|. That's why we are doing
+// const_cast here. It is a bit hacky, but there is no negative side effects.
+inline dbus::FileDescriptor HackMove(const dbus::FileDescriptor& val) {
+ return std::move(const_cast<dbus::FileDescriptor&>(val));
+}
+} // namespace internal
+
+// Extracts the parameters of |ResultTypes...| types from the message reader
+// and puts the values in the resulting |tuple|. Returns false on error and
+// provides additional error details in |error| object.
+template<typename... ResultTypes>
+inline bool ExtractMessageParametersAsTuple(
+ dbus::MessageReader* reader,
+ ErrorPtr* error,
+ std::tuple<ResultTypes...>* val_tuple) {
+ auto callback = [val_tuple](const ResultTypes&... params) {
+ *val_tuple = std::forward_as_tuple(internal::HackMove(params)...);
+ };
+ return DBusParamReader<false, ResultTypes...>::Invoke(
+ callback, reader, error);
+}
+// Overload of ExtractMessageParametersAsTuple to handle reference types in
+// tuples created with std::tie().
+template<typename... ResultTypes>
+inline bool ExtractMessageParametersAsTuple(
+ dbus::MessageReader* reader,
+ ErrorPtr* error,
+ std::tuple<ResultTypes&...>* ref_tuple) {
+ auto callback = [ref_tuple](const ResultTypes&... params) {
+ *ref_tuple = std::forward_as_tuple(internal::HackMove(params)...);
+ };
+ return DBusParamReader<false, ResultTypes...>::Invoke(
+ callback, reader, error);
+}
+
+// A helper method to extract a list of values from a message buffer.
+// The function will return false and provide detailed error information on
+// failure. It fails if the D-Bus message buffer (represented by the |reader|)
+// contains too many, too few parameters or the parameters are of wrong types
+// (signatures).
+// The usage pattern is as follows:
+//
+// int32_t data1;
+// std::string data2;
+// ErrorPtr error;
+// if (ExtractMessageParameters(reader, &error, &data1, &data2)) { ... }
+//
+// The above example extracts an Int32 and a String from D-Bus message buffer.
+template<typename... ResultTypes>
+inline bool ExtractMessageParameters(dbus::MessageReader* reader,
+ ErrorPtr* error,
+ ResultTypes*... results) {
+ auto ref_tuple = std::tie(*results...);
+ return ExtractMessageParametersAsTuple<ResultTypes...>(
+ reader, error, &ref_tuple);
+}
+
+// Convenient helper method to extract return value(s) of a D-Bus method call.
+// |results| must be zero or more pointers to data expected to be returned
+// from the method called. If an error occurs, returns false and provides
+// additional details in |error| object.
+//
+// It is OK to call this method even if the D-Bus method doesn't expect
+// any return values. Just do not specify any output |results|. In this case,
+// ExtractMethodCallResults() will verify that the method didn't return any
+// data in the |message|.
+template<typename... ResultTypes>
+inline bool ExtractMethodCallResults(dbus::Message* message,
+ ErrorPtr* error,
+ ResultTypes*... results) {
+ CHECK(message) << "Unable to extract parameters from a NULL message.";
+
+ dbus::MessageReader reader(message);
+ if (message->GetMessageType() == dbus::Message::MESSAGE_ERROR) {
+ std::string error_message;
+ if (ExtractMessageParameters(&reader, error, &error_message))
+ AddDBusError(error, message->GetErrorName(), error_message);
+ return false;
+ }
+ return ExtractMessageParameters(&reader, error, results...);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Asynchronous method invocation support
+
+using AsyncErrorCallback = base::Callback<void(Error* error)>;
+
+// A helper function that translates dbus::ErrorResponse response
+// from D-Bus into brillo::Error* and invokes the |callback|.
+void BRILLO_EXPORT TranslateErrorResponse(const AsyncErrorCallback& callback,
+ dbus::ErrorResponse* resp);
+
+// A helper function that translates dbus::Response from D-Bus into
+// a list of C++ values passed as parameters to |success_callback|. If the
+// response message doesn't have the correct number of parameters, or they
+// are of wrong types, an error is sent to |error_callback|.
+template<typename... OutArgs>
+void TranslateSuccessResponse(
+ const base::Callback<void(OutArgs...)>& success_callback,
+ const AsyncErrorCallback& error_callback,
+ dbus::Response* resp) {
+ auto callback = [&success_callback](const OutArgs&... params) {
+ if (!success_callback.is_null()) {
+ success_callback.Run(params...);
+ }
+ };
+ ErrorPtr error;
+ dbus::MessageReader reader(resp);
+ if (!DBusParamReader<false, OutArgs...>::Invoke(callback, &reader, &error) &&
+ !error_callback.is_null()) {
+ error_callback.Run(error.get());
+ }
+}
+
+// A helper method to dispatch a non-blocking D-Bus method call. Can specify
+// zero or more method call arguments in |params| which will be sent over D-Bus.
+// This method sends a D-Bus message and returns immediately.
+// When the remote method returns successfully, the success callback is
+// invoked with the return value(s), if any.
+// On error, the error callback is called. Note, the error callback can be
+// called synchronously (before CallMethodWithTimeout returns) if there was
+// a problem invoking a method (e.g. object or method doesn't exist).
+// If the response is not received within |timeout_ms|, an error callback is
+// called with DBUS_ERROR_NO_REPLY error code.
+template<typename... InArgs, typename... OutArgs>
+inline void CallMethodWithTimeout(
+ int timeout_ms,
+ dbus::ObjectProxy* object,
+ const std::string& interface_name,
+ const std::string& method_name,
+ const base::Callback<void(OutArgs...)>& success_callback,
+ const AsyncErrorCallback& error_callback,
+ const InArgs&... params) {
+ dbus::MethodCall method_call(interface_name, method_name);
+ dbus::MessageWriter writer(&method_call);
+ DBusParamWriter::Append(&writer, params...);
+
+ dbus::ObjectProxy::ErrorCallback dbus_error_callback =
+ base::Bind(&TranslateErrorResponse, error_callback);
+ dbus::ObjectProxy::ResponseCallback dbus_success_callback = base::Bind(
+ &TranslateSuccessResponse<OutArgs...>, success_callback, error_callback);
+
+ object->CallMethodWithErrorCallback(
+ &method_call, timeout_ms, dbus_success_callback, dbus_error_callback);
+}
+
+// Same as CallMethodWithTimeout() but uses a default timeout value.
+template<typename... InArgs, typename... OutArgs>
+inline void CallMethod(dbus::ObjectProxy* object,
+ const std::string& interface_name,
+ const std::string& method_name,
+ const base::Callback<void(OutArgs...)>& success_callback,
+ const AsyncErrorCallback& error_callback,
+ const InArgs&... params) {
+ return CallMethodWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+ object,
+ interface_name,
+ method_name,
+ success_callback,
+ error_callback,
+ params...);
+}
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_INVOKER_H_
diff --git a/libbrillo/brillo/dbus/dbus_method_invoker_unittest.cc b/libbrillo/brillo/dbus/dbus_method_invoker_unittest.cc
new file mode 100644
index 0000000..bb653fd
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_method_invoker_unittest.cc
@@ -0,0 +1,318 @@
+// 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 <brillo/dbus/dbus_method_invoker.h>
+
+#include <string>
+
+#include <brillo/bind_lambda.h>
+#include <dbus/mock_bus.h>
+#include <dbus/mock_object_proxy.h>
+#include <dbus/scoped_dbus_error.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::AnyNumber;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::_;
+
+using dbus::MessageReader;
+using dbus::MessageWriter;
+using dbus::Response;
+
+namespace brillo {
+namespace dbus_utils {
+
+const char kTestPath[] = "/test/path";
+const char kTestServiceName[] = "org.test.Object";
+const char kTestInterface[] = "org.test.Object.TestInterface";
+const char kTestMethod1[] = "TestMethod1";
+const char kTestMethod2[] = "TestMethod2";
+const char kTestMethod4[] = "TestMethod4";
+
+class DBusMethodInvokerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ 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_object_proxy_ = new dbus::MockObjectProxy(
+ bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath));
+ EXPECT_CALL(*bus_,
+ GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath)))
+ .WillRepeatedly(Return(mock_object_proxy_.get()));
+ int def_timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
+ EXPECT_CALL(*mock_object_proxy_,
+ MockCallMethodAndBlockWithErrorDetails(_, def_timeout_ms, _))
+ .WillRepeatedly(Invoke(this, &DBusMethodInvokerTest::CreateResponse));
+ }
+
+ void TearDown() override { bus_ = nullptr; }
+
+ Response* CreateResponse(dbus::MethodCall* method_call,
+ int /* timeout_ms */,
+ dbus::ScopedDBusError* dbus_error) {
+ if (method_call->GetInterface() == kTestInterface) {
+ if (method_call->GetMember() == kTestMethod1) {
+ MessageReader reader(method_call);
+ int v1, v2;
+ // Input: two ints.
+ // Output: sum of the ints converted to string.
+ if (reader.PopInt32(&v1) && reader.PopInt32(&v2)) {
+ auto response = Response::CreateEmpty();
+ MessageWriter writer(response.get());
+ writer.AppendString(std::to_string(v1 + v2));
+ return response.release();
+ }
+ } else if (method_call->GetMember() == kTestMethod2) {
+ method_call->SetSerial(123);
+ dbus_set_error(dbus_error->get(), "org.MyError", "My error message");
+ return nullptr;
+ } else if (method_call->GetMember() == kTestMethod4) {
+ method_call->SetSerial(123);
+ MessageReader reader(method_call);
+ dbus::FileDescriptor fd;
+ if (reader.PopFileDescriptor(&fd)) {
+ auto response = Response::CreateEmpty();
+ MessageWriter writer(response.get());
+ fd.CheckValidity();
+ writer.AppendFileDescriptor(fd);
+ return response.release();
+ }
+ }
+ }
+
+ LOG(ERROR) << "Unexpected method call: " << method_call->ToString();
+ return nullptr;
+ }
+
+ std::string CallTestMethod(int v1, int v2) {
+ std::unique_ptr<dbus::Response> response =
+ brillo::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(),
+ kTestInterface, kTestMethod1,
+ nullptr, v1, v2);
+ EXPECT_NE(nullptr, response.get());
+ std::string result;
+ using brillo::dbus_utils::ExtractMethodCallResults;
+ EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &result));
+ return result;
+ }
+
+ // Sends a file descriptor received over D-Bus back to the caller.
+ dbus::FileDescriptor EchoFD(const dbus::FileDescriptor& fd_in) {
+ std::unique_ptr<dbus::Response> response =
+ brillo::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(),
+ kTestInterface, kTestMethod4,
+ nullptr, fd_in);
+ EXPECT_NE(nullptr, response.get());
+ dbus::FileDescriptor fd_out;
+ using brillo::dbus_utils::ExtractMethodCallResults;
+ EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &fd_out));
+ return fd_out;
+ }
+
+ scoped_refptr<dbus::MockBus> bus_;
+ scoped_refptr<dbus::MockObjectProxy> mock_object_proxy_;
+};
+
+TEST_F(DBusMethodInvokerTest, TestSuccess) {
+ EXPECT_EQ("4", CallTestMethod(2, 2));
+ EXPECT_EQ("10", CallTestMethod(3, 7));
+ EXPECT_EQ("-4", CallTestMethod(13, -17));
+}
+
+TEST_F(DBusMethodInvokerTest, TestFailure) {
+ brillo::ErrorPtr error;
+ std::unique_ptr<dbus::Response> response =
+ brillo::dbus_utils::CallMethodAndBlock(
+ mock_object_proxy_.get(), kTestInterface, kTestMethod2, &error);
+ EXPECT_EQ(nullptr, response.get());
+ EXPECT_EQ(brillo::errors::dbus::kDomain, error->GetDomain());
+ EXPECT_EQ("org.MyError", error->GetCode());
+ EXPECT_EQ("My error message", error->GetMessage());
+}
+
+TEST_F(DBusMethodInvokerTest, TestFileDescriptors) {
+ // Passing a file descriptor over D-Bus would effectively duplicate the fd.
+ // So the resulting file descriptor value would be different but it still
+ // should be valid.
+ dbus::FileDescriptor fd_stdin(0);
+ fd_stdin.CheckValidity();
+ EXPECT_NE(fd_stdin.value(), EchoFD(fd_stdin).value());
+ dbus::FileDescriptor fd_stdout(1);
+ fd_stdout.CheckValidity();
+ EXPECT_NE(fd_stdout.value(), EchoFD(fd_stdout).value());
+ dbus::FileDescriptor fd_stderr(2);
+ fd_stderr.CheckValidity();
+ EXPECT_NE(fd_stderr.value(), EchoFD(fd_stderr).value());
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Asynchronous method invocation support
+
+class AsyncDBusMethodInvokerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ 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_object_proxy_ = new dbus::MockObjectProxy(
+ bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath));
+ EXPECT_CALL(*bus_,
+ GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath)))
+ .WillRepeatedly(Return(mock_object_proxy_.get()));
+ int def_timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
+ EXPECT_CALL(*mock_object_proxy_,
+ CallMethodWithErrorCallback(_, def_timeout_ms, _, _))
+ .WillRepeatedly(Invoke(this, &AsyncDBusMethodInvokerTest::HandleCall));
+ }
+
+ void TearDown() override { bus_ = nullptr; }
+
+ void HandleCall(dbus::MethodCall* method_call,
+ int /* timeout_ms */,
+ dbus::ObjectProxy::ResponseCallback success_callback,
+ dbus::ObjectProxy::ErrorCallback error_callback) {
+ if (method_call->GetInterface() == kTestInterface) {
+ if (method_call->GetMember() == kTestMethod1) {
+ MessageReader reader(method_call);
+ int v1, v2;
+ // Input: two ints.
+ // Output: sum of the ints converted to string.
+ if (reader.PopInt32(&v1) && reader.PopInt32(&v2)) {
+ auto response = Response::CreateEmpty();
+ MessageWriter writer(response.get());
+ writer.AppendString(std::to_string(v1 + v2));
+ success_callback.Run(response.get());
+ }
+ return;
+ } else if (method_call->GetMember() == kTestMethod2) {
+ method_call->SetSerial(123);
+ auto error_response = dbus::ErrorResponse::FromMethodCall(
+ method_call, "org.MyError", "My error message");
+ error_callback.Run(error_response.get());
+ return;
+ }
+ }
+
+ LOG(FATAL) << "Unexpected method call: " << method_call->ToString();
+ }
+
+ base::Callback<void(const std::string&)> SuccessCallback(
+ const std::string& in_result, int* in_counter) {
+ return base::Bind(
+ [](const std::string& result,
+ int* counter,
+ const std::string& actual_result) {
+ (*counter)++;
+ EXPECT_EQ(result, actual_result);
+ },
+ in_result,
+ base::Unretained(in_counter));
+ }
+
+ base::Callback<void(const std::string&)> SuccessCallback(int* in_counter) {
+ return base::Bind(
+ [](int* counter, const std::string& actual_result) {
+ (*counter)++;
+ EXPECT_EQ("", actual_result);
+ },
+ base::Unretained(in_counter));
+ }
+
+ AsyncErrorCallback ErrorCallback(int* in_counter) {
+ return base::Bind(
+ [](int* counter, brillo::Error* error) {
+ (*counter)++;
+ EXPECT_NE(nullptr, error);
+ EXPECT_EQ("", error->GetDomain());
+ EXPECT_EQ("", error->GetCode());
+ EXPECT_EQ("", error->GetMessage());
+ },
+ base::Unretained(in_counter));
+ }
+
+ AsyncErrorCallback ErrorCallback(const std::string& domain,
+ const std::string& code,
+ const std::string& message,
+ int* in_counter) {
+ return base::Bind(
+ [](const std::string& domain,
+ const std::string& code,
+ const std::string& message,
+ int* counter,
+ brillo::Error* error) {
+ (*counter)++;
+ EXPECT_NE(nullptr, error);
+ EXPECT_EQ(domain, error->GetDomain());
+ EXPECT_EQ(code, error->GetCode());
+ EXPECT_EQ(message, error->GetMessage());
+ },
+ domain,
+ code,
+ message,
+ base::Unretained(in_counter));
+ }
+
+ scoped_refptr<dbus::MockBus> bus_;
+ scoped_refptr<dbus::MockObjectProxy> mock_object_proxy_;
+};
+
+TEST_F(AsyncDBusMethodInvokerTest, TestSuccess) {
+ int error_count = 0;
+ int success_count = 0;
+ brillo::dbus_utils::CallMethod(
+ mock_object_proxy_.get(),
+ kTestInterface,
+ kTestMethod1,
+ base::Bind(SuccessCallback("4", &success_count)),
+ base::Bind(ErrorCallback(&error_count)),
+ 2, 2);
+ brillo::dbus_utils::CallMethod(
+ mock_object_proxy_.get(),
+ kTestInterface,
+ kTestMethod1,
+ base::Bind(SuccessCallback("10", &success_count)),
+ base::Bind(ErrorCallback(&error_count)),
+ 3, 7);
+ brillo::dbus_utils::CallMethod(
+ mock_object_proxy_.get(),
+ kTestInterface,
+ kTestMethod1,
+ base::Bind(SuccessCallback("-4", &success_count)),
+ base::Bind(ErrorCallback(&error_count)),
+ 13, -17);
+ EXPECT_EQ(0, error_count);
+ EXPECT_EQ(3, success_count);
+}
+
+TEST_F(AsyncDBusMethodInvokerTest, TestFailure) {
+ int error_count = 0;
+ int success_count = 0;
+ brillo::dbus_utils::CallMethod(
+ mock_object_proxy_.get(),
+ kTestInterface,
+ kTestMethod2,
+ base::Bind(SuccessCallback(&success_count)),
+ base::Bind(ErrorCallback(brillo::errors::dbus::kDomain,
+ "org.MyError",
+ "My error message",
+ &error_count)),
+ 2, 2);
+ EXPECT_EQ(1, error_count);
+ EXPECT_EQ(0, success_count);
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_method_response.cc b/libbrillo/brillo/dbus/dbus_method_response.cc
new file mode 100644
index 0000000..265f9f3
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_method_response.cc
@@ -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.
+
+#include <brillo/dbus/dbus_method_response.h>
+
+#include <brillo/dbus/utils.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+DBusMethodResponseBase::DBusMethodResponseBase(dbus::MethodCall* method_call,
+ ResponseSender sender)
+ : sender_(sender), method_call_(method_call) {
+}
+
+DBusMethodResponseBase::~DBusMethodResponseBase() {
+ if (method_call_) {
+ // Response hasn't been sent by the handler. Abort the call.
+ Abort();
+ }
+}
+
+void DBusMethodResponseBase::ReplyWithError(const brillo::Error* error) {
+ CheckCanSendResponse();
+ auto response = GetDBusError(method_call_, error);
+ SendRawResponse(std::move(response));
+}
+
+void DBusMethodResponseBase::ReplyWithError(
+ const tracked_objects::Location& location,
+ const std::string& error_domain,
+ const std::string& error_code,
+ const std::string& error_message) {
+ ErrorPtr error;
+ Error::AddTo(&error, location, error_domain, error_code, error_message);
+ ReplyWithError(error.get());
+}
+
+void DBusMethodResponseBase::Abort() {
+ SendRawResponse(std::unique_ptr<dbus::Response>());
+}
+
+void DBusMethodResponseBase::SendRawResponse(
+ std::unique_ptr<dbus::Response> response) {
+ CheckCanSendResponse();
+ method_call_ = nullptr; // Mark response as sent.
+ sender_.Run(std::move(response));
+}
+
+std::unique_ptr<dbus::Response>
+DBusMethodResponseBase::CreateCustomResponse() const {
+ return dbus::Response::FromMethodCall(method_call_);
+}
+
+bool DBusMethodResponseBase::IsResponseSent() const {
+ return (method_call_ == nullptr);
+}
+
+void DBusMethodResponseBase::CheckCanSendResponse() const {
+ CHECK(method_call_) << "Response already sent";
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_method_response.h b/libbrillo/brillo/dbus/dbus_method_response.h
new file mode 100644
index 0000000..0a9ef08
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_method_response.h
@@ -0,0 +1,98 @@
+// 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 LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_RESPONSE_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_RESPONSE_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+#include <brillo/dbus/dbus_param_writer.h>
+#include <brillo/errors/error.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.h>
+
+namespace brillo {
+
+class Error;
+
+namespace dbus_utils {
+
+using ResponseSender = dbus::ExportedObject::ResponseSender;
+
+// DBusMethodResponseBase is a helper class used with asynchronous D-Bus method
+// handlers to encapsulate the information needed to send the method call
+// response when it is available.
+class BRILLO_EXPORT DBusMethodResponseBase {
+ public:
+ DBusMethodResponseBase(dbus::MethodCall* method_call, ResponseSender sender);
+ virtual ~DBusMethodResponseBase();
+
+ // Sends an error response. Marshals the |error| object over D-Bus.
+ // If |error| is from the "dbus" error domain, takes the |error_code| from
+ // |error| and uses it as the DBus error name.
+ // For error is from other domains, the full error information (domain, error
+ // code, error message) is encoded into the D-Bus error message and returned
+ // to the caller as "org.freedesktop.DBus.Failed".
+ void ReplyWithError(const brillo::Error* error);
+
+ // Constructs brillo::Error object from the parameters specified and send
+ // the error information over D-Bus using the method above.
+ void ReplyWithError(const tracked_objects::Location& location,
+ const std::string& error_domain,
+ const std::string& error_code,
+ const std::string& error_message);
+
+ // Sends a raw D-Bus response message.
+ void SendRawResponse(std::unique_ptr<dbus::Response> response);
+
+ // Creates a custom response object for the current method call.
+ std::unique_ptr<dbus::Response> CreateCustomResponse() const;
+
+ // Checks if the response has been sent already.
+ bool IsResponseSent() const;
+
+ protected:
+ void CheckCanSendResponse() const;
+
+ // Aborts the method execution. Does not send any response message.
+ void Abort();
+
+ private:
+ // A callback to be called to send the method call response message.
+ ResponseSender sender_;
+ // |method_call_| is actually owned by |sender_| (it is embedded as unique_ptr
+ // in the bound parameter list in the Callback). We set it to nullptr after
+ // the method call response has been sent to ensure we can't possibly try
+ // to send a response again somehow.
+ dbus::MethodCall* method_call_;
+
+ DISALLOW_COPY_AND_ASSIGN(DBusMethodResponseBase);
+};
+
+// DBusMethodResponse is an explicitly-typed version of DBusMethodResponse.
+// Using DBusMethodResponse<Types...> indicates the types a D-Bus method
+// is expected to return.
+template<typename... Types>
+class DBusMethodResponse : public DBusMethodResponseBase {
+ public:
+ // Make the base class's custom constructor available to DBusMethodResponse.
+ using DBusMethodResponseBase::DBusMethodResponseBase;
+
+ // Sends the a successful response. |return_values| can contain a list
+ // of return values to be sent to the caller.
+ inline void Return(const Types&... return_values) {
+ CheckCanSendResponse();
+ auto response = CreateCustomResponse();
+ dbus::MessageWriter writer(response.get());
+ DBusParamWriter::Append(&writer, return_values...);
+ SendRawResponse(std::move(response));
+ }
+};
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_METHOD_RESPONSE_H_
diff --git a/libbrillo/brillo/dbus/dbus_object.cc b/libbrillo/brillo/dbus/dbus_object.cc
new file mode 100644
index 0000000..59350a5
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_object.cc
@@ -0,0 +1,279 @@
+// 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 <brillo/dbus/dbus_object.h>
+
+#include <vector>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <brillo/dbus/async_event_sequencer.h>
+#include <brillo/dbus/exported_object_manager.h>
+#include <brillo/dbus/exported_property_set.h>
+#include <dbus/property.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+//////////////////////////////////////////////////////////////////////////////
+
+DBusInterface::DBusInterface(DBusObject* dbus_object,
+ const std::string& interface_name)
+ : dbus_object_(dbus_object), interface_name_(interface_name) {
+}
+
+void DBusInterface::AddProperty(const std::string& property_name,
+ ExportedPropertyBase* prop_base) {
+ dbus_object_->property_set_.RegisterProperty(
+ interface_name_, property_name, prop_base);
+}
+
+void DBusInterface::ExportAsync(
+ ExportedObjectManager* object_manager,
+ dbus::Bus* /* bus */,
+ dbus::ExportedObject* exported_object,
+ const dbus::ObjectPath& object_path,
+ const AsyncEventSequencer::CompletionAction& completion_callback) {
+ VLOG(1) << "Registering D-Bus interface '" << interface_name_ << "' for '"
+ << object_path.value() << "'";
+ scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+ for (const auto& pair : handlers_) {
+ std::string method_name = pair.first;
+ VLOG(1) << "Exporting method: " << interface_name_ << "." << method_name;
+ std::string export_error = "Failed exporting " + method_name + " method";
+ auto export_handler = sequencer->GetExportHandler(
+ interface_name_, method_name, export_error, true);
+ auto method_handler =
+ base::Bind(&DBusInterface::HandleMethodCall, base::Unretained(this));
+ exported_object->ExportMethod(
+ interface_name_, method_name, method_handler, export_handler);
+ }
+
+ std::vector<AsyncEventSequencer::CompletionAction> actions;
+ if (object_manager) {
+ auto property_writer_callback =
+ dbus_object_->property_set_.GetPropertyWriter(interface_name_);
+ actions.push_back(
+ base::Bind(&DBusInterface::ClaimInterface,
+ weak_factory_.GetWeakPtr(),
+ object_manager->AsWeakPtr(),
+ object_path,
+ property_writer_callback));
+ }
+ actions.push_back(completion_callback);
+ sequencer->OnAllTasksCompletedCall(actions);
+}
+
+void DBusInterface::ExportAndBlock(
+ ExportedObjectManager* object_manager,
+ dbus::Bus* /* bus */,
+ dbus::ExportedObject* exported_object,
+ const dbus::ObjectPath& object_path) {
+ VLOG(1) << "Registering D-Bus interface '" << interface_name_ << "' for '"
+ << object_path.value() << "'";
+ for (const auto& pair : handlers_) {
+ std::string method_name = pair.first;
+ VLOG(1) << "Exporting method: " << interface_name_ << "." << method_name;
+ auto method_handler =
+ base::Bind(&DBusInterface::HandleMethodCall, base::Unretained(this));
+ if (!exported_object->ExportMethodAndBlock(
+ interface_name_, method_name, method_handler)) {
+ LOG(FATAL) << "Failed exporting " << method_name << " method";
+ }
+ }
+
+ if (object_manager) {
+ auto property_writer_callback =
+ dbus_object_->property_set_.GetPropertyWriter(interface_name_);
+ ClaimInterface(object_manager->AsWeakPtr(),
+ object_path,
+ property_writer_callback,
+ true);
+ }
+}
+
+void DBusInterface::ClaimInterface(
+ base::WeakPtr<ExportedObjectManager> object_manager,
+ const dbus::ObjectPath& object_path,
+ const ExportedPropertySet::PropertyWriter& writer,
+ bool all_succeeded) {
+ if (!all_succeeded || !object_manager) {
+ LOG(ERROR) << "Skipping claiming interface: " << interface_name_;
+ return;
+ }
+ object_manager->ClaimInterface(object_path, interface_name_, writer);
+ release_interface_cb_.ReplaceClosure(
+ base::Bind(&ExportedObjectManager::ReleaseInterface,
+ object_manager, object_path, interface_name_));
+}
+
+void DBusInterface::HandleMethodCall(dbus::MethodCall* method_call,
+ ResponseSender sender) {
+ std::string method_name = method_call->GetMember();
+ // Make a local copy of |interface_name_| because calling HandleMethod()
+ // can potentially kill this interface object...
+ std::string interface_name = interface_name_;
+ VLOG(1) << "Received method call request: " << interface_name << "."
+ << method_name << "(" << method_call->GetSignature() << ")";
+ auto pair = handlers_.find(method_name);
+ if (pair == handlers_.end()) {
+ auto response =
+ dbus::ErrorResponse::FromMethodCall(method_call,
+ DBUS_ERROR_UNKNOWN_METHOD,
+ "Unknown method: " + method_name);
+ sender.Run(std::move(response));
+ return;
+ }
+ VLOG(1) << "Dispatching DBus method call: " << method_name;
+ pair->second->HandleMethod(method_call, sender);
+}
+
+void DBusInterface::AddHandlerImpl(
+ const std::string& method_name,
+ std::unique_ptr<DBusInterfaceMethodHandlerInterface> handler) {
+ VLOG(1) << "Declaring method handler: " << interface_name_ << "."
+ << method_name;
+ auto res = handlers_.insert(std::make_pair(method_name, std::move(handler)));
+ CHECK(res.second) << "Method '" << method_name << "' already exists";
+}
+
+void DBusInterface::AddSignalImpl(
+ const std::string& signal_name,
+ const std::shared_ptr<DBusSignalBase>& signal) {
+ VLOG(1) << "Declaring a signal sink: " << interface_name_ << "."
+ << signal_name;
+ CHECK(signals_.insert(std::make_pair(signal_name, signal)).second)
+ << "The signal '" << signal_name << "' is already registered";
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+DBusObject::DBusObject(ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ const dbus::ObjectPath& object_path)
+ : property_set_(bus.get()), bus_(bus), object_path_(object_path) {
+ if (object_manager)
+ object_manager_ = object_manager->AsWeakPtr();
+}
+
+DBusObject::~DBusObject() {
+ if (exported_object_)
+ exported_object_->Unregister();
+}
+
+DBusInterface* DBusObject::AddOrGetInterface(
+ const std::string& interface_name) {
+ auto iter = interfaces_.find(interface_name);
+ if (iter == interfaces_.end()) {
+ VLOG(1) << "Adding an interface '" << interface_name << "' to object '"
+ << object_path_.value() << "'.";
+ // Interface doesn't exist yet. Create one...
+ std::unique_ptr<DBusInterface> new_itf(
+ new DBusInterface(this, interface_name));
+ iter = interfaces_.insert(std::make_pair(interface_name,
+ std::move(new_itf))).first;
+ }
+ return iter->second.get();
+}
+
+DBusInterface* DBusObject::FindInterface(
+ const std::string& interface_name) const {
+ auto itf_iter = interfaces_.find(interface_name);
+ return (itf_iter == interfaces_.end()) ? nullptr : itf_iter->second.get();
+}
+
+void DBusObject::RegisterAsync(
+ const AsyncEventSequencer::CompletionAction& completion_callback) {
+ VLOG(1) << "Registering D-Bus object '" << object_path_.value() << "'.";
+ CHECK(exported_object_ == nullptr) << "Object already registered.";
+ scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+ exported_object_ = bus_->GetExportedObject(object_path_);
+
+ // Add the org.freedesktop.DBus.Properties interface to the object.
+ DBusInterface* prop_interface = AddOrGetInterface(dbus::kPropertiesInterface);
+ prop_interface->AddSimpleMethodHandler(
+ dbus::kPropertiesGetAll,
+ base::Unretained(&property_set_),
+ &ExportedPropertySet::HandleGetAll);
+ prop_interface->AddSimpleMethodHandlerWithError(
+ dbus::kPropertiesGet,
+ base::Unretained(&property_set_),
+ &ExportedPropertySet::HandleGet);
+ prop_interface->AddSimpleMethodHandlerWithError(
+ dbus::kPropertiesSet,
+ base::Unretained(&property_set_),
+ &ExportedPropertySet::HandleSet);
+ property_set_.OnPropertiesInterfaceExported(prop_interface);
+
+ // Export interface methods
+ for (const auto& pair : interfaces_) {
+ pair.second->ExportAsync(
+ object_manager_.get(),
+ bus_.get(),
+ exported_object_,
+ object_path_,
+ sequencer->GetHandler("Failed to export interface " + pair.first,
+ false));
+ }
+
+ sequencer->OnAllTasksCompletedCall({completion_callback});
+}
+
+void DBusObject::RegisterAndBlock() {
+ VLOG(1) << "Registering D-Bus object '" << object_path_.value() << "'.";
+ CHECK(exported_object_ == nullptr) << "Object already registered.";
+ exported_object_ = bus_->GetExportedObject(object_path_);
+
+ // Add the org.freedesktop.DBus.Properties interface to the object.
+ DBusInterface* prop_interface = AddOrGetInterface(dbus::kPropertiesInterface);
+ prop_interface->AddSimpleMethodHandler(
+ dbus::kPropertiesGetAll,
+ base::Unretained(&property_set_),
+ &ExportedPropertySet::HandleGetAll);
+ prop_interface->AddSimpleMethodHandlerWithError(
+ dbus::kPropertiesGet,
+ base::Unretained(&property_set_),
+ &ExportedPropertySet::HandleGet);
+ prop_interface->AddSimpleMethodHandlerWithError(
+ dbus::kPropertiesSet,
+ base::Unretained(&property_set_),
+ &ExportedPropertySet::HandleSet);
+ property_set_.OnPropertiesInterfaceExported(prop_interface);
+
+ // Export interface methods
+ for (const auto& pair : interfaces_) {
+ pair.second->ExportAndBlock(
+ object_manager_.get(),
+ bus_.get(),
+ exported_object_,
+ object_path_);
+ }
+}
+
+void DBusObject::UnregisterAsync() {
+ VLOG(1) << "Unregistering D-Bus object '" << object_path_.value() << "'.";
+ CHECK(exported_object_ != nullptr) << "Object not registered.";
+
+ // This will unregister the object path from the bus.
+ exported_object_->Unregister();
+ // This will remove |exported_object_| from bus's object table. This function
+ // will also post a task to unregister |exported_object_| (same as the call
+ // above), which will be a no-op since it is already done by then.
+ // By doing both in here, the object path is guarantee to be reusable upon
+ // return from this function.
+ bus_->UnregisterExportedObject(object_path_);
+ exported_object_ = nullptr;
+}
+
+bool DBusObject::SendSignal(dbus::Signal* signal) {
+ if (exported_object_) {
+ exported_object_->SendSignal(signal);
+ return true;
+ }
+ LOG(ERROR) << "Trying to send a signal from an object that is not exported";
+ return false;
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_object.h b/libbrillo/brillo/dbus/dbus_object.h
new file mode 100644
index 0000000..6cd34dd
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_object.h
@@ -0,0 +1,579 @@
+// 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.
+
+// DBusObject is a special helper class that simplifies the implementation of
+// D-Bus objects in C++. It provides an easy way to define interfaces with
+// methods and properties and offloads a lot of work to register the object and
+// all of its interfaces, to marshal method calls (by converting D-Bus method
+// parameters to native C++ types and invoking native method handlers), etc.
+
+// The basic usage pattern of this class is as follows:
+/*
+class MyDbusObject {
+ public:
+ MyDbusObject(ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus)
+ : dbus_object_(object_manager, bus,
+ dbus::ObjectPath("/org/chromium/my_obj")) {}
+
+ void Init(const AsyncEventSequencer::CompletionAction& callback) {
+ DBusInterface* my_interface =
+ dbus_object_.AddOrGetInterface("org.chromium.MyInterface");
+ my_interface->AddSimpleMethodHandler("Method1", this,
+ &MyDbusObject::Method1);
+ my_interface->AddSimpleMethodHandlerWithError("Method2", this,
+ &MyDbusObject::Method2);
+ my_interface->AddMethodHandler("Method3", this, &MyDbusObject::Method3);
+ my_interface->AddProperty("Property1", &prop1_);
+ my_interface->AddProperty("Property2", &prop2_);
+ prop1_.SetValue("prop1_value");
+ prop2_.SetValue(50);
+ // Register the object by exporting its methods and properties and
+ // exposing them to D-Bus clients.
+ dbus_object_.RegisterAsync(callback);
+ }
+
+ private:
+ DBusObject dbus_object_;
+
+ // Make sure the properties outlive the DBusObject they are registered with.
+ brillo::dbus_utils::ExportedProperty<std::string> prop1_;
+ brillo::dbus_utils::ExportedProperty<int> prop2_;
+ int Method1() { return 5; }
+ bool Method2(brillo::ErrorPtr* error, const std::string& message);
+ void Method3(std::unique_ptr<DBusMethodResponse<int_32>> response,
+ const std::string& message) {
+ if (message.empty()) {
+ response->ReplyWithError(brillo::errors::dbus::kDomain,
+ DBUS_ERROR_INVALID_ARGS,
+ "Message string cannot be empty");
+ return;
+ }
+ int32_t message_len = message.length();
+ response->Return(message_len);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(MyDbusObject);
+};
+*/
+
+#ifndef LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_H_
+
+#include <map>
+#include <string>
+
+#include <base/bind.h>
+#include <base/callback_helpers.h>
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <brillo/brillo_export.h>
+#include <brillo/dbus/async_event_sequencer.h>
+#include <brillo/dbus/dbus_object_internal_impl.h>
+#include <brillo/dbus/dbus_signal.h>
+#include <brillo/dbus/exported_property_set.h>
+#include <brillo/errors/error.h>
+#include <dbus/bus.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.h>
+#include <dbus/object_path.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+class ExportedObjectManager;
+class ExportedPropertyBase;
+class DBusObject;
+
+// This is an implementation proxy class for a D-Bus interface of an object.
+// The important functionality for the users is the ability to add D-Bus method
+// handlers and define D-Bus object properties. This is achieved by using one
+// of the overload of AddSimpleMethodHandler()/AddMethodHandler() and
+// AddProperty() respectively.
+// There are three overloads for DBusInterface::AddSimpleMethodHandler() and
+// AddMethodHandler() each:
+// 1. That takes a handler as base::Callback
+// 2. That takes a static function
+// 3. That takes a class instance pointer and a class member function
+// The signature of the handler for AddSimpleMethodHandler must be one of:
+// R(Args... args) [IN only]
+// void(Args... args) [IN/OUT]
+// The signature of the handler for AddSimpleMethodHandlerWithError must be:
+// bool(ErrorPtr* error, Args... args) [IN/OUT]
+// The signature of the handler for AddSimpleMethodHandlerWithErrorAndMessage:
+// bool(ErrorPtr* error, dbus::Message* msg, Args... args) [IN/OUT]
+// The signature of the handler for AddMethodHandler must be:
+// void(std::unique_ptr<DBusMethodResponse<T...>> response,
+// Args... args) [IN]
+// The signature of the handler for AddMethodHandlerWithMessage must be:
+// void(std::unique_ptr<DBusMethodResponse<T...>> response,
+// dbus::Message* msg, Args... args) [IN]
+// There is also an AddRawMethodHandler() call that lets provide a custom
+// handler that can parse its own input parameter and construct a custom
+// response.
+// The signature of the handler for AddRawMethodHandler must be:
+// void(dbus::MethodCall* method_call, ResponseSender sender)
+class BRILLO_EXPORT DBusInterface final {
+ public:
+ DBusInterface(DBusObject* dbus_object, const std::string& interface_name);
+
+ // Register sync DBus method handler for |method_name| as base::Callback.
+ template<typename R, typename... Args>
+ inline void AddSimpleMethodHandler(
+ const std::string& method_name,
+ const base::Callback<R(Args...)>& handler) {
+ Handler<SimpleDBusInterfaceMethodHandler<R, Args...>>::Add(
+ this, method_name, handler);
+ }
+
+ // Register sync D-Bus method handler for |method_name| as a static
+ // function.
+ template<typename R, typename... Args>
+ inline void AddSimpleMethodHandler(const std::string& method_name,
+ R(*handler)(Args...)) {
+ Handler<SimpleDBusInterfaceMethodHandler<R, Args...>>::Add(
+ this, method_name, base::Bind(handler));
+ }
+
+ // Register sync D-Bus method handler for |method_name| as a class member
+ // function.
+ template<typename Instance, typename Class, typename R, typename... Args>
+ inline void AddSimpleMethodHandler(const std::string& method_name,
+ Instance instance,
+ R(Class::*handler)(Args...)) {
+ Handler<SimpleDBusInterfaceMethodHandler<R, Args...>>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Same as above but for const-method of a class.
+ template<typename Instance, typename Class, typename R, typename... Args>
+ inline void AddSimpleMethodHandler(const std::string& method_name,
+ Instance instance,
+ R(Class::*handler)(Args...) const) {
+ Handler<SimpleDBusInterfaceMethodHandler<R, Args...>>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Register sync DBus method handler for |method_name| as base::Callback.
+ template<typename... Args>
+ inline void AddSimpleMethodHandlerWithError(
+ const std::string& method_name,
+ const base::Callback<bool(ErrorPtr*, Args...)>& handler) {
+ Handler<SimpleDBusInterfaceMethodHandlerWithError<Args...>>::Add(
+ this, method_name, handler);
+ }
+
+ // Register sync D-Bus method handler for |method_name| as a static
+ // function.
+ template<typename... Args>
+ inline void AddSimpleMethodHandlerWithError(
+ const std::string& method_name,
+ bool(*handler)(ErrorPtr*, Args...)) {
+ Handler<SimpleDBusInterfaceMethodHandlerWithError<Args...>>::Add(
+ this, method_name, base::Bind(handler));
+ }
+
+ // Register sync D-Bus method handler for |method_name| as a class member
+ // function.
+ template<typename Instance, typename Class, typename... Args>
+ inline void AddSimpleMethodHandlerWithError(
+ const std::string& method_name,
+ Instance instance,
+ bool(Class::*handler)(ErrorPtr*, Args...)) {
+ Handler<SimpleDBusInterfaceMethodHandlerWithError<Args...>>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Same as above but for const-method of a class.
+ template<typename Instance, typename Class, typename... Args>
+ inline void AddSimpleMethodHandlerWithError(
+ const std::string& method_name,
+ Instance instance,
+ bool(Class::*handler)(ErrorPtr*, Args...) const) {
+ Handler<SimpleDBusInterfaceMethodHandlerWithError<Args...>>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Register sync DBus method handler for |method_name| as base::Callback.
+ // Passing the method sender as a first parameter to the callback.
+ template<typename... Args>
+ inline void AddSimpleMethodHandlerWithErrorAndMessage(
+ const std::string& method_name,
+ const base::Callback<bool(ErrorPtr*, dbus::Message*, Args...)>&
+ handler) {
+ Handler<SimpleDBusInterfaceMethodHandlerWithErrorAndMessage<Args...>>::Add(
+ this, method_name, handler);
+ }
+
+ // Register sync D-Bus method handler for |method_name| as a static
+ // function. Passing the method D-Bus message as the second parameter to the
+ // callback.
+ template<typename... Args>
+ inline void AddSimpleMethodHandlerWithErrorAndMessage(
+ const std::string& method_name,
+ bool(*handler)(ErrorPtr*, dbus::Message*, Args...)) {
+ Handler<SimpleDBusInterfaceMethodHandlerWithErrorAndMessage<Args...>>::Add(
+ this, method_name, base::Bind(handler));
+ }
+
+ // Register sync D-Bus method handler for |method_name| as a class member
+ // function. Passing the method D-Bus message as the second parameter to the
+ // callback.
+ template<typename Instance, typename Class, typename... Args>
+ inline void AddSimpleMethodHandlerWithErrorAndMessage(
+ const std::string& method_name,
+ Instance instance,
+ bool(Class::*handler)(ErrorPtr*, dbus::Message*, Args...)) {
+ Handler<SimpleDBusInterfaceMethodHandlerWithErrorAndMessage<Args...>>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Same as above but for const-method of a class.
+ template<typename Instance, typename Class, typename... Args>
+ inline void AddSimpleMethodHandlerWithErrorAndMessage(
+ const std::string& method_name,
+ Instance instance,
+ bool(Class::*handler)(ErrorPtr*, dbus::Message*, Args...) const) {
+ Handler<SimpleDBusInterfaceMethodHandlerWithErrorAndMessage<Args...>>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Register an async DBus method handler for |method_name| as base::Callback.
+ template<typename Response, typename... Args>
+ inline void AddMethodHandler(
+ const std::string& method_name,
+ const base::Callback<void(std::unique_ptr<Response>, Args...)>& handler) {
+ static_assert(std::is_base_of<DBusMethodResponseBase, Response>::value,
+ "Response must be DBusMethodResponse<T...>");
+ Handler<DBusInterfaceMethodHandler<Response, Args...>>::Add(
+ this, method_name, handler);
+ }
+
+ // Register an async D-Bus method handler for |method_name| as a static
+ // function.
+ template<typename Response, typename... Args>
+ inline void AddMethodHandler(
+ const std::string& method_name,
+ void (*handler)(std::unique_ptr<Response>, Args...)) {
+ static_assert(std::is_base_of<DBusMethodResponseBase, Response>::value,
+ "Response must be DBusMethodResponse<T...>");
+ Handler<DBusInterfaceMethodHandler<Response, Args...>>::Add(
+ this, method_name, base::Bind(handler));
+ }
+
+ // Register an async D-Bus method handler for |method_name| as a class member
+ // function.
+ template<typename Response,
+ typename Instance,
+ typename Class,
+ typename... Args>
+ inline void AddMethodHandler(
+ const std::string& method_name,
+ Instance instance,
+ void(Class::*handler)(std::unique_ptr<Response>, Args...)) {
+ static_assert(std::is_base_of<DBusMethodResponseBase, Response>::value,
+ "Response must be DBusMethodResponse<T...>");
+ Handler<DBusInterfaceMethodHandler<Response, Args...>>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Same as above but for const-method of a class.
+ template<typename Response,
+ typename Instance,
+ typename Class,
+ typename... Args>
+ inline void AddMethodHandler(
+ const std::string& method_name,
+ Instance instance,
+ void(Class::*handler)(std::unique_ptr<Response>, Args...) const) {
+ static_assert(std::is_base_of<DBusMethodResponseBase, Response>::value,
+ "Response must be DBusMethodResponse<T...>");
+ Handler<DBusInterfaceMethodHandler<Response, Args...>>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Register an async DBus method handler for |method_name| as base::Callback.
+ template<typename Response, typename... Args>
+ inline void AddMethodHandlerWithMessage(
+ const std::string& method_name,
+ const base::Callback<void(std::unique_ptr<Response>, dbus::Message*,
+ Args...)>& handler) {
+ static_assert(std::is_base_of<DBusMethodResponseBase, Response>::value,
+ "Response must be DBusMethodResponse<T...>");
+ Handler<DBusInterfaceMethodHandlerWithMessage<Response, Args...>>::Add(
+ this, method_name, handler);
+ }
+
+ // Register an async D-Bus method handler for |method_name| as a static
+ // function.
+ template<typename Response, typename... Args>
+ inline void AddMethodHandlerWithMessage(
+ const std::string& method_name,
+ void (*handler)(std::unique_ptr<Response>, dbus::Message*, Args...)) {
+ static_assert(std::is_base_of<DBusMethodResponseBase, Response>::value,
+ "Response must be DBusMethodResponse<T...>");
+ Handler<DBusInterfaceMethodHandlerWithMessage<Response, Args...>>::Add(
+ this, method_name, base::Bind(handler));
+ }
+
+ // Register an async D-Bus method handler for |method_name| as a class member
+ // function.
+ template<typename Response,
+ typename Instance,
+ typename Class,
+ typename... Args>
+ inline void AddMethodHandlerWithMessage(
+ const std::string& method_name,
+ Instance instance,
+ void(Class::*handler)(std::unique_ptr<Response>,
+ dbus::Message*, Args...)) {
+ static_assert(std::is_base_of<DBusMethodResponseBase, Response>::value,
+ "Response must be DBusMethodResponse<T...>");
+ Handler<DBusInterfaceMethodHandlerWithMessage<Response, Args...>>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Same as above but for const-method of a class.
+ template<typename Response,
+ typename Instance,
+ typename Class,
+ typename... Args>
+ inline void AddMethodHandlerWithMessage(
+ const std::string& method_name,
+ Instance instance,
+ void(Class::*handler)(std::unique_ptr<Response>, dbus::Message*,
+ Args...) const) {
+ static_assert(std::is_base_of<DBusMethodResponseBase, Response>::value,
+ "Response must be DBusMethodResponse<T...>");
+ Handler<DBusInterfaceMethodHandlerWithMessage<Response, Args...>>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Register a raw D-Bus method handler for |method_name| as base::Callback.
+ inline void AddRawMethodHandler(
+ const std::string& method_name,
+ const base::Callback<void(dbus::MethodCall*, ResponseSender)>& handler) {
+ Handler<RawDBusInterfaceMethodHandler>::Add(this, method_name, handler);
+ }
+
+ // Register a raw D-Bus method handler for |method_name| as a class member
+ // function.
+ template<typename Instance, typename Class>
+ inline void AddRawMethodHandler(
+ const std::string& method_name,
+ Instance instance,
+ void(Class::*handler)(dbus::MethodCall*, ResponseSender)) {
+ Handler<RawDBusInterfaceMethodHandler>::Add(
+ this, method_name, base::Bind(handler, instance));
+ }
+
+ // Register a D-Bus property.
+ void AddProperty(const std::string& property_name,
+ ExportedPropertyBase* prop_base);
+
+ // Registers a D-Bus signal that has a specified number and types (|Args|) of
+ // arguments. Returns a weak pointer to the DBusSignal object which can be
+ // used to send the signal on this interface when needed:
+ /*
+ DBusInterface* itf = dbus_object->AddOrGetInterface("Interface");
+ auto signal = itf->RegisterSignal<int, bool>("MySignal");
+ ...
+ // Send the Interface.MySig(12, true) signal.
+ if (signal.lock()->Send(12, true)) { ... }
+ */
+ // Or if the signal signature is long or complex, you can alias the
+ // DBusSignal<Args...> signal type and use RegisterSignalOfType method
+ // instead:
+ /*
+ DBusInterface* itf = dbus_object->AddOrGetInterface("Interface");
+ using MySignal = DBusSignal<int, bool>;
+ auto signal = itf->RegisterSignalOfType<MySignal>("MySignal");
+ ...
+ // Send the Interface.MySig(12, true) signal.
+ if (signal.lock()->Send(12, true)) { ... }
+ */
+ // If the signal with the given name was already registered, the existing
+ // copy of the signal proxy object is returned as long as the method signature
+ // of the original signal matches the current call. If it doesn't, the method
+ // aborts.
+
+ // RegisterSignalOfType can be used to create a signal if the type of the
+ // complete DBusSignal<Args...> class which is pre-defined/aliased earlier.
+ template<typename DBusSignalType>
+ inline std::weak_ptr<DBusSignalType> RegisterSignalOfType(
+ const std::string& signal_name) {
+ auto signal = std::make_shared<DBusSignalType>(
+ dbus_object_, interface_name_, signal_name);
+ AddSignalImpl(signal_name, signal);
+ return signal;
+ }
+
+ // For simple signal arguments, you can specify their types directly in
+ // RegisterSignal<t1, t2, ...>():
+ // auto signal = itf->RegisterSignal<int>("SignalName");
+ // This will create a callback signal object that expects one int argument.
+ template<typename... Args>
+ inline std::weak_ptr<DBusSignal<Args...>> RegisterSignal(
+ const std::string& signal_name) {
+ return RegisterSignalOfType<DBusSignal<Args...>>(signal_name);
+ }
+
+ private:
+ // Helper to create an instance of DBusInterfaceMethodHandlerInterface-derived
+ // handler and add it to the method handler map of the interface.
+ // This makes the actual AddXXXMethodHandler() methods very light-weight and
+ // easier to provide different overloads for various method handler kinds.
+ // Using struct here to allow partial specialization on HandlerType while
+ // letting the compiler to deduce the type of the callback without explicitly
+ // specifying it.
+ template<typename HandlerType>
+ struct Handler {
+ template<typename CallbackType>
+ inline static void Add(DBusInterface* self,
+ const std::string& method_name,
+ const CallbackType& callback) {
+ std::unique_ptr<DBusInterfaceMethodHandlerInterface> sync_method_handler(
+ new HandlerType(callback));
+ self->AddHandlerImpl(method_name, std::move(sync_method_handler));
+ }
+ };
+ // A generic D-Bus method handler for the interface. It extracts the method
+ // name from |method_call|, looks up a registered handler from |handlers_|
+ // map and dispatched the call to that handler.
+ void HandleMethodCall(dbus::MethodCall* method_call, ResponseSender sender);
+ // Helper to add a handler for method |method_name| to the |handlers_| map.
+ // Not marked BRILLO_PRIVATE because it needs to be called by the inline
+ // template functions AddMethodHandler(...)
+ void AddHandlerImpl(
+ const std::string& method_name,
+ std::unique_ptr<DBusInterfaceMethodHandlerInterface> handler);
+ // Helper to add a signal object to the |signals_| map.
+ // Not marked BRILLO_PRIVATE because it needs to be called by the inline
+ // template function RegisterSignalOfType(...)
+ void AddSignalImpl(const std::string& signal_name,
+ const std::shared_ptr<DBusSignalBase>& signal);
+ // Exports all the methods and properties of this interface and claims the
+ // D-Bus interface.
+ // object_manager - ExportedObjectManager instance that notifies D-Bus
+ // listeners of a new interface being claimed.
+ // exported_object - instance of D-Bus object the interface is being added to.
+ // object_path - D-Bus object path for the object instance.
+ // interface_name - name of interface being registered.
+ // completion_callback - a callback to be called when the asynchronous
+ // registration operation is completed.
+ BRILLO_PRIVATE void ExportAsync(
+ ExportedObjectManager* object_manager,
+ dbus::Bus* bus,
+ dbus::ExportedObject* exported_object,
+ const dbus::ObjectPath& object_path,
+ const AsyncEventSequencer::CompletionAction& completion_callback);
+ // Exports all the methods and properties of this interface and claims the
+ // D-Bus interface synchronously.
+ // object_manager - ExportedObjectManager instance that notifies D-Bus
+ // listeners of a new interface being claimed.
+ // exported_object - instance of D-Bus object the interface is being added to.
+ // object_path - D-Bus object path for the object instance.
+ // interface_name - name of interface being registered.
+ BRILLO_PRIVATE void ExportAndBlock(
+ ExportedObjectManager* object_manager,
+ dbus::Bus* bus,
+ dbus::ExportedObject* exported_object,
+ const dbus::ObjectPath& object_path);
+
+ BRILLO_PRIVATE void ClaimInterface(
+ base::WeakPtr<ExportedObjectManager> object_manager,
+ const dbus::ObjectPath& object_path,
+ const ExportedPropertySet::PropertyWriter& writer,
+ bool all_succeeded);
+
+ // Method registration map.
+ std::map<std::string, std::unique_ptr<DBusInterfaceMethodHandlerInterface>>
+ handlers_;
+ // Signal registration map.
+ std::map<std::string, std::shared_ptr<DBusSignalBase>> signals_;
+
+ friend class DBusObject;
+ friend class DBusInterfaceTestHelper;
+ DBusObject* dbus_object_;
+ std::string interface_name_;
+ base::ScopedClosureRunner release_interface_cb_;
+
+ base::WeakPtrFactory<DBusInterface> weak_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(DBusInterface);
+};
+
+// A D-Bus object implementation class. Manages the interfaces implemented
+// by this object.
+class BRILLO_EXPORT DBusObject {
+ public:
+ // object_manager - ExportedObjectManager instance that notifies D-Bus
+ // listeners of a new interface being claimed and property
+ // changes on those interfaces.
+ // object_path - D-Bus object path for the object instance.
+ DBusObject(ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ const dbus::ObjectPath& object_path);
+ virtual ~DBusObject();
+
+ // Returns an proxy handler for the interface |interface_name|. If the
+ // interface proxy does not exist yet, it will be automatically created.
+ DBusInterface* AddOrGetInterface(const std::string& interface_name);
+
+ // Finds an interface with the given name. Returns nullptr if there is no
+ // interface registered by this name.
+ DBusInterface* FindInterface(const std::string& interface_name) const;
+
+ // Registers the object instance with D-Bus. This is an asynchronous call
+ // that will call |completion_callback| when the object and all of its
+ // interfaces are registered.
+ virtual void RegisterAsync(
+ const AsyncEventSequencer::CompletionAction& completion_callback);
+
+ // Registers the object instance with D-Bus. This is call is synchronous and
+ // will block until the object and all of its interfaces are registered.
+ virtual void RegisterAndBlock();
+
+ // Unregister the object instance with D-Bus. This will unregister the
+ // |exported_object_| and its path from the bus. The destruction of
+ // |exported_object_| will be deferred in an async task posted by the bus.
+ // It is guarantee that upon return from this call a new DBusObject with the
+ // same object path can be created/registered.
+ virtual void UnregisterAsync();
+
+ // Returns the ExportedObjectManager proxy, if any. If DBusObject has been
+ // constructed without an object manager, this method returns an empty
+ // smart pointer (containing nullptr).
+ const base::WeakPtr<ExportedObjectManager>& GetObjectManager() const {
+ return object_manager_;
+ }
+
+ // Sends a signal from the exported D-Bus object.
+ bool SendSignal(dbus::Signal* signal);
+
+ // Returns the reference to dbus::Bus this object is associated with.
+ scoped_refptr<dbus::Bus> GetBus() { return bus_; }
+
+ private:
+ // A map of all the interfaces added to this object.
+ std::map<std::string, std::unique_ptr<DBusInterface>> interfaces_;
+ // Exported property set for properties registered with the interfaces
+ // implemented by this D-Bus object.
+ ExportedPropertySet property_set_;
+ // Delegate object implementing org.freedesktop.DBus.ObjectManager interface.
+ base::WeakPtr<ExportedObjectManager> object_manager_;
+ // D-Bus bus object.
+ scoped_refptr<dbus::Bus> bus_;
+ // D-Bus object path for this object.
+ dbus::ObjectPath object_path_;
+ // D-Bus object instance once this object is successfully exported.
+ dbus::ExportedObject* exported_object_ = nullptr; // weak; owned by |bus_|.
+
+ friend class DBusInterface;
+ DISALLOW_COPY_AND_ASSIGN(DBusObject);
+};
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_H_
diff --git a/libbrillo/brillo/dbus/dbus_object_internal_impl.h b/libbrillo/brillo/dbus/dbus_object_internal_impl.h
new file mode 100644
index 0000000..3c5e8d7
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_object_internal_impl.h
@@ -0,0 +1,363 @@
+// 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 file provides internal implementation details of dispatching D-Bus
+// method calls to a D-Bus object methods by reading the expected parameter
+// values from D-Bus message buffer then invoking a native C++ callback with
+// those parameters passed in. If the callback returns a value, that value is
+// sent back to the caller of D-Bus method via the response message.
+
+// This is achieved by redirecting the parsing of parameter values from D-Bus
+// message buffer to DBusParamReader helper class.
+// DBusParamReader de-serializes the parameter values from the D-Bus message
+// and calls the provided native C++ callback with those arguments.
+// However it expects the callback with a simple signature like this:
+// void callback(Args...);
+// The method handlers for DBusObject, on the other hand, have one of the
+// following signatures:
+// void handler(Args...);
+// ReturnType handler(Args...);
+// bool handler(ErrorPtr* error, Args...);
+// void handler(std::unique_ptr<DBusMethodResponse<T1, T2,...>>, Args...);
+//
+// To make this all work, we craft a simple callback suitable for
+// DBusParamReader using a lambda in DBusInvoker::Invoke() and redirect the call
+// to the appropriate method handler using additional data captured by the
+// lambda object.
+
+#ifndef LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_INTERNAL_IMPL_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_INTERNAL_IMPL_H_
+
+#include <memory>
+#include <string>
+#include <type_traits>
+
+#include <brillo/dbus/data_serialization.h>
+#include <brillo/dbus/dbus_method_response.h>
+#include <brillo/dbus/dbus_param_reader.h>
+#include <brillo/dbus/dbus_param_writer.h>
+#include <brillo/dbus/utils.h>
+#include <brillo/errors/error.h>
+#include <dbus/message.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+// This is an abstract base class to allow dispatching a native C++ callback
+// method when a corresponding D-Bus method is called.
+class DBusInterfaceMethodHandlerInterface {
+ public:
+ virtual ~DBusInterfaceMethodHandlerInterface() = default;
+
+ // Returns true if the method has been handled synchronously (whether or not
+ // a success or error response message had been sent).
+ virtual void HandleMethod(dbus::MethodCall* method_call,
+ ResponseSender sender) = 0;
+};
+
+// This is a special implementation of DBusInterfaceMethodHandlerInterface for
+// extremely simple synchronous method handlers that cannot possibly fail
+// (that is, they do not send an error response).
+// The handler is expected to take an arbitrary number of arguments of type
+// |Args...| which can contain both inputs (passed in by value or constant
+// reference) and outputs (passed in as pointers)...
+// It may also return a single value of type R (or could be a void function if
+// no return value is to be sent to the caller). If the handler has a return
+// value, then it cannot have any output parameters in its parameter list.
+// The signature of the callback handler is expected to be:
+// R(Args...)
+template<typename R, typename... Args>
+class SimpleDBusInterfaceMethodHandler
+ : public DBusInterfaceMethodHandlerInterface {
+ public:
+ // A constructor that takes a |handler| to be called when HandleMethod()
+ // virtual function is invoked.
+ explicit SimpleDBusInterfaceMethodHandler(
+ const base::Callback<R(Args...)>& handler) : handler_(handler) {}
+
+ void HandleMethod(dbus::MethodCall* method_call,
+ ResponseSender sender) override {
+ DBusMethodResponse<R> method_response(method_call, sender);
+ auto invoke_callback = [this, &method_response](const Args&... args) {
+ method_response.Return(handler_.Run(args...));
+ };
+
+ ErrorPtr param_reader_error;
+ dbus::MessageReader reader(method_call);
+ // The handler is expected a return value, don't allow output parameters.
+ if (!DBusParamReader<false, Args...>::Invoke(
+ invoke_callback, &reader, ¶m_reader_error)) {
+ // Error parsing method arguments.
+ method_response.ReplyWithError(param_reader_error.get());
+ }
+ }
+
+ private:
+ // C++ callback to be called when a DBus method is dispatched.
+ base::Callback<R(Args...)> handler_;
+ DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandler);
+};
+
+// Specialization of SimpleDBusInterfaceMethodHandlerInterface for
+// R=void (methods with no return values).
+template<typename... Args>
+class SimpleDBusInterfaceMethodHandler<void, Args...>
+ : public DBusInterfaceMethodHandlerInterface {
+ public:
+ // A constructor that takes a |handler| to be called when HandleMethod()
+ // virtual function is invoked.
+ explicit SimpleDBusInterfaceMethodHandler(
+ const base::Callback<void(Args...)>& handler) : handler_(handler) {}
+
+ void HandleMethod(dbus::MethodCall* method_call,
+ ResponseSender sender) override {
+ DBusMethodResponseBase method_response(method_call, sender);
+ auto invoke_callback = [this, &method_response](const Args&... args) {
+ handler_.Run(args...);
+ auto response = method_response.CreateCustomResponse();
+ dbus::MessageWriter writer(response.get());
+ DBusParamWriter::AppendDBusOutParams(&writer, args...);
+ method_response.SendRawResponse(std::move(response));
+ };
+
+ ErrorPtr param_reader_error;
+ dbus::MessageReader reader(method_call);
+ if (!DBusParamReader<true, Args...>::Invoke(
+ invoke_callback, &reader, ¶m_reader_error)) {
+ // Error parsing method arguments.
+ method_response.ReplyWithError(param_reader_error.get());
+ }
+ }
+
+ private:
+ // C++ callback to be called when a DBus method is dispatched.
+ base::Callback<void(Args...)> handler_;
+ DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandler);
+};
+
+// An implementation of DBusInterfaceMethodHandlerInterface for simple
+// synchronous method handlers that may fail and return an error response
+// message.
+// The handler is expected to take an arbitrary number of arguments of type
+// |Args...| which can contain both inputs (passed in by value or constant
+// reference) and outputs (passed in as pointers)...
+// In case of an error, the handler must return false and set the error details
+// into the |error| object provided.
+// The signature of the callback handler is expected to be:
+// bool(ErrorPtr*, Args...)
+template<typename... Args>
+class SimpleDBusInterfaceMethodHandlerWithError
+ : public DBusInterfaceMethodHandlerInterface {
+ public:
+ // A constructor that takes a |handler| to be called when HandleMethod()
+ // virtual function is invoked.
+ explicit SimpleDBusInterfaceMethodHandlerWithError(
+ const base::Callback<bool(ErrorPtr*, Args...)>& handler)
+ : handler_(handler) {}
+
+ void HandleMethod(dbus::MethodCall* method_call,
+ ResponseSender sender) override {
+ DBusMethodResponseBase method_response(method_call, sender);
+ auto invoke_callback = [this, &method_response](const Args&... args) {
+ ErrorPtr error;
+ if (!handler_.Run(&error, args...)) {
+ method_response.ReplyWithError(error.get());
+ } else {
+ auto response = method_response.CreateCustomResponse();
+ dbus::MessageWriter writer(response.get());
+ DBusParamWriter::AppendDBusOutParams(&writer, args...);
+ method_response.SendRawResponse(std::move(response));
+ }
+ };
+
+ ErrorPtr param_reader_error;
+ dbus::MessageReader reader(method_call);
+ if (!DBusParamReader<true, Args...>::Invoke(
+ invoke_callback, &reader, ¶m_reader_error)) {
+ // Error parsing method arguments.
+ method_response.ReplyWithError(param_reader_error.get());
+ }
+ }
+
+ private:
+ // C++ callback to be called when a DBus method is dispatched.
+ base::Callback<bool(ErrorPtr*, Args...)> handler_;
+ DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandlerWithError);
+};
+
+// An implementation of SimpleDBusInterfaceMethodHandlerWithErrorAndMessage
+// which is almost identical to SimpleDBusInterfaceMethodHandlerWithError with
+// the exception that the callback takes an additional parameter - raw D-Bus
+// message used to invoke the method handler.
+// The handler is expected to take an arbitrary number of arguments of type
+// |Args...| which can contain both inputs (passed in by value or constant
+// reference) and outputs (passed in as pointers)...
+// In case of an error, the handler must return false and set the error details
+// into the |error| object provided.
+// The signature of the callback handler is expected to be:
+// bool(ErrorPtr*, dbus::Message*, Args...)
+template<typename... Args>
+class SimpleDBusInterfaceMethodHandlerWithErrorAndMessage
+ : public DBusInterfaceMethodHandlerInterface {
+ public:
+ // A constructor that takes a |handler| to be called when HandleMethod()
+ // virtual function is invoked.
+ explicit SimpleDBusInterfaceMethodHandlerWithErrorAndMessage(
+ const base::Callback<bool(ErrorPtr*, dbus::Message*, Args...)>& handler)
+ : handler_(handler) {}
+
+ void HandleMethod(dbus::MethodCall* method_call,
+ ResponseSender sender) override {
+ DBusMethodResponseBase method_response(method_call, sender);
+ auto invoke_callback =
+ [this, method_call, &method_response](const Args&... args) {
+ ErrorPtr error;
+ if (!handler_.Run(&error, method_call, args...)) {
+ method_response.ReplyWithError(error.get());
+ } else {
+ auto response = method_response.CreateCustomResponse();
+ dbus::MessageWriter writer(response.get());
+ DBusParamWriter::AppendDBusOutParams(&writer, args...);
+ method_response.SendRawResponse(std::move(response));
+ }
+ };
+
+ ErrorPtr param_reader_error;
+ dbus::MessageReader reader(method_call);
+ if (!DBusParamReader<true, Args...>::Invoke(
+ invoke_callback, &reader, ¶m_reader_error)) {
+ // Error parsing method arguments.
+ method_response.ReplyWithError(param_reader_error.get());
+ }
+ }
+
+ private:
+ // C++ callback to be called when a DBus method is dispatched.
+ base::Callback<bool(ErrorPtr*, dbus::Message*, Args...)> handler_;
+ DISALLOW_COPY_AND_ASSIGN(SimpleDBusInterfaceMethodHandlerWithErrorAndMessage);
+};
+
+// An implementation of DBusInterfaceMethodHandlerInterface for more generic
+// (and possibly asynchronous) method handlers. The handler is expected
+// to take an arbitrary number of input arguments of type |Args...| and send
+// the method call response (including a possible error response) using
+// the provided DBusMethodResponse object.
+// The signature of the callback handler is expected to be:
+// void(std::unique_ptr<DBusMethodResponse<RetTypes...>, Args...)
+template<typename Response, typename... Args>
+class DBusInterfaceMethodHandler : public DBusInterfaceMethodHandlerInterface {
+ public:
+ // A constructor that takes a |handler| to be called when HandleMethod()
+ // virtual function is invoked.
+ explicit DBusInterfaceMethodHandler(
+ const base::Callback<void(std::unique_ptr<Response>, Args...)>& handler)
+ : handler_(handler) {}
+
+ // This method forwards the call to |handler_| after extracting the required
+ // arguments from the DBus message buffer specified in |method_call|.
+ // The output parameters of |handler_| (if any) are sent back to the called.
+ void HandleMethod(dbus::MethodCall* method_call,
+ ResponseSender sender) override {
+ auto invoke_callback = [this, method_call, &sender](const Args&... args) {
+ std::unique_ptr<Response> response(new Response(method_call, sender));
+ handler_.Run(std::move(response), args...);
+ };
+
+ ErrorPtr param_reader_error;
+ dbus::MessageReader reader(method_call);
+ if (!DBusParamReader<false, Args...>::Invoke(
+ invoke_callback, &reader, ¶m_reader_error)) {
+ // Error parsing method arguments.
+ DBusMethodResponseBase method_response(method_call, sender);
+ method_response.ReplyWithError(param_reader_error.get());
+ }
+ }
+
+ private:
+ // C++ callback to be called when a D-Bus method is dispatched.
+ base::Callback<void(std::unique_ptr<Response>, Args...)> handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(DBusInterfaceMethodHandler);
+};
+
+// An implementation of DBusInterfaceMethodHandlerWithMessage which is almost
+// identical to AddSimpleMethodHandlerWithError with the exception that the
+// callback takes an additional parameter - raw D-Bus message.
+// The handler is expected to take an arbitrary number of input arguments of
+// type |Args...| and send the method call response (including a possible error
+// response) using the provided DBusMethodResponse object.
+// The signature of the callback handler is expected to be:
+// void(std::unique_ptr<DBusMethodResponse<RetTypes...>, dbus::Message*,
+// Args...);
+template<typename Response, typename... Args>
+class DBusInterfaceMethodHandlerWithMessage
+ : public DBusInterfaceMethodHandlerInterface {
+ public:
+ // A constructor that takes a |handler| to be called when HandleMethod()
+ // virtual function is invoked.
+ explicit DBusInterfaceMethodHandlerWithMessage(
+ const base::Callback<void(std::unique_ptr<Response>, dbus::Message*,
+ Args...)>& handler)
+ : handler_(handler) {}
+
+ // This method forwards the call to |handler_| after extracting the required
+ // arguments from the DBus message buffer specified in |method_call|.
+ // The output parameters of |handler_| (if any) are sent back to the called.
+ void HandleMethod(dbus::MethodCall* method_call,
+ ResponseSender sender) override {
+ auto invoke_callback = [this, method_call, &sender](const Args&... args) {
+ std::unique_ptr<Response> response(new Response(method_call, sender));
+ handler_.Run(std::move(response), method_call, args...);
+ };
+
+ ErrorPtr param_reader_error;
+ dbus::MessageReader reader(method_call);
+ if (!DBusParamReader<false, Args...>::Invoke(
+ invoke_callback, &reader, ¶m_reader_error)) {
+ // Error parsing method arguments.
+ DBusMethodResponseBase method_response(method_call, sender);
+ method_response.ReplyWithError(param_reader_error.get());
+ }
+ }
+
+ private:
+ // C++ callback to be called when a D-Bus method is dispatched.
+ base::Callback<void(std::unique_ptr<Response>,
+ dbus::Message*, Args...)> handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(DBusInterfaceMethodHandlerWithMessage);
+};
+
+// An implementation of DBusInterfaceMethodHandlerInterface that has custom
+// processing of both input and output parameters. This class is used by
+// DBusObject::AddRawMethodHandler and expects the callback to be of the
+// following signature:
+// void(dbus::MethodCall*, ResponseSender)
+// It will be up to the callback to parse the input parameters from the
+// message buffer and construct the D-Bus Response object.
+class RawDBusInterfaceMethodHandler
+ : public DBusInterfaceMethodHandlerInterface {
+ public:
+ // A constructor that takes a |handler| to be called when HandleMethod()
+ // virtual function is invoked.
+ explicit RawDBusInterfaceMethodHandler(
+ const base::Callback<void(dbus::MethodCall*, ResponseSender)>& handler)
+ : handler_(handler) {}
+
+ void HandleMethod(dbus::MethodCall* method_call,
+ ResponseSender sender) override {
+ handler_.Run(method_call, sender);
+ }
+
+ private:
+ // C++ callback to be called when a D-Bus method is dispatched.
+ base::Callback<void(dbus::MethodCall*, ResponseSender)> handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(RawDBusInterfaceMethodHandler);
+};
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_INTERNAL_IMPL_H_
diff --git a/libbrillo/brillo/dbus/dbus_object_test_helpers.h b/libbrillo/brillo/dbus/dbus_object_test_helpers.h
new file mode 100644
index 0000000..59c4a06
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_object_test_helpers.h
@@ -0,0 +1,143 @@
+// 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.
+
+// Helper utilities to simplify testing of D-Bus object implementations.
+// Since the method handlers could now be asynchronous, they use callbacks to
+// provide method return values. This makes it really difficult to invoke
+// such handlers in unit tests (even if they are actually synchronous but
+// still use DBusMethodResponse to send back the method results).
+// This file provide testing-only helpers to make calling D-Bus method handlers
+// easier.
+#ifndef LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_
+
+#include <base/bind.h>
+#include <base/memory/weak_ptr.h>
+#include <brillo/dbus/dbus_method_invoker.h>
+#include <brillo/dbus/dbus_object.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+// Helper friend class to call DBusInterface::HandleMethodCall() since it is
+// a private method of the class and we don't want to make it public.
+class DBusInterfaceTestHelper final {
+ public:
+ static void HandleMethodCall(DBusInterface* itf,
+ dbus::MethodCall* method_call,
+ ResponseSender sender) {
+ itf->HandleMethodCall(method_call, sender);
+ }
+};
+
+namespace testing {
+
+// This is a simple class that has weak pointer semantics and holds an
+// instance of D-Bus method call response message. We use this in tests
+// to get the response in case the handler processes a method call request
+// synchronously. Otherwise the ResponseHolder object will be destroyed and
+// ResponseHolder::ReceiveResponse() will not be called since we bind the
+// callback to the object instance via a weak pointer.
+struct ResponseHolder final : public base::SupportsWeakPtr<ResponseHolder> {
+ void ReceiveResponse(std::unique_ptr<dbus::Response> response) {
+ response_ = std::move(response);
+ }
+
+ std::unique_ptr<dbus::Response> response_;
+};
+
+// Dispatches a D-Bus method call to the corresponding handler.
+// Used mostly for testing purposes. This method is inlined so that it is
+// not included in the shipping code of libbrillo, and included at the
+// call sites. Returns a response from the method handler or nullptr if the
+// method hasn't provided the response message immediately
+// (i.e. it is asynchronous).
+inline std::unique_ptr<dbus::Response> CallMethod(
+ const DBusObject& object, dbus::MethodCall* method_call) {
+ DBusInterface* itf = object.FindInterface(method_call->GetInterface());
+ std::unique_ptr<dbus::Response> response;
+ if (!itf) {
+ response = CreateDBusErrorResponse(
+ method_call,
+ DBUS_ERROR_UNKNOWN_INTERFACE,
+ "Interface you invoked a method on isn't known by the object.");
+ } else {
+ ResponseHolder response_holder;
+ DBusInterfaceTestHelper::HandleMethodCall(
+ itf, method_call, base::Bind(&ResponseHolder::ReceiveResponse,
+ response_holder.AsWeakPtr()));
+ response = std::move(response_holder.response_);
+ }
+ return response;
+}
+
+// MethodHandlerInvoker is similar to CallMethod() function above, except
+// it allows the callers to invoke the method handlers directly bypassing
+// the DBusObject/DBusInterface infrastructure.
+// This works only on synchronous methods though. The handler must reply
+// before the handler exits.
+template<typename RetType>
+struct MethodHandlerInvoker {
+ // MethodHandlerInvoker<RetType>::Call() calls a member |method| of a class
+ // |instance| and passes the |args| to it. The method's return value provided
+ // via handler's DBusMethodResponse is then extracted and returned.
+ // If the method handler returns an error, the error information is passed
+ // to the caller via the |error| object (and the method returns a default
+ // value of type RetType as a placeholder).
+ // If the method handler asynchronous and did not provide a reply (success or
+ // error) before the handler exits, this method aborts with a CHECK().
+ template<class Class, typename... Params, typename... Args>
+ static RetType Call(
+ ErrorPtr* error,
+ Class* instance,
+ void(Class::*method)(std::unique_ptr<DBusMethodResponse<RetType>>,
+ Params...),
+ Args... args) {
+ ResponseHolder response_holder;
+ dbus::MethodCall method_call("test.interface", "TestMethod");
+ method_call.SetSerial(123);
+ std::unique_ptr<DBusMethodResponse<RetType>> method_response{
+ new DBusMethodResponse<RetType>(
+ &method_call, base::Bind(&ResponseHolder::ReceiveResponse,
+ response_holder.AsWeakPtr()))
+ };
+ (instance->*method)(std::move(method_response), args...);
+ CHECK(response_holder.response_.get())
+ << "No response received. Asynchronous methods are not supported.";
+ RetType ret_val;
+ ExtractMethodCallResults(response_holder.response_.get(), error, &ret_val);
+ return ret_val;
+ }
+};
+
+// Specialization of MethodHandlerInvoker for methods that do not return
+// values (void methods).
+template<>
+struct MethodHandlerInvoker<void> {
+ template<class Class, typename... Params, typename... Args>
+ static void Call(
+ ErrorPtr* error,
+ Class* instance,
+ void(Class::*method)(std::unique_ptr<DBusMethodResponse<>>, Params...),
+ Args... args) {
+ ResponseHolder response_holder;
+ dbus::MethodCall method_call("test.interface", "TestMethod");
+ method_call.SetSerial(123);
+ std::unique_ptr<DBusMethodResponse<>> method_response{
+ new DBusMethodResponse<>(&method_call,
+ base::Bind(&ResponseHolder::ReceiveResponse,
+ response_holder.AsWeakPtr()))
+ };
+ (instance->*method)(std::move(method_response), args...);
+ CHECK(response_holder.response_.get())
+ << "No response received. Asynchronous methods are not supported.";
+ ExtractMethodCallResults(response_holder.response_.get(), error);
+ }
+};
+
+} // namespace testing
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_
diff --git a/libbrillo/brillo/dbus/dbus_object_unittest.cc b/libbrillo/brillo/dbus/dbus_object_unittest.cc
new file mode 100644
index 0000000..eaec3d8
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_object_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 <brillo/dbus/dbus_object.h>
+
+#include <memory>
+
+#include <base/bind.h>
+#include <brillo/dbus/dbus_object_test_helpers.h>
+#include <brillo/dbus/mock_exported_object_manager.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>
+
+using ::testing::AnyNumber;
+using ::testing::Return;
+using ::testing::Invoke;
+using ::testing::Mock;
+using ::testing::_;
+
+namespace brillo {
+namespace dbus_utils {
+
+namespace {
+
+const char kMethodsExportedOn[] = "/export";
+
+const char kTestInterface1[] = "org.chromium.Test.MathInterface";
+const char kTestMethod_Add[] = "Add";
+const char kTestMethod_Negate[] = "Negate";
+const char kTestMethod_Positive[] = "Positive";
+const char kTestMethod_AddSubtract[] = "AddSubtract";
+
+const char kTestInterface2[] = "org.chromium.Test.StringInterface";
+const char kTestMethod_StrLen[] = "StrLen";
+const char kTestMethod_CheckNonEmpty[] = "CheckNonEmpty";
+
+const char kTestInterface3[] = "org.chromium.Test.NoOpInterface";
+const char kTestMethod_NoOp[] = "NoOp";
+const char kTestMethod_WithMessage[] = "TestWithMessage";
+const char kTestMethod_WithMessageAsync[] = "TestWithMessageAsync";
+
+struct Calc {
+ int Add(int x, int y) { return x + y; }
+ int Negate(int x) { return -x; }
+ void Positive(std::unique_ptr<DBusMethodResponse<double>> response,
+ double x) {
+ if (x >= 0.0) {
+ response->Return(x);
+ return;
+ }
+ ErrorPtr error;
+ Error::AddTo(&error, FROM_HERE, "test", "not_positive",
+ "Negative value passed in");
+ response->ReplyWithError(error.get());
+ }
+ void AddSubtract(int x, int y, int* sum, int* diff) {
+ *sum = x + y;
+ *diff = x - y;
+ }
+};
+
+int StrLen(const std::string& str) {
+ return str.size();
+}
+
+bool CheckNonEmpty(ErrorPtr* error, const std::string& str) {
+ if (!str.empty())
+ return true;
+ Error::AddTo(error, FROM_HERE, "test", "string_empty", "String is empty");
+ return false;
+}
+
+void NoOp() {}
+
+bool TestWithMessage(ErrorPtr* /* error */,
+ dbus::Message* message,
+ std::string* str) {
+ *str = message->GetSender();
+ return true;
+}
+
+void TestWithMessageAsync(
+ std::unique_ptr<DBusMethodResponse<std::string>> response,
+ dbus::Message* message) {
+ response->Return(message->GetSender());
+}
+
+} // namespace
+
+class DBusObjectTest : 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.
+ const dbus::ObjectPath kMethodsExportedOnPath{
+ std::string{kMethodsExportedOn}};
+ mock_exported_object_ =
+ new dbus::MockExportedObject(bus_.get(), kMethodsExportedOnPath);
+ EXPECT_CALL(*bus_, GetExportedObject(kMethodsExportedOnPath))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(mock_exported_object_.get()));
+ EXPECT_CALL(*mock_exported_object_, ExportMethod(_, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1);
+
+ dbus_object_ = std::unique_ptr<DBusObject>(
+ new DBusObject(nullptr, bus_, kMethodsExportedOnPath));
+
+ DBusInterface* itf1 = dbus_object_->AddOrGetInterface(kTestInterface1);
+ itf1->AddSimpleMethodHandler(
+ kTestMethod_Add, base::Unretained(&calc_), &Calc::Add);
+ itf1->AddSimpleMethodHandler(
+ kTestMethod_Negate, base::Unretained(&calc_), &Calc::Negate);
+ itf1->AddMethodHandler(
+ kTestMethod_Positive, base::Unretained(&calc_), &Calc::Positive);
+ itf1->AddSimpleMethodHandler(
+ kTestMethod_AddSubtract, base::Unretained(&calc_), &Calc::AddSubtract);
+ DBusInterface* itf2 = dbus_object_->AddOrGetInterface(kTestInterface2);
+ itf2->AddSimpleMethodHandler(kTestMethod_StrLen, StrLen);
+ itf2->AddSimpleMethodHandlerWithError(kTestMethod_CheckNonEmpty,
+ CheckNonEmpty);
+ DBusInterface* itf3 = dbus_object_->AddOrGetInterface(kTestInterface3);
+ base::Callback<void()> noop_callback = base::Bind(NoOp);
+ itf3->AddSimpleMethodHandler(kTestMethod_NoOp, noop_callback);
+ itf3->AddSimpleMethodHandlerWithErrorAndMessage(
+ kTestMethod_WithMessage, base::Bind(&TestWithMessage));
+ itf3->AddMethodHandlerWithMessage(kTestMethod_WithMessageAsync,
+ base::Bind(&TestWithMessageAsync));
+
+ dbus_object_->RegisterAsync(
+ AsyncEventSequencer::GetDefaultCompletionAction());
+ }
+
+ void ExpectError(dbus::Response* response, const std::string& expected_code) {
+ EXPECT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ EXPECT_EQ(expected_code, response->GetErrorName());
+ }
+
+ scoped_refptr<dbus::MockBus> bus_;
+ scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
+ std::unique_ptr<DBusObject> dbus_object_;
+ Calc calc_;
+};
+
+TEST_F(DBusObjectTest, Add) {
+ dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendInt32(2);
+ writer.AppendInt32(3);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ dbus::MessageReader reader(response.get());
+ int result;
+ ASSERT_TRUE(reader.PopInt32(&result));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(5, result);
+}
+
+TEST_F(DBusObjectTest, Negate) {
+ dbus::MethodCall method_call(kTestInterface1, kTestMethod_Negate);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendInt32(98765);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ dbus::MessageReader reader(response.get());
+ int result;
+ ASSERT_TRUE(reader.PopInt32(&result));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(-98765, result);
+}
+
+TEST_F(DBusObjectTest, PositiveSuccess) {
+ dbus::MethodCall method_call(kTestInterface1, kTestMethod_Positive);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendDouble(17.5);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ dbus::MessageReader reader(response.get());
+ double result;
+ ASSERT_TRUE(reader.PopDouble(&result));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_DOUBLE_EQ(17.5, result);
+}
+
+TEST_F(DBusObjectTest, PositiveFailure) {
+ dbus::MethodCall method_call(kTestInterface1, kTestMethod_Positive);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendDouble(-23.2);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ ExpectError(response.get(), DBUS_ERROR_FAILED);
+}
+
+TEST_F(DBusObjectTest, AddSubtract) {
+ dbus::MethodCall method_call(kTestInterface1, kTestMethod_AddSubtract);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendInt32(2);
+ writer.AppendInt32(3);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ dbus::MessageReader reader(response.get());
+ int sum = 0, diff = 0;
+ ASSERT_TRUE(reader.PopInt32(&sum));
+ ASSERT_TRUE(reader.PopInt32(&diff));
+ ASSERT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(5, sum);
+ EXPECT_EQ(-1, diff);
+}
+
+TEST_F(DBusObjectTest, StrLen0) {
+ dbus::MethodCall method_call(kTestInterface2, kTestMethod_StrLen);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString("");
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ dbus::MessageReader reader(response.get());
+ int result;
+ ASSERT_TRUE(reader.PopInt32(&result));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(0, result);
+}
+
+TEST_F(DBusObjectTest, StrLen4) {
+ dbus::MethodCall method_call(kTestInterface2, kTestMethod_StrLen);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString("test");
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ dbus::MessageReader reader(response.get());
+ int result;
+ ASSERT_TRUE(reader.PopInt32(&result));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(4, result);
+}
+
+TEST_F(DBusObjectTest, CheckNonEmpty_Success) {
+ dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString("test");
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ ASSERT_EQ(dbus::Message::MESSAGE_METHOD_RETURN, response->GetMessageType());
+ dbus::MessageReader reader(response.get());
+ EXPECT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(DBusObjectTest, CheckNonEmpty_Failure) {
+ dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString("");
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ ErrorPtr error;
+ ExtractMethodCallResults(response.get(), &error);
+ ASSERT_NE(nullptr, error.get());
+ EXPECT_EQ("test", error->GetDomain());
+ EXPECT_EQ("string_empty", error->GetCode());
+ EXPECT_EQ("String is empty", error->GetMessage());
+}
+
+TEST_F(DBusObjectTest, CheckNonEmpty_MissingParams) {
+ dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty);
+ method_call.SetSerial(123);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ dbus::MessageReader reader(response.get());
+ std::string message;
+ ASSERT_TRUE(reader.PopString(&message));
+ EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName());
+ EXPECT_EQ("Too few parameters in a method call", message);
+ EXPECT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(DBusObjectTest, NoOp) {
+ dbus::MethodCall method_call(kTestInterface3, kTestMethod_NoOp);
+ method_call.SetSerial(123);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ dbus::MessageReader reader(response.get());
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(DBusObjectTest, TestWithMessage) {
+ const std::string sender{":1.2345"};
+ dbus::MethodCall method_call(kTestInterface3, kTestMethod_WithMessage);
+ method_call.SetSerial(123);
+ method_call.SetSender(sender);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ dbus::MessageReader reader(response.get());
+ std::string message;
+ ASSERT_TRUE(reader.PopString(&message));
+ ASSERT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(sender, message);
+}
+
+TEST_F(DBusObjectTest, TestWithMessageAsync) {
+ const std::string sender{":6.7890"};
+ dbus::MethodCall method_call(kTestInterface3, kTestMethod_WithMessageAsync);
+ method_call.SetSerial(123);
+ method_call.SetSender(sender);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ dbus::MessageReader reader(response.get());
+ std::string message;
+ ASSERT_TRUE(reader.PopString(&message));
+ ASSERT_FALSE(reader.HasMoreData());
+ EXPECT_EQ(sender, message);
+}
+
+TEST_F(DBusObjectTest, TooFewParams) {
+ dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendInt32(2);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS);
+}
+
+TEST_F(DBusObjectTest, TooManyParams) {
+ dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendInt32(1);
+ writer.AppendInt32(2);
+ writer.AppendInt32(3);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS);
+}
+
+TEST_F(DBusObjectTest, ParamTypeMismatch) {
+ dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendInt32(1);
+ writer.AppendBool(false);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS);
+}
+
+TEST_F(DBusObjectTest, ParamAsVariant) {
+ dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendVariantOfInt32(10);
+ writer.AppendVariantOfInt32(3);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ dbus::MessageReader reader(response.get());
+ int result;
+ ASSERT_TRUE(reader.PopInt32(&result));
+ ASSERT_FALSE(reader.HasMoreData());
+ ASSERT_EQ(13, result);
+}
+
+TEST_F(DBusObjectTest, UnknownMethod) {
+ dbus::MethodCall method_call(kTestInterface2, kTestMethod_Add);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendInt32(1);
+ writer.AppendBool(false);
+ auto response = testing::CallMethod(*dbus_object_, &method_call);
+ ExpectError(response.get(), DBUS_ERROR_UNKNOWN_METHOD);
+}
+
+TEST_F(DBusObjectTest, ShouldReleaseOnlyClaimedInterfaces) {
+ const dbus::ObjectPath kObjectManagerPath{std::string{"/"}};
+ const dbus::ObjectPath kMethodsExportedOnPath{
+ std::string{kMethodsExportedOn}};
+ MockExportedObjectManager mock_object_manager{bus_, kObjectManagerPath};
+ dbus_object_ = std::unique_ptr<DBusObject>(
+ new DBusObject(&mock_object_manager, bus_, kMethodsExportedOnPath));
+ EXPECT_CALL(mock_object_manager, ClaimInterface(_, _, _)).Times(0);
+ EXPECT_CALL(mock_object_manager, ReleaseInterface(_, _)).Times(0);
+ DBusInterface* itf1 = dbus_object_->AddOrGetInterface(kTestInterface1);
+ itf1->AddSimpleMethodHandler(
+ kTestMethod_Add, base::Unretained(&calc_), &Calc::Add);
+ // When we tear down our DBusObject, it should release only interfaces it has
+ // previously claimed. This prevents a check failing inside the
+ // ExportedObjectManager. Since no interfaces have finished exporting
+ // handlers, nothing should be released.
+ dbus_object_.reset();
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_param_reader.h b/libbrillo/brillo/dbus/dbus_param_reader.h
new file mode 100644
index 0000000..228cfb6
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_param_reader.h
@@ -0,0 +1,165 @@
+// 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 file provides generic method to parse function call arguments from
+// D-Bus message buffer and subsequently invokes a provided native C++ callback
+// with the parameter values passed as the callback arguments.
+
+// This functionality is achieved by parsing method arguments one by one,
+// left to right from the C++ callback's type signature, and moving the parsed
+// arguments to the back to the next call to DBusInvoke::Invoke's arguments as
+// const refs. Each iteration has one fewer template specialization arguments,
+// until there is only the return type remaining and we fall through to either
+// the void or the non-void final specialization.
+
+#ifndef LIBBRILLO_BRILLO_DBUS_DBUS_PARAM_READER_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_PARAM_READER_H_
+
+#include <type_traits>
+
+#include <brillo/dbus/data_serialization.h>
+#include <brillo/dbus/utils.h>
+#include <brillo/errors/error.h>
+#include <brillo/errors/error_codes.h>
+#include <dbus/message.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+// A generic DBusParamReader stub class which allows us to specialize on
+// a variable list of expected function parameters later on.
+// This struct in itself is not used. But its concrete template specializations
+// defined below are.
+// |allow_out_params| controls whether DBusParamReader allows the parameter
+// list to contain OUT parameters (pointers).
+template<bool allow_out_params, typename...>
+struct DBusParamReader;
+
+// A generic specialization of DBusParamReader to handle variable function
+// parameters. This specialization pops one parameter off the D-Bus message
+// buffer and calls other specializations of DBusParamReader with fewer
+// parameters to pop the remaining parameters.
+// CurrentParam - the type of the current method parameter we are processing.
+// RestOfParams - the types of remaining parameters to be processed.
+template<bool allow_out_params, typename CurrentParam, typename... RestOfParams>
+struct DBusParamReader<allow_out_params, CurrentParam, RestOfParams...> {
+ // DBusParamReader::Invoke() is a member function that actually extracts the
+ // current parameter from the message buffer.
+ // handler - the C++ callback functor to be called when all the
+ // parameters are processed.
+ // method_call - D-Bus method call object we are processing.
+ // reader - D-Bus message reader to pop the current argument value from.
+ // args... - the callback parameters processed so far.
+ template<typename CallbackType, typename... Args>
+ static bool Invoke(const CallbackType& handler,
+ dbus::MessageReader* reader,
+ ErrorPtr* error,
+ const Args&... args) {
+ return InvokeHelper<CurrentParam, CallbackType, Args...>(
+ handler, reader, error, static_cast<const Args&>(args)...);
+ }
+
+ //
+ // There are two specializations of this function:
+ // 1. For the case where ParamType is a value type (D-Bus IN parameter).
+ // 2. For the case where ParamType is a pointer (D-Bus OUT parameter).
+ // In the second case, the parameter is not popped off the message reader,
+ // since we do not expect the client to provide any data for it.
+ // However after the final handler is called, the values for the OUT
+ // parameters should be sent back in the method call response message.
+
+ // Overload 1: ParamType is not a pointer.
+ template<typename ParamType, typename CallbackType, typename... Args>
+ static typename std::enable_if<!std::is_pointer<ParamType>::value, bool>::type
+ InvokeHelper(const CallbackType& handler,
+ dbus::MessageReader* reader,
+ ErrorPtr* error,
+ const Args&... args) {
+ if (!reader->HasMoreData()) {
+ Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
+ DBUS_ERROR_INVALID_ARGS,
+ "Too few parameters in a method call");
+ return false;
+ }
+ // ParamType could be a reference type (e.g. 'const std::string&').
+ // Here we need a value type so we can create an object of this type and
+ // pop the value off the message buffer into. Using std::decay<> to get
+ // the value type. If ParamType is already a value type, ParamValueType will
+ // be the same as ParamType.
+ using ParamValueType = typename std::decay<ParamType>::type;
+ // The variable to hold the value of the current parameter we reading from
+ // the message buffer.
+ ParamValueType current_param;
+ if (!DBusType<ParamValueType>::Read(reader, ¤t_param)) {
+ Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
+ DBUS_ERROR_INVALID_ARGS,
+ "Method parameter type mismatch");
+ return false;
+ }
+ // Call DBusParamReader::Invoke() to process the rest of parameters.
+ // Note that this is not a recursive call because it is calling a different
+ // method of a different class. We exclude the current parameter type
+ // (ParamType) from DBusParamReader<> template parameter list and forward
+ // all the parameters to the arguments of Invoke() and append the current
+ // parameter to the end of the parameter list. We pass it as a const
+ // reference to allow to use move-only types such as std::unique_ptr<> and
+ // to eliminate unnecessarily copying data.
+ return DBusParamReader<allow_out_params, RestOfParams...>::Invoke(
+ handler, reader, error,
+ static_cast<const Args&>(args)...,
+ static_cast<const ParamValueType&>(current_param));
+ }
+
+ // Overload 2: ParamType is a pointer.
+ template<typename ParamType, typename CallbackType, typename... Args>
+ static typename std::enable_if<allow_out_params &&
+ std::is_pointer<ParamType>::value, bool>::type
+ InvokeHelper(const CallbackType& handler,
+ dbus::MessageReader* reader,
+ ErrorPtr* error,
+ const Args&... args) {
+ // ParamType is a pointer. This is expected to be an output parameter.
+ // Create storage for it and the handler will provide a value for it.
+ using ParamValueType = typename std::remove_pointer<ParamType>::type;
+ // The variable to hold the value of the current parameter we are passing
+ // to the handler.
+ ParamValueType current_param{}; // Default-initialize the value.
+ // Call DBusParamReader::Invoke() to process the rest of parameters.
+ // Note that this is not a recursive call because it is calling a different
+ // method of a different class. We exclude the current parameter type
+ // (ParamType) from DBusParamReader<> template parameter list and forward
+ // all the parameters to the arguments of Invoke() and append the current
+ // parameter to the end of the parameter list.
+ return DBusParamReader<allow_out_params, RestOfParams...>::Invoke(
+ handler, reader, error,
+ static_cast<const Args&>(args)...,
+ ¤t_param);
+ }
+}; // struct DBusParamReader<ParamType, RestOfParams...>
+
+// The final specialization of DBusParamReader<> used when no more parameters
+// are expected in the message buffer. Actually dispatches the call to the
+// handler with all the accumulated arguments.
+template<bool allow_out_params>
+struct DBusParamReader<allow_out_params> {
+ template<typename CallbackType, typename... Args>
+ static bool Invoke(const CallbackType& handler,
+ dbus::MessageReader* reader,
+ ErrorPtr* error,
+ const Args&... args) {
+ if (reader->HasMoreData()) {
+ Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
+ DBUS_ERROR_INVALID_ARGS,
+ "Too many parameters in a method call");
+ return false;
+ }
+ handler(args...);
+ return true;
+ }
+}; // struct DBusParamReader<>
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_PARAM_READER_H_
diff --git a/libbrillo/brillo/dbus/dbus_param_reader_unittest.cc b/libbrillo/brillo/dbus/dbus_param_reader_unittest.cc
new file mode 100644
index 0000000..fd9f243
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_param_reader_unittest.cc
@@ -0,0 +1,253 @@
+// 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 <brillo/dbus/dbus_param_reader.h>
+
+#include <string>
+
+#include <brillo/variant_dictionary.h>
+#include <gtest/gtest.h>
+
+using dbus::MessageReader;
+using dbus::MessageWriter;
+using dbus::Response;
+
+namespace brillo {
+namespace dbus_utils {
+
+TEST(DBusParamReader, NoArgs) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called]() { called = true; };
+ EXPECT_TRUE(DBusParamReader<false>::Invoke(callback, &reader, nullptr));
+ EXPECT_TRUE(called);
+}
+
+TEST(DBusParamReader, OneArg) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, 123);
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](int param1) {
+ EXPECT_EQ(123, param1);
+ called = true;
+ };
+ EXPECT_TRUE(
+ (DBusParamReader<false, int>::Invoke(callback, &reader, nullptr)));
+ EXPECT_TRUE(called);
+}
+
+TEST(DBusParamReader, ManyArgs) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, true);
+ AppendValueToWriter(&writer, 1972);
+ AppendValueToWriter(&writer,
+ VariantDictionary{{"key", std::string{"value"}}});
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](bool p1, int p2, const VariantDictionary& p3) {
+ EXPECT_TRUE(p1);
+ EXPECT_EQ(1972, p2);
+ EXPECT_EQ(1, p3.size());
+ EXPECT_EQ("value", p3.find("key")->second.Get<std::string>());
+ called = true;
+ };
+ EXPECT_TRUE((DBusParamReader<false, bool, int, VariantDictionary>::Invoke(
+ callback, &reader, nullptr)));
+ EXPECT_TRUE(called);
+}
+
+TEST(DBusParamReader, TooManyArgs) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, true);
+ AppendValueToWriter(&writer, 1972);
+ AppendValueToWriter(&writer,
+ VariantDictionary{{"key", std::string{"value"}}});
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](bool param1, int param2) {
+ EXPECT_TRUE(param1);
+ EXPECT_EQ(1972, param2);
+ called = true;
+ };
+ ErrorPtr error;
+ EXPECT_FALSE(
+ (DBusParamReader<false, bool, int>::Invoke(callback, &reader, &error)));
+ EXPECT_FALSE(called);
+ EXPECT_EQ(errors::dbus::kDomain, error->GetDomain());
+ EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode());
+ EXPECT_EQ("Too many parameters in a method call", error->GetMessage());
+}
+
+TEST(DBusParamReader, TooFewArgs) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, true);
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](bool param1, int param2) {
+ EXPECT_TRUE(param1);
+ EXPECT_EQ(1972, param2);
+ called = true;
+ };
+ ErrorPtr error;
+ EXPECT_FALSE(
+ (DBusParamReader<false, bool, int>::Invoke(callback, &reader, &error)));
+ EXPECT_FALSE(called);
+ EXPECT_EQ(errors::dbus::kDomain, error->GetDomain());
+ EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode());
+ EXPECT_EQ("Too few parameters in a method call", error->GetMessage());
+}
+
+TEST(DBusParamReader, TypeMismatch) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, true);
+ AppendValueToWriter(&writer, 1972);
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](bool param1, double param2) {
+ EXPECT_TRUE(param1);
+ EXPECT_DOUBLE_EQ(1972.0, param2);
+ called = true;
+ };
+ ErrorPtr error;
+ EXPECT_FALSE((
+ DBusParamReader<false, bool, double>::Invoke(callback, &reader, &error)));
+ EXPECT_FALSE(called);
+ EXPECT_EQ(errors::dbus::kDomain, error->GetDomain());
+ EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode());
+ EXPECT_EQ("Method parameter type mismatch", error->GetMessage());
+}
+
+TEST(DBusParamReader, NoArgs_With_OUT) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](int* param1) {
+ EXPECT_EQ(0, *param1);
+ called = true;
+ };
+ EXPECT_TRUE(
+ (DBusParamReader<true, int*>::Invoke(callback, &reader, nullptr)));
+ EXPECT_TRUE(called);
+}
+
+TEST(DBusParamReader, OneArg_Before_OUT) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, 123);
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](int param1, double* param2) {
+ EXPECT_EQ(123, param1);
+ EXPECT_DOUBLE_EQ(0.0, *param2);
+ called = true;
+ };
+ EXPECT_TRUE((
+ DBusParamReader<true, int, double*>::Invoke(callback, &reader, nullptr)));
+ EXPECT_TRUE(called);
+}
+
+TEST(DBusParamReader, OneArg_After_OUT) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, 123);
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](double* param1, int param2) {
+ EXPECT_DOUBLE_EQ(0.0, *param1);
+ EXPECT_EQ(123, param2);
+ called = true;
+ };
+ EXPECT_TRUE((
+ DBusParamReader<true, double*, int>::Invoke(callback, &reader, nullptr)));
+ EXPECT_TRUE(called);
+}
+
+TEST(DBusParamReader, ManyArgs_With_OUT) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, true);
+ AppendValueToWriter(&writer, 1972);
+ AppendValueToWriter(&writer,
+ VariantDictionary{{"key", std::string{"value"}}});
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](bool p1,
+ std::string* p2,
+ int p3,
+ int* p4,
+ const VariantDictionary& p5,
+ bool* p6) {
+ EXPECT_TRUE(p1);
+ EXPECT_EQ("", *p2);
+ EXPECT_EQ(1972, p3);
+ EXPECT_EQ(0, *p4);
+ EXPECT_EQ(1, p5.size());
+ EXPECT_EQ("value", p5.find("key")->second.Get<std::string>());
+ EXPECT_FALSE(*p6);
+ called = true;
+ };
+ EXPECT_TRUE((DBusParamReader<true,
+ bool,
+ std::string*,
+ int,
+ int*,
+ VariantDictionary,
+ bool*>::Invoke(callback, &reader, nullptr)));
+ EXPECT_TRUE(called);
+}
+
+TEST(DBusParamReader, TooManyArgs_With_OUT) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, true);
+ AppendValueToWriter(&writer, 1972);
+ AppendValueToWriter(&writer,
+ VariantDictionary{{"key", std::string{"value"}}});
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](bool param1, int param2, int* param3) {
+ EXPECT_TRUE(param1);
+ EXPECT_EQ(1972, param2);
+ EXPECT_EQ(0, *param3);
+ called = true;
+ };
+ ErrorPtr error;
+ EXPECT_FALSE((DBusParamReader<true, bool, int, int*>::Invoke(
+ callback, &reader, &error)));
+ EXPECT_FALSE(called);
+ EXPECT_EQ(errors::dbus::kDomain, error->GetDomain());
+ EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode());
+ EXPECT_EQ("Too many parameters in a method call", error->GetMessage());
+}
+
+TEST(DBusParamReader, TooFewArgs_With_OUT) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ AppendValueToWriter(&writer, true);
+ MessageReader reader(message.get());
+ bool called = false;
+ auto callback = [&called](bool param1, int param2, int* param3) {
+ EXPECT_TRUE(param1);
+ EXPECT_EQ(1972, param2);
+ EXPECT_EQ(0, *param3);
+ called = true;
+ };
+ ErrorPtr error;
+ EXPECT_FALSE((DBusParamReader<true, bool, int, int*>::Invoke(
+ callback, &reader, &error)));
+ EXPECT_FALSE(called);
+ EXPECT_EQ(errors::dbus::kDomain, error->GetDomain());
+ EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, error->GetCode());
+ EXPECT_EQ("Too few parameters in a method call", error->GetMessage());
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_param_writer.h b/libbrillo/brillo/dbus/dbus_param_writer.h
new file mode 100644
index 0000000..7c7f45e
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_param_writer.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.
+
+// DBusParamWriter::Append(writer, ...) provides functionality opposite
+// to that of DBusParamReader. It writes each of the arguments to D-Bus message
+// writer and return true if successful.
+
+// DBusParamWriter::AppendDBusOutParams(writer, ...) is similar to Append()
+// but is used to send out the D-Bus OUT (pointer type) parameters in a D-Bus
+// method response message. This method skips any non-pointer parameters and
+// only appends the data for arguments that are pointers.
+
+#ifndef LIBBRILLO_BRILLO_DBUS_DBUS_PARAM_WRITER_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_PARAM_WRITER_H_
+
+#include <brillo/dbus/data_serialization.h>
+#include <dbus/message.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+class DBusParamWriter final {
+ public:
+ // Generic writer method that takes 1 or more arguments. It recursively calls
+ // itself (each time with one fewer arguments) until no more is left.
+ template<typename ParamType, typename... RestOfParams>
+ static void Append(dbus::MessageWriter* writer,
+ const ParamType& param,
+ const RestOfParams&... rest) {
+ // Append the current |param| to D-Bus, then call Append() with one
+ // fewer arguments, until none is left and stand-alone version of
+ // Append(dbus::MessageWriter*) is called to end the iteration.
+ DBusType<ParamType>::Write(writer, param);
+ Append(writer, rest...);
+ }
+
+ // The final overload of DBusParamWriter::Append() used when no more
+ // parameters are remaining to be written.
+ // Does nothing and finishes meta-recursion.
+ static void Append(dbus::MessageWriter* /*writer*/) {}
+
+ // Generic writer method that takes 1 or more arguments. It recursively calls
+ // itself (each time with one fewer arguments) until no more is left.
+ // Handles non-pointer parameter by just skipping over it.
+ template<typename ParamType, typename... RestOfParams>
+ static void AppendDBusOutParams(dbus::MessageWriter* writer,
+ const ParamType& /* param */,
+ const RestOfParams&... rest) {
+ // Skip the current |param| and call Append() with one fewer arguments,
+ // until none is left and stand-alone version of
+ // AppendDBusOutParams(dbus::MessageWriter*) is called to end the iteration.
+ AppendDBusOutParams(writer, rest...);
+ }
+
+ // Generic writer method that takes 1 or more arguments. It recursively calls
+ // itself (each time with one fewer arguments) until no more is left.
+ // Handles only a parameter of pointer type and writes the data pointed to
+ // to the output message buffer.
+ template<typename ParamType, typename... RestOfParams>
+ static void AppendDBusOutParams(dbus::MessageWriter* writer,
+ ParamType* param,
+ const RestOfParams&... rest) {
+ // Append the current |param| to D-Bus, then call Append() with one
+ // fewer arguments, until none is left and stand-alone version of
+ // Append(dbus::MessageWriter*) is called to end the iteration.
+ DBusType<ParamType>::Write(writer, *param);
+ AppendDBusOutParams(writer, rest...);
+ }
+
+ // The final overload of DBusParamWriter::AppendDBusOutParams() used when no
+ // more parameters are remaining to be written.
+ // Does nothing and finishes meta-recursion.
+ static void AppendDBusOutParams(dbus::MessageWriter* /*writer*/) {}
+};
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_PARAM_WRITER_H_
diff --git a/libbrillo/brillo/dbus/dbus_param_writer_unittest.cc b/libbrillo/brillo/dbus/dbus_param_writer_unittest.cc
new file mode 100644
index 0000000..6ab863a
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_param_writer_unittest.cc
@@ -0,0 +1,189 @@
+// 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 <brillo/dbus/dbus_param_writer.h>
+
+#include <string>
+
+#include <brillo/any.h>
+#include <gtest/gtest.h>
+
+using dbus::MessageReader;
+using dbus::MessageWriter;
+using dbus::ObjectPath;
+using dbus::Response;
+
+namespace brillo {
+namespace dbus_utils {
+
+TEST(DBusParamWriter, Append_NoArgs) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ DBusParamWriter::Append(&writer);
+ EXPECT_EQ("", message->GetSignature());
+}
+
+TEST(DBusParamWriter, Append_OneArg) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ DBusParamWriter::Append(&writer, int32_t{2});
+ EXPECT_EQ("i", message->GetSignature());
+ DBusParamWriter::Append(&writer, std::string{"foo"});
+ EXPECT_EQ("is", message->GetSignature());
+ DBusParamWriter::Append(&writer, ObjectPath{"/o"});
+ EXPECT_EQ("iso", message->GetSignature());
+
+ int32_t int_value = 0;
+ std::string string_value;
+ ObjectPath path_value;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &int_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &string_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &path_value));
+
+ EXPECT_EQ(2, int_value);
+ EXPECT_EQ("foo", string_value);
+ EXPECT_EQ(ObjectPath{"/o"}, path_value);
+}
+
+TEST(DBusParamWriter, Append_ManyArgs) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ DBusParamWriter::Append(&writer, int32_t{9}, Any{7.5}, true);
+ EXPECT_EQ("ivb", message->GetSignature());
+
+ int32_t int_value = 0;
+ Any variant_value;
+ bool bool_value = false;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &int_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &variant_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &bool_value));
+
+ EXPECT_EQ(9, int_value);
+ EXPECT_DOUBLE_EQ(7.5, variant_value.Get<double>());
+ EXPECT_TRUE(bool_value);
+}
+
+TEST(DBusParamWriter, AppendDBusOutParams_NoArgs) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ DBusParamWriter::AppendDBusOutParams(&writer);
+ EXPECT_EQ("", message->GetSignature());
+}
+
+TEST(DBusParamWriter, AppendDBusOutParams_OneArg) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ int32_t int_value_in{5};
+ std::string string_value_in{"bar"};
+ ObjectPath path_value_in{"/obj/path"};
+
+ DBusParamWriter::AppendDBusOutParams(&writer, &int_value_in);
+ EXPECT_EQ("i", message->GetSignature());
+ DBusParamWriter::AppendDBusOutParams(&writer, &string_value_in);
+ EXPECT_EQ("is", message->GetSignature());
+ DBusParamWriter::AppendDBusOutParams(&writer, &path_value_in);
+ EXPECT_EQ("iso", message->GetSignature());
+
+ int32_t int_value = 0;
+ std::string string_value;
+ ObjectPath path_value;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &int_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &string_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &path_value));
+
+ EXPECT_EQ(5, int_value);
+ EXPECT_EQ("bar", string_value);
+ EXPECT_EQ(ObjectPath{"/obj/path"}, path_value);
+}
+
+TEST(DBusParamWriter, AppendDBusOutParams_ManyArgs) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ int32_t int_value_in{8};
+ Any variant_value_in{8.5};
+ bool bool_value_in{true};
+ DBusParamWriter::AppendDBusOutParams(
+ &writer, &int_value_in, &variant_value_in, &bool_value_in);
+ EXPECT_EQ("ivb", message->GetSignature());
+
+ int32_t int_value = 0;
+ Any variant_value;
+ bool bool_value = false;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &int_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &variant_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &bool_value));
+
+ EXPECT_EQ(8, int_value);
+ EXPECT_DOUBLE_EQ(8.5, variant_value.Get<double>());
+ EXPECT_TRUE(bool_value);
+}
+
+TEST(DBusParamWriter, AppendDBusOutParams_Mixed_NoArgs) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ DBusParamWriter::AppendDBusOutParams(&writer, 3, 5);
+ EXPECT_EQ("", message->GetSignature());
+}
+
+TEST(DBusParamWriter, AppendDBusOutParams_Mixed_OneArg) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ int32_t int_value_in{5};
+ std::string str_value_in{"bar"};
+ ObjectPath path_value_in{"/obj"};
+
+ DBusParamWriter::AppendDBusOutParams(&writer, 2, &int_value_in);
+ EXPECT_EQ("i", message->GetSignature());
+ DBusParamWriter::AppendDBusOutParams(&writer, &str_value_in, 0);
+ EXPECT_EQ("is", message->GetSignature());
+ DBusParamWriter::AppendDBusOutParams(&writer, 1, &path_value_in, 2);
+ EXPECT_EQ("iso", message->GetSignature());
+
+ int32_t int_value = 0;
+ std::string string_value;
+ ObjectPath path_value;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &int_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &string_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &path_value));
+
+ EXPECT_EQ(5, int_value);
+ EXPECT_EQ("bar", string_value);
+ EXPECT_EQ(ObjectPath{"/obj"}, path_value);
+}
+
+TEST(DBusParamWriter, AppendDBusOutParams_Mixed_ManyArgs) {
+ std::unique_ptr<Response> message = Response::CreateEmpty();
+ MessageWriter writer(message.get());
+ int32_t int_value_in{8};
+ Any variant_value_in{7.5};
+ bool bool_value_in{true};
+ DBusParamWriter::AppendDBusOutParams(
+ &writer, 0, &int_value_in, 1, &variant_value_in, 2, &bool_value_in, 3);
+ EXPECT_EQ("ivb", message->GetSignature());
+
+ int32_t int_value = 0;
+ Any variant_value;
+ bool bool_value = false;
+
+ MessageReader reader(message.get());
+ EXPECT_TRUE(PopValueFromReader(&reader, &int_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &variant_value));
+ EXPECT_TRUE(PopValueFromReader(&reader, &bool_value));
+
+ EXPECT_EQ(8, int_value);
+ EXPECT_DOUBLE_EQ(7.5, variant_value.Get<double>());
+ EXPECT_TRUE(bool_value);
+}
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_property.h b/libbrillo/brillo/dbus/dbus_property.h
new file mode 100644
index 0000000..01b850d
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_property.h
@@ -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.
+
+#ifndef LIBBRILLO_BRILLO_DBUS_DBUS_PROPERTY_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_PROPERTY_H_
+
+#include <brillo/dbus/data_serialization.h>
+#include <dbus/property.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+// Re-implementation of dbus::Property<T> that can handle any type supported by
+// D-Bus data serialization layer, such as vectors, maps, tuples, etc.
+// This class is pretty much a copy of dbus::Property<T> from dbus/property.h
+// except that it provides the implementations for PopValueFromReader and
+// AppendSetValueToWriter.
+template<class T>
+class Property : public dbus::PropertyBase {
+ public:
+ Property() = default;
+
+ // Retrieves the cached value.
+ const T& value() const { return value_; }
+
+ // Requests an updated value from the remote object incurring a
+ // round-trip. |callback| will be called when the new value is available.
+ // This may not be implemented by some interfaces.
+ void Get(dbus::PropertySet::GetCallback callback) {
+ property_set()->Get(this, callback);
+ }
+
+ // Synchronous vesion of Get().
+ bool GetAndBlock() {
+ return property_set()->GetAndBlock(this);
+ }
+
+ // Requests that the remote object change the property value to |value|,
+ // |callback| will be called to indicate the success or failure of the
+ // request, however the new value may not be available depending on the
+ // remote object.
+ void Set(const T& value, dbus::PropertySet::SetCallback callback) {
+ set_value_ = value;
+ property_set()->Set(this, callback);
+ }
+
+ // Synchronous version of Set().
+ bool SetAndBlock(const T& value) {
+ set_value_ = value;
+ return property_set()->SetAndBlock(this);
+ }
+
+ // Method used by PropertySet to retrieve the value from a MessageReader,
+ // no knowledge of the contained type is required, this method returns
+ // true if its expected type was found, false if not.
+ bool PopValueFromReader(dbus::MessageReader* reader) override {
+ return PopVariantValueFromReader(reader, &value_);
+ }
+
+ // Method used by PropertySet to append the set value to a MessageWriter,
+ // no knowledge of the contained type is required.
+ // Implementation provided by specialization.
+ void AppendSetValueToWriter(dbus::MessageWriter* writer) override {
+ AppendValueToWriterAsVariant(writer, set_value_);
+ }
+
+ // Method used by test and stub implementations of dbus::PropertySet::Set
+ // to replace the property value with the set value without using a
+ // dbus::MessageReader.
+ void ReplaceValueWithSetValue() override {
+ value_ = set_value_;
+ property_set()->NotifyPropertyChanged(name());
+ }
+
+ // Method used by test and stub implementations to directly set the
+ // value of a property.
+ void ReplaceValue(const T& value) {
+ value_ = value;
+ property_set()->NotifyPropertyChanged(name());
+ }
+
+ private:
+ // Current cached value of the property.
+ T value_;
+
+ // Replacement value of the property.
+ T set_value_;
+};
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_PROPERTY_H_
diff --git a/libbrillo/brillo/dbus/dbus_service_watcher.cc b/libbrillo/brillo/dbus/dbus_service_watcher.cc
new file mode 100644
index 0000000..ede2e6e
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_service_watcher.cc
@@ -0,0 +1,39 @@
+// Copyright 2015 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 <brillo/dbus/dbus_service_watcher.h>
+
+#include <base/bind.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+DBusServiceWatcher::DBusServiceWatcher(
+ scoped_refptr<dbus::Bus> bus,
+ const std::string& connection_name,
+ const base::Closure& on_connection_vanish)
+ : bus_{bus},
+ connection_name_{connection_name},
+ on_connection_vanish_{on_connection_vanish} {
+ monitoring_callback_ = base::Bind(
+ &DBusServiceWatcher::OnServiceOwnerChange, weak_factory_.GetWeakPtr());
+ // Register to listen, and then request the current owner;
+ bus_->ListenForServiceOwnerChange(connection_name_, monitoring_callback_);
+ bus_->GetServiceOwner(connection_name_, monitoring_callback_);
+}
+
+DBusServiceWatcher::~DBusServiceWatcher() {
+ bus_->UnlistenForServiceOwnerChange(
+ connection_name_, monitoring_callback_);
+}
+
+void DBusServiceWatcher::OnServiceOwnerChange(
+ const std::string& service_owner) {
+ if (service_owner.empty()) {
+ on_connection_vanish_.Run();
+ }
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_service_watcher.h b/libbrillo/brillo/dbus/dbus_service_watcher.h
new file mode 100644
index 0000000..0031771
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_service_watcher.h
@@ -0,0 +1,53 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_DBUS_DBUS_SERVICE_WATCHER_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_SERVICE_WATCHER_H_
+
+#include <string>
+
+#include <base/callback.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/weak_ptr.h>
+#include <brillo/brillo_export.h>
+#include <dbus/bus.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+// DBusServiceWatcher just asks the bus to notify us when the owner of a remote
+// DBus connection transitions to the empty string. After registering a
+// callback to be notified of name owner transitions, for the given
+// |connection_name|, DBusServiceWatcher asks for the current owner. If at any
+// point an empty string is found for the connection name owner,
+// DBusServiceWatcher will call back to notify of the connection vanishing.
+//
+// The chief value of this class is that it manages the lifetime of the
+// registered callback in the Bus, because failure to remove callbacks will
+// cause the Bus to crash the process on destruction.
+class BRILLO_EXPORT DBusServiceWatcher {
+ public:
+ DBusServiceWatcher(scoped_refptr<dbus::Bus> bus,
+ const std::string& connection_name,
+ const base::Closure& on_connection_vanish);
+ virtual ~DBusServiceWatcher();
+ virtual std::string connection_name() const { return connection_name_; }
+
+ private:
+ void OnServiceOwnerChange(const std::string& service_owner);
+
+ scoped_refptr<dbus::Bus> bus_;
+ const std::string connection_name_;
+ dbus::Bus::GetServiceOwnerCallback monitoring_callback_;
+ base::Closure on_connection_vanish_;
+
+ base::WeakPtrFactory<DBusServiceWatcher> weak_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(DBusServiceWatcher);
+};
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_SERVICE_WATCHER_H_
diff --git a/libbrillo/brillo/dbus/dbus_signal.cc b/libbrillo/brillo/dbus/dbus_signal.cc
new file mode 100644
index 0000000..d227bd7
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_signal.cc
@@ -0,0 +1,28 @@
+// 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 <brillo/dbus/dbus_signal.h>
+
+#include <brillo/dbus/dbus_object.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+DBusSignalBase::DBusSignalBase(DBusObject* dbus_object,
+ const std::string& interface_name,
+ const std::string& signal_name)
+ : interface_name_(interface_name),
+ signal_name_(signal_name),
+ dbus_object_(dbus_object) {
+}
+
+bool DBusSignalBase::SendSignal(dbus::Signal* signal) const {
+ // This sends the signal asynchronously. However, the raw message inside
+ // the signal object is ref-counted, so we're fine to pass a stack-allocated
+ // Signal object here.
+ return dbus_object_->SendSignal(signal);
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/dbus_signal.h b/libbrillo/brillo/dbus/dbus_signal.h
new file mode 100644
index 0000000..bda322a
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_signal.h
@@ -0,0 +1,68 @@
+// 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 LIBBRILLO_BRILLO_DBUS_DBUS_SIGNAL_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_SIGNAL_H_
+
+#include <string>
+#include <typeinfo>
+
+#include <base/bind.h>
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+#include <brillo/dbus/dbus_param_writer.h>
+#include <dbus/message.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+class DBusObject;
+
+// Base class for D-Bus signal proxy classes.
+// Used mostly to store the polymorphic DBusSignal<...> in a single map
+// container inside DBusInterface object.
+class BRILLO_EXPORT DBusSignalBase {
+ public:
+ DBusSignalBase(DBusObject* dbus_object,
+ const std::string& interface_name,
+ const std::string& signal_name);
+ virtual ~DBusSignalBase() = default;
+
+ protected:
+ bool SendSignal(dbus::Signal* signal) const;
+
+ std::string interface_name_;
+ std::string signal_name_;
+
+ private:
+ DBusObject* dbus_object_;
+
+ DISALLOW_COPY_AND_ASSIGN(DBusSignalBase);
+};
+
+// DBusSignal<...> is a concrete signal proxy class that knows about the
+// exact number of signal arguments and their types.
+template<typename... Args>
+class DBusSignal : public DBusSignalBase {
+ public:
+ // Expose the custom constructor from DBusSignalBase.
+ using DBusSignalBase::DBusSignalBase;
+ ~DBusSignal() override = default;
+
+ // DBusSignal<...>::Send(...) dispatches the signal with the given arguments.
+ bool Send(const Args&... args) const {
+ dbus::Signal signal(interface_name_, signal_name_);
+ dbus::MessageWriter signal_writer(&signal);
+ DBusParamWriter::Append(&signal_writer, args...);
+ return SendSignal(&signal);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DBusSignal);
+};
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_SIGNAL_H_
diff --git a/libbrillo/brillo/dbus/dbus_signal_handler.h b/libbrillo/brillo/dbus/dbus_signal_handler.h
new file mode 100644
index 0000000..9c3246c
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_signal_handler.h
@@ -0,0 +1,70 @@
+// 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 LIBBRILLO_BRILLO_DBUS_DBUS_SIGNAL_HANDLER_H_
+#define LIBBRILLO_BRILLO_DBUS_DBUS_SIGNAL_HANDLER_H_
+
+#include <string>
+
+#include <brillo/bind_lambda.h>
+#include <brillo/dbus/dbus_param_reader.h>
+#include <dbus/message.h>
+#include <dbus/object_proxy.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+// brillo::dbus_utils::ConnectToSignal() is a helper function similar to
+// dbus::ObjectProxy::ConnectToSignal() but the |signal_callback| is an actual
+// C++ signal handler with expected signal parameters as native method args.
+//
+// brillo::dbus_utils::ConnectToSignal() actually registers a stub signal
+// handler with D-Bus which has a standard signature that matches
+// dbus::ObjectProxy::SignalCallback.
+//
+// When a D-Bus signal is emitted, the stub handler is invoked, which unpacks
+// the expected parameters from dbus::Signal message and then calls
+// |signal_callback| with unpacked arguments.
+//
+// If the signal message doesn't contain correct number or types of arguments,
+// an error message is logged to the system log and the signal is ignored
+// (|signal_callback| is not invoked).
+template<typename... Args>
+void ConnectToSignal(
+ dbus::ObjectProxy* object_proxy,
+ const std::string& interface_name,
+ const std::string& signal_name,
+ base::Callback<void(Args...)> signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) {
+ // Raw signal handler stub method. When called, unpacks the signal arguments
+ // from |signal| message buffer and redirects the call to
+ // |signal_callback_wrapper| which, in turn, would call the user-provided
+ // |signal_callback|.
+ auto dbus_signal_callback = [](
+ const base::Callback<void(Args...)>& signal_callback,
+ dbus::Signal* signal) {
+ // DBusParamReader::Invoke() needs a functor object, not a base::Callback.
+ // Wrap the callback with lambda so we can redirect the call.
+ auto signal_callback_wrapper = [signal_callback](const Args&... args) {
+ if (!signal_callback.is_null()) {
+ signal_callback.Run(args...);
+ }
+ };
+
+ dbus::MessageReader reader(signal);
+ DBusParamReader<false, Args...>::Invoke(
+ signal_callback_wrapper, &reader, nullptr);
+ };
+
+ // Register our stub handler with D-Bus ObjectProxy.
+ object_proxy->ConnectToSignal(interface_name,
+ signal_name,
+ base::Bind(dbus_signal_callback, signal_callback),
+ on_connected_callback);
+}
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_DBUS_SIGNAL_HANDLER_H_
diff --git a/libbrillo/brillo/dbus/dbus_signal_handler_unittest.cc b/libbrillo/brillo/dbus/dbus_signal_handler_unittest.cc
new file mode 100644
index 0000000..e0bea10
--- /dev/null
+++ b/libbrillo/brillo/dbus/dbus_signal_handler_unittest.cc
@@ -0,0 +1,145 @@
+// 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 <brillo/dbus/dbus_signal_handler.h>
+
+#include <string>
+
+#include <brillo/bind_lambda.h>
+#include <brillo/dbus/dbus_param_writer.h>
+#include <dbus/mock_bus.h>
+#include <dbus/mock_object_proxy.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::AnyNumber;
+using testing::Return;
+using testing::SaveArg;
+using testing::_;
+
+namespace brillo {
+namespace dbus_utils {
+
+const char kTestPath[] = "/test/path";
+const char kTestServiceName[] = "org.test.Object";
+const char kInterface[] = "org.test.Object.TestInterface";
+const char kSignal[] = "TestSignal";
+
+class DBusSignalHandlerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ 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 object proxy.
+ mock_object_proxy_ = new dbus::MockObjectProxy(
+ bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath));
+ EXPECT_CALL(*bus_,
+ GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath)))
+ .WillRepeatedly(Return(mock_object_proxy_.get()));
+ }
+
+ void TearDown() override { bus_ = nullptr; }
+
+ protected:
+ template<typename SignalHandlerSink, typename... Args>
+ void CallSignal(SignalHandlerSink* sink, Args... args) {
+ dbus::ObjectProxy::SignalCallback signal_callback;
+ EXPECT_CALL(*mock_object_proxy_, ConnectToSignal(kInterface, kSignal, _, _))
+ .WillOnce(SaveArg<2>(&signal_callback));
+
+ brillo::dbus_utils::ConnectToSignal(
+ mock_object_proxy_.get(),
+ kInterface,
+ kSignal,
+ base::Bind(&SignalHandlerSink::Handler, base::Unretained(sink)),
+ {});
+
+ dbus::Signal signal(kInterface, kSignal);
+ dbus::MessageWriter writer(&signal);
+ DBusParamWriter::Append(&writer, args...);
+ signal_callback.Run(&signal);
+ }
+
+ scoped_refptr<dbus::MockBus> bus_;
+ scoped_refptr<dbus::MockObjectProxy> mock_object_proxy_;
+};
+
+TEST_F(DBusSignalHandlerTest, ConnectToSignal) {
+ EXPECT_CALL(*mock_object_proxy_, ConnectToSignal(kInterface, kSignal, _, _))
+ .Times(1);
+
+ brillo::dbus_utils::ConnectToSignal(
+ mock_object_proxy_.get(), kInterface, kSignal, base::Closure{}, {});
+}
+
+TEST_F(DBusSignalHandlerTest, CallSignal_3Args) {
+ class SignalHandlerSink {
+ public:
+ MOCK_METHOD3(Handler, void(int, int, double));
+ } sink;
+
+ EXPECT_CALL(sink, Handler(10, 20, 30.5)).Times(1);
+ CallSignal(&sink, 10, 20, 30.5);
+}
+
+TEST_F(DBusSignalHandlerTest, CallSignal_2Args) {
+ class SignalHandlerSink {
+ public:
+ // Take string both by reference and by value to make sure this works too.
+ MOCK_METHOD2(Handler, void(const std::string&, std::string));
+ } sink;
+
+ EXPECT_CALL(sink, Handler(std::string{"foo"}, std::string{"bar"})).Times(1);
+ CallSignal(&sink, std::string{"foo"}, std::string{"bar"});
+}
+
+TEST_F(DBusSignalHandlerTest, CallSignal_NoArgs) {
+ class SignalHandlerSink {
+ public:
+ MOCK_METHOD0(Handler, void());
+ } sink;
+
+ EXPECT_CALL(sink, Handler()).Times(1);
+ CallSignal(&sink);
+}
+
+TEST_F(DBusSignalHandlerTest, CallSignal_Error_TooManyArgs) {
+ class SignalHandlerSink {
+ public:
+ MOCK_METHOD0(Handler, void());
+ } sink;
+
+ // Handler() expects no args, but we send an int.
+ EXPECT_CALL(sink, Handler()).Times(0);
+ CallSignal(&sink, 5);
+}
+
+TEST_F(DBusSignalHandlerTest, CallSignal_Error_TooFewArgs) {
+ class SignalHandlerSink {
+ public:
+ MOCK_METHOD2(Handler, void(std::string, bool));
+ } sink;
+
+ // Handler() expects 2 args while we send it just one.
+ EXPECT_CALL(sink, Handler(_, _)).Times(0);
+ CallSignal(&sink, std::string{"bar"});
+}
+
+TEST_F(DBusSignalHandlerTest, CallSignal_Error_TypeMismatchArgs) {
+ class SignalHandlerSink {
+ public:
+ MOCK_METHOD2(Handler, void(std::string, bool));
+ } sink;
+
+ // Handler() expects "sb" while we send it "ii".
+ EXPECT_CALL(sink, Handler(_, _)).Times(0);
+ CallSignal(&sink, 1, 2);
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/exported_object_manager.cc b/libbrillo/brillo/dbus/exported_object_manager.cc
new file mode 100644
index 0000000..61dae68
--- /dev/null
+++ b/libbrillo/brillo/dbus/exported_object_manager.cc
@@ -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.
+
+#include <brillo/dbus/exported_object_manager.h>
+
+#include <vector>
+
+#include <brillo/dbus/async_event_sequencer.h>
+#include <dbus/object_manager.h>
+
+using brillo::dbus_utils::AsyncEventSequencer;
+
+namespace brillo {
+
+namespace dbus_utils {
+
+ExportedObjectManager::ExportedObjectManager(scoped_refptr<dbus::Bus> bus,
+ const dbus::ObjectPath& path)
+ : bus_(bus), dbus_object_(nullptr, bus, path) {
+}
+
+void ExportedObjectManager::RegisterAsync(
+ const AsyncEventSequencer::CompletionAction& completion_callback) {
+ VLOG(1) << "Registering object manager";
+ bus_->AssertOnOriginThread();
+ DBusInterface* itf =
+ dbus_object_.AddOrGetInterface(dbus::kObjectManagerInterface);
+ itf->AddSimpleMethodHandler(dbus::kObjectManagerGetManagedObjects,
+ base::Unretained(this),
+ &ExportedObjectManager::HandleGetManagedObjects);
+
+ signal_itf_added_ = itf->RegisterSignalOfType<SignalInterfacesAdded>(
+ dbus::kObjectManagerInterfacesAdded);
+ signal_itf_removed_ = itf->RegisterSignalOfType<SignalInterfacesRemoved>(
+ dbus::kObjectManagerInterfacesRemoved);
+ dbus_object_.RegisterAsync(completion_callback);
+}
+
+void ExportedObjectManager::ClaimInterface(
+ const dbus::ObjectPath& path,
+ const std::string& interface_name,
+ const ExportedPropertySet::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);
+ VariantDictionary property_dict;
+ property_writer.Run(&property_dict);
+ std::map<std::string, VariantDictionary> interfaces_and_properties{
+ {interface_name, property_dict}
+ };
+ signal_itf_added_.lock()->Send(path, interfaces_and_properties);
+ 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.empty())
+ registered_objects_.erase(path);
+
+ // We're sending signals that look like:
+ // org.freedesktop.DBus.ObjectManager.InterfacesRemoved (
+ // OBJPATH object_path, ARRAY<STRING> interfaces);
+ signal_itf_removed_.lock()->Send(path,
+ std::vector<std::string>{interface_name});
+}
+
+ExportedObjectManager::ObjectMap
+ExportedObjectManager::HandleGetManagedObjects() {
+ // Implements the GetManagedObjects method:
+ //
+ // org.freedesktop.DBus.ObjectManager.GetManagedObjects (
+ // out DICT<OBJPATH,
+ // DICT<STRING,
+ // DICT<STRING,VARIANT>>> )
+ bus_->AssertOnOriginThread();
+ ExportedObjectManager::ObjectMap objects;
+ for (const auto path_pair : registered_objects_) {
+ std::map<std::string, VariantDictionary>& interfaces =
+ objects[path_pair.first];
+ const InterfaceProperties& interface2properties = path_pair.second;
+ for (const auto interface : interface2properties) {
+ interface.second.Run(&interfaces[interface.first]);
+ }
+ }
+ return objects;
+}
+
+} // namespace dbus_utils
+
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/exported_object_manager.h b/libbrillo/brillo/dbus/exported_object_manager.h
new file mode 100644
index 0000000..01dab5b
--- /dev/null
+++ b/libbrillo/brillo/dbus/exported_object_manager.h
@@ -0,0 +1,135 @@
+// 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 LIBBRILLO_BRILLO_DBUS_EXPORTED_OBJECT_MANAGER_H_
+#define LIBBRILLO_BRILLO_DBUS_EXPORTED_OBJECT_MANAGER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/memory/weak_ptr.h>
+#include <brillo/brillo_export.h>
+#include <brillo/dbus/dbus_object.h>
+#include <brillo/dbus/exported_property_set.h>
+#include <brillo/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.h>
+#include <dbus/object_path.h>
+
+namespace brillo {
+
+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 RegisterAsync(const CompletionAction& cb) {
+// object_manager_.RegisterAsync(cb);
+// }
+// void ClaimInterface(const dbus::ObjectPath& path,
+// const std::string& interface_name,
+// const ExportedPropertySet::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 BRILLO_EXPORT ExportedObjectManager
+ : public base::SupportsWeakPtr<ExportedObjectManager> {
+ public:
+ using ObjectMap =
+ std::map<dbus::ObjectPath, std::map<std::string, VariantDictionary>>;
+ using InterfaceProperties =
+ std::map<std::string, ExportedPropertySet::PropertyWriter>;
+
+ ExportedObjectManager(scoped_refptr<dbus::Bus> bus,
+ const dbus::ObjectPath& path);
+ virtual ~ExportedObjectManager() = default;
+
+ // Registers methods implementing the ObjectManager interface on the object
+ // exported on the path given in the constructor. Must be called on the
+ // origin thread.
+ virtual void RegisterAsync(
+ const brillo::dbus_utils::AsyncEventSequencer::CompletionAction&
+ completion_callback);
+
+ // Trigger a signal that |path| has added an interface |interface_name|
+ // with properties as given by |writer|.
+ virtual void ClaimInterface(
+ const dbus::ObjectPath& path,
+ const std::string& interface_name,
+ const ExportedPropertySet::PropertyWriter& writer);
+
+ // Trigger a signal that |path| has removed an interface |interface_name|.
+ virtual void ReleaseInterface(const dbus::ObjectPath& path,
+ const std::string& interface_name);
+
+ const scoped_refptr<dbus::Bus>& GetBus() const { return bus_; }
+
+ private:
+ BRILLO_PRIVATE ObjectMap HandleGetManagedObjects();
+
+ scoped_refptr<dbus::Bus> bus_;
+ brillo::dbus_utils::DBusObject dbus_object_;
+ // Tracks all objects currently known to the ExportedObjectManager.
+ std::map<dbus::ObjectPath, InterfaceProperties> registered_objects_;
+
+ using SignalInterfacesAdded =
+ DBusSignal<dbus::ObjectPath, std::map<std::string, VariantDictionary>>;
+ using SignalInterfacesRemoved =
+ DBusSignal<dbus::ObjectPath, std::vector<std::string>>;
+
+ std::weak_ptr<SignalInterfacesAdded> signal_itf_added_;
+ std::weak_ptr<SignalInterfacesRemoved> signal_itf_removed_;
+
+ friend class ExportedObjectManagerTest;
+ DISALLOW_COPY_AND_ASSIGN(ExportedObjectManager);
+};
+
+} // namespace dbus_utils
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_EXPORTED_OBJECT_MANAGER_H_
diff --git a/libbrillo/brillo/dbus/exported_object_manager_unittest.cc b/libbrillo/brillo/dbus/exported_object_manager_unittest.cc
new file mode 100644
index 0000000..00fe108
--- /dev/null
+++ b/libbrillo/brillo/dbus/exported_object_manager_unittest.cc
@@ -0,0 +1,194 @@
+// 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 <brillo/dbus/exported_object_manager.h>
+
+#include <base/bind.h>
+#include <brillo/dbus/dbus_object_test_helpers.h>
+#include <brillo/dbus/utils.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 brillo {
+
+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(VariantDictionary* dict) {
+ dict->insert(std::make_pair(kTestPropertyName, Any(kTestPropertyValue)));
+}
+
+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:
+ void SetUp() override {
+ 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()));
+ EXPECT_CALL(*mock_exported_object_, ExportMethod(_, _, _, _))
+ .Times(AnyNumber());
+ om_.reset(new ExportedObjectManager(bus_.get(), kTestPath));
+ property_writer_ = base::Bind(&WriteTestPropertyDict);
+ om_->RegisterAsync(AsyncEventSequencer::GetDefaultCompletionAction());
+ }
+
+ void TearDown() override {
+ EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1);
+ om_.reset();
+ bus_ = nullptr;
+ }
+
+ std::unique_ptr<dbus::Response> CallHandleGetManagedObjects() {
+ dbus::MethodCall method_call(dbus::kObjectManagerInterface,
+ dbus::kObjectManagerGetManagedObjects);
+ method_call.SetSerial(1234);
+ return brillo::dbus_utils::testing::CallMethod(om_->dbus_object_,
+ &method_call);
+ }
+
+ scoped_refptr<dbus::MockBus> bus_;
+ scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
+ std::unique_ptr<ExportedObjectManager> om_;
+ ExportedPropertySet::PropertyWriter property_writer_;
+};
+
+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) {
+ auto response = CallHandleGetManagedObjects();
+ dbus::MessageReader reader(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>>> )
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1);
+ om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_);
+ auto response = CallHandleGetManagedObjects();
+ dbus::MessageReader reader(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 brillo
diff --git a/libbrillo/brillo/dbus/exported_property_set.cc b/libbrillo/brillo/dbus/exported_property_set.cc
new file mode 100644
index 0000000..8d6ae65
--- /dev/null
+++ b/libbrillo/brillo/dbus/exported_property_set.cc
@@ -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.
+
+#include <brillo/dbus/exported_property_set.h>
+
+#include <base/bind.h>
+#include <dbus/bus.h>
+#include <dbus/property.h> // For kPropertyInterface
+
+#include <brillo/dbus/async_event_sequencer.h>
+#include <brillo/dbus/dbus_object.h>
+#include <brillo/errors/error_codes.h>
+
+using brillo::dbus_utils::AsyncEventSequencer;
+
+namespace brillo {
+
+namespace dbus_utils {
+
+ExportedPropertySet::ExportedPropertySet(dbus::Bus* bus)
+ : bus_(bus), weak_ptr_factory_(this) {
+}
+
+void ExportedPropertySet::OnPropertiesInterfaceExported(
+ DBusInterface* prop_interface) {
+ signal_properties_changed_ =
+ prop_interface->RegisterSignalOfType<SignalPropertiesChanged>(
+ dbus::kPropertiesChanged);
+}
+
+ExportedPropertySet::PropertyWriter ExportedPropertySet::GetPropertyWriter(
+ const std::string& interface_name) {
+ return base::Bind(&ExportedPropertySet::WritePropertiesToDict,
+ weak_ptr_factory_.GetWeakPtr(),
+ interface_name);
+}
+
+void ExportedPropertySet::RegisterProperty(
+ const std::string& interface_name,
+ const std::string& property_name,
+ ExportedPropertyBase* exported_property) {
+ bus_->AssertOnOriginThread();
+ auto& prop_map = properties_[interface_name];
+ auto res = prop_map.insert(std::make_pair(property_name, exported_property));
+ CHECK(res.second) << "Property '" << property_name << "' already exists";
+ // 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);
+}
+
+VariantDictionary ExportedPropertySet::HandleGetAll(
+ const std::string& interface_name) {
+ bus_->AssertOnOriginThread();
+ return GetInterfaceProperties(interface_name);
+}
+
+VariantDictionary ExportedPropertySet::GetInterfaceProperties(
+ const std::string& interface_name) const {
+ VariantDictionary properties;
+ auto property_map_itr = properties_.find(interface_name);
+ if (property_map_itr != properties_.end()) {
+ for (const auto& kv : property_map_itr->second)
+ properties.insert(std::make_pair(kv.first, kv.second->GetValue()));
+ }
+ return properties;
+}
+
+void ExportedPropertySet::WritePropertiesToDict(
+ const std::string& interface_name,
+ VariantDictionary* dict) {
+ *dict = GetInterfaceProperties(interface_name);
+}
+
+bool ExportedPropertySet::HandleGet(brillo::ErrorPtr* error,
+ const std::string& interface_name,
+ const std::string& property_name,
+ brillo::Any* result) {
+ bus_->AssertOnOriginThread();
+ auto property_map_itr = properties_.find(interface_name);
+ if (property_map_itr == properties_.end()) {
+ brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
+ DBUS_ERROR_UNKNOWN_INTERFACE,
+ "No such interface on object.");
+ return false;
+ }
+ LOG(INFO) << "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()) {
+ brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
+ DBUS_ERROR_UNKNOWN_PROPERTY,
+ "No such property on interface.");
+ return false;
+ }
+ *result = property_itr->second->GetValue();
+ return true;
+}
+
+bool ExportedPropertySet::HandleSet(brillo::ErrorPtr* error,
+ const std::string& interface_name,
+ const std::string& property_name,
+ const brillo::Any& value) {
+ bus_->AssertOnOriginThread();
+ auto property_map_itr = properties_.find(interface_name);
+ if (property_map_itr == properties_.end()) {
+ brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
+ DBUS_ERROR_UNKNOWN_INTERFACE,
+ "No such interface on object.");
+ return false;
+ }
+ LOG(INFO) << "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()) {
+ brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
+ DBUS_ERROR_UNKNOWN_PROPERTY,
+ "No such property on interface.");
+ return false;
+ }
+
+ return property_itr->second->SetValue(error, value);
+}
+
+void ExportedPropertySet::HandlePropertyUpdated(
+ const std::string& interface_name,
+ const std::string& property_name,
+ const ExportedPropertyBase* exported_property) {
+ bus_->AssertOnOriginThread();
+ // Send signal only if the object has been exported successfully.
+ // This could happen when a property value is changed (which triggers
+ // the notification) before D-Bus interface is completely exported/claimed.
+ auto signal = signal_properties_changed_.lock();
+ if (!signal)
+ return;
+ VariantDictionary changed_properties{
+ {property_name, exported_property->GetValue()}};
+ // The interface specification tells us to include this list of properties
+ // which have changed, but for whom no value is conveyed. Currently, we
+ // don't do anything interesting here.
+ std::vector<std::string> invalidated_properties; // empty.
+ signal->Send(interface_name, changed_properties, invalidated_properties);
+}
+
+void ExportedPropertyBase::NotifyPropertyChanged() {
+ // 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_callback_.is_null()) {
+ on_update_callback_.Run(this);
+ }
+}
+
+void ExportedPropertyBase::SetUpdateCallback(const OnUpdateCallback& cb) {
+ on_update_callback_ = cb;
+}
+
+void ExportedPropertyBase::SetAccessMode(
+ ExportedPropertyBase::Access access_mode) {
+ access_mode_ = access_mode;
+}
+
+ExportedPropertyBase::Access ExportedPropertyBase::GetAccessMode() const {
+ return access_mode_;
+}
+
+} // namespace dbus_utils
+
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/exported_property_set.h b/libbrillo/brillo/dbus/exported_property_set.h
new file mode 100644
index 0000000..f10511f
--- /dev/null
+++ b/libbrillo/brillo/dbus/exported_property_set.h
@@ -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.
+
+#ifndef LIBBRILLO_BRILLO_DBUS_EXPORTED_PROPERTY_SET_H_
+#define LIBBRILLO_BRILLO_DBUS_EXPORTED_PROPERTY_SET_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/memory/weak_ptr.h>
+#include <brillo/any.h>
+#include <brillo/brillo_export.h>
+#include <brillo/dbus/dbus_signal.h>
+#include <brillo/errors/error.h>
+#include <brillo/errors/error_codes.h>
+#include <brillo/variant_dictionary.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.h>
+
+namespace brillo {
+
+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.
+// It is used as part of DBusObject to implement D-Bus object properties on
+// registered interfaces. See description of DBusObject class for more details.
+
+class DBusInterface;
+class DBusObject;
+
+class BRILLO_EXPORT ExportedPropertyBase {
+ public:
+ enum class Access {
+ kReadOnly,
+ kWriteOnly,
+ kReadWrite,
+ };
+
+ ExportedPropertyBase() = default;
+ virtual ~ExportedPropertyBase() = default;
+
+ using OnUpdateCallback = base::Callback<void(const ExportedPropertyBase*)>;
+
+ // 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);
+
+ // Returns the contained value as Any.
+ virtual brillo::Any GetValue() const = 0;
+
+ virtual bool SetValue(brillo::ErrorPtr* error,
+ const brillo::Any& value) = 0;
+
+ void SetAccessMode(Access access_mode);
+ Access GetAccessMode() const;
+
+ protected:
+ // Notify the listeners of OnUpdateCallback that the property has changed.
+ void NotifyPropertyChanged();
+
+ private:
+ OnUpdateCallback on_update_callback_;
+ // Default to read-only.
+ Access access_mode_{Access::kReadOnly};
+};
+
+class BRILLO_EXPORT ExportedPropertySet {
+ public:
+ using PropertyWriter = base::Callback<void(VariantDictionary* dict)>;
+
+ explicit ExportedPropertySet(dbus::Bus* bus);
+ virtual ~ExportedPropertySet() = default;
+
+ // Called to notify ExportedPropertySet that the Properties interface of the
+ // D-Bus object has been exported successfully and property notification
+ // signals can be sent out.
+ void OnPropertiesInterfaceExported(DBusInterface* prop_interface);
+
+ // 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_name);
+
+ void RegisterProperty(const std::string& interface_name,
+ const std::string& property_name,
+ ExportedPropertyBase* exported_property);
+
+ // D-Bus methods for org.freedesktop.DBus.Properties interface.
+ VariantDictionary HandleGetAll(const std::string& interface_name);
+ bool HandleGet(brillo::ErrorPtr* error,
+ const std::string& interface_name,
+ const std::string& property_name,
+ brillo::Any* result);
+ // 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.
+ bool HandleSet(brillo::ErrorPtr* error,
+ const std::string& interface_name,
+ const std::string& property_name,
+ const brillo::Any& value);
+ // Returns a string-to-variant map of all the properties for the given
+ // interface and their values.
+ VariantDictionary GetInterfaceProperties(
+ const std::string& interface_name) const;
+
+ private:
+ // Used to write the dictionary of string->variant to a message.
+ // This dictionary represents the property name/value pairs for the
+ // given interface.
+ BRILLO_PRIVATE void WritePropertiesToDict(const std::string& interface_name,
+ VariantDictionary* dict);
+ BRILLO_PRIVATE void HandlePropertyUpdated(
+ const std::string& interface_name,
+ const std::string& property_name,
+ const ExportedPropertyBase* exported_property);
+
+ dbus::Bus* bus_; // weak; owned by outer DBusObject containing this 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_;
+
+ using SignalPropertiesChanged =
+ DBusSignal<std::string, VariantDictionary, std::vector<std::string>>;
+
+ std::weak_ptr<SignalPropertiesChanged> signal_properties_changed_;
+
+ friend class DBusObject;
+ friend class ExportedPropertySetTest;
+ DISALLOW_COPY_AND_ASSIGN(ExportedPropertySet);
+};
+
+template<typename T>
+class ExportedProperty : public ExportedPropertyBase {
+ public:
+ ExportedProperty() = default;
+ ~ExportedProperty() override = default;
+
+ // Retrieves the current value.
+ const T& value() const { return value_; }
+
+ // Set the value exposed to remote applications. This triggers notifications
+ // of changes over the Properties interface.
+ void SetValue(const T& new_value) {
+ if (value_ != new_value) {
+ value_ = new_value;
+ this->NotifyPropertyChanged();
+ }
+ }
+
+ // Set the validator for value checking when setting the property by remote
+ // application.
+ void SetValidator(
+ const base::Callback<bool(brillo::ErrorPtr*, const T&)>& validator) {
+ validator_ = validator;
+ }
+
+ // Implementation provided by specialization.
+ brillo::Any GetValue() const override { return value_; }
+
+ bool SetValue(brillo::ErrorPtr* error,
+ const brillo::Any& value) override {
+ if (GetAccessMode() == ExportedPropertyBase::Access::kReadOnly) {
+ brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
+ DBUS_ERROR_PROPERTY_READ_ONLY,
+ "Property is read-only.");
+ return false;
+ }
+ if (!value.IsTypeCompatible<T>()) {
+ brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
+ DBUS_ERROR_INVALID_ARGS,
+ "Argument type mismatched.");
+ return false;
+ }
+ if (value_ == value.Get<T>()) {
+ // No change to the property value, nothing to be done.
+ return true;
+ }
+ if (!validator_.is_null() && !validator_.Run(error, value.Get<T>())) {
+ return false;
+ }
+ value_ = value.Get<T>();
+ return true;
+ }
+
+ private:
+ T value_{};
+ base::Callback<bool(brillo::ErrorPtr*, const T&)> validator_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExportedProperty);
+};
+
+} // namespace dbus_utils
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_EXPORTED_PROPERTY_SET_H_
diff --git a/libbrillo/brillo/dbus/exported_property_set_unittest.cc b/libbrillo/brillo/dbus/exported_property_set_unittest.cc
new file mode 100644
index 0000000..9233b4a
--- /dev/null
+++ b/libbrillo/brillo/dbus/exported_property_set_unittest.cc
@@ -0,0 +1,592 @@
+// 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 <brillo/dbus/exported_property_set.h>
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/macros.h>
+#include <brillo/dbus/dbus_object.h>
+#include <brillo/dbus/dbus_object_test_helpers.h>
+#include <brillo/errors/error_codes.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::_;
+using ::testing::Unused;
+
+namespace brillo {
+
+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:
+ ExportedProperty<bool> bool_prop_;
+ ExportedProperty<uint8_t> uint8_prop_;
+ ExportedProperty<int16_t> int16_prop_;
+ ExportedProperty<uint16_t> uint16_prop_;
+ ExportedProperty<int32_t> int32_prop_;
+ ExportedProperty<uint32_t> uint32_prop_;
+ ExportedProperty<int64_t> int64_prop_;
+ ExportedProperty<uint64_t> 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_t>> uint8list_prop_;
+
+ Properties(scoped_refptr<dbus::Bus> bus, const dbus::ObjectPath& path)
+ : dbus_object_(nullptr, bus, path) {
+ // The empty string is not a valid value for an ObjectPath.
+ path_prop_.SetValue(kTestObjectPathInit);
+ DBusInterface* itf1 = dbus_object_.AddOrGetInterface(kTestInterface1);
+ itf1->AddProperty(kBoolPropName, &bool_prop_);
+ itf1->AddProperty(kUint8PropName, &uint8_prop_);
+ itf1->AddProperty(kInt16PropName, &int16_prop_);
+ // I chose this weird grouping because N=2 is about all the permutations
+ // of GetAll that I want to anticipate.
+ DBusInterface* itf2 = dbus_object_.AddOrGetInterface(kTestInterface2);
+ itf2->AddProperty(kUint16PropName, &uint16_prop_);
+ itf2->AddProperty(kInt32PropName, &int32_prop_);
+ DBusInterface* itf3 = dbus_object_.AddOrGetInterface(kTestInterface3);
+ itf3->AddProperty(kUint32PropName, &uint32_prop_);
+ itf3->AddProperty(kInt64PropName, &int64_prop_);
+ itf3->AddProperty(kUint64PropName, &uint64_prop_);
+ itf3->AddProperty(kDoublePropName, &double_prop_);
+ itf3->AddProperty(kStringPropName, &string_prop_);
+ itf3->AddProperty(kPathPropName, &path_prop_);
+ itf3->AddProperty(kStringListPropName, &stringlist_prop_);
+ itf3->AddProperty(kPathListPropName, &pathlist_prop_);
+ itf3->AddProperty(kUint8ListPropName, &uint8list_prop_);
+ dbus_object_.RegisterAsync(
+ AsyncEventSequencer::GetDefaultCompletionAction());
+ }
+ virtual ~Properties() {}
+
+ DBusObject dbus_object_;
+ };
+
+ void SetUp() override {
+ 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()));
+
+ EXPECT_CALL(*mock_exported_object_,
+ ExportMethod(dbus::kPropertiesInterface, _, _, _)).Times(3);
+ p_.reset(new Properties(bus_, kMethodsExportedOnPath));
+ }
+
+ void TearDown() override {
+ EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1);
+ }
+
+ void AssertMethodReturnsError(dbus::MethodCall* method_call) {
+ method_call->SetSerial(123);
+ auto response = testing::CallMethod(p_->dbus_object_, method_call);
+ ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ }
+
+ std::unique_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);
+ return testing::CallMethod(p_->dbus_object_, &method_call);
+ }
+
+ std::unique_ptr<dbus::Response> SetPropertyOnInterface(
+ const std::string& interface_name,
+ const std::string& property_name,
+ const brillo::Any& value) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesSet);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(interface_name);
+ writer.AppendString(property_name);
+ dbus_utils::AppendValueToWriter(&writer, value);
+ return testing::CallMethod(p_->dbus_object_, &method_call);
+ }
+
+ std::unique_ptr<dbus::Response> last_response_;
+ scoped_refptr<dbus::MockBus> bus_;
+ scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
+ std::unique_ptr<Properties> p_;
+};
+
+template<typename T>
+class PropertyValidatorObserver {
+ public:
+ PropertyValidatorObserver()
+ : validate_property_callback_(
+ base::Bind(&PropertyValidatorObserver::ValidateProperty,
+ base::Unretained(this))) {}
+ virtual ~PropertyValidatorObserver() {}
+
+ MOCK_METHOD2_T(ValidateProperty,
+ bool(brillo::ErrorPtr* error, const T& value));
+
+ const base::Callback<bool(brillo::ErrorPtr*, const T&)>&
+ validate_property_callback() const {
+ return validate_property_callback_;
+ }
+
+ private:
+ base::Callback<bool(brillo::ErrorPtr*, const T&)>
+ validate_property_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(PropertyValidatorObserver);
+};
+
+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);
+ AssertMethodReturnsError(&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 = testing::CallMethod(p_->dbus_object_, &method_call);
+ dbus::MessageReader response_reader(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);
+ AssertMethodReturnsError(&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 = testing::CallMethod(p_->dbus_object_, &method_call);
+ dbus::MessageReader response_reader(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_t value16;
+ int32_t 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);
+ AssertMethodReturnsError(&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);
+ AssertMethodReturnsError(&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");
+ AssertMethodReturnsError(&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);
+ AssertMethodReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetNoPropertyName) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ AssertMethodReturnsError(&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");
+ AssertMethodReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithBool) {
+ auto response = GetPropertyOnInterface(kTestInterface1, kBoolPropName);
+ dbus::MessageReader reader(response.get());
+ bool value;
+ ASSERT_TRUE(reader.PopVariantOfBool(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint8) {
+ auto response = GetPropertyOnInterface(kTestInterface1, kUint8PropName);
+ dbus::MessageReader reader(response.get());
+ uint8_t value;
+ ASSERT_TRUE(reader.PopVariantOfByte(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt16) {
+ auto response = GetPropertyOnInterface(kTestInterface1, kInt16PropName);
+ dbus::MessageReader reader(response.get());
+ int16_t value;
+ ASSERT_TRUE(reader.PopVariantOfInt16(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint16) {
+ auto response = GetPropertyOnInterface(kTestInterface2, kUint16PropName);
+ dbus::MessageReader reader(response.get());
+ uint16_t value;
+ ASSERT_TRUE(reader.PopVariantOfUint16(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt32) {
+ auto response = GetPropertyOnInterface(kTestInterface2, kInt32PropName);
+ dbus::MessageReader reader(response.get());
+ int32_t value;
+ ASSERT_TRUE(reader.PopVariantOfInt32(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint32) {
+ auto response = GetPropertyOnInterface(kTestInterface3, kUint32PropName);
+ dbus::MessageReader reader(response.get());
+ uint32_t value;
+ ASSERT_TRUE(reader.PopVariantOfUint32(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt64) {
+ auto response = GetPropertyOnInterface(kTestInterface3, kInt64PropName);
+ dbus::MessageReader reader(response.get());
+ int64_t value;
+ ASSERT_TRUE(reader.PopVariantOfInt64(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint64) {
+ auto response = GetPropertyOnInterface(kTestInterface3, kUint64PropName);
+ dbus::MessageReader reader(response.get());
+ uint64_t value;
+ ASSERT_TRUE(reader.PopVariantOfUint64(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithDouble) {
+ auto response = GetPropertyOnInterface(kTestInterface3, kDoublePropName);
+ dbus::MessageReader reader(response.get());
+ double value;
+ ASSERT_TRUE(reader.PopVariantOfDouble(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithString) {
+ auto 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) {
+ auto 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) {
+ auto 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) {
+ auto 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) {
+ auto response = GetPropertyOnInterface(kTestInterface3, kPathListPropName);
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(nullptr);
+ const uint8_t* 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, SetInvalidInterface) {
+ auto response = SetPropertyOnInterface(
+ "BadInterfaceName", kStringPropName, brillo::Any(kTestString));
+ ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ ASSERT_EQ(DBUS_ERROR_UNKNOWN_INTERFACE, response->GetErrorName());
+}
+
+TEST_F(ExportedPropertySetTest, SetBadPropertyName) {
+ auto response = SetPropertyOnInterface(
+ kTestInterface3, "IAmNotAProperty", brillo::Any(kTestString));
+ ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ ASSERT_EQ(DBUS_ERROR_UNKNOWN_PROPERTY, response->GetErrorName());
+}
+
+TEST_F(ExportedPropertySetTest, SetFailsWithReadOnlyProperty) {
+ auto response = SetPropertyOnInterface(
+ kTestInterface3, kStringPropName, brillo::Any(kTestString));
+ ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ ASSERT_EQ(DBUS_ERROR_PROPERTY_READ_ONLY, response->GetErrorName());
+}
+
+TEST_F(ExportedPropertySetTest, SetFailsWithMismatchedValueType) {
+ p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite);
+ auto response = SetPropertyOnInterface(
+ kTestInterface3, kStringPropName, brillo::Any(true));
+ ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ ASSERT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName());
+}
+
+namespace {
+
+bool SetInvalidProperty(brillo::ErrorPtr* error, Unused) {
+ brillo::Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
+ DBUS_ERROR_INVALID_ARGS, "Invalid value");
+ return false;
+}
+
+} // namespace
+
+TEST_F(ExportedPropertySetTest, SetFailsWithValidator) {
+ PropertyValidatorObserver<std::string> property_validator;
+ p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite);
+ p_->string_prop_.SetValidator(
+ property_validator.validate_property_callback());
+
+ brillo::ErrorPtr error = brillo::Error::Create(
+ FROM_HERE, errors::dbus::kDomain, DBUS_ERROR_INVALID_ARGS, "");
+ EXPECT_CALL(property_validator, ValidateProperty(_, kTestString))
+ .WillOnce(Invoke(SetInvalidProperty));
+ auto response = SetPropertyOnInterface(
+ kTestInterface3, kStringPropName, brillo::Any(kTestString));
+ ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ ASSERT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName());
+}
+
+TEST_F(ExportedPropertySetTest, SetWorksWithValidator) {
+ PropertyValidatorObserver<std::string> property_validator;
+ p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite);
+ p_->string_prop_.SetValidator(
+ property_validator.validate_property_callback());
+
+ EXPECT_CALL(property_validator, ValidateProperty(_, kTestString))
+ .WillOnce(Return(true));
+ auto response = SetPropertyOnInterface(
+ kTestInterface3, kStringPropName, brillo::Any(kTestString));
+ ASSERT_NE(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ ASSERT_EQ(kTestString, p_->string_prop_.value());
+}
+
+TEST_F(ExportedPropertySetTest, SetWorksWithSameValue) {
+ PropertyValidatorObserver<std::string> property_validator;
+ p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite);
+ p_->string_prop_.SetValidator(
+ property_validator.validate_property_callback());
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1);
+ p_->string_prop_.SetValue(kTestString);
+
+ // No need to validate the value if it is the same as the current one.
+ EXPECT_CALL(property_validator, ValidateProperty(_, _)).Times(0);
+ auto response = SetPropertyOnInterface(
+ kTestInterface3, kStringPropName, brillo::Any(kTestString));
+ ASSERT_NE(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ ASSERT_EQ(kTestString, p_->string_prop_.value());
+}
+
+TEST_F(ExportedPropertySetTest, SetWorksWithoutValidator) {
+ p_->string_prop_.SetAccessMode(ExportedPropertyBase::Access::kReadWrite);
+ auto response = SetPropertyOnInterface(
+ kTestInterface3, kStringPropName, brillo::Any(kTestString));
+ ASSERT_NE(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
+ ASSERT_EQ(kTestString, p_->string_prop_.value());
+}
+
+namespace {
+
+void VerifySignal(dbus::Signal* signal) {
+ ASSERT_NE(signal, nullptr);
+ std::string interface_name;
+ std::string property_name;
+ uint8_t 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 brillo
diff --git a/libbrillo/brillo/dbus/mock_dbus_object.h b/libbrillo/brillo/dbus/mock_dbus_object.h
new file mode 100644
index 0000000..82e2fc7
--- /dev/null
+++ b/libbrillo/brillo/dbus/mock_dbus_object.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 LIBBRILLO_BRILLO_DBUS_MOCK_DBUS_OBJECT_H_
+#define LIBBRILLO_BRILLO_DBUS_MOCK_DBUS_OBJECT_H_
+
+#include <string>
+
+#include <brillo/dbus/async_event_sequencer.h>
+#include <brillo/dbus/dbus_object.h>
+#include <gmock/gmock.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+class MockDBusObject : public DBusObject {
+ public:
+ MockDBusObject(ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ const dbus::ObjectPath& object_path)
+ : DBusObject(object_manager, bus, object_path) {}
+ ~MockDBusObject() override = default;
+
+ MOCK_METHOD1(RegisterAsync,
+ void(const AsyncEventSequencer::CompletionAction&));
+}; // class MockDBusObject
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_MOCK_DBUS_OBJECT_H_
diff --git a/libbrillo/brillo/dbus/mock_exported_object_manager.h b/libbrillo/brillo/dbus/mock_exported_object_manager.h
new file mode 100644
index 0000000..d8abc0a
--- /dev/null
+++ b/libbrillo/brillo/dbus/mock_exported_object_manager.h
@@ -0,0 +1,42 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_DBUS_MOCK_EXPORTED_OBJECT_MANAGER_H_
+#define LIBBRILLO_BRILLO_DBUS_MOCK_EXPORTED_OBJECT_MANAGER_H_
+
+#include <string>
+
+#include <brillo/dbus/async_event_sequencer.h>
+#include <brillo/dbus/exported_object_manager.h>
+#include <dbus/object_path.h>
+#include <gmock/gmock.h>
+
+namespace brillo {
+
+namespace dbus_utils {
+
+class MockExportedObjectManager : public ExportedObjectManager {
+ public:
+ using CompletionAction =
+ brillo::dbus_utils::AsyncEventSequencer::CompletionAction;
+
+ using ExportedObjectManager::ExportedObjectManager;
+ ~MockExportedObjectManager() override = default;
+
+ MOCK_METHOD1(RegisterAsync,
+ void(const CompletionAction& completion_callback));
+ MOCK_METHOD3(ClaimInterface,
+ void(const dbus::ObjectPath& path,
+ const std::string& interface_name,
+ const ExportedPropertySet::PropertyWriter& writer));
+ MOCK_METHOD2(ReleaseInterface,
+ void(const dbus::ObjectPath& path,
+ const std::string& interface_name));
+};
+
+} // namespace dbus_utils
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_MOCK_EXPORTED_OBJECT_MANAGER_H_
diff --git a/libbrillo/brillo/dbus/test.proto b/libbrillo/brillo/dbus/test.proto
new file mode 100644
index 0000000..84607a3
--- /dev/null
+++ b/libbrillo/brillo/dbus/test.proto
@@ -0,0 +1,8 @@
+option optimize_for = LITE_RUNTIME;
+
+package dbus_utils_test;
+
+message TestMessage {
+ optional int32 foo = 1;
+ optional string bar = 2;
+}
diff --git a/libbrillo/brillo/dbus/utils.cc b/libbrillo/brillo/dbus/utils.cc
new file mode 100644
index 0000000..c9bb4db
--- /dev/null
+++ b/libbrillo/brillo/dbus/utils.cc
@@ -0,0 +1,93 @@
+// 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 <brillo/dbus/utils.h>
+
+#include <tuple>
+#include <vector>
+
+#include <base/bind.h>
+#include <brillo/errors/error_codes.h>
+#include <brillo/strings/string_utils.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+std::unique_ptr<dbus::Response> CreateDBusErrorResponse(
+ dbus::MethodCall* method_call,
+ const std::string& error_name,
+ const std::string& error_message) {
+ return dbus::ErrorResponse::FromMethodCall(method_call, error_name,
+ error_message);
+}
+
+std::unique_ptr<dbus::Response> GetDBusError(dbus::MethodCall* method_call,
+ const brillo::Error* error) {
+ CHECK(error) << "Error object must be specified";
+ std::string error_name = DBUS_ERROR_FAILED; // Default error code.
+ std::string error_message;
+
+ // Special case for "dbus" error domain.
+ // Pop the error code and message from the error chain and use them as the
+ // actual D-Bus error message.
+ if (error->GetDomain() == errors::dbus::kDomain) {
+ error_name = error->GetCode();
+ error_message = error->GetMessage();
+ error = error->GetInnerError();
+ }
+
+ // Append any inner errors to the error message.
+ while (error) {
+ // Format error string as "domain/code:message".
+ if (!error_message.empty())
+ error_message += ';';
+ error_message +=
+ error->GetDomain() + '/' + error->GetCode() + ':' + error->GetMessage();
+ error = error->GetInnerError();
+ }
+ return CreateDBusErrorResponse(method_call, error_name, error_message);
+}
+
+void AddDBusError(brillo::ErrorPtr* error,
+ const std::string& dbus_error_name,
+ const std::string& dbus_error_message) {
+ std::vector<std::string> parts = string_utils::Split(dbus_error_message, ";");
+ std::vector<std::tuple<std::string, std::string, std::string>> errors;
+ for (const std::string& part : parts) {
+ // Each part should be in format of "domain/code:message"
+ size_t slash_pos = part.find('/');
+ size_t colon_pos = part.find(':');
+ if (slash_pos != std::string::npos && colon_pos != std::string::npos &&
+ slash_pos < colon_pos) {
+ // If we have both '/' and ':' and in proper order, then we have a
+ // correctly encoded error object.
+ std::string domain = part.substr(0, slash_pos);
+ std::string code = part.substr(slash_pos + 1, colon_pos - slash_pos - 1);
+ std::string message = part.substr(colon_pos + 1);
+ errors.emplace_back(domain, code, message);
+ } else if (slash_pos == std::string::npos &&
+ colon_pos == std::string::npos && errors.empty()) {
+ // If we don't have both '/' and ':' and this is the first error object,
+ // then we had a D-Bus error at the top of the error chain.
+ errors.emplace_back(errors::dbus::kDomain, dbus_error_name, part);
+ } else {
+ // We have a malformed part. The whole D-Bus error was most likely
+ // not generated by GetDBusError(). To be safe, stop parsing it
+ // and return the error as received from D-Bus.
+ errors.clear(); // Remove any errors accumulated so far.
+ errors.emplace_back(
+ errors::dbus::kDomain, dbus_error_name, dbus_error_message);
+ break;
+ }
+ }
+
+ // Go backwards and add the parsed errors to the error chain.
+ for (auto it = errors.crbegin(); it != errors.crend(); ++it) {
+ Error::AddTo(
+ error, FROM_HERE, std::get<0>(*it), std::get<1>(*it), std::get<2>(*it));
+ }
+}
+
+} // namespace dbus_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/dbus/utils.h b/libbrillo/brillo/dbus/utils.h
new file mode 100644
index 0000000..a548756
--- /dev/null
+++ b/libbrillo/brillo/dbus/utils.h
@@ -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.
+
+#ifndef LIBBRILLO_BRILLO_DBUS_UTILS_H_
+#define LIBBRILLO_BRILLO_DBUS_UTILS_H_
+
+#include <memory>
+#include <string>
+
+#include <brillo/brillo_export.h>
+#include <brillo/errors/error.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.h>
+#include <dbus/scoped_dbus_error.h>
+
+namespace brillo {
+namespace dbus_utils {
+
+// A helper function to create a D-Bus error response object as unique_ptr<>.
+BRILLO_EXPORT std::unique_ptr<dbus::Response> CreateDBusErrorResponse(
+ dbus::MethodCall* method_call,
+ const std::string& error_name,
+ const std::string& error_message);
+
+// Create a D-Bus error response object from brillo::Error. If the last
+// error in the error chain belongs to "dbus" error domain, its error code
+// and message are directly translated to D-Bus error code and message.
+// Any inner errors are formatted as "domain/code:message" string and appended
+// to the D-Bus error message, delimited by semi-colons.
+BRILLO_EXPORT std::unique_ptr<dbus::Response> GetDBusError(
+ dbus::MethodCall* method_call,
+ const brillo::Error* error);
+
+// AddDBusError() is the opposite of GetDBusError(). It de-serializes the Error
+// object received over D-Bus.
+BRILLO_EXPORT void AddDBusError(brillo::ErrorPtr* error,
+ const std::string& dbus_error_name,
+ const std::string& dbus_error_message);
+
+} // namespace dbus_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_DBUS_UTILS_H_
diff --git a/libbrillo/brillo/errors/error.cc b/libbrillo/brillo/errors/error.cc
new file mode 100644
index 0000000..1236220
--- /dev/null
+++ b/libbrillo/brillo/errors/error.cc
@@ -0,0 +1,138 @@
+// 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 <brillo/errors/error.h>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+using brillo::Error;
+using brillo::ErrorPtr;
+
+namespace {
+inline void LogError(const tracked_objects::Location& location,
+ const std::string& domain,
+ const std::string& code,
+ const std::string& message) {
+ // Use logging::LogMessage() directly instead of LOG(ERROR) to substitute
+ // the current error location with the location passed in to the Error object.
+ // This way the log will contain the actual location of the error, and not
+ // as if it always comes from brillo/errors/error.cc(22).
+ logging::LogMessage(
+ location.file_name(), location.line_number(), logging::LOG_ERROR).stream()
+ << location.function_name() << "(...): "
+ << "Domain=" << domain << ", Code=" << code << ", Message=" << message;
+}
+} // anonymous namespace
+
+ErrorPtr Error::Create(const tracked_objects::Location& location,
+ const std::string& domain,
+ const std::string& code,
+ const std::string& message) {
+ return Create(location, domain, code, message, ErrorPtr());
+}
+
+ErrorPtr Error::Create(const tracked_objects::Location& location,
+ const std::string& domain,
+ const std::string& code,
+ const std::string& message,
+ ErrorPtr inner_error) {
+ LogError(location, domain, code, message);
+ return ErrorPtr(
+ new Error(location, domain, code, message, std::move(inner_error)));
+}
+
+void Error::AddTo(ErrorPtr* error,
+ const tracked_objects::Location& location,
+ const std::string& domain,
+ const std::string& code,
+ const std::string& message) {
+ if (error) {
+ *error = Create(location, domain, code, message, std::move(*error));
+ } else {
+ // Create already logs the error, but if |error| is nullptr,
+ // we still want to log the error...
+ LogError(location, domain, code, message);
+ }
+}
+
+void Error::AddToPrintf(ErrorPtr* error,
+ const tracked_objects::Location& location,
+ 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, location, domain, code, message);
+}
+
+ErrorPtr Error::Clone() const {
+ ErrorPtr inner_error = inner_error_ ? inner_error_->Clone() : nullptr;
+ return ErrorPtr(
+ new Error(location_, domain_, code_, message_, std::move(inner_error)));
+}
+
+bool Error::HasDomain(const std::string& domain) const {
+ return FindErrorOfDomain(this, domain) != nullptr;
+}
+
+bool Error::HasError(const std::string& domain, const std::string& code) const {
+ return FindError(this, domain, code) != nullptr;
+}
+
+const Error* Error::GetFirstError() const {
+ const Error* err = this;
+ while (err->GetInnerError())
+ err = err->GetInnerError();
+ return err;
+}
+
+Error::Error(const tracked_objects::Location& location,
+ const std::string& domain,
+ const std::string& code,
+ const std::string& message,
+ ErrorPtr inner_error)
+ : Error{tracked_objects::LocationSnapshot{location},
+ domain,
+ code,
+ message,
+ std::move(inner_error)} {
+}
+
+Error::Error(const tracked_objects::LocationSnapshot& location,
+ const std::string& domain,
+ const std::string& code,
+ const std::string& message,
+ ErrorPtr inner_error)
+ : domain_(domain),
+ code_(code),
+ message_(message),
+ location_(location),
+ inner_error_(std::move(inner_error)) {
+}
+
+const Error* Error::FindErrorOfDomain(const Error* error_chain_start,
+ const std::string& domain) {
+ while (error_chain_start) {
+ if (error_chain_start->GetDomain() == domain)
+ break;
+ error_chain_start = error_chain_start->GetInnerError();
+ }
+ return error_chain_start;
+}
+
+const Error* Error::FindError(const Error* error_chain_start,
+ const std::string& domain,
+ const std::string& code) {
+ while (error_chain_start) {
+ if (error_chain_start->GetDomain() == domain &&
+ error_chain_start->GetCode() == code)
+ break;
+ error_chain_start = error_chain_start->GetInnerError();
+ }
+ return error_chain_start;
+}
diff --git a/libbrillo/brillo/errors/error.h b/libbrillo/brillo/errors/error.h
new file mode 100644
index 0000000..4cf60f5
--- /dev/null
+++ b/libbrillo/brillo/errors/error.h
@@ -0,0 +1,129 @@
+// 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 LIBBRILLO_BRILLO_ERRORS_ERROR_H_
+#define LIBBRILLO_BRILLO_ERRORS_ERROR_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <base/tracked_objects.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+class Error; // Forward declaration.
+
+using ErrorPtr = std::unique_ptr<Error>;
+
+class BRILLO_EXPORT Error {
+ public:
+ virtual ~Error() = default;
+
+ // Creates an instance of Error class.
+ static ErrorPtr Create(const tracked_objects::Location& location,
+ const std::string& domain,
+ const std::string& code,
+ const std::string& message);
+ static ErrorPtr Create(const tracked_objects::Location& location,
+ 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 tracked_objects::Location& location,
+ 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 tracked_objects::Location& location,
+ const std::string& domain,
+ const std::string& code,
+ const char* format,
+ ...) PRINTF_FORMAT(5, 6);
+
+ // Clones error with all inner errors.
+ ErrorPtr Clone() const;
+
+ // 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_; }
+
+ // Returns the location of the error in the source code.
+ const tracked_objects::LocationSnapshot& GetLocation() const {
+ return location_;
+ }
+
+ // Checks if this or any of the inner errors in the chain has the specified
+ // error domain.
+ bool HasDomain(const std::string& domain) const;
+
+ // Checks if this or any of the inner errors 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;
+
+ // Finds an error object of particular domain in the error chain stating at
+ // |error_chain_start|. Returns the a pointer to the first matching error
+ // object found.
+ // Returns nullptr if no match is found.
+ // This method is safe to call on a nullptr |error_chain_start| in which case
+ // the result will also be nullptr.
+ static const Error* FindErrorOfDomain(const Error* error_chain_start,
+ const std::string& domain);
+ // Finds an error of particular domain with the given code in the error chain
+ // stating at |error_chain_start|. Returns the pointer to the first matching
+ // error object.
+ // Returns nullptr if no match is found or if |error_chain_start| is nullptr.
+ static const Error* FindError(const Error* error_chain_start,
+ const std::string& domain,
+ const std::string& code);
+
+ protected:
+ // Constructor is protected since this object is supposed to be
+ // created via the Create factory methods.
+ Error(const tracked_objects::Location& location,
+ const std::string& domain,
+ const std::string& code,
+ const std::string& message,
+ ErrorPtr inner_error);
+
+ Error(const tracked_objects::LocationSnapshot& location,
+ 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_;
+ // Error origin in the source code.
+ tracked_objects::LocationSnapshot location_;
+ // Pointer to inner error, if any. This forms a chain of errors.
+ ErrorPtr inner_error_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Error);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_ERRORS_ERROR_H_
diff --git a/libbrillo/brillo/errors/error_codes.cc b/libbrillo/brillo/errors/error_codes.cc
new file mode 100644
index 0000000..4e48b4e
--- /dev/null
+++ b/libbrillo/brillo/errors/error_codes.cc
@@ -0,0 +1,225 @@
+// 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 <brillo/errors/error_codes.h>
+
+#include <base/posix/safe_strerror.h>
+
+namespace brillo {
+namespace errors {
+
+namespace dbus {
+const char kDomain[] = "dbus";
+} // namespace dbus
+
+namespace json {
+const char kDomain[] = "json_parser";
+const char kParseError[] = "json_parse_error";
+const char kObjectExpected[] = "json_object_expected";
+} // namespace json
+
+namespace http {
+const char kDomain[] = "http";
+} // namespace http
+
+namespace system {
+const char kDomain[] = "system";
+
+namespace {
+const struct ErrorMapEntry {
+ const char* error_code;
+ int errnum;
+} error_map[] = {
+#define ERROR_ENTRY(err) { #err, err }
+ ERROR_ENTRY(EPERM), // Operation not permitted
+ ERROR_ENTRY(ENOENT), // No such file or directory
+ ERROR_ENTRY(ESRCH), // No such process
+ ERROR_ENTRY(EINTR), // Interrupted system call
+ ERROR_ENTRY(EIO), // I/O error
+ ERROR_ENTRY(ENXIO), // No such device or address
+ ERROR_ENTRY(E2BIG), // Argument list too long
+ ERROR_ENTRY(ENOEXEC), // Exec format error
+ ERROR_ENTRY(EBADF), // Bad file number
+ ERROR_ENTRY(ECHILD), // No child processes
+ ERROR_ENTRY(EAGAIN), // Try again
+ ERROR_ENTRY(ENOMEM), // Out of memory
+ ERROR_ENTRY(EACCES), // Permission denied
+ ERROR_ENTRY(EFAULT), // Bad address
+ ERROR_ENTRY(ENOTBLK), // Block device required
+ ERROR_ENTRY(EBUSY), // Device or resource busy
+ ERROR_ENTRY(EEXIST), // File exists
+ ERROR_ENTRY(EXDEV), // Cross-device link
+ ERROR_ENTRY(ENODEV), // No such device
+ ERROR_ENTRY(ENOTDIR), // Not a directory
+ ERROR_ENTRY(EISDIR), // Is a directory
+ ERROR_ENTRY(EINVAL), // Invalid argument
+ ERROR_ENTRY(ENFILE), // File table overflow
+ ERROR_ENTRY(EMFILE), // Too many open files
+ ERROR_ENTRY(ENOTTY), // Not a typewriter
+ ERROR_ENTRY(ETXTBSY), // Text file busy
+ ERROR_ENTRY(EFBIG), // File too large
+ ERROR_ENTRY(ENOSPC), // No space left on device
+ ERROR_ENTRY(ESPIPE), // Illegal seek
+ ERROR_ENTRY(EROFS), // Read-only file system
+ ERROR_ENTRY(EMLINK), // Too many links
+ ERROR_ENTRY(EPIPE), // Broken pipe
+ ERROR_ENTRY(EDOM), // Math argument out of domain of func
+ ERROR_ENTRY(ERANGE), // Math result not representable
+ ERROR_ENTRY(EDEADLK), // Resource deadlock would occur
+ ERROR_ENTRY(ENAMETOOLONG), // File name too long
+ ERROR_ENTRY(ENOLCK), // No record locks available
+ ERROR_ENTRY(ENOSYS), // Function not implemented
+ ERROR_ENTRY(ENOTEMPTY), // Directory not empty
+ ERROR_ENTRY(ELOOP), // Too many symbolic links encountered
+ ERROR_ENTRY(ENOMSG), // No message of desired type
+ ERROR_ENTRY(EIDRM), // Identifier removed
+#ifdef __linux__
+ ERROR_ENTRY(ECHRNG), // Channel number out of range
+ ERROR_ENTRY(EL2NSYNC), // Level 2 not synchronized
+ ERROR_ENTRY(EL3HLT), // Level 3 halted
+ ERROR_ENTRY(EL3RST), // Level 3 reset
+ ERROR_ENTRY(ELNRNG), // Link number out of range
+ ERROR_ENTRY(EUNATCH), // Protocol driver not attached
+ ERROR_ENTRY(ENOCSI), // No CSI structure available
+ ERROR_ENTRY(EL2HLT), // Level 2 halted
+ ERROR_ENTRY(EBADE), // Invalid exchange
+ ERROR_ENTRY(EBADR), // Invalid request descriptor
+ ERROR_ENTRY(EXFULL), // Exchange full
+ ERROR_ENTRY(ENOANO), // No anode
+ ERROR_ENTRY(EBADRQC), // Invalid request code
+ ERROR_ENTRY(EBADSLT), // Invalid slot
+ ERROR_ENTRY(EBFONT), // Bad font file format
+#endif // __linux__
+ ERROR_ENTRY(ENOSTR), // Device not a stream
+ ERROR_ENTRY(ENODATA), // No data available
+ ERROR_ENTRY(ETIME), // Timer expired
+ ERROR_ENTRY(ENOSR), // Out of streams resources
+#ifdef __linux__
+ ERROR_ENTRY(ENONET), // Machine is not on the network
+ ERROR_ENTRY(ENOPKG), // Package not installed
+#endif // __linux__
+ ERROR_ENTRY(EREMOTE), // Object is remote
+ ERROR_ENTRY(ENOLINK), // Link has been severed
+#ifdef __linux__
+ ERROR_ENTRY(EADV), // Advertise error
+ ERROR_ENTRY(ESRMNT), // Srmount error
+ ERROR_ENTRY(ECOMM), // Communication error on send
+#endif // __linux__
+ ERROR_ENTRY(EPROTO), // Protocol error
+ ERROR_ENTRY(EMULTIHOP), // Multihop attempted
+#ifdef __linux__
+ ERROR_ENTRY(EDOTDOT), // RFS specific error
+#endif // __linux__
+ ERROR_ENTRY(EBADMSG), // Not a data message
+ ERROR_ENTRY(EOVERFLOW), // Value too large for defined data type
+#ifdef __linux__
+ ERROR_ENTRY(ENOTUNIQ), // Name not unique on network
+ ERROR_ENTRY(EBADFD), // File descriptor in bad state
+ ERROR_ENTRY(EREMCHG), // Remote address changed
+ ERROR_ENTRY(ELIBACC), // Can not access a needed shared library
+ ERROR_ENTRY(ELIBBAD), // Accessing a corrupted shared library
+ ERROR_ENTRY(ELIBSCN), // .lib section in a.out corrupted
+ ERROR_ENTRY(ELIBMAX), // Attempting to link in too many shared libs.
+ ERROR_ENTRY(ELIBEXEC), // Cannot exec a shared library directly
+#endif // __linux__
+ ERROR_ENTRY(EILSEQ), // Illegal byte sequence
+#ifdef __linux__
+ ERROR_ENTRY(ERESTART), // Interrupted system call should be restarted
+ ERROR_ENTRY(ESTRPIPE), // Streams pipe error
+#endif // __linux__
+ ERROR_ENTRY(EUSERS), // Too many users
+ ERROR_ENTRY(ENOTSOCK), // Socket operation on non-socket
+ ERROR_ENTRY(EDESTADDRREQ), // Destination address required
+ ERROR_ENTRY(EMSGSIZE), // Message too long
+ ERROR_ENTRY(EPROTOTYPE), // Protocol wrong type for socket
+ ERROR_ENTRY(ENOPROTOOPT), // Protocol not available
+ ERROR_ENTRY(EPROTONOSUPPORT), // Protocol not supported
+ ERROR_ENTRY(ESOCKTNOSUPPORT), // Socket type not supported
+ ERROR_ENTRY(EOPNOTSUPP), // Operation not supported o/transport endpoint
+ ERROR_ENTRY(EPFNOSUPPORT), // Protocol family not supported
+ ERROR_ENTRY(EAFNOSUPPORT), // Address family not supported by protocol
+ ERROR_ENTRY(EADDRINUSE), // Address already in use
+ ERROR_ENTRY(EADDRNOTAVAIL), // Cannot assign requested address
+ ERROR_ENTRY(ENETDOWN), // Network is down
+ ERROR_ENTRY(ENETUNREACH), // Network is unreachable
+ ERROR_ENTRY(ENETRESET), // Network dropped connection because of reset
+ ERROR_ENTRY(ECONNABORTED), // Software caused connection abort
+ ERROR_ENTRY(ECONNRESET), // Connection reset by peer
+ ERROR_ENTRY(ENOBUFS), // No buffer space available
+ ERROR_ENTRY(EISCONN), // Transport endpoint is already connected
+ ERROR_ENTRY(ENOTCONN), // Transport endpoint is not connected
+ ERROR_ENTRY(ESHUTDOWN), // Cannot send after transp. endpoint shutdown
+ ERROR_ENTRY(ETOOMANYREFS), // Too many references: cannot splice
+ ERROR_ENTRY(ETIMEDOUT), // Connection timed out
+ ERROR_ENTRY(ECONNREFUSED), // Connection refused
+ ERROR_ENTRY(EHOSTDOWN), // Host is down
+ ERROR_ENTRY(EHOSTUNREACH), // No route to host
+ ERROR_ENTRY(EALREADY), // Operation already in progress
+ ERROR_ENTRY(EINPROGRESS), // Operation now in progress
+ ERROR_ENTRY(ESTALE), // Stale file handle
+#ifdef __linux__
+ ERROR_ENTRY(EUCLEAN), // Structure needs cleaning
+ ERROR_ENTRY(ENOTNAM), // Not a XENIX named type file
+ ERROR_ENTRY(ENAVAIL), // No XENIX semaphores available
+ ERROR_ENTRY(EISNAM), // Is a named type file
+ ERROR_ENTRY(EREMOTEIO), // Remote I/O error
+#endif // __linux__
+ ERROR_ENTRY(EDQUOT), // Quota exceeded
+#ifdef __linux__
+ ERROR_ENTRY(ENOMEDIUM), // No medium found
+ ERROR_ENTRY(EMEDIUMTYPE), // Wrong medium type
+#endif // __linux__
+ ERROR_ENTRY(ECANCELED), // Operation Canceled
+#ifdef __linux__
+ ERROR_ENTRY(ENOKEY), // Required key not available
+ ERROR_ENTRY(EKEYEXPIRED), // Key has expired
+ ERROR_ENTRY(EKEYREVOKED), // Key has been revoked
+ ERROR_ENTRY(EKEYREJECTED), // Key was rejected by service
+#endif // __linux__
+ ERROR_ENTRY(EOWNERDEAD), // Owner died
+ ERROR_ENTRY(ENOTRECOVERABLE), // State not recoverable
+#ifdef __linux__
+ ERROR_ENTRY(ERFKILL), // Operation not possible due to RF-kill
+ ERROR_ENTRY(EHWPOISON), // Memory page has hardware error
+#endif // __linux__
+#undef ERROR_ENTRY
+ // This list comes from <errno.h> system header. The elements are ordered
+ // by increasing errnum values which is the same order used in the header
+ // file. So, when new error codes are added to glibc, it should be relatively
+ // easy to identify them and add them to this list.
+};
+
+// Gets the error code string from system error code. If unknown system error
+// number is provided, returns an empty string.
+std::string ErrorCodeFromSystemError(int errnum) {
+ std::string error_code;
+ for (const ErrorMapEntry& entry : error_map) {
+ if (entry.errnum == errnum) {
+ error_code = entry.error_code;
+ break;
+ }
+ }
+ return error_code;
+}
+
+} // anonymous namespace
+
+void AddSystemError(ErrorPtr* error,
+ const tracked_objects::Location& location,
+ int errnum) {
+ std::string message = base::safe_strerror(errnum);
+ std::string code = ErrorCodeFromSystemError(errnum);
+ if (message.empty())
+ message = "Unknown error " + std::to_string(errnum);
+
+ if (code.empty())
+ code = "error_" + std::to_string(errnum);
+
+ Error::AddTo(error, location, kDomain, code, message);
+}
+
+} // namespace system
+
+} // namespace errors
+} // namespace brillo
diff --git a/libbrillo/brillo/errors/error_codes.h b/libbrillo/brillo/errors/error_codes.h
new file mode 100644
index 0000000..a9964f6
--- /dev/null
+++ b/libbrillo/brillo/errors/error_codes.h
@@ -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.
+
+#ifndef LIBBRILLO_BRILLO_ERRORS_ERROR_CODES_H_
+#define LIBBRILLO_BRILLO_ERRORS_ERROR_CODES_H_
+
+#include <string>
+
+#include <brillo/brillo_export.h>
+#include <brillo/errors/error.h>
+
+namespace brillo {
+namespace errors {
+
+namespace dbus {
+BRILLO_EXPORT extern const char kDomain[];
+} // namespace dbus
+
+namespace json {
+BRILLO_EXPORT extern const char kDomain[];
+BRILLO_EXPORT extern const char kParseError[];
+BRILLO_EXPORT extern const char kObjectExpected[];
+} // namespace json
+
+namespace http {
+BRILLO_EXPORT extern const char kDomain[];
+} // namespace http
+
+namespace system {
+BRILLO_EXPORT extern const char kDomain[];
+
+// Adds an Error object to the error chain identified by |error|, using
+// the system error code (see "errno").
+BRILLO_EXPORT void AddSystemError(ErrorPtr* error,
+ const tracked_objects::Location& location,
+ int errnum);
+} // namespace system
+
+} // namespace errors
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_ERRORS_ERROR_CODES_H_
diff --git a/libbrillo/brillo/errors/error_codes_unittest.cc b/libbrillo/brillo/errors/error_codes_unittest.cc
new file mode 100644
index 0000000..2baa28f
--- /dev/null
+++ b/libbrillo/brillo/errors/error_codes_unittest.cc
@@ -0,0 +1,33 @@
+// 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 <brillo/errors/error_codes.h>
+
+#include <gtest/gtest.h>
+
+using brillo::errors::system::AddSystemError;
+
+TEST(SystemErrorCodes, AddTo) {
+ brillo::ErrorPtr error;
+
+ AddSystemError(&error, FROM_HERE, ENOENT);
+ EXPECT_EQ(brillo::errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("ENOENT", error->GetCode());
+ EXPECT_EQ("No such file or directory", error->GetMessage());
+ error.reset();
+
+ AddSystemError(&error, FROM_HERE, EPROTO);
+ EXPECT_EQ(brillo::errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("EPROTO", error->GetCode());
+ EXPECT_EQ("Protocol error", error->GetMessage());
+ error.reset();
+}
+
+TEST(SystemErrorCodes, AddTo_UnknownError) {
+ brillo::ErrorPtr error;
+ AddSystemError(&error, FROM_HERE, 10000);
+ EXPECT_EQ(brillo::errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("error_10000", error->GetCode());
+ EXPECT_EQ("Unknown error 10000", error->GetMessage());
+}
diff --git a/libbrillo/brillo/errors/error_unittest.cc b/libbrillo/brillo/errors/error_unittest.cc
new file mode 100644
index 0000000..517dab5
--- /dev/null
+++ b/libbrillo/brillo/errors/error_unittest.cc
@@ -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.
+
+#include <brillo/errors/error.h>
+
+#include <gtest/gtest.h>
+
+using brillo::Error;
+
+namespace {
+
+brillo::ErrorPtr GenerateNetworkError() {
+ tracked_objects::Location loc("GenerateNetworkError",
+ "error_unittest.cc",
+ 15,
+ ::tracked_objects::GetProgramCounter());
+ return Error::Create(loc, "network", "not_found", "Resource not found");
+}
+
+brillo::ErrorPtr GenerateHttpError() {
+ brillo::ErrorPtr inner = GenerateNetworkError();
+ return Error::Create(FROM_HERE, "HTTP", "404", "Not found", std::move(inner));
+}
+
+} // namespace
+
+TEST(Error, Single) {
+ brillo::ErrorPtr err = GenerateNetworkError();
+ EXPECT_EQ("network", err->GetDomain());
+ EXPECT_EQ("not_found", err->GetCode());
+ EXPECT_EQ("Resource not found", err->GetMessage());
+ EXPECT_EQ("GenerateNetworkError", err->GetLocation().function_name);
+ EXPECT_EQ("error_unittest.cc", err->GetLocation().file_name);
+ EXPECT_EQ(15, err->GetLocation().line_number);
+ 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) {
+ brillo::ErrorPtr 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"));
+}
+
+TEST(Error, Clone) {
+ brillo::ErrorPtr err = GenerateHttpError();
+ brillo::ErrorPtr clone = err->Clone();
+ const brillo::Error* error1 = err.get();
+ const brillo::Error* error2 = clone.get();
+ while (error1 && error2) {
+ EXPECT_NE(error1, error2);
+ EXPECT_EQ(error1->GetDomain(), error2->GetDomain());
+ EXPECT_EQ(error1->GetCode(), error2->GetCode());
+ EXPECT_EQ(error1->GetMessage(), error2->GetMessage());
+ EXPECT_EQ(error1->GetLocation().function_name,
+ error2->GetLocation().function_name);
+ EXPECT_EQ(error1->GetLocation().file_name, error2->GetLocation().file_name);
+ EXPECT_EQ(error1->GetLocation().line_number,
+ error2->GetLocation().line_number);
+ error1 = error1->GetInnerError();
+ error2 = error2->GetInnerError();
+ }
+ EXPECT_EQ(error1, error2);
+}
diff --git a/libbrillo/brillo/file_utils.cc b/libbrillo/brillo/file_utils.cc
new file mode 100644
index 0000000..24ed347
--- /dev/null
+++ b/libbrillo/brillo/file_utils.cc
@@ -0,0 +1,165 @@
+// 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 "brillo/file_utils.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_file.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+
+namespace brillo {
+
+namespace {
+
+enum {
+ kPermissions600 = S_IRUSR | S_IWUSR,
+ kPermissions777 = S_IRWXU | S_IRWXG | S_IRWXO
+};
+
+// Verify that base file permission enums are compatible with S_Ixxx. If these
+// asserts ever fail, we'll need to ensure that users of these functions switch
+// away from using base permission enums and add a note to the function comments
+// indicating that base enums can not be used.
+static_assert(base::FILE_PERMISSION_READ_BY_USER == S_IRUSR,
+ "base file permissions don't match unistd.h permissions");
+static_assert(base::FILE_PERMISSION_WRITE_BY_USER == S_IWUSR,
+ "base file permissions don't match unistd.h permissions");
+static_assert(base::FILE_PERMISSION_EXECUTE_BY_USER == S_IXUSR,
+ "base file permissions don't match unistd.h permissions");
+static_assert(base::FILE_PERMISSION_READ_BY_GROUP == S_IRGRP,
+ "base file permissions don't match unistd.h permissions");
+static_assert(base::FILE_PERMISSION_WRITE_BY_GROUP == S_IWGRP,
+ "base file permissions don't match unistd.h permissions");
+static_assert(base::FILE_PERMISSION_EXECUTE_BY_GROUP == S_IXGRP,
+ "base file permissions don't match unistd.h permissions");
+static_assert(base::FILE_PERMISSION_READ_BY_OTHERS == S_IROTH,
+ "base file permissions don't match unistd.h permissions");
+static_assert(base::FILE_PERMISSION_WRITE_BY_OTHERS == S_IWOTH,
+ "base file permissions don't match unistd.h permissions");
+static_assert(base::FILE_PERMISSION_EXECUTE_BY_OTHERS == S_IXOTH,
+ "base file permissions don't match unistd.h permissions");
+
+enum RegularFileOrDeleteResult {
+ kFailure = 0, // Failed to delete whatever was at the path.
+ kRegularFile = 1, // Regular file existed and was unchanged.
+ kEmpty = 2 // Anything that was at the path has been deleted.
+};
+
+// Checks if a regular file owned by |uid| and |gid| exists at |path|, otherwise
+// deletes anything that might be at |path|. Returns a RegularFileOrDeleteResult
+// enum indicating what is at |path| after the function finishes.
+RegularFileOrDeleteResult RegularFileOrDelete(const base::FilePath& path,
+ uid_t uid,
+ gid_t gid) {
+ // Check for symlinks by setting O_NOFOLLOW and checking for ELOOP. This lets
+ // us use the safer fstat() instead of having to use lstat().
+ base::ScopedFD scoped_fd(HANDLE_EINTR(openat(
+ AT_FDCWD, path.value().c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)));
+ bool path_not_empty = (errno == ELOOP || scoped_fd != -1);
+
+ // If there is a file/directory at |path|, see if it matches our criteria.
+ if (scoped_fd != -1) {
+ struct stat file_stat;
+ if (fstat(scoped_fd.get(), &file_stat) != -1 &&
+ S_ISREG(file_stat.st_mode) && file_stat.st_uid == uid &&
+ file_stat.st_gid == gid) {
+ return kRegularFile;
+ }
+ }
+
+ // If we get here and anything was at |path|, try to delete it so we can put
+ // our file there.
+ if (path_not_empty) {
+ if (!base::DeleteFile(path, true)) {
+ PLOG(WARNING) << "Failed to delete entity at \"" << path.value() << '"';
+ return kFailure;
+ }
+ }
+
+ return kEmpty;
+}
+
+// Handles common touch functionality but also provides an optional |fd_out|
+// so that any further modifications to the file (e.g. permissions) can safely
+// use the fd rather than the path. |fd_out| will only be set if a new file
+// is created, otherwise it will be unchanged.
+// If |fd_out| is null, this function will close the file, otherwise it's
+// expected that |fd_out| will close the file when it goes out of scope.
+bool TouchFileInternal(const base::FilePath& path,
+ uid_t uid,
+ gid_t gid,
+ base::ScopedFD* fd_out) {
+ RegularFileOrDeleteResult result = RegularFileOrDelete(path, uid, gid);
+ switch (result) {
+ case kFailure:
+ return false;
+ case kRegularFile:
+ return true;
+ case kEmpty:
+ break;
+ }
+
+ // base::CreateDirectory() returns true if the directory already existed.
+ if (!base::CreateDirectory(path.DirName())) {
+ PLOG(WARNING) << "Failed to create directory for \"" << path.value() << '"';
+ return false;
+ }
+
+ // Create the file as owner-only initially.
+ base::ScopedFD scoped_fd(
+ HANDLE_EINTR(openat(AT_FDCWD,
+ path.value().c_str(),
+ O_RDONLY | O_NOFOLLOW | O_CREAT | O_EXCL | O_CLOEXEC,
+ kPermissions600)));
+ if (scoped_fd == -1) {
+ PLOG(WARNING) << "Failed to create file \"" << path.value() << '"';
+ return false;
+ }
+
+ if (fd_out) {
+ fd_out->swap(scoped_fd);
+ }
+ return true;
+}
+
+} // namespace
+
+bool TouchFile(const base::FilePath& path,
+ int new_file_permissions,
+ uid_t uid,
+ gid_t gid) {
+ // Make sure |permissions| doesn't have any out-of-range bits.
+ if (new_file_permissions & ~kPermissions777) {
+ LOG(WARNING) << "Illegal permissions: " << new_file_permissions;
+ return false;
+ }
+
+ base::ScopedFD scoped_fd;
+ if (!TouchFileInternal(path, uid, gid, &scoped_fd)) {
+ return false;
+ }
+
+ // scoped_fd is valid only if a new file was created.
+ if (scoped_fd != -1 &&
+ HANDLE_EINTR(fchmod(scoped_fd.get(), new_file_permissions)) == -1) {
+ PLOG(WARNING) << "Failed to set permissions for \"" << path.value() << '"';
+ base::DeleteFile(path, false);
+ return false;
+ }
+
+ return true;
+}
+
+bool TouchFile(const base::FilePath& path) {
+ // Use TouchFile() instead of TouchFileInternal() to explicitly set
+ // permissions to 600 in case umask is set strangely.
+ return TouchFile(path, kPermissions600, geteuid(), getegid());
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/file_utils.h b/libbrillo/brillo/file_utils.h
new file mode 100644
index 0000000..60e0cdc
--- /dev/null
+++ b/libbrillo/brillo/file_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 LIBBRILLO_BRILLO_FILE_UTILS_H_
+#define LIBBRILLO_BRILLO_FILE_UTILS_H_
+
+#include <sys/types.h>
+
+#include <base/files/file_path.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+// Ensures a regular file owned by user |uid| and group |gid| exists at |path|.
+// Any other entity at |path| will be deleted and replaced with an empty
+// regular file. If a new file is needed, any missing parent directories will
+// be created, and the file will be assigned |new_file_permissions|.
+// Should be safe to use in all directories, including tmpdirs with the sticky
+// bit set.
+// Returns true if the file existed or was able to be created.
+BRILLO_EXPORT bool TouchFile(const base::FilePath& path,
+ int new_file_permissions,
+ uid_t uid,
+ gid_t gid);
+
+// Convenience version of TouchFile() defaulting to 600 permissions and the
+// current euid/egid.
+// Should be safe to use in all directories, including tmpdirs with the sticky
+// bit set.
+BRILLO_EXPORT bool TouchFile(const base::FilePath& path);
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_FILE_UTILS_H_
diff --git a/libbrillo/brillo/file_utils_unittest.cc b/libbrillo/brillo/file_utils_unittest.cc
new file mode 100644
index 0000000..f8898a0
--- /dev/null
+++ b/libbrillo/brillo/file_utils_unittest.cc
@@ -0,0 +1,135 @@
+// 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 "brillo/file_utils.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+class FileUtilsTest : public testing::Test {
+ public:
+ FileUtilsTest() {
+ CHECK(temp_dir_.CreateUniqueTempDir());
+ file_path_ = temp_dir_.path().Append("test.temp");
+ }
+
+ protected:
+ base::FilePath file_path_;
+ base::ScopedTempDir temp_dir_;
+
+ // Writes |contents| to |file_path_|. Pulled into a separate function just
+ // to improve readability of tests.
+ void WriteFile(const std::string& contents) {
+ EXPECT_EQ(contents.length(),
+ base::WriteFile(file_path_, contents.c_str(), contents.length()));
+ }
+
+ // Verifies that the file at |file_path_| exists and contains |contents|.
+ void ExpectFileContains(const std::string& contents) {
+ EXPECT_TRUE(base::PathExists(file_path_));
+ std::string new_contents;
+ EXPECT_TRUE(base::ReadFileToString(file_path_, &new_contents));
+ EXPECT_EQ(contents, new_contents);
+ }
+
+ // Verifies that the file at |file_path_| has |permissions|.
+ void ExpectFilePermissions(int permissions) {
+ int actual_permissions;
+ EXPECT_TRUE(base::GetPosixFilePermissions(file_path_, &actual_permissions));
+ EXPECT_EQ(permissions, actual_permissions);
+ }
+};
+
+namespace {
+
+enum {
+ kPermissions600 =
+ base::FILE_PERMISSION_READ_BY_USER | base::FILE_PERMISSION_WRITE_BY_USER,
+ kPermissions700 = base::FILE_PERMISSION_USER_MASK,
+ kPermissions777 = base::FILE_PERMISSION_MASK
+};
+
+} // namespace
+
+TEST_F(FileUtilsTest, TouchFileCreate) {
+ EXPECT_TRUE(TouchFile(file_path_));
+ ExpectFileContains("");
+ ExpectFilePermissions(kPermissions600);
+}
+
+TEST_F(FileUtilsTest, TouchFileCreateThroughUmask) {
+ mode_t old_umask = umask(kPermissions777);
+ EXPECT_TRUE(TouchFile(file_path_));
+ umask(old_umask);
+ ExpectFileContains("");
+ ExpectFilePermissions(kPermissions600);
+}
+
+TEST_F(FileUtilsTest, TouchFileCreateDirectoryStructure) {
+ file_path_ = temp_dir_.path().Append("foo/bar/baz/test.temp");
+ EXPECT_TRUE(TouchFile(file_path_));
+ ExpectFileContains("");
+}
+
+TEST_F(FileUtilsTest, TouchFileExisting) {
+ WriteFile("abcd");
+ EXPECT_TRUE(TouchFile(file_path_));
+ ExpectFileContains("abcd");
+}
+
+TEST_F(FileUtilsTest, TouchFileReplaceDirectory) {
+ EXPECT_TRUE(base::CreateDirectory(file_path_));
+ EXPECT_TRUE(TouchFile(file_path_));
+ EXPECT_FALSE(base::DirectoryExists(file_path_));
+ ExpectFileContains("");
+}
+
+TEST_F(FileUtilsTest, TouchFileReplaceSymlink) {
+ base::FilePath symlink_target = temp_dir_.path().Append("target.temp");
+ EXPECT_TRUE(base::CreateSymbolicLink(symlink_target, file_path_));
+ EXPECT_TRUE(TouchFile(file_path_));
+ EXPECT_FALSE(base::IsLink(file_path_));
+ ExpectFileContains("");
+}
+
+TEST_F(FileUtilsTest, TouchFileReplaceOtherUser) {
+ WriteFile("abcd");
+ EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid() + 1, getegid()));
+ ExpectFileContains("");
+}
+
+TEST_F(FileUtilsTest, TouchFileReplaceOtherGroup) {
+ WriteFile("abcd");
+ EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid() + 1));
+ ExpectFileContains("");
+}
+
+TEST_F(FileUtilsTest, TouchFileCreateWithAllPermissions) {
+ EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid()));
+ ExpectFileContains("");
+ ExpectFilePermissions(kPermissions777);
+}
+
+TEST_F(FileUtilsTest, TouchFileCreateWithOwnerPermissions) {
+ EXPECT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid()));
+ ExpectFileContains("");
+ ExpectFilePermissions(kPermissions700);
+}
+
+TEST_F(FileUtilsTest, TouchFileExistingPermissionsUnchanged) {
+ EXPECT_TRUE(TouchFile(file_path_, kPermissions777, geteuid(), getegid()));
+ EXPECT_TRUE(TouchFile(file_path_, kPermissions700, geteuid(), getegid()));
+ ExpectFileContains("");
+ ExpectFilePermissions(kPermissions777);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/flag_helper.cc b/libbrillo/brillo/flag_helper.cc
new file mode 100644
index 0000000..bb51818
--- /dev/null
+++ b/libbrillo/brillo/flag_helper.cc
@@ -0,0 +1,267 @@
+// 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 "brillo/flag_helper.h"
+
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <sysexits.h>
+
+#include <base/base_switches.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_number_conversions.h>
+
+namespace brillo {
+
+Flag::Flag(const char* name,
+ const char* default_value,
+ const char* help,
+ bool visible)
+ : name_(name),
+ default_value_(default_value),
+ help_(help),
+ visible_(visible) {
+}
+
+class HelpFlag : public brillo::Flag {
+ public:
+ HelpFlag() : Flag("help", "false", "Show this help message", true) {}
+
+ bool SetValue(const std::string& /* value */) override { return true; };
+ const char* GetType() const override { return "bool"; }
+};
+
+BoolFlag::BoolFlag(const char* name,
+ bool* value,
+ bool* no_value,
+ const char* default_value,
+ const char* help,
+ bool visible)
+ : Flag(name, default_value, help, visible),
+ value_(value),
+ no_value_(no_value) {
+}
+
+bool BoolFlag::SetValue(const std::string& value) {
+ if (value.empty()) {
+ *value_ = true;
+ } else {
+ if (!value.compare("true"))
+ *value_ = true;
+ else if (!value.compare("false"))
+ *value_ = false;
+ else
+ return false;
+ }
+
+ *no_value_ = !*value_;
+
+ return true;
+}
+
+const char* BoolFlag::GetType() const {
+ return "bool";
+}
+
+Int32Flag::Int32Flag(const char* name,
+ int* value,
+ const char* default_value,
+ const char* help,
+ bool visible)
+ : Flag(name, default_value, help, visible), value_(value) {
+}
+
+bool Int32Flag::SetValue(const std::string& value) {
+ return base::StringToInt(value, value_);
+}
+
+const char* Int32Flag::GetType() const {
+ return "int";
+}
+
+Int64Flag::Int64Flag(const char* name,
+ int64_t* value,
+ const char* default_value,
+ const char* help,
+ bool visible)
+ : Flag(name, default_value, help, visible), value_(value) {
+}
+
+bool Int64Flag::SetValue(const std::string& value) {
+ return base::StringToInt64(value, value_);
+}
+
+const char* Int64Flag::GetType() const {
+ return "int64";
+}
+
+UInt64Flag::UInt64Flag(const char* name,
+ uint64_t* value,
+ const char* default_value,
+ const char* help,
+ bool visible)
+ : Flag(name, default_value, help, visible), value_(value) {
+}
+
+bool UInt64Flag::SetValue(const std::string& value) {
+ return base::StringToUint64(value, value_);
+}
+
+const char* UInt64Flag::GetType() const {
+ return "uint64";
+}
+
+DoubleFlag::DoubleFlag(const char* name,
+ double* value,
+ const char* default_value,
+ const char* help,
+ bool visible)
+ : Flag(name, default_value, help, visible), value_(value) {
+}
+
+bool DoubleFlag::SetValue(const std::string& value) {
+ return base::StringToDouble(value, value_);
+}
+
+const char* DoubleFlag::GetType() const {
+ return "double";
+}
+
+StringFlag::StringFlag(const char* name,
+ std::string* value,
+ const char* default_value,
+ const char* help,
+ bool visible)
+ : Flag(name, default_value, help, visible), value_(value) {
+}
+
+bool StringFlag::SetValue(const std::string& value) {
+ value_->assign(value);
+
+ return true;
+}
+
+const char* StringFlag::GetType() const {
+ return "string";
+}
+
+namespace {
+brillo::FlagHelper* instance_ = nullptr;
+} // namespace
+
+FlagHelper::FlagHelper() : command_line_(nullptr) {
+ AddFlag(std::unique_ptr<Flag>(new HelpFlag()));
+}
+
+FlagHelper::~FlagHelper() {
+}
+
+brillo::FlagHelper* FlagHelper::GetInstance() {
+ if (!instance_)
+ instance_ = new FlagHelper();
+
+ return instance_;
+}
+
+void FlagHelper::ResetForTesting() {
+ delete instance_;
+ instance_ = nullptr;
+}
+
+void FlagHelper::Init(int argc,
+ const char* const* argv,
+ std::string help_usage) {
+ brillo::FlagHelper* helper = GetInstance();
+ if (!helper->command_line_) {
+ if (!base::CommandLine::InitializedForCurrentProcess())
+ base::CommandLine::Init(argc, argv);
+ helper->command_line_ = base::CommandLine::ForCurrentProcess();
+ }
+
+ GetInstance()->SetUsageMessage(help_usage);
+
+ GetInstance()->UpdateFlagValues();
+}
+
+void FlagHelper::UpdateFlagValues() {
+ std::string error_msg;
+ int error_code = EX_OK;
+
+ // Check that base::CommandLine has been initialized.
+ CHECK(base::CommandLine::InitializedForCurrentProcess());
+
+ // If the --help flag exists, print out help message and exit.
+ if (command_line_->HasSwitch("help")) {
+ puts(GetHelpMessage().c_str());
+ exit(EX_OK);
+ }
+
+ // Iterate over the base::CommandLine switches. Update the value
+ // of the corresponding Flag if it exists, or output an error message
+ // if the flag wasn't defined.
+ const base::CommandLine::SwitchMap& switch_map = command_line_->GetSwitches();
+
+ for (const auto& pair : switch_map) {
+ const std::string& key = pair.first;
+ // Make sure we allow the standard logging switches (--v and --vmodule).
+ if (key == switches::kV || key == switches::kVModule)
+ continue;
+
+ const std::string& value = pair.second;
+
+ auto df_it = defined_flags_.find(key);
+ if (df_it != defined_flags_.end()) {
+ Flag* flag = df_it->second.get();
+ if (!flag->SetValue(value)) {
+ base::StringAppendF(
+ &error_msg,
+ "ERROR: illegal value '%s' specified for %s flag '%s'\n",
+ value.c_str(),
+ flag->GetType(),
+ flag->name_);
+ error_code = EX_DATAERR;
+ }
+ } else {
+ base::StringAppendF(
+ &error_msg, "ERROR: unknown command line flag '%s'\n", key.c_str());
+ error_code = EX_USAGE;
+ }
+ }
+
+ if (error_code != EX_OK) {
+ puts(error_msg.c_str());
+ exit(error_code);
+ }
+}
+
+void FlagHelper::AddFlag(std::unique_ptr<Flag> flag) {
+ defined_flags_.emplace(flag->name_, std::move(flag));
+}
+
+void FlagHelper::SetUsageMessage(std::string help_usage) {
+ help_usage_.assign(std::move(help_usage));
+}
+
+std::string FlagHelper::GetHelpMessage() const {
+ std::string help = help_usage_;
+ help.append("\n\n");
+ for (const auto& pair : defined_flags_) {
+ const Flag* flag = pair.second.get();
+ if (flag->visible_) {
+ base::StringAppendF(&help,
+ " --%s (%s) type: %s default: %s\n",
+ flag->name_,
+ flag->help_,
+ flag->GetType(),
+ flag->default_value_);
+ }
+ }
+ return help;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/flag_helper.h b/libbrillo/brillo/flag_helper.h
new file mode 100644
index 0000000..810a00c
--- /dev/null
+++ b/libbrillo/brillo/flag_helper.h
@@ -0,0 +1,274 @@
+// 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 a helper class for dealing with command line flags. It uses
+// base/command_line.h to parse flags from argv, but provides an API similar
+// to gflags. Command line arguments with either '-' or '--' prefixes are
+// treated as flags. Flags can optionally have a value set using an '='
+// delimeter, e.g. "--flag=value". An argument of "--" will terminate flag
+// parsing, so that any subsequent arguments will be treated as non-flag
+// arguments, regardless of prefix. Non-flag arguments are outside the scope
+// of this class, and can instead be accessed through the GetArgs() function
+// of the base::CommandLine singleton after FlagHelper initialization.
+//
+// The FlagHelper class will automatically take care of the --help flag, as
+// well as aborting the program when unknown flags are passed to the
+// application and when passed in parameters cannot be correctly parsed to
+// their respective types. Developers define flags at compile time using the
+// following macros from within main():
+//
+// DEFINE_bool(name, default_value, help)
+// DEFINE_int32(name, default_value, help)
+// DEFINE_int64(name, default_value, help)
+// DEFINE_uint64(name, default_value, help)
+// DEFINE_double(name, default_value, help)
+// DEFINE_string(name, default_value, help)
+//
+// Using the macro will create a scoped variable of the appropriate type
+// with the name FLAGS_<name>, that can be used to access the flag's
+// value within the program. Here is an example of how the FlagHelper
+// class is to be used:
+//
+// --
+//
+// #include <brillo/flag_helper.h>
+// #include <stdio.h>
+//
+// int main(int argc, char** argv) {
+// DEFINE_int32(example, 0, "Example int flag");
+// brillo::FlagHelper::Init(argc, argv, "Test application.");
+//
+// printf("You passed in %d to --example command line flag\n",
+// FLAGS_example);
+// return 0;
+// }
+//
+// --
+//
+// In order to update the FLAGS_xxxx values from their defaults to the
+// values passed in to the command line, Init(...) must be called after
+// all the DEFINE_xxxx macros have instantiated the variables.
+
+#ifndef LIBBRILLO_BRILLO_FLAG_HELPER_H_
+#define LIBBRILLO_BRILLO_FLAG_HELPER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/command_line.h>
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+// The corresponding class representation of a command line flag, used
+// to keep track of pointers to the FLAGS_xxxx variables so that they
+// can be updated.
+class Flag {
+ public:
+ Flag(const char* name,
+ const char* default_value,
+ const char* help,
+ bool visible);
+ virtual ~Flag() = default;
+
+ // Sets the associated FLAGS_xxxx value, taking into account the flag type
+ virtual bool SetValue(const std::string& value) = 0;
+
+ // Returns the type of the flag as a char array, for use in the help message
+ virtual const char* GetType() const = 0;
+
+ const char* name_;
+ const char* default_value_;
+ const char* help_;
+ bool visible_;
+};
+
+class BRILLO_EXPORT BoolFlag final : public Flag {
+ public:
+ BoolFlag(const char* name,
+ bool* value,
+ bool* no_value,
+ const char* default_value,
+ const char* help,
+ bool visible);
+ bool SetValue(const std::string& value) override;
+
+ const char* GetType() const override;
+
+ private:
+ bool* value_;
+ bool* no_value_;
+};
+
+class BRILLO_EXPORT Int32Flag final : public Flag {
+ public:
+ Int32Flag(const char* name,
+ int* value,
+ const char* default_value,
+ const char* help,
+ bool visible);
+ bool SetValue(const std::string& value) override;
+
+ const char* GetType() const override;
+
+ private:
+ int* value_;
+};
+
+class BRILLO_EXPORT Int64Flag final : public Flag {
+ public:
+ Int64Flag(const char* name,
+ int64_t* value,
+ const char* default_value,
+ const char* help,
+ bool visible);
+ bool SetValue(const std::string& value) override;
+
+ const char* GetType() const override;
+
+ private:
+ int64_t* value_;
+};
+
+class BRILLO_EXPORT UInt64Flag final : public Flag {
+ public:
+ UInt64Flag(const char* name,
+ uint64_t* value,
+ const char* default_value,
+ const char* help,
+ bool visible);
+ bool SetValue(const std::string& value) override;
+
+ const char* GetType() const override;
+
+ private:
+ uint64_t* value_;
+};
+
+class BRILLO_EXPORT DoubleFlag final : public Flag {
+ public:
+ DoubleFlag(const char* name,
+ double* value,
+ const char* default_value,
+ const char* help,
+ bool visible);
+ bool SetValue(const std::string& value) override;
+
+ const char* GetType() const override;
+
+ private:
+ double* value_;
+};
+
+class BRILLO_EXPORT StringFlag final : public Flag {
+ public:
+ StringFlag(const char* name,
+ std::string* value,
+ const char* default_value,
+ const char* help,
+ bool visible);
+ bool SetValue(const std::string& value) override;
+
+ const char* GetType() const override;
+
+ private:
+ std::string* value_;
+};
+
+// The following macros are to be used from within main() to create
+// scoped FLAGS_xxxx variables for easier access to command line flag
+// values. FLAGS_noxxxx variables are also created, which are used to
+// set bool flags to false. Creating the FLAGS_noxxxx variables here
+// will also ensure a compiler error will be thrown if another flag
+// is created with a conflicting name.
+#define DEFINE_type(type, classtype, name, value, help) \
+ type FLAGS_##name = value; \
+ brillo::FlagHelper::GetInstance()->AddFlag(std::unique_ptr<brillo::Flag>( \
+ new brillo::classtype(#name, &FLAGS_##name, #value, help, true)));
+
+#define DEFINE_int32(name, value, help) \
+ DEFINE_type(int, Int32Flag, name, value, help)
+#define DEFINE_int64(name, value, help) \
+ DEFINE_type(int64_t, Int64Flag, name, value, help)
+#define DEFINE_uint64(name, value, help) \
+ DEFINE_type(uint64_t, UInt64Flag, name, value, help)
+#define DEFINE_double(name, value, help) \
+ DEFINE_type(double, DoubleFlag, name, value, help)
+#define DEFINE_string(name, value, help) \
+ DEFINE_type(std::string, StringFlag, name, value, help)
+
+// Due to the FLAGS_no##name variables, can't re-use the same DEFINE_type macro
+// for defining bool flags
+#define DEFINE_bool(name, value, help) \
+ bool FLAGS_##name = value; \
+ bool FLAGS_no##name = !(value); \
+ brillo::FlagHelper::GetInstance()->AddFlag( \
+ std::unique_ptr<brillo::Flag>(new brillo::BoolFlag( \
+ #name, &FLAGS_##name, &FLAGS_no##name, #value, help, true))); \
+ brillo::FlagHelper::GetInstance()->AddFlag( \
+ std::unique_ptr<brillo::Flag>(new brillo::BoolFlag( \
+ "no" #name, &FLAGS_no##name, &FLAGS_##name, #value, help, false)));
+
+// The FlagHelper class is a singleton class used for registering command
+// line flags and pointers to their associated scoped variables, so that
+// the variables can be updated once the command line arguments have been
+// parsed by base::CommandLine.
+class BRILLO_EXPORT FlagHelper final {
+ public:
+ // The singleton accessor function.
+ static FlagHelper* GetInstance();
+
+ // Resets the singleton object. Developers shouldn't ever need to use this,
+ // however it is required to be run at the end of every unit test to prevent
+ // Flag definitions from carrying over from previous tests.
+ static void ResetForTesting();
+
+ // Initializes the base::CommandLine class, then calls UpdateFlagValues().
+ static void Init(int argc, const char* const* argv, std::string help_usage);
+
+ // Only to be used for running unit tests.
+ void set_command_line_for_testing(base::CommandLine* command_line) {
+ command_line_ = command_line;
+ }
+
+ // Checks all the parsed command line flags. This iterates over the switch
+ // map from base::CommandLine, and finds the corresponding Flag in order to
+ // update the FLAGS_xxxx values to the parsed value. If the --help flag is
+ // passed in, it outputs a help message and exits the program. If an unknown
+ // flag is passed in, it outputs an error message and exits the program with
+ // exit code EX_USAGE.
+ void UpdateFlagValues();
+
+ // Adds a flag to be tracked and updated once the command line is actually
+ // parsed. This function is an implementation detail, and is not meant
+ // to be used directly by developers. Developers should instead use the
+ // DEFINE_xxxx macros to register a command line flag.
+ void AddFlag(std::unique_ptr<Flag> flag);
+
+ // Sets the usage message, which is prepended to the --help message.
+ void SetUsageMessage(std::string help_usage);
+
+ private:
+ FlagHelper();
+ ~FlagHelper();
+
+ // Generates a help message from the Usage Message and registered flags.
+ std::string GetHelpMessage() const;
+
+ std::string help_usage_;
+ std::map<std::string, std::unique_ptr<Flag>> defined_flags_;
+
+ // base::CommandLine object for parsing the command line switches. This
+ // object isn't owned by this class, so don't need to delete it in the
+ // destructor.
+ base::CommandLine* command_line_;
+
+ DISALLOW_COPY_AND_ASSIGN(FlagHelper);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_FLAG_HELPER_H_
diff --git a/libbrillo/brillo/flag_helper_unittest.cc b/libbrillo/brillo/flag_helper_unittest.cc
new file mode 100644
index 0000000..29c6429
--- /dev/null
+++ b/libbrillo/brillo/flag_helper_unittest.cc
@@ -0,0 +1,362 @@
+// 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 <cstdint>
+#include <cstdio>
+#include <sysexits.h>
+
+#include <base/command_line.h>
+#include <base/macros.h>
+#include <brillo/flag_helper.h>
+
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+class FlagHelperTest : public ::testing::Test {
+ public:
+ FlagHelperTest() {}
+ ~FlagHelperTest() override { brillo::FlagHelper::ResetForTesting(); }
+ static void SetUpTestCase() { base::CommandLine::Init(0, nullptr); }
+};
+
+// Test that the DEFINE_xxxx macros can create the respective variables
+// correctly with the default value.
+TEST_F(FlagHelperTest, Defaults) {
+ DEFINE_bool(bool1, true, "Test bool flag");
+ DEFINE_bool(bool2, false, "Test bool flag");
+ DEFINE_int32(int32_1, INT32_MIN, "Test int32 flag");
+ DEFINE_int32(int32_2, 0, "Test int32 flag");
+ DEFINE_int32(int32_3, INT32_MAX, "Test int32 flag");
+ DEFINE_int64(int64_1, INT64_MIN, "Test int64 flag");
+ DEFINE_int64(int64_2, 0, "Test int64 flag");
+ DEFINE_int64(int64_3, INT64_MAX, "Test int64 flag");
+ DEFINE_uint64(uint64_1, 0, "Test uint64 flag");
+ DEFINE_uint64(uint64_2, UINT_LEAST64_MAX, "Test uint64 flag");
+ DEFINE_double(double_1, -100.5, "Test double flag");
+ DEFINE_double(double_2, 0, "Test double flag");
+ DEFINE_double(double_3, 100.5, "Test double flag");
+ DEFINE_string(string_1, "", "Test string flag");
+ DEFINE_string(string_2, "value", "Test string flag");
+
+ const char* argv[] = {"test_program"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+ brillo::FlagHelper::Init(arraysize(argv), argv, "TestDefaultTrue");
+
+ EXPECT_TRUE(FLAGS_bool1);
+ EXPECT_FALSE(FLAGS_bool2);
+ EXPECT_EQ(FLAGS_int32_1, INT32_MIN);
+ EXPECT_EQ(FLAGS_int32_2, 0);
+ EXPECT_EQ(FLAGS_int32_3, INT32_MAX);
+ EXPECT_EQ(FLAGS_int64_1, INT64_MIN);
+ EXPECT_EQ(FLAGS_int64_2, 0);
+ EXPECT_EQ(FLAGS_int64_3, INT64_MAX);
+ EXPECT_EQ(FLAGS_uint64_1, 0);
+ EXPECT_EQ(FLAGS_uint64_2, UINT_LEAST64_MAX);
+ EXPECT_DOUBLE_EQ(FLAGS_double_1, -100.5);
+ EXPECT_DOUBLE_EQ(FLAGS_double_2, 0);
+ EXPECT_DOUBLE_EQ(FLAGS_double_3, 100.5);
+ EXPECT_STREQ(FLAGS_string_1.c_str(), "");
+ EXPECT_STREQ(FLAGS_string_2.c_str(), "value");
+}
+
+// Test that command line flag values are parsed and update the flag
+// variable values correctly when using double '--' flags
+TEST_F(FlagHelperTest, SetValueDoubleDash) {
+ DEFINE_bool(bool1, false, "Test bool flag");
+ DEFINE_bool(bool2, true, "Test bool flag");
+ DEFINE_bool(bool3, false, "Test bool flag");
+ DEFINE_bool(bool4, true, "Test bool flag");
+ DEFINE_int32(int32_1, 1, "Test int32 flag");
+ DEFINE_int32(int32_2, 1, "Test int32 flag");
+ DEFINE_int32(int32_3, 1, "Test int32 flag");
+ DEFINE_int64(int64_1, 1, "Test int64 flag");
+ DEFINE_int64(int64_2, 1, "Test int64 flag");
+ DEFINE_int64(int64_3, 1, "Test int64 flag");
+ DEFINE_uint64(uint64_1, 1, "Test uint64 flag");
+ DEFINE_uint64(uint64_2, 1, "Test uint64 flag");
+ DEFINE_double(double_1, 1, "Test double flag");
+ DEFINE_double(double_2, 1, "Test double flag");
+ DEFINE_double(double_3, 1, "Test double flag");
+ DEFINE_string(string_1, "default", "Test string flag");
+ DEFINE_string(string_2, "default", "Test string flag");
+
+ const char* argv[] = {"test_program",
+ "--bool1",
+ "--nobool2",
+ "--bool3=true",
+ "--bool4=false",
+ "--int32_1=-2147483648",
+ "--int32_2=0",
+ "--int32_3=2147483647",
+ "--int64_1=-9223372036854775808",
+ "--int64_2=0",
+ "--int64_3=9223372036854775807",
+ "--uint64_1=0",
+ "--uint64_2=18446744073709551615",
+ "--double_1=-100.5",
+ "--double_2=0",
+ "--double_3=100.5",
+ "--string_1=",
+ "--string_2=value"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+ brillo::FlagHelper::Init(arraysize(argv), argv, "TestDefaultTrue");
+
+ EXPECT_TRUE(FLAGS_bool1);
+ EXPECT_FALSE(FLAGS_bool2);
+ EXPECT_TRUE(FLAGS_bool3);
+ EXPECT_FALSE(FLAGS_bool4);
+ EXPECT_EQ(FLAGS_int32_1, INT32_MIN);
+ EXPECT_EQ(FLAGS_int32_2, 0);
+ EXPECT_EQ(FLAGS_int32_3, INT32_MAX);
+ EXPECT_EQ(FLAGS_int64_1, INT64_MIN);
+ EXPECT_EQ(FLAGS_int64_2, 0);
+ EXPECT_EQ(FLAGS_int64_3, INT64_MAX);
+ EXPECT_EQ(FLAGS_uint64_1, 0);
+ EXPECT_EQ(FLAGS_uint64_2, UINT_LEAST64_MAX);
+ EXPECT_DOUBLE_EQ(FLAGS_double_1, -100.5);
+ EXPECT_DOUBLE_EQ(FLAGS_double_2, 0);
+ EXPECT_DOUBLE_EQ(FLAGS_double_3, 100.5);
+ EXPECT_STREQ(FLAGS_string_1.c_str(), "");
+ EXPECT_STREQ(FLAGS_string_2.c_str(), "value");
+}
+
+// Test that command line flag values are parsed and update the flag
+// variable values correctly when using single '-' flags
+TEST_F(FlagHelperTest, SetValueSingleDash) {
+ DEFINE_bool(bool1, false, "Test bool flag");
+ DEFINE_bool(bool2, true, "Test bool flag");
+ DEFINE_int32(int32_1, 1, "Test int32 flag");
+ DEFINE_int32(int32_2, 1, "Test int32 flag");
+ DEFINE_int32(int32_3, 1, "Test int32 flag");
+ DEFINE_int64(int64_1, 1, "Test int64 flag");
+ DEFINE_int64(int64_2, 1, "Test int64 flag");
+ DEFINE_int64(int64_3, 1, "Test int64 flag");
+ DEFINE_uint64(uint64_1, 1, "Test uint64 flag");
+ DEFINE_uint64(uint64_2, 1, "Test uint64 flag");
+ DEFINE_double(double_1, 1, "Test double flag");
+ DEFINE_double(double_2, 1, "Test double flag");
+ DEFINE_double(double_3, 1, "Test double flag");
+ DEFINE_string(string_1, "default", "Test string flag");
+ DEFINE_string(string_2, "default", "Test string flag");
+
+ const char* argv[] = {"test_program",
+ "-bool1",
+ "-nobool2",
+ "-int32_1=-2147483648",
+ "-int32_2=0",
+ "-int32_3=2147483647",
+ "-int64_1=-9223372036854775808",
+ "-int64_2=0",
+ "-int64_3=9223372036854775807",
+ "-uint64_1=0",
+ "-uint64_2=18446744073709551615",
+ "-double_1=-100.5",
+ "-double_2=0",
+ "-double_3=100.5",
+ "-string_1=",
+ "-string_2=value"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+ brillo::FlagHelper::Init(arraysize(argv), argv, "TestDefaultTrue");
+
+ EXPECT_TRUE(FLAGS_bool1);
+ EXPECT_FALSE(FLAGS_bool2);
+ EXPECT_EQ(FLAGS_int32_1, INT32_MIN);
+ EXPECT_EQ(FLAGS_int32_2, 0);
+ EXPECT_EQ(FLAGS_int32_3, INT32_MAX);
+ EXPECT_EQ(FLAGS_int64_1, INT64_MIN);
+ EXPECT_EQ(FLAGS_int64_2, 0);
+ EXPECT_EQ(FLAGS_int64_3, INT64_MAX);
+ EXPECT_EQ(FLAGS_uint64_1, 0);
+ EXPECT_EQ(FLAGS_uint64_2, UINT_LEAST64_MAX);
+ EXPECT_DOUBLE_EQ(FLAGS_double_1, -100.5);
+ EXPECT_DOUBLE_EQ(FLAGS_double_2, 0);
+ EXPECT_DOUBLE_EQ(FLAGS_double_3, 100.5);
+ EXPECT_STREQ(FLAGS_string_1.c_str(), "");
+ EXPECT_STREQ(FLAGS_string_2.c_str(), "value");
+}
+
+// Test that a duplicated flag on the command line picks up the last
+// value set.
+TEST_F(FlagHelperTest, DuplicateSetValue) {
+ DEFINE_int32(int32_1, 0, "Test in32 flag");
+
+ const char* argv[] = {"test_program", "--int32_1=5", "--int32_1=10"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+ brillo::FlagHelper::Init(arraysize(argv), argv, "TestDuplicateSetvalue");
+
+ EXPECT_EQ(FLAGS_int32_1, 10);
+}
+
+// Test that flags set after the -- marker are not parsed as command line flags
+TEST_F(FlagHelperTest, FlagTerminator) {
+ DEFINE_int32(int32_1, 0, "Test int32 flag");
+
+ const char* argv[] = {"test_program", "--int32_1=5", "--", "--int32_1=10"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+ brillo::FlagHelper::Init(arraysize(argv), argv, "TestFlagTerminator");
+
+ EXPECT_EQ(FLAGS_int32_1, 5);
+}
+
+// Test that help messages are generated correctly when the --help flag
+// is passed to the program.
+TEST_F(FlagHelperTest, HelpMessage) {
+ DEFINE_bool(bool_1, true, "Test bool flag");
+ DEFINE_int32(int_1, 0, "Test int flag");
+ DEFINE_int64(int64_1, 0, "Test int64 flag");
+ DEFINE_uint64(uint64_1, 0, "Test uint64 flag");
+ DEFINE_double(double_1, 0, "Test double flag");
+ DEFINE_string(string_1, "", "Test string flag");
+
+ const char* argv[] = {"test_program", "--int_1=value", "--help"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+
+ FILE* orig = stdout;
+ stdout = stderr;
+
+ ASSERT_EXIT(
+ brillo::FlagHelper::Init(arraysize(argv), argv, "TestHelpMessage"),
+ ::testing::ExitedWithCode(EX_OK),
+ "TestHelpMessage\n\n"
+ " --bool_1 \\(Test bool flag\\) type: bool default: true\n"
+ " --double_1 \\(Test double flag\\) type: double default: 0\n"
+ " --help \\(Show this help message\\) type: bool default: false\n"
+ " --int64_1 \\(Test int64 flag\\) type: int64 default: 0\n"
+ " --int_1 \\(Test int flag\\) type: int default: 0\n"
+ " --string_1 \\(Test string flag\\) type: string default: \"\"\n"
+ " --uint64_1 \\(Test uint64 flag\\) type: uint64 default: 0\n");
+
+ stdout = orig;
+}
+
+// Test that passing in unknown command line flags causes the program
+// to exit with EX_USAGE error code and corresponding error message.
+TEST_F(FlagHelperTest, UnknownFlag) {
+ const char* argv[] = {"test_program", "--flag=value"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+
+ FILE* orig = stdout;
+ stdout = stderr;
+
+ ASSERT_EXIT(brillo::FlagHelper::Init(arraysize(argv), argv, "TestIntExit"),
+ ::testing::ExitedWithCode(EX_USAGE),
+ "ERROR: unknown command line flag 'flag'");
+
+ stdout = orig;
+}
+
+// Test that when passing an incorrect/unparsable type to a command line flag,
+// the program exits with code EX_DATAERR and outputs a corresponding message.
+TEST_F(FlagHelperTest, BoolParseError) {
+ DEFINE_bool(bool_1, 0, "Test bool flag");
+
+ const char* argv[] = {"test_program", "--bool_1=value"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+
+ FILE* orig = stdout;
+ stdout = stderr;
+
+ ASSERT_EXIT(
+ brillo::FlagHelper::Init(arraysize(argv), argv, "TestBoolParseError"),
+ ::testing::ExitedWithCode(EX_DATAERR),
+ "ERROR: illegal value 'value' specified for bool flag 'bool_1'");
+
+ stdout = orig;
+}
+
+// Test that when passing an incorrect/unparsable type to a command line flag,
+// the program exits with code EX_DATAERR and outputs a corresponding message.
+TEST_F(FlagHelperTest, Int32ParseError) {
+ DEFINE_int32(int_1, 0, "Test int flag");
+
+ const char* argv[] = {"test_program", "--int_1=value"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+
+ FILE* orig = stdout;
+ stdout = stderr;
+
+ ASSERT_EXIT(brillo::FlagHelper::Init(arraysize(argv),
+ argv,
+ "TestInt32ParseError"),
+ ::testing::ExitedWithCode(EX_DATAERR),
+ "ERROR: illegal value 'value' specified for int flag 'int_1'");
+
+ stdout = orig;
+}
+
+// Test that when passing an incorrect/unparsable type to a command line flag,
+// the program exits with code EX_DATAERR and outputs a corresponding message.
+TEST_F(FlagHelperTest, Int64ParseError) {
+ DEFINE_int64(int64_1, 0, "Test int64 flag");
+
+ const char* argv[] = {"test_program", "--int64_1=value"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+
+ FILE* orig = stdout;
+ stdout = stderr;
+
+ ASSERT_EXIT(
+ brillo::FlagHelper::Init(arraysize(argv), argv, "TestInt64ParseError"),
+ ::testing::ExitedWithCode(EX_DATAERR),
+ "ERROR: illegal value 'value' specified for int64 flag "
+ "'int64_1'");
+
+ stdout = orig;
+}
+
+// Test that when passing an incorrect/unparsable type to a command line flag,
+// the program exits with code EX_DATAERR and outputs a corresponding message.
+TEST_F(FlagHelperTest, UInt64ParseError) {
+ DEFINE_uint64(uint64_1, 0, "Test uint64 flag");
+
+ const char* argv[] = {"test_program", "--uint64_1=value"};
+ base::CommandLine command_line(arraysize(argv), argv);
+
+ brillo::FlagHelper::GetInstance()->set_command_line_for_testing(
+ &command_line);
+
+ FILE* orig = stdout;
+ stdout = stderr;
+
+ ASSERT_EXIT(
+ brillo::FlagHelper::Init(arraysize(argv), argv, "TestUInt64ParseError"),
+ ::testing::ExitedWithCode(EX_DATAERR),
+ "ERROR: illegal value 'value' specified for uint64 flag "
+ "'uint64_1'");
+
+ stdout = orig;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/glib/abstract_dbus_service.cc b/libbrillo/brillo/glib/abstract_dbus_service.cc
new file mode 100644
index 0000000..c5ed27d
--- /dev/null
+++ b/libbrillo/brillo/glib/abstract_dbus_service.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2010 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/logging.h>
+
+#include "brillo/glib/abstract_dbus_service.h"
+
+namespace brillo {
+namespace dbus {
+
+bool AbstractDbusService::Register(const brillo::dbus::BusConnection& conn) {
+ return RegisterExclusiveService(conn,
+ service_interface(),
+ service_name(),
+ service_path(),
+ service_object());
+}
+
+bool AbstractDbusService::Run() {
+ if (!main_loop()) {
+ LOG(ERROR) << "No run loop. Call Initialize before use.";
+ return false;
+ }
+ ::g_main_loop_run(main_loop());
+ DLOG(INFO) << "Run() completed";
+ return true;
+}
+
+bool AbstractDbusService::Shutdown() {
+ ::g_main_loop_quit(main_loop());
+ return true;
+}
+
+} // namespace dbus
+} // namespace brillo
diff --git a/libbrillo/brillo/glib/abstract_dbus_service.h b/libbrillo/brillo/glib/abstract_dbus_service.h
new file mode 100644
index 0000000..c40b658
--- /dev/null
+++ b/libbrillo/brillo/glib/abstract_dbus_service.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2010 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 LIBBRILLO_BRILLO_GLIB_ABSTRACT_DBUS_SERVICE_H_
+#define LIBBRILLO_BRILLO_GLIB_ABSTRACT_DBUS_SERVICE_H_
+
+#include <brillo/brillo_export.h>
+#include <brillo/glib/dbus.h>
+
+namespace brillo {
+
+// \precondition No functions in the dbus namespace can be called before
+// ::g_type_init();
+
+namespace dbus {
+class BRILLO_EXPORT AbstractDbusService {
+ public:
+ virtual ~AbstractDbusService() {}
+
+ // Setup the wrapped GObject and the GMainLoop
+ virtual bool Initialize() = 0;
+ virtual bool Reset() = 0;
+
+ // Registers the GObject as a service with the system DBus
+ // TODO(wad) make this testable by making BusConn and Proxy
+ // subclassing friendly.
+ virtual bool Register(const brillo::dbus::BusConnection& conn);
+
+ // Starts the run loop
+ virtual bool Run();
+
+ // Stops the run loop
+ virtual bool Shutdown();
+
+ // Used internally during registration to set the
+ // proper service information.
+ virtual const char* service_name() const = 0;
+ virtual const char* service_path() const = 0;
+ virtual const char* service_interface() const = 0;
+ virtual GObject* service_object() const = 0;
+
+ protected:
+ virtual GMainLoop* main_loop() = 0;
+};
+
+} // namespace dbus
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_GLIB_ABSTRACT_DBUS_SERVICE_H_
diff --git a/libbrillo/brillo/glib/dbus.cc b/libbrillo/brillo/glib/dbus.cc
new file mode 100644
index 0000000..c56a88c
--- /dev/null
+++ b/libbrillo/brillo/glib/dbus.cc
@@ -0,0 +1,356 @@
+// Copyright (c) 2009 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 "brillo/glib/dbus.h"
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+namespace brillo {
+namespace dbus {
+
+bool CallPtrArray(const Proxy& proxy,
+ const char* method,
+ glib::ScopedPtrArray<const char*>* result) {
+ glib::ScopedError error;
+
+ ::GType g_type_array = ::dbus_g_type_get_collection("GPtrArray",
+ DBUS_TYPE_G_OBJECT_PATH);
+
+
+ if (!::dbus_g_proxy_call(proxy.gproxy(), method, &Resetter(&error).lvalue(),
+ G_TYPE_INVALID, g_type_array,
+ &Resetter(result).lvalue(), G_TYPE_INVALID)) {
+ LOG(WARNING) << "CallPtrArray failed: "
+ << (error->message ? error->message : "Unknown Error.");
+ return false;
+ }
+
+ return true;
+}
+
+BusConnection GetSystemBusConnection() {
+ glib::ScopedError error;
+ ::DBusGConnection* result = ::dbus_g_bus_get(DBUS_BUS_SYSTEM,
+ &Resetter(&error).lvalue());
+ if (!result) {
+ LOG(ERROR) << "dbus_g_bus_get(DBUS_BUS_SYSTEM) failed: "
+ << ((error.get() && error->message) ?
+ error->message : "Unknown Error");
+ return BusConnection(nullptr);
+ }
+ // Set to not exit when system bus is disconnected.
+ // This fixes the problem where when the dbus daemon is stopped, exit is
+ // called which kills Chrome.
+ ::dbus_connection_set_exit_on_disconnect(
+ ::dbus_g_connection_get_connection(result), FALSE);
+ return BusConnection(result);
+}
+
+BusConnection GetPrivateBusConnection(const char* address) {
+ // Since dbus-glib does not have an API like dbus_g_connection_open_private(),
+ // we have to implement our own.
+
+ // We have to call _dbus_g_value_types_init() to register standard marshalers
+ // just like as dbus_g_bus_get() and dbus_g_connection_open() do, but the
+ // function is not exported. So we call GetPrivateBusConnection() which calls
+ // dbus_g_bus_get() here instead. Note that if we don't call
+ // _dbus_g_value_types_init(), we might get "WARNING **: No demarshaller
+ // registered for type xxxxx" error and might not be able to handle incoming
+ // signals nor method calls.
+ {
+ BusConnection system_bus_connection = GetSystemBusConnection();
+ if (!system_bus_connection.HasConnection()) {
+ return system_bus_connection; // returns NULL connection.
+ }
+ }
+
+ ::DBusError error;
+ ::dbus_error_init(&error);
+
+ ::DBusGConnection* result = nullptr;
+ ::DBusConnection* raw_connection
+ = ::dbus_connection_open_private(address, &error);
+ if (!raw_connection) {
+ LOG(WARNING) << "dbus_connection_open_private failed: " << address;
+ return BusConnection(nullptr);
+ }
+
+ if (!::dbus_bus_register(raw_connection, &error)) {
+ LOG(ERROR) << "dbus_bus_register failed: "
+ << (error.message ? error.message : "Unknown Error.");
+ ::dbus_error_free(&error);
+ // TODO(yusukes): We don't call dbus_connection_close() nor g_object_unref()
+ // here for now since these calls might interfere with IBusBus connections
+ // in libcros and Chrome. See the comment in ~InputMethodStatusConnection()
+ // function in platform/cros/chromeos_input_method.cc for details.
+ return BusConnection(nullptr);
+ }
+
+ ::dbus_connection_setup_with_g_main(
+ raw_connection, nullptr /* default context */);
+
+ // A reference count of |raw_connection| is transferred to |result|. You don't
+ // have to (and should not) unref the |raw_connection|.
+ result = ::dbus_connection_get_g_connection(raw_connection);
+ CHECK(result);
+
+ ::dbus_connection_set_exit_on_disconnect(
+ ::dbus_g_connection_get_connection(result), FALSE);
+
+ return BusConnection(result);
+}
+
+bool RetrieveProperties(const Proxy& proxy,
+ const char* interface,
+ glib::ScopedHashTable* result) {
+ glib::ScopedError error;
+
+ if (!::dbus_g_proxy_call(proxy.gproxy(), "GetAll", &Resetter(&error).lvalue(),
+ G_TYPE_STRING, interface, G_TYPE_INVALID,
+ ::dbus_g_type_get_map("GHashTable", G_TYPE_STRING,
+ G_TYPE_VALUE),
+ &Resetter(result).lvalue(), G_TYPE_INVALID)) {
+ LOG(WARNING) << "RetrieveProperties failed: "
+ << (error->message ? error->message : "Unknown Error.");
+ return false;
+ }
+ return true;
+}
+
+Proxy::Proxy()
+ : object_(nullptr) {
+}
+
+// Set |connect_to_name_owner| true if you'd like to use
+// dbus_g_proxy_new_for_name_owner() rather than dbus_g_proxy_new_for_name().
+Proxy::Proxy(const BusConnection& connection,
+ const char* name,
+ const char* path,
+ const char* interface,
+ bool connect_to_name_owner)
+ : object_(GetGProxy(
+ connection, name, path, interface, connect_to_name_owner)) {
+}
+
+// Equivalent to Proxy(connection, name, path, interface, false).
+Proxy::Proxy(const BusConnection& connection,
+ const char* name,
+ const char* path,
+ const char* interface)
+ : object_(GetGProxy(connection, name, path, interface, false)) {
+}
+
+// Creates a peer proxy using dbus_g_proxy_new_for_peer.
+Proxy::Proxy(const BusConnection& connection,
+ const char* path,
+ const char* interface)
+ : object_(GetGPeerProxy(connection, path, interface)) {
+}
+
+Proxy::Proxy(const Proxy& x)
+ : object_(x.object_) {
+ if (object_)
+ ::g_object_ref(object_);
+}
+
+Proxy::~Proxy() {
+ if (object_)
+ ::g_object_unref(object_);
+}
+
+/* static */
+Proxy::value_type Proxy::GetGProxy(const BusConnection& connection,
+ const char* name,
+ const char* path,
+ const char* interface,
+ bool connect_to_name_owner) {
+ value_type result = nullptr;
+ if (connect_to_name_owner) {
+ glib::ScopedError error;
+ result = ::dbus_g_proxy_new_for_name_owner(connection.object_,
+ name,
+ path,
+ interface,
+ &Resetter(&error).lvalue());
+ if (!result) {
+ DLOG(ERROR) << "Failed to construct proxy: "
+ << (error->message ? error->message : "Unknown Error")
+ << ": " << path;
+ }
+ } else {
+ result = ::dbus_g_proxy_new_for_name(connection.object_,
+ name,
+ path,
+ interface);
+ if (!result) {
+ LOG(ERROR) << "Failed to construct proxy: " << path;
+ }
+ }
+ return result;
+}
+
+/* static */
+Proxy::value_type Proxy::GetGPeerProxy(const BusConnection& connection,
+ const char* path,
+ const char* interface) {
+ value_type result = ::dbus_g_proxy_new_for_peer(connection.object_,
+ path,
+ interface);
+ if (!result)
+ LOG(ERROR) << "Failed to construct peer proxy: " << path;
+
+ return result;
+}
+
+bool RegisterExclusiveService(const BusConnection& connection,
+ const char* interface_name,
+ const char* service_name,
+ const char* service_path,
+ GObject* object) {
+ CHECK(object);
+ CHECK(interface_name);
+ CHECK(service_name);
+ // Create a proxy to DBus itself so that we can request to become a
+ // service name owner and then register an object at the related service path.
+ Proxy proxy = brillo::dbus::Proxy(connection,
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS);
+ // Exclusivity is determined by replacing any existing
+ // service, not queuing, and ensuring we are the primary
+ // owner after the name is ours.
+ glib::ScopedError err;
+ guint result = 0;
+ // TODO(wad) determine if we are moving away from using generated functions
+ if (!org_freedesktop_DBus_request_name(proxy.gproxy(),
+ service_name,
+ 0,
+ &result,
+ &Resetter(&err).lvalue())) {
+ LOG(ERROR) << "Unable to request service name: "
+ << (err->message ? err->message : "Unknown Error.");
+ return false;
+ }
+
+ // Handle the error codes, releasing the name if exclusivity conditions
+ // are not met.
+ bool needs_release = false;
+ if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+ LOG(ERROR) << "Failed to become the primary owner. Releasing . . .";
+ needs_release = true;
+ }
+ if (result == DBUS_REQUEST_NAME_REPLY_EXISTS) {
+ LOG(ERROR) << "Service name exists: " << service_name;
+ return false;
+ } else if (result == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
+ LOG(ERROR) << "Service name request enqueued despite our flags. Releasing";
+ needs_release = true;
+ }
+ LOG_IF(WARNING, result == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER)
+ << "Service name already owned by this process";
+ if (needs_release) {
+ if (!org_freedesktop_DBus_release_name(
+ proxy.gproxy(),
+ service_name,
+ &result,
+ &Resetter(&err).lvalue())) {
+ LOG(ERROR) << "Unabled to release service name: "
+ << (err->message ? err->message : "Unknown Error.");
+ }
+ DLOG(INFO) << "ReleaseName returned code " << result;
+ return false;
+ }
+
+ // Determine a path from the service name and register the object.
+ dbus_g_connection_register_g_object(connection.g_connection(),
+ service_path,
+ object);
+ return true;
+}
+
+void CallMethodWithNoArguments(const char* service_name,
+ const char* path,
+ const char* interface_name,
+ const char* method_name) {
+ Proxy proxy(dbus::GetSystemBusConnection(),
+ service_name,
+ path,
+ interface_name);
+ ::dbus_g_proxy_call_no_reply(proxy.gproxy(), method_name, G_TYPE_INVALID);
+}
+
+void SignalWatcher::StartMonitoring(const std::string& interface,
+ const std::string& signal) {
+ DCHECK(interface_.empty()) << "StartMonitoring() must be called only once";
+ interface_ = interface;
+ signal_ = signal;
+
+ // Snoop on D-Bus messages so we can get notified about signals.
+ DBusConnection* dbus_conn = dbus_g_connection_get_connection(
+ GetSystemBusConnection().g_connection());
+ DCHECK(dbus_conn);
+
+ DBusError error;
+ dbus_error_init(&error);
+ dbus_bus_add_match(dbus_conn, GetDBusMatchString().c_str(), &error);
+ if (dbus_error_is_set(&error)) {
+ LOG(DFATAL) << "Got error while adding D-Bus match rule: " << error.name
+ << " (" << error.message << ")";
+ }
+
+ if (!dbus_connection_add_filter(dbus_conn,
+ &SignalWatcher::FilterDBusMessage,
+ this, // user_data
+ nullptr)) { // free_data_function
+ LOG(DFATAL) << "Unable to add D-Bus filter";
+ }
+}
+
+SignalWatcher::~SignalWatcher() {
+ if (interface_.empty())
+ return;
+
+ DBusConnection* dbus_conn = dbus_g_connection_get_connection(
+ dbus::GetSystemBusConnection().g_connection());
+ DCHECK(dbus_conn);
+
+ dbus_connection_remove_filter(dbus_conn,
+ &SignalWatcher::FilterDBusMessage,
+ this);
+
+ DBusError error;
+ dbus_error_init(&error);
+ dbus_bus_remove_match(dbus_conn, GetDBusMatchString().c_str(), &error);
+ if (dbus_error_is_set(&error)) {
+ LOG(DFATAL) << "Got error while removing D-Bus match rule: " << error.name
+ << " (" << error.message << ")";
+ }
+}
+
+std::string SignalWatcher::GetDBusMatchString() const {
+ return base::StringPrintf("type='signal', interface='%s', member='%s'",
+ interface_.c_str(), signal_.c_str());
+}
+
+/* static */
+DBusHandlerResult SignalWatcher::FilterDBusMessage(DBusConnection* dbus_conn,
+ DBusMessage* message,
+ void* data) {
+ SignalWatcher* self = static_cast<SignalWatcher*>(data);
+ if (dbus_message_is_signal(
+ message, self->interface_.c_str(), self->signal_.c_str())) {
+ self->OnSignal(message);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ } else {
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+}
+
+} // namespace dbus
+} // namespace brillo
diff --git a/libbrillo/brillo/glib/dbus.h b/libbrillo/brillo/glib/dbus.h
new file mode 100644
index 0000000..6006009
--- /dev/null
+++ b/libbrillo/brillo/glib/dbus.h
@@ -0,0 +1,468 @@
+// Copyright (c) 2009 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 LIBBRILLO_BRILLO_GLIB_DBUS_H_
+#define LIBBRILLO_BRILLO_GLIB_DBUS_H_
+
+#include <dbus/dbus-glib.h>
+#include <glib-object.h>
+
+#include <algorithm>
+#include <string>
+
+#include "base/logging.h"
+#include <brillo/brillo_export.h>
+#include <brillo/glib/object.h>
+
+struct DBusMessage;
+struct DBusConnection;
+
+namespace brillo {
+
+// \precondition No functions in the dbus namespace can be called before
+// ::g_type_init();
+
+namespace dbus {
+
+// \brief BusConnection manages the ref-count for a ::DBusGConnection*.
+//
+// A BusConnection has reference semantics bound to a particular communication
+// bus.
+//
+// \models Copyable, Assignable
+// \related GetSystemBusConnection()
+
+class BRILLO_EXPORT BusConnection {
+ public:
+ typedef ::DBusGConnection* value_type;
+
+ BusConnection(const BusConnection& x) : object_(x.object_) {
+ if (object_)
+ ::dbus_g_connection_ref(object_);
+ }
+
+ ~BusConnection() {
+ if (object_)
+ ::dbus_g_connection_unref(object_);
+ }
+
+ BusConnection& operator=(BusConnection x) {
+ swap(*this, x);
+ return *this;
+ }
+
+ const value_type& g_connection() const {
+ DCHECK(object_) << "referencing an empty connection";
+ return object_;
+ }
+
+ operator bool() const { return object_; }
+
+ bool HasConnection() const { return object_; }
+
+ private:
+ friend void swap(BusConnection& x, BusConnection& y);
+
+ friend class Proxy;
+ friend BusConnection GetSystemBusConnection();
+ friend BusConnection GetPrivateBusConnection(const char* address);
+
+ // Constructor takes ownership
+ BRILLO_PRIVATE explicit BusConnection(::DBusGConnection* x) : object_(x) {}
+
+ value_type object_;
+};
+
+inline void swap(BusConnection& x, BusConnection& y) {
+ std::swap(x.object_, y.object_);
+}
+
+// \brief Proxy manages the ref-count for a ::DBusGProxy*.
+//
+// Proxy has reference semantics and represents a connection to on object on
+// the bus. A proxy object is constructed with a connection to a bus, a name
+// to an entity on the bus, a path to an object owned by the entity, and an
+// interface protocol name used to communicate with the object.
+
+class BRILLO_EXPORT Proxy {
+ public:
+ typedef ::DBusGProxy* value_type;
+
+ Proxy();
+
+ // Set |connect_to_name_owner| true if you'd like to use
+ // dbus_g_proxy_new_for_name_owner() rather than dbus_g_proxy_new_for_name().
+ Proxy(const BusConnection& connection,
+ const char* name,
+ const char* path,
+ const char* interface,
+ bool connect_to_name_owner);
+
+ // Equivalent to Proxy(connection, name, path, interface, false).
+ Proxy(const BusConnection& connection,
+ const char* name,
+ const char* path,
+ const char* interface);
+
+ // Creates a peer proxy using dbus_g_proxy_new_for_peer.
+ Proxy(const BusConnection& connection,
+ const char* path,
+ const char* interface);
+
+ Proxy(const Proxy& x);
+
+ ~Proxy();
+
+ Proxy& operator=(Proxy x) {
+ swap(*this, x);
+ return *this;
+ }
+
+ const char* path() const {
+ DCHECK(object_) << "referencing an empty proxy";
+ return ::dbus_g_proxy_get_path(object_);
+ }
+
+ // gproxy() returns a reference to the underlying ::DBusGProxy*. As this
+ // library evolves, the gproxy() will be moved to be private.
+
+ const value_type& gproxy() const {
+ DCHECK(object_) << "referencing an empty proxy";
+ return object_;
+ }
+
+ operator bool() const { return object_; }
+
+ private:
+ BRILLO_PRIVATE static value_type GetGProxy(const BusConnection& connection,
+ const char* name,
+ const char* path,
+ const char* interface,
+ bool connect_to_name_owner);
+
+ BRILLO_PRIVATE static value_type GetGPeerProxy(
+ const BusConnection& connection,
+ const char* path,
+ const char* interface);
+
+ BRILLO_PRIVATE operator int() const; // for safe bool cast
+ friend void swap(Proxy& x, Proxy& y);
+
+ value_type object_;
+};
+
+inline void swap(Proxy& x, Proxy& y) {
+ std::swap(x.object_, y.object_);
+}
+
+// \brief RegisterExclusiveService configures a GObject to run as a service on
+// a supplied ::BusConnection.
+//
+// RegisterExclusiveService encapsulates the process of configuring the
+// supplied \param object at \param service_path on the \param connection.
+// Exclusivity is ensured by replacing any existing services at that named
+// location and confirming that the connection is the primary owner.
+//
+// Type information for the \param object must be installed with
+// dbus_g_object_type_install_info prior to use.
+
+BRILLO_EXPORT bool RegisterExclusiveService(const BusConnection& connection,
+ const char* interface_name,
+ const char* service_name,
+ const char* service_path,
+ GObject* object);
+
+template<typename F> // F is a function signature
+class MonitorConnection;
+
+template<typename A1>
+class MonitorConnection<void(A1)> {
+ public:
+ MonitorConnection(const Proxy& proxy,
+ const char* name,
+ void (*monitor)(void*, A1),
+ void* object)
+ : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {}
+
+ static void Run(::DBusGProxy*, A1 x, MonitorConnection* self) {
+ self->monitor_(self->object_, x);
+ }
+ const Proxy& proxy() const { return proxy_; }
+ const std::string& name() const { return name_; }
+
+ private:
+ Proxy proxy_;
+ std::string name_;
+ void (*monitor_)(void*, A1);
+ void* object_;
+};
+
+template<typename A1, typename A2>
+class MonitorConnection<void(A1, A2)> {
+ public:
+ MonitorConnection(const Proxy& proxy,
+ const char* name,
+ void (*monitor)(void*, A1, A2),
+ void* object)
+ : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {}
+
+ static void Run(::DBusGProxy*, A1 x, A2 y, MonitorConnection* self) {
+ self->monitor_(self->object_, x, y);
+ }
+ const Proxy& proxy() const { return proxy_; }
+ const std::string& name() const { return name_; }
+
+ private:
+ Proxy proxy_;
+ std::string name_;
+ void (*monitor_)(void*, A1, A2);
+ void* object_;
+};
+
+template<typename A1, typename A2, typename A3>
+class MonitorConnection<void(A1, A2, A3)> {
+ public:
+ MonitorConnection(const Proxy& proxy,
+ const char* name,
+ void (*monitor)(void*, A1, A2, A3),
+ void* object)
+ : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {}
+
+ static void Run(::DBusGProxy*, A1 x, A2 y, A3 z, MonitorConnection* self) {
+ self->monitor_(self->object_, x, y, z);
+ }
+ const Proxy& proxy() const { return proxy_; }
+ const std::string& name() const { return name_; }
+
+ private:
+ Proxy proxy_;
+ std::string name_;
+ void (*monitor_)(void*, A1, A2, A3);
+ void* object_;
+};
+
+template<typename A1, typename A2, typename A3, typename A4>
+class MonitorConnection<void(A1, A2, A3, A4)> {
+ public:
+ MonitorConnection(const Proxy& proxy,
+ const char* name,
+ void (*monitor)(void*, A1, A2, A3, A4),
+ void* object)
+ : proxy_(proxy), name_(name), monitor_(monitor), object_(object) {}
+
+ static void Run(::DBusGProxy*,
+ A1 x,
+ A2 y,
+ A3 z,
+ A4 w,
+ MonitorConnection* self) {
+ self->monitor_(self->object_, x, y, z, w);
+ }
+ const Proxy& proxy() const { return proxy_; }
+ const std::string& name() const { return name_; }
+
+ private:
+ Proxy proxy_;
+ std::string name_;
+ void (*monitor_)(void*, A1, A2, A3, A4);
+ void* object_;
+};
+
+template<typename A1>
+MonitorConnection<void(A1)>* Monitor(const Proxy& proxy,
+ const char* name,
+ void (*monitor)(void*, A1),
+ void* object) {
+ typedef MonitorConnection<void(A1)> ConnectionType;
+
+ ConnectionType* result = new ConnectionType(proxy, name, monitor, object);
+
+ ::dbus_g_proxy_add_signal(
+ proxy.gproxy(), name, glib::type_to_gtypeid<A1>(), G_TYPE_INVALID);
+ ::dbus_g_proxy_connect_signal(
+ proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr);
+ return result;
+}
+
+template<typename A1, typename A2>
+MonitorConnection<void(A1, A2)>* Monitor(const Proxy& proxy,
+ const char* name,
+ void (*monitor)(void*, A1, A2),
+ void* object) {
+ typedef MonitorConnection<void(A1, A2)> ConnectionType;
+
+ ConnectionType* result = new ConnectionType(proxy, name, monitor, object);
+
+ ::dbus_g_proxy_add_signal(proxy.gproxy(),
+ name,
+ glib::type_to_gtypeid<A1>(),
+ glib::type_to_gtypeid<A2>(),
+ G_TYPE_INVALID);
+ ::dbus_g_proxy_connect_signal(
+ proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr);
+ return result;
+}
+
+template<typename A1, typename A2, typename A3>
+MonitorConnection<void(A1, A2, A3)>* Monitor(const Proxy& proxy,
+ const char* name,
+ void (*monitor)(void*, A1, A2, A3),
+ void* object) {
+ typedef MonitorConnection<void(A1, A2, A3)> ConnectionType;
+
+ ConnectionType* result = new ConnectionType(proxy, name, monitor, object);
+
+ ::dbus_g_proxy_add_signal(proxy.gproxy(),
+ name,
+ glib::type_to_gtypeid<A1>(),
+ glib::type_to_gtypeid<A2>(),
+ glib::type_to_gtypeid<A3>(),
+ G_TYPE_INVALID);
+ ::dbus_g_proxy_connect_signal(
+ proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr);
+ return result;
+}
+
+template<typename A1, typename A2, typename A3, typename A4>
+MonitorConnection<void(A1, A2, A3, A4)>* Monitor(
+ const Proxy& proxy,
+ const char* name,
+ void (*monitor)(void*, A1, A2, A3, A4),
+ void* object) {
+ typedef MonitorConnection<void(A1, A2, A3, A4)> ConnectionType;
+
+ ConnectionType* result = new ConnectionType(proxy, name, monitor, object);
+
+ ::dbus_g_proxy_add_signal(proxy.gproxy(),
+ name,
+ glib::type_to_gtypeid<A1>(),
+ glib::type_to_gtypeid<A2>(),
+ glib::type_to_gtypeid<A3>(),
+ glib::type_to_gtypeid<A4>(),
+ G_TYPE_INVALID);
+ ::dbus_g_proxy_connect_signal(
+ proxy.gproxy(), name, G_CALLBACK(&ConnectionType::Run), result, nullptr);
+ return result;
+}
+
+template<typename F>
+void Disconnect(MonitorConnection<F>* connection) {
+ typedef MonitorConnection<F> ConnectionType;
+
+ ::dbus_g_proxy_disconnect_signal(connection->proxy().gproxy(),
+ connection->name().c_str(),
+ G_CALLBACK(&ConnectionType::Run),
+ connection);
+ delete connection;
+}
+
+// \brief call_PtrArray() invokes a method on a proxy returning a
+// glib::PtrArray.
+//
+// CallPtrArray is the first instance of what is likely to be a general
+// way to make method calls to a proxy. It will likely be replaced with
+// something like Call(proxy, method, arg1, arg2, ..., ResultType*) in the
+// future. However, I don't yet have enough cases to generalize from.
+
+BRILLO_EXPORT bool CallPtrArray(const Proxy& proxy,
+ const char* method,
+ glib::ScopedPtrArray<const char*>* result);
+
+// \brief RetrieveProperty() retrieves a property of an object associated with a
+// proxy.
+//
+// Given a proxy to an object supporting the org.freedesktop.DBus.Properties
+// interface, the RetrieveProperty() call will retrieve a property of the
+// specified interface on the object storing it in \param result and returning
+// \true. If the dbus call fails or the object returned is not of type \param T,
+// then \false is returned and \param result is unchanged.
+//
+// \example
+// Proxy proxy(GetSystemBusConnection(),
+// "org.freedesktop.DeviceKit.Power", // A named entity on the bus
+// battery_name, // Path to a battery on the bus
+// "org.freedesktop.DBus.Properties") // Properties interface
+//
+// double x;
+// if (RetrieveProperty(proxy,
+// "org.freedesktop.DeviceKit.Power.Device",
+// "percentage")
+// std::cout << "Battery charge is " << x << "% of capacity.";
+// \end_example
+
+template<typename T>
+inline bool RetrieveProperty(const Proxy& proxy,
+ const char* interface,
+ const char* property,
+ T* result) {
+ glib::ScopedError error;
+ glib::Value value;
+
+ if (!::dbus_g_proxy_call(proxy.gproxy(), "Get", &Resetter(&error).lvalue(),
+ G_TYPE_STRING, interface,
+ G_TYPE_STRING, property,
+ G_TYPE_INVALID,
+ G_TYPE_VALUE, &value,
+ G_TYPE_INVALID)) {
+ LOG(ERROR) << "Getting property failed: "
+ << (error->message ? error->message : "Unknown Error.");
+ return false;
+ }
+ return glib::Retrieve(value, result);
+}
+
+// \brief RetrieveProperties returns a HashTable of all properties for the
+// specified interface.
+
+BRILLO_EXPORT bool RetrieveProperties(const Proxy& proxy,
+ const char* interface,
+ glib::ScopedHashTable* result);
+
+// \brief Returns a connection to the system bus.
+
+BRILLO_EXPORT BusConnection GetSystemBusConnection();
+
+// \brief Returns a private connection to a bus at |address|.
+
+BRILLO_EXPORT BusConnection GetPrivateBusConnection(const char* address);
+
+// \brief Calls a method |method_name| with no arguments per the given |path|
+// and |interface_name|. Ignores return value.
+
+BRILLO_EXPORT void CallMethodWithNoArguments(const char* service_name,
+ const char* path,
+ const char* interface_name,
+ const char* method_name);
+
+// \brief Low-level signal monitor base class.
+//
+// Used when there is no definite named signal sender (that Proxy
+// could be used for).
+
+class BRILLO_EXPORT SignalWatcher {
+ public:
+ SignalWatcher() {}
+ ~SignalWatcher();
+ void StartMonitoring(const std::string& interface, const std::string& signal);
+
+ private:
+ // Callback invoked on the given signal arrival.
+ virtual void OnSignal(DBusMessage* message) = 0;
+
+ // Returns a string matching the D-Bus messages that we want to listen for.
+ BRILLO_PRIVATE std::string GetDBusMatchString() const;
+
+ // A D-Bus message filter to receive signals.
+ BRILLO_PRIVATE static DBusHandlerResult FilterDBusMessage(
+ DBusConnection* dbus_conn,
+ DBusMessage* message,
+ void* data);
+ std::string interface_;
+ std::string signal_;
+};
+
+} // namespace dbus
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_GLIB_DBUS_H_
diff --git a/libbrillo/brillo/glib/object.h b/libbrillo/brillo/glib/object.h
new file mode 100644
index 0000000..dfa96b6
--- /dev/null
+++ b/libbrillo/brillo/glib/object.h
@@ -0,0 +1,508 @@
+// Copyright (c) 2009 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 LIBBRILLO_BRILLO_GLIB_OBJECT_H_
+#define LIBBRILLO_BRILLO_GLIB_OBJECT_H_
+
+#include <glib-object.h>
+#include <stdint.h>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <memory>
+#include <string>
+
+namespace brillo {
+
+namespace details { // NOLINT
+
+// \brief ResetHelper is a private class for use with Resetter().
+//
+// ResetHelper passes ownership of a pointer to a scoped pointer type with reset
+// on destruction.
+
+template <typename T> // T models ScopedPtr
+class ResetHelper {
+ public:
+ typedef typename T::element_type element_type;
+
+ explicit ResetHelper(T* x)
+ : ptr_(nullptr),
+ scoped_(x) {
+ }
+ ~ResetHelper() {
+ scoped_->reset(ptr_);
+ }
+ element_type*& lvalue() {
+ return ptr_;
+ }
+
+ private:
+ element_type* ptr_;
+ T* scoped_;
+};
+
+} // namespace details
+
+// \brief Resetter() is a utility function for passing pointers to
+// scoped pointers.
+//
+// The Resetter() function return a temporary object containing an lvalue of
+// \code T::element_type which can be assigned to. When the temporary object
+// destructs, the associated scoped pointer is reset with the lvalue. It is of
+// general use when a pointer is returned as an out-argument.
+//
+// \example
+// void function(int** x) {
+// *x = new int(10);
+// }
+// ...
+// std::unique_ptr<int> x;
+// function(Resetter(x).lvalue());
+//
+// \end_example
+
+template <typename T> // T models ScopedPtr
+details::ResetHelper<T> Resetter(T* x) {
+ return details::ResetHelper<T>(x);
+}
+
+// \precondition No functions in the glib namespace can be called before
+// ::g_type_init();
+
+namespace glib {
+
+// \brief type_to_gtypeid is a type function mapping from a canonical type to
+// the GType typeid for the associated GType (see type_to_gtype).
+
+template <typename T> ::GType type_to_gtypeid();
+
+template < >
+inline ::GType type_to_gtypeid<const char*>() {
+ return G_TYPE_STRING;
+}
+template < >
+inline ::GType type_to_gtypeid<char*>() {
+ return G_TYPE_STRING;
+}
+template < >
+inline ::GType type_to_gtypeid< ::uint8_t>() {
+ return G_TYPE_UCHAR;
+}
+template < >
+inline ::GType type_to_gtypeid<double>() {
+ return G_TYPE_DOUBLE;
+}
+template < >
+inline ::GType type_to_gtypeid<bool>() {
+ return G_TYPE_BOOLEAN;
+}
+class Value;
+template < >
+inline ::GType type_to_gtypeid<const Value*>() {
+ return G_TYPE_VALUE;
+}
+
+template < >
+inline ::GType type_to_gtypeid< ::uint32_t>() {
+ // REVISIT (seanparent) : There currently isn't any G_TYPE_UINT32, this code
+ // assumes sizeof(guint) == sizeof(guint32). Need a static_assert to assert
+ // that.
+ return G_TYPE_UINT;
+}
+
+template < >
+inline ::GType type_to_gtypeid< ::int64_t>() {
+ return G_TYPE_INT64;
+}
+
+template < >
+inline ::GType type_to_gtypeid< ::int32_t>() {
+ return G_TYPE_INT;
+}
+
+// \brief Value (and Retrieve) support using std::string as well as const char*
+// by promoting from const char* to the string. promote_from provides a mapping
+// for this promotion (and possibly others in the future).
+
+template <typename T> struct promotes_from {
+ typedef T type;
+};
+template < > struct promotes_from<std::string> {
+ typedef const char* type;
+};
+
+// \brief RawCast converts from a GValue to a value of a canonical type.
+//
+// RawCast is a low level function. Generally, use Cast() instead.
+//
+// \precondition \param x contains a value of type \param T.
+
+template <typename T>
+inline T RawCast(const ::GValue& x) {
+ // Use static_assert() to issue a meaningful compile-time error.
+ // To prevent this from happening for all references to RawCast, use sizeof(T)
+ // to make static_assert depend on type T and therefore prevent binding it
+ // unconditionally until the actual RawCast<T> instantiation happens.
+ static_assert(sizeof(T) == 0, "Using RawCast on unsupported type");
+ return T();
+}
+
+template < >
+inline const char* RawCast<const char*>(const ::GValue& x) {
+ return static_cast<const char*>(::g_value_get_string(&x));
+}
+template < >
+inline double RawCast<double>(const ::GValue& x) {
+ return static_cast<double>(::g_value_get_double(&x));
+}
+template < >
+inline bool RawCast<bool>(const ::GValue& x) {
+ return static_cast<bool>(::g_value_get_boolean(&x));
+}
+template < >
+inline ::uint32_t RawCast< ::uint32_t>(const ::GValue& x) {
+ return static_cast< ::uint32_t>(::g_value_get_uint(&x));
+}
+template < >
+inline ::uint8_t RawCast< ::uint8_t>(const ::GValue& x) {
+ return static_cast< ::uint8_t>(::g_value_get_uchar(&x));
+}
+template < >
+inline ::int64_t RawCast< ::int64_t>(const ::GValue& x) {
+ return static_cast< ::int64_t>(::g_value_get_int64(&x));
+}
+template < >
+inline ::int32_t RawCast< ::int32_t>(const ::GValue& x) {
+ return static_cast< ::int32_t>(::g_value_get_int(&x));
+}
+
+inline void RawSet(GValue* x, const std::string& v) {
+ ::g_value_set_string(x, v.c_str());
+}
+inline void RawSet(GValue* x, const char* v) {
+ ::g_value_set_string(x, v);
+}
+inline void RawSet(GValue* x, double v) {
+ ::g_value_set_double(x, v);
+}
+inline void RawSet(GValue* x, bool v) {
+ ::g_value_set_boolean(x, v);
+}
+inline void RawSet(GValue* x, ::uint32_t v) {
+ ::g_value_set_uint(x, v);
+}
+inline void RawSet(GValue* x, ::uint8_t v) {
+ ::g_value_set_uchar(x, v);
+}
+inline void RawSet(GValue* x, ::int64_t v) {
+ ::g_value_set_int64(x, v);
+}
+inline void RawSet(GValue* x, ::int32_t v) {
+ ::g_value_set_int(x, v);
+}
+
+// \brief Value is a data type for managing GValues.
+//
+// A Value is a polymorphic container holding at most a single value.
+//
+// The Value wrapper ensures proper initialization, copies, and assignment of
+// GValues.
+//
+// \note GValues are equationally incomplete and so can't support proper
+// equality. The semantics of copy are verified with equality of retrieved
+// values.
+
+class Value : public ::GValue {
+ public:
+ Value()
+ : GValue() {
+ }
+ explicit Value(const ::GValue& x)
+ : GValue() {
+ *this = *static_cast<const Value*>(&x);
+ }
+ template <typename T>
+ explicit Value(T x)
+ : GValue() {
+ ::g_value_init(this,
+ type_to_gtypeid<typename promotes_from<T>::type>());
+ RawSet(this, x);
+ }
+ Value(const Value& x)
+ : GValue() {
+ if (x.empty())
+ return;
+ ::g_value_init(this, G_VALUE_TYPE(&x));
+ ::g_value_copy(&x, this);
+ }
+ ~Value() {
+ clear();
+ }
+ Value& operator=(const Value& x) {
+ if (this == &x)
+ return *this;
+ clear();
+ if (x.empty())
+ return *this;
+ ::g_value_init(this, G_VALUE_TYPE(&x));
+ ::g_value_copy(&x, this);
+ return *this;
+ }
+ template <typename T>
+ Value& operator=(const T& x) {
+ clear();
+ ::g_value_init(this,
+ type_to_gtypeid<typename promotes_from<T>::type>());
+ RawSet(this, x);
+ return *this;
+ }
+
+ // Lower-case names to follow STL container conventions.
+
+ void clear() {
+ if (!empty())
+ ::g_value_unset(this);
+ }
+
+ bool empty() const {
+ return G_VALUE_TYPE(this) == G_TYPE_INVALID;
+ }
+};
+
+template < >
+inline const Value* RawCast<const Value*>(const ::GValue& x) {
+ return static_cast<const Value*>(&x);
+}
+
+// \brief Retrieve gets a value from a GValue.
+//
+// \postcondition If \param x contains a value of type \param T, then the
+// value is copied to \param result and \true is returned. Otherwise, \param
+// result is unchanged and \false is returned.
+//
+// \precondition \param result is not \nullptr.
+
+template <typename T>
+bool Retrieve(const ::GValue& x, T* result) {
+ if (!G_VALUE_HOLDS(&x, type_to_gtypeid<typename promotes_from<T>::type>())) {
+ LOG(WARNING) << "GValue retrieve failed. Expected: "
+ << g_type_name(type_to_gtypeid<typename promotes_from<T>::type>())
+ << ", Found: " << g_type_name(G_VALUE_TYPE(&x));
+ return false;
+ }
+
+ *result = RawCast<typename promotes_from<T>::type>(x);
+ return true;
+}
+
+inline bool Retrieve(const ::GValue& x, Value* result) {
+ *result = Value(x);
+ return true;
+}
+
+// \brief ScopedError holds a ::GError* and deletes it on destruction.
+
+struct FreeError {
+ void operator()(::GError* x) const {
+ if (x)
+ ::g_error_free(x);
+ }
+};
+
+typedef std::unique_ptr< ::GError, FreeError> ScopedError;
+
+// \brief ScopedArray holds a ::GArray* and deletes both the container and the
+// segment containing the elements on destruction.
+
+struct FreeArray {
+ void operator()(::GArray* x) const {
+ if (x)
+ ::g_array_free(x, TRUE);
+ }
+};
+
+typedef std::unique_ptr< ::GArray, FreeArray> ScopedArray;
+
+// \brief ScopedPtrArray adapts ::GPtrArray* to conform to the standard
+// container requirements.
+//
+// \note ScopedPtrArray is only partially implemented and is being fleshed out
+// as needed.
+//
+// \models Random Access Container, Back Insertion Sequence, ScopedPtrArray is
+// not copyable and equationally incomplete.
+
+template <typename T> // T models pointer
+class ScopedPtrArray {
+ public:
+ typedef ::GPtrArray element_type;
+
+ typedef T value_type;
+ typedef const value_type& const_reference;
+ typedef value_type* iterator;
+ typedef const value_type* const_iterator;
+
+ ScopedPtrArray()
+ : object_(0) {
+ }
+
+ explicit ScopedPtrArray(::GPtrArray* x)
+ : object_(x) {
+ }
+
+ ~ScopedPtrArray() {
+ clear();
+ }
+
+ iterator begin() {
+ return iterator(object_ ? object_->pdata : nullptr);
+ }
+ iterator end() {
+ return begin() + size();
+ }
+ const_iterator begin() const {
+ return const_iterator(object_ ? object_->pdata : nullptr);
+ }
+ const_iterator end() const {
+ return begin() + size();
+ }
+
+ // \precondition x is a pointer to an object allocated with g_new().
+
+ void push_back(T x) {
+ if (!object_)
+ object_ = ::g_ptr_array_sized_new(1);
+ ::g_ptr_array_add(object_, ::gpointer(x));
+ }
+
+ T& operator[](std::size_t n) {
+ DCHECK(!(size() < n)) << "ScopedPtrArray index out-of-bound.";
+ return *(begin() + n);
+ }
+
+ std::size_t size() const {
+ return object_ ? object_->len : 0;
+ }
+
+ void clear() {
+ if (object_) {
+ std::for_each(begin(), end(), FreeHelper());
+ ::g_ptr_array_free(object_, true);
+ object_ = nullptr;
+ }
+ }
+
+ void reset(::GPtrArray* p = nullptr) {
+ if (p != object_) {
+ clear();
+ object_ = p;
+ }
+ }
+
+ private:
+ struct FreeHelper {
+ void operator()(T x) const {
+ ::g_free(::gpointer(x));
+ }
+ };
+
+ template <typename U>
+ friend void swap(ScopedPtrArray<U>& x, ScopedPtrArray<U>& y);
+
+ ::GPtrArray* object_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedPtrArray);
+};
+
+template <typename U>
+inline void swap(ScopedPtrArray<U>& x, ScopedPtrArray<U>& y) {
+ std::swap(x.object_, y.object_);
+}
+
+// \brief ScopedHashTable manages the lifetime of a ::GHashTable* with an
+// interface compatibitle with a scoped ptr.
+//
+// The ScopedHashTable is also the start of an adaptor to model a standard
+// Container. The standard for an associative container would have an iterator
+// returning a key value pair. However, that isn't possible with
+// ::GHashTable because there is no interface returning a reference to the
+// key value pair, only to retrieve the keys and values and individual elements.
+//
+// So the standard interface of find() wouldn't work. I considered implementing
+// operator[] and count() - operator []. So retrieving a value would look like:
+//
+// if (table.count(key))
+// success = Retrieve(table[key], &value);
+//
+// But that requires hashing the key twice.
+// For now I implemented a Retrieve member function to follow the pattern
+// developed elsewhere in the code.
+//
+// bool success = Retrieve(key, &x);
+//
+// This is also a template to retrieve the corect type from the stored GValue
+// type.
+//
+// I may revisit this and use scoped_ptr_malloc and a non-member function
+// Retrieve() in the future. The Retrieve pattern is becoming common enough
+// that I want to give some thought as to how to generalize it further.
+
+class ScopedHashTable {
+ public:
+ typedef ::GHashTable element_type;
+
+ ScopedHashTable()
+ : object_(nullptr) {
+ }
+
+ explicit ScopedHashTable(::GHashTable* p)
+ : object_(p) {
+ }
+
+ ~ScopedHashTable() {
+ clear();
+ }
+
+ template <typename T>
+ bool Retrieve(const char* key, T* result) const {
+ DCHECK(object_) << "Retrieve on empty ScopedHashTable.";
+ if (!object_)
+ return false;
+
+ ::gpointer ptr = ::g_hash_table_lookup(object_, key);
+ if (!ptr)
+ return false;
+ return glib::Retrieve(*static_cast< ::GValue*>(ptr), result);
+ }
+
+ void clear() {
+ if (object_) {
+ ::g_hash_table_unref(object_);
+ object_ = nullptr;
+ }
+ }
+
+ GHashTable* get() {
+ return object_;
+ }
+
+ void reset(::GHashTable* p = nullptr) {
+ if (p != object_) {
+ clear();
+ object_ = p;
+ }
+ }
+
+ private:
+ ::GHashTable* object_;
+};
+
+} // namespace glib
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_GLIB_OBJECT_H_
diff --git a/libbrillo/brillo/glib/object_unittest.cc b/libbrillo/brillo/glib/object_unittest.cc
new file mode 100644
index 0000000..a1ed408
--- /dev/null
+++ b/libbrillo/brillo/glib/object_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2009 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 "brillo/glib/object.h"
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <cstring>
+#include <iterator>
+#include <string>
+
+using brillo::glib::ScopedPtrArray;
+using brillo::glib::ScopedError;
+using brillo::glib::Retrieve;
+using brillo::glib::Value;
+using brillo::Resetter;
+
+namespace { // NOLINT
+
+template <typename T>
+void SetRetrieveTest(const T& x) {
+ Value tmp(x);
+ T result;
+ EXPECT_TRUE(Retrieve(tmp, &result));
+ EXPECT_EQ(result, x);
+}
+
+void ModifyValue(Value* x) {
+ *x = 1.0 / 1231415926.0; // An unlikely value
+}
+
+template <typename T, typename O>
+void MutableRegularTestValue(const T& x, O modify) {
+ Value tmp(x);
+ Value y = tmp; // copy-construction
+ T result;
+ EXPECT_TRUE(Retrieve(y, &result));
+ EXPECT_EQ(result, x);
+ modify(&y);
+ LOG(INFO) << "Warning Expected.";
+ EXPECT_TRUE(!(Retrieve(y, &result) && result == x));
+ y = tmp; // assignment
+ EXPECT_TRUE(Retrieve(y, &result));
+ EXPECT_EQ(result, x);
+ modify(&y);
+ LOG(INFO) << "Warning Expected.";
+ EXPECT_TRUE(!(Retrieve(y, &result) && result == x));
+}
+
+void OutArgument(int** x) {
+ *x = new int(10); // NOLINT
+}
+
+} // namespace
+
+TEST(ResetterTest, All) {
+ std::unique_ptr<int> x;
+ OutArgument(&Resetter(&x).lvalue());
+ EXPECT_EQ(*x, 10);
+}
+
+TEST(RetrieveTest, Types) {
+ SetRetrieveTest(std::string("Hello!"));
+ SetRetrieveTest(static_cast<uint32_t>(10));
+ SetRetrieveTest(10.5);
+ SetRetrieveTest(true);
+}
+
+TEST(ValueTest, All) {
+ Value x; // default construction
+ Value y = x; // copy with default value
+ x = y; // assignment with default value
+ Value z(1.5);
+ x = z; // assignment to default value
+ MutableRegularTestValue(std::string("Hello!"), &ModifyValue);
+}
+
+TEST(ScopedErrorTest, All) {
+ ScopedError a; // default construction
+ ScopedError b(::g_error_new(::g_quark_from_static_string("error"), -1,
+ "")); // constructor
+ ::GError* c = ::g_error_new(::g_quark_from_static_string("error"), -1,
+ "");
+ ::GError* d = ::g_error_new(::g_quark_from_static_string("error"), -1,
+ "");
+ a.reset(c); // reset form 1
+ (void)d;
+}
+
+TEST(ScopedPtrArrayTest, Construction) {
+ const char item[] = "a string";
+ char* a = static_cast<char*>(::g_malloc(sizeof(item)));
+ std::strcpy(a, &item[0]); // NOLINT
+
+ ::GPtrArray* array = ::g_ptr_array_new();
+ ::g_ptr_array_add(array, ::gpointer(a));
+
+ ScopedPtrArray<const char*> x(array);
+ EXPECT_EQ(x.size(), 1);
+ EXPECT_EQ(x[0], a); // indexing
+}
+
+TEST(ScopedPtrArrayTest, Reset) {
+ const char item[] = "a string";
+ char* a = static_cast<char*>(::g_malloc(sizeof(item)));
+ std::strcpy(a, &item[0]); // NOLINT
+
+ ScopedPtrArray<const char*> x; // default construction
+ x.push_back(a);
+ EXPECT_EQ(x.size(), 1);
+ x.reset();
+ EXPECT_EQ(x.size(), 0);
+
+ char* b = static_cast<char*>(::g_malloc(sizeof(item)));
+ std::strcpy(b, &item[0]); // NOLINT
+
+ ::GPtrArray* array = ::g_ptr_array_new();
+ ::g_ptr_array_add(array, ::gpointer(b));
+
+ x.reset(array);
+ EXPECT_EQ(x.size(), 1);
+}
+
+TEST(ScopedPtrArrayTest, Iteration) {
+ char* a[] = { static_cast<char*>(::g_malloc(1)),
+ static_cast<char*>(::g_malloc(1)), static_cast<char*>(::g_malloc(1)) };
+
+ ScopedPtrArray<const char*> x;
+ std::copy(&a[0], &a[3], std::back_inserter(x));
+ EXPECT_TRUE(std::equal(x.begin(), x.end(), &a[0]));
+}
+
diff --git a/libbrillo/brillo/http/curl_api.cc b/libbrillo/brillo/http/curl_api.cc
new file mode 100644
index 0000000..1c018bc
--- /dev/null
+++ b/libbrillo/brillo/http/curl_api.cc
@@ -0,0 +1,197 @@
+// 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 <brillo/http/curl_api.h>
+
+#include <base/logging.h>
+
+namespace brillo {
+namespace http {
+
+namespace {
+
+static_assert(CURLOPTTYPE_LONG == 0 &&
+ CURLOPTTYPE_OBJECTPOINT == 10000 &&
+ CURLOPTTYPE_FUNCTIONPOINT == 20000 &&
+ CURLOPTTYPE_OFF_T == 30000,
+ "CURL option types are expected to be multiples of 10000");
+
+inline bool VerifyOptionType(CURLoption option, int expected_type) {
+ int option_type = (static_cast<int>(option) / 10000) * 10000;
+ return (option_type == expected_type);
+}
+
+} // anonymous namespace
+
+CurlApi::CurlApi() {
+ curl_global_init(CURL_GLOBAL_ALL);
+}
+
+CurlApi::~CurlApi() {
+ curl_global_cleanup();
+}
+
+CURL* CurlApi::EasyInit() {
+ return curl_easy_init();
+}
+
+void CurlApi::EasyCleanup(CURL* curl) {
+ curl_easy_cleanup(curl);
+}
+
+CURLcode CurlApi::EasySetOptInt(CURL* curl, CURLoption option, int value) {
+ CHECK(VerifyOptionType(option, CURLOPTTYPE_LONG))
+ << "Only options that expect a LONG data type must be specified here";
+ // CURL actually uses "long" type, so have to make sure we feed it what it
+ // expects.
+ // NOLINTNEXTLINE(runtime/int)
+ return curl_easy_setopt(curl, option, static_cast<long>(value));
+}
+
+CURLcode CurlApi::EasySetOptStr(CURL* curl,
+ CURLoption option,
+ const std::string& value) {
+ CHECK(VerifyOptionType(option, CURLOPTTYPE_OBJECTPOINT))
+ << "Only options that expect a STRING data type must be specified here";
+ return curl_easy_setopt(curl, option, value.c_str());
+}
+
+CURLcode CurlApi::EasySetOptPtr(CURL* curl, CURLoption option, void* value) {
+ CHECK(VerifyOptionType(option, CURLOPTTYPE_OBJECTPOINT))
+ << "Only options that expect a pointer data type must be specified here";
+ return curl_easy_setopt(curl, option, value);
+}
+
+CURLcode CurlApi::EasySetOptCallback(CURL* curl,
+ CURLoption option,
+ intptr_t address) {
+ CHECK(VerifyOptionType(option, CURLOPTTYPE_FUNCTIONPOINT))
+ << "Only options that expect a function pointers must be specified here";
+ return curl_easy_setopt(curl, option, address);
+}
+
+CURLcode CurlApi::EasySetOptOffT(CURL* curl,
+ CURLoption option,
+ curl_off_t value) {
+ CHECK(VerifyOptionType(option, CURLOPTTYPE_OFF_T))
+ << "Only options that expect a large data size must be specified here";
+ return curl_easy_setopt(curl, option, value);
+}
+
+CURLcode CurlApi::EasyPerform(CURL* curl) {
+ return curl_easy_perform(curl);
+}
+
+CURLcode CurlApi::EasyGetInfoInt(CURL* curl, CURLINFO info, int* value) const {
+ CHECK_EQ(CURLINFO_LONG, info & CURLINFO_TYPEMASK) << "Wrong option type";
+ long data = 0; // NOLINT(runtime/int) - curl expects a long here.
+ CURLcode code = curl_easy_getinfo(curl, info, &data);
+ if (code == CURLE_OK)
+ *value = static_cast<int>(data);
+ return code;
+}
+
+CURLcode CurlApi::EasyGetInfoDbl(CURL* curl,
+ CURLINFO info,
+ double* value) const {
+ CHECK_EQ(CURLINFO_DOUBLE, info & CURLINFO_TYPEMASK) << "Wrong option type";
+ return curl_easy_getinfo(curl, info, value);
+}
+
+CURLcode CurlApi::EasyGetInfoStr(CURL* curl,
+ CURLINFO info,
+ std::string* value) const {
+ CHECK_EQ(CURLINFO_STRING, info & CURLINFO_TYPEMASK) << "Wrong option type";
+ char* data = nullptr;
+ CURLcode code = curl_easy_getinfo(curl, info, &data);
+ if (code == CURLE_OK)
+ *value = data;
+ return code;
+}
+
+CURLcode CurlApi::EasyGetInfoPtr(CURL* curl,
+ CURLINFO info,
+ void** value) const {
+ // CURL uses "string" type for generic pointer info. Go figure.
+ CHECK_EQ(CURLINFO_STRING, info & CURLINFO_TYPEMASK) << "Wrong option type";
+ return curl_easy_getinfo(curl, info, value);
+}
+
+std::string CurlApi::EasyStrError(CURLcode code) const {
+ return curl_easy_strerror(code);
+}
+
+CURLM* CurlApi::MultiInit() {
+ return curl_multi_init();
+}
+
+CURLMcode CurlApi::MultiCleanup(CURLM* multi_handle) {
+ return curl_multi_cleanup(multi_handle);
+}
+
+CURLMsg* CurlApi::MultiInfoRead(CURLM* multi_handle, int* msgs_in_queue) {
+ return curl_multi_info_read(multi_handle, msgs_in_queue);
+}
+
+CURLMcode CurlApi::MultiAddHandle(CURLM* multi_handle, CURL* curl_handle) {
+ return curl_multi_add_handle(multi_handle, curl_handle);
+}
+
+CURLMcode CurlApi::MultiRemoveHandle(CURLM* multi_handle, CURL* curl_handle) {
+ return curl_multi_remove_handle(multi_handle, curl_handle);
+}
+
+CURLMcode CurlApi::MultiSetSocketCallback(CURLM* multi_handle,
+ curl_socket_callback socket_callback,
+ void* userp) {
+ CURLMcode code =
+ curl_multi_setopt(multi_handle, CURLMOPT_SOCKETFUNCTION, socket_callback);
+ if (code != CURLM_OK)
+ return code;
+ return curl_multi_setopt(multi_handle, CURLMOPT_SOCKETDATA, userp);
+}
+
+CURLMcode CurlApi::MultiSetTimerCallback(
+ CURLM* multi_handle,
+ curl_multi_timer_callback timer_callback,
+ void* userp) {
+ CURLMcode code =
+ curl_multi_setopt(multi_handle, CURLMOPT_TIMERFUNCTION, timer_callback);
+ if (code != CURLM_OK)
+ return code;
+ return curl_multi_setopt(multi_handle, CURLMOPT_TIMERDATA, userp);
+}
+
+CURLMcode CurlApi::MultiAssign(CURLM* multi_handle,
+ curl_socket_t sockfd,
+ void* sockp) {
+ return curl_multi_assign(multi_handle, sockfd, sockp);
+}
+
+CURLMcode CurlApi::MultiSocketAction(CURLM* multi_handle,
+ curl_socket_t s,
+ int ev_bitmask,
+ int* running_handles) {
+ return curl_multi_socket_action(multi_handle, s, ev_bitmask, running_handles);
+}
+
+std::string CurlApi::MultiStrError(CURLMcode code) const {
+ return curl_multi_strerror(code);
+}
+
+CURLMcode CurlApi::MultiPerform(CURLM* multi_handle, int* running_handles) {
+ return curl_multi_perform(multi_handle, running_handles);
+}
+
+CURLMcode CurlApi::MultiWait(CURLM* multi_handle,
+ curl_waitfd extra_fds[],
+ unsigned int extra_nfds,
+ int timeout_ms,
+ int* numfds) {
+ return curl_multi_wait(multi_handle, extra_fds, extra_nfds, timeout_ms,
+ numfds);
+}
+
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/curl_api.h b/libbrillo/brillo/http/curl_api.h
new file mode 100644
index 0000000..8e9001d
--- /dev/null
+++ b/libbrillo/brillo/http/curl_api.h
@@ -0,0 +1,232 @@
+// 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 LIBBRILLO_BRILLO_HTTP_CURL_API_H_
+#define LIBBRILLO_BRILLO_HTTP_CURL_API_H_
+
+#include <curl/curl.h>
+
+#include <string>
+
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+namespace http {
+
+// Abstract wrapper around libcurl C API that allows us to mock it out in tests.
+class CurlInterface {
+ public:
+ CurlInterface() = default;
+ virtual ~CurlInterface() = default;
+
+ // Wrapper around curl_easy_init().
+ virtual CURL* EasyInit() = 0;
+
+ // Wrapper around curl_easy_cleanup().
+ virtual void EasyCleanup(CURL* curl) = 0;
+
+ // Wrappers around curl_easy_setopt().
+ virtual CURLcode EasySetOptInt(CURL* curl, CURLoption option, int value) = 0;
+ virtual CURLcode EasySetOptStr(CURL* curl,
+ CURLoption option,
+ const std::string& value) = 0;
+ virtual CURLcode EasySetOptPtr(CURL* curl,
+ CURLoption option,
+ void* value) = 0;
+ virtual CURLcode EasySetOptCallback(CURL* curl,
+ CURLoption option,
+ intptr_t address) = 0;
+ virtual CURLcode EasySetOptOffT(CURL* curl,
+ CURLoption option,
+ curl_off_t value) = 0;
+
+ // A type-safe wrapper around function callback options.
+ template<typename R, typename... Args>
+ inline CURLcode EasySetOptCallback(CURL* curl,
+ CURLoption option,
+ R(*callback)(Args...)) {
+ return EasySetOptCallback(
+ curl, option, reinterpret_cast<intptr_t>(callback));
+ }
+
+ // Wrapper around curl_easy_perform().
+ virtual CURLcode EasyPerform(CURL* curl) = 0;
+
+ // Wrappers around curl_easy_getinfo().
+ virtual CURLcode EasyGetInfoInt(CURL* curl,
+ CURLINFO info,
+ int* value) const = 0;
+ virtual CURLcode EasyGetInfoDbl(CURL* curl,
+ CURLINFO info,
+ double* value) const = 0;
+ virtual CURLcode EasyGetInfoStr(CURL* curl,
+ CURLINFO info,
+ std::string* value) const = 0;
+ virtual CURLcode EasyGetInfoPtr(CURL* curl,
+ CURLINFO info,
+ void** value) const = 0;
+
+ // Wrapper around curl_easy_strerror().
+ virtual std::string EasyStrError(CURLcode code) const = 0;
+
+ // Wrapper around curl_multi_init().
+ virtual CURLM* MultiInit() = 0;
+
+ // Wrapper around curl_multi_cleanup().
+ virtual CURLMcode MultiCleanup(CURLM* multi_handle) = 0;
+
+ // Wrapper around curl_multi_info_read().
+ virtual CURLMsg* MultiInfoRead(CURLM* multi_handle, int* msgs_in_queue) = 0;
+
+ // Wrapper around curl_multi_add_handle().
+ virtual CURLMcode MultiAddHandle(CURLM* multi_handle, CURL* curl_handle) = 0;
+
+ // Wrapper around curl_multi_remove_handle().
+ virtual CURLMcode MultiRemoveHandle(CURLM* multi_handle,
+ CURL* curl_handle) = 0;
+
+ // Wrapper around curl_multi_setopt(CURLMOPT_SOCKETFUNCTION/SOCKETDATA).
+ virtual CURLMcode MultiSetSocketCallback(
+ CURLM* multi_handle,
+ curl_socket_callback socket_callback,
+ void* userp) = 0;
+
+ // Wrapper around curl_multi_setopt(CURLMOPT_TIMERFUNCTION/TIMERDATA).
+ virtual CURLMcode MultiSetTimerCallback(
+ CURLM* multi_handle,
+ curl_multi_timer_callback timer_callback,
+ void* userp) = 0;
+
+ // Wrapper around curl_multi_assign().
+ virtual CURLMcode MultiAssign(CURLM* multi_handle,
+ curl_socket_t sockfd,
+ void* sockp) = 0;
+
+ // Wrapper around curl_multi_socket_action().
+ virtual CURLMcode MultiSocketAction(CURLM* multi_handle,
+ curl_socket_t s,
+ int ev_bitmask,
+ int* running_handles) = 0;
+
+ // Wrapper around curl_multi_strerror().
+ virtual std::string MultiStrError(CURLMcode code) const = 0;
+
+ // Wrapper around curl_multi_perform().
+ virtual CURLMcode MultiPerform(CURLM* multi_handle,
+ int* running_handles) = 0;
+
+ // Wrapper around curl_multi_wait().
+ virtual CURLMcode MultiWait(CURLM* multi_handle,
+ curl_waitfd extra_fds[],
+ unsigned int extra_nfds,
+ int timeout_ms,
+ int* numfds) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CurlInterface);
+};
+
+class BRILLO_EXPORT CurlApi : public CurlInterface {
+ public:
+ CurlApi();
+ ~CurlApi() override;
+
+ // Wrapper around curl_easy_init().
+ CURL* EasyInit() override;
+
+ // Wrapper around curl_easy_cleanup().
+ void EasyCleanup(CURL* curl) override;
+
+ // Wrappers around curl_easy_setopt().
+ CURLcode EasySetOptInt(CURL* curl, CURLoption option, int value) override;
+ CURLcode EasySetOptStr(CURL* curl,
+ CURLoption option,
+ const std::string& value) override;
+ CURLcode EasySetOptPtr(CURL* curl, CURLoption option, void* value) override;
+ CURLcode EasySetOptCallback(CURL* curl,
+ CURLoption option,
+ intptr_t address) override;
+ CURLcode EasySetOptOffT(CURL* curl,
+ CURLoption option,
+ curl_off_t value) override;
+
+ // Wrapper around curl_easy_perform().
+ CURLcode EasyPerform(CURL* curl) override;
+
+ // Wrappers around curl_easy_getinfo().
+ CURLcode EasyGetInfoInt(CURL* curl, CURLINFO info, int* value) const override;
+ CURLcode EasyGetInfoDbl(CURL* curl,
+ CURLINFO info,
+ double* value) const override;
+ CURLcode EasyGetInfoStr(CURL* curl,
+ CURLINFO info,
+ std::string* value) const override;
+ CURLcode EasyGetInfoPtr(CURL* curl,
+ CURLINFO info,
+ void** value) const override;
+
+ // Wrapper around curl_easy_strerror().
+ std::string EasyStrError(CURLcode code) const override;
+
+ // Wrapper around curl_multi_init().
+ CURLM* MultiInit() override;
+
+ // Wrapper around curl_multi_cleanup().
+ CURLMcode MultiCleanup(CURLM* multi_handle) override;
+
+ // Wrapper around curl_multi_info_read().
+ CURLMsg* MultiInfoRead(CURLM* multi_handle, int* msgs_in_queue) override;
+
+ // Wrapper around curl_multi_add_handle().
+ CURLMcode MultiAddHandle(CURLM* multi_handle, CURL* curl_handle) override;
+
+ // Wrapper around curl_multi_remove_handle().
+ CURLMcode MultiRemoveHandle(CURLM* multi_handle, CURL* curl_handle) override;
+
+ // Wrapper around curl_multi_setopt(CURLMOPT_SOCKETFUNCTION/SOCKETDATA).
+ CURLMcode MultiSetSocketCallback(
+ CURLM* multi_handle,
+ curl_socket_callback socket_callback,
+ void* userp) override;
+
+ // Wrapper around curl_multi_setopt(CURLMOPT_TIMERFUNCTION/TIMERDATA).
+ CURLMcode MultiSetTimerCallback(
+ CURLM* multi_handle,
+ curl_multi_timer_callback timer_callback,
+ void* userp) override;
+
+ // Wrapper around curl_multi_assign().
+ CURLMcode MultiAssign(CURLM* multi_handle,
+ curl_socket_t sockfd,
+ void* sockp) override;
+
+ // Wrapper around curl_multi_socket_action().
+ CURLMcode MultiSocketAction(CURLM* multi_handle,
+ curl_socket_t s,
+ int ev_bitmask,
+ int* running_handles) override;
+
+ // Wrapper around curl_multi_strerror().
+ std::string MultiStrError(CURLMcode code) const override;
+
+ // Wrapper around curl_multi_perform().
+ CURLMcode MultiPerform(CURLM* multi_handle,
+ int* running_handles) override;
+
+ // Wrapper around curl_multi_wait().
+ CURLMcode MultiWait(CURLM* multi_handle,
+ curl_waitfd extra_fds[],
+ unsigned int extra_nfds,
+ int timeout_ms,
+ int* numfds) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CurlApi);
+};
+
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_CURL_API_H_
diff --git a/libbrillo/brillo/http/http_connection.h b/libbrillo/brillo/http/http_connection.h
new file mode 100644
index 0000000..9150929
--- /dev/null
+++ b/libbrillo/brillo/http/http_connection.h
@@ -0,0 +1,108 @@
+// 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 LIBBRILLO_BRILLO_HTTP_HTTP_CONNECTION_H_
+#define LIBBRILLO_BRILLO_HTTP_HTTP_CONNECTION_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+#include <brillo/errors/error.h>
+#include <brillo/http/http_transport.h>
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+namespace http {
+
+class Response;
+
+///////////////////////////////////////////////////////////////////////////////
+// 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.
+// Effectively this class is the interface for the request/response objects to
+// the transport-specific instance of the communication channel with the
+// destination server. It is created by Transport as part of initiating
+// the connection to the destination URI and is shared between the request and
+// response object until all the data is sent to the server and the response
+// is received. The class does NOT represent a persistent TCP connection though
+// (e.g. in keep-alive scenarios).
+///////////////////////////////////////////////////////////////////////////////
+class BRILLO_EXPORT Connection
+ : public std::enable_shared_from_this<Connection> {
+ public:
+ explicit Connection(const std::shared_ptr<Transport>& transport)
+ : transport_(transport) {}
+ virtual ~Connection() = default;
+
+ // The following methods are used by http::Request object to initiate the
+ // communication with the server, send the request data and receive the
+ // response.
+
+ // 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,
+ brillo::ErrorPtr* error) = 0;
+ // If needed, this function can be called to send the request body data.
+ virtual bool SetRequestData(StreamPtr stream, brillo::ErrorPtr* error) = 0;
+ // If needed, this function can be called to customize where the response
+ // data is streamed to.
+ virtual void SetResponseData(StreamPtr stream) = 0;
+ // This function is called when all the data is sent off and it's time
+ // to receive the response data. The method will block until the whole
+ // response message is received, or if an error occur in which case the
+ // function will return false and fill the error details in |error| object.
+ virtual bool FinishRequest(brillo::ErrorPtr* error) = 0;
+ // Send the request asynchronously and invoke the callback with the response
+ // received. Returns the ID of the pending async request.
+ virtual RequestID FinishRequestAsync(const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) = 0;
+
+ // The following methods are used by http::Response object to obtain the
+ // response data. They are used only after the response data has been received
+ // since the http::Response object is not constructed until
+ // Request::GetResponse()/Request::GetResponseAndBlock() methods are called.
+
+ // 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 stream. This function can be called only once
+ // as it transfers ownership of the data stream to the caller. Subsequent
+ // calls will fail with "Stream closed" error.
+ // Returns empty stream on failure and fills in the error information in
+ // |error| object.
+ virtual StreamPtr ExtractDataStream(brillo::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 brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_HTTP_CONNECTION_H_
diff --git a/libbrillo/brillo/http/http_connection_curl.cc b/libbrillo/brillo/http/http_connection_curl.cc
new file mode 100644
index 0000000..3720330
--- /dev/null
+++ b/libbrillo/brillo/http/http_connection_curl.cc
@@ -0,0 +1,268 @@
+// 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 <brillo/http/http_connection_curl.h>
+
+#include <base/logging.h>
+#include <brillo/http/http_request.h>
+#include <brillo/http/http_transport_curl.h>
+#include <brillo/streams/memory_stream.h>
+#include <brillo/streams/stream_utils.h>
+#include <brillo/strings/string_utils.h>
+
+namespace brillo {
+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,
+ const std::shared_ptr<CurlInterface>& curl_interface,
+ const std::shared_ptr<http::Transport>& transport)
+ : http::Connection(transport),
+ method_(method),
+ curl_handle_(curl_handle),
+ curl_interface_(curl_interface) {
+ // Store the connection pointer inside the CURL handle so we can easily
+ // retrieve it when doing asynchronous I/O.
+ curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_PRIVATE, this);
+ VLOG(2) << "curl::Connection created: " << method_;
+}
+
+Connection::~Connection() {
+ if (header_list_)
+ curl_slist_free_all(header_list_);
+ curl_interface_->EasyCleanup(curl_handle_);
+ VLOG(2) << "curl::Connection destroyed";
+}
+
+bool Connection::SendHeaders(const HeaderList& headers,
+ brillo::ErrorPtr* /* error */) {
+ headers_.insert(headers.begin(), headers.end());
+ return true;
+}
+
+bool Connection::SetRequestData(StreamPtr stream,
+ brillo::ErrorPtr* /* error */) {
+ request_data_stream_ = std::move(stream);
+ return true;
+}
+
+void Connection::SetResponseData(StreamPtr stream) {
+ response_data_stream_ = std::move(stream);
+}
+
+void Connection::PrepareRequest() {
+ if (VLOG_IS_ON(3)) {
+ curl_interface_->EasySetOptCallback(
+ curl_handle_, CURLOPT_DEBUGFUNCTION, &curl_trace);
+ curl_interface_->EasySetOptInt(curl_handle_, CURLOPT_VERBOSE, 1);
+ }
+
+ if (method_ != request_type::kGet) {
+ // Set up HTTP request data.
+ uint64_t data_size = 0;
+ if (request_data_stream_ && request_data_stream_->CanGetSize())
+ data_size = request_data_stream_->GetRemainingSize();
+
+ if (!request_data_stream_ || request_data_stream_->CanGetSize()) {
+ // Data size is known (either no data, or data size is available).
+ if (method_ == request_type::kPut) {
+ curl_interface_->EasySetOptOffT(
+ curl_handle_, CURLOPT_INFILESIZE_LARGE, data_size);
+ } else {
+ curl_interface_->EasySetOptOffT(
+ curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE, data_size);
+ }
+ } else {
+ // Data size is unknown, so use chunked upload.
+ headers_.emplace(http::request_header::kTransferEncoding, "chunked");
+ }
+
+ if (request_data_stream_) {
+ curl_interface_->EasySetOptCallback(
+ curl_handle_, CURLOPT_READFUNCTION, &Connection::read_callback);
+ curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_READDATA, this);
+ }
+ }
+
+ if (!headers_.empty()) {
+ CHECK(header_list_ == nullptr);
+ for (auto pair : headers_) {
+ std::string header =
+ brillo::string_utils::Join(": ", pair.first, pair.second);
+ VLOG(2) << "Request header: " << header;
+ header_list_ = curl_slist_append(header_list_, header.c_str());
+ }
+ curl_interface_->EasySetOptPtr(
+ curl_handle_, CURLOPT_HTTPHEADER, header_list_);
+ }
+
+ headers_.clear();
+
+ // Set up HTTP response data.
+ if (!response_data_stream_)
+ response_data_stream_ = MemoryStream::Create(nullptr);
+ if (method_ != request_type::kHead) {
+ curl_interface_->EasySetOptCallback(
+ curl_handle_, CURLOPT_WRITEFUNCTION, &Connection::write_callback);
+ curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_WRITEDATA, this);
+ }
+
+ // HTTP response headers
+ curl_interface_->EasySetOptCallback(
+ curl_handle_, CURLOPT_HEADERFUNCTION, &Connection::header_callback);
+ curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_HEADERDATA, this);
+}
+
+bool Connection::FinishRequest(brillo::ErrorPtr* error) {
+ PrepareRequest();
+ CURLcode ret = curl_interface_->EasyPerform(curl_handle_);
+ if (ret != CURLE_OK) {
+ Transport::AddEasyCurlError(error, FROM_HERE, ret, curl_interface_.get());
+ } else {
+ // Rewind our data stream to the beginning so that it can be read back.
+ if (response_data_stream_->CanSeek() &&
+ !response_data_stream_->SetPosition(0, error))
+ return false;
+ LOG(INFO) << "Response: " << GetResponseStatusCode() << " ("
+ << GetResponseStatusText() << ")";
+ }
+ return (ret == CURLE_OK);
+}
+
+RequestID Connection::FinishRequestAsync(
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ PrepareRequest();
+ return transport_->StartAsyncTransfer(this, success_callback, error_callback);
+}
+
+int Connection::GetResponseStatusCode() const {
+ int status_code = 0;
+ curl_interface_->EasyGetInfoInt(
+ 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();
+}
+
+StreamPtr Connection::ExtractDataStream(brillo::ErrorPtr* error) {
+ if (!response_data_stream_) {
+ stream_utils::ErrorStreamClosed(FROM_HERE, error);
+ }
+ return std::move(response_data_stream_);
+}
+
+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;
+ VLOG(1) << "Response data (" << data_len << "): "
+ << std::string{ptr, data_len};
+ // TODO(nathanbullock): Currently we are relying on the stream not blocking,
+ // but if the stream is representing a pipe or some other construct that might
+ // block then this code will behave badly.
+ if (!me->response_data_stream_->WriteAllBlocking(ptr, data_len, nullptr)) {
+ LOG(ERROR) << "Failed to write response data";
+ data_len = 0;
+ }
+ 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;
+
+ size_t read_size = 0;
+ bool success = me->request_data_stream_->ReadBlocking(ptr, data_len,
+ &read_size, nullptr);
+ VLOG_IF(3, success) << "Sending data: " << std::string{ptr, read_size};
+ return success ? read_size : CURL_READFUNC_ABORT;
+}
+
+size_t Connection::header_callback(char* ptr,
+ size_t size,
+ size_t num,
+ void* data) {
+ using brillo::string_utils::SplitAtFirst;
+ 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 = SplitAtFirst(header, " ");
+ me->protocol_version_ = pair.first;
+ me->status_text_ = SplitAtFirst(pair.second, " ").second;
+ me->status_text_set_ = true;
+ } else {
+ auto pair = SplitAtFirst(header, ":");
+ if (!pair.second.empty())
+ me->headers_.insert(pair);
+ }
+ return hdr_len;
+}
+
+} // namespace curl
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_connection_curl.h b/libbrillo/brillo/http/http_connection_curl.h
new file mode 100644
index 0000000..c34de57
--- /dev/null
+++ b/libbrillo/brillo/http/http_connection_curl.h
@@ -0,0 +1,103 @@
+// 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 LIBBRILLO_BRILLO_HTTP_HTTP_CONNECTION_CURL_H_
+#define LIBBRILLO_BRILLO_HTTP_HTTP_CONNECTION_CURL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+#include <brillo/http/http_connection.h>
+#include <brillo/http/http_transport_curl.h>
+#include <curl/curl.h>
+
+namespace brillo {
+namespace http {
+namespace curl {
+
+// This is a libcurl-based implementation of http::Connection.
+class BRILLO_EXPORT Connection : public http::Connection {
+ public:
+ Connection(CURL* curl_handle,
+ const std::string& method,
+ const std::shared_ptr<CurlInterface>& curl_interface,
+ const std::shared_ptr<http::Transport>& transport);
+ ~Connection() override;
+
+ // Overrides from http::Connection.
+ // See http_connection.h for description of these methods.
+ bool SendHeaders(const HeaderList& headers, brillo::ErrorPtr* error) override;
+ bool SetRequestData(StreamPtr stream, brillo::ErrorPtr* error) override;
+ void SetResponseData(StreamPtr stream) override;
+ bool FinishRequest(brillo::ErrorPtr* error) override;
+ RequestID FinishRequestAsync(
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) override;
+
+ int GetResponseStatusCode() const override;
+ std::string GetResponseStatusText() const override;
+ std::string GetProtocolVersion() const override;
+ std::string GetResponseHeader(const std::string& header_name) const override;
+ StreamPtr ExtractDataStream(brillo::ErrorPtr* error) override;
+
+ protected:
+ // Write data callback. Used by CURL when receiving response data.
+ BRILLO_PRIVATE 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.
+ BRILLO_PRIVATE 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.
+ BRILLO_PRIVATE static size_t header_callback(char* ptr,
+ size_t size,
+ size_t num,
+ void* data);
+
+ // Helper method to set up the |curl_handle_| with all the parameters
+ // pertaining to the current connection.
+ BRILLO_PRIVATE void PrepareRequest();
+
+ // HTTP request verb, such as "GET", "POST", "PUT", ...
+ std::string method_;
+
+ // Binary data for request body.
+ StreamPtr request_data_stream_;
+
+ // Received response data.
+ StreamPtr response_data_stream_;
+
+ // List of optional request headers provided by the caller.
+ // After request has been sent, contains the received response headers.
+ std::multimap<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};
+ curl_slist* header_list_{nullptr};
+
+ std::shared_ptr<CurlInterface> curl_interface_;
+
+ private:
+ friend class http::curl::Transport;
+ DISALLOW_COPY_AND_ASSIGN(Connection);
+};
+
+} // namespace curl
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_HTTP_CONNECTION_CURL_H_
diff --git a/libbrillo/brillo/http/http_connection_curl_unittest.cc b/libbrillo/brillo/http/http_connection_curl_unittest.cc
new file mode 100644
index 0000000..90a5626
--- /dev/null
+++ b/libbrillo/brillo/http/http_connection_curl_unittest.cc
@@ -0,0 +1,324 @@
+// 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 <brillo/http/http_connection_curl.h>
+
+#include <algorithm>
+#include <set>
+
+#include <base/callback.h>
+#include <brillo/http/http_request.h>
+#include <brillo/http/http_transport.h>
+#include <brillo/http/mock_curl_api.h>
+#include <brillo/http/mock_transport.h>
+#include <brillo/streams/memory_stream.h>
+#include <brillo/streams/mock_stream.h>
+#include <brillo/strings/string_utils.h>
+#include <brillo/mime_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::DoAll;
+using testing::Invoke;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace brillo {
+namespace http {
+namespace curl {
+
+namespace {
+
+using ReadWriteCallback =
+ size_t(char* ptr, size_t size, size_t num, void* data);
+
+// A helper class to simulate curl_easy_perform action. It invokes the
+// read callbacks to obtain the request data from the Connection and then
+// calls the header and write callbacks to "send" the response header and body.
+class CurlPerformer {
+ public:
+ // During the tests, use the address of |this| as the CURL* handle.
+ // This allows the static Perform() method to obtain the instance pointer
+ // having only CURL*.
+ CURL* GetCurlHandle() { return reinterpret_cast<CURL*>(this); }
+
+ // Callback to be invoked when mocking out curl_easy_perform() method.
+ static CURLcode Perform(CURL* curl) {
+ CurlPerformer* me = reinterpret_cast<CurlPerformer*>(curl);
+ return me->DoPerform();
+ }
+
+ // CURL callback functions and |connection| pointer needed to invoke the
+ // callbacks from the Connection class.
+ Connection* connection{nullptr};
+ ReadWriteCallback* write_callback{nullptr};
+ ReadWriteCallback* read_callback{nullptr};
+ ReadWriteCallback* header_callback{nullptr};
+
+ // Request body read from the connection.
+ std::string request_body;
+
+ // Response data to be sent back to connection.
+ std::string status_line;
+ HeaderList response_headers;
+ std::string response_body;
+
+ private:
+ // The actual implementation of curl_easy_perform() fake.
+ CURLcode DoPerform() {
+ // Read request body.
+ char buffer[1024];
+ for (;;) {
+ size_t size_read = read_callback(buffer, sizeof(buffer), 1, connection);
+ if (size_read == CURL_READFUNC_ABORT)
+ return CURLE_ABORTED_BY_CALLBACK;
+ if (size_read == CURL_READFUNC_PAUSE)
+ return CURLE_READ_ERROR; // Shouldn't happen.
+ if (size_read == 0)
+ break;
+ request_body.append(buffer, size_read);
+ }
+
+ // Send the response headers.
+ std::vector<std::string> header_lines;
+ header_lines.push_back(status_line + "\r\n");
+ for (const auto& pair : response_headers) {
+ header_lines.push_back(string_utils::Join(": ", pair.first, pair.second) +
+ "\r\n");
+ }
+
+ for (const std::string& line : header_lines) {
+ CURLcode code = WriteString(header_callback, line);
+ if (code != CURLE_OK)
+ return code;
+ }
+
+ // Send response body.
+ return WriteString(write_callback, response_body);
+ }
+
+ // Helper method to send a string to a write callback. Keeps calling
+ // the callback until all the data is written.
+ CURLcode WriteString(ReadWriteCallback* callback, const std::string& str) {
+ size_t pos = 0;
+ size_t size_remaining = str.size();
+ while (size_remaining) {
+ size_t size_written = callback(
+ const_cast<char*>(str.data() + pos), size_remaining, 1, connection);
+ if (size_written == CURL_WRITEFUNC_PAUSE)
+ return CURLE_WRITE_ERROR; // Shouldn't happen.
+ CHECK(size_written <= size_remaining) << "Unexpected size returned";
+ size_remaining -= size_written;
+ pos += size_written;
+ }
+ return CURLE_OK;
+ }
+};
+
+// Custom matcher to validate the parameter of CURLOPT_HTTPHEADER CURL option
+// which contains the request headers as curl_slist* chain.
+MATCHER_P(HeadersMatch, headers, "") {
+ std::multiset<std::string> test_headers;
+ for (const auto& pair : headers)
+ test_headers.insert(string_utils::Join(": ", pair.first, pair.second));
+
+ std::multiset<std::string> src_headers;
+ const curl_slist* head = static_cast<const curl_slist*>(arg);
+ while (head) {
+ src_headers.insert(head->data);
+ head = head->next;
+ }
+
+ std::vector<std::string> difference;
+ std::set_symmetric_difference(src_headers.begin(), src_headers.end(),
+ test_headers.begin(), test_headers.end(),
+ std::back_inserter(difference));
+ return difference.empty();
+}
+
+// Custom action to save a CURL callback pointer into a member of CurlPerformer.
+ACTION_TEMPLATE(SaveCallback,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_2_VALUE_PARAMS(performer, mem_ptr)) {
+ performer->*mem_ptr = reinterpret_cast<ReadWriteCallback*>(std::get<k>(args));
+}
+
+} // anonymous namespace
+
+class HttpCurlConnectionTest : public testing::Test {
+ public:
+ void SetUp() override {
+ curl_api_ = std::make_shared<MockCurlInterface>();
+ transport_ = std::make_shared<MockTransport>();
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _))
+ .WillOnce(Return(CURLE_OK));
+ connection_ = std::make_shared<Connection>(
+ handle_, request_type::kPost, curl_api_, transport_);
+ performer_.connection = connection_.get();
+ }
+
+ void TearDown() override {
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection_.reset();
+ transport_.reset();
+ curl_api_.reset();
+ }
+
+ protected:
+ std::shared_ptr<MockCurlInterface> curl_api_;
+ std::shared_ptr<MockTransport> transport_;
+ CurlPerformer performer_;
+ CURL* handle_{performer_.GetCurlHandle()};
+ std::shared_ptr<Connection> connection_;
+};
+
+TEST_F(HttpCurlConnectionTest, FinishRequestAsync) {
+ std::string request_data{"Foo Bar Baz"};
+ StreamPtr stream = MemoryStream::OpenRef(request_data, nullptr);
+ EXPECT_TRUE(connection_->SetRequestData(std::move(stream), nullptr));
+ EXPECT_TRUE(connection_->SendHeaders({{"X-Foo", "bar"}}, nullptr));
+
+ if (VLOG_IS_ON(3)) {
+ EXPECT_CALL(*curl_api_,
+ EasySetOptCallback(handle_, CURLOPT_DEBUGFUNCTION, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_VERBOSE, 1))
+ .WillOnce(Return(CURLE_OK));
+ }
+
+ EXPECT_CALL(
+ *curl_api_,
+ EasySetOptOffT(handle_, CURLOPT_POSTFIELDSIZE_LARGE, request_data.size()))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_READFUNCTION, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_READDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HTTPHEADER, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_WRITEFUNCTION, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_WRITEDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_,
+ EasySetOptCallback(handle_, CURLOPT_HEADERFUNCTION, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HEADERDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*transport_, StartAsyncTransfer(connection_.get(), _, _))
+ .Times(1);
+ connection_->FinishRequestAsync({}, {});
+}
+
+MATCHER_P(MatchStringBuffer, data, "") {
+ return data.compare(static_cast<const char*>(arg)) == 0;
+}
+
+TEST_F(HttpCurlConnectionTest, FinishRequest) {
+ std::string request_data{"Foo Bar Baz"};
+ std::string response_data{"<html><body>OK</body></html>"};
+ StreamPtr stream = MemoryStream::OpenRef(request_data, nullptr);
+ HeaderList headers{
+ {request_header::kAccept, "*/*"},
+ {request_header::kContentType, mime::text::kPlain},
+ {request_header::kContentLength, std::to_string(request_data.size())},
+ {"X-Foo", "bar"},
+ };
+ std::unique_ptr<MockStream> response_stream(new MockStream);
+ EXPECT_CALL(*response_stream,
+ WriteAllBlocking(MatchStringBuffer(response_data),
+ response_data.size(), _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*response_stream, CanSeek())
+ .WillOnce(Return(false));
+ connection_->SetResponseData(std::move(response_stream));
+ EXPECT_TRUE(connection_->SetRequestData(std::move(stream), nullptr));
+ EXPECT_TRUE(connection_->SendHeaders(headers, nullptr));
+
+ // Expectations for Connection::FinishRequest() call.
+ if (VLOG_IS_ON(3)) {
+ EXPECT_CALL(*curl_api_,
+ EasySetOptCallback(handle_, CURLOPT_DEBUGFUNCTION, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_VERBOSE, 1))
+ .WillOnce(Return(CURLE_OK));
+ }
+
+ EXPECT_CALL(
+ *curl_api_,
+ EasySetOptOffT(handle_, CURLOPT_POSTFIELDSIZE_LARGE, request_data.size()))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_READFUNCTION, _))
+ .WillOnce(
+ DoAll(SaveCallback<2>(&performer_, &CurlPerformer::read_callback),
+ Return(CURLE_OK)));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_READDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_,
+ EasySetOptPtr(handle_, CURLOPT_HTTPHEADER, HeadersMatch(headers)))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_WRITEFUNCTION, _))
+ .WillOnce(
+ DoAll(SaveCallback<2>(&performer_, &CurlPerformer::write_callback),
+ Return(CURLE_OK)));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_WRITEDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_,
+ EasySetOptCallback(handle_, CURLOPT_HEADERFUNCTION, _))
+ .WillOnce(
+ DoAll(SaveCallback<2>(&performer_, &CurlPerformer::header_callback),
+ Return(CURLE_OK)));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HEADERDATA, _))
+ .WillOnce(Return(CURLE_OK));
+
+ EXPECT_CALL(*curl_api_, EasyPerform(handle_))
+ .WillOnce(Invoke(&CurlPerformer::Perform));
+
+ EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _))
+ .WillOnce(DoAll(SetArgPointee<2>(status_code::Ok), Return(CURLE_OK)));
+
+ // Set up the CurlPerformer with the response data expected to be received.
+ HeaderList response_headers{
+ {response_header::kContentLength, std::to_string(response_data.size())},
+ {response_header::kContentType, mime::text::kHtml},
+ {"X-Foo", "baz"},
+ };
+ performer_.status_line = "HTTP/1.1 200 OK";
+ performer_.response_body = response_data;
+ performer_.response_headers = response_headers;
+
+ // Perform the request.
+ EXPECT_TRUE(connection_->FinishRequest(nullptr));
+
+ // Make sure we sent out the request body correctly.
+ EXPECT_EQ(request_data, performer_.request_body);
+
+ // Validate the parsed response data.
+ EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _))
+ .WillOnce(DoAll(SetArgPointee<2>(status_code::Ok), Return(CURLE_OK)));
+ EXPECT_EQ(status_code::Ok, connection_->GetResponseStatusCode());
+ EXPECT_EQ("HTTP/1.1", connection_->GetProtocolVersion());
+ EXPECT_EQ("OK", connection_->GetResponseStatusText());
+ EXPECT_EQ(std::to_string(response_data.size()),
+ connection_->GetResponseHeader(response_header::kContentLength));
+ EXPECT_EQ(mime::text::kHtml,
+ connection_->GetResponseHeader(response_header::kContentType));
+ EXPECT_EQ("baz", connection_->GetResponseHeader("X-Foo"));
+ auto data_stream = connection_->ExtractDataStream(nullptr);
+ ASSERT_NE(nullptr, data_stream.get());
+}
+
+} // namespace curl
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_connection_fake.cc b/libbrillo/brillo/http/http_connection_fake.cc
new file mode 100644
index 0000000..15e5181
--- /dev/null
+++ b/libbrillo/brillo/http/http_connection_fake.cc
@@ -0,0 +1,120 @@
+// 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 <brillo/http/http_connection_fake.h>
+
+#include <base/logging.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/http/http_request.h>
+#include <brillo/mime_utils.h>
+#include <brillo/streams/memory_stream.h>
+#include <brillo/strings/string_utils.h>
+
+namespace brillo {
+namespace http {
+namespace fake {
+
+Connection::Connection(const std::string& url,
+ const std::string& method,
+ const 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,
+ brillo::ErrorPtr* /* error */) {
+ request_.AddHeaders(headers);
+ return true;
+}
+
+bool Connection::SetRequestData(StreamPtr stream,
+ brillo::ErrorPtr* /* error */) {
+ request_.SetData(std::move(stream));
+ return true;
+}
+
+bool Connection::FinishRequest(brillo::ErrorPtr* /* error */) {
+ using brillo::string_utils::ToString;
+ request_.AddHeaders(
+ {{request_header::kContentLength, 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>",
+ brillo::mime::text::kHtml);
+ } else {
+ handler.Run(request_, &response_);
+ }
+ return true;
+}
+
+RequestID Connection::FinishRequestAsync(
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ // Make sure the produced Closure holds a reference to the instance of this
+ // connection.
+ auto connection = std::static_pointer_cast<Connection>(shared_from_this());
+ auto callback = [](std::shared_ptr<Connection> connection,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ connection->FinishRequestAsyncHelper(success_callback, error_callback);
+ };
+ transport_->RunCallbackAsync(FROM_HERE,
+ base::Bind(callback,
+ base::Passed(&connection),
+ success_callback,
+ error_callback));
+ return 1;
+}
+
+void Connection::FinishRequestAsyncHelper(
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ brillo::ErrorPtr error;
+ if (!FinishRequest(&error)) {
+ error_callback.Run(1, error.get());
+ } else {
+ std::unique_ptr<Response> response{new Response{shared_from_this()}};
+ success_callback.Run(1, std::move(response));
+ }
+}
+
+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);
+}
+
+StreamPtr Connection::ExtractDataStream(brillo::ErrorPtr* error) {
+ // HEAD requests must not return body.
+ if (request_.GetMethod() != request_type::kHead) {
+ return MemoryStream::OpenRef(response_.GetData(), error);
+ } else {
+ // Return empty data stream for HEAD requests.
+ return MemoryStream::OpenCopyOf(nullptr, 0, error);
+ }
+}
+
+} // namespace fake
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_connection_fake.h b/libbrillo/brillo/http/http_connection_fake.h
new file mode 100644
index 0000000..a6ebeee
--- /dev/null
+++ b/libbrillo/brillo/http/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 LIBBRILLO_BRILLO_HTTP_HTTP_CONNECTION_FAKE_H_
+#define LIBBRILLO_BRILLO_HTTP_HTTP_CONNECTION_FAKE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/http/http_connection.h>
+#include <brillo/http/http_transport_fake.h>
+
+namespace brillo {
+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,
+ const std::shared_ptr<http::Transport>& transport);
+ ~Connection() override;
+
+ // Overrides from http::Connection.
+ // See http_connection.h for description of these methods.
+ bool SendHeaders(const HeaderList& headers, brillo::ErrorPtr* error) override;
+ bool SetRequestData(StreamPtr stream, brillo::ErrorPtr* error) override;
+ void SetResponseData(StreamPtr /* stream */) override {}
+ bool FinishRequest(brillo::ErrorPtr* error) override;
+ RequestID FinishRequestAsync(const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) override;
+
+ int GetResponseStatusCode() const override;
+ std::string GetResponseStatusText() const override;
+ std::string GetProtocolVersion() const override;
+ std::string GetResponseHeader(const std::string& header_name) const override;
+ StreamPtr ExtractDataStream(brillo::ErrorPtr* error) override;
+
+ private:
+ // A helper method for FinishRequestAsync() implementation.
+ void FinishRequestAsyncHelper(const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+ // 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(Connection);
+};
+
+} // namespace fake
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_HTTP_CONNECTION_FAKE_H_
diff --git a/libbrillo/brillo/http/http_form_data.cc b/libbrillo/brillo/http/http_form_data.cc
new file mode 100644
index 0000000..4d8f6f0
--- /dev/null
+++ b/libbrillo/brillo/http/http_form_data.cc
@@ -0,0 +1,221 @@
+// 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 <brillo/http/http_form_data.h>
+
+#include <limits>
+
+#include <base/format_macros.h>
+#include <base/rand_util.h>
+#include <base/strings/stringprintf.h>
+
+#include <brillo/errors/error_codes.h>
+#include <brillo/http/http_transport.h>
+#include <brillo/mime_utils.h>
+#include <brillo/streams/file_stream.h>
+#include <brillo/streams/input_stream_set.h>
+#include <brillo/streams/memory_stream.h>
+
+namespace brillo {
+namespace http {
+
+namespace form_header {
+const char kContentDisposition[] = "Content-Disposition";
+const char kContentTransferEncoding[] = "Content-Transfer-Encoding";
+const char kContentType[] = "Content-Type";
+} // namespace form_header
+
+const char content_disposition::kFile[] = "file";
+const char content_disposition::kFormData[] = "form-data";
+
+FormField::FormField(const std::string& name,
+ const std::string& content_disposition,
+ const std::string& content_type,
+ const std::string& transfer_encoding)
+ : name_{name},
+ content_disposition_{content_disposition},
+ content_type_{content_type},
+ transfer_encoding_{transfer_encoding} {
+}
+
+std::string FormField::GetContentDisposition() const {
+ std::string disposition = content_disposition_;
+ if (!name_.empty())
+ base::StringAppendF(&disposition, "; name=\"%s\"", name_.c_str());
+ return disposition;
+}
+
+std::string FormField::GetContentType() const {
+ return content_type_;
+}
+
+std::string FormField::GetContentHeader() const {
+ HeaderList headers{
+ {form_header::kContentDisposition, GetContentDisposition()}
+ };
+
+ if (!content_type_.empty())
+ headers.emplace_back(form_header::kContentType, GetContentType());
+
+ if (!transfer_encoding_.empty()) {
+ headers.emplace_back(form_header::kContentTransferEncoding,
+ transfer_encoding_);
+ }
+
+ std::string result;
+ for (const auto& pair : headers) {
+ base::StringAppendF(
+ &result, "%s: %s\r\n", pair.first.c_str(), pair.second.c_str());
+ }
+ result += "\r\n";
+ return result;
+}
+
+TextFormField::TextFormField(const std::string& name,
+ const std::string& data,
+ const std::string& content_type,
+ const std::string& transfer_encoding)
+ : FormField{name,
+ content_disposition::kFormData,
+ content_type,
+ transfer_encoding},
+ data_{data} {
+}
+
+bool TextFormField::ExtractDataStreams(std::vector<StreamPtr>* streams) {
+ streams->push_back(MemoryStream::OpenCopyOf(data_, nullptr));
+ return true;
+}
+
+FileFormField::FileFormField(const std::string& name,
+ StreamPtr stream,
+ const std::string& file_name,
+ const std::string& content_disposition,
+ const std::string& content_type,
+ const std::string& transfer_encoding)
+ : FormField{name, content_disposition, content_type, transfer_encoding},
+ stream_{std::move(stream)},
+ file_name_{file_name} {
+}
+
+std::string FileFormField::GetContentDisposition() const {
+ std::string disposition = FormField::GetContentDisposition();
+ base::StringAppendF(&disposition, "; filename=\"%s\"", file_name_.c_str());
+ return disposition;
+}
+
+bool FileFormField::ExtractDataStreams(std::vector<StreamPtr>* streams) {
+ if (!stream_)
+ return false;
+ streams->push_back(std::move(stream_));
+ return true;
+}
+
+MultiPartFormField::MultiPartFormField(const std::string& name,
+ const std::string& content_type,
+ const std::string& boundary)
+ : FormField{name,
+ content_disposition::kFormData,
+ content_type.empty() ? mime::multipart::kMixed : content_type,
+ {}},
+ boundary_{boundary} {
+ if (boundary_.empty())
+ boundary_ = base::StringPrintf("%016" PRIx64, base::RandUint64());
+}
+
+bool MultiPartFormField::ExtractDataStreams(std::vector<StreamPtr>* streams) {
+ for (auto& part : parts_) {
+ std::string data = GetBoundaryStart() + part->GetContentHeader();
+ streams->push_back(MemoryStream::OpenCopyOf(data, nullptr));
+ if (!part->ExtractDataStreams(streams))
+ return false;
+
+ streams->push_back(MemoryStream::OpenRef("\r\n", nullptr));
+ }
+ if (!parts_.empty()) {
+ std::string data = GetBoundaryEnd();
+ streams->push_back(MemoryStream::OpenCopyOf(data, nullptr));
+ }
+ return true;
+}
+
+std::string MultiPartFormField::GetContentType() const {
+ return base::StringPrintf(
+ "%s; boundary=\"%s\"", content_type_.c_str(), boundary_.c_str());
+}
+
+void MultiPartFormField::AddCustomField(std::unique_ptr<FormField> field) {
+ parts_.push_back(std::move(field));
+}
+
+void MultiPartFormField::AddTextField(const std::string& name,
+ const std::string& data) {
+ AddCustomField(std::unique_ptr<FormField>{new TextFormField{name, data}});
+}
+
+bool MultiPartFormField::AddFileField(const std::string& name,
+ const base::FilePath& file_path,
+ const std::string& content_disposition,
+ const std::string& content_type,
+ brillo::ErrorPtr* error) {
+ StreamPtr stream = FileStream::Open(file_path, Stream::AccessMode::READ,
+ FileStream::Disposition::OPEN_EXISTING,
+ error);
+ if (!stream)
+ return false;
+ std::string file_name = file_path.BaseName().value();
+ std::unique_ptr<FormField> file_field{new FileFormField{name,
+ std::move(stream),
+ file_name,
+ content_disposition,
+ content_type,
+ "binary"}};
+ AddCustomField(std::move(file_field));
+ return true;
+}
+
+std::string MultiPartFormField::GetBoundaryStart() const {
+ return base::StringPrintf("--%s\r\n", boundary_.c_str());
+}
+
+std::string MultiPartFormField::GetBoundaryEnd() const {
+ return base::StringPrintf("--%s--", boundary_.c_str());
+}
+
+FormData::FormData() : FormData{std::string{}} {
+}
+
+FormData::FormData(const std::string& boundary)
+ : form_data_{"", mime::multipart::kFormData, boundary} {
+}
+
+void FormData::AddCustomField(std::unique_ptr<FormField> field) {
+ form_data_.AddCustomField(std::move(field));
+}
+
+void FormData::AddTextField(const std::string& name, const std::string& data) {
+ form_data_.AddTextField(name, data);
+}
+
+bool FormData::AddFileField(const std::string& name,
+ const base::FilePath& file_path,
+ const std::string& content_type,
+ brillo::ErrorPtr* error) {
+ return form_data_.AddFileField(
+ name, file_path, content_disposition::kFormData, content_type, error);
+}
+
+std::string FormData::GetContentType() const {
+ return form_data_.GetContentType();
+}
+
+StreamPtr FormData::ExtractDataStream() {
+ std::vector<StreamPtr> source_streams;
+ if (form_data_.ExtractDataStreams(&source_streams))
+ return InputStreamSet::Create(std::move(source_streams), nullptr);
+ return {};
+}
+
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_form_data.h b/libbrillo/brillo/http/http_form_data.h
new file mode 100644
index 0000000..e12d3d8
--- /dev/null
+++ b/libbrillo/brillo/http/http_form_data.h
@@ -0,0 +1,233 @@
+// 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 LIBBRILLO_BRILLO_HTTP_HTTP_FORM_DATA_H_
+#define LIBBRILLO_BRILLO_HTTP_HTTP_FORM_DATA_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+#include <brillo/errors/error.h>
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+namespace http {
+
+namespace content_disposition {
+BRILLO_EXPORT extern const char kFormData[];
+BRILLO_EXPORT extern const char kFile[];
+} // namespace content_disposition
+
+// An abstract base class for all types of form fields used by FormData class.
+// This class represents basic information about a form part in
+// multipart/form-data and multipart/mixed content.
+// For more details on multipart content, see the following RFC:
+// http://www.ietf.org/rfc/rfc2388
+// For more details on MIME and content headers, see the following RFC:
+// http://www.ietf.org/rfc/rfc2045
+class BRILLO_EXPORT FormField {
+ public:
+ // The constructor that takes the basic data part information common to
+ // all part types. An example of part's headers could include:
+ //
+ // Content-Disposition: form-data; name="field1"
+ // Content-Type: text/plain;charset=windows-1250
+ // Content-Transfer-Encoding: quoted-printable
+ //
+ // The constructor parameters correspond to the basic part attributes:
+ // |name| = the part name ("name" parameter of Content-Disposition header;
+ // "field1" in the example above)
+ // |content_disposition| = the part disposition ("form-data" in the example)
+ // |content_type| = the content type ("text/plain;charset=windows-1250")
+ // |transfer_encoding| = the encoding type for transport ("quoted-printable")
+ // See http://www.ietf.org/rfc/rfc2045, section 6.1
+ FormField(const std::string& name,
+ const std::string& content_disposition,
+ const std::string& content_type,
+ const std::string& transfer_encoding);
+ virtual ~FormField() = default;
+
+ // Returns the full Content-Disposition header value. This might include the
+ // disposition type itself as well as the field "name" and/or "filename"
+ // parameters.
+ virtual std::string GetContentDisposition() const;
+
+ // Returns the full content type of field data. MultiPartFormField overloads
+ // this method to append "boundary" parameter to it.
+ virtual std::string GetContentType() const;
+
+ // Returns a string with all of the field headers, delimited by CRLF
+ // characters ("\r\n").
+ std::string GetContentHeader() const;
+
+ // Adds the data stream(s) to the list of streams to read from.
+ // This is a potentially destructive operation and can be guaranteed to
+ // succeed only on the first try. Subsequent calls will fail for certain
+ // types of form fields.
+ virtual bool ExtractDataStreams(std::vector<StreamPtr>* streams) = 0;
+
+ protected:
+ // Form field name. If not empty, it will be appended to Content-Disposition
+ // field header using "name" attribute.
+ std::string name_;
+
+ // Form field disposition. Most of the time this will be "form-data". But for
+ // nested file uploads inside "multipart/mixed" sections, this can be "file".
+ std::string content_disposition_;
+
+ // Content type. If omitted (empty), "plain/text" assumed.
+ std::string content_type_;
+
+ // Transfer encoding for field data. If omitted, "7bit" is assumed. For most
+ // binary contents (e.g. for file content), use "binary".
+ std::string transfer_encoding_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FormField);
+};
+
+// Simple text form field.
+class BRILLO_EXPORT TextFormField : public FormField {
+ public:
+ // Constructor. Parameters:
+ // name: field name
+ // data: field text data
+ // content_type: the data content type. Empty if not specified.
+ // transfer_encoding: the encoding type of data. If omitted, no encoding
+ // is specified (and "7bit" is assumed).
+ TextFormField(const std::string& name,
+ const std::string& data,
+ const std::string& content_type = {},
+ const std::string& transfer_encoding = {});
+
+ bool ExtractDataStreams(std::vector<StreamPtr>* streams) override;
+
+ private:
+ std::string data_; // Buffer/reader for field data.
+
+ DISALLOW_COPY_AND_ASSIGN(TextFormField);
+};
+
+// File upload form field.
+class BRILLO_EXPORT FileFormField : public FormField {
+ public:
+ // Constructor. Parameters:
+ // name: field name
+ // stream: open stream with the contents of the file.
+ // file_name: just the base file name of the file, e.g. "file.txt".
+ // Used in "filename" parameter of Content-Disposition header.
+ // content_type: valid content type of the file.
+ // transfer_encoding: the encoding type of data.
+ // If omitted, "binary" is used.
+ FileFormField(const std::string& name,
+ StreamPtr stream,
+ const std::string& file_name,
+ const std::string& content_disposition,
+ const std::string& content_type,
+ const std::string& transfer_encoding = {});
+
+ // Override from FormField.
+ // Appends "filename" parameter to Content-Disposition header.
+ std::string GetContentDisposition() const override;
+
+ bool ExtractDataStreams(std::vector<StreamPtr>* streams) override;
+
+ private:
+ StreamPtr stream_;
+ std::string file_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileFormField);
+};
+
+// Multipart form field.
+// This is used directly by FormData class to build the request body for
+// form upload. It can also be used with multiple file uploads for a single
+// file field, when the uploaded files should be sent as "multipart/mixed".
+class BRILLO_EXPORT MultiPartFormField : public FormField {
+ public:
+ // Constructor. Parameters:
+ // name: field name
+ // content_type: valid content type. If omitted, "multipart/mixed" is used.
+ // boundary: multipart boundary separator.
+ // If omitted/empty, a random string is generated.
+ explicit MultiPartFormField(const std::string& name,
+ const std::string& content_type = {},
+ const std::string& boundary = {});
+
+ // Override from FormField.
+ // Appends "boundary" parameter to Content-Type header.
+ std::string GetContentType() const override;
+
+ bool ExtractDataStreams(std::vector<StreamPtr>* streams) override;
+
+ // Adds a form field to the form data. The |field| could be a simple text
+ // field, a file upload field or a multipart form field.
+ void AddCustomField(std::unique_ptr<FormField> field);
+
+ // Adds a simple text form field.
+ void AddTextField(const std::string& name, const std::string& data);
+
+ // Adds a file upload form field using a file path.
+ bool AddFileField(const std::string& name,
+ const base::FilePath& file_path,
+ const std::string& content_disposition,
+ const std::string& content_type,
+ brillo::ErrorPtr* error);
+
+ // Returns a boundary string used to separate multipart form fields.
+ const std::string& GetBoundary() const { return boundary_; }
+
+ private:
+ // Returns the starting boundary string: "--<boundary>".
+ std::string GetBoundaryStart() const;
+ // Returns the ending boundary string: "--<boundary>--".
+ std::string GetBoundaryEnd() const;
+
+ std::string boundary_; // Boundary string used as field separator.
+ std::vector<std::unique_ptr<FormField>> parts_; // Form field list.
+
+ DISALLOW_COPY_AND_ASSIGN(MultiPartFormField);
+};
+
+// A class representing a multipart form data for sending as HTTP POST request.
+class BRILLO_EXPORT FormData final {
+ public:
+ FormData();
+ // Allows to specify a custom |boundary| separator string.
+ explicit FormData(const std::string& boundary);
+
+ // Adds a form field to the form data. The |field| could be a simple text
+ // field, a file upload field or a multipart form field.
+ void AddCustomField(std::unique_ptr<FormField> field);
+
+ // Adds a simple text form field.
+ void AddTextField(const std::string& name, const std::string& data);
+
+ // Adds a file upload form field using a file path.
+ bool AddFileField(const std::string& name,
+ const base::FilePath& file_path,
+ const std::string& content_type,
+ brillo::ErrorPtr* error);
+
+ // Returns the complete content type string to be used in HTTP requests.
+ std::string GetContentType() const;
+
+ // Returns the data stream for the form data. This is a potentially
+ // destructive operation and can be called only once.
+ StreamPtr ExtractDataStream();
+
+ private:
+ MultiPartFormField form_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(FormData);
+};
+
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_HTTP_FORM_DATA_H_
diff --git a/libbrillo/brillo/http/http_form_data_unittest.cc b/libbrillo/brillo/http/http_form_data_unittest.cc
new file mode 100644
index 0000000..842225d
--- /dev/null
+++ b/libbrillo/brillo/http/http_form_data_unittest.cc
@@ -0,0 +1,202 @@
+// 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 <brillo/http/http_form_data.h>
+
+#include <set>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <brillo/mime_utils.h>
+#include <brillo/streams/file_stream.h>
+#include <brillo/streams/input_stream_set.h>
+#include <gtest/gtest.h>
+
+namespace brillo {
+namespace http {
+namespace {
+std::string GetFormFieldData(FormField* field) {
+ std::vector<StreamPtr> streams;
+ CHECK(field->ExtractDataStreams(&streams));
+ StreamPtr stream = InputStreamSet::Create(std::move(streams), nullptr);
+
+ std::vector<uint8_t> data(stream->GetSize());
+ EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr));
+ return std::string{data.begin(), data.end()};
+}
+} // anonymous namespace
+
+TEST(HttpFormData, TextFormField) {
+ TextFormField form_field{"field1", "abcdefg", mime::text::kPlain, "7bit"};
+ const char expected_header[] =
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Transfer-Encoding: 7bit\r\n"
+ "\r\n";
+ EXPECT_EQ(expected_header, form_field.GetContentHeader());
+ EXPECT_EQ("abcdefg", GetFormFieldData(&form_field));
+}
+
+TEST(HttpFormData, FileFormField) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ std::string file_content{"text line1\ntext line2\n"};
+ base::FilePath file_name = dir.path().Append("sample.txt");
+ ASSERT_EQ(file_content.size(),
+ static_cast<size_t>(base::WriteFile(
+ file_name, file_content.data(), file_content.size())));
+
+ StreamPtr stream = FileStream::Open(file_name, Stream::AccessMode::READ,
+ FileStream::Disposition::OPEN_EXISTING,
+ nullptr);
+ ASSERT_NE(nullptr, stream);
+ FileFormField form_field{"test_file",
+ std::move(stream),
+ "sample.txt",
+ content_disposition::kFormData,
+ mime::text::kPlain,
+ ""};
+ const char expected_header[] =
+ "Content-Disposition: form-data; name=\"test_file\";"
+ " filename=\"sample.txt\"\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n";
+ EXPECT_EQ(expected_header, form_field.GetContentHeader());
+ EXPECT_EQ(file_content, GetFormFieldData(&form_field));
+}
+
+TEST(HttpFormData, MultiPartFormField) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ std::string file1{"text line1\ntext line2\n"};
+ base::FilePath filename1 = dir.path().Append("sample.txt");
+ ASSERT_EQ(file1.size(),
+ static_cast<size_t>(
+ base::WriteFile(filename1, file1.data(), file1.size())));
+ std::string file2{"\x01\x02\x03\x04\x05"};
+ base::FilePath filename2 = dir.path().Append("test.bin");
+ ASSERT_EQ(file2.size(),
+ static_cast<size_t>(
+ base::WriteFile(filename2, file2.data(), file2.size())));
+
+ MultiPartFormField form_field{"foo", mime::multipart::kFormData, "Delimiter"};
+ form_field.AddTextField("name", "John Doe");
+ EXPECT_TRUE(form_field.AddFileField("file1",
+ filename1,
+ content_disposition::kFormData,
+ mime::text::kPlain,
+ nullptr));
+ EXPECT_TRUE(form_field.AddFileField("file2",
+ filename2,
+ content_disposition::kFormData,
+ mime::application::kOctet_stream,
+ nullptr));
+ const char expected_header[] =
+ "Content-Disposition: form-data; name=\"foo\"\r\n"
+ "Content-Type: multipart/form-data; boundary=\"Delimiter\"\r\n"
+ "\r\n";
+ EXPECT_EQ(expected_header, form_field.GetContentHeader());
+ const char expected_data[] =
+ "--Delimiter\r\n"
+ "Content-Disposition: form-data; name=\"name\"\r\n"
+ "\r\n"
+ "John Doe\r\n"
+ "--Delimiter\r\n"
+ "Content-Disposition: form-data; name=\"file1\";"
+ " filename=\"sample.txt\"\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Transfer-Encoding: binary\r\n"
+ "\r\n"
+ "text line1\ntext line2\n\r\n"
+ "--Delimiter\r\n"
+ "Content-Disposition: form-data; name=\"file2\";"
+ " filename=\"test.bin\"\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ "Content-Transfer-Encoding: binary\r\n"
+ "\r\n"
+ "\x01\x02\x03\x04\x05\r\n"
+ "--Delimiter--";
+ EXPECT_EQ(expected_data, GetFormFieldData(&form_field));
+}
+
+TEST(HttpFormData, MultiPartBoundary) {
+ const int count = 10;
+ std::set<std::string> boundaries;
+ for (int i = 0; i < count; i++) {
+ MultiPartFormField field{""};
+ std::string boundary = field.GetBoundary();
+ boundaries.insert(boundary);
+ // Our generated boundary must be 16 character long and contain lowercase
+ // hexadecimal digits only.
+ EXPECT_EQ(16u, boundary.size());
+ EXPECT_EQ(std::string::npos,
+ boundary.find_first_not_of("0123456789abcdef"));
+ }
+ // Now make sure the boundary strings were generated at random, so we should
+ // get |count| unique boundary strings. However since the strings are random,
+ // there is a very slim change of generating the same string twice, so
+ // expect at least 90% of unique strings. 90% is picked arbitrarily here.
+ int expected_min_unique = count * 9 / 10;
+ EXPECT_GE(boundaries.size(), expected_min_unique);
+}
+
+TEST(HttpFormData, FormData) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ std::string file1{"text line1\ntext line2\n"};
+ base::FilePath filename1 = dir.path().Append("sample.txt");
+ ASSERT_EQ(file1.size(),
+ static_cast<size_t>(
+ base::WriteFile(filename1, file1.data(), file1.size())));
+ std::string file2{"\x01\x02\x03\x04\x05"};
+ base::FilePath filename2 = dir.path().Append("test.bin");
+ ASSERT_EQ(file2.size(),
+ static_cast<size_t>(
+ base::WriteFile(filename2, file2.data(), file2.size())));
+
+ FormData form_data{"boundary1"};
+ form_data.AddTextField("name", "John Doe");
+ std::unique_ptr<MultiPartFormField> files{
+ new MultiPartFormField{"files", "", "boundary2"}};
+ EXPECT_TRUE(files->AddFileField(
+ "", filename1, content_disposition::kFile, mime::text::kPlain, nullptr));
+ EXPECT_TRUE(files->AddFileField("",
+ filename2,
+ content_disposition::kFile,
+ mime::application::kOctet_stream,
+ nullptr));
+ form_data.AddCustomField(std::move(files));
+ EXPECT_EQ("multipart/form-data; boundary=\"boundary1\"",
+ form_data.GetContentType());
+
+ StreamPtr stream = form_data.ExtractDataStream();
+ std::vector<uint8_t> data(stream->GetSize());
+ EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr));
+ const char expected_data[] =
+ "--boundary1\r\n"
+ "Content-Disposition: form-data; name=\"name\"\r\n"
+ "\r\n"
+ "John Doe\r\n"
+ "--boundary1\r\n"
+ "Content-Disposition: form-data; name=\"files\"\r\n"
+ "Content-Type: multipart/mixed; boundary=\"boundary2\"\r\n"
+ "\r\n"
+ "--boundary2\r\n"
+ "Content-Disposition: file; filename=\"sample.txt\"\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Transfer-Encoding: binary\r\n"
+ "\r\n"
+ "text line1\ntext line2\n\r\n"
+ "--boundary2\r\n"
+ "Content-Disposition: file; filename=\"test.bin\"\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ "Content-Transfer-Encoding: binary\r\n"
+ "\r\n"
+ "\x01\x02\x03\x04\x05\r\n"
+ "--boundary2--\r\n"
+ "--boundary1--";
+ EXPECT_EQ(expected_data, (std::string{data.begin(), data.end()}));
+}
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_request.cc b/libbrillo/brillo/http/http_request.cc
new file mode 100644
index 0000000..2784b8e
--- /dev/null
+++ b/libbrillo/brillo/http/http_request.cc
@@ -0,0 +1,357 @@
+// 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 <brillo/http/http_request.h>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <brillo/http/http_form_data.h>
+#include <brillo/map_utils.h>
+#include <brillo/mime_utils.h>
+#include <brillo/streams/memory_stream.h>
+#include <brillo/strings/string_utils.h>
+
+namespace brillo {
+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 std::string& method,
+ std::shared_ptr<Transport> transport)
+ : transport_(transport), request_url_(url), method_(method) {
+ VLOG(1) << "http::Request created";
+ if (!transport_)
+ transport_ = http::Transport::CreateDefault();
+}
+
+Request::~Request() {
+ VLOG(1) << "http::Request destroyed";
+}
+
+void Request::AddRange(int64_t bytes) {
+ DCHECK(transport_) << "Request already sent";
+ 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) {
+ DCHECK(transport_) << "Request already sent";
+ ranges_.emplace_back(from_byte, to_byte);
+}
+
+std::unique_ptr<Response> Request::GetResponseAndBlock(
+ brillo::ErrorPtr* error) {
+ if (!SendRequestIfNeeded(error) || !connection_->FinishRequest(error))
+ return std::unique_ptr<Response>();
+ std::unique_ptr<Response> response(new Response(connection_));
+ connection_.reset();
+ transport_.reset(); // Indicate that the response has been received
+ return response;
+}
+
+RequestID Request::GetResponse(const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ ErrorPtr error;
+ if (!SendRequestIfNeeded(&error)) {
+ transport_->RunCallbackAsync(
+ FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release())));
+ return 0;
+ }
+ RequestID id =
+ connection_->FinishRequestAsync(success_callback, error_callback);
+ connection_.reset();
+ transport_.reset(); // Indicate that the request has been dispatched.
+ return id;
+}
+
+void Request::SetAccept(const std::string& accept_mime_types) {
+ DCHECK(transport_) << "Request already sent";
+ accept_ = accept_mime_types;
+}
+
+const std::string& Request::GetAccept() const {
+ return accept_;
+}
+
+void Request::SetContentType(const std::string& contentType) {
+ DCHECK(transport_) << "Request already sent";
+ content_type_ = contentType;
+}
+
+const std::string& Request::GetContentType() const {
+ return content_type_;
+}
+
+void Request::AddHeader(const std::string& header, const std::string& value) {
+ DCHECK(transport_) << "Request already sent";
+ headers_.emplace(header, value);
+}
+
+void Request::AddHeaders(const HeaderList& headers) {
+ DCHECK(transport_) << "Request already sent";
+ headers_.insert(headers.begin(), headers.end());
+}
+
+bool Request::AddRequestBody(const void* data,
+ size_t size,
+ brillo::ErrorPtr* error) {
+ if (!SendRequestIfNeeded(error))
+ return false;
+ StreamPtr stream = MemoryStream::OpenCopyOf(data, size, error);
+ return stream && connection_->SetRequestData(std::move(stream), error);
+}
+
+bool Request::AddRequestBody(StreamPtr stream, brillo::ErrorPtr* error) {
+ return SendRequestIfNeeded(error) &&
+ connection_->SetRequestData(std::move(stream), error);
+}
+
+bool Request::AddRequestBodyAsFormData(std::unique_ptr<FormData> form_data,
+ brillo::ErrorPtr* error) {
+ AddHeader(request_header::kContentType, form_data->GetContentType());
+ if (!SendRequestIfNeeded(error))
+ return false;
+ return connection_->SetRequestData(form_data->ExtractDataStream(), error);
+}
+
+bool Request::AddResponseStream(StreamPtr stream, brillo::ErrorPtr* error) {
+ if (!SendRequestIfNeeded(error))
+ return false;
+ connection_->SetResponseData(std::move(stream));
+ return true;
+}
+
+const std::string& Request::GetRequestURL() const {
+ return request_url_;
+}
+
+const std::string& Request::GetRequestMethod() const {
+ return method_;
+}
+
+void Request::SetReferer(const std::string& referer) {
+ DCHECK(transport_) << "Request already sent";
+ referer_ = referer;
+}
+
+const std::string& Request::GetReferer() const {
+ return referer_;
+}
+
+void Request::SetUserAgent(const std::string& user_agent) {
+ DCHECK(transport_) << "Request already sent";
+ user_agent_ = user_agent;
+}
+
+const std::string& Request::GetUserAgent() const {
+ return user_agent_;
+}
+
+bool Request::SendRequestIfNeeded(brillo::ErrorPtr* error) {
+ if (transport_) {
+ if (!connection_) {
+ http::HeaderList headers = brillo::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 = brillo::string_utils::ToString(p.first);
+ }
+ range += '-';
+ if (p.second != range_value_omitted) {
+ range += brillo::string_utils::ToString(p.second);
+ }
+ ranges.push_back(range);
+ }
+ }
+ }
+ if (!ranges.empty())
+ headers.emplace_back(
+ request_header::kRange,
+ "bytes=" + brillo::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(
+ request_url_, method_, headers, user_agent_, referer_, error);
+ }
+
+ if (connection_)
+ return true;
+ } else {
+ brillo::Error::AddTo(error,
+ FROM_HERE,
+ http::kErrorDomain,
+ "response_already_received",
+ "HTTP response already received");
+ }
+ return false;
+}
+
+// ************************************************************
+// ********************** Response Class **********************
+// ************************************************************
+Response::Response(const std::shared_ptr<Connection>& connection)
+ : connection_{connection} {
+ VLOG(1) << "http::Response created";
+}
+
+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);
+}
+
+StreamPtr Response::ExtractDataStream(ErrorPtr* error) {
+ return connection_->ExtractDataStream(error);
+}
+
+std::vector<uint8_t> Response::ExtractData() {
+ std::vector<uint8_t> data;
+ StreamPtr src_stream = connection_->ExtractDataStream(nullptr);
+ StreamPtr dest_stream = MemoryStream::CreateRef(&data, nullptr);
+ if (src_stream && dest_stream) {
+ char buffer[1024];
+ size_t read = 0;
+ while (src_stream->ReadBlocking(buffer, sizeof(buffer), &read, nullptr) &&
+ read > 0) {
+ CHECK(dest_stream->WriteAllBlocking(buffer, read, nullptr));
+ }
+ }
+ return data;
+}
+
+std::string Response::ExtractDataAsString() {
+ std::vector<uint8_t> data = ExtractData();
+ return std::string{data.begin(), data.end()};
+}
+
+std::string Response::GetHeader(const std::string& header_name) const {
+ if (connection_)
+ return connection_->GetResponseHeader(header_name);
+
+ return std::string();
+}
+
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_request.h b/libbrillo/brillo/http/http_request.h
new file mode 100644
index 0000000..42d8048
--- /dev/null
+++ b/libbrillo/brillo/http/http_request.h
@@ -0,0 +1,381 @@
+// 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 LIBBRILLO_BRILLO_HTTP_HTTP_REQUEST_H_
+#define LIBBRILLO_BRILLO_HTTP_HTTP_REQUEST_H_
+
+#include <limits>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+#include <brillo/errors/error.h>
+#include <brillo/http/http_connection.h>
+#include <brillo/http/http_transport.h>
+
+namespace brillo {
+namespace http {
+
+// HTTP request verbs
+namespace request_type {
+BRILLO_EXPORT extern const char kOptions[];
+BRILLO_EXPORT extern const char kGet[];
+BRILLO_EXPORT extern const char kHead[];
+BRILLO_EXPORT extern const char kPost[];
+BRILLO_EXPORT extern const char kPut[];
+BRILLO_EXPORT extern const char kPatch[]; // Non-standard HTTP/1.1 verb
+BRILLO_EXPORT extern const char kDelete[];
+BRILLO_EXPORT extern const char kTrace[];
+BRILLO_EXPORT extern const char kConnect[];
+BRILLO_EXPORT extern const char kCopy[]; // Non-standard HTTP/1.1 verb
+BRILLO_EXPORT extern const char kMove[]; // Non-standard HTTP/1.1 verb
+} // namespace request_type
+
+// HTTP request header names
+namespace request_header {
+BRILLO_EXPORT extern const char kAccept[];
+BRILLO_EXPORT extern const char kAcceptCharset[];
+BRILLO_EXPORT extern const char kAcceptEncoding[];
+BRILLO_EXPORT extern const char kAcceptLanguage[];
+BRILLO_EXPORT extern const char kAllow[];
+BRILLO_EXPORT extern const char kAuthorization[];
+BRILLO_EXPORT extern const char kCacheControl[];
+BRILLO_EXPORT extern const char kConnection[];
+BRILLO_EXPORT extern const char kContentEncoding[];
+BRILLO_EXPORT extern const char kContentLanguage[];
+BRILLO_EXPORT extern const char kContentLength[];
+BRILLO_EXPORT extern const char kContentLocation[];
+BRILLO_EXPORT extern const char kContentMd5[];
+BRILLO_EXPORT extern const char kContentRange[];
+BRILLO_EXPORT extern const char kContentType[];
+BRILLO_EXPORT extern const char kCookie[];
+BRILLO_EXPORT extern const char kDate[];
+BRILLO_EXPORT extern const char kExpect[];
+BRILLO_EXPORT extern const char kExpires[];
+BRILLO_EXPORT extern const char kFrom[];
+BRILLO_EXPORT extern const char kHost[];
+BRILLO_EXPORT extern const char kIfMatch[];
+BRILLO_EXPORT extern const char kIfModifiedSince[];
+BRILLO_EXPORT extern const char kIfNoneMatch[];
+BRILLO_EXPORT extern const char kIfRange[];
+BRILLO_EXPORT extern const char kIfUnmodifiedSince[];
+BRILLO_EXPORT extern const char kLastModified[];
+BRILLO_EXPORT extern const char kMaxForwards[];
+BRILLO_EXPORT extern const char kPragma[];
+BRILLO_EXPORT extern const char kProxyAuthorization[];
+BRILLO_EXPORT extern const char kRange[];
+BRILLO_EXPORT extern const char kReferer[];
+BRILLO_EXPORT extern const char kTE[];
+BRILLO_EXPORT extern const char kTrailer[];
+BRILLO_EXPORT extern const char kTransferEncoding[];
+BRILLO_EXPORT extern const char kUpgrade[];
+BRILLO_EXPORT extern const char kUserAgent[];
+BRILLO_EXPORT extern const char kVia[];
+BRILLO_EXPORT extern const char kWarning[];
+} // namespace request_header
+
+// HTTP response header names
+namespace response_header {
+BRILLO_EXPORT extern const char kAcceptRanges[];
+BRILLO_EXPORT extern const char kAge[];
+BRILLO_EXPORT extern const char kAllow[];
+BRILLO_EXPORT extern const char kCacheControl[];
+BRILLO_EXPORT extern const char kConnection[];
+BRILLO_EXPORT extern const char kContentEncoding[];
+BRILLO_EXPORT extern const char kContentLanguage[];
+BRILLO_EXPORT extern const char kContentLength[];
+BRILLO_EXPORT extern const char kContentLocation[];
+BRILLO_EXPORT extern const char kContentMd5[];
+BRILLO_EXPORT extern const char kContentRange[];
+BRILLO_EXPORT extern const char kContentType[];
+BRILLO_EXPORT extern const char kDate[];
+BRILLO_EXPORT extern const char kETag[];
+BRILLO_EXPORT extern const char kExpires[];
+BRILLO_EXPORT extern const char kLastModified[];
+BRILLO_EXPORT extern const char kLocation[];
+BRILLO_EXPORT extern const char kPragma[];
+BRILLO_EXPORT extern const char kProxyAuthenticate[];
+BRILLO_EXPORT extern const char kRetryAfter[];
+BRILLO_EXPORT extern const char kServer[];
+BRILLO_EXPORT extern const char kSetCookie[];
+BRILLO_EXPORT extern const char kTrailer[];
+BRILLO_EXPORT extern const char kTransferEncoding[];
+BRILLO_EXPORT extern const char kUpgrade[];
+BRILLO_EXPORT extern const char kVary[];
+BRILLO_EXPORT extern const char kVia[];
+BRILLO_EXPORT extern const char kWarning[];
+BRILLO_EXPORT 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.
+class FormData;
+
+///////////////////////////////////////////////////////////////////////////////
+// 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 BRILLO_EXPORT Request final {
+ 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 std::string& method,
+ std::shared_ptr<Transport> transport);
+ ~Request();
+
+ // Gets/Sets "Accept:" header value. The default value is "*/*" if not set.
+ void SetAccept(const std::string& accept_mime_types);
+ const std::string& GetAccept() const;
+
+ // Gets/Sets "Content-Type:" header value
+ void SetContentType(const std::string& content_type);
+ const std::string& GetContentType() const;
+
+ // Adds additional HTTP request header
+ void AddHeader(const std::string& header, const std::string& value);
+ void AddHeaders(const HeaderList& headers);
+
+ // Removes HTTP request header
+ void RemoveHeader(const std::string& header);
+
+ // Adds a request body. This is not to be used with GET method
+ bool AddRequestBody(const void* data, size_t size, brillo::ErrorPtr* error);
+ bool AddRequestBody(StreamPtr stream, brillo::ErrorPtr* error);
+
+ // Adds a request body. This is not to be used with GET method.
+ // This method also sets the correct content-type of the request, including
+ // the multipart data boundary.
+ bool AddRequestBodyAsFormData(std::unique_ptr<FormData> form_data,
+ brillo::ErrorPtr* error);
+
+ // Adds a stream for the response. Otherwise a MemoryStream will be used.
+ bool AddResponseStream(StreamPtr stream, brillo::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
+ const std::string& GetRequestURL() const;
+
+ // Returns the request verb.
+ const std::string& GetRequestMethod() const;
+
+ // Gets/Sets a request referer URL (sent as "Referer:" request header).
+ void SetReferer(const std::string& referer);
+ const std::string& GetReferer() const;
+
+ // Gets/Sets a user agent string (sent as "User-Agent:" request header).
+ void SetUserAgent(const std::string& user_agent);
+ const std::string& GetUserAgent() const;
+
+ // Sends the request to the server and blocks until the response is received,
+ // which is returned as 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> GetResponseAndBlock(brillo::ErrorPtr* error);
+
+ // Sends out the request and invokes the |success_callback| when the response
+ // is received. In case of an error, the |error_callback| is invoked.
+ // Returns the ID of the asynchronous request created.
+ RequestID GetResponse(const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+ private:
+ friend class HttpRequestTest;
+
+ // Helper function to create an http::Connection and send off request headers.
+ BRILLO_PRIVATE bool SendRequestIfNeeded(brillo::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::shared_ptr<Connection> connection_;
+
+ // Full request URL, such as "http://www.host.com/path/to/object"
+ const std::string request_url_;
+ // HTTP request verb, such as "GET", "POST", "PUT", ...
+ const 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.
+ std::multimap<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 BRILLO_EXPORT Response final {
+ public:
+ explicit Response(const std::shared_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 stream by transferring ownership of the data stream
+ // from Response class to the caller.
+ StreamPtr ExtractDataStream(ErrorPtr* error);
+
+ // Extracts the data from the underlying response data stream as a byte array.
+ std::vector<uint8_t> ExtractData();
+
+ // Extracts the data from the underlying response data stream as a string.
+ std::string ExtractDataAsString();
+
+ // Returns a value of a given response HTTP header.
+ std::string GetHeader(const std::string& header_name) const;
+
+ private:
+ friend class HttpRequestTest;
+
+ std::shared_ptr<Connection> connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(Response);
+};
+
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_HTTP_REQUEST_H_
diff --git a/libbrillo/brillo/http/http_request_unittest.cc b/libbrillo/brillo/http/http_request_unittest.cc
new file mode 100644
index 0000000..edf056b
--- /dev/null
+++ b/libbrillo/brillo/http/http_request_unittest.cc
@@ -0,0 +1,208 @@
+// 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 <brillo/http/http_request.h>
+
+#include <string>
+
+#include <base/callback.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/http/mock_connection.h>
+#include <brillo/http/mock_transport.h>
+#include <brillo/mime_utils.h>
+#include <brillo/streams/mock_stream.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::DoAll;
+using testing::Invoke;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Unused;
+using testing::WithArg;
+using testing::_;
+
+namespace brillo {
+namespace http {
+
+MATCHER_P(ContainsStringData, str, "") {
+ if (arg->GetSize() != str.size())
+ return false;
+
+ std::string data;
+ char buf[100];
+ size_t read = 0;
+ while (arg->ReadBlocking(buf, sizeof(buf), &read, nullptr) && read > 0) {
+ data.append(buf, read);
+ }
+ return data == str;
+}
+
+class HttpRequestTest : public testing::Test {
+ public:
+ void SetUp() override {
+ transport_ = std::make_shared<MockTransport>();
+ connection_ = std::make_shared<MockConnection>(transport_);
+ }
+
+ void TearDown() override {
+ // Having shared pointers to mock objects (some of methods in these tests
+ // return shared pointers to connection and transport) could cause the
+ // test expectations to hold on to the mock object without releasing them
+ // at the end of a test, causing Mock's object leak detection to erroneously
+ // detect mock object "leaks". Verify and clear the expectations manually
+ // and explicitly to ensure the shared pointer refcounters are not
+ // preventing the mocks to be destroyed at the end of each test.
+ testing::Mock::VerifyAndClearExpectations(connection_.get());
+ connection_.reset();
+ testing::Mock::VerifyAndClearExpectations(transport_.get());
+ transport_.reset();
+ }
+
+ protected:
+ std::shared_ptr<MockTransport> transport_;
+ std::shared_ptr<MockConnection> connection_;
+};
+
+TEST_F(HttpRequestTest, Defaults) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ EXPECT_TRUE(request.GetContentType().empty());
+ EXPECT_TRUE(request.GetReferer().empty());
+ EXPECT_TRUE(request.GetUserAgent().empty());
+ EXPECT_EQ("*/*", request.GetAccept());
+ EXPECT_EQ("http://www.foo.bar", request.GetRequestURL());
+ EXPECT_EQ(request_type::kPost, request.GetRequestMethod());
+
+ Request request2{"http://www.foo.bar/baz", request_type::kGet, transport_};
+ EXPECT_EQ("http://www.foo.bar/baz", request2.GetRequestURL());
+ EXPECT_EQ(request_type::kGet, request2.GetRequestMethod());
+}
+
+TEST_F(HttpRequestTest, ContentType) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ request.SetContentType(mime::image::kJpeg);
+ EXPECT_EQ(mime::image::kJpeg, request.GetContentType());
+}
+
+TEST_F(HttpRequestTest, Referer) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ request.SetReferer("http://www.foo.bar/baz");
+ EXPECT_EQ("http://www.foo.bar/baz", request.GetReferer());
+}
+
+TEST_F(HttpRequestTest, UserAgent) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ request.SetUserAgent("FooBar Browser");
+ EXPECT_EQ("FooBar Browser", request.GetUserAgent());
+}
+
+TEST_F(HttpRequestTest, Accept) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ request.SetAccept("text/*, text/html, text/html;level=1, */*");
+ EXPECT_EQ("text/*, text/html, text/html;level=1, */*", request.GetAccept());
+}
+
+TEST_F(HttpRequestTest, GetResponseAndBlock) {
+ Request request{"http://www.foo.bar", request_type::kPost, transport_};
+ request.SetUserAgent("FooBar Browser");
+ request.SetReferer("http://www.foo.bar/baz");
+ request.SetAccept("text/*, text/html, text/html;level=1, */*");
+ request.AddHeader(request_header::kAcceptEncoding, "compress, gzip");
+ request.AddHeaders({
+ {request_header::kAcceptLanguage, "da, en-gb;q=0.8, en;q=0.7"},
+ {request_header::kConnection, "close"},
+ });
+ request.AddRange(-10);
+ request.AddRange(100, 200);
+ request.AddRange(300);
+ std::string req_body{"Foo bar baz"};
+ request.AddHeader(request_header::kContentType, mime::text::kPlain);
+
+ EXPECT_CALL(*transport_, CreateConnection(
+ "http://www.foo.bar",
+ request_type::kPost,
+ HeaderList{
+ {request_header::kAcceptEncoding, "compress, gzip"},
+ {request_header::kAcceptLanguage, "da, en-gb;q=0.8, en;q=0.7"},
+ {request_header::kConnection, "close"},
+ {request_header::kContentType, mime::text::kPlain},
+ {request_header::kRange, "bytes=-10,100-200,300-"},
+ {request_header::kAccept, "text/*, text/html, text/html;level=1, */*"},
+ },
+ "FooBar Browser",
+ "http://www.foo.bar/baz",
+ nullptr)).WillOnce(Return(connection_));
+
+ EXPECT_CALL(*connection_, MockSetRequestData(ContainsStringData(req_body), _))
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(
+ request.AddRequestBody(req_body.data(), req_body.size(), nullptr));
+
+ EXPECT_CALL(*connection_, FinishRequest(_)).WillOnce(Return(true));
+ auto resp = request.GetResponseAndBlock(nullptr);
+ EXPECT_NE(nullptr, resp.get());
+}
+
+TEST_F(HttpRequestTest, GetResponse) {
+ Request request{"http://foo.bar", request_type::kGet, transport_};
+
+ std::string resp_data{"FooBar response body"};
+ auto read_data =
+ [&resp_data](void* buffer, Unused, size_t* read, Unused) -> bool {
+ memcpy(buffer, resp_data.data(), resp_data.size());
+ *read = resp_data.size();
+ return true;
+ };
+
+ auto success_callback = [](decltype(this) test,
+ const std::string& resp_data,
+ RequestID request_id,
+ std::unique_ptr<Response> resp) {
+ EXPECT_EQ(23, request_id);
+ EXPECT_CALL(*test->connection_, GetResponseStatusCode())
+ .WillOnce(Return(status_code::Partial));
+ EXPECT_EQ(status_code::Partial, resp->GetStatusCode());
+
+ EXPECT_CALL(*test->connection_, GetResponseStatusText())
+ .WillOnce(Return("Partial completion"));
+ EXPECT_EQ("Partial completion", resp->GetStatusText());
+
+ EXPECT_CALL(*test->connection_,
+ GetResponseHeader(response_header::kContentType))
+ .WillOnce(Return(mime::text::kHtml));
+ EXPECT_EQ(mime::text::kHtml, resp->GetContentType());
+
+ EXPECT_EQ(resp_data, resp->ExtractDataAsString());
+ };
+
+ auto finish_request_async =
+ [this, &read_data, &resp_data](const SuccessCallback& success_callback) {
+ std::unique_ptr<MockStream> mock_stream{new MockStream};
+ EXPECT_CALL(*mock_stream, ReadBlocking(_, _, _, _))
+ .WillOnce(Invoke(read_data))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+
+ EXPECT_CALL(*connection_, MockExtractDataStream(_))
+ .WillOnce(Return(mock_stream.release()));
+ std::unique_ptr<Response> resp{new Response{connection_}};
+ success_callback.Run(23, std::move(resp));
+ };
+
+ EXPECT_CALL(
+ *transport_,
+ CreateConnection("http://foo.bar", request_type::kGet, _, "", "", _))
+ .WillOnce(Return(connection_));
+
+ EXPECT_CALL(*connection_, FinishRequestAsync(_, _))
+ .WillOnce(DoAll(WithArg<0>(Invoke(finish_request_async)), Return(23)));
+
+ EXPECT_EQ(
+ 23,
+ request.GetResponse(
+ base::Bind(success_callback, base::Unretained(this), resp_data), {}));
+}
+
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_transport.cc b/libbrillo/brillo/http/http_transport.cc
new file mode 100644
index 0000000..d77eabe
--- /dev/null
+++ b/libbrillo/brillo/http/http_transport.cc
@@ -0,0 +1,19 @@
+// 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 <brillo/http/http_transport.h>
+
+#include <brillo/http/http_transport_curl.h>
+
+namespace brillo {
+namespace http {
+
+const char kErrorDomain[] = "http_transport";
+
+std::shared_ptr<Transport> Transport::CreateDefault() {
+ return std::make_shared<http::curl::Transport>(std::make_shared<CurlApi>());
+}
+
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_transport.h b/libbrillo/brillo/http/http_transport.h
new file mode 100644
index 0000000..63e135c
--- /dev/null
+++ b/libbrillo/brillo/http/http_transport.h
@@ -0,0 +1,95 @@
+// 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 LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_H_
+#define LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/location.h>
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <brillo/brillo_export.h>
+#include <brillo/errors/error.h>
+
+namespace brillo {
+namespace http {
+
+BRILLO_EXPORT extern const char kErrorDomain[];
+
+class Request;
+class Response;
+class Connection;
+
+using RequestID = int;
+
+using HeaderList = std::vector<std::pair<std::string, std::string>>;
+using SuccessCallback =
+ base::Callback<void(RequestID, std::unique_ptr<Response>)>;
+using ErrorCallback = base::Callback<void(RequestID, const brillo::Error*)>;
+
+///////////////////////////////////////////////////////////////////////////////
+// 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 BRILLO_EXPORT Transport : public std::enable_shared_from_this<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.
+ // The |url| here is the full URL specified in the request. It is passed
+ // to the underlying transport (e.g. CURL) to establish the connection.
+ virtual std::shared_ptr<Connection> CreateConnection(
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ brillo::ErrorPtr* error) = 0;
+
+ // Runs |callback| on the task runner (message loop) associated with the
+ // transport. For transports that do not contain references to real message
+ // loops (e.g. a fake transport), calls the callback immediately.
+ virtual void RunCallbackAsync(const tracked_objects::Location& from_here,
+ const base::Closure& callback) = 0;
+
+ // Initiates an asynchronous transfer on the given |connection|.
+ // The actual implementation of an async I/O is transport-specific.
+ // Returns a request ID which can be used to cancel the request.
+ virtual RequestID StartAsyncTransfer(
+ Connection* connection,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) = 0;
+
+ // Cancels a pending asynchronous request. This will cancel a pending request
+ // scheduled by the transport while the I/O operations are still in progress.
+ // As soon as all I/O completes for the request/response, or when an error
+ // occurs, the success/error callbacks are invoked and the request is
+ // considered complete and can no longer be canceled.
+ // Returns false if pending request with |request_id| is not found (e.g. it
+ // has already completed/its callbacks are dispatched).
+ virtual bool CancelRequest(RequestID request_id) = 0;
+
+ // Set the default timeout of requests made.
+ virtual void SetDefaultTimeout(base::TimeDelta timeout) = 0;
+
+ // Creates a default http::Transport (currently, using http::curl::Transport).
+ static std::shared_ptr<Transport> CreateDefault();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Transport);
+};
+
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_H_
diff --git a/libbrillo/brillo/http/http_transport_curl.cc b/libbrillo/brillo/http/http_transport_curl.cc
new file mode 100644
index 0000000..f7e3b71
--- /dev/null
+++ b/libbrillo/brillo/http/http_transport_curl.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 <brillo/http/http_transport_curl.h>
+
+#include <limits>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+#include <brillo/http/http_connection_curl.h>
+#include <brillo/http/http_request.h>
+#include <brillo/strings/string_utils.h>
+
+namespace {
+
+const char kCACertificatePath[] =
+#ifdef __ANDROID__
+ "/system/etc/security/cacerts_google";
+#else
+ "/usr/share/brillo-ca-certificates";
+#endif
+
+} // namespace
+
+namespace brillo {
+namespace http {
+namespace curl {
+
+// This is a class that stores connection data on particular CURL socket
+// and provides file descriptor watcher to monitor read and/or write operations
+// on the socket's file descriptor.
+class Transport::SocketPollData : public base::MessageLoopForIO::Watcher {
+ public:
+ SocketPollData(const std::shared_ptr<CurlInterface>& curl_interface,
+ CURLM* curl_multi_handle,
+ Transport* transport,
+ curl_socket_t socket_fd)
+ : curl_interface_(curl_interface),
+ curl_multi_handle_(curl_multi_handle),
+ transport_(transport),
+ socket_fd_(socket_fd) {}
+
+ // Returns the pointer for the socket-specific file descriptor watcher.
+ base::MessageLoopForIO::FileDescriptorWatcher* GetWatcher() {
+ return &file_descriptor_watcher_;
+ }
+
+ private:
+ // Overrides from base::MessageLoopForIO::Watcher.
+ void OnFileCanReadWithoutBlocking(int fd) override {
+ OnSocketReady(fd, CURL_CSELECT_IN);
+ }
+ void OnFileCanWriteWithoutBlocking(int fd) override {
+ OnSocketReady(fd, CURL_CSELECT_OUT);
+ }
+
+ // Data on the socket is available to be read from or written to.
+ // Notify CURL of the action it needs to take on the socket file descriptor.
+ void OnSocketReady(int fd, int action) {
+ CHECK_EQ(socket_fd_, fd) << "Unexpected socket file descriptor";
+ int still_running_count = 0;
+ CURLMcode code = curl_interface_->MultiSocketAction(
+ curl_multi_handle_, socket_fd_, action, &still_running_count);
+ CHECK_NE(CURLM_CALL_MULTI_PERFORM, code)
+ << "CURL should no longer return CURLM_CALL_MULTI_PERFORM here";
+
+ if (code == CURLM_OK)
+ transport_->ProcessAsyncCurlMessages();
+ }
+
+ // The CURL interface to use.
+ std::shared_ptr<CurlInterface> curl_interface_;
+ // CURL multi-handle associated with the transport.
+ CURLM* curl_multi_handle_;
+ // Transport object itself.
+ Transport* transport_;
+ // The socket file descriptor for the connection.
+ curl_socket_t socket_fd_;
+ // File descriptor watcher to notify us of asynchronous I/O on the FD.
+ base::MessageLoopForIO::FileDescriptorWatcher file_descriptor_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketPollData);
+};
+
+// The request data associated with an asynchronous operation on a particular
+// connection.
+struct Transport::AsyncRequestData {
+ // Success/error callbacks to be invoked at the end of the request.
+ SuccessCallback success_callback;
+ ErrorCallback error_callback;
+ // We store a connection here to make sure the object is alive for
+ // as long as asynchronous operation is running.
+ std::shared_ptr<Connection> connection;
+ // The ID of this request.
+ RequestID request_id;
+};
+
+Transport::Transport(const std::shared_ptr<CurlInterface>& curl_interface)
+ : curl_interface_{curl_interface} {
+ VLOG(2) << "curl::Transport created";
+}
+
+Transport::Transport(const std::shared_ptr<CurlInterface>& curl_interface,
+ const std::string& proxy)
+ : curl_interface_{curl_interface}, proxy_{proxy} {
+ VLOG(2) << "curl::Transport created with proxy " << proxy;
+}
+
+Transport::~Transport() {
+ ShutDownAsyncCurl();
+ VLOG(2) << "curl::Transport destroyed";
+}
+
+std::shared_ptr<http::Connection> Transport::CreateConnection(
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ brillo::ErrorPtr* error) {
+ std::shared_ptr<http::Connection> connection;
+ CURL* curl_handle = curl_interface_->EasyInit();
+ if (!curl_handle) {
+ LOG(ERROR) << "Failed to initialize CURL";
+ brillo::Error::AddTo(error, FROM_HERE, http::kErrorDomain,
+ "curl_init_failed", "Failed to initialize CURL");
+ return connection;
+ }
+
+ LOG(INFO) << "Sending a " << method << " request to " << url;
+ CURLcode code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_URL, url);
+
+ if (code == CURLE_OK) {
+ code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_CAPATH,
+ kCACertificatePath);
+ }
+ if (code == CURLE_OK) {
+ code =
+ curl_interface_->EasySetOptInt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1);
+ }
+ if (code == CURLE_OK) {
+ code =
+ curl_interface_->EasySetOptInt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2);
+ }
+ if (code == CURLE_OK && !user_agent.empty()) {
+ code = curl_interface_->EasySetOptStr(
+ curl_handle, CURLOPT_USERAGENT, user_agent);
+ }
+ if (code == CURLE_OK && !referer.empty()) {
+ code =
+ curl_interface_->EasySetOptStr(curl_handle, CURLOPT_REFERER, referer);
+ }
+ if (code == CURLE_OK && !proxy_.empty()) {
+ code = curl_interface_->EasySetOptStr(curl_handle, CURLOPT_PROXY, proxy_);
+ }
+ if (code == CURLE_OK) {
+ int64_t timeout_ms = connection_timeout_.InMillisecondsRoundedUp();
+
+ if (timeout_ms > 0 && timeout_ms <= std::numeric_limits<int>::max()) {
+ code = curl_interface_->EasySetOptInt(
+ curl_handle, CURLOPT_TIMEOUT_MS,
+ static_cast<int>(timeout_ms));
+ }
+ }
+
+ // Setup HTTP request method and optional request body.
+ if (code == CURLE_OK) {
+ if (method == request_type::kGet) {
+ code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_HTTPGET, 1);
+ } else if (method == request_type::kHead) {
+ code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_NOBODY, 1);
+ } else if (method == request_type::kPut) {
+ code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_UPLOAD, 1);
+ } else {
+ // POST and custom request methods
+ code = curl_interface_->EasySetOptInt(curl_handle, CURLOPT_POST, 1);
+ if (code == CURLE_OK) {
+ code = curl_interface_->EasySetOptPtr(
+ curl_handle, CURLOPT_POSTFIELDS, nullptr);
+ }
+ if (code == CURLE_OK && method != request_type::kPost) {
+ code = curl_interface_->EasySetOptStr(
+ curl_handle, CURLOPT_CUSTOMREQUEST, method);
+ }
+ }
+ }
+
+ if (code != CURLE_OK) {
+ AddEasyCurlError(error, FROM_HERE, code, curl_interface_.get());
+ curl_interface_->EasyCleanup(curl_handle);
+ return connection;
+ }
+
+ connection = std::make_shared<http::curl::Connection>(
+ curl_handle, method, curl_interface_, shared_from_this());
+ if (!connection->SendHeaders(headers, error)) {
+ connection.reset();
+ }
+ return connection;
+}
+
+void Transport::RunCallbackAsync(const tracked_objects::Location& from_here,
+ const base::Closure& callback) {
+ base::MessageLoopForIO::current()->PostTask(from_here, callback);
+}
+
+RequestID Transport::StartAsyncTransfer(http::Connection* connection,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ brillo::ErrorPtr error;
+ if (!SetupAsyncCurl(&error)) {
+ RunCallbackAsync(
+ FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release())));
+ return 0;
+ }
+
+ RequestID request_id = ++last_request_id_;
+
+ auto curl_connection = static_cast<http::curl::Connection*>(connection);
+ std::unique_ptr<AsyncRequestData> request_data{new AsyncRequestData};
+ // Add the request data to |async_requests_| before adding the CURL handle
+ // in case CURL feels like calling the socket callback synchronously which
+ // will need the data to be in |async_requests_| map already.
+ request_data->success_callback = success_callback;
+ request_data->error_callback = error_callback;
+ request_data->connection =
+ std::static_pointer_cast<Connection>(curl_connection->shared_from_this());
+ request_data->request_id = request_id;
+ async_requests_.emplace(curl_connection, std::move(request_data));
+ request_id_map_.emplace(request_id, curl_connection);
+
+ // Add the connection's CURL handle to the multi-handle.
+ CURLMcode code = curl_interface_->MultiAddHandle(
+ curl_multi_handle_, curl_connection->curl_handle_);
+ if (code != CURLM_OK) {
+ brillo::ErrorPtr error;
+ AddMultiCurlError(&error, FROM_HERE, code, curl_interface_.get());
+ RunCallbackAsync(
+ FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release())));
+ async_requests_.erase(curl_connection);
+ request_id_map_.erase(request_id);
+ return 0;
+ }
+ LOG(INFO) << "Started asynchronous HTTP request with ID " << request_id;
+ return request_id;
+}
+
+bool Transport::CancelRequest(RequestID request_id) {
+ auto p = request_id_map_.find(request_id);
+ if (p == request_id_map_.end()) {
+ // The request must have been completed already...
+ // This is not necessarily an error condition, so fail gracefully.
+ LOG(WARNING) << "HTTP request #" << request_id << " not found";
+ return false;
+ }
+ LOG(INFO) << "Canceling HTTP request #" << request_id;
+ CleanAsyncConnection(p->second);
+ return true;
+}
+
+void Transport::SetDefaultTimeout(base::TimeDelta timeout) {
+ connection_timeout_ = timeout;
+}
+
+void Transport::AddEasyCurlError(brillo::ErrorPtr* error,
+ const tracked_objects::Location& location,
+ CURLcode code,
+ CurlInterface* curl_interface) {
+ brillo::Error::AddTo(error, location, "curl_easy_error",
+ brillo::string_utils::ToString(code),
+ curl_interface->EasyStrError(code));
+}
+
+void Transport::AddMultiCurlError(brillo::ErrorPtr* error,
+ const tracked_objects::Location& location,
+ CURLMcode code,
+ CurlInterface* curl_interface) {
+ brillo::Error::AddTo(error, location, "curl_multi_error",
+ brillo::string_utils::ToString(code),
+ curl_interface->MultiStrError(code));
+}
+
+bool Transport::SetupAsyncCurl(brillo::ErrorPtr* error) {
+ if (curl_multi_handle_)
+ return true;
+
+ curl_multi_handle_ = curl_interface_->MultiInit();
+ if (!curl_multi_handle_) {
+ LOG(ERROR) << "Failed to initialize CURL";
+ brillo::Error::AddTo(error, FROM_HERE, http::kErrorDomain,
+ "curl_init_failed", "Failed to initialize CURL");
+ return false;
+ }
+
+ CURLMcode code = curl_interface_->MultiSetSocketCallback(
+ curl_multi_handle_, &Transport::MultiSocketCallback, this);
+ if (code == CURLM_OK) {
+ code = curl_interface_->MultiSetTimerCallback(
+ curl_multi_handle_, &Transport::MultiTimerCallback, this);
+ }
+ if (code != CURLM_OK) {
+ AddMultiCurlError(error, FROM_HERE, code, curl_interface_.get());
+ return false;
+ }
+ return true;
+}
+
+void Transport::ShutDownAsyncCurl() {
+ if (!curl_multi_handle_)
+ return;
+ LOG_IF(WARNING, !poll_data_map_.empty())
+ << "There are pending requests at the time of transport's shutdown";
+ // Make sure we are not leaking any memory here.
+ for (const auto& pair : poll_data_map_)
+ delete pair.second;
+ poll_data_map_.clear();
+ curl_interface_->MultiCleanup(curl_multi_handle_);
+ curl_multi_handle_ = nullptr;
+}
+
+int Transport::MultiSocketCallback(CURL* easy,
+ curl_socket_t s,
+ int what,
+ void* userp,
+ void* socketp) {
+ auto transport = static_cast<Transport*>(userp);
+ CHECK(transport) << "Transport must be set for this callback";
+ auto poll_data = static_cast<SocketPollData*>(socketp);
+ if (!poll_data) {
+ // We haven't attached polling data to this socket yet. Let's do this now.
+ poll_data = new SocketPollData{transport->curl_interface_,
+ transport->curl_multi_handle_,
+ transport,
+ s};
+ transport->poll_data_map_.emplace(std::make_pair(easy, s), poll_data);
+ transport->curl_interface_->MultiAssign(
+ transport->curl_multi_handle_, s, poll_data);
+ }
+
+ if (what == CURL_POLL_NONE) {
+ return 0;
+ } else if (what == CURL_POLL_REMOVE) {
+ // Remove the attached data from the socket.
+ transport->curl_interface_->MultiAssign(
+ transport->curl_multi_handle_, s, nullptr);
+ transport->poll_data_map_.erase(std::make_pair(easy, s));
+
+ // Make sure we stop watching the socket file descriptor now, before
+ // we schedule the SocketPollData for deletion.
+ poll_data->GetWatcher()->StopWatchingFileDescriptor();
+ // This method can be called indirectly from SocketPollData::OnSocketReady,
+ // so delay destruction of SocketPollData object till the next loop cycle.
+ base::MessageLoopForIO::current()->DeleteSoon(FROM_HERE, poll_data);
+ return 0;
+ }
+
+ base::MessageLoopForIO::Mode watch_mode = base::MessageLoopForIO::WATCH_READ;
+ switch (what) {
+ case CURL_POLL_IN:
+ watch_mode = base::MessageLoopForIO::WATCH_READ;
+ break;
+ case CURL_POLL_OUT:
+ watch_mode = base::MessageLoopForIO::WATCH_WRITE;
+ break;
+ case CURL_POLL_INOUT:
+ watch_mode = base::MessageLoopForIO::WATCH_READ_WRITE;
+ break;
+ default:
+ LOG(FATAL) << "Unknown CURL socket action: " << what;
+ break;
+ }
+
+ // WatchFileDescriptor() can be called with the same controller object
+ // (watcher) to amend the watch mode, however this has cumulative effect.
+ // For example, if we were watching a file descriptor for READ operations
+ // and now call it to watch for WRITE, it will end up watching for both
+ // READ and WRITE. This is not what we want here, so stop watching the
+ // file descriptor on previous controller before starting with a different
+ // mode.
+ if (!poll_data->GetWatcher()->StopWatchingFileDescriptor())
+ LOG(WARNING) << "Failed to stop watching the previous socket descriptor";
+ CHECK(base::MessageLoopForIO::current()->WatchFileDescriptor(
+ s, true, watch_mode, poll_data->GetWatcher(), poll_data))
+ << "Failed to watch the CURL socket.";
+ return 0;
+}
+
+// CURL actually uses "long" types in callback signatures, so we must comply.
+int Transport::MultiTimerCallback(CURLM* /* multi */,
+ long timeout_ms, // NOLINT(runtime/int)
+ void* userp) {
+ auto transport = static_cast<Transport*>(userp);
+ // Cancel any previous timer callbacks.
+ transport->weak_ptr_factory_for_timer_.InvalidateWeakPtrs();
+ if (timeout_ms >= 0) {
+ base::MessageLoopForIO::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&Transport::OnTimer,
+ transport->weak_ptr_factory_for_timer_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(timeout_ms));
+ }
+ return 0;
+}
+
+void Transport::OnTimer() {
+ if (curl_multi_handle_) {
+ int still_running_count = 0;
+ curl_interface_->MultiSocketAction(
+ curl_multi_handle_, CURL_SOCKET_TIMEOUT, 0, &still_running_count);
+ ProcessAsyncCurlMessages();
+ }
+}
+
+void Transport::ProcessAsyncCurlMessages() {
+ CURLMsg* msg = nullptr;
+ int msgs_left = 0;
+ while ((msg = curl_interface_->MultiInfoRead(curl_multi_handle_,
+ &msgs_left))) {
+ if (msg->msg == CURLMSG_DONE) {
+ // Async I/O complete for a connection. Invoke the user callbacks.
+ Connection* connection = nullptr;
+ CHECK_EQ(CURLE_OK,
+ curl_interface_->EasyGetInfoPtr(
+ msg->easy_handle,
+ CURLINFO_PRIVATE,
+ reinterpret_cast<void**>(&connection)));
+ CHECK(connection != nullptr);
+ OnTransferComplete(connection, msg->data.result);
+ }
+ }
+}
+
+void Transport::OnTransferComplete(Connection* connection, CURLcode code) {
+ auto p = async_requests_.find(connection);
+ CHECK(p != async_requests_.end()) << "Unknown connection";
+ AsyncRequestData* request_data = p->second.get();
+ LOG(INFO) << "HTTP request # " << request_data->request_id
+ << " has completed "
+ << (code == CURLE_OK ? "successfully" : "with an error");
+ if (code != CURLE_OK) {
+ brillo::ErrorPtr error;
+ AddEasyCurlError(&error, FROM_HERE, code, curl_interface_.get());
+ RunCallbackAsync(FROM_HERE,
+ base::Bind(request_data->error_callback,
+ p->second->request_id,
+ base::Owned(error.release())));
+ } else {
+ LOG(INFO) << "Response: " << connection->GetResponseStatusCode() << " ("
+ << connection->GetResponseStatusText() << ")";
+ brillo::ErrorPtr error;
+ // Rewind the response data stream to the beginning so the clients can
+ // read the data back.
+ const auto& stream = request_data->connection->response_data_stream_;
+ if (stream && stream->CanSeek() && !stream->SetPosition(0, &error)) {
+ RunCallbackAsync(FROM_HERE,
+ base::Bind(request_data->error_callback,
+ p->second->request_id,
+ base::Owned(error.release())));
+ } else {
+ std::unique_ptr<Response> resp{new Response{request_data->connection}};
+ RunCallbackAsync(FROM_HERE,
+ base::Bind(request_data->success_callback,
+ p->second->request_id,
+ base::Passed(&resp)));
+ }
+ }
+ // In case of an error on CURL side, we would have dispatched the error
+ // callback and we need to clean up the current connection, however the
+ // error callback has no reference to the connection itself and
+ // |async_requests_| is the only reference to the shared pointer that
+ // maintains the lifetime of |connection| and possibly even this Transport
+ // object instance. As a result, if we call CleanAsyncConnection() directly,
+ // there is a chance that this object might be deleted.
+ // Instead, schedule an asynchronous task to clean up the connection.
+ RunCallbackAsync(FROM_HERE,
+ base::Bind(&Transport::CleanAsyncConnection,
+ weak_ptr_factory_.GetWeakPtr(),
+ connection));
+}
+
+void Transport::CleanAsyncConnection(Connection* connection) {
+ auto p = async_requests_.find(connection);
+ CHECK(p != async_requests_.end()) << "Unknown connection";
+ // Remove the request data from the map first, since this might be the only
+ // reference to the Connection class and even possibly to this Transport.
+ auto request_data = std::move(p->second);
+
+ // Remove associated request ID.
+ request_id_map_.erase(request_data->request_id);
+
+ // Remove the connection's CURL handle from multi-handle.
+ curl_interface_->MultiRemoveHandle(curl_multi_handle_,
+ connection->curl_handle_);
+
+ // Remove all the socket data associated with this connection.
+ auto iter = poll_data_map_.begin();
+ while (iter != poll_data_map_.end()) {
+ if (iter->first.first == connection->curl_handle_)
+ iter = poll_data_map_.erase(iter);
+ else
+ ++iter;
+ }
+ // Remove pending asynchronous request data.
+ // This must be last since there is a chance of this object being
+ // destroyed as the result. See the comment in Transport::OnTransferComplete.
+ async_requests_.erase(p);
+}
+
+} // namespace curl
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_transport_curl.h b/libbrillo/brillo/http/http_transport_curl.h
new file mode 100644
index 0000000..0a95c63
--- /dev/null
+++ b/libbrillo/brillo/http/http_transport_curl.h
@@ -0,0 +1,140 @@
+// 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 LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_CURL_H_
+#define LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_CURL_H_
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include <base/memory/weak_ptr.h>
+#include <brillo/brillo_export.h>
+#include <brillo/http/curl_api.h>
+#include <brillo/http/http_transport.h>
+
+namespace brillo {
+namespace http {
+namespace curl {
+
+class Connection;
+
+///////////////////////////////////////////////////////////////////////////////
+// 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 BRILLO_EXPORT Transport : public http::Transport {
+ public:
+ // Constructs the transport using the current message loop for async
+ // operations.
+ explicit Transport(const std::shared_ptr<CurlInterface>& curl_interface);
+ // Creates a transport object using a proxy.
+ // |proxy| is of the form [protocol://][user:password@]host[:port].
+ // If not defined, protocol is assumed to be http://.
+ Transport(const std::shared_ptr<CurlInterface>& curl_interface,
+ const std::string& proxy);
+ ~Transport() override;
+
+ // Overrides from http::Transport.
+ std::shared_ptr<http::Connection> CreateConnection(
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ brillo::ErrorPtr* error) override;
+
+ void RunCallbackAsync(const tracked_objects::Location& from_here,
+ const base::Closure& callback) override;
+
+ RequestID StartAsyncTransfer(http::Connection* connection,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) override;
+
+ bool CancelRequest(RequestID request_id) override;
+
+ void SetDefaultTimeout(base::TimeDelta timeout) override;
+
+ // Helper methods to convert CURL error codes (CURLcode and CURLMcode)
+ // into brillo::Error object.
+ static void AddEasyCurlError(brillo::ErrorPtr* error,
+ const tracked_objects::Location& location,
+ CURLcode code,
+ CurlInterface* curl_interface);
+
+ static void AddMultiCurlError(brillo::ErrorPtr* error,
+ const tracked_objects::Location& location,
+ CURLMcode code,
+ CurlInterface* curl_interface);
+
+ private:
+ // Forward-declaration of internal implementation structures.
+ struct AsyncRequestData;
+ class SocketPollData;
+
+ // Initializes CURL for async operation.
+ bool SetupAsyncCurl(brillo::ErrorPtr* error);
+
+ // Stops CURL's async operations.
+ void ShutDownAsyncCurl();
+
+ // Handles all pending async messages from CURL.
+ void ProcessAsyncCurlMessages();
+
+ // Processes the transfer completion message (success or failure).
+ void OnTransferComplete(http::curl::Connection* connection,
+ CURLcode code);
+
+ // Cleans up internal data for a completed/canceled asynchronous operation
+ // on a connection.
+ void CleanAsyncConnection(http::curl::Connection* connection);
+
+ // Called after a timeout delay requested by CURL has elapsed.
+ void OnTimer();
+
+ // Callback for CURL to handle curl_socket_callback() notifications.
+ // The parameters correspond to those of curl_socket_callback().
+ static int MultiSocketCallback(CURL* easy,
+ curl_socket_t s,
+ int what,
+ void* userp,
+ void* socketp);
+
+ // Callback for CURL to handle curl_multi_timer_callback() notifications.
+ // The parameters correspond to those of curl_multi_timer_callback().
+ // CURL actually uses "long" types in callback signatures, so we must comply.
+ static int MultiTimerCallback(CURLM* multi,
+ long timeout_ms, // NOLINT(runtime/int)
+ void* userp);
+
+ std::shared_ptr<CurlInterface> curl_interface_;
+ std::string proxy_;
+ // CURL "multi"-handle for processing requests on multiple connections.
+ CURLM* curl_multi_handle_{nullptr};
+ // A map to find a corresponding Connection* using a request ID.
+ std::map<RequestID, Connection*> request_id_map_;
+ // Stores the connection-specific asynchronous data (such as the success
+ // and error callbacks that need to be called at the end of the async
+ // operation).
+ std::map<Connection*, std::unique_ptr<AsyncRequestData>> async_requests_;
+ // Internal data associated with in-progress asynchronous operations.
+ std::map<std::pair<CURL*, curl_socket_t>, SocketPollData*> poll_data_map_;
+ // The last request ID used for asynchronous operations.
+ RequestID last_request_id_{0};
+ // The connection timeout for the requests made.
+ base::TimeDelta connection_timeout_;
+
+ base::WeakPtrFactory<Transport> weak_ptr_factory_for_timer_{this};
+ base::WeakPtrFactory<Transport> weak_ptr_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(Transport);
+};
+
+} // namespace curl
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_CURL_H_
diff --git a/libbrillo/brillo/http/http_transport_curl_unittest.cc b/libbrillo/brillo/http/http_transport_curl_unittest.cc
new file mode 100644
index 0000000..702c9d9
--- /dev/null
+++ b/libbrillo/brillo/http/http_transport_curl_unittest.cc
@@ -0,0 +1,312 @@
+// 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 <brillo/http/http_transport_curl.h>
+
+#include <base/at_exit.h>
+#include <base/message_loop/message_loop.h>
+#include <base/run_loop.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/http/http_connection_curl.h>
+#include <brillo/http/http_request.h>
+#include <brillo/http/mock_curl_api.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::DoAll;
+using testing::Invoke;
+using testing::Return;
+using testing::SaveArg;
+using testing::SetArgPointee;
+using testing::WithoutArgs;
+using testing::_;
+
+namespace brillo {
+namespace http {
+namespace curl {
+
+class HttpCurlTransportTest : public testing::Test {
+ public:
+ void SetUp() override {
+ curl_api_ = std::make_shared<MockCurlInterface>();
+ transport_ = std::make_shared<Transport>(curl_api_);
+ handle_ = reinterpret_cast<CURL*>(100); // Mock handle value.
+ EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
+ EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CAPATH, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYPEER, 1))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYHOST, 2))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _))
+ .WillRepeatedly(Return(CURLE_OK));
+ }
+
+ void TearDown() override {
+ transport_.reset();
+ curl_api_.reset();
+ }
+
+ protected:
+ std::shared_ptr<MockCurlInterface> curl_api_;
+ std::shared_ptr<Transport> transport_;
+ CURL* handle_{nullptr};
+};
+
+TEST_F(HttpCurlTransportTest, RequestGet) {
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_USERAGENT, "User Agent"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_REFERER, "http://foo.bar/baz"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection("http://foo.bar/get",
+ request_type::kGet,
+ {},
+ "User Agent",
+ "http://foo.bar/baz",
+ nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+TEST_F(HttpCurlTransportTest, RequestHead) {
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/head"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_NOBODY, 1))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://foo.bar/head", request_type::kHead, {}, "", "", nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+TEST_F(HttpCurlTransportTest, RequestPut) {
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/put"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_UPLOAD, 1))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://foo.bar/put", request_type::kPut, {}, "", "", nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+TEST_F(HttpCurlTransportTest, RequestPost) {
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/post"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://www.foo.bar/post", request_type::kPost, {}, "", "", nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+TEST_F(HttpCurlTransportTest, RequestPatch) {
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/patch"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(
+ *curl_api_,
+ EasySetOptStr(handle_, CURLOPT_CUSTOMREQUEST, request_type::kPatch))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://www.foo.bar/patch", request_type::kPatch, {}, "", "", nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+TEST_F(HttpCurlTransportTest, CurlFailure) {
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
+ .WillOnce(Return(CURLE_OUT_OF_MEMORY));
+ EXPECT_CALL(*curl_api_, EasyStrError(CURLE_OUT_OF_MEMORY))
+ .WillOnce(Return("Out of Memory"));
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ ErrorPtr error;
+ auto connection = transport_->CreateConnection(
+ "http://foo.bar/get", request_type::kGet, {}, "", "", &error);
+
+ EXPECT_EQ(nullptr, connection.get());
+ EXPECT_EQ("curl_easy_error", error->GetDomain());
+ EXPECT_EQ(std::to_string(CURLE_OUT_OF_MEMORY), error->GetCode());
+ EXPECT_EQ("Out of Memory", error->GetMessage());
+}
+
+class HttpCurlTransportAsyncTest : public testing::Test {
+ public:
+ void SetUp() override {
+ curl_api_ = std::make_shared<MockCurlInterface>();
+ transport_ = std::make_shared<Transport>(curl_api_);
+ EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
+ EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CAPATH, _))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYPEER, 1))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYHOST, 2))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _))
+ .WillOnce(Return(CURLE_OK));
+ }
+
+ protected:
+ std::shared_ptr<MockCurlInterface> curl_api_;
+ std::shared_ptr<Transport> transport_;
+ CURL* handle_{reinterpret_cast<CURL*>(123)}; // Mock handle value.
+ CURLM* multi_handle_{reinterpret_cast<CURLM*>(456)}; // Mock handle value.
+ curl_socket_t dummy_socket_{789};
+};
+
+TEST_F(HttpCurlTransportAsyncTest, StartAsyncTransfer) {
+ // This test is a bit tricky because it deals with asynchronous I/O which
+ // relies on a message loop to run all the async tasks.
+ // For this, create a temporary I/O message loop and run it ourselves for the
+ // duration of the test.
+ base::MessageLoopForIO message_loop;
+ base::RunLoop run_loop;
+
+ // Initial expectations for creating a CURL connection.
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr);
+ ASSERT_NE(nullptr, connection.get());
+
+ // Success/error callback needed to report the result of an async operation.
+ int success_call_count = 0;
+ auto success_callback = [](int* success_call_count,
+ base::RunLoop* run_loop,
+ RequestID /* request_id */,
+ std::unique_ptr<http::Response> /* resp */) {
+ base::MessageLoop::current()->PostTask(FROM_HERE, run_loop->QuitClosure());
+ (*success_call_count)++;
+ };
+
+ auto error_callback = [](RequestID /* request_id */,
+ const Error* /* error */) {
+ FAIL() << "This callback shouldn't have been called";
+ };
+
+ EXPECT_CALL(*curl_api_, MultiInit()).WillOnce(Return(multi_handle_));
+ EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _))
+ .WillRepeatedly(DoAll(SetArgPointee<2>(200), Return(CURLE_OK)));
+
+ curl_socket_callback socket_callback = nullptr;
+ EXPECT_CALL(*curl_api_,
+ MultiSetSocketCallback(multi_handle_, _, transport_.get()))
+ .WillOnce(DoAll(SaveArg<1>(&socket_callback), Return(CURLM_OK)));
+
+ curl_multi_timer_callback timer_callback = nullptr;
+ EXPECT_CALL(*curl_api_,
+ MultiSetTimerCallback(multi_handle_, _, transport_.get()))
+ .WillOnce(DoAll(SaveArg<1>(&timer_callback), Return(CURLM_OK)));
+
+ EXPECT_CALL(*curl_api_, MultiAddHandle(multi_handle_, handle_))
+ .WillOnce(Return(CURLM_OK));
+
+ EXPECT_EQ(1,
+ transport_->StartAsyncTransfer(
+ connection.get(),
+ base::Bind(success_callback,
+ base::Unretained(&success_call_count),
+ base::Unretained(&run_loop)),
+ base::Bind(error_callback)));
+ EXPECT_EQ(0, success_call_count);
+
+ timer_callback(multi_handle_, 1, transport_.get());
+
+ auto do_socket_action = [&socket_callback, this] {
+ EXPECT_CALL(*curl_api_, MultiAssign(multi_handle_, dummy_socket_, _))
+ .Times(2).WillRepeatedly(Return(CURLM_OK));
+ EXPECT_EQ(0, socket_callback(handle_, dummy_socket_, CURL_POLL_REMOVE,
+ transport_.get(), nullptr));
+ };
+
+ EXPECT_CALL(*curl_api_,
+ MultiSocketAction(multi_handle_, CURL_SOCKET_TIMEOUT, 0, _))
+ .WillOnce(DoAll(SetArgPointee<3>(1),
+ WithoutArgs(Invoke(do_socket_action)),
+ Return(CURLM_OK)))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(0), Return(CURLM_OK)));
+
+ CURLMsg msg = {};
+ msg.msg = CURLMSG_DONE;
+ msg.easy_handle = handle_;
+ msg.data.result = CURLE_OK;
+
+ EXPECT_CALL(*curl_api_, MultiInfoRead(multi_handle_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(0), Return(&msg)))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(0), Return(nullptr)));
+ EXPECT_CALL(*curl_api_, EasyGetInfoPtr(handle_, CURLINFO_PRIVATE, _))
+ .WillRepeatedly(DoAll(SetArgPointee<2>(connection.get()),
+ Return(CURLE_OK)));
+
+ EXPECT_CALL(*curl_api_, MultiRemoveHandle(multi_handle_, handle_))
+ .WillOnce(Return(CURLM_OK));
+
+ // Just in case something goes wrong and |success_callback| isn't called,
+ // post a time-out quit closure to abort the message loop after 1 second.
+ message_loop.PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromSeconds(1));
+ run_loop.Run();
+ EXPECT_EQ(1, success_call_count);
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+
+ EXPECT_CALL(*curl_api_, MultiCleanup(multi_handle_))
+ .WillOnce(Return(CURLM_OK));
+ transport_.reset();
+}
+
+TEST_F(HttpCurlTransportTest, RequestGetTimeout) {
+ transport_->SetDefaultTimeout(base::TimeDelta::FromMilliseconds(2000));
+ EXPECT_CALL(*curl_api_,
+ EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_TIMEOUT_MS, 2000))
+ .WillOnce(Return(CURLE_OK));
+ EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
+ .WillOnce(Return(CURLE_OK));
+ auto connection = transport_->CreateConnection(
+ "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr);
+ EXPECT_NE(nullptr, connection.get());
+
+ EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
+ connection.reset();
+}
+
+} // namespace curl
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_transport_fake.cc b/libbrillo/brillo/http/http_transport_fake.cc
new file mode 100644
index 0000000..cda885f
--- /dev/null
+++ b/libbrillo/brillo/http/http_transport_fake.cc
@@ -0,0 +1,331 @@
+// 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 <brillo/http/http_transport_fake.h>
+
+#include <utility>
+
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/http/http_connection_fake.h>
+#include <brillo/http/http_request.h>
+#include <brillo/mime_utils.h>
+#include <brillo/streams/memory_stream.h>
+#include <brillo/strings/string_utils.h>
+#include <brillo/url_utils.h>
+
+namespace brillo {
+
+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::shared_ptr<http::Connection> Transport::CreateConnection(
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ brillo::ErrorPtr* error) {
+ std::shared_ptr<http::Connection> connection;
+ if (create_connection_error_) {
+ if (error)
+ *error = std::move(create_connection_error_);
+ return connection;
+ }
+ 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));
+ }
+ connection =
+ std::make_shared<http::fake::Connection>(url, method, shared_from_this());
+ CHECK(connection) << "Unable to create Connection object";
+ if (!connection->SendHeaders(headers_copy, error))
+ connection.reset();
+ request_count_++;
+ return connection;
+}
+
+void Transport::RunCallbackAsync(
+ const tracked_objects::Location& /* from_here */,
+ const base::Closure& callback) {
+ if (!async_) {
+ callback.Run();
+ return;
+ }
+ async_callback_queue_.push(callback);
+}
+
+bool Transport::HandleOneAsyncRequest() {
+ if (async_callback_queue_.empty())
+ return false;
+
+ base::Closure callback = async_callback_queue_.front();
+ async_callback_queue_.pop();
+ callback.Run();
+ return true;
+}
+
+void Transport::HandleAllAsyncRequests() {
+ while (!async_callback_queue_.empty())
+ HandleOneAsyncRequest();
+}
+
+http::RequestID Transport::StartAsyncTransfer(
+ http::Connection* /* connection */,
+ const SuccessCallback& /* success_callback */,
+ const ErrorCallback& /* error_callback */) {
+ // Fake transport doesn't use this method.
+ LOG(FATAL) << "This method should not be called on fake transport";
+ return 0;
+}
+
+bool Transport::CancelRequest(RequestID /* request_id */) {
+ return false;
+}
+
+void Transport::SetDefaultTimeout(base::TimeDelta /* timeout */) {
+}
+
+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) {
+ // Make sure we can override/replace existing handlers.
+ handlers_[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 = [](int status_code,
+ const std::string& reply_text,
+ const std::string& mime_type,
+ const ServerRequest& /* request */,
+ ServerResponse* response) {
+ response->ReplyText(status_code, reply_text, mime_type);
+ };
+ AddHandler(
+ url, method, base::Bind(handler, status_code, reply_text, mime_type));
+}
+
+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::SetData(StreamPtr stream) {
+ data_.clear();
+ if (stream) {
+ uint8_t buffer[1024];
+ size_t size = 0;
+ if (stream->CanGetSize())
+ data_.reserve(stream->GetRemainingSize());
+
+ do {
+ CHECK(stream->ReadBlocking(buffer, sizeof(buffer), &size, nullptr));
+ data_.insert(data_.end(), buffer, buffer + size);
+ } while (size > 0);
+ }
+}
+
+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 {
+ std::unique_ptr<base::DictionaryValue> result;
+ if (brillo::mime::RemoveParameters(
+ GetHeader(request_header::kContentType)) ==
+ brillo::mime::application::kJson) {
+ auto value = base::JSONReader::Read(GetDataAsString());
+ result = base::DictionaryValue::From(std::move(value));
+ }
+ return result;
+}
+
+std::string ServerRequestResponseBase::GetDataAsNormalizedJsonString() const {
+ std::string value;
+ // Make sure we serialize the JSON back without any pretty print so
+ // the string comparison works correctly.
+ auto json = GetDataAsJson();
+ if (json)
+ base::JSONWriter::Write(*json, &value);
+ return value;
+}
+
+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 = brillo::url::GetQueryStringParameters(url);
+ url_ = brillo::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 = brillo::mime::RemoveParameters(
+ GetHeader(request_header::kContentType));
+ if (mime_type == brillo::mime::application::kWwwFormUrlEncoded &&
+ !GetData().empty()) {
+ auto fields = brillo::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 std::string& mime_type) {
+ data_.clear();
+ status_code_ = status_code;
+ SetData(MemoryStream::OpenCopyOf(data, data_size, nullptr));
+ AddHeaders({{response_header::kContentLength,
+ brillo::string_utils::ToString(data_size)},
+ {response_header::kContentType, mime_type}});
+}
+
+void ServerResponse::ReplyText(int status_code,
+ const std::string& text,
+ const std::string& 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 =
+ brillo::mime::AppendParameter(brillo::mime::application::kJson,
+ brillo::mime::parameters::kCharset,
+ "utf-8");
+ ReplyText(status_code, text, mime_type);
+}
+
+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 brillo
diff --git a/libbrillo/brillo/http/http_transport_fake.h b/libbrillo/brillo/http/http_transport_fake.h
new file mode 100644
index 0000000..ac8bc2a
--- /dev/null
+++ b/libbrillo/brillo/http/http_transport_fake.h
@@ -0,0 +1,265 @@
+// 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 LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_FAKE_H_
+#define LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_FAKE_H_
+
+#include <map>
+#include <queue>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/values.h>
+#include <brillo/http/http_transport.h>
+#include <brillo/http/http_utils.h>
+
+namespace brillo {
+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();
+ ~Transport() override;
+
+ // Server handler callback signature.
+ using HandlerCallback =
+ base::Callback<void(const ServerRequest&, ServerResponse*)>;
+
+ // 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; }
+
+ // For tests that wish to simulate critical transport errors, this method
+ // can be used to specify the error to be returned when creating a connection.
+ void SetCreateConnectionError(brillo::ErrorPtr create_connection_error) {
+ create_connection_error_ = std::move(create_connection_error);
+ }
+
+ // For tests that really need async operations with message loop, call this
+ // function with true.
+ void SetAsyncMode(bool async) { async_ = async; }
+
+ // Pops one callback from the top of |async_callback_queue_| and invokes it.
+ // Returns false if the queue is empty.
+ bool HandleOneAsyncRequest();
+
+ // Invokes all the callbacks currently queued in |async_callback_queue_|.
+ void HandleAllAsyncRequests();
+
+ // Overrides from http::Transport.
+ std::shared_ptr<http::Connection> CreateConnection(
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ brillo::ErrorPtr* error) override;
+
+ void RunCallbackAsync(const tracked_objects::Location& from_here,
+ const base::Closure& callback) override;
+
+ RequestID StartAsyncTransfer(http::Connection* connection,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) override;
+
+ bool CancelRequest(RequestID request_id) override;
+
+ void SetDefaultTimeout(base::TimeDelta timeout) 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};
+ bool async_{false};
+ // A list of queued callbacks that need to be called at some point.
+ // Call HandleOneAsyncRequest() or HandleAllAsyncRequests() to invoke them.
+ std::queue<base::Closure> async_callback_queue_;
+
+ // Fake error to be returned from CreateConnection method.
+ brillo::ErrorPtr create_connection_error_;
+
+ 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 SetData(StreamPtr stream);
+ const std::vector<uint8_t>& GetData() const { return data_; }
+ std::string GetDataAsString() const;
+ std::unique_ptr<base::DictionaryValue> GetDataAsJson() const;
+ // Parses the data into a JSON object and writes it back to JSON to normalize
+ // its string representation (no pretty print, extra spaces, etc).
+ std::string GetDataAsNormalizedJsonString() const;
+
+ // Add/retrieve request/response HTTP headers.
+ void AddHeaders(const HeaderList& headers);
+ std::string GetHeader(const std::string& header_name) const;
+ const std::multimap<std::string, std::string>& GetHeaders() const {
+ return headers_;
+ }
+
+ protected:
+ // Data buffer.
+ std::vector<uint8_t> data_;
+ // Header map.
+ std::multimap<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 std::string& mime_type);
+ // Reply with text body.
+ void ReplyText(int status_code,
+ const std::string& text,
+ const std::string& 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 std::string& 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 std::string& 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 brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_FAKE_H_
diff --git a/libbrillo/brillo/http/http_utils.cc b/libbrillo/brillo/http/http_utils.cc
new file mode 100644
index 0000000..6132d11
--- /dev/null
+++ b/libbrillo/brillo/http/http_utils.cc
@@ -0,0 +1,437 @@
+// 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 <brillo/http/http_utils.h>
+
+#include <algorithm>
+
+#include <base/bind.h>
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/values.h>
+#include <brillo/data_encoding.h>
+#include <brillo/errors/error_codes.h>
+#include <brillo/mime_utils.h>
+#include <brillo/streams/memory_stream.h>
+
+using brillo::mime::AppendParameter;
+using brillo::mime::RemoveParameters;
+
+namespace brillo {
+namespace http {
+
+std::unique_ptr<Response> GetAndBlock(const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error) {
+ return SendRequestWithNoDataAndBlock(
+ request_type::kGet, url, headers, transport, error);
+}
+
+RequestID Get(const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ return SendRequestWithNoData(request_type::kGet,
+ url,
+ headers,
+ transport,
+ success_callback,
+ error_callback);
+}
+
+std::unique_ptr<Response> HeadAndBlock(const std::string& url,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error) {
+ return SendRequestWithNoDataAndBlock(
+ request_type::kHead, url, {}, transport, error);
+}
+
+RequestID Head(const std::string& url,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ return SendRequestWithNoData(request_type::kHead,
+ url,
+ {},
+ transport,
+ success_callback,
+ error_callback);
+}
+
+std::unique_ptr<Response> PostTextAndBlock(const std::string& url,
+ const std::string& data,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error) {
+ return PostBinaryAndBlock(
+ url, data.data(), data.size(), mime_type, headers, transport, error);
+}
+
+RequestID PostText(const std::string& url,
+ const std::string& data,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ return PostBinary(url,
+ data.data(),
+ data.size(),
+ mime_type,
+ headers,
+ transport,
+ success_callback,
+ error_callback);
+}
+
+std::unique_ptr<Response> SendRequestAndBlock(
+ const std::string& method,
+ const std::string& url,
+ const void* data,
+ size_t data_size,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error) {
+ Request request(url, method, transport);
+ request.AddHeaders(headers);
+ if (data_size > 0) {
+ CHECK(!mime_type.empty()) << "MIME type must be specified if request body "
+ "message is provided";
+ request.SetContentType(mime_type);
+ if (!request.AddRequestBody(data, data_size, error))
+ return std::unique_ptr<Response>();
+ }
+ return request.GetResponseAndBlock(error);
+}
+
+std::unique_ptr<Response> SendRequestWithNoDataAndBlock(
+ const std::string& method,
+ const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error) {
+ return SendRequestAndBlock(
+ method, url, nullptr, 0, {}, headers, transport, error);
+}
+
+RequestID SendRequest(const std::string& method,
+ const std::string& url,
+ StreamPtr stream,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ Request request(url, method, transport);
+ request.AddHeaders(headers);
+ if (stream && (!stream->CanGetSize() || stream->GetRemainingSize() > 0)) {
+ CHECK(!mime_type.empty()) << "MIME type must be specified if request body "
+ "message is provided";
+ request.SetContentType(mime_type);
+ brillo::ErrorPtr error;
+ if (!request.AddRequestBody(std::move(stream), &error)) {
+ transport->RunCallbackAsync(
+ FROM_HERE, base::Bind(error_callback,
+ 0, base::Owned(error.release())));
+ return 0;
+ }
+ }
+ return request.GetResponse(success_callback, error_callback);
+}
+
+RequestID SendRequest(const std::string& method,
+ const std::string& url,
+ const void* data,
+ size_t data_size,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ return SendRequest(method,
+ url,
+ MemoryStream::OpenCopyOf(data, data_size, nullptr),
+ mime_type,
+ headers,
+ transport,
+ success_callback,
+ error_callback);
+}
+
+RequestID SendRequestWithNoData(const std::string& method,
+ const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ return SendRequest(method,
+ url,
+ {},
+ {},
+ headers,
+ transport,
+ success_callback,
+ error_callback);
+}
+
+std::unique_ptr<Response> PostBinaryAndBlock(
+ const std::string& url,
+ const void* data,
+ size_t data_size,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error) {
+ return SendRequestAndBlock(request_type::kPost,
+ url,
+ data,
+ data_size,
+ mime_type,
+ headers,
+ transport,
+ error);
+}
+
+RequestID PostBinary(const std::string& url,
+ StreamPtr stream,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ return SendRequest(request_type::kPost,
+ url,
+ std::move(stream),
+ mime_type,
+ headers,
+ transport,
+ success_callback,
+ error_callback);
+}
+
+RequestID PostBinary(const std::string& url,
+ const void* data,
+ size_t data_size,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ return SendRequest(request_type::kPost,
+ url,
+ data,
+ data_size,
+ mime_type,
+ headers,
+ transport,
+ success_callback,
+ error_callback);
+}
+
+std::unique_ptr<Response> PostFormDataAndBlock(
+ const std::string& url,
+ const FormFieldList& data,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error) {
+ std::string encoded_data = brillo::data_encoding::WebParamsEncode(data);
+ return PostBinaryAndBlock(url,
+ encoded_data.c_str(),
+ encoded_data.size(),
+ brillo::mime::application::kWwwFormUrlEncoded,
+ headers,
+ transport,
+ error);
+}
+
+std::unique_ptr<Response> PostFormDataAndBlock(
+ const std::string& url,
+ std::unique_ptr<FormData> form_data,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error) {
+ Request request(url, request_type::kPost, transport);
+ request.AddHeaders(headers);
+ if (!request.AddRequestBodyAsFormData(std::move(form_data), error))
+ return std::unique_ptr<Response>();
+ return request.GetResponseAndBlock(error);
+}
+
+RequestID PostFormData(const std::string& url,
+ const FormFieldList& data,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ std::string encoded_data = brillo::data_encoding::WebParamsEncode(data);
+ return PostBinary(url,
+ encoded_data.c_str(),
+ encoded_data.size(),
+ brillo::mime::application::kWwwFormUrlEncoded,
+ headers,
+ transport,
+ success_callback,
+ error_callback);
+}
+
+RequestID PostFormData(const std::string& url,
+ std::unique_ptr<FormData> form_data,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ Request request(url, request_type::kPost, transport);
+ request.AddHeaders(headers);
+ brillo::ErrorPtr error;
+ if (!request.AddRequestBodyAsFormData(std::move(form_data), &error)) {
+ transport->RunCallbackAsync(
+ FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release())));
+ return 0;
+ }
+ return request.GetResponse(success_callback, error_callback);
+}
+
+std::unique_ptr<Response> PostJsonAndBlock(const std::string& url,
+ const base::Value* json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error) {
+ std::string data;
+ if (json)
+ base::JSONWriter::Write(*json, &data);
+ std::string mime_type = AppendParameter(brillo::mime::application::kJson,
+ brillo::mime::parameters::kCharset,
+ "utf-8");
+ return PostBinaryAndBlock(
+ url, data.c_str(), data.size(), mime_type, headers, transport, error);
+}
+
+RequestID PostJson(const std::string& url,
+ std::unique_ptr<base::Value> json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ std::string data;
+ if (json)
+ base::JSONWriter::Write(*json, &data);
+ std::string mime_type = AppendParameter(brillo::mime::application::kJson,
+ brillo::mime::parameters::kCharset,
+ "utf-8");
+ return PostBinary(url,
+ data.c_str(),
+ data.size(),
+ mime_type,
+ headers,
+ transport,
+ success_callback,
+ error_callback);
+}
+
+std::unique_ptr<Response> PatchJsonAndBlock(
+ const std::string& url,
+ const base::Value* json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error) {
+ std::string data;
+ if (json)
+ base::JSONWriter::Write(*json, &data);
+ std::string mime_type = AppendParameter(brillo::mime::application::kJson,
+ brillo::mime::parameters::kCharset,
+ "utf-8");
+ return SendRequestAndBlock(request_type::kPatch,
+ url,
+ data.c_str(),
+ data.size(),
+ mime_type,
+ headers,
+ transport,
+ error);
+}
+
+RequestID PatchJson(const std::string& url,
+ std::unique_ptr<base::Value> json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ std::string data;
+ if (json)
+ base::JSONWriter::Write(*json, &data);
+ std::string mime_type =
+ AppendParameter(brillo::mime::application::kJson,
+ brillo::mime::parameters::kCharset, "utf-8");
+ return SendRequest(request_type::kPatch, url, data.c_str(), data.size(),
+ mime_type, headers, transport, success_callback,
+ error_callback);
+}
+
+std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
+ Response* response,
+ int* status_code,
+ brillo::ErrorPtr* error) {
+ std::unique_ptr<base::DictionaryValue> result;
+ if (!response)
+ return result;
+
+ 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 = RemoveParameters(response->GetContentType());
+ if (content_type != brillo::mime::application::kJson &&
+ content_type != brillo::mime::text::kPlain) {
+ brillo::Error::AddTo(error, FROM_HERE, brillo::errors::json::kDomain,
+ "non_json_content_type",
+ "Unexpected response content type: " + content_type);
+ return result;
+ }
+
+ std::string json = response->ExtractDataAsString();
+ std::string error_message;
+ auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC,
+ nullptr, &error_message);
+ if (!value) {
+ brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::json::kDomain,
+ brillo::errors::json::kParseError,
+ "Error '%s' occurred parsing JSON string '%s'",
+ error_message.c_str(), json.c_str());
+ return result;
+ }
+ result = base::DictionaryValue::From(std::move(value));
+ if (!result) {
+ brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::json::kDomain,
+ brillo::errors::json::kObjectExpected,
+ "Response is not a valid JSON object: '%s'",
+ json.c_str());
+ }
+ return result;
+}
+
+std::string GetCanonicalHeaderName(const std::string& name) {
+ std::string canonical_name = name;
+ bool word_begin = true;
+ for (char& c : canonical_name) {
+ if (c == '-') {
+ word_begin = true;
+ } else {
+ if (word_begin) {
+ c = toupper(c);
+ } else {
+ c = tolower(c);
+ }
+ word_begin = false;
+ }
+ }
+ return canonical_name;
+}
+
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/http_utils.h b/libbrillo/brillo/http/http_utils.h
new file mode 100644
index 0000000..e09bab8
--- /dev/null
+++ b/libbrillo/brillo/http/http_utils.h
@@ -0,0 +1,319 @@
+// 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 LIBBRILLO_BRILLO_HTTP_HTTP_UTILS_H_
+#define LIBBRILLO_BRILLO_HTTP_HTTP_UTILS_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <brillo/brillo_export.h>
+#include <brillo/errors/error.h>
+#include <brillo/http/http_form_data.h>
+#include <brillo/http/http_request.h>
+
+namespace base {
+class Value;
+class DictionaryValue;
+} // namespace base
+
+namespace brillo {
+namespace http {
+
+using FormFieldList = std::vector<std::pair<std::string, std::string>>;
+
+////////////////////////////////////////////////////////////////////////////////
+// The following are simple utility helper functions for common HTTP operations
+// that use http::Request object behind the scenes and set it up accordingly.
+// The values for request method, data MIME type, request header names should
+// not be directly encoded in most cases, but use predefined constants from
+// http_request.h.
+// So, instead of calling:
+// SendRequestAndBlock("POST",
+// "http://url",
+// "data", 4,
+// "text/plain",
+// {{"Authorization", "Bearer TOKEN"}},
+// transport, error);
+// You should do use this instead:
+// SendRequestAndBlock(brillo::http::request_type::kPost,
+// "http://url",
+// "data", 4,
+// brillo::mime::text::kPlain,
+// {{brillo::http::request_header::kAuthorization,
+// "Bearer TOKEN"}},
+// transport, error);
+//
+// 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.
+BRILLO_EXPORT std::unique_ptr<Response> SendRequestAndBlock(
+ const std::string& method,
+ const std::string& url,
+ const void* data,
+ size_t data_size,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error);
+
+// Same as above, but without sending the request body.
+// This is especially useful for requests like "GET" and "HEAD".
+BRILLO_EXPORT std::unique_ptr<Response> SendRequestWithNoDataAndBlock(
+ const std::string& method,
+ const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error);
+
+// Same as above but asynchronous. On success, |success_callback| is called
+// with the response object. On failure, |error_callback| is called with the
+// error details.
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID SendRequest(
+ const std::string& method,
+ const std::string& url,
+ StreamPtr stream,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// Same as above, but takes a memory buffer. The pointer should be valid only
+// until the function returns. The data is copied into an internal buffer to be
+// available for the duration of the asynchronous operation.
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID SendRequest(
+ const std::string& method,
+ const std::string& url,
+ const void* data,
+ size_t data_size,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// Asynchronous version of SendRequestNoData().
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID SendRequestWithNoData(
+ const std::string& method,
+ const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// Performs a GET request. Success status, returned data and additional
+// information (such as returned HTTP headers) can be obtained from
+// the returned Response object.
+BRILLO_EXPORT std::unique_ptr<Response> GetAndBlock(
+ const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error);
+
+// Asynchronous version of http::Get().
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID Get(
+ const std::string& url,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// Performs a HEAD request. Success status and additional
+// information (such as returned HTTP headers) can be obtained from
+// the returned Response object.
+BRILLO_EXPORT std::unique_ptr<Response> HeadAndBlock(
+ const std::string& url,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error);
+
+// Performs an asynchronous HEAD request.
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID Head(
+ const std::string& url,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// 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.
+BRILLO_EXPORT std::unique_ptr<Response> PostBinaryAndBlock(
+ const std::string& url,
+ const void* data,
+ size_t data_size,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error);
+
+// Async version of PostBinary().
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID PostBinary(
+ const std::string& url,
+ StreamPtr stream,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// Same as above, but takes a memory buffer. The pointer should be valid only
+// until the function returns. The data is copied into an internal buffer
+// to be available for the duration of the asynchronous operation.
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID PostBinary(
+ const std::string& url,
+ const void* data,
+ size_t data_size,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// 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.
+BRILLO_EXPORT std::unique_ptr<Response> PostTextAndBlock(
+ const std::string& url,
+ const std::string& data,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error);
+
+// Async version of PostText().
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID PostText(
+ const std::string& url,
+ const std::string& data,
+ const std::string& mime_type,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// 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".
+BRILLO_EXPORT std::unique_ptr<Response> PostFormDataAndBlock(
+ const std::string& url,
+ const FormFieldList& data,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error);
+
+// Async version of PostFormData() above.
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID PostFormData(
+ const std::string& url,
+ const FormFieldList& data,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// Performs a POST request with form data, including binary file uploads.
+// Success status, returned data and additional information (such as returned
+// HTTP headers) can be obtained from the returned Response object.
+// The data is posed as "multipart/form-data".
+BRILLO_EXPORT std::unique_ptr<Response> PostFormDataAndBlock(
+ const std::string& url,
+ std::unique_ptr<FormData> form_data,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error);
+
+// Async version of PostFormData() above.
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID PostFormData(
+ const std::string& url,
+ std::unique_ptr<FormData> form_data,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// 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.
+BRILLO_EXPORT std::unique_ptr<Response> PostJsonAndBlock(
+ const std::string& url,
+ const base::Value* json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error);
+
+// Async version of PostJson().
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID PostJson(
+ const std::string& url,
+ std::unique_ptr<base::Value> json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// 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.
+BRILLO_EXPORT std::unique_ptr<Response> PatchJsonAndBlock(
+ const std::string& url,
+ const base::Value* json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ brillo::ErrorPtr* error);
+
+// Async version of PatchJson().
+// Returns the ID of the request which can be used to cancel the pending
+// request using Transport::CancelRequest().
+BRILLO_EXPORT RequestID PatchJson(
+ const std::string& url,
+ std::unique_ptr<base::Value> json,
+ const HeaderList& headers,
+ std::shared_ptr<Transport> transport,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback);
+
+// 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.
+BRILLO_EXPORT std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
+ Response* response,
+ int* status_code,
+ brillo::ErrorPtr* error);
+
+// Converts a request header name to canonical form (lowercase with uppercase
+// first letter and each letter after a hyphen ('-')).
+// "content-TYPE" will be converted to "Content-Type".
+BRILLO_EXPORT std::string GetCanonicalHeaderName(const std::string& name);
+
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_HTTP_UTILS_H_
diff --git a/libbrillo/brillo/http/http_utils_unittest.cc b/libbrillo/brillo/http/http_utils_unittest.cc
new file mode 100644
index 0000000..376ba53
--- /dev/null
+++ b/libbrillo/brillo/http/http_utils_unittest.cc
@@ -0,0 +1,502 @@
+// 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 <numeric>
+#include <string>
+#include <vector>
+
+#include <base/values.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/http/http_transport_fake.h>
+#include <brillo/http/http_utils.h>
+#include <brillo/mime_utils.h>
+#include <brillo/strings/string_utils.h>
+#include <brillo/url_utils.h>
+#include <gtest/gtest.h>
+
+namespace brillo {
+namespace http {
+
+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));
+}
+
+// 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(), brillo::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<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
+ auto response =
+ http::SendRequestAndBlock(request_type::kPost,
+ kEchoUrl,
+ custom_data.data(),
+ custom_data.size(),
+ brillo::mime::application::kOctet_stream,
+ {},
+ transport,
+ nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(brillo::mime::application::kOctet_stream,
+ response->GetContentType());
+ EXPECT_EQ(custom_data, response->ExtractData());
+}
+
+TEST(HttpUtils, SendRequestAsync_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<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
+ auto success_callback = [](const std::vector<uint8_t>& custom_data,
+ RequestID /* id */,
+ std::unique_ptr<http::Response> response) {
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(brillo::mime::application::kOctet_stream,
+ response->GetContentType());
+ EXPECT_EQ(custom_data, response->ExtractData());
+ };
+ auto error_callback = [](RequestID /* id */, const Error* /* error */) {
+ FAIL() << "This callback shouldn't have been called";
+ };
+ http::SendRequest(request_type::kPost,
+ kEchoUrl,
+ custom_data.data(),
+ custom_data.size(),
+ brillo::mime::application::kOctet_stream,
+ {},
+ transport,
+ base::Bind(success_callback, custom_data),
+ base::Bind(error_callback));
+}
+
+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<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
+
+ // Check the correct HTTP method used.
+ auto response =
+ http::SendRequestAndBlock(request_type::kPost,
+ kMethodEchoUrl,
+ custom_data.data(),
+ custom_data.size(),
+ brillo::mime::application::kOctet_stream,
+ {},
+ transport,
+ nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(request_type::kPost, response->ExtractDataAsString());
+}
+
+TEST(HttpUtils, SendRequest_Get) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+
+ auto response = http::SendRequestAndBlock(request_type::kGet,
+ kMethodEchoUrl,
+ nullptr,
+ 0,
+ std::string{},
+ {},
+ transport,
+ nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(request_type::kGet, response->ExtractDataAsString());
+}
+
+TEST(HttpUtils, SendRequest_Put) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+
+ auto response = http::SendRequestAndBlock(request_type::kPut,
+ kMethodEchoUrl,
+ nullptr,
+ 0,
+ std::string{},
+ {},
+ transport,
+ nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(request_type::kPut, response->ExtractDataAsString());
+}
+
+TEST(HttpUtils, SendRequest_NotFound) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ // Test failed response (URL not found).
+ auto response = http::SendRequestWithNoDataAndBlock(
+ request_type::kGet, "http://blah.com", {}, transport, nullptr);
+ EXPECT_FALSE(response->IsSuccessful());
+ EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
+}
+
+TEST(HttpUtils, SendRequestAsync_NotFound) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ // Test failed response (URL not found).
+ auto success_callback =
+ [](RequestID /* request_id */, std::unique_ptr<http::Response> response) {
+ EXPECT_FALSE(response->IsSuccessful());
+ EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
+ };
+ auto error_callback = [](RequestID /* request_id */,
+ const Error* /* error */) {
+ FAIL() << "This callback shouldn't have been called";
+ };
+ http::SendRequestWithNoData(request_type::kGet,
+ "http://blah.com",
+ {},
+ transport,
+ base::Bind(success_callback),
+ base::Bind(error_callback));
+}
+
+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::SendRequestAndBlock(
+ request_type::kPost, json_echo_url, "abcd", 4,
+ brillo::mime::application::kOctet_stream, {
+ {request_header::kCookie, "flavor=vanilla"},
+ {request_header::kIfMatch, "*"},
+ }, transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(brillo::mime::application::kJson,
+ brillo::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(brillo::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"),
+ brillo::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() actually does the GET request
+ auto response = http::GetAndBlock(kMethodEchoUrl, {}, transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(request_type::kGet, response->ExtractDataAsString());
+
+ for (std::string data : {"blah", "some data", ""}) {
+ std::string url = brillo::url::AppendQueryParam(kFakeUrl, "test", data);
+ response = http::GetAndBlock(url, {}, transport, nullptr);
+ EXPECT_EQ(data, response->ExtractDataAsString());
+ }
+}
+
+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", brillo::mime::text::kPlain);
+ };
+
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kFakeUrl, request_type::kHead, base::Bind(HeadHandler));
+
+ auto response = http::HeadAndBlock(kFakeUrl, transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ("", response->ExtractDataAsString()); // 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(brillo::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, "", brillo::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<uint8_t> data(256);
+ std::iota(data.begin(), data.end(), 0);
+
+ auto response = http::PostBinaryAndBlock(kFakeUrl,
+ data.data(),
+ data.size(),
+ mime::application::kOctet_stream,
+ {},
+ transport,
+ nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+}
+
+TEST(HttpUtils, PostText) {
+ std::string fake_data = "Some data";
+ auto PostHandler = [](const std::string& 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(brillo::mime::text::kPlain,
+ request.GetHeader(request_header::kContentType));
+ response->ReplyText(status_code::Ok,
+ request.GetDataAsString(),
+ brillo::mime::text::kPlain);
+ };
+
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(
+ kFakeUrl, request_type::kPost, base::Bind(PostHandler, fake_data));
+
+ auto response = http::PostTextAndBlock(kFakeUrl,
+ fake_data,
+ brillo::mime::text::kPlain,
+ {},
+ transport,
+ nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(fake_data, response->ExtractDataAsString());
+}
+
+TEST(HttpUtils, PostFormData) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(
+ kFakeUrl, request_type::kPost, base::Bind(EchoDataHandler));
+
+ auto response = http::PostFormDataAndBlock(
+ kFakeUrl, {
+ {"key", "value"},
+ {"field", "field value"},
+ }, {}, transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(brillo::mime::application::kWwwFormUrlEncoded,
+ response->GetContentType());
+ EXPECT_EQ("key=value&field=field+value", response->ExtractDataAsString());
+}
+
+TEST(HttpUtils, PostMultipartFormData) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(
+ kFakeUrl, request_type::kPost, base::Bind(EchoDataHandler));
+
+ std::unique_ptr<FormData> form_data{new FormData{"boundary123"}};
+ form_data->AddTextField("key1", "value1");
+ form_data->AddTextField("key2", "value2");
+ std::string expected_content_type = form_data->GetContentType();
+ auto response = http::PostFormDataAndBlock(
+ kFakeUrl, std::move(form_data), {}, transport, nullptr);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(expected_content_type, response->GetContentType());
+ const char expected_value[] =
+ "--boundary123\r\n"
+ "Content-Disposition: form-data; name=\"key1\"\r\n"
+ "\r\n"
+ "value1\r\n"
+ "--boundary123\r\n"
+ "Content-Disposition: form-data; name=\"key2\"\r\n"
+ "\r\n"
+ "value2\r\n"
+ "--boundary123--";
+ EXPECT_EQ(expected_value, response->ExtractDataAsString());
+}
+
+TEST(HttpUtils, PostPatchJson) {
+ auto JsonHandler =
+ [](const fake::ServerRequest& request, fake::ServerResponse* response) {
+ auto mime_type = brillo::mime::RemoveParameters(
+ request.GetHeader(request_header::kContentType));
+ EXPECT_EQ(brillo::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::PostJsonAndBlock(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::PatchJsonAndBlock(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 = brillo::string_utils::SplitAtFirst(item, ";");
+ auto response = http::PostFormDataAndBlock(
+ 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, brillo::string_utils::ToString(code));
+ EXPECT_EQ(pair.second, value);
+ }
+
+ // Test invalid (non-JSON) response.
+ auto response = http::GetAndBlock("http://bad.url", {}, transport, nullptr);
+ EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
+ EXPECT_EQ(brillo::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);
+}
+
+TEST(HttpUtils, SendRequest_Failure) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+ ErrorPtr error;
+ Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message");
+ transport->SetCreateConnectionError(std::move(error));
+ error.reset(); // Just to make sure it is empty...
+ auto response = http::SendRequestWithNoDataAndBlock(
+ request_type::kGet, "http://blah.com", {}, transport, &error);
+ EXPECT_EQ(nullptr, response.get());
+ EXPECT_EQ("test_domain", error->GetDomain());
+ EXPECT_EQ("test_code", error->GetCode());
+ EXPECT_EQ("Test message", error->GetMessage());
+}
+
+TEST(HttpUtils, SendRequestAsync_Failure) {
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
+ ErrorPtr error;
+ Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message");
+ transport->SetCreateConnectionError(std::move(error));
+ auto success_callback =
+ [](RequestID /* request_id */,
+ std::unique_ptr<http::Response> /* response */) {
+ FAIL() << "This callback shouldn't have been called";
+ };
+ auto error_callback = [](RequestID /* request_id */, const Error* error) {
+ EXPECT_EQ("test_domain", error->GetDomain());
+ EXPECT_EQ("test_code", error->GetCode());
+ EXPECT_EQ("Test message", error->GetMessage());
+ };
+ http::SendRequestWithNoData(request_type::kGet,
+ "http://blah.com",
+ {},
+ transport,
+ base::Bind(success_callback),
+ base::Bind(error_callback));
+}
+
+TEST(HttpUtils, GetCanonicalHeaderName) {
+ EXPECT_EQ("Foo", GetCanonicalHeaderName("foo"));
+ EXPECT_EQ("Bar", GetCanonicalHeaderName("BaR"));
+ EXPECT_EQ("Baz", GetCanonicalHeaderName("BAZ"));
+ EXPECT_EQ("Foo-Bar", GetCanonicalHeaderName("foo-bar"));
+ EXPECT_EQ("Foo-Bar-Baz", GetCanonicalHeaderName("foo-Bar-BAZ"));
+ EXPECT_EQ("Foo-Bar-Baz", GetCanonicalHeaderName("FOO-BAR-BAZ"));
+ EXPECT_EQ("Foo-Bar-", GetCanonicalHeaderName("fOO-bAR-"));
+ EXPECT_EQ("-Bar", GetCanonicalHeaderName("-bAR"));
+ EXPECT_EQ("", GetCanonicalHeaderName(""));
+ EXPECT_EQ("A-B-C", GetCanonicalHeaderName("a-B-c"));
+}
+
+} // namespace http
+} // namespace brillo
diff --git a/libbrillo/brillo/http/mock_connection.h b/libbrillo/brillo/http/mock_connection.h
new file mode 100644
index 0000000..0796a7e
--- /dev/null
+++ b/libbrillo/brillo/http/mock_connection.h
@@ -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.
+
+#ifndef LIBBRILLO_BRILLO_HTTP_MOCK_CONNECTION_H_
+#define LIBBRILLO_BRILLO_HTTP_MOCK_CONNECTION_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <brillo/http/http_connection.h>
+#include <gmock/gmock.h>
+
+namespace brillo {
+namespace http {
+
+class MockConnection : public Connection {
+ public:
+ using Connection::Connection;
+
+ MOCK_METHOD2(SendHeaders, bool(const HeaderList&, ErrorPtr*));
+ MOCK_METHOD2(MockSetRequestData, bool(Stream*, ErrorPtr*));
+ MOCK_METHOD1(MockSetResponseData, void(Stream*));
+ MOCK_METHOD1(FinishRequest, bool(ErrorPtr*));
+ MOCK_METHOD2(FinishRequestAsync,
+ RequestID(const SuccessCallback&, const ErrorCallback&));
+ MOCK_CONST_METHOD0(GetResponseStatusCode, int());
+ MOCK_CONST_METHOD0(GetResponseStatusText, std::string());
+ MOCK_CONST_METHOD0(GetProtocolVersion, std::string());
+ MOCK_CONST_METHOD1(GetResponseHeader, std::string(const std::string&));
+ MOCK_CONST_METHOD1(MockExtractDataStream, Stream*(brillo::ErrorPtr*));
+
+ private:
+ bool SetRequestData(StreamPtr stream, brillo::ErrorPtr* error) override {
+ return MockSetRequestData(stream.get(), error);
+ }
+ void SetResponseData(StreamPtr stream) override {
+ MockSetResponseData(stream.get());
+ }
+ StreamPtr ExtractDataStream(brillo::ErrorPtr* error) override {
+ return StreamPtr{MockExtractDataStream(error)};
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(MockConnection);
+};
+
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_MOCK_CONNECTION_H_
diff --git a/libbrillo/brillo/http/mock_curl_api.h b/libbrillo/brillo/http/mock_curl_api.h
new file mode 100644
index 0000000..32b6e0d
--- /dev/null
+++ b/libbrillo/brillo/http/mock_curl_api.h
@@ -0,0 +1,59 @@
+// 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 LIBBRILLO_BRILLO_HTTP_MOCK_CURL_API_H_
+#define LIBBRILLO_BRILLO_HTTP_MOCK_CURL_API_H_
+
+#include <string>
+
+#include <brillo/http/curl_api.h>
+#include <gmock/gmock.h>
+
+namespace brillo {
+namespace http {
+
+// This is a mock for CURL interfaces which allows to mock out the CURL's
+// low-level C APIs in tests by intercepting the virtual function calls on
+// the abstract CurlInterface.
+class MockCurlInterface : public CurlInterface {
+ public:
+ MockCurlInterface() = default;
+
+ MOCK_METHOD0(EasyInit, CURL*());
+ MOCK_METHOD1(EasyCleanup, void(CURL*));
+ MOCK_METHOD3(EasySetOptInt, CURLcode(CURL*, CURLoption, int));
+ MOCK_METHOD3(EasySetOptStr, CURLcode(CURL*, CURLoption, const std::string&));
+ MOCK_METHOD3(EasySetOptPtr, CURLcode(CURL*, CURLoption, void*));
+ MOCK_METHOD3(EasySetOptCallback, CURLcode(CURL*, CURLoption, intptr_t));
+ MOCK_METHOD3(EasySetOptOffT, CURLcode(CURL*, CURLoption, curl_off_t));
+ MOCK_METHOD1(EasyPerform, CURLcode(CURL*));
+ MOCK_CONST_METHOD3(EasyGetInfoInt, CURLcode(CURL*, CURLINFO, int*));
+ MOCK_CONST_METHOD3(EasyGetInfoDbl, CURLcode(CURL*, CURLINFO, double*));
+ MOCK_CONST_METHOD3(EasyGetInfoStr, CURLcode(CURL*, CURLINFO, std::string*));
+ MOCK_CONST_METHOD3(EasyGetInfoPtr, CURLcode(CURL*, CURLINFO, void**));
+ MOCK_CONST_METHOD1(EasyStrError, std::string(CURLcode));
+ MOCK_METHOD0(MultiInit, CURLM*());
+ MOCK_METHOD1(MultiCleanup, CURLMcode(CURLM*));
+ MOCK_METHOD2(MultiInfoRead, CURLMsg*(CURLM*, int*));
+ MOCK_METHOD2(MultiAddHandle, CURLMcode(CURLM*, CURL*));
+ MOCK_METHOD2(MultiRemoveHandle, CURLMcode(CURLM*, CURL*));
+ MOCK_METHOD3(MultiSetSocketCallback,
+ CURLMcode(CURLM*, curl_socket_callback, void*));
+ MOCK_METHOD3(MultiSetTimerCallback,
+ CURLMcode(CURLM*, curl_multi_timer_callback, void*));
+ MOCK_METHOD3(MultiAssign, CURLMcode(CURLM*, curl_socket_t, void*));
+ MOCK_METHOD4(MultiSocketAction, CURLMcode(CURLM*, curl_socket_t, int, int*));
+ MOCK_CONST_METHOD1(MultiStrError, std::string(CURLMcode));
+ MOCK_METHOD2(MultiPerform, CURLMcode(CURLM*, int*));
+ MOCK_METHOD5(MultiWait,
+ CURLMcode(CURLM*, curl_waitfd[], unsigned int, int, int*));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCurlInterface);
+};
+
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_MOCK_CURL_API_H_
diff --git a/libbrillo/brillo/http/mock_transport.h b/libbrillo/brillo/http/mock_transport.h
new file mode 100644
index 0000000..9bc6c14
--- /dev/null
+++ b/libbrillo/brillo/http/mock_transport.h
@@ -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.
+
+#ifndef LIBBRILLO_BRILLO_HTTP_MOCK_TRANSPORT_H_
+#define LIBBRILLO_BRILLO_HTTP_MOCK_TRANSPORT_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <brillo/http/http_transport.h>
+#include <gmock/gmock.h>
+
+namespace brillo {
+namespace http {
+
+class MockTransport : public Transport {
+ public:
+ MockTransport() = default;
+
+ MOCK_METHOD6(CreateConnection,
+ std::shared_ptr<Connection>(const std::string&,
+ const std::string&,
+ const HeaderList&,
+ const std::string&,
+ const std::string&,
+ brillo::ErrorPtr*));
+ MOCK_METHOD2(RunCallbackAsync,
+ void(const tracked_objects::Location&, const base::Closure&));
+ MOCK_METHOD3(StartAsyncTransfer, RequestID(Connection*,
+ const SuccessCallback&,
+ const ErrorCallback&));
+ MOCK_METHOD1(CancelRequest, bool(RequestID));
+ MOCK_METHOD1(SetDefaultTimeout, void(base::TimeDelta));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockTransport);
+};
+
+} // namespace http
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_HTTP_MOCK_TRANSPORT_H_
diff --git a/libbrillo/brillo/key_value_store.cc b/libbrillo/brillo/key_value_store.cc
new file mode 100644
index 0000000..7840427
--- /dev/null
+++ b/libbrillo/brillo/key_value_store.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2010 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 "brillo/key_value_store.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/files/important_file_writer.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <brillo/strings/string_utils.h>
+#include <brillo/map_utils.h>
+
+using std::map;
+using std::string;
+using std::vector;
+
+namespace brillo {
+
+namespace {
+
+// Values used for booleans.
+const char kTrueValue[] = "true";
+const char kFalseValue[] = "false";
+
+// Returns a copy of |key| with leading and trailing whitespace removed.
+string TrimKey(const string& key) {
+ string trimmed_key;
+ base::TrimWhitespaceASCII(key, base::TRIM_ALL, &trimmed_key);
+ CHECK(!trimmed_key.empty());
+ return trimmed_key;
+}
+
+} // namespace
+
+bool KeyValueStore::Load(const base::FilePath& path) {
+ string file_data;
+ if (!base::ReadFileToString(path, &file_data))
+ return false;
+ return LoadFromString(file_data);
+}
+
+bool KeyValueStore::LoadFromString(const std::string& data) {
+ // Split along '\n', then along '='.
+ vector<string> lines = base::SplitString(data, "\n", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ for (auto it = lines.begin(); it != lines.end(); ++it) {
+ std::string line;
+ base::TrimWhitespaceASCII(*it, base::TRIM_LEADING, &line);
+ if (line.empty() || line.front() == '#')
+ continue;
+
+ std::string key;
+ std::string value;
+ if (!string_utils::SplitAtFirst(line, "=", &key, &value, false))
+ return false;
+
+ base::TrimWhitespaceASCII(key, base::TRIM_TRAILING, &key);
+ if (key.empty())
+ return false;
+
+ // Append additional lines to the value as long as we see trailing
+ // backslashes.
+ while (!value.empty() && value.back() == '\\') {
+ ++it;
+ if (it == lines.end() || it->empty())
+ return false;
+ value.pop_back();
+ value += *it;
+ }
+
+ store_[key] = value;
+ }
+ return true;
+}
+
+bool KeyValueStore::Save(const base::FilePath& path) const {
+ return base::ImportantFileWriter::WriteFileAtomically(path, SaveToString());
+}
+
+string KeyValueStore::SaveToString() const {
+ string data;
+ for (const auto& key_value : store_)
+ data += key_value.first + "=" + key_value.second + "\n";
+ return data;
+}
+
+bool KeyValueStore::GetString(const string& key, string* value) const {
+ const auto key_value = store_.find(TrimKey(key));
+ if (key_value == store_.end())
+ return false;
+ *value = key_value->second;
+ return true;
+}
+
+void KeyValueStore::SetString(const string& key, const string& value) {
+ store_[TrimKey(key)] = value;
+}
+
+bool KeyValueStore::GetBoolean(const string& key, bool* value) const {
+ string string_value;
+ if (!GetString(key, &string_value))
+ return false;
+
+ if (string_value == kTrueValue) {
+ *value = true;
+ return true;
+ } else if (string_value == kFalseValue) {
+ *value = false;
+ return true;
+ }
+ return false;
+}
+
+void KeyValueStore::SetBoolean(const string& key, bool value) {
+ SetString(key, value ? kTrueValue : kFalseValue);
+}
+
+std::vector<std::string> KeyValueStore::GetKeys() const {
+ return GetMapKeysAsVector(store_);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/key_value_store.h b/libbrillo/brillo/key_value_store.h
new file mode 100644
index 0000000..cc5fa40
--- /dev/null
+++ b/libbrillo/brillo/key_value_store.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2010 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.
+
+// These functions can parse a blob of data that's formatted as a simple
+// key value store. Each key/value pair is stored on its own line and
+// separated by the first '=' on the line.
+
+#ifndef LIBBRILLO_BRILLO_KEY_VALUE_STORE_H_
+#define LIBBRILLO_BRILLO_KEY_VALUE_STORE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+class BRILLO_EXPORT KeyValueStore {
+ public:
+ // Creates an empty KeyValueStore.
+ KeyValueStore() = default;
+ virtual ~KeyValueStore() = default;
+
+ // Loads the key=value pairs from the given |path|. Lines starting with '#'
+ // and empty lines are ignored, and whitespace around keys is trimmed.
+ // Trailing backslashes may be used to extend values across multiple lines.
+ // Adds all the read key=values to the store, overriding those already defined
+ // but persisting the ones that aren't present on the passed file. Returns
+ // whether reading the file succeeded.
+ bool Load(const base::FilePath& path);
+
+ // Loads the key=value pairs parsing the text passed in |data|. See Load() for
+ // details.
+ // Returns whether the parsing succeeded.
+ bool LoadFromString(const std::string& data);
+
+ // Saves the current store to the given |path| file. See SaveToString() for
+ // details on the formate of the created file.
+ // Returns whether the file creation succeeded.
+ bool Save(const base::FilePath& path) const;
+
+ // Returns a string with the contents of the store as key=value lines.
+ // Calling LoadFromString() and then SaveToString() may result in different
+ // result if the original string contained backslash-terminated lines (i.e.
+ // these values will be rewritten on single lines), comments or empty lines.
+ std::string SaveToString() const;
+
+ // Getter for the given key. Returns whether the key was found on the store.
+ bool GetString(const std::string& key, std::string* value) const;
+
+ // Setter for the given key. It overrides the key if already exists.
+ void SetString(const std::string& key, const std::string& value);
+
+ // Boolean getter. Returns whether the key was found on the store and if it
+ // has a valid value ("true" or "false").
+ bool GetBoolean(const std::string& key, bool* value) const;
+
+ // Boolean setter. Sets the value as "true" or "false".
+ void SetBoolean(const std::string& key, bool value);
+
+ // Retrieves the keys for all values currently stored in the map.
+ std::vector<std::string> GetKeys() const;
+
+ private:
+ // The map storing all the key-value pairs.
+ std::map<std::string, std::string> store_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyValueStore);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_KEY_VALUE_STORE_H_
diff --git a/libbrillo/brillo/key_value_store_unittest.cc b/libbrillo/brillo/key_value_store_unittest.cc
new file mode 100644
index 0000000..cd18e89
--- /dev/null
+++ b/libbrillo/brillo/key_value_store_unittest.cc
@@ -0,0 +1,192 @@
+// Copyright (c) 2010 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 <brillo/key_value_store.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <brillo/map_utils.h>
+#include <gtest/gtest.h>
+
+using base::FilePath;
+using base::ReadFileToString;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace brillo {
+
+class KeyValueStoreTest : public ::testing::Test {
+ protected:
+ // Returns the value from |store_| corresponding to |key|, or an empty string
+ // if the key is not present. Crashes if the store returns an empty value.
+ string GetNonemptyStringValue(const string& key) {
+ string value;
+ if (store_.GetString(key, &value))
+ CHECK(!value.empty());
+ return value;
+ }
+
+ KeyValueStore store_; // KeyValueStore under test.
+};
+
+TEST_F(KeyValueStoreTest, LoadAndSaveFromFile) {
+ base::ScopedTempDir temp_dir_;
+ CHECK(temp_dir_.CreateUniqueTempDir());
+ base::FilePath temp_file_ = temp_dir_.path().Append("temp.conf");
+ base::FilePath saved_temp_file_ = temp_dir_.path().Append("saved_temp.conf");
+
+ string blob = "A=B\n# Comment\n";
+ ASSERT_EQ(blob.size(), base::WriteFile(temp_file_, blob.data(), blob.size()));
+ ASSERT_TRUE(store_.Load(temp_file_));
+
+ string value;
+ EXPECT_TRUE(store_.GetString("A", &value));
+ EXPECT_EQ("B", value);
+
+ ASSERT_TRUE(store_.Save(saved_temp_file_));
+ string read_blob;
+ ASSERT_TRUE(ReadFileToString(FilePath(saved_temp_file_), &read_blob));
+ EXPECT_EQ("A=B\n", read_blob);
+}
+
+TEST_F(KeyValueStoreTest, CommentsAreIgnored) {
+ EXPECT_TRUE(store_.LoadFromString(
+ "# comment\nA=B\n\n\n#another=comment\n # leading spaces\n"));
+ EXPECT_EQ("A=B\n", store_.SaveToString());
+}
+
+TEST_F(KeyValueStoreTest, EmptyTest) {
+ EXPECT_TRUE(store_.LoadFromString(""));
+ EXPECT_EQ("", store_.SaveToString());
+}
+
+TEST_F(KeyValueStoreTest, LoadAndReloadTest) {
+ EXPECT_TRUE(store_.LoadFromString(
+ "A=B\nC=\nFOO=BAR=BAZ\nBAR=BAX\nMISSING=NEWLINE"));
+
+ map<string, string> expected = {{"A", "B"},
+ {"C", ""},
+ {"FOO", "BAR=BAZ"},
+ {"BAR", "BAX"},
+ {"MISSING", "NEWLINE"}};
+
+ // Test expected values.
+ string value;
+ for (const auto& it : expected) {
+ EXPECT_TRUE(store_.GetString(it.first, &value));
+ EXPECT_EQ(it.second, value) << "Testing key: " << it.first;
+ }
+
+ // Save, load and test again.
+ KeyValueStore new_store;
+ ASSERT_TRUE(new_store.LoadFromString(store_.SaveToString()));
+
+ for (const auto& it : expected) {
+ EXPECT_TRUE(new_store.GetString(it.first, &value)) << "key: " << it.first;
+ EXPECT_EQ(it.second, value) << "key: " << it.first;
+ }
+}
+
+TEST_F(KeyValueStoreTest, SimpleBooleanTest) {
+ bool result;
+ EXPECT_FALSE(store_.GetBoolean("A", &result));
+
+ store_.SetBoolean("A", true);
+ EXPECT_TRUE(store_.GetBoolean("A", &result));
+ EXPECT_TRUE(result);
+
+ store_.SetBoolean("A", false);
+ EXPECT_TRUE(store_.GetBoolean("A", &result));
+ EXPECT_FALSE(result);
+}
+
+TEST_F(KeyValueStoreTest, BooleanParsingTest) {
+ string blob = "TRUE=true\nfalse=false\nvar=false\nDONT_SHOUT=TRUE\n";
+ EXPECT_TRUE(store_.LoadFromString(blob));
+
+ map<string, bool> expected = {
+ {"TRUE", true}, {"false", false}, {"var", false}};
+ bool value;
+ EXPECT_FALSE(store_.GetBoolean("DONT_SHOUT", &value));
+ string str_value;
+ EXPECT_TRUE(store_.GetString("DONT_SHOUT", &str_value));
+
+ // Test expected values.
+ for (const auto& it : expected) {
+ EXPECT_TRUE(store_.GetBoolean(it.first, &value)) << "key: " << it.first;
+ EXPECT_EQ(it.second, value) << "key: " << it.first;
+ }
+}
+
+TEST_F(KeyValueStoreTest, TrimWhitespaceAroundKey) {
+ EXPECT_TRUE(store_.LoadFromString(" a=1\nb =2\n c =3\n"));
+
+ EXPECT_EQ("1", GetNonemptyStringValue("a"));
+ EXPECT_EQ("2", GetNonemptyStringValue("b"));
+ EXPECT_EQ("3", GetNonemptyStringValue("c"));
+
+ // Keys should also be trimmed when setting new values.
+ store_.SetString(" foo ", "4");
+ EXPECT_EQ("4", GetNonemptyStringValue("foo"));
+
+ store_.SetBoolean(" bar ", true);
+ bool value = false;
+ ASSERT_TRUE(store_.GetBoolean("bar", &value));
+ EXPECT_TRUE(value);
+}
+
+TEST_F(KeyValueStoreTest, IgnoreWhitespaceLine) {
+ EXPECT_TRUE(store_.LoadFromString("a=1\n \t \nb=2"));
+
+ EXPECT_EQ("1", GetNonemptyStringValue("a"));
+ EXPECT_EQ("2", GetNonemptyStringValue("b"));
+}
+
+TEST_F(KeyValueStoreTest, RejectEmptyKeys) {
+ EXPECT_FALSE(store_.LoadFromString("=1"));
+ EXPECT_FALSE(store_.LoadFromString(" =2"));
+
+ // Trying to set an empty (after trimming) key should fail an assert.
+ EXPECT_DEATH(store_.SetString(" ", "3"), "");
+ EXPECT_DEATH(store_.SetBoolean(" ", "4"), "");
+}
+
+TEST_F(KeyValueStoreTest, RejectBogusLines) {
+ EXPECT_FALSE(store_.LoadFromString("a=1\nbogus\nb=2"));
+}
+
+TEST_F(KeyValueStoreTest, MultilineValue) {
+ EXPECT_TRUE(store_.LoadFromString("a=foo\nb=bar\\\n baz \\ \nc=3\n"));
+
+ EXPECT_EQ("foo", GetNonemptyStringValue("a"));
+ EXPECT_EQ("bar baz \\ ", GetNonemptyStringValue("b"));
+ EXPECT_EQ("3", GetNonemptyStringValue("c"));
+}
+
+TEST_F(KeyValueStoreTest, UnterminatedMultilineValue) {
+ EXPECT_FALSE(store_.LoadFromString("a=foo\\"));
+ EXPECT_FALSE(store_.LoadFromString("a=foo\\\n"));
+ EXPECT_FALSE(store_.LoadFromString("a=foo\\\n\n# blah\n"));
+}
+
+TEST_F(KeyValueStoreTest, GetKeys) {
+ map<string, string> entries = {
+ {"1", "apple"}, {"2", "banana"}, {"3", "cherry"}
+ };
+ for (const auto& it : entries) {
+ store_.SetString(it.first, it.second);
+ }
+
+ vector<string> keys = GetMapKeysAsVector(entries);
+ EXPECT_EQ(keys, store_.GetKeys());
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/location_logging.h b/libbrillo/brillo/location_logging.h
new file mode 100644
index 0000000..8e18c05
--- /dev/null
+++ b/libbrillo/brillo/location_logging.h
@@ -0,0 +1,24 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_LOCATION_LOGGING_H_
+#define LIBBRILLO_BRILLO_LOCATION_LOGGING_H_
+
+// These macros help to log Location objects in verbose mode.
+
+#include <base/logging.h>
+
+#define VLOG_LOC_STREAM(from_here, verbose_level) \
+ logging::LogMessage((from_here).file_name(), (from_here).line_number(), \
+ -(verbose_level)).stream()
+
+#define VLOG_LOC(from_here, verbose_level) \
+ LAZY_STREAM(VLOG_LOC_STREAM(from_here, verbose_level), \
+ VLOG_IS_ON(verbose_level))
+
+#define DVLOG_LOC(from_here, verbose_level) \
+ LAZY_STREAM(VLOG_LOC_STREAM(from_here, verbose_level), \
+ ::logging::DEBUG_MODE && VLOG_IS_ON(verbose_level))
+
+#endif // LIBBRILLO_BRILLO_LOCATION_LOGGING_H_
diff --git a/libbrillo/brillo/make_unique_ptr.h b/libbrillo/brillo/make_unique_ptr.h
new file mode 100644
index 0000000..89f56e6
--- /dev/null
+++ b/libbrillo/brillo/make_unique_ptr.h
@@ -0,0 +1,25 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_MAKE_UNIQUE_PTR_H_
+#define LIBBRILLO_BRILLO_MAKE_UNIQUE_PTR_H_
+
+#include <memory>
+
+namespace brillo {
+
+// A function to convert T* into unique_ptr<T>
+// Doing e.g. make_unique_ptr(new FooBarBaz<type>(arg)) is a shorter notation
+// for unique_ptr<FooBarBaz<type>>(new FooBarBaz<type>(arg))
+// Basically the same as Chromium's make_scoped_ptr().
+// Deliberately not named "make_unique" to avoid conflicting with the similar,
+// but more complex and semantically different C++14 function.
+template <typename T>
+std::unique_ptr<T> make_unique_ptr(T* ptr) {
+ return std::unique_ptr<T>(ptr);
+}
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MAKE_UNIQUE_PTR_H_
diff --git a/libbrillo/brillo/map_utils.h b/libbrillo/brillo/map_utils.h
new file mode 100644
index 0000000..a4568f3
--- /dev/null
+++ b/libbrillo/brillo/map_utils.h
@@ -0,0 +1,71 @@
+// 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 LIBBRILLO_BRILLO_MAP_UTILS_H_
+#define LIBBRILLO_BRILLO_MAP_UTILS_H_
+
+#include <map>
+#include <set>
+#include <utility>
+#include <vector>
+
+namespace brillo {
+
+// Given an STL map, returns a set containing all keys from the map.
+template<typename T>
+inline std::set<typename T::key_type> GetMapKeys(const T& map) {
+ std::set<typename T::key_type> keys;
+ for (const auto& pair : map)
+ keys.insert(keys.end(), pair.first); // Map keys are already sorted.
+ return keys;
+}
+
+// Given an STL map, returns a vector containing all keys from the map.
+// The keys in the vector are sorted.
+template<typename T>
+inline std::vector<typename T::key_type> GetMapKeysAsVector(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>
+inline 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>
+inline 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;
+}
+
+// Given an STL map, returns the value associated with a given key or a default
+// value if the key is not present in the map.
+template<typename T>
+inline typename T::mapped_type GetOrDefault(
+ const T& map,
+ typename T::key_type key,
+ const typename T::mapped_type& def) {
+ typename T::const_iterator it = map.find(key);
+ if (it == map.end())
+ return def;
+ return it->second;
+}
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MAP_UTILS_H_
diff --git a/libbrillo/brillo/map_utils_unittest.cc b/libbrillo/brillo/map_utils_unittest.cc
new file mode 100644
index 0000000..19bda1d
--- /dev/null
+++ b/libbrillo/brillo/map_utils_unittest.cc
@@ -0,0 +1,59 @@
+// 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 <brillo/map_utils.h>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+class MapUtilsTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ map_ = {
+ {"key1", 1}, {"key2", 2}, {"key3", 3}, {"key4", 4}, {"key5", 5},
+ };
+ }
+
+ void TearDown() override { map_.clear(); }
+
+ std::map<std::string, int> map_;
+};
+
+TEST_F(MapUtilsTest, GetMapKeys) {
+ std::set<std::string> keys = GetMapKeys(map_);
+ EXPECT_EQ((std::set<std::string>{"key1", "key2", "key3", "key4", "key5"}),
+ keys);
+}
+
+TEST_F(MapUtilsTest, GetMapKeysAsVector) {
+ std::vector<std::string> keys = GetMapKeysAsVector(map_);
+ EXPECT_EQ((std::vector<std::string>{"key1", "key2", "key3", "key4", "key5"}),
+ keys);
+}
+
+TEST_F(MapUtilsTest, GetMapValues) {
+ std::vector<int> values = GetMapValues(map_);
+ EXPECT_EQ((std::vector<int>{1, 2, 3, 4, 5}), values);
+}
+
+TEST_F(MapUtilsTest, MapToVector) {
+ std::vector<std::pair<std::string, int>> elements = MapToVector(map_);
+ std::vector<std::pair<std::string, int>> expected{
+ {"key1", 1}, {"key2", 2}, {"key3", 3}, {"key4", 4}, {"key5", 5},
+ };
+ EXPECT_EQ(expected, elements);
+}
+
+TEST_F(MapUtilsTest, Empty) {
+ std::map<int, double> empty_map;
+ EXPECT_TRUE(GetMapKeys(empty_map).empty());
+ EXPECT_TRUE(GetMapKeysAsVector(empty_map).empty());
+ EXPECT_TRUE(GetMapValues(empty_map).empty());
+ EXPECT_TRUE(MapToVector(empty_map).empty());
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/message_loops/base_message_loop.cc b/libbrillo/brillo/message_loops/base_message_loop.cc
new file mode 100644
index 0000000..4ea0f08
--- /dev/null
+++ b/libbrillo/brillo/message_loops/base_message_loop.cc
@@ -0,0 +1,436 @@
+// Copyright 2015 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 <brillo/message_loops/base_message_loop.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifndef __ANDROID_HOST__
+// Used for MISC_MAJOR. Only required for the target and not always available
+// for the host.
+#include <linux/major.h>
+#endif
+
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/run_loop.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+
+#include <brillo/location_logging.h>
+#include <brillo/strings/string_utils.h>
+
+using base::Closure;
+
+namespace {
+
+const char kMiscMinorPath[] = "/proc/misc";
+const char kBinderDriverName[] = "binder";
+
+} // namespace
+
+namespace brillo {
+
+const int BaseMessageLoop::kInvalidMinor = -1;
+const int BaseMessageLoop::kUninitializedMinor = -2;
+
+BaseMessageLoop::BaseMessageLoop() {
+ CHECK(!base::MessageLoop::current())
+ << "You can't create a base::MessageLoopForIO when another "
+ "base::MessageLoop is already created for this thread.";
+ owned_base_loop_.reset(new base::MessageLoopForIO);
+ base_loop_ = owned_base_loop_.get();
+}
+
+BaseMessageLoop::BaseMessageLoop(base::MessageLoopForIO* base_loop)
+ : base_loop_(base_loop) {}
+
+BaseMessageLoop::~BaseMessageLoop() {
+ for (auto& io_task : io_tasks_) {
+ DVLOG_LOC(io_task.second.location(), 1)
+ << "Removing file descriptor watcher task_id " << io_task.first
+ << " leaked on BaseMessageLoop, scheduled from this location.";
+ io_task.second.StopWatching();
+ }
+
+ // Note all pending canceled delayed tasks when destroying the message loop.
+ size_t lazily_deleted_tasks = 0;
+ for (const auto& delayed_task : delayed_tasks_) {
+ if (delayed_task.second.closure.is_null()) {
+ lazily_deleted_tasks++;
+ } else {
+ DVLOG_LOC(delayed_task.second.location, 1)
+ << "Removing delayed task_id " << delayed_task.first
+ << " leaked on BaseMessageLoop, scheduled from this location.";
+ }
+ }
+ if (lazily_deleted_tasks) {
+ LOG(INFO) << "Leaking " << lazily_deleted_tasks << " canceled tasks.";
+ }
+}
+
+MessageLoop::TaskId BaseMessageLoop::PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const Closure &task,
+ base::TimeDelta delay) {
+ TaskId task_id = NextTaskId();
+ bool base_scheduled = base_loop_->task_runner()->PostDelayedTask(
+ from_here,
+ base::Bind(&BaseMessageLoop::OnRanPostedTask,
+ weak_ptr_factory_.GetWeakPtr(),
+ task_id),
+ delay);
+ DVLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << task_id
+ << " to run in " << delay << ".";
+ if (!base_scheduled)
+ return MessageLoop::kTaskIdNull;
+
+ delayed_tasks_.emplace(task_id,
+ DelayedTask{from_here, task_id, std::move(task)});
+ return task_id;
+}
+
+MessageLoop::TaskId BaseMessageLoop::WatchFileDescriptor(
+ const tracked_objects::Location& from_here,
+ int fd,
+ WatchMode mode,
+ bool persistent,
+ const Closure &task) {
+ // base::MessageLoopForIO CHECKS that "fd >= 0", so we handle that case here.
+ if (fd < 0)
+ return MessageLoop::kTaskIdNull;
+
+ base::MessageLoopForIO::Mode base_mode = base::MessageLoopForIO::WATCH_READ;
+ switch (mode) {
+ case MessageLoop::kWatchRead:
+ base_mode = base::MessageLoopForIO::WATCH_READ;
+ break;
+ case MessageLoop::kWatchWrite:
+ base_mode = base::MessageLoopForIO::WATCH_WRITE;
+ break;
+ default:
+ return MessageLoop::kTaskIdNull;
+ }
+
+ TaskId task_id = NextTaskId();
+ auto it_bool = io_tasks_.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(task_id),
+ std::forward_as_tuple(
+ from_here, this, task_id, fd, base_mode, persistent, task));
+ // This should always insert a new element.
+ DCHECK(it_bool.second);
+ bool scheduled = it_bool.first->second.StartWatching();
+ DVLOG_LOC(from_here, 1)
+ << "Watching fd " << fd << " for "
+ << (mode == MessageLoop::kWatchRead ? "reading" : "writing")
+ << (persistent ? " persistently" : " just once")
+ << " as task_id " << task_id
+ << (scheduled ? " successfully" : " failed.");
+
+ if (!scheduled) {
+ io_tasks_.erase(task_id);
+ return MessageLoop::kTaskIdNull;
+ }
+
+#ifndef __ANDROID_HOST__
+ // Determine if the passed fd is the binder file descriptor. For that, we need
+ // to check that is a special char device and that the major and minor device
+ // numbers match. The binder file descriptor can't be removed and added back
+ // to an epoll group when there's work available to be done by the file
+ // descriptor due to bugs in the binder driver (b/26524111) when used with
+ // epoll. Therefore, we flag the binder fd and never attempt to remove it.
+ // This may cause the binder file descriptor to be attended with higher
+ // priority and cause starvation of other events.
+ struct stat buf;
+ if (fstat(fd, &buf) == 0 &&
+ S_ISCHR(buf.st_mode) &&
+ major(buf.st_rdev) == MISC_MAJOR &&
+ minor(buf.st_rdev) == GetBinderMinor()) {
+ it_bool.first->second.RunImmediately();
+ }
+#endif
+
+ return task_id;
+}
+
+bool BaseMessageLoop::CancelTask(TaskId task_id) {
+ if (task_id == kTaskIdNull)
+ return false;
+ auto delayed_task_it = delayed_tasks_.find(task_id);
+ if (delayed_task_it == delayed_tasks_.end()) {
+ // This might be an IOTask then.
+ auto io_task_it = io_tasks_.find(task_id);
+ if (io_task_it == io_tasks_.end())
+ return false;
+ return io_task_it->second.CancelTask();
+ }
+ // A DelayedTask was found for this task_id at this point.
+
+ // Check if the callback was already canceled but we have the entry in
+ // delayed_tasks_ since it didn't fire yet in the message loop.
+ if (delayed_task_it->second.closure.is_null())
+ return false;
+
+ DVLOG_LOC(delayed_task_it->second.location, 1)
+ << "Removing task_id " << task_id << " scheduled from this location.";
+ // We reset to closure to a null Closure to release all the resources
+ // used by this closure at this point, but we don't remove the task_id from
+ // delayed_tasks_ since we can't tell base::MessageLoopForIO to not run it.
+ delayed_task_it->second.closure = Closure();
+
+ return true;
+}
+
+bool BaseMessageLoop::RunOnce(bool may_block) {
+ run_once_ = true;
+ base::RunLoop run_loop; // Uses the base::MessageLoopForIO implicitly.
+ base_run_loop_ = &run_loop;
+ if (!may_block)
+ run_loop.RunUntilIdle();
+ else
+ run_loop.Run();
+ base_run_loop_ = nullptr;
+ // If the flag was reset to false, it means a closure was run.
+ if (!run_once_)
+ return true;
+
+ run_once_ = false;
+ return false;
+}
+
+void BaseMessageLoop::Run() {
+ base::RunLoop run_loop; // Uses the base::MessageLoopForIO implicitly.
+ base_run_loop_ = &run_loop;
+ run_loop.Run();
+ base_run_loop_ = nullptr;
+}
+
+void BaseMessageLoop::BreakLoop() {
+ if (base_run_loop_ == nullptr) {
+ DVLOG(1) << "Message loop not running, ignoring BreakLoop().";
+ return; // Message loop not running, nothing to do.
+ }
+ base_run_loop_->Quit();
+}
+
+Closure BaseMessageLoop::QuitClosure() const {
+ if (base_run_loop_ == nullptr)
+ return base::Bind(&base::DoNothing);
+ return base_run_loop_->QuitClosure();
+}
+
+MessageLoop::TaskId BaseMessageLoop::NextTaskId() {
+ TaskId res;
+ do {
+ res = ++last_id_;
+ // We would run out of memory before we run out of task ids.
+ } while (!res ||
+ delayed_tasks_.find(res) != delayed_tasks_.end() ||
+ io_tasks_.find(res) != io_tasks_.end());
+ return res;
+}
+
+void BaseMessageLoop::OnRanPostedTask(MessageLoop::TaskId task_id) {
+ auto task_it = delayed_tasks_.find(task_id);
+ DCHECK(task_it != delayed_tasks_.end());
+ if (!task_it->second.closure.is_null()) {
+ DVLOG_LOC(task_it->second.location, 1)
+ << "Running delayed task_id " << task_id
+ << " scheduled from this location.";
+ // Mark the task as canceled while we are running it so CancelTask returns
+ // false.
+ Closure closure = std::move(task_it->second.closure);
+ task_it->second.closure = Closure();
+ closure.Run();
+
+ // If the |run_once_| flag is set, it is because we are instructed to run
+ // only once callback.
+ if (run_once_) {
+ run_once_ = false;
+ BreakLoop();
+ }
+ }
+ delayed_tasks_.erase(task_it);
+}
+
+void BaseMessageLoop::OnFileReadyPostedTask(MessageLoop::TaskId task_id) {
+ auto task_it = io_tasks_.find(task_id);
+ // Even if this task was canceled while we were waiting in the message loop
+ // for this method to run, the entry in io_tasks_ should still be present, but
+ // won't do anything.
+ DCHECK(task_it != io_tasks_.end());
+ task_it->second.OnFileReadyPostedTask();
+}
+
+int BaseMessageLoop::ParseBinderMinor(
+ const std::string& file_contents) {
+ int result = kInvalidMinor;
+ // Split along '\n', then along the ' '. Note that base::SplitString trims all
+ // white spaces at the beginning and end after splitting.
+ std::vector<std::string> lines =
+ base::SplitString(file_contents, "\n", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ for (const std::string& line : lines) {
+ if (line.empty())
+ continue;
+ std::string number;
+ std::string name;
+ if (!string_utils::SplitAtFirst(line, " ", &number, &name, false))
+ continue;
+
+ if (name == kBinderDriverName && base::StringToInt(number, &result))
+ break;
+ }
+ return result;
+}
+
+unsigned int BaseMessageLoop::GetBinderMinor() {
+ if (binder_minor_ != kUninitializedMinor)
+ return binder_minor_;
+
+ std::string proc_misc;
+ if (!base::ReadFileToString(base::FilePath(kMiscMinorPath), &proc_misc))
+ return binder_minor_;
+ binder_minor_ = ParseBinderMinor(proc_misc);
+ return binder_minor_;
+}
+
+BaseMessageLoop::IOTask::IOTask(const tracked_objects::Location& location,
+ BaseMessageLoop* loop,
+ MessageLoop::TaskId task_id,
+ int fd,
+ base::MessageLoopForIO::Mode base_mode,
+ bool persistent,
+ const Closure& task)
+ : location_(location), loop_(loop), task_id_(task_id),
+ fd_(fd), base_mode_(base_mode), persistent_(persistent), closure_(task) {}
+
+bool BaseMessageLoop::IOTask::StartWatching() {
+ return loop_->base_loop_->WatchFileDescriptor(
+ fd_, persistent_, base_mode_, &fd_watcher_, this);
+}
+
+void BaseMessageLoop::IOTask::StopWatching() {
+ // This is safe to call even if we are not watching for it.
+ fd_watcher_.StopWatchingFileDescriptor();
+}
+
+void BaseMessageLoop::IOTask::OnFileCanReadWithoutBlocking(int /* fd */) {
+ OnFileReady();
+}
+
+void BaseMessageLoop::IOTask::OnFileCanWriteWithoutBlocking(int /* fd */) {
+ OnFileReady();
+}
+
+void BaseMessageLoop::IOTask::OnFileReady() {
+ // For file descriptors marked with the immediate_run flag, we don't call
+ // StopWatching() and wait, instead we dispatch the callback immediately.
+ if (immediate_run_) {
+ posted_task_pending_ = true;
+ OnFileReadyPostedTask();
+ return;
+ }
+
+ // When the file descriptor becomes available we stop watching for it and
+ // schedule a task to run the callback from the main loop. The callback will
+ // run using the same scheduler used to run other delayed tasks, avoiding
+ // starvation of the available posted tasks if there are file descriptors
+ // always available. The new posted task will use the same TaskId as the
+ // current file descriptor watching task an could be canceled in either state,
+ // when waiting for the file descriptor or waiting in the main loop.
+ StopWatching();
+ bool base_scheduled = loop_->base_loop_->task_runner()->PostTask(
+ location_,
+ base::Bind(&BaseMessageLoop::OnFileReadyPostedTask,
+ loop_->weak_ptr_factory_.GetWeakPtr(),
+ task_id_));
+ posted_task_pending_ = true;
+ if (base_scheduled) {
+ DVLOG_LOC(location_, 1)
+ << "Dispatching task_id " << task_id_ << " for "
+ << (base_mode_ == base::MessageLoopForIO::WATCH_READ ?
+ "reading" : "writing")
+ << " file descriptor " << fd_ << ", scheduled from this location.";
+ } else {
+ // In the rare case that PostTask() fails, we fall back to run it directly.
+ // This would indicate a bigger problem with the message loop setup.
+ LOG(ERROR) << "Error on base::MessageLoopForIO::PostTask().";
+ OnFileReadyPostedTask();
+ }
+}
+
+void BaseMessageLoop::IOTask::OnFileReadyPostedTask() {
+ // We can't access |this| after running the |closure_| since it could call
+ // CancelTask on its own task_id, so we copy the members we need now.
+ BaseMessageLoop* loop_ptr = loop_;
+ DCHECK(posted_task_pending_ = true);
+ posted_task_pending_ = false;
+
+ // If this task was already canceled, the closure will be null and there is
+ // nothing else to do here. This execution doesn't count a step for RunOnce()
+ // unless we have a callback to run.
+ if (closure_.is_null()) {
+ loop_->io_tasks_.erase(task_id_);
+ return;
+ }
+
+ DVLOG_LOC(location_, 1)
+ << "Running task_id " << task_id_ << " for "
+ << (base_mode_ == base::MessageLoopForIO::WATCH_READ ?
+ "reading" : "writing")
+ << " file descriptor " << fd_ << ", scheduled from this location.";
+
+ if (persistent_) {
+ // In the persistent case we just run the callback. If this callback cancels
+ // the task id, we can't access |this| anymore, so we re-start watching the
+ // file descriptor before running the callback, unless this is a fd where
+ // we didn't stop watching the file descriptor when it became available.
+ if (!immediate_run_)
+ StartWatching();
+ closure_.Run();
+ } else {
+ // This will destroy |this|, the fd_watcher and therefore stop watching this
+ // file descriptor.
+ Closure closure_copy = std::move(closure_);
+ loop_->io_tasks_.erase(task_id_);
+ // Run the closure from the local copy we just made.
+ closure_copy.Run();
+ }
+
+ if (loop_ptr->run_once_) {
+ loop_ptr->run_once_ = false;
+ loop_ptr->BreakLoop();
+ }
+}
+
+bool BaseMessageLoop::IOTask::CancelTask() {
+ if (closure_.is_null())
+ return false;
+
+ DVLOG_LOC(location_, 1)
+ << "Removing task_id " << task_id_ << " scheduled from this location.";
+
+ if (!posted_task_pending_) {
+ // Destroying the FileDescriptorWatcher implicitly stops watching the file
+ // descriptor. This will delete our instance.
+ loop_->io_tasks_.erase(task_id_);
+ return true;
+ }
+ // The IOTask is waiting for the message loop to run its delayed task, so
+ // it is not watching for the file descriptor. We release the closure
+ // resources now but keep the IOTask instance alive while we wait for the
+ // callback to run and delete the IOTask.
+ closure_ = Closure();
+ return true;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/message_loops/base_message_loop.h b/libbrillo/brillo/message_loops/base_message_loop.h
new file mode 100644
index 0000000..f615a1b
--- /dev/null
+++ b/libbrillo/brillo/message_loops/base_message_loop.h
@@ -0,0 +1,198 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_MESSAGE_LOOPS_BASE_MESSAGE_LOOP_H_
+#define LIBBRILLO_BRILLO_MESSAGE_LOOPS_BASE_MESSAGE_LOOP_H_
+
+// BaseMessageLoop is a brillo::MessageLoop implementation based on
+// base::MessageLoopForIO. This allows to mix new code using
+// brillo::MessageLoop and legacy code using base::MessageLoopForIO in the
+// same thread and share a single main loop. This disadvantage of using this
+// class is a less efficient implementation of CancelTask() for delayed tasks
+// since base::MessageLoopForIO doesn't provide a way to remove the event.
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/location.h>
+#include <base/memory/weak_ptr.h>
+#include <base/message_loop/message_loop.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h>
+
+#include <brillo/brillo_export.h>
+#include <brillo/message_loops/message_loop.h>
+
+namespace brillo {
+
+class BRILLO_EXPORT BaseMessageLoop : public MessageLoop {
+ public:
+ // Construct a base::MessageLoopForIO message loop instance and use it as
+ // the default message loop for this thread.
+ BaseMessageLoop();
+
+ // Construct a brillo::BaseMessageLoop using the passed base::MessageLoopForIO
+ // instance.
+ explicit BaseMessageLoop(base::MessageLoopForIO* base_loop);
+ ~BaseMessageLoop() override;
+
+ // MessageLoop overrides.
+ TaskId PostDelayedTask(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) override;
+ using MessageLoop::PostDelayedTask;
+ TaskId WatchFileDescriptor(const tracked_objects::Location& from_here,
+ int fd,
+ WatchMode mode,
+ bool persistent,
+ const base::Closure& task) override;
+ using MessageLoop::WatchFileDescriptor;
+ bool CancelTask(TaskId task_id) override;
+ bool RunOnce(bool may_block) override;
+ void Run() override;
+ void BreakLoop() override;
+
+ // Returns a callback that will quit the current message loop. If the message
+ // loop is not running, an empty (null) callback is returned.
+ base::Closure QuitClosure() const;
+
+ private:
+ FRIEND_TEST(BaseMessageLoopTest, ParseBinderMinor);
+
+ static const int kInvalidMinor;
+ static const int kUninitializedMinor;
+
+ // Parses the contents of the file /proc/misc passed in |file_contents| and
+ // returns the minor device number reported for binder. On error or if not
+ // found, returns kInvalidMinor.
+ static int ParseBinderMinor(const std::string& file_contents);
+
+ // Called by base::MessageLoopForIO when is time to call the callback
+ // scheduled with Post*Task() of id |task_id|, even if it was canceled.
+ void OnRanPostedTask(MessageLoop::TaskId task_id);
+
+ // Called from the message loop when the IOTask should run the scheduled
+ // callback. This is a simple wrapper of IOTask::OnFileReadyPostedTask()
+ // posted from the BaseMessageLoop so it is deleted when the BaseMessageLoop
+ // goes out of scope since we can't cancel the callback otherwise.
+ void OnFileReadyPostedTask(MessageLoop::TaskId task_id);
+
+ // Return a new unused task_id.
+ TaskId NextTaskId();
+
+ // Returns binder minor device number.
+ unsigned int GetBinderMinor();
+
+ struct DelayedTask {
+ tracked_objects::Location location;
+
+ MessageLoop::TaskId task_id;
+ base::Closure closure;
+ };
+
+ class IOTask : public base::MessageLoopForIO::Watcher {
+ public:
+ IOTask(const tracked_objects::Location& location,
+ BaseMessageLoop* loop,
+ MessageLoop::TaskId task_id,
+ int fd,
+ base::MessageLoopForIO::Mode base_mode,
+ bool persistent,
+ const base::Closure& task);
+
+ const tracked_objects::Location& location() const { return location_; }
+
+ // Used to start/stop watching the file descriptor while keeping the
+ // IOTask entry available.
+ bool StartWatching();
+ void StopWatching();
+
+ // Called from the message loop as a PostTask() when the file descriptor is
+ // available, scheduled to run from OnFileReady().
+ void OnFileReadyPostedTask();
+
+ // Cancel the IOTask and returns whether it was actually canceled, with the
+ // same semantics as MessageLoop::CancelTask().
+ bool CancelTask();
+
+ // Sets the closure to be run immediately whenever the file descriptor
+ // becomes ready.
+ void RunImmediately() { immediate_run_= true; }
+
+ private:
+ tracked_objects::Location location_;
+ BaseMessageLoop* loop_;
+
+ // These are the arguments passed in the constructor, basically forwarding
+ // all the arguments passed to WatchFileDescriptor() plus the assigned
+ // TaskId for this task.
+ MessageLoop::TaskId task_id_;
+ int fd_;
+ base::MessageLoopForIO::Mode base_mode_;
+ bool persistent_;
+ base::Closure closure_;
+
+ base::MessageLoopForIO::FileDescriptorWatcher fd_watcher_;
+
+ // Tells whether there is a pending call to OnFileReadPostedTask().
+ bool posted_task_pending_{false};
+
+ // Whether the registered callback should be running immediately when the
+ // file descriptor is ready, as opposed to posting a task to the main loop
+ // to prevent starvation.
+ bool immediate_run_{false};
+
+ // base::MessageLoopForIO::Watcher overrides:
+ void OnFileCanReadWithoutBlocking(int fd) override;
+ void OnFileCanWriteWithoutBlocking(int fd) override;
+
+ // Common implementation for both the read and write case.
+ void OnFileReady();
+
+ DISALLOW_COPY_AND_ASSIGN(IOTask);
+ };
+
+ // The base::MessageLoopForIO instance owned by this class, if any. This
+ // is declared first in this class so it is destroyed last.
+ std::unique_ptr<base::MessageLoopForIO> owned_base_loop_;
+
+ // Tasks blocked on a timeout.
+ std::map<MessageLoop::TaskId, DelayedTask> delayed_tasks_;
+
+ // Tasks blocked on I/O.
+ std::map<MessageLoop::TaskId, IOTask> io_tasks_;
+
+ // Flag to mark that we should run the message loop only one iteration.
+ bool run_once_{false};
+
+ // The last used TaskId. While base::MessageLoopForIO doesn't allow to cancel
+ // delayed tasks, we handle that functionality by not running the callback
+ // if it fires at a later point.
+ MessageLoop::TaskId last_id_{kTaskIdNull};
+
+ // The pointer to the libchrome base::MessageLoopForIO we are wrapping with
+ // this interface. If the instance was created from this object, this will
+ // point to that instance.
+ base::MessageLoopForIO* base_loop_;
+
+ // The RunLoop instance used to run the main loop from Run().
+ base::RunLoop* base_run_loop_{nullptr};
+
+ // The binder minor device number. Binder is a "misc" char device with a
+ // dynamically allocated minor number. When uninitialized, this value will
+ // be negative, otherwise, it will hold the minor part of the binder device
+ // number. This is populated by GetBinderMinor().
+ int binder_minor_{kUninitializedMinor};
+
+ // We use a WeakPtrFactory to schedule tasks with the base::MessageLoopForIO
+ // since we can't cancel the callbacks we have scheduled there once this
+ // instance is destroyed.
+ base::WeakPtrFactory<BaseMessageLoop> weak_ptr_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(BaseMessageLoop);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MESSAGE_LOOPS_BASE_MESSAGE_LOOP_H_
diff --git a/libbrillo/brillo/message_loops/base_message_loop_unittest.cc b/libbrillo/brillo/message_loops/base_message_loop_unittest.cc
new file mode 100644
index 0000000..9e052a8
--- /dev/null
+++ b/libbrillo/brillo/message_loops/base_message_loop_unittest.cc
@@ -0,0 +1,24 @@
+// Copyright 2016 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 <brillo/message_loops/base_message_loop.h>
+
+#include <gtest/gtest.h>
+
+#include <brillo/message_loops/message_loop.h>
+
+namespace brillo {
+
+class BaseMessageLoopTest : public ::testing::Test {};
+
+TEST(BaseMessageLoopTest, ParseBinderMinor) {
+ EXPECT_EQ(57, BaseMessageLoop::ParseBinderMinor(
+ "227 mcelog\n 58 sw_sync\n 59 ashmem\n 57 binder\n239 uhid\n"));
+ EXPECT_EQ(123, BaseMessageLoop::ParseBinderMinor("123 binder\n"));
+
+ EXPECT_EQ(BaseMessageLoop::kInvalidMinor,
+ BaseMessageLoop::ParseBinderMinor("227 foo\n239 bar\n"));
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/message_loops/fake_message_loop.cc b/libbrillo/brillo/message_loops/fake_message_loop.cc
new file mode 100644
index 0000000..4d0f157
--- /dev/null
+++ b/libbrillo/brillo/message_loops/fake_message_loop.cc
@@ -0,0 +1,141 @@
+// Copyright 2015 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 <brillo/message_loops/fake_message_loop.h>
+
+#include <base/logging.h>
+#include <brillo/location_logging.h>
+
+namespace brillo {
+
+FakeMessageLoop::FakeMessageLoop(base::SimpleTestClock* clock)
+ : test_clock_(clock) {
+}
+
+MessageLoop::TaskId FakeMessageLoop::PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) {
+ // If no SimpleTestClock was provided, we use the last time we fired a
+ // callback. In this way, tasks scheduled from a Closure will have the right
+ // time.
+ if (test_clock_)
+ current_time_ = test_clock_->Now();
+ MessageLoop::TaskId current_id = ++last_id_;
+ // FakeMessageLoop is limited to only 2^64 tasks. That should be enough.
+ CHECK(current_id);
+ tasks_.emplace(current_id, ScheduledTask{from_here, false, task});
+ fire_order_.push(std::make_pair(current_time_ + delay, current_id));
+ VLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << current_id
+ << " to run at " << current_time_ + delay
+ << " (in " << delay << ").";
+ return current_id;
+}
+
+MessageLoop::TaskId FakeMessageLoop::WatchFileDescriptor(
+ const tracked_objects::Location& from_here,
+ int fd,
+ WatchMode mode,
+ bool persistent,
+ const base::Closure& task) {
+ MessageLoop::TaskId current_id = ++last_id_;
+ // FakeMessageLoop is limited to only 2^64 tasks. That should be enough.
+ CHECK(current_id);
+ tasks_.emplace(current_id, ScheduledTask{from_here, persistent, task});
+ fds_watched_.emplace(std::make_pair(fd, mode), current_id);
+ return current_id;
+}
+
+bool FakeMessageLoop::CancelTask(TaskId task_id) {
+ if (task_id == MessageLoop::kTaskIdNull)
+ return false;
+ bool ret = tasks_.erase(task_id) > 0;
+ VLOG_IF(1, ret) << "Removing task_id " << task_id;
+ return ret;
+}
+
+bool FakeMessageLoop::RunOnce(bool may_block) {
+ if (test_clock_)
+ current_time_ = test_clock_->Now();
+ // Try to fire ready file descriptors first.
+ for (const auto& fd_mode : fds_ready_) {
+ const auto& fd_watched = fds_watched_.find(fd_mode);
+ if (fd_watched == fds_watched_.end())
+ continue;
+ // The fd_watched->second task might have been canceled and we never removed
+ // it from the fds_watched_, so we fix that now.
+ const auto& scheduled_task_ref = tasks_.find(fd_watched->second);
+ if (scheduled_task_ref == tasks_.end()) {
+ fds_watched_.erase(fd_watched);
+ continue;
+ }
+ VLOG_LOC(scheduled_task_ref->second.location, 1)
+ << "Running task_id " << fd_watched->second
+ << " for watching file descriptor " << fd_mode.first << " for "
+ << (fd_mode.second == MessageLoop::kWatchRead ? "reading" : "writing")
+ << (scheduled_task_ref->second.persistent ?
+ " persistently" : " just once")
+ << " scheduled from this location.";
+ if (scheduled_task_ref->second.persistent) {
+ scheduled_task_ref->second.callback.Run();
+ } else {
+ base::Closure callback = std::move(scheduled_task_ref->second.callback);
+ tasks_.erase(scheduled_task_ref);
+ fds_watched_.erase(fd_watched);
+ callback.Run();
+ }
+ return true;
+ }
+
+ // Try to fire time-based callbacks afterwards.
+ while (!fire_order_.empty() &&
+ (may_block || fire_order_.top().first <= current_time_)) {
+ const auto task_ref = fire_order_.top();
+ fire_order_.pop();
+ // We need to skip tasks in the priority_queue not in the |tasks_| map.
+ // This is normal if the task was canceled, as there is no efficient way
+ // to remove a task from the priority_queue.
+ const auto scheduled_task_ref = tasks_.find(task_ref.second);
+ if (scheduled_task_ref == tasks_.end())
+ continue;
+ // Advance the clock to the task firing time, if needed.
+ if (current_time_ < task_ref.first) {
+ current_time_ = task_ref.first;
+ if (test_clock_)
+ test_clock_->SetNow(current_time_);
+ }
+ // Move the Closure out of the map before delete it. We need to delete the
+ // entry from the map before we call the callback, since calling CancelTask
+ // for the task you are running now should fail and return false.
+ base::Closure callback = std::move(scheduled_task_ref->second.callback);
+ VLOG_LOC(scheduled_task_ref->second.location, 1)
+ << "Running task_id " << task_ref.second
+ << " at time " << current_time_ << " from this location.";
+ tasks_.erase(scheduled_task_ref);
+
+ callback.Run();
+ return true;
+ }
+ return false;
+}
+
+void FakeMessageLoop::SetFileDescriptorReadiness(int fd,
+ WatchMode mode,
+ bool ready) {
+ if (ready)
+ fds_ready_.emplace(fd, mode);
+ else
+ fds_ready_.erase(std::make_pair(fd, mode));
+}
+
+bool FakeMessageLoop::PendingTasks() {
+ for (const auto& task : tasks_) {
+ VLOG_LOC(task.second.location, 1)
+ << "Pending " << (task.second.persistent ? "persistent " : "")
+ << "task_id " << task.first << " scheduled from here.";
+ }
+ return !tasks_.empty();
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/message_loops/fake_message_loop.h b/libbrillo/brillo/message_loops/fake_message_loop.h
new file mode 100644
index 0000000..c8c1313
--- /dev/null
+++ b/libbrillo/brillo/message_loops/fake_message_loop.h
@@ -0,0 +1,99 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_MESSAGE_LOOPS_FAKE_MESSAGE_LOOP_H_
+#define LIBBRILLO_BRILLO_MESSAGE_LOOPS_FAKE_MESSAGE_LOOP_H_
+
+#include <functional>
+#include <map>
+#include <queue>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <base/location.h>
+#include <base/test/simple_test_clock.h>
+#include <base/time/time.h>
+
+#include <brillo/brillo_export.h>
+#include <brillo/message_loops/message_loop.h>
+
+namespace brillo {
+
+// The FakeMessageLoop implements a message loop that doesn't block or wait for
+// time based tasks to be ready. The tasks are executed in the order they should
+// be executed in a real message loop implementation, but the time is advanced
+// to the time when the first task should be executed instead of blocking.
+// To keep a consistent notion of time for other classes, FakeMessageLoop
+// optionally updates a SimpleTestClock instance when it needs to advance the
+// clock.
+// This message loop implementation is useful for unittests.
+class BRILLO_EXPORT FakeMessageLoop : public MessageLoop {
+ public:
+ // Create a FakeMessageLoop optionally using a SimpleTestClock to update the
+ // time when Run() or RunOnce(true) are called and should block.
+ explicit FakeMessageLoop(base::SimpleTestClock* clock);
+ ~FakeMessageLoop() override = default;
+
+ TaskId PostDelayedTask(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) override;
+ using MessageLoop::PostDelayedTask;
+ TaskId WatchFileDescriptor(const tracked_objects::Location& from_here,
+ int fd,
+ WatchMode mode,
+ bool persistent,
+ const base::Closure& task) override;
+ using MessageLoop::WatchFileDescriptor;
+ bool CancelTask(TaskId task_id) override;
+ bool RunOnce(bool may_block) override;
+
+ // FakeMessageLoop methods:
+
+ // Pretend, for the purpose of the FakeMessageLoop watching for file
+ // descriptors, that the file descriptor |fd| readiness to perform the
+ // operation described by |mode| is |ready|. Initially, no file descriptor
+ // is ready for any operation.
+ void SetFileDescriptorReadiness(int fd, WatchMode mode, bool ready);
+
+ // Return whether there are peding tasks. Useful to check that no
+ // callbacks were leaked.
+ bool PendingTasks();
+
+ private:
+ struct ScheduledTask {
+ tracked_objects::Location location;
+ bool persistent;
+ base::Closure callback;
+ };
+
+ // The sparse list of scheduled pending callbacks.
+ std::map<MessageLoop::TaskId, ScheduledTask> tasks_;
+
+ // Using std::greater<> for the priority_queue means that the top() of the
+ // queue is the lowest (earliest) time, and for the same time, the smallest
+ // TaskId. This determines the order in which the tasks will be fired.
+ std::priority_queue<
+ std::pair<base::Time, MessageLoop::TaskId>,
+ std::vector<std::pair<base::Time, MessageLoop::TaskId>>,
+ std::greater<std::pair<base::Time, MessageLoop::TaskId>>> fire_order_;
+
+ // The bag of watched (fd, mode) pair associated with the TaskId that's
+ // watching them.
+ std::multimap<std::pair<int, WatchMode>, MessageLoop::TaskId> fds_watched_;
+
+ // The set of (fd, mode) pairs that are faked as ready.
+ std::set<std::pair<int, WatchMode>> fds_ready_;
+
+ base::SimpleTestClock* test_clock_ = nullptr;
+ base::Time current_time_ = base::Time::FromDoubleT(1246996800.);
+
+ MessageLoop::TaskId last_id_ = kTaskIdNull;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeMessageLoop);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MESSAGE_LOOPS_FAKE_MESSAGE_LOOP_H_
diff --git a/libbrillo/brillo/message_loops/fake_message_loop_unittest.cc b/libbrillo/brillo/message_loops/fake_message_loop_unittest.cc
new file mode 100644
index 0000000..1f94a4b
--- /dev/null
+++ b/libbrillo/brillo/message_loops/fake_message_loop_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright 2015 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 <brillo/message_loops/fake_message_loop.h>
+
+#include <memory>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/test/simple_test_clock.h>
+#include <gtest/gtest.h>
+
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/message_loop.h>
+
+using base::Bind;
+using base::Time;
+using base::TimeDelta;
+using std::vector;
+
+namespace brillo {
+
+using TaskId = MessageLoop::TaskId;
+
+class FakeMessageLoopTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.reset(new FakeMessageLoop(nullptr));
+ EXPECT_TRUE(loop_.get());
+ }
+ void TearDown() override {
+ EXPECT_FALSE(loop_->PendingTasks());
+ }
+
+ base::SimpleTestClock clock_;
+ std::unique_ptr<FakeMessageLoop> loop_;
+};
+
+TEST_F(FakeMessageLoopTest, CancelTaskInvalidValuesTest) {
+ EXPECT_FALSE(loop_->CancelTask(MessageLoop::kTaskIdNull));
+ EXPECT_FALSE(loop_->CancelTask(1234));
+}
+
+TEST_F(FakeMessageLoopTest, PostDelayedTaskRunsInOrder) {
+ vector<int> order;
+ auto callback = [](std::vector<int>* order, int value) {
+ order->push_back(value);
+ };
+ loop_->PostDelayedTask(Bind(callback, base::Unretained(&order), 1),
+ TimeDelta::FromSeconds(1));
+ loop_->PostDelayedTask(Bind(callback, base::Unretained(&order), 4),
+ TimeDelta::FromSeconds(4));
+ loop_->PostDelayedTask(Bind(callback, base::Unretained(&order), 3),
+ TimeDelta::FromSeconds(3));
+ loop_->PostDelayedTask(Bind(callback, base::Unretained(&order), 2),
+ TimeDelta::FromSeconds(2));
+ // Run until all the tasks are run.
+ loop_->Run();
+ EXPECT_EQ((vector<int>{1, 2, 3, 4}), order);
+}
+
+TEST_F(FakeMessageLoopTest, PostDelayedTaskAdvancesTheTime) {
+ Time start = Time::FromInternalValue(1000000);
+ clock_.SetNow(start);
+ loop_.reset(new FakeMessageLoop(&clock_));
+ loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta::FromSeconds(1));
+ loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta::FromSeconds(2));
+ EXPECT_FALSE(loop_->RunOnce(false));
+ // If the callback didn't run, the time shouldn't change.
+ EXPECT_EQ(start, clock_.Now());
+
+ // If we run only one callback, the time should be set to the time that
+ // callack ran.
+ EXPECT_TRUE(loop_->RunOnce(true));
+ EXPECT_EQ(start + TimeDelta::FromSeconds(1), clock_.Now());
+
+ // If the clock is advanced manually, we should be able to run the
+ // callback without blocking, since the firing time is in the past.
+ clock_.SetNow(start + TimeDelta::FromSeconds(3));
+ EXPECT_TRUE(loop_->RunOnce(false));
+ // The time should not change even if the callback is due in the past.
+ EXPECT_EQ(start + TimeDelta::FromSeconds(3), clock_.Now());
+}
+
+TEST_F(FakeMessageLoopTest, WatchFileDescriptorWaits) {
+ int fd = 1234;
+ // We will simulate this situation. At the beginning, we will watch for a
+ // file descriptor that won't trigger for 10s. Then we will pretend it is
+ // ready after 10s and expect its callback to run just once.
+ int called = 0;
+ TaskId task_id = loop_->WatchFileDescriptor(
+ FROM_HERE, fd, MessageLoop::kWatchRead, false,
+ Bind([](int* called) { (*called)++; }, base::Unretained(&called)));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+
+ auto callback = [](FakeMessageLoop* loop) { loop->BreakLoop(); };
+ EXPECT_NE(
+ MessageLoop::kTaskIdNull,
+ loop_->PostDelayedTask(Bind(callback, base::Unretained(loop_.get())),
+ TimeDelta::FromSeconds(10)));
+ EXPECT_NE(
+ MessageLoop::kTaskIdNull,
+ loop_->PostDelayedTask(Bind(callback, base::Unretained(loop_.get())),
+ TimeDelta::FromSeconds(20)));
+ loop_->Run();
+ EXPECT_EQ(0, called);
+
+ loop_->SetFileDescriptorReadiness(fd, MessageLoop::kWatchRead, true);
+ loop_->Run();
+ EXPECT_EQ(1, called);
+ EXPECT_FALSE(loop_->CancelTask(task_id));
+}
+
+TEST_F(FakeMessageLoopTest, PendingTasksTest) {
+ loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta::FromSeconds(1));
+ EXPECT_TRUE(loop_->PendingTasks());
+ loop_->Run();
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/message_loops/glib_message_loop.cc b/libbrillo/brillo/message_loops/glib_message_loop.cc
new file mode 100644
index 0000000..20c271d
--- /dev/null
+++ b/libbrillo/brillo/message_loops/glib_message_loop.cc
@@ -0,0 +1,194 @@
+// Copyright 2015 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 <brillo/message_loops/glib_message_loop.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <brillo/location_logging.h>
+
+using base::Closure;
+
+namespace brillo {
+
+GlibMessageLoop::GlibMessageLoop() {
+ loop_ = g_main_loop_new(g_main_context_default(), FALSE);
+}
+
+GlibMessageLoop::~GlibMessageLoop() {
+ // Cancel all pending tasks when destroying the message loop.
+ for (const auto& task : tasks_) {
+ DVLOG_LOC(task.second->location, 1)
+ << "Removing task_id " << task.second->task_id
+ << " leaked on GlibMessageLoop, scheduled from this location.";
+ g_source_remove(task.second->source_id);
+ }
+ g_main_loop_unref(loop_);
+}
+
+MessageLoop::TaskId GlibMessageLoop::PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const Closure &task,
+ base::TimeDelta delay) {
+ TaskId task_id = NextTaskId();
+ // Note: While we store persistent = false in the ScheduledTask object, we
+ // don't check it in OnRanPostedTask() since it is always false for delayed
+ // tasks. This is only used for WatchFileDescriptor below.
+ ScheduledTask* scheduled_task = new ScheduledTask{
+ this, from_here, task_id, 0, false, std::move(task)};
+ DVLOG_LOC(from_here, 1) << "Scheduling delayed task_id " << task_id
+ << " to run in " << delay << ".";
+ scheduled_task->source_id = g_timeout_add_full(
+ G_PRIORITY_DEFAULT,
+ delay.InMillisecondsRoundedUp(),
+ &GlibMessageLoop::OnRanPostedTask,
+ reinterpret_cast<gpointer>(scheduled_task),
+ DestroyPostedTask);
+ tasks_[task_id] = scheduled_task;
+ return task_id;
+}
+
+MessageLoop::TaskId GlibMessageLoop::WatchFileDescriptor(
+ const tracked_objects::Location& from_here,
+ int fd,
+ WatchMode mode,
+ bool persistent,
+ const Closure &task) {
+ // Quick check to see if the fd is valid.
+ if (fcntl(fd, F_GETFD) == -1 && errno == EBADF)
+ return MessageLoop::kTaskIdNull;
+
+ GIOCondition condition = G_IO_NVAL;
+ switch (mode) {
+ case MessageLoop::kWatchRead:
+ condition = static_cast<GIOCondition>(G_IO_IN | G_IO_HUP | G_IO_NVAL);
+ break;
+ case MessageLoop::kWatchWrite:
+ condition = static_cast<GIOCondition>(G_IO_OUT | G_IO_HUP | G_IO_NVAL);
+ break;
+ default:
+ return MessageLoop::kTaskIdNull;
+ }
+
+ // TODO(deymo): Used g_unix_fd_add_full() instead of g_io_add_watch_full()
+ // when/if we switch to glib 2.36 or newer so we don't need to create a
+ // GIOChannel for this.
+ GIOChannel* io_channel = g_io_channel_unix_new(fd);
+ if (!io_channel)
+ return MessageLoop::kTaskIdNull;
+ GError* error = nullptr;
+ GIOStatus status = g_io_channel_set_encoding(io_channel, nullptr, &error);
+ if (status != G_IO_STATUS_NORMAL) {
+ LOG(ERROR) << "GError(" << error->code << "): "
+ << (error->message ? error->message : "(unknown)");
+ g_error_free(error);
+ // g_io_channel_set_encoding() documentation states that this should be
+ // valid in this context (a new io_channel), but enforce the check in
+ // debug mode.
+ DCHECK(status == G_IO_STATUS_NORMAL);
+ return MessageLoop::kTaskIdNull;
+ }
+
+ TaskId task_id = NextTaskId();
+ ScheduledTask* scheduled_task = new ScheduledTask{
+ this, from_here, task_id, 0, persistent, std::move(task)};
+ scheduled_task->source_id = g_io_add_watch_full(
+ io_channel,
+ G_PRIORITY_DEFAULT,
+ condition,
+ &GlibMessageLoop::OnWatchedFdReady,
+ reinterpret_cast<gpointer>(scheduled_task),
+ DestroyPostedTask);
+ // g_io_add_watch_full() increases the reference count on the newly created
+ // io_channel, so we can dereference it now and it will be free'd once the
+ // source is removed or now if g_io_add_watch_full() failed.
+ g_io_channel_unref(io_channel);
+
+ DVLOG_LOC(from_here, 1)
+ << "Watching fd " << fd << " for "
+ << (mode == MessageLoop::kWatchRead ? "reading" : "writing")
+ << (persistent ? " persistently" : " just once")
+ << " as task_id " << task_id
+ << (scheduled_task->source_id ? " successfully" : " failed.");
+
+ if (!scheduled_task->source_id) {
+ delete scheduled_task;
+ return MessageLoop::kTaskIdNull;
+ }
+ tasks_[task_id] = scheduled_task;
+ return task_id;
+}
+
+bool GlibMessageLoop::CancelTask(TaskId task_id) {
+ if (task_id == kTaskIdNull)
+ return false;
+ const auto task = tasks_.find(task_id);
+ // It is a programmer error to attempt to remove a non-existent source.
+ if (task == tasks_.end())
+ return false;
+ DVLOG_LOC(task->second->location, 1)
+ << "Removing task_id " << task_id << " scheduled from this location.";
+ guint source_id = task->second->source_id;
+ // We remove here the entry from the tasks_ map, the pointer will be deleted
+ // by the g_source_remove() call.
+ tasks_.erase(task);
+ return g_source_remove(source_id);
+}
+
+bool GlibMessageLoop::RunOnce(bool may_block) {
+ return g_main_context_iteration(nullptr, may_block);
+}
+
+void GlibMessageLoop::Run() {
+ g_main_loop_run(loop_);
+}
+
+void GlibMessageLoop::BreakLoop() {
+ g_main_loop_quit(loop_);
+}
+
+MessageLoop::TaskId GlibMessageLoop::NextTaskId() {
+ TaskId res;
+ do {
+ res = ++last_id_;
+ // We would run out of memory before we run out of task ids.
+ } while (!res || tasks_.find(res) != tasks_.end());
+ return res;
+}
+
+gboolean GlibMessageLoop::OnRanPostedTask(gpointer user_data) {
+ ScheduledTask* scheduled_task = reinterpret_cast<ScheduledTask*>(user_data);
+ DVLOG_LOC(scheduled_task->location, 1)
+ << "Running delayed task_id " << scheduled_task->task_id
+ << " scheduled from this location.";
+ // We only need to remove this task_id from the map. DestroyPostedTask will be
+ // called with this same |user_data| where we can delete the ScheduledTask.
+ scheduled_task->loop->tasks_.erase(scheduled_task->task_id);
+ scheduled_task->closure.Run();
+ return FALSE; // Removes the source since a callback can only be called once.
+}
+
+gboolean GlibMessageLoop::OnWatchedFdReady(GIOChannel *source,
+ GIOCondition condition,
+ gpointer user_data) {
+ ScheduledTask* scheduled_task = reinterpret_cast<ScheduledTask*>(user_data);
+ DVLOG_LOC(scheduled_task->location, 1)
+ << "Running task_id " << scheduled_task->task_id
+ << " for watching a file descriptor, scheduled from this location.";
+ if (!scheduled_task->persistent) {
+ // We only need to remove this task_id from the map. DestroyPostedTask will
+ // be called with this same |user_data| where we can delete the
+ // ScheduledTask.
+ scheduled_task->loop->tasks_.erase(scheduled_task->task_id);
+ }
+ scheduled_task->closure.Run();
+ return scheduled_task->persistent;
+}
+
+void GlibMessageLoop::DestroyPostedTask(gpointer user_data) {
+ delete reinterpret_cast<ScheduledTask*>(user_data);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/message_loops/glib_message_loop.h b/libbrillo/brillo/message_loops/glib_message_loop.h
new file mode 100644
index 0000000..50fe2ce
--- /dev/null
+++ b/libbrillo/brillo/message_loops/glib_message_loop.h
@@ -0,0 +1,83 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_MESSAGE_LOOPS_GLIB_MESSAGE_LOOP_H_
+#define LIBBRILLO_BRILLO_MESSAGE_LOOPS_GLIB_MESSAGE_LOOP_H_
+
+#include <map>
+#include <memory>
+
+#include <base/location.h>
+#include <base/time/time.h>
+#include <glib.h>
+
+#include <brillo/brillo_export.h>
+#include <brillo/message_loops/message_loop.h>
+
+namespace brillo {
+
+class BRILLO_EXPORT GlibMessageLoop : public MessageLoop {
+ public:
+ GlibMessageLoop();
+ ~GlibMessageLoop() override;
+
+ // MessageLoop overrides.
+ TaskId PostDelayedTask(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) override;
+ using MessageLoop::PostDelayedTask;
+ TaskId WatchFileDescriptor(const tracked_objects::Location& from_here,
+ int fd,
+ WatchMode mode,
+ bool persistent,
+ const base::Closure& task) override;
+ using MessageLoop::WatchFileDescriptor;
+ bool CancelTask(TaskId task_id) override;
+ bool RunOnce(bool may_block) override;
+ void Run() override;
+ void BreakLoop() override;
+
+ private:
+ // Called by the GLib's main loop when is time to call the callback scheduled
+ // with Post*Task(). The pointer to the callback passed when scheduling it is
+ // passed to this function as a gpointer on |user_data|.
+ static gboolean OnRanPostedTask(gpointer user_data);
+
+ // Called by the GLib's main loop when the watched source |source| is
+ // ready to perform the operation given in |condition| without blocking.
+ static gboolean OnWatchedFdReady(GIOChannel *source,
+ GIOCondition condition,
+ gpointer user_data);
+
+ // Called by the GLib's main loop when the scheduled callback is removed due
+ // to it being executed or canceled.
+ static void DestroyPostedTask(gpointer user_data);
+
+ // Return a new unused task_id.
+ TaskId NextTaskId();
+
+ GMainLoop* loop_;
+
+ struct ScheduledTask {
+ // A pointer to this GlibMessageLoop so we can remove the Task from the
+ // glib callback.
+ GlibMessageLoop* loop;
+ tracked_objects::Location location;
+
+ MessageLoop::TaskId task_id;
+ guint source_id;
+ bool persistent;
+ base::Closure closure;
+ };
+
+ std::map<MessageLoop::TaskId, ScheduledTask*> tasks_;
+
+ MessageLoop::TaskId last_id_ = kTaskIdNull;
+
+ DISALLOW_COPY_AND_ASSIGN(GlibMessageLoop);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MESSAGE_LOOPS_GLIB_MESSAGE_LOOP_H_
diff --git a/libbrillo/brillo/message_loops/glib_message_loop_unittest.cc b/libbrillo/brillo/message_loops/glib_message_loop_unittest.cc
new file mode 100644
index 0000000..4b72a11
--- /dev/null
+++ b/libbrillo/brillo/message_loops/glib_message_loop_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright 2015 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 <brillo/message_loops/glib_message_loop.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/posix/eintr_wrapper.h>
+#include <gtest/gtest.h>
+
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+
+using base::Bind;
+
+namespace brillo {
+
+using TaskId = MessageLoop::TaskId;
+
+class GlibMessageLoopTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.reset(new GlibMessageLoop());
+ EXPECT_TRUE(loop_.get());
+ }
+
+ std::unique_ptr<GlibMessageLoop> loop_;
+};
+
+// When you watch a file descriptor for reading, the guaranties are that a
+// blocking call to read() on that file descriptor will not block. This should
+// include the case when the other end of a pipe is closed or the file is empty.
+TEST_F(GlibMessageLoopTest, WatchFileDescriptorTriggersWhenEmpty) {
+ int fd = HANDLE_EINTR(open("/dev/null", O_RDONLY));
+ int called = 0;
+ TaskId task_id = loop_->WatchFileDescriptor(
+ FROM_HERE, fd, MessageLoop::kWatchRead, true,
+ Bind([&called] { called++; }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+ EXPECT_NE(0, MessageLoopRunMaxIterations(loop_.get(), 10));
+ EXPECT_LT(2, called);
+ EXPECT_TRUE(loop_->CancelTask(task_id));
+}
+
+// Test that an invalid file descriptor triggers the callback.
+TEST_F(GlibMessageLoopTest, WatchFileDescriptorTriggersWhenInvalid) {
+ int fd = HANDLE_EINTR(open("/dev/zero", O_RDONLY));
+ int called = 0;
+ TaskId task_id = loop_->WatchFileDescriptor(
+ FROM_HERE, fd, MessageLoop::kWatchRead, true,
+ Bind([&called, fd] {
+ if (!called)
+ IGNORE_EINTR(close(fd));
+ called++;
+ }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+ EXPECT_NE(0, MessageLoopRunMaxIterations(loop_.get(), 10));
+ EXPECT_LT(2, called);
+ EXPECT_TRUE(loop_->CancelTask(task_id));
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/message_loops/message_loop.cc b/libbrillo/brillo/message_loops/message_loop.cc
new file mode 100644
index 0000000..3d64ccb
--- /dev/null
+++ b/libbrillo/brillo/message_loops/message_loop.cc
@@ -0,0 +1,63 @@
+// Copyright 2015 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 <brillo/message_loops/message_loop.h>
+
+#include <base/lazy_instance.h>
+#include <base/logging.h>
+#include <base/threading/thread_local.h>
+
+namespace brillo {
+
+namespace {
+
+// A lazily created thread local storage for quick access to a thread's message
+// loop, if one exists. This should be safe and free of static constructors.
+base::LazyInstance<base::ThreadLocalPointer<MessageLoop> >::Leaky lazy_tls_ptr =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+const MessageLoop::TaskId MessageLoop::kTaskIdNull = 0;
+
+MessageLoop* MessageLoop::current() {
+ DCHECK(lazy_tls_ptr.Pointer()->Get() != nullptr) <<
+ "There isn't a MessageLoop for this thread. You need to initialize it "
+ "first.";
+ return lazy_tls_ptr.Pointer()->Get();
+}
+
+bool MessageLoop::ThreadHasCurrent() {
+ return lazy_tls_ptr.Pointer()->Get() != nullptr;
+}
+
+void MessageLoop::SetAsCurrent() {
+ DCHECK(lazy_tls_ptr.Pointer()->Get() == nullptr) <<
+ "There's already a MessageLoop for this thread.";
+ lazy_tls_ptr.Pointer()->Set(this);
+}
+
+void MessageLoop::ReleaseFromCurrent() {
+ DCHECK(lazy_tls_ptr.Pointer()->Get() == this) <<
+ "This is not the MessageLoop bound to the current thread.";
+ lazy_tls_ptr.Pointer()->Set(nullptr);
+}
+
+MessageLoop::~MessageLoop() {
+ if (lazy_tls_ptr.Pointer()->Get() == this)
+ lazy_tls_ptr.Pointer()->Set(nullptr);
+}
+
+void MessageLoop::Run() {
+ // Default implementation is to call RunOnce() blocking until there aren't
+ // more tasks scheduled.
+ while (!should_exit_ && RunOnce(true)) {}
+ should_exit_ = false;
+}
+
+void MessageLoop::BreakLoop() {
+ should_exit_ = true;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/message_loops/message_loop.h b/libbrillo/brillo/message_loops/message_loop.h
new file mode 100644
index 0000000..c7e3586
--- /dev/null
+++ b/libbrillo/brillo/message_loops/message_loop.h
@@ -0,0 +1,135 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_H_
+#define LIBBRILLO_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_H_
+
+#include <string>
+
+#include <base/callback.h>
+#include <base/location.h>
+#include <base/time/time.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+class BRILLO_EXPORT MessageLoop {
+ public:
+ virtual ~MessageLoop();
+
+ // A unique task identifier used to refer to scheduled callbacks.
+ using TaskId = uint64_t;
+
+ // The kNullEventId is reserved for an invalid task and will never be used
+ // to refer to a real task.
+ static const TaskId kTaskIdNull;
+
+ // Return the MessageLoop for the current thread. It is a fatal error to
+ // request the current MessageLoop if SetAsCurrent() was not called on the
+ // current thread. If you really need to, use ThreadHasCurrent() to check if
+ // there is a current thread.
+ static MessageLoop* current();
+
+ // Return whether there is a MessageLoop in the current thread.
+ static bool ThreadHasCurrent();
+
+ // Set this message loop as the current thread main loop. Only one message
+ // loop can be set at a time. Use ReleaseFromCurrent() to release it.
+ void SetAsCurrent();
+
+ // Release this instance from the current thread. This instance must have
+ // been previously set with SetAsCurrent().
+ void ReleaseFromCurrent();
+
+ // Schedule a Closure |task| to be executed after a |delay|. Returns a task
+ // identifier for the scheduled task that can be used to cancel the task
+ // before it is fired by passing it to CancelTask().
+ // In case of an error scheduling the task, the kTaskIdNull is returned.
+ // Note that once the call is executed or canceled, the TaskId could be reused
+ // at a later point.
+ // This methond can only be called from the same thread running the main loop.
+ virtual TaskId PostDelayedTask(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) = 0;
+ // Variant without the Location for easier usage.
+ TaskId PostDelayedTask(const base::Closure& task, base::TimeDelta delay) {
+ return PostDelayedTask(tracked_objects::Location(), task, delay);
+ }
+
+ // A convenience method to schedule a call with no delay.
+ // This methond can only be called from the same thread running the main loop.
+ TaskId PostTask(const base::Closure& task) {
+ return PostDelayedTask(task, base::TimeDelta());
+ }
+ TaskId PostTask(const tracked_objects::Location& from_here,
+ const base::Closure& task) {
+ return PostDelayedTask(from_here, task, base::TimeDelta());
+ }
+
+ // Watch mode flag used to watch for file descriptors.
+ enum WatchMode {
+ kWatchRead,
+ kWatchWrite,
+ };
+
+ // Watch a file descriptor |fd| for it to be ready to perform the operation
+ // passed in |mode| without blocking. When that happens, the |task| closure
+ // will be executed. If |persistent| is true, the file descriptor will
+ // continue to be watched and |task| will continue to be called until the task
+ // is canceled with CancelTask().
+ // Returns the TaskId describing this task. In case of error, returns
+ // kTaskIdNull.
+ virtual TaskId WatchFileDescriptor(const tracked_objects::Location& from_here,
+ int fd,
+ WatchMode mode,
+ bool persistent,
+ const base::Closure& task) = 0;
+
+ // Convenience function to call WatchFileDescriptor() without a location.
+ TaskId WatchFileDescriptor(int fd,
+ WatchMode mode,
+ bool persistent,
+ const base::Closure& task) {
+ return WatchFileDescriptor(
+ tracked_objects::Location(), fd, mode, persistent, task);
+ }
+
+ // Cancel a scheduled task. Returns whether the task was canceled. For
+ // example, if the callback was already executed (or is being executed) or was
+ // already canceled this method will fail. Note that the TaskId can be reused
+ // after it was executed or cancelled.
+ virtual bool CancelTask(TaskId task_id) = 0;
+
+ // ---------------------------------------------------------------------------
+ // Methods used to run and stop the message loop.
+
+ // Run one iteration of the message loop, dispatching up to one task. The
+ // |may_block| tells whether this method is allowed to block waiting for a
+ // task to be ready to run. Returns whether it ran a task. Note that even
+ // if |may_block| is true, this method can return false immediately if there
+ // are no more tasks registered.
+ virtual bool RunOnce(bool may_block) = 0;
+
+ // Run the main loop until there are no more registered tasks.
+ virtual void Run();
+
+ // Quit the running main loop immediately. This method will make the current
+ // running Run() method to return right after the current task returns back
+ // to the message loop without processing any other task.
+ virtual void BreakLoop();
+
+ protected:
+ MessageLoop() = default;
+
+ private:
+ // Tells whether Run() should quit the message loop in the default
+ // implementation.
+ bool should_exit_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageLoop);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_H_
diff --git a/libbrillo/brillo/message_loops/message_loop_unittest.cc b/libbrillo/brillo/message_loops/message_loop_unittest.cc
new file mode 100644
index 0000000..7cc2a30
--- /dev/null
+++ b/libbrillo/brillo/message_loops/message_loop_unittest.cc
@@ -0,0 +1,362 @@
+// Copyright 2015 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 <brillo/message_loops/message_loop.h>
+
+// These are the common tests for all the brillo::MessageLoop implementations
+// that should conform to this interface's contracts. For extra
+// implementation-specific tests see the particular implementation unittests in
+// the *_unittest.cc files.
+
+#include <memory>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/posix/eintr_wrapper.h>
+#include <gtest/gtest.h>
+
+#include <brillo/bind_lambda.h>
+#include <brillo/unittest_utils.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/glib_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+
+using base::Bind;
+using base::TimeDelta;
+
+namespace brillo {
+
+using TaskId = MessageLoop::TaskId;
+
+template <typename T>
+class MessageLoopTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ MessageLoopSetUp();
+ EXPECT_TRUE(this->loop_.get());
+ }
+
+ std::unique_ptr<base::MessageLoopForIO> base_loop_;
+
+ std::unique_ptr<MessageLoop> loop_;
+
+ private:
+ // These MessageLoopSetUp() methods are used to setup each MessageLoop
+ // according to its constructor requirements.
+ void MessageLoopSetUp();
+};
+
+template <>
+void MessageLoopTest<GlibMessageLoop>::MessageLoopSetUp() {
+ loop_.reset(new GlibMessageLoop());
+}
+
+template <>
+void MessageLoopTest<BaseMessageLoop>::MessageLoopSetUp() {
+ base_loop_.reset(new base::MessageLoopForIO());
+ loop_.reset(new BaseMessageLoop(base::MessageLoopForIO::current()));
+}
+
+// This setups gtest to run each one of the following TYPED_TEST test cases on
+// on each implementation.
+typedef ::testing::Types<
+ GlibMessageLoop,
+ BaseMessageLoop> MessageLoopTypes;
+TYPED_TEST_CASE(MessageLoopTest, MessageLoopTypes);
+
+
+TYPED_TEST(MessageLoopTest, CancelTaskInvalidValuesTest) {
+ EXPECT_FALSE(this->loop_->CancelTask(MessageLoop::kTaskIdNull));
+ EXPECT_FALSE(this->loop_->CancelTask(1234));
+}
+
+TYPED_TEST(MessageLoopTest, PostTaskTest) {
+ bool called = false;
+ TaskId task_id = this->loop_->PostTask(FROM_HERE,
+ Bind([&called]() { called = true; }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+ MessageLoopRunMaxIterations(this->loop_.get(), 100);
+ EXPECT_TRUE(called);
+}
+
+// Tests that we can cancel tasks right after we schedule them.
+TYPED_TEST(MessageLoopTest, PostTaskCancelledTest) {
+ bool called = false;
+ TaskId task_id = this->loop_->PostTask(FROM_HERE,
+ Bind([&called]() { called = true; }));
+ EXPECT_TRUE(this->loop_->CancelTask(task_id));
+ MessageLoopRunMaxIterations(this->loop_.get(), 100);
+ EXPECT_FALSE(called);
+ // Can't remove a task you already removed.
+ EXPECT_FALSE(this->loop_->CancelTask(task_id));
+}
+
+TYPED_TEST(MessageLoopTest, PostDelayedTaskRunsEventuallyTest) {
+ bool called = false;
+ TaskId task_id = this->loop_->PostDelayedTask(
+ FROM_HERE,
+ Bind([&called]() { called = true; }),
+ TimeDelta::FromMilliseconds(50));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+ MessageLoopRunUntil(this->loop_.get(),
+ TimeDelta::FromSeconds(10),
+ Bind([&called]() { return called; }));
+ // Check that the main loop finished before the 10 seconds timeout, so it
+ // finished due to the callback being called and not due to the timeout.
+ EXPECT_TRUE(called);
+}
+
+// Test that you can call the overloaded version of PostDelayedTask from
+// MessageLoop. This is important because only one of the two methods is
+// virtual, so you need to unhide the other when overriding the virtual one.
+TYPED_TEST(MessageLoopTest, PostDelayedTaskWithoutLocation) {
+ this->loop_->PostDelayedTask(Bind(&base::DoNothing), TimeDelta());
+ EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100));
+}
+
+TYPED_TEST(MessageLoopTest, WatchForInvalidFD) {
+ bool called = false;
+ EXPECT_EQ(MessageLoop::kTaskIdNull, this->loop_->WatchFileDescriptor(
+ FROM_HERE, -1, MessageLoop::kWatchRead, true,
+ Bind([&called] { called = true; })));
+ EXPECT_EQ(MessageLoop::kTaskIdNull, this->loop_->WatchFileDescriptor(
+ FROM_HERE, -1, MessageLoop::kWatchWrite, true,
+ Bind([&called] { called = true; })));
+ EXPECT_EQ(0, MessageLoopRunMaxIterations(this->loop_.get(), 100));
+ EXPECT_FALSE(called);
+}
+
+TYPED_TEST(MessageLoopTest, CancelWatchedFileDescriptor) {
+ ScopedPipe pipe;
+ bool called = false;
+ TaskId task_id = this->loop_->WatchFileDescriptor(
+ FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true,
+ Bind([&called] { called = true; }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+ // The reader end is blocked because we didn't write anything to the writer
+ // end.
+ EXPECT_EQ(0, MessageLoopRunMaxIterations(this->loop_.get(), 100));
+ EXPECT_FALSE(called);
+ EXPECT_TRUE(this->loop_->CancelTask(task_id));
+}
+
+// When you watch a file descriptor for reading, the guaranties are that a
+// blocking call to read() on that file descriptor will not block. This should
+// include the case when the other end of a pipe is closed or the file is empty.
+TYPED_TEST(MessageLoopTest, WatchFileDescriptorTriggersWhenPipeClosed) {
+ ScopedPipe pipe;
+ bool called = false;
+ EXPECT_EQ(0, HANDLE_EINTR(close(pipe.writer)));
+ pipe.writer = -1;
+ TaskId task_id = this->loop_->WatchFileDescriptor(
+ FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true,
+ Bind([&called] { called = true; }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+ // The reader end is not blocked because we closed the writer end so a read on
+ // the reader end would return 0 bytes read.
+ EXPECT_NE(0, MessageLoopRunMaxIterations(this->loop_.get(), 10));
+ EXPECT_TRUE(called);
+ EXPECT_TRUE(this->loop_->CancelTask(task_id));
+}
+
+// When a WatchFileDescriptor task is scheduled with |persistent| = true, we
+// should keep getting a call whenever the file descriptor is ready.
+TYPED_TEST(MessageLoopTest, WatchFileDescriptorPersistently) {
+ ScopedPipe pipe;
+ EXPECT_EQ(1, HANDLE_EINTR(write(pipe.writer, "a", 1)));
+
+ int called = 0;
+ TaskId task_id = this->loop_->WatchFileDescriptor(
+ FROM_HERE, pipe.reader, MessageLoop::kWatchRead, true,
+ Bind([&called] { called++; }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+ // We let the main loop run for 20 iterations to give it enough iterations to
+ // verify that our callback was called more than one. We only check that our
+ // callback is called more than once.
+ EXPECT_EQ(20, MessageLoopRunMaxIterations(this->loop_.get(), 20));
+ EXPECT_LT(1, called);
+ EXPECT_TRUE(this->loop_->CancelTask(task_id));
+}
+
+TYPED_TEST(MessageLoopTest, WatchFileDescriptorNonPersistent) {
+ ScopedPipe pipe;
+ EXPECT_EQ(1, HANDLE_EINTR(write(pipe.writer, "a", 1)));
+
+ int called = 0;
+ TaskId task_id = this->loop_->WatchFileDescriptor(
+ FROM_HERE, pipe.reader, MessageLoop::kWatchRead, false,
+ Bind([&called] { called++; }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+ // We let the main loop run for 20 iterations but we just expect it to run
+ // at least once. The callback should be called exactly once since we
+ // scheduled it non-persistently. After it ran, we shouldn't be able to cancel
+ // this task.
+ EXPECT_LT(0, MessageLoopRunMaxIterations(this->loop_.get(), 20));
+ EXPECT_EQ(1, called);
+ EXPECT_FALSE(this->loop_->CancelTask(task_id));
+}
+
+TYPED_TEST(MessageLoopTest, WatchFileDescriptorForReadAndWriteSimultaneously) {
+ ScopedSocketPair socks;
+ EXPECT_EQ(1, HANDLE_EINTR(write(socks.right, "a", 1)));
+ // socks.left should be able to read this "a" and should also be able to write
+ // without blocking since the kernel has some buffering for it.
+
+ TaskId read_task_id = this->loop_->WatchFileDescriptor(
+ FROM_HERE, socks.left, MessageLoop::kWatchRead, true,
+ Bind([this, &read_task_id] {
+ EXPECT_TRUE(this->loop_->CancelTask(read_task_id))
+ << "task_id" << read_task_id;
+ }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, read_task_id);
+
+ TaskId write_task_id = this->loop_->WatchFileDescriptor(
+ FROM_HERE, socks.left, MessageLoop::kWatchWrite, true,
+ Bind([this, &write_task_id] {
+ EXPECT_TRUE(this->loop_->CancelTask(write_task_id));
+ }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, write_task_id);
+
+ EXPECT_LT(0, MessageLoopRunMaxIterations(this->loop_.get(), 20));
+
+ EXPECT_FALSE(this->loop_->CancelTask(read_task_id));
+ EXPECT_FALSE(this->loop_->CancelTask(write_task_id));
+}
+
+// Test that we can cancel the task we are running, and should just fail.
+TYPED_TEST(MessageLoopTest, DeleteTaskFromSelf) {
+ bool cancel_result = true; // We would expect this to be false.
+ MessageLoop* loop_ptr = this->loop_.get();
+ TaskId task_id;
+ task_id = this->loop_->PostTask(
+ FROM_HERE,
+ Bind([&cancel_result, loop_ptr, &task_id]() {
+ cancel_result = loop_ptr->CancelTask(task_id);
+ }));
+ EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100));
+ EXPECT_FALSE(cancel_result);
+}
+
+// Test that we can cancel a non-persistent file descriptor watching callback,
+// which should fail.
+TYPED_TEST(MessageLoopTest, DeleteNonPersistenIOTaskFromSelf) {
+ ScopedPipe pipe;
+ TaskId task_id = this->loop_->WatchFileDescriptor(
+ FROM_HERE, pipe.writer, MessageLoop::kWatchWrite, false /* persistent */,
+ Bind([this, &task_id] {
+ EXPECT_FALSE(this->loop_->CancelTask(task_id));
+ task_id = MessageLoop::kTaskIdNull;
+ }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+ EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100));
+ EXPECT_EQ(MessageLoop::kTaskIdNull, task_id);
+}
+
+// Test that we can cancel a persistent file descriptor watching callback from
+// the same callback.
+TYPED_TEST(MessageLoopTest, DeletePersistenIOTaskFromSelf) {
+ ScopedPipe pipe;
+ TaskId task_id = this->loop_->WatchFileDescriptor(
+ FROM_HERE, pipe.writer, MessageLoop::kWatchWrite, true /* persistent */,
+ Bind([this, &task_id] {
+ EXPECT_TRUE(this->loop_->CancelTask(task_id));
+ task_id = MessageLoop::kTaskIdNull;
+ }));
+ EXPECT_NE(MessageLoop::kTaskIdNull, task_id);
+ EXPECT_EQ(1, MessageLoopRunMaxIterations(this->loop_.get(), 100));
+ EXPECT_EQ(MessageLoop::kTaskIdNull, task_id);
+}
+
+// Test that we can cancel several persistent file descriptor watching callbacks
+// from a scheduled callback. In the BaseMessageLoop implementation, this code
+// will cause us to cancel an IOTask that has a pending delayed task, but
+// otherwise is a valid test case on all implementations.
+TYPED_TEST(MessageLoopTest, DeleteAllPersistenIOTaskFromSelf) {
+ const int kNumTasks = 5;
+ ScopedPipe pipes[kNumTasks];
+ TaskId task_ids[kNumTasks];
+
+ for (int i = 0; i < kNumTasks; ++i) {
+ task_ids[i] = this->loop_->WatchFileDescriptor(
+ FROM_HERE, pipes[i].writer, MessageLoop::kWatchWrite,
+ true /* persistent */,
+ Bind([this, kNumTasks, &task_ids] {
+ for (int j = 0; j < kNumTasks; ++j) {
+ // Once we cancel all the tasks, none should run, so this code runs
+ // only once from one callback.
+ EXPECT_TRUE(this->loop_->CancelTask(task_ids[j]));
+ task_ids[j] = MessageLoop::kTaskIdNull;
+ }
+ }));
+ }
+ MessageLoopRunMaxIterations(this->loop_.get(), 100);
+ for (int i = 0; i < kNumTasks; ++i) {
+ EXPECT_EQ(MessageLoop::kTaskIdNull, task_ids[i]);
+ }
+}
+
+// Test that if there are several tasks watching for file descriptors to be
+// available or simply waiting in the message loop are fairly scheduled to run.
+// In other words, this test ensures that having a file descriptor always
+// available doesn't prevent other file descriptors watching tasks or delayed
+// tasks to be dispatched, causing starvation.
+TYPED_TEST(MessageLoopTest, AllTasksAreEqual) {
+ int total_calls = 0;
+
+ // First, schedule a repeating timeout callback to run from the main loop.
+ int timeout_called = 0;
+ base::Closure timeout_callback;
+ MessageLoop::TaskId timeout_task;
+ timeout_callback = base::Bind(
+ [this, &timeout_called, &total_calls, &timeout_callback, &timeout_task] {
+ timeout_called++;
+ total_calls++;
+ timeout_task = this->loop_->PostTask(FROM_HERE, Bind(timeout_callback));
+ if (total_calls > 100)
+ this->loop_->BreakLoop();
+ });
+ timeout_task = this->loop_->PostTask(FROM_HERE, timeout_callback);
+
+ // Second, schedule several file descriptor watchers.
+ const int kNumTasks = 3;
+ ScopedPipe pipes[kNumTasks];
+ MessageLoop::TaskId tasks[kNumTasks];
+
+ int reads[kNumTasks] = {};
+ auto fd_callback = [this, &pipes, &reads, &total_calls](int i) {
+ reads[i]++;
+ total_calls++;
+ char c;
+ EXPECT_EQ(1, HANDLE_EINTR(read(pipes[i].reader, &c, 1)));
+ if (total_calls > 100)
+ this->loop_->BreakLoop();
+ };
+
+ for (int i = 0; i < kNumTasks; ++i) {
+ tasks[i] = this->loop_->WatchFileDescriptor(
+ FROM_HERE, pipes[i].reader, MessageLoop::kWatchRead,
+ true /* persistent */,
+ Bind(fd_callback, i));
+ // Make enough bytes available on each file descriptor. This should not
+ // block because we set the size of the file descriptor buffer when
+ // creating it.
+ std::vector<char> blob(1000, 'a');
+ EXPECT_EQ(blob.size(),
+ HANDLE_EINTR(write(pipes[i].writer, blob.data(), blob.size())));
+ }
+ this->loop_->Run();
+ EXPECT_GT(total_calls, 100);
+ // We run the loop up 100 times and expect each callback to run at least 10
+ // times. A good scheduler should balance these callbacks.
+ EXPECT_GE(timeout_called, 10);
+ EXPECT_TRUE(this->loop_->CancelTask(timeout_task));
+ for (int i = 0; i < kNumTasks; ++i) {
+ EXPECT_GE(reads[i], 10) << "Reading from pipes[" << i << "], fd "
+ << pipes[i].reader;
+ EXPECT_TRUE(this->loop_->CancelTask(tasks[i]));
+ }
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/message_loops/message_loop_utils.cc b/libbrillo/brillo/message_loops/message_loop_utils.cc
new file mode 100644
index 0000000..9ebe865
--- /dev/null
+++ b/libbrillo/brillo/message_loops/message_loop_utils.cc
@@ -0,0 +1,35 @@
+// Copyright 2015 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 <brillo/message_loops/message_loop_utils.h>
+
+#include <base/location.h>
+#include <brillo/bind_lambda.h>
+
+namespace brillo {
+
+void MessageLoopRunUntil(
+ MessageLoop* loop,
+ base::TimeDelta timeout,
+ base::Callback<bool()> terminate) {
+ bool timeout_called = false;
+ MessageLoop::TaskId task_id = loop->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](bool* timeout_called) { *timeout_called = true; },
+ base::Unretained(&timeout_called)),
+ timeout);
+ while (!timeout_called && (terminate.is_null() || !terminate.Run()))
+ loop->RunOnce(true);
+
+ if (!timeout_called)
+ loop->CancelTask(task_id);
+}
+
+int MessageLoopRunMaxIterations(MessageLoop* loop, int iterations) {
+ int result;
+ for (result = 0; result < iterations && loop->RunOnce(false); result++) {}
+ return result;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/message_loops/message_loop_utils.h b/libbrillo/brillo/message_loops/message_loop_utils.h
new file mode 100644
index 0000000..d49ebdf
--- /dev/null
+++ b/libbrillo/brillo/message_loops/message_loop_utils.h
@@ -0,0 +1,30 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_UTILS_H_
+#define LIBBRILLO_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_UTILS_H_
+
+#include <base/callback.h>
+#include <base/time/time.h>
+
+#include <brillo/brillo_export.h>
+#include <brillo/message_loops/message_loop.h>
+
+namespace brillo {
+
+// Run the MessageLoop until the condition passed in |terminate| returns true
+// or the timeout expires.
+BRILLO_EXPORT void MessageLoopRunUntil(
+ MessageLoop* loop,
+ base::TimeDelta timeout,
+ base::Callback<bool()> terminate);
+
+// Run the MessageLoop |loop| for up to |iterations| times without blocking.
+// Return the number of tasks run.
+BRILLO_EXPORT int MessageLoopRunMaxIterations(MessageLoop* loop,
+ int iterations);
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MESSAGE_LOOPS_MESSAGE_LOOP_UTILS_H_
diff --git a/libbrillo/brillo/message_loops/mock_message_loop.h b/libbrillo/brillo/message_loops/mock_message_loop.h
new file mode 100644
index 0000000..71632f2
--- /dev/null
+++ b/libbrillo/brillo/message_loops/mock_message_loop.h
@@ -0,0 +1,89 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_MESSAGE_LOOPS_MOCK_MESSAGE_LOOP_H_
+#define LIBBRILLO_BRILLO_MESSAGE_LOOPS_MOCK_MESSAGE_LOOP_H_
+
+#include <gmock/gmock.h>
+
+#include <base/location.h>
+#include <base/test/simple_test_clock.h>
+#include <base/time/time.h>
+
+#include <brillo/brillo_export.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+
+namespace brillo {
+
+// The MockMessageLoop is a mockable MessageLoop that will by default act as a
+// FakeMessageLoop. It is possible to set expectations with EXPECT_CALL without
+// any action associated and they will call the same methods in the underlying
+// FakeMessageLoop implementation.
+// This message loop implementation is useful to check interaction with the
+// message loop when running unittests.
+class BRILLO_EXPORT MockMessageLoop : public MessageLoop {
+ public:
+ // Create a FakeMessageLoop optionally using a SimpleTestClock to update the
+ // time when Run() or RunOnce(true) are called and should block.
+ explicit MockMessageLoop(base::SimpleTestClock* clock)
+ : fake_loop_(clock) {
+ // Redirect all actions to calling the underlying FakeMessageLoop by
+ // default. For the overloaded methods, we need to disambiguate between the
+ // different options by specifying the type of the method pointer.
+ ON_CALL(*this, PostDelayedTask(::testing::_, ::testing::_, ::testing::_))
+ .WillByDefault(::testing::Invoke(
+ &fake_loop_,
+ static_cast<TaskId(FakeMessageLoop::*)(
+ const tracked_objects::Location&,
+ const base::Closure&,
+ base::TimeDelta)>(
+ &FakeMessageLoop::PostDelayedTask)));
+ ON_CALL(*this, WatchFileDescriptor(
+ ::testing::_, ::testing::_, ::testing::_, ::testing::_, ::testing::_))
+ .WillByDefault(::testing::Invoke(
+ &fake_loop_,
+ static_cast<TaskId(FakeMessageLoop::*)(
+ const tracked_objects::Location&, int, WatchMode, bool,
+ const base::Closure&)>(
+ &FakeMessageLoop::WatchFileDescriptor)));
+ ON_CALL(*this, CancelTask(::testing::_))
+ .WillByDefault(::testing::Invoke(&fake_loop_,
+ &FakeMessageLoop::CancelTask));
+ ON_CALL(*this, RunOnce(::testing::_))
+ .WillByDefault(::testing::Invoke(&fake_loop_,
+ &FakeMessageLoop::RunOnce));
+ }
+ ~MockMessageLoop() override = default;
+
+ MOCK_METHOD3(PostDelayedTask,
+ TaskId(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay));
+ using MessageLoop::PostDelayedTask;
+ MOCK_METHOD5(WatchFileDescriptor,
+ TaskId(const tracked_objects::Location& from_here,
+ int fd,
+ WatchMode mode,
+ bool persistent,
+ const base::Closure& task));
+ using MessageLoop::WatchFileDescriptor;
+ MOCK_METHOD1(CancelTask, bool(TaskId task_id));
+ MOCK_METHOD1(RunOnce, bool(bool may_block));
+
+ // Returns the actual FakeMessageLoop instance so default actions can be
+ // override with other actions or call
+ FakeMessageLoop* fake_loop() {
+ return &fake_loop_;
+ }
+
+ private:
+ FakeMessageLoop fake_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockMessageLoop);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MESSAGE_LOOPS_MOCK_MESSAGE_LOOP_H_
diff --git a/libbrillo/brillo/mime_utils.cc b/libbrillo/brillo/mime_utils.cc
new file mode 100644
index 0000000..d98eb43
--- /dev/null
+++ b/libbrillo/brillo/mime_utils.cc
@@ -0,0 +1,163 @@
+// 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 <brillo/mime_utils.h>
+
+#include <algorithm>
+#include <base/strings/string_util.h>
+#include <brillo/strings/string_utils.h>
+
+namespace brillo {
+
+// ***************************************************************************
+// ******************************* 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";
+const char mime::application::kProtobuf[] = "application/x-protobuf";
+
+const char mime::multipart::kFormData[] = "multipart/form-data";
+const char mime::multipart::kMixed[] = "multipart/mixed";
+
+// ***************************************************************************
+// **************************** 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 =
+ brillo::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 = brillo::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 = brillo::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(brillo::string_utils::Join("/", type, subtype));
+ for (const auto& pair : parameters) {
+ parts.push_back(
+ brillo::string_utils::Join("=", pair.first, EncodeParam(pair.second)));
+ }
+ return brillo::string_utils::Join("; ", parts);
+}
+
+std::string mime::GetType(const std::string& mime_string) {
+ std::string mime = mime::RemoveParameters(mime_string);
+ return brillo::string_utils::SplitAtFirst(mime, "/").first;
+}
+
+std::string mime::GetSubtype(const std::string& mime_string) {
+ std::string mime = mime::RemoveParameters(mime_string);
+ return brillo::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, ¶meters))
+ return parameters;
+
+ return mime::Parameters();
+}
+
+std::string mime::RemoveParameters(const std::string& mime_string) {
+ return brillo::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 += brillo::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::EqualsCaseInsensitiveASCII(pair.first.c_str(), paramName.c_str()))
+ return pair.second;
+ }
+ return std::string();
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/mime_utils.h b/libbrillo/brillo/mime_utils.h
new file mode 100644
index 0000000..869b19e
--- /dev/null
+++ b/libbrillo/brillo/mime_utils.h
@@ -0,0 +1,126 @@
+// 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 LIBBRILLO_BRILLO_MIME_UTILS_H_
+#define LIBBRILLO_BRILLO_MIME_UTILS_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/compiler_specific.h>
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+namespace mime {
+
+namespace types {
+// Main MIME type categories
+BRILLO_EXPORT extern const char kApplication[]; // application
+BRILLO_EXPORT extern const char kAudio[]; // audio
+BRILLO_EXPORT extern const char kImage[]; // image
+BRILLO_EXPORT extern const char kMessage[]; // message
+BRILLO_EXPORT extern const char kMultipart[]; // multipart
+BRILLO_EXPORT extern const char kText[]; // test
+BRILLO_EXPORT extern const char kVideo[]; // video
+} // namespace types
+
+namespace parameters {
+// Common MIME parameters
+BRILLO_EXPORT extern const char kCharset[]; // charset=...
+} // namespace parameters
+
+namespace image {
+// Common image MIME types
+BRILLO_EXPORT extern const char kJpeg[]; // image/jpeg
+BRILLO_EXPORT extern const char kPng[]; // image/png
+BRILLO_EXPORT extern const char kBmp[]; // image/bmp
+BRILLO_EXPORT extern const char kTiff[]; // image/tiff
+BRILLO_EXPORT extern const char kGif[]; // image/gif
+} // namespace image
+
+namespace text {
+// Common text MIME types
+BRILLO_EXPORT extern const char kPlain[]; // text/plain
+BRILLO_EXPORT extern const char kHtml[]; // text/html
+BRILLO_EXPORT extern const char kXml[]; // text/xml
+} // namespace text
+
+namespace application {
+// Common application MIME types
+// application/octet-stream
+BRILLO_EXPORT extern const char kOctet_stream[];
+// application/json
+BRILLO_EXPORT extern const char kJson[];
+// application/x-www-form-urlencoded
+BRILLO_EXPORT extern const char kWwwFormUrlEncoded[];
+// application/x-protobuf
+BRILLO_EXPORT extern const char kProtobuf[];
+} // namespace application
+
+namespace multipart {
+// Common multipart MIME types
+// multipart/form-data
+BRILLO_EXPORT extern const char kFormData[];
+// multipart/mixed
+BRILLO_EXPORT extern const char kMixed[];
+} // namespace multipart
+
+using Parameters = std::vector<std::pair<std::string, std::string>>;
+
+// 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"
+BRILLO_EXPORT 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")
+BRILLO_EXPORT 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"}})
+BRILLO_EXPORT 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"
+BRILLO_EXPORT std::string GetType(const std::string& mime_string);
+
+// Returns the MIME sub-type from MIME string.
+// "text/plain;charset=utf-8" => "plain"
+BRILLO_EXPORT std::string GetSubtype(const std::string& mime_string);
+
+// Returns the MIME parameters from MIME string.
+// "text/plain;charset=utf-8" => {{"charset","utf-8"}}
+BRILLO_EXPORT Parameters GetParameters(const std::string& mime_string);
+
+// Removes parameters from a MIME string
+// "text/plain;charset=utf-8" => "text/plain"
+BRILLO_EXPORT 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"
+BRILLO_EXPORT 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"
+BRILLO_EXPORT std::string GetParameterValue(const std::string& mime_string,
+ const std::string& paramName);
+
+} // namespace mime
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MIME_UTILS_H_
diff --git a/libbrillo/brillo/mime_utils_unittest.cc b/libbrillo/brillo/mime_utils_unittest.cc
new file mode 100644
index 0000000..a7595dc
--- /dev/null
+++ b/libbrillo/brillo/mime_utils_unittest.cc
@@ -0,0 +1,66 @@
+// 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 <brillo/mime_utils.h>
+
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+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, ¶meters));
+ 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);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/minijail/minijail.cc b/libbrillo/brillo/minijail/minijail.cc
new file mode 100644
index 0000000..4d47c05
--- /dev/null
+++ b/libbrillo/brillo/minijail/minijail.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "brillo/minijail/minijail.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+using std::vector;
+
+namespace brillo {
+
+static base::LazyInstance<Minijail> g_minijail = LAZY_INSTANCE_INITIALIZER;
+
+Minijail::Minijail() {}
+
+Minijail::~Minijail() {}
+
+// static
+Minijail* Minijail::GetInstance() {
+ return g_minijail.Pointer();
+}
+
+struct minijail* Minijail::New() {
+ return minijail_new();
+}
+
+void Minijail::Destroy(struct minijail* jail) {
+ minijail_destroy(jail);
+}
+
+void Minijail::DropRoot(struct minijail* jail, uid_t uid, gid_t gid) {
+ minijail_change_uid(jail, uid);
+ minijail_change_gid(jail, gid);
+}
+
+bool Minijail::DropRoot(struct minijail* jail,
+ const char* user,
+ const char* group) {
+ // |user| and |group| are copied so the only reason either of these
+ // calls can fail is ENOMEM.
+ return !minijail_change_user(jail, user) &&
+ !minijail_change_group(jail, group);
+}
+
+void Minijail::EnterNewPidNamespace(struct minijail* jail) {
+ minijail_namespace_pids(jail);
+}
+
+void Minijail::MountTmp(struct minijail* jail) {
+ minijail_mount_tmp(jail);
+}
+
+void Minijail::UseSeccompFilter(struct minijail* jail, const char* path) {
+ minijail_no_new_privs(jail);
+ minijail_use_seccomp_filter(jail);
+ minijail_parse_seccomp_filters(jail, path);
+}
+
+void Minijail::UseCapabilities(struct minijail* jail, uint64_t capmask) {
+ minijail_use_caps(jail, capmask);
+}
+
+void Minijail::ResetSignalMask(struct minijail* jail) {
+ minijail_reset_signal_mask(jail);
+}
+
+void Minijail::Enter(struct minijail* jail) {
+ minijail_enter(jail);
+}
+
+bool Minijail::Run(struct minijail* jail, vector<char*> args, pid_t* pid) {
+ return minijail_run_pid(jail, args[0], args.data(), pid) == 0;
+}
+
+bool Minijail::RunSync(struct minijail* jail, vector<char*> args, int* status) {
+ pid_t pid;
+ if (Run(jail, args, &pid) && waitpid(pid, status, 0) == pid) {
+ return true;
+ }
+
+ return false;
+}
+
+bool Minijail::RunPipe(struct minijail* jail,
+ vector<char*> args,
+ pid_t* pid,
+ int* stdin) {
+#if defined(__ANDROID__)
+ return minijail_run_pid_pipes_no_preload(jail, args[0], args.data(), pid,
+ stdin, NULL, NULL) == 0;
+#else
+ return minijail_run_pid_pipes(jail, args[0], args.data(), pid, stdin, NULL,
+ NULL) == 0;
+#endif // __ANDROID__
+}
+
+bool Minijail::RunPipes(struct minijail* jail,
+ vector<char*> args,
+ pid_t* pid,
+ int* stdin,
+ int* stdout,
+ int* stderr) {
+#if defined(__ANDROID__)
+ return minijail_run_pid_pipes_no_preload(jail, args[0], args.data(), pid,
+ stdin, stdout, stderr) == 0;
+#else
+ return minijail_run_pid_pipes(jail, args[0], args.data(), pid, stdin, stdout,
+ stderr) == 0;
+#endif // __ANDROID__
+}
+
+bool Minijail::RunAndDestroy(struct minijail* jail,
+ vector<char*> args,
+ pid_t* pid) {
+ bool res = Run(jail, args, pid);
+ Destroy(jail);
+ return res;
+}
+
+bool Minijail::RunSyncAndDestroy(struct minijail* jail,
+ vector<char*> args,
+ int* status) {
+ bool res = RunSync(jail, args, status);
+ Destroy(jail);
+ return res;
+}
+
+bool Minijail::RunPipeAndDestroy(struct minijail* jail,
+ vector<char*> args,
+ pid_t* pid,
+ int* stdin) {
+ bool res = RunPipe(jail, args, pid, stdin);
+ Destroy(jail);
+ return res;
+}
+
+bool Minijail::RunPipesAndDestroy(struct minijail* jail,
+ vector<char*> args,
+ pid_t* pid,
+ int* stdin,
+ int* stdout,
+ int* stderr) {
+ bool res = RunPipes(jail, args, pid, stdin, stdout, stderr);
+ Destroy(jail);
+ return res;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/minijail/minijail.h b/libbrillo/brillo/minijail/minijail.h
new file mode 100644
index 0000000..4c1431d
--- /dev/null
+++ b/libbrillo/brillo/minijail/minijail.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_BRILLO_MINIJAIL_MINIJAIL_H_
+#define LIBBRILLO_BRILLO_MINIJAIL_MINIJAIL_H_
+
+#include <vector>
+
+extern "C" {
+#include <linux/capability.h>
+#include <sys/types.h>
+}
+
+#include <base/lazy_instance.h>
+
+#include <libminijail.h>
+
+namespace brillo {
+
+// A Minijail abstraction allowing Minijail mocking in tests.
+class Minijail {
+ public:
+ virtual ~Minijail();
+
+ // This is a singleton -- use Minijail::GetInstance()->Foo().
+ static Minijail* GetInstance();
+
+ // minijail_new
+ virtual struct minijail* New();
+ // minijail_destroy
+ virtual void Destroy(struct minijail* jail);
+
+ // minijail_change_uid/minijail_change_gid
+ virtual void DropRoot(struct minijail* jail, uid_t uid, gid_t gid);
+
+ // minijail_change_user/minijail_change_group
+ virtual bool DropRoot(struct minijail* jail,
+ const char* user,
+ const char* group);
+
+ // minijail_namespace_pids
+ virtual void EnterNewPidNamespace(struct minijail* jail);
+
+ // minijail_mount_tmp
+ virtual void MountTmp(struct minijail* jail);
+
+ // minijail_use_seccomp_filter/minijail_no_new_privs/
+ // minijail_parse_seccomp_filters
+ virtual void UseSeccompFilter(struct minijail* jail, const char* path);
+
+ // minijail_use_caps
+ virtual void UseCapabilities(struct minijail* jail, uint64_t capmask);
+
+ // minijail_reset_signal_mask
+ virtual void ResetSignalMask(struct minijail* jail);
+
+ // minijail_enter
+ virtual void Enter(struct minijail* jail);
+
+ // minijail_run_pid
+ virtual bool Run(struct minijail* jail, std::vector<char*> args, pid_t* pid);
+
+ // minijail_run_pid and waitpid
+ virtual bool RunSync(struct minijail* jail,
+ std::vector<char*> args,
+ int* status);
+
+ // minijail_run_pid_pipes, with |pstdout_fd| and |pstderr_fd| set to NULL.
+ virtual bool RunPipe(struct minijail* jail,
+ std::vector<char*> args,
+ pid_t* pid,
+ int* stdin);
+
+ // minijail_run_pid_pipes
+ virtual bool RunPipes(struct minijail* jail,
+ std::vector<char*> args,
+ pid_t* pid,
+ int* stdin,
+ int* stdout,
+ int* stderr);
+
+ // Run() and Destroy()
+ virtual bool RunAndDestroy(struct minijail* jail,
+ std::vector<char*> args,
+ pid_t* pid);
+
+ // RunSync() and Destroy()
+ virtual bool RunSyncAndDestroy(struct minijail* jail,
+ std::vector<char*> args,
+ int* status);
+
+ // RunPipe() and Destroy()
+ virtual bool RunPipeAndDestroy(struct minijail* jail,
+ std::vector<char*> args,
+ pid_t* pid,
+ int* stdin);
+
+ // RunPipes() and Destroy()
+ virtual bool RunPipesAndDestroy(struct minijail* jail,
+ std::vector<char*> args,
+ pid_t* pid,
+ int* stdin,
+ int* stdout,
+ int* stderr);
+
+ protected:
+ Minijail();
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<Minijail>;
+
+ DISALLOW_COPY_AND_ASSIGN(Minijail);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MINIJAIL_MINIJAIL_H_
diff --git a/libbrillo/brillo/minijail/mock_minijail.h b/libbrillo/brillo/minijail/mock_minijail.h
new file mode 100644
index 0000000..a855632
--- /dev/null
+++ b/libbrillo/brillo/minijail/mock_minijail.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_BRILLO_MINIJAIL_MOCK_MINIJAIL_H_
+#define LIBBRILLO_BRILLO_MINIJAIL_MOCK_MINIJAIL_H_
+
+#include <vector>
+
+#include <base/macros.h>
+#include <gmock/gmock.h>
+
+#include "brillo/minijail/minijail.h"
+
+namespace brillo {
+
+class MockMinijail : public brillo::Minijail {
+ public:
+ MockMinijail() {}
+ virtual ~MockMinijail() {}
+
+ MOCK_METHOD0(New, struct minijail*());
+ MOCK_METHOD1(Destroy, void(struct minijail*));
+
+ MOCK_METHOD3(DropRoot,
+ bool(struct minijail* jail,
+ const char* user,
+ const char* group));
+ MOCK_METHOD2(UseSeccompFilter, void(struct minijail* jail, const char* path));
+ MOCK_METHOD2(UseCapabilities, void(struct minijail* jail, uint64_t capmask));
+ MOCK_METHOD1(ResetSignalMask, void(struct minijail* jail));
+ MOCK_METHOD1(Enter, void(struct minijail* jail));
+ MOCK_METHOD3(Run,
+ bool(struct minijail* jail,
+ std::vector<char*> args,
+ pid_t* pid));
+ MOCK_METHOD3(RunSync,
+ bool(struct minijail* jail,
+ std::vector<char*> args,
+ int* status));
+ MOCK_METHOD3(RunAndDestroy,
+ bool(struct minijail* jail,
+ std::vector<char*> args,
+ pid_t* pid));
+ MOCK_METHOD3(RunSyncAndDestroy,
+ bool(struct minijail* jail,
+ std::vector<char*> args,
+ int* status));
+ MOCK_METHOD4(RunPipeAndDestroy,
+ bool(struct minijail* jail,
+ std::vector<char*> args,
+ pid_t* pid,
+ int* stdin));
+ MOCK_METHOD6(RunPipesAndDestroy,
+ bool(struct minijail* jail,
+ std::vector<char*> args,
+ pid_t* pid,
+ int* stdin,
+ int* stdout,
+ int* stderr));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockMinijail);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_MINIJAIL_MOCK_MINIJAIL_H_
diff --git a/libbrillo/brillo/osrelease_reader.cc b/libbrillo/brillo/osrelease_reader.cc
new file mode 100644
index 0000000..6e4bf90
--- /dev/null
+++ b/libbrillo/brillo/osrelease_reader.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 <brillo/osrelease_reader.h>
+
+#include <base/files/file_enumerator.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <brillo/strings/string_utils.h>
+
+namespace brillo {
+
+void OsReleaseReader::Load() {
+ Load(base::FilePath("/"));
+}
+
+bool OsReleaseReader::GetString(const std::string& key,
+ std::string* value) const {
+ CHECK(initialized_) << "OsReleaseReader.Load() must be called first.";
+ return store_.GetString(key, value);
+}
+
+void OsReleaseReader::LoadTestingOnly(const base::FilePath& root_dir) {
+ Load(root_dir);
+}
+
+void OsReleaseReader::Load(const base::FilePath& root_dir) {
+ base::FilePath osrelease = root_dir.Append("etc").Append("os-release");
+ if (!store_.Load(osrelease)) {
+ // /etc/os-release might not be present (cros deploying a new configuration
+ // or no fields set at all). Just print a debug message and continue.
+ DLOG(INFO) << "Could not load fields from " << osrelease.value();
+ }
+
+ base::FilePath osreleased = root_dir.Append("etc").Append("os-release.d");
+ base::FileEnumerator enumerator(
+ osreleased, false, base::FileEnumerator::FILES);
+
+ for (base::FilePath path = enumerator.Next(); !path.empty();
+ path = enumerator.Next()) {
+ std::string content;
+ if (!base::ReadFileToString(path, &content)) {
+ // The only way to fail is if a file exist in /etc/os-release.d but we
+ // cannot read it.
+ PLOG(FATAL) << "Could not read " << path.value();
+ }
+ // There might be a trailing new line. Strip it to keep only the first line
+ // of the file.
+ content = brillo::string_utils::SplitAtFirst(content, "\n", true).first;
+ store_.SetString(path.BaseName().value(), content);
+ }
+ initialized_ = true;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/osrelease_reader.h b/libbrillo/brillo/osrelease_reader.h
new file mode 100644
index 0000000..f29c14d
--- /dev/null
+++ b/libbrillo/brillo/osrelease_reader.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.
+
+// Wrapper around /etc/os-release and /etc/os-release.d.
+// Standard fields can come from both places depending on how we set them. They
+// should always be accessed through this interface.
+
+#ifndef LIBBRILLO_BRILLO_OSRELEASE_READER_H_
+#define LIBBRILLO_BRILLO_OSRELEASE_READER_H_
+
+#include <string>
+
+#include <brillo/brillo_export.h>
+#include <brillo/key_value_store.h>
+#include <gtest/gtest_prod.h>
+
+namespace brillo {
+
+class BRILLO_EXPORT OsReleaseReader final {
+ public:
+ // Create an empty reader
+ OsReleaseReader() = default;
+
+ // Loads the key=value pairs from either /etc/os-release.d/<KEY> or
+ // /etc/os-release.
+ void Load();
+
+ // Same as the private Load method.
+ // This need to be public so that services can use it in testing mode (for
+ // autotest tests for example).
+ // This should not be used in production so suffix it with TestingOnly to
+ // make it obvious.
+ void LoadTestingOnly(const base::FilePath& root_dir);
+
+ // Getter for the given key. Returns whether the key was found on the store.
+ bool GetString(const std::string& key, std::string* value) const;
+
+ private:
+ // The map storing all the key-value pairs.
+ KeyValueStore store_;
+
+ // os-release can be lazily loaded if need be.
+ bool initialized_;
+
+ // Load the data from a given root_dir.
+ BRILLO_PRIVATE void Load(const base::FilePath& root_dir);
+
+ DISALLOW_COPY_AND_ASSIGN(OsReleaseReader);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_OSRELEASE_READER_H_
diff --git a/libbrillo/brillo/osrelease_reader_unittest.cc b/libbrillo/brillo/osrelease_reader_unittest.cc
new file mode 100644
index 0000000..88185a0
--- /dev/null
+++ b/libbrillo/brillo/osrelease_reader_unittest.cc
@@ -0,0 +1,95 @@
+// 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 <brillo/osrelease_reader.h>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+using std::string;
+
+namespace brillo {
+
+class OsReleaseReaderTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ CHECK(temp_dir_.CreateUniqueTempDir());
+ osreleased_ = temp_dir_.path().Append("etc").Append("os-release.d");
+ osrelease_ = temp_dir_.path().Append("etc").Append("os-release");
+ base::CreateDirectory(osreleased_);
+ }
+
+ protected:
+ base::FilePath temp_file_, osrelease_, osreleased_;
+ base::ScopedTempDir temp_dir_;
+ OsReleaseReader store_; // reader under test.
+};
+
+TEST_F(OsReleaseReaderTest, MissingOsReleaseTest) {
+ store_.LoadTestingOnly(temp_dir_.path());
+}
+
+TEST_F(OsReleaseReaderTest, MissingOsReleaseDTest) {
+ base::DeleteFile(osreleased_, true);
+ store_.LoadTestingOnly(temp_dir_.path());
+}
+
+TEST_F(OsReleaseReaderTest, CompleteTest) {
+ string hello = "hello";
+ string ola = "ola";
+ string bob = "bob";
+ string osreleasecontent = "TEST_KEY=bonjour\nNAME=bob\n";
+
+ base::WriteFile(osreleased_.Append("TEST_KEY"), hello.data(), hello.size());
+ base::WriteFile(osreleased_.Append("GREETINGS"), ola.data(), ola.size());
+ base::WriteFile(osrelease_, osreleasecontent.data(), osreleasecontent.size());
+
+ store_.LoadTestingOnly(temp_dir_.path());
+
+ string test_key_value;
+ ASSERT_TRUE(store_.GetString("TEST_KEY", &test_key_value));
+
+ string greetings_value;
+ ASSERT_TRUE(store_.GetString("GREETINGS", &greetings_value));
+
+ string name_value;
+ ASSERT_TRUE(store_.GetString("NAME", &name_value));
+
+ string nonexistent_value;
+ // Getting the string should fail if the key does not exist.
+ ASSERT_FALSE(store_.GetString("DOES_NOT_EXIST", &nonexistent_value));
+
+ // hello in chosen (from os-release.d) instead of bonjour from os-release.
+ ASSERT_EQ(hello, test_key_value);
+
+ // greetings is set to ola.
+ ASSERT_EQ(ola, greetings_value);
+
+ // Name from os-release is set.
+ ASSERT_EQ(bob, name_value);
+}
+
+TEST_F(OsReleaseReaderTest, NoNewLine) {
+ // New lines should be stripped from os-release.d files.
+ string hello = "hello\n";
+ string bonjour = "bonjour\ngarbage";
+
+ base::WriteFile(osreleased_.Append("HELLO"), hello.data(), hello.size());
+ base::WriteFile(
+ osreleased_.Append("BONJOUR"), bonjour.data(), bonjour.size());
+
+ store_.LoadTestingOnly(temp_dir_.path());
+
+ string hello_value;
+ string bonjour_value;
+
+ ASSERT_TRUE(store_.GetString("HELLO", &hello_value));
+ ASSERT_TRUE(store_.GetString("BONJOUR", &bonjour_value));
+
+ ASSERT_EQ("hello", hello_value);
+ ASSERT_EQ("bonjour", bonjour_value);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/pointer_utils.h b/libbrillo/brillo/pointer_utils.h
new file mode 100644
index 0000000..f688fa8
--- /dev/null
+++ b/libbrillo/brillo/pointer_utils.h
@@ -0,0 +1,24 @@
+// 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 LIBBRILLO_BRILLO_POINTER_UTILS_H_
+#define LIBBRILLO_BRILLO_POINTER_UTILS_H_
+
+#include <cstdint>
+#include <sys/types.h>
+
+namespace brillo {
+
+// AdvancePointer() is a helper function to advance void pointer by
+// |byte_offset| bytes. Both const and non-const overloads are provided.
+inline void* AdvancePointer(void* pointer, ssize_t byte_offset) {
+ return reinterpret_cast<uint8_t*>(pointer) + byte_offset;
+}
+inline const void* AdvancePointer(const void* pointer, ssize_t byte_offset) {
+ return reinterpret_cast<const uint8_t*>(pointer) + byte_offset;
+}
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_POINTER_UTILS_H_
diff --git a/libbrillo/brillo/process.cc b/libbrillo/brillo/process.cc
new file mode 100644
index 0000000..7af8fbb
--- /dev/null
+++ b/libbrillo/brillo/process.cc
@@ -0,0 +1,425 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "brillo/process.h"
+
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <map>
+#include <memory>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/memory/ptr_util.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/process/process_metrics.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/time/time.h>
+
+#ifndef __linux__
+#define setresuid(_u1, _u2, _u3) setreuid(_u1, _u2)
+#define setresgid(_g1, _g2, _g3) setregid(_g1, _g2)
+#endif // !__linux__
+
+namespace brillo {
+
+bool ReturnTrue() {
+ return true;
+}
+
+Process::Process() {
+}
+
+Process::~Process() {
+}
+
+bool Process::ProcessExists(pid_t pid) {
+ return base::DirectoryExists(
+ base::FilePath(base::StringPrintf("/proc/%d", pid)));
+}
+
+ProcessImpl::ProcessImpl()
+ : pid_(0),
+ uid_(-1),
+ gid_(-1),
+ pre_exec_(base::Bind(&ReturnTrue)),
+ search_path_(false),
+ inherit_parent_signal_mask_(false),
+ close_unused_file_descriptors_(false) {
+}
+
+ProcessImpl::~ProcessImpl() {
+ Reset(0);
+}
+
+void ProcessImpl::AddArg(const std::string& arg) {
+ arguments_.push_back(arg);
+}
+
+void ProcessImpl::RedirectOutput(const std::string& output_file) {
+ output_file_ = output_file;
+}
+
+void ProcessImpl::RedirectUsingPipe(int child_fd, bool is_input) {
+ PipeInfo info;
+ info.is_input_ = is_input;
+ info.is_bound_ = false;
+ pipe_map_[child_fd] = info;
+}
+
+void ProcessImpl::BindFd(int parent_fd, int child_fd) {
+ PipeInfo info;
+ info.is_bound_ = true;
+
+ // info.child_fd_ is the 'child half' of the pipe, which gets dup2()ed into
+ // place over child_fd. Since we already have the child we want to dup2() into
+ // place, we can set info.child_fd_ to parent_fd and leave info.parent_fd_
+ // invalid.
+ info.child_fd_ = parent_fd;
+ info.parent_fd_ = -1;
+ pipe_map_[child_fd] = info;
+}
+
+void ProcessImpl::SetCloseUnusedFileDescriptors(bool close_unused_fds) {
+ close_unused_file_descriptors_ = close_unused_fds;
+}
+
+void ProcessImpl::SetUid(uid_t uid) {
+ uid_ = uid;
+}
+
+void ProcessImpl::SetGid(gid_t gid) {
+ gid_ = gid;
+}
+
+void ProcessImpl::SetCapabilities(uint64_t /*capmask*/) {
+ // No-op, since ProcessImpl does not support sandboxing.
+ return;
+}
+
+void ProcessImpl::ApplySyscallFilter(const std::string& /*path*/) {
+ // No-op, since ProcessImpl does not support sandboxing.
+ return;
+}
+
+void ProcessImpl::EnterNewPidNamespace() {
+ // No-op, since ProcessImpl does not support sandboxing.
+ return;
+}
+
+void ProcessImpl::SetInheritParentSignalMask(bool inherit) {
+ inherit_parent_signal_mask_ = inherit;
+}
+
+void ProcessImpl::SetPreExecCallback(const PreExecCallback& cb) {
+ pre_exec_ = cb;
+}
+
+void ProcessImpl::SetSearchPath(bool search_path) {
+ search_path_ = search_path;
+}
+
+int ProcessImpl::GetPipe(int child_fd) {
+ PipeMap::iterator i = pipe_map_.find(child_fd);
+ if (i == pipe_map_.end())
+ return -1;
+ else
+ return i->second.parent_fd_;
+}
+
+bool ProcessImpl::PopulatePipeMap() {
+ // Verify all target fds are already open. With this assumption we
+ // can be sure that the pipe fds created below do not overlap with
+ // any of the target fds which simplifies how we dup2 to them. Note
+ // that multi-threaded code could close i->first between this loop
+ // and the next.
+ for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) {
+ struct stat stat_buffer;
+ if (fstat(i->first, &stat_buffer) < 0) {
+ int saved_errno = errno;
+ LOG(ERROR) << "Unable to fstat fd " << i->first << ": " << saved_errno;
+ return false;
+ }
+ }
+
+ for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) {
+ if (i->second.is_bound_) {
+ // already have a parent fd, and the child fd gets dup()ed later.
+ continue;
+ }
+ int pipefds[2];
+ if (pipe(pipefds) < 0) {
+ int saved_errno = errno;
+ LOG(ERROR) << "pipe call failed with: " << saved_errno;
+ return false;
+ }
+ if (i->second.is_input_) {
+ // pipe is an input from the prospective of the child.
+ i->second.parent_fd_ = pipefds[1];
+ i->second.child_fd_ = pipefds[0];
+ } else {
+ i->second.parent_fd_ = pipefds[0];
+ i->second.child_fd_ = pipefds[1];
+ }
+ }
+ return true;
+}
+
+bool ProcessImpl::IsFileDescriptorInPipeMap(int fd) const {
+ for (const auto& pipe : pipe_map_) {
+ if (fd == pipe.second.parent_fd_ ||
+ fd == pipe.second.child_fd_ ||
+ fd == pipe.first) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ProcessImpl::CloseUnusedFileDescriptors() {
+ size_t max_fds = base::GetMaxFds();
+ for (size_t i = 0; i < max_fds; i++) {
+ const int fd = static_cast<int>(i);
+
+ // Ignore STD file descriptors.
+ if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) {
+ continue;
+ }
+
+ // Ignore file descriptors used by the PipeMap, they will be handled
+ // by this process later on.
+ if (IsFileDescriptorInPipeMap(fd)) {
+ continue;
+ }
+
+ // Since we're just trying to close anything we can find,
+ // ignore any error return values of close().
+ IGNORE_EINTR(close(fd));
+ }
+}
+
+bool ProcessImpl::Start() {
+ // If no arguments are provided, fail.
+ if (arguments_.empty()) {
+ return false;
+ }
+ std::unique_ptr<char* []> argv =
+ base::MakeUnique<char* []>(arguments_.size() + 1);
+
+ for (size_t i = 0; i < arguments_.size(); ++i)
+ argv[i] = const_cast<char*>(arguments_[i].c_str());
+
+ argv[arguments_.size()] = nullptr;
+
+ if (!PopulatePipeMap()) {
+ LOG(ERROR) << "Failing to start because pipe creation failed";
+ return false;
+ }
+
+ pid_t pid = fork();
+ int saved_errno = errno;
+ if (pid < 0) {
+ LOG(ERROR) << "Fork failed: " << saved_errno;
+ Reset(0);
+ return false;
+ }
+
+ if (pid == 0) {
+ // Executing inside the child process.
+ // Close unused file descriptors.
+ if (close_unused_file_descriptors_) {
+ CloseUnusedFileDescriptors();
+ }
+ // Close parent's side of the child pipes. dup2 ours into place and
+ // then close our ends.
+ for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) {
+ if (i->second.parent_fd_ != -1)
+ IGNORE_EINTR(close(i->second.parent_fd_));
+ // If we want to bind a fd to the same fd in the child, we don't need to
+ // close and dup2 it.
+ if (i->second.child_fd_ == i->first)
+ continue;
+ HANDLE_EINTR(dup2(i->second.child_fd_, i->first));
+ }
+ // Defer the actual close() of the child fd until afterward; this lets the
+ // same child fd be bound to multiple fds using BindFd. Don't close the fd
+ // if it was bound to itself.
+ for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i) {
+ if (i->second.child_fd_ == i->first)
+ continue;
+ IGNORE_EINTR(close(i->second.child_fd_));
+ }
+ if (!output_file_.empty()) {
+ int output_handle = HANDLE_EINTR(open(
+ output_file_.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW,
+ 0666));
+ if (output_handle < 0) {
+ PLOG(ERROR) << "Could not create " << output_file_;
+ // Avoid exit() to avoid atexit handlers from parent.
+ _exit(kErrorExitStatus);
+ }
+ HANDLE_EINTR(dup2(output_handle, STDOUT_FILENO));
+ HANDLE_EINTR(dup2(output_handle, STDERR_FILENO));
+ // Only close output_handle if it does not happen to be one of
+ // the two standard file descriptors we are trying to redirect.
+ if (output_handle != STDOUT_FILENO && output_handle != STDERR_FILENO) {
+ IGNORE_EINTR(close(output_handle));
+ }
+ }
+ if (gid_ != static_cast<gid_t>(-1) && setresgid(gid_, gid_, gid_) < 0) {
+ int saved_errno = errno;
+ LOG(ERROR) << "Unable to set GID to " << gid_ << ": " << saved_errno;
+ _exit(kErrorExitStatus);
+ }
+ if (uid_ != static_cast<uid_t>(-1) && setresuid(uid_, uid_, uid_) < 0) {
+ int saved_errno = errno;
+ LOG(ERROR) << "Unable to set UID to " << uid_ << ": " << saved_errno;
+ _exit(kErrorExitStatus);
+ }
+ if (!pre_exec_.Run()) {
+ LOG(ERROR) << "Pre-exec callback failed";
+ _exit(kErrorExitStatus);
+ }
+ // Reset signal mask for the child process if not inheriting signal mask
+ // from the parent process.
+ if (!inherit_parent_signal_mask_) {
+ sigset_t signal_mask;
+ CHECK_EQ(0, sigemptyset(&signal_mask));
+ CHECK_EQ(0, sigprocmask(SIG_SETMASK, &signal_mask, nullptr));
+ }
+ if (search_path_) {
+ execvp(argv[0], &argv[0]);
+ } else {
+ execv(argv[0], &argv[0]);
+ }
+ PLOG(ERROR) << "Exec of " << argv[0] << " failed:";
+ _exit(kErrorExitStatus);
+ } else {
+ // Still executing inside the parent process with known child pid.
+ arguments_.clear();
+ UpdatePid(pid);
+ // Close our copy of child side pipes only if we created those pipes.
+ for (const auto& i : pipe_map_) {
+ if (!i.second.is_bound_) {
+ IGNORE_EINTR(close(i.second.child_fd_));
+ }
+ }
+ }
+ return true;
+}
+
+int ProcessImpl::Wait() {
+ int status = 0;
+ if (pid_ == 0) {
+ LOG(ERROR) << "Process not running";
+ return -1;
+ }
+ if (HANDLE_EINTR(waitpid(pid_, &status, 0)) < 0) {
+ int saved_errno = errno;
+ LOG(ERROR) << "Problem waiting for pid " << pid_ << ": " << saved_errno;
+ return -1;
+ }
+ pid_t old_pid = pid_;
+ // Update the pid to 0 - do not Reset as we do not want to try to
+ // kill the process that has just exited.
+ UpdatePid(0);
+ if (!WIFEXITED(status)) {
+ DCHECK(WIFSIGNALED(status)) << old_pid
+ << " neither exited, nor died on a signal?";
+ LOG(ERROR) << "Process " << old_pid
+ << " did not exit normally: " << WTERMSIG(status);
+ return -1;
+ }
+ return WEXITSTATUS(status);
+}
+
+int ProcessImpl::Run() {
+ if (!Start()) {
+ return -1;
+ }
+ return Wait();
+}
+
+pid_t ProcessImpl::pid() {
+ return pid_;
+}
+
+bool ProcessImpl::Kill(int signal, int timeout) {
+ if (pid_ == 0) {
+ // Passing pid == 0 to kill is committing suicide. Check specifically.
+ LOG(ERROR) << "Process not running";
+ return false;
+ }
+ if (kill(pid_, signal) < 0) {
+ PLOG(ERROR) << "Unable to send signal to " << pid_;
+ return false;
+ }
+ base::TimeTicks start_signal = base::TimeTicks::Now();
+ do {
+ int status = 0;
+ pid_t w = waitpid(pid_, &status, WNOHANG);
+ if (w < 0) {
+ if (errno == ECHILD)
+ return true;
+ PLOG(ERROR) << "Waitpid returned " << w;
+ return false;
+ }
+ if (w > 0) {
+ Reset(0);
+ return true;
+ }
+ usleep(100);
+ } while ((base::TimeTicks::Now() - start_signal).InSecondsF() <= timeout);
+ LOG(INFO) << "process " << pid_ << " did not exit from signal " << signal
+ << " in " << timeout << " seconds";
+ return false;
+}
+
+void ProcessImpl::UpdatePid(pid_t new_pid) {
+ pid_ = new_pid;
+}
+
+void ProcessImpl::Reset(pid_t new_pid) {
+ arguments_.clear();
+ // Close our side of all pipes to this child giving the child to
+ // handle sigpipes and shutdown nicely, though likely it won't
+ // have time.
+ for (PipeMap::iterator i = pipe_map_.begin(); i != pipe_map_.end(); ++i)
+ IGNORE_EINTR(close(i->second.parent_fd_));
+ pipe_map_.clear();
+ if (pid_)
+ Kill(SIGKILL, 0);
+ UpdatePid(new_pid);
+}
+
+bool ProcessImpl::ResetPidByFile(const std::string& pid_file) {
+ std::string contents;
+ if (!base::ReadFileToString(base::FilePath(pid_file), &contents)) {
+ LOG(ERROR) << "Could not read pid file" << pid_file;
+ return false;
+ }
+ base::TrimWhitespaceASCII(contents, base::TRIM_TRAILING, &contents);
+ int64_t pid_int64 = 0;
+ if (!base::StringToInt64(contents, &pid_int64)) {
+ LOG(ERROR) << "Unexpected pid file contents";
+ return false;
+ }
+ Reset(pid_int64);
+ return true;
+}
+
+pid_t ProcessImpl::Release() {
+ pid_t old_pid = pid_;
+ pid_ = 0;
+ return old_pid;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/process.h b/libbrillo/brillo/process.h
new file mode 100644
index 0000000..9760ab3
--- /dev/null
+++ b/libbrillo/brillo/process.h
@@ -0,0 +1,236 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_BRILLO_PROCESS_H_
+#define LIBBRILLO_BRILLO_PROCESS_H_
+
+#include <sys/types.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/brillo_export.h>
+#include <gtest/gtest_prod.h>
+
+namespace brillo {
+// Manages a process. Can create the process, attach to an existing
+// process by pid or pid file, and kill the process. Upon destruction
+// any managed process is killed with SIGKILL. Use Release() to
+// release the process from management. A given system process may
+// only be managed by one Process at a time.
+class BRILLO_EXPORT Process {
+ public:
+ Process();
+ virtual ~Process();
+
+ // Adds |arg| to the executable command-line to be run. The
+ // executable name itself is the first argument.
+ virtual void AddArg(const std::string& arg) = 0;
+
+ // Adds |option| and |value| as an option with a string value to the
+ // command line to be run.
+ inline void AddStringOption(const std::string& option,
+ const std::string& value) {
+ AddArg(option);
+ AddArg(value);
+ }
+
+ // Adds |option| and |value| as an option which takes an integer
+ // value to the command line to be run.
+ inline void AddIntOption(const std::string& option, int value) {
+ AddArg(option);
+ AddArg(base::StringPrintf("%d", value));
+ }
+
+ // Redirects stderr and stdout to |output_file|.
+ virtual void RedirectOutput(const std::string& output_file) = 0;
+
+ // Indicates we want to redirect |child_fd| in the child process's
+ // file table to a pipe. |child_fd| will be available for reading
+ // from child process's perspective iff |is_input|.
+ virtual void RedirectUsingPipe(int child_fd, bool is_input) = 0;
+
+ // Binds the given file descriptor in the parent to the given file
+ // descriptor in the child.
+ virtual void BindFd(int parent_fd, int child_fd) = 0;
+
+ // Set a flag |close_unused_fds| to indicate if the child process
+ // should close all unused file descriptors inherited from the
+ // parent process. This will not close the file descriptors for
+ // the standard streams (stdin, stdout, and stderr).
+ virtual void SetCloseUnusedFileDescriptors(bool close_unused_fds) = 0;
+
+ // Set the real/effective/saved user ID of the child process.
+ virtual void SetUid(uid_t uid) = 0;
+
+ // Set the real/effective/saved group ID of the child process.
+ virtual void SetGid(gid_t gid) = 0;
+
+ // Set the capabilities assigned to the child process.
+ // NOTE: |capmask| is indeed a mask and should be passed in as the result of
+ // the CAP_TO_MASK(capability) macro, e.g.
+ // my_process.SetCapabilities(CAP_TO_MASK(CAP_SETUID) |
+ // CAP_TO_MASK(CAP_SETGID));
+ // NOTE: supporting this sandboxing feature is optional (provide no-op
+ // implementation if your Process implementation does not support this).
+ virtual void SetCapabilities(uint64_t capmask) = 0;
+
+ // Apply a syscall filter to the process using the policy file at |path|.
+ // NOTE: supporting this sandboxing feature is optional (provide no-op
+ // implementation if your Process implementation does not support this).
+ virtual void ApplySyscallFilter(const std::string& path) = 0;
+
+ // Enter new PID namespace when this process is run.
+ // NOTE: supporting this sandboxing feature is optional (provide no-op
+ // implementation if your Process implementation does not support this).
+ virtual void EnterNewPidNamespace() = 0;
+
+ // Set a flag |inherit| to indicate if the child process intend to
+ // inherit signal mask from the parent process. When |inherit| is
+ // set to true, the child process will inherit signal mask from the
+ // parent process. This could cause unintended side effect, where all
+ // the signals to the child process might be blocked if they are set
+ // in the parent's signal mask.
+ virtual void SetInheritParentSignalMask(bool inherit) = 0;
+
+ typedef base::Callback<bool(void)> PreExecCallback;
+
+ // Set the pre-exec callback. This is called after all setup is complete but
+ // before we exec() the process. The callback may return false to cause Start
+ // to return false without starting the process.
+ virtual void SetPreExecCallback(const PreExecCallback& cb) = 0;
+
+ // Sets whether starting the process should search the system path or not.
+ // By default the system path will not be searched.
+ virtual void SetSearchPath(bool search_path) = 0;
+
+ // Gets the pipe file descriptor mapped to the process's |child_fd|.
+ virtual int GetPipe(int child_fd) = 0;
+
+ // Starts this process, returning true if successful.
+ virtual bool Start() = 0;
+
+ // Waits for this process to finish. Returns the process's exit
+ // status if it exited normally, or otherwise returns -1. Note
+ // that kErrorExitStatus may be returned if an error occurred
+ // after forking and before execing the child process.
+ virtual int Wait() = 0;
+
+ // Start and wait for this process to finish. Returns same value as
+ // Wait().
+ virtual int Run() = 0;
+
+ // Returns the pid of this process or else returns 0 if there is no
+ // corresponding process (either because it has not yet been started
+ // or has since exited).
+ virtual pid_t pid() = 0;
+
+ // Sends |signal| to process and wait |timeout| seconds until it
+ // dies. If process is not a child, returns immediately with a
+ // value based on whether kill was successful. If the process is a
+ // child and |timeout| is non-zero, returns true if the process is
+ // able to be reaped within the given |timeout| in seconds.
+ virtual bool Kill(int signal, int timeout) = 0;
+
+ // Resets this Process object to refer to the process with |pid|.
+ // If |pid| is zero, this object no longer refers to a process.
+ virtual void Reset(pid_t new_pid) = 0;
+
+ // Same as Reset but reads the pid from |pid_file|. Returns false
+ // only when the file cannot be read/parsed.
+ virtual bool ResetPidByFile(const std::string& pid_file) = 0;
+
+ // Releases the process so that on destruction, the process is not killed.
+ virtual pid_t Release() = 0;
+
+ // Returns if |pid| is a currently running process.
+ static bool ProcessExists(pid_t pid);
+
+ // When returned from Wait or Run, indicates an error may have occurred
+ // creating the process.
+ enum { kErrorExitStatus = 127 };
+};
+
+class BRILLO_EXPORT ProcessImpl : public Process {
+ public:
+ ProcessImpl();
+ virtual ~ProcessImpl();
+
+ virtual void AddArg(const std::string& arg);
+ virtual void RedirectOutput(const std::string& output_file);
+ virtual void RedirectUsingPipe(int child_fd, bool is_input);
+ virtual void BindFd(int parent_fd, int child_fd);
+ virtual void SetCloseUnusedFileDescriptors(bool close_unused_fds);
+ virtual void SetUid(uid_t uid);
+ virtual void SetGid(gid_t gid);
+ virtual void SetCapabilities(uint64_t capmask);
+ virtual void ApplySyscallFilter(const std::string& path);
+ virtual void EnterNewPidNamespace();
+ virtual void SetInheritParentSignalMask(bool inherit);
+ virtual void SetPreExecCallback(const PreExecCallback& cb);
+ virtual void SetSearchPath(bool search_path);
+ virtual int GetPipe(int child_fd);
+ virtual bool Start();
+ virtual int Wait();
+ virtual int Run();
+ virtual pid_t pid();
+ virtual bool Kill(int signal, int timeout);
+ virtual void Reset(pid_t pid);
+ virtual bool ResetPidByFile(const std::string& pid_file);
+ virtual pid_t Release();
+
+ protected:
+ struct PipeInfo {
+ PipeInfo() : parent_fd_(-1), child_fd_(-1), is_input_(false) {}
+ // Parent (our) side of the pipe to the child process.
+ int parent_fd_;
+ // Child's side of the pipe to the parent.
+ int child_fd_;
+ // Is this an input or output pipe from child's perspective.
+ bool is_input_;
+ // Is this a bound (pre-existing) file descriptor?
+ bool is_bound_;
+ };
+ typedef std::map<int, PipeInfo> PipeMap;
+
+ void UpdatePid(pid_t new_pid);
+ bool PopulatePipeMap();
+
+ private:
+ FRIEND_TEST(ProcessTest, ResetPidByFile);
+
+ bool IsFileDescriptorInPipeMap(int fd) const;
+ void CloseUnusedFileDescriptors();
+
+ // Pid of currently managed process or 0 if no currently managed
+ // process. pid must not be modified except by calling
+ // UpdatePid(new_pid).
+ pid_t pid_;
+ std::string output_file_;
+ std::vector<std::string> arguments_;
+ // Map of child target file descriptors (first) to information about
+ // pipes created (second).
+ PipeMap pipe_map_;
+ uid_t uid_;
+ gid_t gid_;
+ PreExecCallback pre_exec_;
+ bool search_path_;
+ // Flag indicating to inherit signal mask from the parent process. It
+ // is set to false by default, which means by default the child process
+ // will not inherit signal mask from the parent process.
+ bool inherit_parent_signal_mask_;
+ // Flag indicating to close unused file descriptors inherited from the
+ // parent process when starting the child process, which avoids leaking
+ // unnecessary file descriptors to the child process.
+ bool close_unused_file_descriptors_;
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_PROCESS_H_
diff --git a/libbrillo/brillo/process_information.cc b/libbrillo/brillo/process_information.cc
new file mode 100644
index 0000000..6b03c40
--- /dev/null
+++ b/libbrillo/brillo/process_information.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "brillo/process_information.h"
+
+namespace brillo {
+
+ProcessInformation::ProcessInformation() : cmd_line_(), process_id_(-1) {
+}
+ProcessInformation::~ProcessInformation() {
+}
+
+std::string ProcessInformation::GetCommandLine() {
+ std::string result;
+ for (std::vector<std::string>::iterator cmd_itr = cmd_line_.begin();
+ cmd_itr != cmd_line_.end();
+ cmd_itr++) {
+ if (result.length()) {
+ result.append(" ");
+ }
+ result.append((*cmd_itr));
+ }
+ return result;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/process_information.h b/libbrillo/brillo/process_information.h
new file mode 100644
index 0000000..3f0a2c9
--- /dev/null
+++ b/libbrillo/brillo/process_information.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_BRILLO_PROCESS_INFORMATION_H_
+#define LIBBRILLO_BRILLO_PROCESS_INFORMATION_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+// Information for a single running process. Stores its command line, set of
+// open files, process id and working directory.
+class BRILLO_EXPORT ProcessInformation {
+ public:
+ ProcessInformation();
+ virtual ~ProcessInformation();
+
+ std::string GetCommandLine();
+
+ // Set the command line array. This method DOES swap out the contents of
+ // |value|. The caller should expect an empty vector on return.
+ void set_cmd_line(std::vector<std::string>* value) {
+ cmd_line_.clear();
+ cmd_line_.swap(*value);
+ }
+
+ const std::vector<std::string>& get_cmd_line() { return cmd_line_; }
+
+ // Set the command line array. This method DOES swap out the contents of
+ // |value|. The caller should expect an empty set on return.
+ void set_open_files(std::set<std::string>* value) {
+ open_files_.clear();
+ open_files_.swap(*value);
+ }
+
+ const std::set<std::string>& get_open_files() { return open_files_; }
+
+ // Set the command line array. This method DOES swap out the contents of
+ // |value|. The caller should expect an empty string on return.
+ void set_cwd(std::string* value) {
+ cwd_.clear();
+ cwd_.swap(*value);
+ }
+
+ const std::string& get_cwd() { return cwd_; }
+
+ void set_process_id(int value) { process_id_ = value; }
+
+ int get_process_id() { return process_id_; }
+
+ private:
+ std::vector<std::string> cmd_line_;
+ std::set<std::string> open_files_;
+ std::string cwd_;
+ int process_id_;
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_PROCESS_INFORMATION_H_
diff --git a/libbrillo/brillo/process_mock.h b/libbrillo/brillo/process_mock.h
new file mode 100644
index 0000000..f73d242
--- /dev/null
+++ b/libbrillo/brillo/process_mock.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_BRILLO_PROCESS_MOCK_H_
+#define LIBBRILLO_BRILLO_PROCESS_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "brillo/process.h"
+
+namespace brillo {
+
+class ProcessMock : public Process {
+ public:
+ ProcessMock() {}
+ virtual ~ProcessMock() {}
+
+ MOCK_METHOD1(AddArg, void(const std::string& arg));
+ MOCK_METHOD1(RedirectOutput, void(const std::string& output_file));
+ MOCK_METHOD2(RedirectUsingPipe, void(int child_fd, bool is_input));
+ MOCK_METHOD2(BindFd, void(int parent_fd, int child_fd));
+ MOCK_METHOD1(SetUid, void(uid_t));
+ MOCK_METHOD1(SetGid, void(gid_t));
+ MOCK_METHOD1(SetCapabilities, void(uint64_t capmask));
+ MOCK_METHOD1(ApplySyscallFilter, void(const std::string& path));
+ MOCK_METHOD0(EnterNewPidNamespace, void());
+ MOCK_METHOD1(SetInheritParentSignalMask, void(bool));
+ MOCK_METHOD1(SetPreExecCallback, void(const PreExecCallback&));
+ MOCK_METHOD1(SetSearchPath, void(bool));
+ MOCK_METHOD1(GetPipe, int(int child_fd));
+ MOCK_METHOD0(Start, bool());
+ MOCK_METHOD0(Wait, int());
+ MOCK_METHOD0(Run, int());
+ MOCK_METHOD0(pid, pid_t());
+ MOCK_METHOD2(Kill, bool(int signal, int timeout));
+ MOCK_METHOD1(Reset, void(pid_t));
+ MOCK_METHOD1(ResetPidByFile, bool(const std::string& pid_file));
+ MOCK_METHOD0(Release, pid_t());
+ MOCK_METHOD1(SetCloseUnusedFileDescriptors, void(bool close_unused_fds));
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_PROCESS_MOCK_H_
diff --git a/libbrillo/brillo/process_reaper.cc b/libbrillo/brillo/process_reaper.cc
new file mode 100644
index 0000000..c4cb1cf
--- /dev/null
+++ b/libbrillo/brillo/process_reaper.cc
@@ -0,0 +1,91 @@
+// Copyright 2015 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 "brillo/process_reaper.h"
+
+#include <sys/signalfd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <base/bind.h>
+#include <base/posix/eintr_wrapper.h>
+#include <brillo/asynchronous_signal_handler.h>
+#include <brillo/daemons/daemon.h>
+#include <brillo/location_logging.h>
+
+namespace brillo {
+
+ProcessReaper::~ProcessReaper() {
+ Unregister();
+}
+
+void ProcessReaper::Register(
+ AsynchronousSignalHandlerInterface* async_signal_handler) {
+ CHECK(!async_signal_handler_);
+ async_signal_handler_ = async_signal_handler;
+ async_signal_handler->RegisterHandler(
+ SIGCHLD,
+ base::Bind(&ProcessReaper::HandleSIGCHLD, base::Unretained(this)));
+}
+
+void ProcessReaper::Unregister() {
+ if (!async_signal_handler_)
+ return;
+ async_signal_handler_->UnregisterHandler(SIGCHLD);
+ async_signal_handler_ = nullptr;
+}
+
+bool ProcessReaper::WatchForChild(const tracked_objects::Location& from_here,
+ pid_t pid,
+ const ChildCallback& callback) {
+ if (watched_processes_.find(pid) != watched_processes_.end())
+ return false;
+ watched_processes_.emplace(pid, WatchedProcess{from_here, callback});
+ return true;
+}
+
+bool ProcessReaper::ForgetChild(pid_t pid) {
+ return watched_processes_.erase(pid) != 0;
+}
+
+bool ProcessReaper::HandleSIGCHLD(
+ const struct signalfd_siginfo& /* sigfd_info */) {
+ // One SIGCHLD may correspond to multiple terminated children, so ignore
+ // sigfd_info and reap any available children.
+ while (true) {
+ siginfo_t info;
+ info.si_pid = 0;
+ int rc = HANDLE_EINTR(waitid(P_ALL, 0, &info, WNOHANG | WEXITED));
+
+ if (rc == -1) {
+ if (errno != ECHILD) {
+ PLOG(ERROR) << "waitid failed";
+ }
+ break;
+ }
+
+ if (info.si_pid == 0) {
+ break;
+ }
+
+ auto proc = watched_processes_.find(info.si_pid);
+ if (proc == watched_processes_.end()) {
+ LOG(INFO) << "Untracked process " << info.si_pid
+ << " terminated with status " << info.si_status
+ << " (code = " << info.si_code << ")";
+ } else {
+ DVLOG_LOC(proc->second.location, 1)
+ << "Process " << info.si_pid << " terminated with status "
+ << info.si_status << " (code = " << info.si_code << ")";
+ ChildCallback callback = std::move(proc->second.callback);
+ watched_processes_.erase(proc);
+ callback.Run(info);
+ }
+ }
+
+ // Return false to indicate that our handler should not be uninstalled.
+ return false;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/process_reaper.h b/libbrillo/brillo/process_reaper.h
new file mode 100644
index 0000000..937c88c
--- /dev/null
+++ b/libbrillo/brillo/process_reaper.h
@@ -0,0 +1,74 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_PROCESS_REAPER_H_
+#define LIBBRILLO_BRILLO_PROCESS_REAPER_H_
+
+#include <sys/wait.h>
+
+#include <map>
+
+#include <base/callback.h>
+#include <base/location.h>
+#include <base/macros.h>
+#include <brillo/asynchronous_signal_handler.h>
+#include <brillo/daemons/daemon.h>
+
+namespace brillo {
+
+class BRILLO_EXPORT ProcessReaper final {
+ public:
+ // The callback called when a child exits.
+ using ChildCallback = base::Callback<void(const siginfo_t&)>;
+
+ ProcessReaper() = default;
+ ~ProcessReaper();
+
+ // Register the ProcessReaper using either the provided
+ // brillo::AsynchronousSignalHandlerInterface. You can call Unregister() to
+ // remove this ProcessReapper or it will be called during shutdown.
+ // You can only register this ProcessReaper with one signal handler at a time.
+ void Register(AsynchronousSignalHandlerInterface* async_signal_handler);
+
+ // Unregisters the ProcessReaper from the
+ // brillo::AsynchronousSignalHandlerInterface passed in Register(). It
+ // doesn't do anything if not registered.
+ void Unregister();
+
+ // Watch for the child process |pid| to finish and call |callback| when the
+ // selected process exits or the process terminates for other reason. The
+ // |callback| receives the exit status and exit code of the terminated process
+ // as a siginfo_t. See wait(2) for details about siginfo_t.
+ bool WatchForChild(const tracked_objects::Location& from_here,
+ pid_t pid,
+ const ChildCallback& callback);
+
+ // Stop watching child process |pid|. This is useful in situations
+ // where the child process may have been reaped outside of the signal
+ // handler, or the caller is no longer interested in being notified about
+ // this child process anymore. Returns true if a child was removed from
+ // the watchlist.
+ bool ForgetChild(pid_t pid);
+
+ private:
+ // SIGCHLD handler for the AsynchronousSignalHandler. Always returns false
+ // (meaning that the signal handler should not be unregistered).
+ bool HandleSIGCHLD(const signalfd_siginfo& sigfd_info);
+
+ struct WatchedProcess {
+ tracked_objects::Location location;
+ ChildCallback callback;
+ };
+ std::map<pid_t, WatchedProcess> watched_processes_;
+
+ // The |async_signal_handler_| is owned by the caller and is |nullptr| when
+ // not registered.
+ AsynchronousSignalHandlerInterface* async_signal_handler_{nullptr};
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessReaper);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_PROCESS_REAPER_H_
diff --git a/libbrillo/brillo/process_reaper_unittest.cc b/libbrillo/brillo/process_reaper_unittest.cc
new file mode 100644
index 0000000..0ca6e34
--- /dev/null
+++ b/libbrillo/brillo/process_reaper_unittest.cc
@@ -0,0 +1,150 @@
+// Copyright 2015 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 <brillo/process_reaper.h>
+
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/message_loop/message_loop.h>
+#include <brillo/asynchronous_signal_handler.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <gtest/gtest.h>
+
+namespace {
+
+pid_t ForkChildAndExit(int exit_code) {
+ pid_t pid = fork();
+ PCHECK(pid != -1);
+ if (pid == 0) {
+ _exit(exit_code);
+ }
+ return pid;
+}
+
+pid_t ForkChildAndKill(int sig) {
+ pid_t pid = fork();
+ PCHECK(pid != -1);
+ if (pid == 0) {
+ if (raise(sig) != 0) {
+ PLOG(ERROR) << "raise(" << sig << ")";
+ }
+ _exit(0); // Not reached. This value will cause the test to fail.
+ }
+ return pid;
+}
+
+} // namespace
+
+namespace brillo {
+
+class ProcessReaperTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ brillo_loop_.SetAsCurrent();
+ async_signal_handler_.Init();
+ process_reaper_.Register(&async_signal_handler_);
+ }
+
+ protected:
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop brillo_loop_{&base_loop_};
+ brillo::AsynchronousSignalHandler async_signal_handler_;
+
+ // ProcessReaper under test.
+ ProcessReaper process_reaper_;
+};
+
+TEST_F(ProcessReaperTest, UnregisterWhenNotRegistered) {
+ ProcessReaper another_process_reaper_;
+ another_process_reaper_.Unregister();
+}
+
+TEST_F(ProcessReaperTest, UnregisterAndReregister) {
+ process_reaper_.Unregister();
+ process_reaper_.Register(&async_signal_handler_);
+ // This checks that we can unregister the ProcessReaper and then destroy it.
+ process_reaper_.Unregister();
+}
+
+TEST_F(ProcessReaperTest, ReapExitedChild) {
+ pid_t pid = ForkChildAndExit(123);
+ EXPECT_TRUE(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind(
+ [](decltype(this) test, const siginfo_t& info) {
+ EXPECT_EQ(CLD_EXITED, info.si_code);
+ EXPECT_EQ(123, info.si_status);
+ test->brillo_loop_.BreakLoop();
+ }, base::Unretained(this))));
+ brillo_loop_.Run();
+}
+
+// Test that simultaneous child processes fire their respective callbacks when
+// exiting.
+TEST_F(ProcessReaperTest, ReapedChildsMatchCallbacks) {
+ int running_childs = 10;
+ for (int i = 0; i < running_childs; ++i) {
+ // Different processes will have different exit values.
+ int exit_value = 1 + i;
+ pid_t pid = ForkChildAndExit(exit_value);
+ EXPECT_TRUE(process_reaper_.WatchForChild(
+ FROM_HERE,
+ pid,
+ base::Bind(
+ [](decltype(this) test,
+ int exit_value,
+ int* running_childs,
+ const siginfo_t& info) {
+ EXPECT_EQ(CLD_EXITED, info.si_code);
+ EXPECT_EQ(exit_value, info.si_status);
+ (*running_childs)--;
+ if (*running_childs == 0)
+ test->brillo_loop_.BreakLoop();
+ },
+ base::Unretained(this),
+ exit_value,
+ base::Unretained(&running_childs))));
+ }
+ // This sleep is optional. It helps to have more processes exit before we
+ // start watching for them in the message loop.
+ usleep(10 * 1000);
+ brillo_loop_.Run();
+ EXPECT_EQ(0, running_childs);
+}
+
+TEST_F(ProcessReaperTest, ReapKilledChild) {
+ pid_t pid = ForkChildAndKill(SIGKILL);
+ EXPECT_TRUE(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind(
+ [](decltype(this) test, const siginfo_t& info) {
+ EXPECT_EQ(CLD_KILLED, info.si_code);
+ EXPECT_EQ(SIGKILL, info.si_status);
+ test->brillo_loop_.BreakLoop();
+ }, base::Unretained(this))));
+ brillo_loop_.Run();
+}
+
+TEST_F(ProcessReaperTest, ReapKilledAndForgottenChild) {
+ pid_t pid = ForkChildAndExit(0);
+ EXPECT_TRUE(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind(
+ [](decltype(this) test, const siginfo_t& /* info */) {
+ ADD_FAILURE() << "Child process was still tracked.";
+ test->brillo_loop_.BreakLoop();
+ }, base::Unretained(this))));
+ EXPECT_TRUE(process_reaper_.ForgetChild(pid));
+
+ // A second call should return failure.
+ EXPECT_FALSE(process_reaper_.ForgetChild(pid));
+
+ // Run the loop with a timeout, as the BreakLoop() above is not expected.
+ brillo_loop_.PostDelayedTask(FROM_HERE,
+ base::Bind(&MessageLoop::BreakLoop,
+ base::Unretained(&brillo_loop_)),
+ base::TimeDelta::FromMilliseconds(100));
+ brillo_loop_.Run();
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/process_unittest.cc b/libbrillo/brillo/process_unittest.cc
new file mode 100644
index 0000000..d2c92e6
--- /dev/null
+++ b/libbrillo/brillo/process_unittest.cc
@@ -0,0 +1,384 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "brillo/process.h"
+
+#include <unistd.h>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "brillo/process_mock.h"
+#include "brillo/unittest_utils.h"
+#include "brillo/test_helpers.h"
+
+using base::FilePath;
+
+// This test assumes the following standard binaries are installed.
+#if defined(__ANDROID__)
+# define SYSTEM_PREFIX "/system"
+static const char kBinStat[] = SYSTEM_PREFIX "/bin/stat";
+#else
+# define SYSTEM_PREFIX ""
+static const char kBinStat[] = "/usr/bin/stat";
+#endif
+
+static const char kBinSh[] = SYSTEM_PREFIX "/bin/sh";
+static const char kBinCat[] = SYSTEM_PREFIX "/bin/cat";
+static const char kBinCp[] = SYSTEM_PREFIX "/bin/cp";
+static const char kBinEcho[] = SYSTEM_PREFIX "/bin/echo";
+static const char kBinFalse[] = SYSTEM_PREFIX "/bin/false";
+static const char kBinSleep[] = SYSTEM_PREFIX "/bin/sleep";
+static const char kBinTrue[] = SYSTEM_PREFIX "/bin/true";
+
+namespace brillo {
+
+// Test that the mock has all the functions of the interface by
+// instantiating it. This variable is not used elsewhere.
+struct CompileMocks {
+ ProcessMock process_mock;
+};
+
+TEST(SimpleProcess, Basic) {
+ // Log must be cleared before running this test, just as ProcessTest::SetUp.
+ ClearLog();
+ ProcessImpl process;
+ process.AddArg(kBinEcho);
+ EXPECT_EQ(0, process.Run());
+ EXPECT_EQ("", GetLog());
+}
+
+TEST(SimpleProcess, NoSearchPath) {
+ ProcessImpl process;
+ process.AddArg("echo");
+ EXPECT_EQ(127, process.Run());
+}
+
+TEST(SimpleProcess, SearchPath) {
+ ProcessImpl process;
+ process.AddArg("echo");
+ process.SetSearchPath(true);
+ EXPECT_EQ(EXIT_SUCCESS, process.Run());
+}
+
+TEST(SimpleProcess, BindFd) {
+ int fds[2];
+ char buf[16];
+ static const char* kMsg = "hello, world!";
+ ProcessImpl process;
+ EXPECT_EQ(0, pipe(fds));
+ process.AddArg(kBinEcho);
+ process.AddArg(kMsg);
+ process.BindFd(fds[1], 1);
+ process.Run();
+ memset(buf, 0, sizeof(buf));
+ EXPECT_EQ(read(fds[0], buf, sizeof(buf) - 1), strlen(kMsg) + 1);
+ EXPECT_EQ(std::string(kMsg) + "\n", std::string(buf));
+}
+
+// The test framework uses the device's dash shell as "sh", which doesn't
+// support redirecting stdout to arbitrary large file descriptor numbers
+// directly, nor has /proc mounted to open /proc/self/fd/NN. This test would
+// fail if pipe.writer is big enough.
+// TODO(deymo): Write a helper program that writes "hello_world" to the passed
+// file descriptor and re-enabled this test.
+TEST(DISABLED_SimpleProcess, BindFdToSameFd) {
+ static const char* kMsg = "hello_world";
+ ScopedPipe pipe;
+ ProcessImpl process;
+ process.AddArg(kBinSh);
+ process.AddArg("-c");
+ process.AddArg(base::StringPrintf("echo %s >&%d", kMsg, pipe.writer));
+ process.BindFd(pipe.writer, pipe.writer);
+ process.Run();
+ close(pipe.writer);
+ pipe.writer = -1;
+
+ char buf[16];
+ memset(buf, 0, sizeof(buf));
+ EXPECT_EQ(read(pipe.reader, buf, sizeof(buf) - 1), strlen(kMsg) + 1);
+ EXPECT_EQ(std::string(kMsg) + "\n", std::string(buf));
+}
+
+class ProcessTest : public ::testing::Test {
+ public:
+ void SetUp() {
+ CHECK(temp_dir_.CreateUniqueTempDir());
+ output_file_ = temp_dir_.path().Append("fork_out").value();
+ process_.RedirectOutput(output_file_);
+ ClearLog();
+ }
+
+ static void SetUpTestCase() {
+ base::CommandLine::Init(0, nullptr);
+ ::brillo::InitLog(brillo::kLogToStderr);
+ ::brillo::LogToString(true);
+ }
+
+ protected:
+ void CheckStderrCaptured();
+ FilePath GetFdPath(int fd);
+
+ ProcessImpl process_;
+ std::vector<const char*> args_;
+ std::string output_file_;
+ base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(ProcessTest, Basic) {
+ process_.AddArg(kBinEcho);
+ process_.AddArg("hello world");
+ EXPECT_EQ(0, process_.Run());
+ ExpectFileEquals("hello world\n", output_file_.c_str());
+ EXPECT_EQ("", GetLog());
+}
+
+TEST_F(ProcessTest, AddStringOption) {
+ process_.AddArg(kBinEcho);
+ process_.AddStringOption("--hello", "world");
+ EXPECT_EQ(0, process_.Run());
+ ExpectFileEquals("--hello world\n", output_file_.c_str());
+}
+
+TEST_F(ProcessTest, AddIntValue) {
+ process_.AddArg(kBinEcho);
+ process_.AddIntOption("--answer", 42);
+ EXPECT_EQ(0, process_.Run());
+ ExpectFileEquals("--answer 42\n", output_file_.c_str());
+}
+
+TEST_F(ProcessTest, NonZeroReturnValue) {
+ process_.AddArg(kBinFalse);
+ EXPECT_EQ(1, process_.Run());
+ ExpectFileEquals("", output_file_.c_str());
+ EXPECT_EQ("", GetLog());
+}
+
+TEST_F(ProcessTest, BadOutputFile) {
+ process_.AddArg(kBinEcho);
+ process_.RedirectOutput("/bad/path");
+ EXPECT_EQ(static_cast<pid_t>(Process::kErrorExitStatus), process_.Run());
+}
+
+TEST_F(ProcessTest, BadExecutable) {
+ process_.AddArg("false");
+ EXPECT_EQ(static_cast<pid_t>(Process::kErrorExitStatus), process_.Run());
+}
+
+void ProcessTest::CheckStderrCaptured() {
+ std::string contents;
+ process_.AddArg(kBinSh);
+ process_.AddArg("-c");
+ process_.AddArg("echo errormessage 1>&2 && exit 1");
+ EXPECT_EQ(1, process_.Run());
+ EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents));
+ EXPECT_NE(std::string::npos, contents.find("errormessage"));
+ EXPECT_EQ("", GetLog());
+}
+
+TEST_F(ProcessTest, StderrCaptured) {
+ CheckStderrCaptured();
+}
+
+TEST_F(ProcessTest, StderrCapturedWhenPreviouslyClosed) {
+ int saved_stderr = dup(STDERR_FILENO);
+ close(STDERR_FILENO);
+ CheckStderrCaptured();
+ dup2(saved_stderr, STDERR_FILENO);
+}
+
+FilePath ProcessTest::GetFdPath(int fd) {
+ return FilePath(base::StringPrintf("/proc/self/fd/%d", fd));
+}
+
+TEST_F(ProcessTest, RedirectStderrUsingPipe) {
+ std::string contents;
+ process_.RedirectOutput("");
+ process_.AddArg(kBinSh);
+ process_.AddArg("-c");
+ process_.AddArg("echo errormessage >&2 && exit 1");
+ process_.RedirectUsingPipe(STDERR_FILENO, false);
+ EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO));
+ EXPECT_EQ(1, process_.Run());
+ int pipe_fd = process_.GetPipe(STDERR_FILENO);
+ EXPECT_GE(pipe_fd, 0);
+ EXPECT_EQ(-1, process_.GetPipe(STDOUT_FILENO));
+ EXPECT_EQ(-1, process_.GetPipe(STDIN_FILENO));
+ EXPECT_TRUE(base::ReadFileToString(GetFdPath(pipe_fd), &contents));
+ EXPECT_NE(std::string::npos, contents.find("errormessage"));
+ EXPECT_EQ("", GetLog());
+}
+
+TEST_F(ProcessTest, RedirectStderrUsingPipeWhenPreviouslyClosed) {
+ int saved_stderr = dup(STDERR_FILENO);
+ close(STDERR_FILENO);
+ process_.RedirectOutput("");
+ process_.AddArg(kBinCp);
+ process_.RedirectUsingPipe(STDERR_FILENO, false);
+ EXPECT_FALSE(process_.Start());
+ EXPECT_TRUE(FindLog("Unable to fstat fd 2:"));
+ dup2(saved_stderr, STDERR_FILENO);
+}
+
+TEST_F(ProcessTest, RedirectStdoutUsingPipe) {
+ std::string contents;
+ process_.RedirectOutput("");
+ process_.AddArg(kBinEcho);
+ process_.AddArg("hello world\n");
+ process_.RedirectUsingPipe(STDOUT_FILENO, false);
+ EXPECT_EQ(-1, process_.GetPipe(STDOUT_FILENO));
+ EXPECT_EQ(0, process_.Run());
+ int pipe_fd = process_.GetPipe(STDOUT_FILENO);
+ EXPECT_GE(pipe_fd, 0);
+ EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO));
+ EXPECT_EQ(-1, process_.GetPipe(STDIN_FILENO));
+ EXPECT_TRUE(base::ReadFileToString(GetFdPath(pipe_fd), &contents));
+ EXPECT_NE(std::string::npos, contents.find("hello world\n"));
+ EXPECT_EQ("", GetLog());
+}
+
+TEST_F(ProcessTest, RedirectStdinUsingPipe) {
+ std::string contents;
+ const char kMessage[] = "made it!\n";
+ process_.AddArg(kBinCat);
+ process_.RedirectUsingPipe(STDIN_FILENO, true);
+ process_.RedirectOutput(output_file_);
+ EXPECT_TRUE(process_.Start());
+ int write_fd = process_.GetPipe(STDIN_FILENO);
+ EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO));
+ EXPECT_TRUE(base::WriteFile(GetFdPath(write_fd), kMessage, strlen(kMessage)));
+ close(write_fd);
+ EXPECT_EQ(0, process_.Wait());
+ ExpectFileEquals(kMessage, output_file_.c_str());
+}
+
+TEST_F(ProcessTest, WithSameUid) {
+ gid_t uid = geteuid();
+ process_.AddArg(kBinEcho);
+ process_.SetUid(uid);
+ EXPECT_EQ(0, process_.Run());
+}
+
+TEST_F(ProcessTest, WithSameGid) {
+ gid_t gid = getegid();
+ process_.AddArg(kBinEcho);
+ process_.SetGid(gid);
+ EXPECT_EQ(0, process_.Run());
+}
+
+TEST_F(ProcessTest, WithIllegalUid) {
+ ASSERT_NE(0, geteuid());
+ process_.AddArg(kBinEcho);
+ process_.SetUid(0);
+ EXPECT_EQ(static_cast<pid_t>(Process::kErrorExitStatus), process_.Run());
+ std::string contents;
+ EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents));
+ EXPECT_NE(std::string::npos, contents.find("Unable to set UID to 0: 1\n"));
+}
+
+TEST_F(ProcessTest, WithIllegalGid) {
+ ASSERT_NE(0, getegid());
+ process_.AddArg(kBinEcho);
+ process_.SetGid(0);
+ EXPECT_EQ(static_cast<pid_t>(Process::kErrorExitStatus), process_.Run());
+ std::string contents;
+ EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents));
+ EXPECT_NE(std::string::npos, contents.find("Unable to set GID to 0: 1\n"));
+}
+
+TEST_F(ProcessTest, NoParams) {
+ EXPECT_EQ(-1, process_.Run());
+}
+
+#if !defined(__BIONIC__) // Bionic intercepts the segfault on Android.
+TEST_F(ProcessTest, SegFaultHandling) {
+ process_.AddArg(kBinSh);
+ process_.AddArg("-c");
+ process_.AddArg("kill -SEGV $$");
+ EXPECT_EQ(-1, process_.Run());
+ EXPECT_TRUE(FindLog("did not exit normally: 11"));
+}
+#endif
+
+TEST_F(ProcessTest, KillHandling) {
+ process_.AddArg(kBinSh);
+ process_.AddArg("-c");
+ process_.AddArg("kill -KILL $$");
+ EXPECT_EQ(-1, process_.Run());
+ EXPECT_TRUE(FindLog("did not exit normally: 9"));
+}
+
+
+TEST_F(ProcessTest, KillNoPid) {
+ process_.Kill(SIGTERM, 0);
+ EXPECT_TRUE(FindLog("Process not running"));
+}
+
+TEST_F(ProcessTest, ProcessExists) {
+ EXPECT_FALSE(Process::ProcessExists(0));
+ EXPECT_TRUE(Process::ProcessExists(1));
+ EXPECT_TRUE(Process::ProcessExists(getpid()));
+}
+
+TEST_F(ProcessTest, ResetPidByFile) {
+ FilePath pid_path = temp_dir_.path().Append("pid");
+ EXPECT_FALSE(process_.ResetPidByFile(pid_path.value()));
+ EXPECT_TRUE(base::WriteFile(pid_path, "456\n", 4));
+ EXPECT_TRUE(process_.ResetPidByFile(pid_path.value()));
+ EXPECT_EQ(456, process_.pid());
+ // The purpose of this unit test is to check if Process::ResetPidByFile() can
+ // properly read a pid from a file. We don't really want to kill the process
+ // with pid 456, so update the pid to 0 to prevent the Process destructor from
+ // killing any innocent process.
+ process_.UpdatePid(0);
+}
+
+TEST_F(ProcessTest, KillSleeper) {
+ process_.AddArg(kBinSleep);
+ process_.AddArg("10000");
+ ASSERT_TRUE(process_.Start());
+ pid_t pid = process_.pid();
+ ASSERT_GT(pid, 1);
+ EXPECT_TRUE(process_.Kill(SIGTERM, 1));
+ EXPECT_EQ(0, process_.pid());
+}
+
+TEST_F(ProcessTest, Reset) {
+ process_.AddArg(kBinFalse);
+ process_.Reset(0);
+ process_.AddArg(kBinEcho);
+ EXPECT_EQ(0, process_.Run());
+}
+
+bool ReturnFalse() { return false; }
+
+TEST_F(ProcessTest, PreExecCallback) {
+ process_.AddArg(kBinTrue);
+ process_.SetPreExecCallback(base::Bind(&ReturnFalse));
+ ASSERT_NE(0, process_.Run());
+}
+
+TEST_F(ProcessTest, LeakUnusedFileDescriptors) {
+ ScopedPipe pipe;
+ process_.AddArg(kBinStat);
+ process_.AddArg(GetFdPath(pipe.reader).value());
+ process_.AddArg(GetFdPath(pipe.writer).value());
+ process_.SetCloseUnusedFileDescriptors(false);
+ EXPECT_EQ(0, process_.Run());
+}
+
+TEST_F(ProcessTest, CloseUnusedFileDescriptors) {
+ ScopedPipe pipe;
+ process_.AddArg(kBinStat);
+ process_.AddArg(GetFdPath(pipe.reader).value());
+ process_.AddArg(GetFdPath(pipe.writer).value());
+ process_.SetCloseUnusedFileDescriptors(true);
+ // Stat should fail when running on these file descriptor because the files
+ // should not be there.
+ EXPECT_EQ(1, process_.Run());
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/secure_blob.cc b/libbrillo/brillo/secure_blob.cc
new file mode 100644
index 0000000..9e6d570
--- /dev/null
+++ b/libbrillo/brillo/secure_blob.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstring> // memcpy
+
+#include <base/stl_util.h>
+
+#include "brillo/secure_blob.h"
+
+namespace brillo {
+
+SecureBlob::SecureBlob(const std::string& data)
+ : SecureBlob(data.begin(), data.end()) {}
+
+SecureBlob::~SecureBlob() {
+ clear();
+}
+
+void SecureBlob::resize(size_type count) {
+ if (count < size()) {
+ SecureMemset(data() + count, 0, capacity() - count);
+ }
+ Blob::resize(count);
+}
+
+void SecureBlob::resize(size_type count, const value_type& value) {
+ if (count < size()) {
+ SecureMemset(data() + count, 0, capacity() - count);
+ }
+ Blob::resize(count, value);
+}
+
+void SecureBlob::clear() {
+ SecureMemset(data(), 0, capacity());
+ Blob::clear();
+}
+
+std::string SecureBlob::to_string() const {
+ return std::string(data(), data() + size());
+}
+
+SecureBlob SecureBlob::Combine(const SecureBlob& blob1,
+ const SecureBlob& blob2) {
+ SecureBlob result;
+ result.reserve(blob1.size() + blob2.size());
+ result.insert(result.end(), blob1.begin(), blob1.end());
+ result.insert(result.end(), blob2.begin(), blob2.end());
+ return result;
+}
+
+void* SecureMemset(void* v, int c, size_t n) {
+ volatile uint8_t* p = reinterpret_cast<volatile uint8_t*>(v);
+ while (n--)
+ *p++ = c;
+ return v;
+}
+
+int SecureMemcmp(const void* s1, const void* s2, size_t n) {
+ const uint8_t* us1 = reinterpret_cast<const uint8_t*>(s1);
+ const uint8_t* us2 = reinterpret_cast<const uint8_t*>(s2);
+ int result = 0;
+
+ if (0 == n)
+ return 1;
+
+ /* Code snippet without data-dependent branch due to
+ * Nate Lawson (nate@root.org) of Root Labs. */
+ while (n--)
+ result |= *us1++ ^ *us2++;
+
+ return result != 0;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/secure_blob.h b/libbrillo/brillo/secure_blob.h
new file mode 100644
index 0000000..b6111c7
--- /dev/null
+++ b/libbrillo/brillo/secure_blob.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_BRILLO_SECURE_BLOB_H_
+#define LIBBRILLO_BRILLO_SECURE_BLOB_H_
+
+#include <string>
+#include <vector>
+
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+using Blob = std::vector<uint8_t>;
+
+// SecureBlob erases the contents on destruction. It does not guarantee erasure
+// on resize, assign, etc.
+class BRILLO_EXPORT SecureBlob : public Blob {
+ public:
+ SecureBlob() = default;
+ using Blob::vector; // Inherit standard constructors from vector.
+ explicit SecureBlob(const std::string& data);
+ ~SecureBlob();
+
+ void resize(size_type count);
+ void resize(size_type count, const value_type& value);
+ void clear();
+
+ std::string to_string() const;
+ char* char_data() { return reinterpret_cast<char*>(data()); }
+ const char* char_data() const {
+ return reinterpret_cast<const char*>(data());
+ }
+ static SecureBlob Combine(const SecureBlob& blob1, const SecureBlob& blob2);
+};
+
+// Secure memset(). This function is guaranteed to fill in the whole buffer
+// and is not subject to compiler optimization as allowed by Sub-clause 5.1.2.3
+// of C Standard [ISO/IEC 9899:2011] which states:
+// In the abstract machine, all expressions are evaluated as specified by the
+// semantics. An actual implementation need not evaluate part of an expression
+// if it can deduce that its value is not used and that no needed side effects
+// are produced (including any caused by calling a function or accessing
+// a volatile object).
+// While memset() can be optimized out in certain situations (since most
+// compilers implement this function as intrinsic and know of its side effects),
+// this function will not be optimized out.
+BRILLO_EXPORT void* SecureMemset(void* v, int c, size_t n);
+
+// Compare [n] bytes starting at [s1] with [s2] and return 0 if they match,
+// 1 if they don't. Time taken to perform the comparison is only dependent on
+// [n] and not on the relationship of the match between [s1] and [s2].
+BRILLO_EXPORT int SecureMemcmp(const void* s1, const void* s2, size_t n);
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_SECURE_BLOB_H_
diff --git a/libbrillo/brillo/secure_blob_unittest.cc b/libbrillo/brillo/secure_blob_unittest.cc
new file mode 100644
index 0000000..d4fd555
--- /dev/null
+++ b/libbrillo/brillo/secure_blob_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Unit tests for SecureBlob.
+
+#include "brillo/secure_blob.h"
+
+#include <algorithm>
+#include <iterator>
+#include <numeric>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+namespace brillo {
+using std::string;
+
+class SecureBlobTest : public ::testing::Test {
+ public:
+ SecureBlobTest() {}
+ virtual ~SecureBlobTest() {}
+
+ static bool FindBlobInBlob(const brillo::Blob& haystack,
+ const brillo::Blob& needle) {
+ auto pos = std::search(
+ haystack.begin(), haystack.end(), needle.begin(), needle.end());
+ return (pos != haystack.end());
+ }
+
+ static int FindBlobIndexInBlob(const brillo::Blob& haystack,
+ const brillo::Blob& needle) {
+ auto pos = std::search(
+ haystack.begin(), haystack.end(), needle.begin(), needle.end());
+ if (pos == haystack.end()) {
+ return -1;
+ }
+ return std::distance(haystack.begin(), pos);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SecureBlobTest);
+};
+
+TEST_F(SecureBlobTest, AllocationSizeTest) {
+ // Check that allocating a SecureBlob of a specified size works
+ SecureBlob blob(32);
+
+ EXPECT_EQ(32, blob.size());
+}
+
+TEST_F(SecureBlobTest, AllocationCopyTest) {
+ // Check that allocating a SecureBlob with an iterator works
+ unsigned char from_data[32];
+ std::iota(std::begin(from_data), std::end(from_data), 0);
+
+ SecureBlob blob(std::begin(from_data), std::end(from_data));
+
+ EXPECT_EQ(sizeof(from_data), blob.size());
+
+ for (unsigned int i = 0; i < sizeof(from_data); i++) {
+ EXPECT_EQ(from_data[i], blob[i]);
+ }
+}
+
+TEST_F(SecureBlobTest, IteratorConstructorTest) {
+ // Check that allocating a SecureBlob with an iterator works
+ brillo::Blob from_blob(32);
+ for (unsigned int i = 0; i < from_blob.size(); i++) {
+ from_blob[i] = i;
+ }
+
+ SecureBlob blob(from_blob.begin(), from_blob.end());
+
+ EXPECT_EQ(from_blob.size(), blob.size());
+ EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(from_blob, blob));
+}
+
+TEST_F(SecureBlobTest, ResizeTest) {
+ // Check that resizing a SecureBlob wipes the excess memory. The test assumes
+ // that resize() down by one will not re-allocate the memory, so the last byte
+ // will still be part of the SecureBlob's allocation
+ size_t length = 1024;
+ SecureBlob blob(length);
+ void* original_data = blob.data();
+ for (size_t i = 0; i < length; i++) {
+ blob[i] = i;
+ }
+
+ blob.resize(length - 1);
+
+ EXPECT_EQ(original_data, blob.data());
+ EXPECT_EQ(length - 1, blob.size());
+ EXPECT_EQ(0, blob.data()[length - 1]);
+}
+
+TEST_F(SecureBlobTest, CombineTest) {
+ SecureBlob blob1(32);
+ SecureBlob blob2(32);
+ std::iota(blob1.begin(), blob1.end(), 0);
+ std::iota(blob2.begin(), blob2.end(), 32);
+ SecureBlob combined_blob = SecureBlob::Combine(blob1, blob2);
+ EXPECT_EQ(combined_blob.size(), (blob1.size() + blob2.size()));
+ EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(combined_blob, blob1));
+ EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(combined_blob, blob2));
+ int blob1_index = SecureBlobTest::FindBlobIndexInBlob(combined_blob, blob1);
+ int blob2_index = SecureBlobTest::FindBlobIndexInBlob(combined_blob, blob2);
+ EXPECT_EQ(blob1_index, 0);
+ EXPECT_EQ(blob2_index, 32);
+}
+
+TEST_F(SecureBlobTest, BlobToStringTest) {
+ std::string test_string("Test String");
+ SecureBlob blob = SecureBlob(test_string.begin(), test_string.end());
+ EXPECT_EQ(blob.size(), test_string.length());
+ std::string result_string = blob.to_string();
+ EXPECT_EQ(test_string.compare(result_string), 0);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/fake_stream.cc b/libbrillo/brillo/streams/fake_stream.cc
new file mode 100644
index 0000000..0428935
--- /dev/null
+++ b/libbrillo/brillo/streams/fake_stream.cc
@@ -0,0 +1,404 @@
+// Copyright 2015 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 <brillo/streams/fake_stream.h>
+
+#include <algorithm>
+
+#include <base/bind.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/streams/stream_utils.h>
+
+namespace brillo {
+
+namespace {
+
+// Gets a delta between the two times, makes sure that the delta is positive.
+base::TimeDelta CalculateDelay(const base::Time& now,
+ const base::Time& delay_until) {
+ const base::TimeDelta zero_delay;
+ if (delay_until.is_null() || now >= delay_until) {
+ return zero_delay;
+ }
+
+ base::TimeDelta delay = delay_until - now;
+ if (delay < zero_delay)
+ delay = zero_delay;
+ return delay;
+}
+
+// Given the current clock time, and expected delays for read and write
+// operations calculates the smaller wait delay of the two and sets the
+// resulting operation to |*mode| and the delay to wait for into |*delay|.
+void GetMinDelayAndMode(const base::Time& now,
+ bool read, const base::Time& delay_read_until,
+ bool write, const base::Time& delay_write_until,
+ Stream::AccessMode* mode, base::TimeDelta* delay) {
+ base::TimeDelta read_delay = base::TimeDelta::Max();
+ base::TimeDelta write_delay = base::TimeDelta::Max();
+
+ if (read)
+ read_delay = CalculateDelay(now, delay_read_until);
+ if (write)
+ write_delay = CalculateDelay(now, delay_write_until);
+
+ if (read_delay > write_delay) {
+ read = false;
+ } else if (read_delay < write_delay) {
+ write = false;
+ }
+ *mode = stream_utils::MakeAccessMode(read, write);
+ *delay = std::min(read_delay, write_delay);
+}
+
+} // anonymous namespace
+
+FakeStream::FakeStream(Stream::AccessMode mode,
+ base::Clock* clock)
+ : mode_{mode}, clock_{clock} {}
+
+void FakeStream::AddReadPacketData(base::TimeDelta delay,
+ const void* data,
+ size_t size) {
+ auto* byte_ptr = static_cast<const uint8_t*>(data);
+ AddReadPacketData(delay, brillo::Blob{byte_ptr, byte_ptr + size});
+}
+
+void FakeStream::AddReadPacketData(base::TimeDelta delay, brillo::Blob data) {
+ InputDataPacket packet;
+ packet.data = std::move(data);
+ packet.delay_before = delay;
+ incoming_queue_.push(std::move(packet));
+}
+
+void FakeStream::AddReadPacketString(base::TimeDelta delay,
+ const std::string& data) {
+ AddReadPacketData(delay, brillo::Blob{data.begin(), data.end()});
+}
+
+void FakeStream::QueueReadError(base::TimeDelta delay) {
+ QueueReadErrorWithMessage(delay, std::string{});
+}
+
+void FakeStream::QueueReadErrorWithMessage(base::TimeDelta delay,
+ const std::string& message) {
+ InputDataPacket packet;
+ packet.data.assign(message.begin(), message.end());
+ packet.delay_before = delay;
+ packet.read_error = true;
+ incoming_queue_.push(std::move(packet));
+}
+
+void FakeStream::ClearReadQueue() {
+ std::queue<InputDataPacket>().swap(incoming_queue_);
+ delay_input_until_ = base::Time{};
+ input_buffer_.clear();
+ input_ptr_ = 0;
+ report_read_error_ = 0;
+}
+
+void FakeStream::ExpectWritePacketSize(base::TimeDelta delay,
+ size_t data_size) {
+ OutputDataPacket packet;
+ packet.expected_size = data_size;
+ packet.delay_before = delay;
+ outgoing_queue_.push(std::move(packet));
+}
+
+void FakeStream::ExpectWritePacketData(base::TimeDelta delay,
+ const void* data,
+ size_t size) {
+ auto* byte_ptr = static_cast<const uint8_t*>(data);
+ ExpectWritePacketData(delay, brillo::Blob{byte_ptr, byte_ptr + size});
+}
+
+void FakeStream::ExpectWritePacketData(base::TimeDelta delay,
+ brillo::Blob data) {
+ OutputDataPacket packet;
+ packet.expected_size = data.size();
+ packet.data = std::move(data);
+ packet.delay_before = delay;
+ outgoing_queue_.push(std::move(packet));
+}
+
+void FakeStream::ExpectWritePacketString(base::TimeDelta delay,
+ const std::string& data) {
+ ExpectWritePacketData(delay, brillo::Blob{data.begin(), data.end()});
+}
+
+void FakeStream::QueueWriteError(base::TimeDelta delay) {
+ QueueWriteErrorWithMessage(delay, std::string{});
+}
+
+void FakeStream::QueueWriteErrorWithMessage(base::TimeDelta delay,
+ const std::string& message) {
+ OutputDataPacket packet;
+ packet.expected_size = 0;
+ packet.data.assign(message.begin(), message.end());
+ packet.delay_before = delay;
+ packet.write_error = true;
+ outgoing_queue_.push(std::move(packet));
+}
+
+void FakeStream::ClearWriteQueue() {
+ std::queue<OutputDataPacket>().swap(outgoing_queue_);
+ delay_output_until_ = base::Time{};
+ output_buffer_.clear();
+ expected_output_data_.clear();
+ max_output_buffer_size_ = 0;
+ all_output_data_.clear();
+ report_write_error_ = 0;
+}
+
+const brillo::Blob& FakeStream::GetFlushedOutputData() const {
+ return all_output_data_;
+}
+
+std::string FakeStream::GetFlushedOutputDataAsString() const {
+ return std::string{all_output_data_.begin(), all_output_data_.end()};
+}
+
+bool FakeStream::CanRead() const {
+ return stream_utils::IsReadAccessMode(mode_);
+}
+
+bool FakeStream::CanWrite() const {
+ return stream_utils::IsWriteAccessMode(mode_);
+}
+
+bool FakeStream::SetSizeBlocking(uint64_t /* size */, ErrorPtr* error) {
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+}
+
+bool FakeStream::Seek(int64_t /* offset */,
+ Whence /* whence */,
+ uint64_t* /* new_position */,
+ ErrorPtr* error) {
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+}
+
+bool FakeStream::IsReadBufferEmpty() const {
+ return input_ptr_ >= input_buffer_.size();
+}
+
+bool FakeStream::PopReadPacket() {
+ if (incoming_queue_.empty())
+ return false;
+ const InputDataPacket& packet = incoming_queue_.front();
+ input_ptr_ = 0;
+ input_buffer_ = std::move(packet.data);
+ delay_input_until_ = clock_->Now() + packet.delay_before;
+ incoming_queue_.pop();
+ report_read_error_ = packet.read_error;
+ return true;
+}
+
+bool FakeStream::ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) {
+ if (!CanRead())
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ for (;;) {
+ if (!delay_input_until_.is_null() && clock_->Now() < delay_input_until_) {
+ *size_read = 0;
+ if (end_of_stream)
+ *end_of_stream = false;
+ break;
+ }
+
+ if (report_read_error_) {
+ report_read_error_ = false;
+ std::string message{input_buffer_.begin(), input_buffer_.end()};
+ if (message.empty())
+ message = "Simulating read error for tests";
+ input_buffer_.clear();
+ Error::AddTo(error, FROM_HERE, "fake_stream", "read_error", message);
+ return false;
+ }
+
+ if (!IsReadBufferEmpty()) {
+ size_to_read = std::min(size_to_read, input_buffer_.size() - input_ptr_);
+ std::memcpy(buffer, input_buffer_.data() + input_ptr_, size_to_read);
+ input_ptr_ += size_to_read;
+ *size_read = size_to_read;
+ if (end_of_stream)
+ *end_of_stream = false;
+ break;
+ }
+
+ if (!PopReadPacket()) {
+ *size_read = 0;
+ if (end_of_stream)
+ *end_of_stream = true;
+ break;
+ }
+ }
+ return true;
+}
+
+bool FakeStream::IsWriteBufferFull() const {
+ return output_buffer_.size() >= max_output_buffer_size_;
+}
+
+bool FakeStream::PopWritePacket() {
+ if (outgoing_queue_.empty())
+ return false;
+ const OutputDataPacket& packet = outgoing_queue_.front();
+ expected_output_data_ = std::move(packet.data);
+ delay_output_until_ = clock_->Now() + packet.delay_before;
+ max_output_buffer_size_ = packet.expected_size;
+ report_write_error_ = packet.write_error;
+ outgoing_queue_.pop();
+ return true;
+}
+
+bool FakeStream::WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) {
+ if (!CanWrite())
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ for (;;) {
+ if (!delay_output_until_.is_null() && clock_->Now() < delay_output_until_) {
+ *size_written = 0;
+ return true;
+ }
+
+ if (report_write_error_) {
+ report_write_error_ = false;
+ std::string message{expected_output_data_.begin(),
+ expected_output_data_.end()};
+ if (message.empty())
+ message = "Simulating write error for tests";
+ output_buffer_.clear();
+ max_output_buffer_size_ = 0;
+ expected_output_data_.clear();
+ Error::AddTo(error, FROM_HERE, "fake_stream", "write_error", message);
+ return false;
+ }
+
+ if (!IsWriteBufferFull()) {
+ bool success = true;
+ size_to_write = std::min(size_to_write,
+ max_output_buffer_size_ - output_buffer_.size());
+ auto byte_ptr = static_cast<const uint8_t*>(buffer);
+ output_buffer_.insert(output_buffer_.end(),
+ byte_ptr, byte_ptr + size_to_write);
+ if (output_buffer_.size() == max_output_buffer_size_) {
+ if (!expected_output_data_.empty() &&
+ expected_output_data_ != output_buffer_) {
+ // We expected different data to be written, report an error.
+ Error::AddTo(error, FROM_HERE, "fake_stream", "data_mismatch",
+ "Unexpected data written");
+ success = false;
+ }
+
+ all_output_data_.insert(all_output_data_.end(),
+ output_buffer_.begin(), output_buffer_.end());
+
+ output_buffer_.clear();
+ max_output_buffer_size_ = 0;
+ expected_output_data_.clear();
+ }
+ *size_written = size_to_write;
+ return success;
+ }
+
+ if (!PopWritePacket()) {
+ // No more data expected.
+ Error::AddTo(error, FROM_HERE, "fake_stream", "full",
+ "No more output data expected");
+ return false;
+ }
+ }
+}
+
+bool FakeStream::FlushBlocking(ErrorPtr* error) {
+ if (!CanWrite())
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ bool success = true;
+ if (!output_buffer_.empty()) {
+ if (!expected_output_data_.empty() &&
+ expected_output_data_ != output_buffer_) {
+ // We expected different data to be written, report an error.
+ Error::AddTo(error, FROM_HERE, "fake_stream", "data_mismatch",
+ "Unexpected data written");
+ success = false;
+ }
+ all_output_data_.insert(all_output_data_.end(),
+ output_buffer_.begin(), output_buffer_.end());
+
+ output_buffer_.clear();
+ max_output_buffer_size_ = 0;
+ expected_output_data_.clear();
+ }
+ return success;
+}
+
+bool FakeStream::CloseBlocking(ErrorPtr* /* error */) {
+ is_open_ = false;
+ return true;
+}
+
+bool FakeStream::WaitForData(AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) {
+ bool read_requested = stream_utils::IsReadAccessMode(mode);
+ bool write_requested = stream_utils::IsWriteAccessMode(mode);
+
+ if ((read_requested && !CanRead()) || (write_requested && !CanWrite()))
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+
+ if (read_requested && IsReadBufferEmpty())
+ PopReadPacket();
+ if (write_requested && IsWriteBufferFull())
+ PopWritePacket();
+
+ base::TimeDelta delay;
+ GetMinDelayAndMode(clock_->Now(), read_requested, delay_input_until_,
+ write_requested, delay_output_until_, &mode, &delay);
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::Bind(callback, mode), delay);
+ return true;
+}
+
+bool FakeStream::WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) {
+ const base::TimeDelta zero_delay;
+ bool read_requested = stream_utils::IsReadAccessMode(in_mode);
+ bool write_requested = stream_utils::IsWriteAccessMode(in_mode);
+
+ if ((read_requested && !CanRead()) || (write_requested && !CanWrite()))
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+
+ base::TimeDelta delay;
+ GetMinDelayAndMode(clock_->Now(), read_requested, delay_input_until_,
+ write_requested, delay_output_until_, out_mode, &delay);
+
+ if (timeout < delay)
+ return stream_utils::ErrorOperationTimeout(FROM_HERE, error);
+
+ LOG(INFO) << "TEST: Would have blocked for " << delay.InMilliseconds()
+ << " ms.";
+
+ return true;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/fake_stream.h b/libbrillo/brillo/streams/fake_stream.h
new file mode 100644
index 0000000..fb9b522
--- /dev/null
+++ b/libbrillo/brillo/streams/fake_stream.h
@@ -0,0 +1,171 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_FAKE_STREAM_H_
+#define LIBBRILLO_BRILLO_STREAMS_FAKE_STREAM_H_
+
+#include <queue>
+#include <string>
+
+#include <base/callback_forward.h>
+#include <base/macros.h>
+#include <base/time/clock.h>
+#include <base/time/time.h>
+#include <brillo/secure_blob.h>
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+
+// Fake stream implementation for testing.
+// This class allows to provide data for the stream in tests that can be later
+// read through the Stream interface. Also, data written into the stream can be
+// later inspected and verified.
+//
+// NOTE: This class provides a fake implementation for streams with separate
+// input and output channels. That is, read and write operations do not affect
+// each other. Also, the stream implementation is sequential only (no seeking).
+// Good examples of a use case for fake stream are:
+// - read-only sequential streams (file, memory, pipe, ...)
+// - write-only sequential streams (same as above)
+// - independent channel read-write streams (sockets, ...)
+//
+// For more complex read/write stream test scenarios using a real MemoryStream
+// or temporary FileStream is probably a better choice.
+class FakeStream : public Stream {
+ public:
+ // Construct a new instance of the fake stream.
+ // mode - expected read/write mode supported by the stream.
+ // clock - the clock to use to get the current time.
+ FakeStream(Stream::AccessMode mode,
+ base::Clock* clock);
+
+ // Add data packets to the read queue of the stream.
+ // Optional |delay| indicates that the data packet should be delayed.
+ void AddReadPacketData(base::TimeDelta delay, const void* data, size_t size);
+ void AddReadPacketData(base::TimeDelta delay, brillo::Blob data);
+ void AddReadPacketString(base::TimeDelta delay, const std::string& data);
+
+ // Schedule a read error by adding a special error packet to the queue.
+ void QueueReadError(base::TimeDelta delay);
+ void QueueReadErrorWithMessage(base::TimeDelta delay,
+ const std::string& message);
+
+ // Resets read queue and clears any input data buffers.
+ void ClearReadQueue();
+
+ // Add expectations for output data packets to be written by the stream.
+ // Optional |delay| indicates that the initial write operation for the data in
+ // the packet should be delayed.
+ // ExpectWritePacketSize just limits the size of output packet while
+ // ExpectWritePacketData also validates that the data matches that of |data|.
+ void ExpectWritePacketSize(base::TimeDelta delay, size_t data_size);
+ void ExpectWritePacketData(base::TimeDelta delay,
+ const void* data,
+ size_t size);
+ void ExpectWritePacketData(base::TimeDelta delay, brillo::Blob data);
+ void ExpectWritePacketString(base::TimeDelta delay, const std::string& data);
+
+ // Schedule a write error by adding a special error packet to the queue.
+ void QueueWriteError(base::TimeDelta delay);
+ void QueueWriteErrorWithMessage(base::TimeDelta delay,
+ const std::string& message);
+
+ // Resets write queue and clears any output data buffers.
+ void ClearWriteQueue();
+
+ // Returns the output data accumulated so far by all complete write packets,
+ // or explicitly flushed.
+ const brillo::Blob& GetFlushedOutputData() const;
+ std::string GetFlushedOutputDataAsString() const;
+
+ // Overrides from brillo::Stream.
+ bool IsOpen() const override { return is_open_; }
+ bool CanRead() const override;
+ bool CanWrite() const override;
+ bool CanSeek() const override { return false; }
+ bool CanGetSize() const override { return false; }
+ uint64_t GetSize() const override { return 0; }
+ bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override;
+ uint64_t GetRemainingSize() const override { return 0; }
+ uint64_t GetPosition() const override { return 0; }
+ bool Seek(int64_t offset,
+ Whence whence,
+ uint64_t* new_position,
+ ErrorPtr* error) override;
+
+ bool ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) override;
+ bool WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) override;
+ bool FlushBlocking(ErrorPtr* error) override;
+ bool CloseBlocking(ErrorPtr* error) override;
+ bool WaitForData(AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) override;
+ bool WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) override;
+
+ private:
+ // Input data packet to be placed on the read queue.
+ struct InputDataPacket {
+ brillo::Blob data; // Data to be read.
+ base::TimeDelta delay_before; // Possible delay for the first read.
+ bool read_error{false}; // Set to true if this packet generates an error.
+ };
+
+ // Output data packet to be placed on the write queue.
+ struct OutputDataPacket {
+ size_t expected_size{0}; // Output packet size
+ brillo::Blob data; // Possible data to verify the output with.
+ base::TimeDelta delay_before; // Possible delay for the first write.
+ bool write_error{false}; // Set to true if this packet generates an error.
+ };
+
+ // Check if there is any pending read data in the input buffer.
+ bool IsReadBufferEmpty() const;
+ // Pops the next read packet from the queue and sets its data into the
+ // internal input buffer.
+ bool PopReadPacket();
+
+ // Check if the output buffer is full.
+ bool IsWriteBufferFull() const;
+
+ // Moves the current full output buffer into |all_output_data_|, clears the
+ // buffer, and pops the information about the next expected output packet
+ // from the write queue.
+ bool PopWritePacket();
+
+ bool is_open_{true};
+ Stream::AccessMode mode_;
+ base::Clock* clock_;
+
+ // Internal data for read operations.
+ std::queue<InputDataPacket> incoming_queue_;
+ base::Time delay_input_until_;
+ brillo::Blob input_buffer_;
+ size_t input_ptr_{0};
+ bool report_read_error_{false};
+
+ // Internal data for write operations.
+ std::queue<OutputDataPacket> outgoing_queue_;
+ base::Time delay_output_until_;
+ brillo::Blob output_buffer_;
+ brillo::Blob expected_output_data_;
+ size_t max_output_buffer_size_{0};
+ bool report_write_error_{false};
+ brillo::Blob all_output_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeStream);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_FAKE_STREAM_H_
diff --git a/libbrillo/brillo/streams/fake_stream_unittest.cc b/libbrillo/brillo/streams/fake_stream_unittest.cc
new file mode 100644
index 0000000..df8b223
--- /dev/null
+++ b/libbrillo/brillo/streams/fake_stream_unittest.cc
@@ -0,0 +1,535 @@
+// Copyright 2015 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 <brillo/streams/fake_stream.h>
+
+#include <vector>
+
+#include <base/callback.h>
+#include <base/test/simple_test_clock.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/mock_message_loop.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::AnyNumber;
+using testing::InSequence;
+using testing::_;
+
+namespace brillo {
+
+class FakeStreamTest : public testing::Test {
+ public:
+ void SetUp() override {
+ mock_loop_.SetAsCurrent();
+ // Ignore calls to RunOnce().
+ EXPECT_CALL(mock_loop_, RunOnce(true)).Times(AnyNumber());
+ }
+
+ void CreateStream(Stream::AccessMode mode) {
+ stream_.reset(new FakeStream{mode, &clock_});
+ }
+
+ // Performs non-blocking read on the stream and returns the read data
+ // as a string in |out_buffer|. Returns true if the read was successful or
+ // false when an error occurs. |*eos| is set to true when end of stream is
+ // reached.
+ bool ReadString(size_t size_to_read, std::string* out_buffer, bool* eos) {
+ std::vector<char> data;
+ data.resize(size_to_read);
+ size_t size_read = 0;
+ bool ok = stream_->ReadNonBlocking(data.data(), data.size(), &size_read,
+ eos, nullptr);
+ if (ok) {
+ out_buffer->assign(data.data(), data.data() + size_read);
+ } else {
+ out_buffer->clear();
+ }
+ return ok;
+ }
+
+ // Writes a string to a stream. Returns the number of bytes written or -1
+ // in case an error occurred.
+ int WriteString(const std::string& data) {
+ size_t written = 0;
+ if (!stream_->WriteNonBlocking(data.data(), data.size(), &written, nullptr))
+ return -1;
+ return static_cast<int>(written);
+ }
+
+ protected:
+ base::SimpleTestClock clock_;
+ MockMessageLoop mock_loop_{&clock_};
+ std::unique_ptr<FakeStream> stream_;
+ const base::TimeDelta zero_delay;
+};
+
+TEST_F(FakeStreamTest, InitReadOnly) {
+ CreateStream(Stream::AccessMode::READ);
+ EXPECT_TRUE(stream_->IsOpen());
+ EXPECT_TRUE(stream_->CanRead());
+ EXPECT_FALSE(stream_->CanWrite());
+ EXPECT_FALSE(stream_->CanSeek());
+ EXPECT_FALSE(stream_->CanGetSize());
+ EXPECT_EQ(0, stream_->GetSize());
+ EXPECT_EQ(0, stream_->GetRemainingSize());
+ EXPECT_EQ(0, stream_->GetPosition());
+}
+
+TEST_F(FakeStreamTest, InitWriteOnly) {
+ CreateStream(Stream::AccessMode::WRITE);
+ EXPECT_TRUE(stream_->IsOpen());
+ EXPECT_FALSE(stream_->CanRead());
+ EXPECT_TRUE(stream_->CanWrite());
+ EXPECT_FALSE(stream_->CanSeek());
+ EXPECT_FALSE(stream_->CanGetSize());
+ EXPECT_EQ(0, stream_->GetSize());
+ EXPECT_EQ(0, stream_->GetRemainingSize());
+ EXPECT_EQ(0, stream_->GetPosition());
+}
+
+TEST_F(FakeStreamTest, InitReadWrite) {
+ CreateStream(Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->IsOpen());
+ EXPECT_TRUE(stream_->CanRead());
+ EXPECT_TRUE(stream_->CanWrite());
+ EXPECT_FALSE(stream_->CanSeek());
+ EXPECT_FALSE(stream_->CanGetSize());
+ EXPECT_EQ(0, stream_->GetSize());
+ EXPECT_EQ(0, stream_->GetRemainingSize());
+ EXPECT_EQ(0, stream_->GetPosition());
+}
+
+TEST_F(FakeStreamTest, ReadEmpty) {
+ CreateStream(Stream::AccessMode::READ);
+ std::string data;
+ bool eos = false;
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_TRUE(eos);
+ EXPECT_TRUE(data.empty());
+}
+
+TEST_F(FakeStreamTest, ReadFullPacket) {
+ CreateStream(Stream::AccessMode::READ);
+ stream_->AddReadPacketString({}, "foo");
+ std::string data;
+ bool eos = false;
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("foo", data);
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_TRUE(eos);
+ EXPECT_TRUE(data.empty());
+}
+
+TEST_F(FakeStreamTest, ReadPartialPacket) {
+ CreateStream(Stream::AccessMode::READ);
+ stream_->AddReadPacketString({}, "foobar");
+ std::string data;
+ bool eos = false;
+ EXPECT_TRUE(ReadString(3, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("foo", data);
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("bar", data);
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_TRUE(eos);
+ EXPECT_TRUE(data.empty());
+}
+
+TEST_F(FakeStreamTest, ReadMultiplePackets) {
+ CreateStream(Stream::AccessMode::READ);
+ stream_->AddReadPacketString({}, "foobar");
+ stream_->AddReadPacketString({}, "baz");
+ stream_->AddReadPacketString({}, "quux");
+ std::string data;
+ bool eos = false;
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("foobar", data);
+
+ EXPECT_TRUE(ReadString(2, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("ba", data);
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("z", data);
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("quux", data);
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_TRUE(eos);
+ EXPECT_TRUE(data.empty());
+
+ stream_->AddReadPacketString({}, "foo-bar");
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("foo-bar", data);
+}
+
+TEST_F(FakeStreamTest, ReadPacketsWithDelay) {
+ CreateStream(Stream::AccessMode::READ);
+ stream_->AddReadPacketString({}, "foobar");
+ stream_->AddReadPacketString(base::TimeDelta::FromSeconds(1), "baz");
+ std::string data;
+ bool eos = false;
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("foobar", data);
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_TRUE(data.empty());
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_TRUE(data.empty());
+
+ clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("baz", data);
+}
+
+TEST_F(FakeStreamTest, ReadPacketsWithError) {
+ CreateStream(Stream::AccessMode::READ);
+ stream_->AddReadPacketString({}, "foobar");
+ stream_->QueueReadErrorWithMessage(base::TimeDelta::FromSeconds(1),
+ "Dummy error");
+ stream_->AddReadPacketString({}, "baz");
+
+ std::string data;
+ bool eos = false;
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("foobar", data);
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_TRUE(data.empty());
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_TRUE(data.empty());
+
+ clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_FALSE(ReadString(100, &data, &eos));
+
+ EXPECT_TRUE(ReadString(100, &data, &eos));
+ EXPECT_FALSE(eos);
+ EXPECT_EQ("baz", data);
+}
+
+TEST_F(FakeStreamTest, WaitForDataRead) {
+ CreateStream(Stream::AccessMode::READ);
+
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(2);
+
+ int call_count = 0;
+ auto callback = [](int* call_count, Stream::AccessMode mode) {
+ (*call_count)++;
+ EXPECT_EQ(Stream::AccessMode::READ, mode);
+ };
+
+ EXPECT_TRUE(
+ stream_->WaitForData(Stream::AccessMode::READ,
+ base::Bind(callback, base::Unretained(&call_count)),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(1, call_count);
+
+ stream_->AddReadPacketString({}, "foobar");
+ EXPECT_TRUE(
+ stream_->WaitForData(Stream::AccessMode::READ,
+ base::Bind(callback, base::Unretained(&call_count)),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(2, call_count);
+
+ stream_->ClearReadQueue();
+
+ auto one_sec_delay = base::TimeDelta::FromSeconds(1);
+ stream_->AddReadPacketString(one_sec_delay, "baz");
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1);
+ EXPECT_TRUE(
+ stream_->WaitForData(Stream::AccessMode::READ,
+ base::Bind(callback, base::Unretained(&call_count)),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(3, call_count);
+}
+
+TEST_F(FakeStreamTest, ReadAsync) {
+ CreateStream(Stream::AccessMode::READ);
+ std::string input_data = "foobar-baz";
+ size_t split_pos = input_data.find('-');
+
+ auto one_sec_delay = base::TimeDelta::FromSeconds(1);
+ stream_->AddReadPacketString({}, input_data.substr(0, split_pos));
+ stream_->AddReadPacketString(one_sec_delay, input_data.substr(split_pos));
+
+ {
+ InSequence seq;
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(1);
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1);
+ }
+
+ std::vector<char> buffer;
+ buffer.resize(input_data.size());
+
+ int success_count = 0;
+ int error_count = 0;
+ auto on_success = [](int* success_count) { (*success_count)++; };
+ auto on_failure = [](int* error_count, const Error* /* error */) {
+ (*error_count)++;
+ };
+
+ EXPECT_TRUE(stream_->ReadAllAsync(
+ buffer.data(),
+ buffer.size(),
+ base::Bind(on_success, base::Unretained(&success_count)),
+ base::Bind(on_failure, base::Unretained(&error_count)),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(1, success_count);
+ EXPECT_EQ(0, error_count);
+ EXPECT_EQ(input_data, (std::string{buffer.begin(), buffer.end()}));
+}
+
+TEST_F(FakeStreamTest, WriteEmpty) {
+ CreateStream(Stream::AccessMode::WRITE);
+ EXPECT_EQ(-1, WriteString("foo"));
+}
+
+TEST_F(FakeStreamTest, WritePartial) {
+ CreateStream(Stream::AccessMode::WRITE);
+ stream_->ExpectWritePacketSize({}, 6);
+ EXPECT_EQ(3, WriteString("foo"));
+ EXPECT_EQ(3, WriteString("bar"));
+ EXPECT_EQ(-1, WriteString("baz"));
+
+ EXPECT_EQ("foobar", stream_->GetFlushedOutputDataAsString());
+}
+
+TEST_F(FakeStreamTest, WriteFullPackets) {
+ CreateStream(Stream::AccessMode::WRITE);
+
+ stream_->ExpectWritePacketSize({}, 3);
+ EXPECT_EQ(3, WriteString("foo"));
+ EXPECT_EQ(-1, WriteString("bar"));
+
+ stream_->ExpectWritePacketSize({}, 3);
+ EXPECT_EQ(3, WriteString("bar"));
+
+ stream_->ExpectWritePacketSize({}, 3);
+ EXPECT_EQ(3, WriteString("quux"));
+
+ EXPECT_EQ("foobarquu", stream_->GetFlushedOutputDataAsString());
+}
+
+TEST_F(FakeStreamTest, WriteAndVerifyData) {
+ CreateStream(Stream::AccessMode::WRITE);
+
+ stream_->ExpectWritePacketString({}, "foo");
+ stream_->ExpectWritePacketString({}, "bar");
+ EXPECT_EQ(3, WriteString("foobar"));
+ EXPECT_EQ(3, WriteString("bar"));
+
+ stream_->ExpectWritePacketString({}, "foo");
+ stream_->ExpectWritePacketString({}, "baz");
+ EXPECT_EQ(3, WriteString("foobar"));
+ EXPECT_EQ(-1, WriteString("bar"));
+
+ stream_->ExpectWritePacketString({}, "foobar");
+ EXPECT_EQ(3, WriteString("foo"));
+ EXPECT_EQ(2, WriteString("ba"));
+ EXPECT_EQ(-1, WriteString("z"));
+}
+
+TEST_F(FakeStreamTest, WriteWithDelay) {
+ CreateStream(Stream::AccessMode::WRITE);
+
+ const auto delay = base::TimeDelta::FromMilliseconds(500);
+
+ stream_->ExpectWritePacketSize({}, 3);
+ stream_->ExpectWritePacketSize(delay, 3);
+ EXPECT_EQ(3, WriteString("foobar"));
+
+ EXPECT_EQ(0, WriteString("bar"));
+ EXPECT_EQ(0, WriteString("bar"));
+ clock_.Advance(delay);
+ EXPECT_EQ(3, WriteString("bar"));
+
+ EXPECT_EQ("foobar", stream_->GetFlushedOutputDataAsString());
+}
+
+TEST_F(FakeStreamTest, WriteWithError) {
+ CreateStream(Stream::AccessMode::WRITE);
+
+ const auto delay = base::TimeDelta::FromMilliseconds(500);
+
+ stream_->ExpectWritePacketSize({}, 3);
+ stream_->QueueWriteError({});
+ stream_->ExpectWritePacketSize({}, 3);
+ stream_->QueueWriteErrorWithMessage(delay, "Dummy message");
+ stream_->ExpectWritePacketString({}, "foobar");
+
+ const std::string data = "foobarbaz";
+ EXPECT_EQ(3, WriteString(data));
+ EXPECT_EQ(-1, WriteString(data)); // Simulated error #1.
+ EXPECT_EQ(3, WriteString(data));
+ EXPECT_EQ(0, WriteString(data)); // Waiting for data...
+ clock_.Advance(delay);
+ EXPECT_EQ(-1, WriteString(data)); // Simulated error #2.
+ EXPECT_EQ(6, WriteString(data));
+ EXPECT_EQ(-1, WriteString(data)); // No more data expected.
+}
+
+TEST_F(FakeStreamTest, WaitForDataWrite) {
+ CreateStream(Stream::AccessMode::WRITE);
+
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(2);
+
+ int call_count = 0;
+ auto callback = [](int* call_count, Stream::AccessMode mode) {
+ (*call_count)++;
+ EXPECT_EQ(Stream::AccessMode::WRITE, mode);
+ };
+
+ EXPECT_TRUE(
+ stream_->WaitForData(Stream::AccessMode::WRITE,
+ base::Bind(callback, base::Unretained(&call_count)),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(1, call_count);
+
+ stream_->ExpectWritePacketString({}, "foobar");
+ EXPECT_TRUE(
+ stream_->WaitForData(Stream::AccessMode::WRITE,
+ base::Bind(callback, base::Unretained(&call_count)),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(2, call_count);
+
+ stream_->ClearWriteQueue();
+
+ auto one_sec_delay = base::TimeDelta::FromSeconds(1);
+ stream_->ExpectWritePacketString(one_sec_delay, "baz");
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1);
+ EXPECT_TRUE(
+ stream_->WaitForData(Stream::AccessMode::WRITE,
+ base::Bind(callback, base::Unretained(&call_count)),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(3, call_count);
+}
+
+TEST_F(FakeStreamTest, WriteAsync) {
+ CreateStream(Stream::AccessMode::WRITE);
+ std::string output_data = "foobar-baz";
+ size_t split_pos = output_data.find('-');
+
+ auto one_sec_delay = base::TimeDelta::FromSeconds(1);
+ stream_->ExpectWritePacketString({}, output_data.substr(0, split_pos));
+ stream_->ExpectWritePacketString(one_sec_delay,
+ output_data.substr(split_pos));
+
+ {
+ InSequence seq;
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(1);
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1);
+ }
+
+ int success_count = 0;
+ int error_count = 0;
+ auto on_success = [](int* success_count) { (*success_count)++; };
+ auto on_failure = [](int* error_count, const Error* /* error */) {
+ (*error_count)++;
+ };
+
+ EXPECT_TRUE(stream_->WriteAllAsync(
+ output_data.data(),
+ output_data.size(),
+ base::Bind(on_success, base::Unretained(&success_count)),
+ base::Bind(on_failure, base::Unretained(&error_count)),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(1, success_count);
+ EXPECT_EQ(0, error_count);
+ EXPECT_EQ(output_data, stream_->GetFlushedOutputDataAsString());
+}
+
+TEST_F(FakeStreamTest, WaitForDataReadWrite) {
+ CreateStream(Stream::AccessMode::READ_WRITE);
+ auto one_sec_delay = base::TimeDelta::FromSeconds(1);
+ auto two_sec_delay = base::TimeDelta::FromSeconds(2);
+
+ int call_count = 0;
+ auto callback = [](int* call_count,
+ Stream::AccessMode mode,
+ Stream::AccessMode expected_mode) {
+ (*call_count)++;
+ EXPECT_EQ(static_cast<int>(expected_mode), static_cast<int>(mode));
+ };
+
+ stream_->AddReadPacketString(one_sec_delay, "foo");
+ stream_->ExpectWritePacketString(two_sec_delay, "bar");
+
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1);
+ EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE,
+ base::Bind(callback,
+ base::Unretained(&call_count),
+ Stream::AccessMode::READ),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(1, call_count);
+
+ // The above step has adjusted the clock by 1 second already.
+ stream_->ClearReadQueue();
+ stream_->AddReadPacketString(two_sec_delay, "foo");
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1);
+ EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE,
+ base::Bind(callback,
+ base::Unretained(&call_count),
+ Stream::AccessMode::WRITE),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(2, call_count);
+
+ clock_.Advance(one_sec_delay);
+
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, zero_delay)).Times(1);
+ EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE,
+ base::Bind(callback,
+ base::Unretained(&call_count),
+ Stream::AccessMode::READ_WRITE),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(3, call_count);
+
+ stream_->ClearReadQueue();
+ stream_->ClearWriteQueue();
+ stream_->AddReadPacketString(one_sec_delay, "foo");
+ stream_->ExpectWritePacketString(one_sec_delay, "bar");
+
+ EXPECT_CALL(mock_loop_, PostDelayedTask(_, _, one_sec_delay)).Times(1);
+ EXPECT_TRUE(stream_->WaitForData(Stream::AccessMode::READ_WRITE,
+ base::Bind(callback,
+ base::Unretained(&call_count),
+ Stream::AccessMode::READ_WRITE),
+ nullptr));
+ mock_loop_.Run();
+ EXPECT_EQ(4, call_count);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/file_stream.cc b/libbrillo/brillo/streams/file_stream.cc
new file mode 100644
index 0000000..7b28a5a
--- /dev/null
+++ b/libbrillo/brillo/streams/file_stream.cc
@@ -0,0 +1,548 @@
+// Copyright 2015 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 <brillo/streams/file_stream.h>
+
+#include <algorithm>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/posix/eintr_wrapper.h>
+#include <brillo/errors/error_codes.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/streams/stream_errors.h>
+#include <brillo/streams/stream_utils.h>
+
+namespace brillo {
+
+// FileDescriptor is a helper class that serves two purposes:
+// 1. It wraps low-level system APIs (as FileDescriptorInterface) to allow
+// mocking calls to them in tests.
+// 2. It provides file descriptor watching services using FileDescriptorWatcher
+// and MessageLoopForIO::Watcher interface.
+// The real FileStream uses this class to perform actual file I/O on the
+// contained file descriptor.
+class FileDescriptor : public FileStream::FileDescriptorInterface {
+ public:
+ FileDescriptor(int fd, bool own) : fd_{fd}, own_{own} {}
+ ~FileDescriptor() override {
+ if (IsOpen()) {
+ Close();
+ }
+ }
+
+ // Overrides for FileStream::FileDescriptorInterface methods.
+ bool IsOpen() const override { return fd_ >= 0; }
+
+ ssize_t Read(void* buf, size_t nbyte) override {
+ return HANDLE_EINTR(read(fd_, buf, nbyte));
+ }
+
+ ssize_t Write(const void* buf, size_t nbyte) override {
+ return HANDLE_EINTR(write(fd_, buf, nbyte));
+ }
+
+ off64_t Seek(off64_t offset, int whence) override {
+ return lseek64(fd_, offset, whence);
+ }
+
+ mode_t GetFileMode() const override {
+ struct stat file_stat;
+ if (fstat(fd_, &file_stat) < 0)
+ return 0;
+ return file_stat.st_mode;
+ }
+
+ uint64_t GetSize() const override {
+ struct stat file_stat;
+ if (fstat(fd_, &file_stat) < 0)
+ return 0;
+ return file_stat.st_size;
+ }
+
+ int Truncate(off64_t length) const override {
+ return HANDLE_EINTR(ftruncate(fd_, length));
+ }
+
+ int Close() override {
+ int fd = -1;
+ // The stream may or may not own the file descriptor stored in |fd_|.
+ // Despite that, we will need to set |fd_| to -1 when Close() finished.
+ // So, here we set it to -1 first and if we own the old descriptor, close
+ // it before exiting.
+ std::swap(fd, fd_);
+ CancelPendingAsyncOperations();
+ return own_ ? IGNORE_EINTR(close(fd)) : 0;
+ }
+
+ bool WaitForData(Stream::AccessMode mode,
+ const DataCallback& data_callback,
+ ErrorPtr* error) override {
+ if (stream_utils::IsReadAccessMode(mode)) {
+ CHECK(read_data_callback_.is_null());
+ MessageLoop::current()->CancelTask(read_watcher_);
+ read_watcher_ = MessageLoop::current()->WatchFileDescriptor(
+ FROM_HERE,
+ fd_,
+ MessageLoop::WatchMode::kWatchRead,
+ false, // persistent
+ base::Bind(&FileDescriptor::OnFileCanReadWithoutBlocking,
+ base::Unretained(this)));
+ if (read_watcher_ == MessageLoop::kTaskIdNull) {
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kInvalidParameter,
+ "File descriptor doesn't support watching for reading.");
+ return false;
+ }
+ read_data_callback_ = data_callback;
+ }
+ if (stream_utils::IsWriteAccessMode(mode)) {
+ CHECK(write_data_callback_.is_null());
+ MessageLoop::current()->CancelTask(write_watcher_);
+ write_watcher_ = MessageLoop::current()->WatchFileDescriptor(
+ FROM_HERE,
+ fd_,
+ MessageLoop::WatchMode::kWatchWrite,
+ false, // persistent
+ base::Bind(&FileDescriptor::OnFileCanWriteWithoutBlocking,
+ base::Unretained(this)));
+ if (write_watcher_ == MessageLoop::kTaskIdNull) {
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kInvalidParameter,
+ "File descriptor doesn't support watching for writing.");
+ return false;
+ }
+ write_data_callback_ = data_callback;
+ }
+ return true;
+ }
+
+ int WaitForDataBlocking(Stream::AccessMode in_mode,
+ base::TimeDelta timeout,
+ Stream::AccessMode* out_mode) override {
+ fd_set read_fds;
+ fd_set write_fds;
+ fd_set error_fds;
+
+ FD_ZERO(&read_fds);
+ FD_ZERO(&write_fds);
+ FD_ZERO(&error_fds);
+
+ if (stream_utils::IsReadAccessMode(in_mode))
+ FD_SET(fd_, &read_fds);
+
+ if (stream_utils::IsWriteAccessMode(in_mode))
+ FD_SET(fd_, &write_fds);
+
+ FD_SET(fd_, &error_fds);
+ timeval timeout_val = {};
+ if (!timeout.is_max()) {
+ const timespec ts = timeout.ToTimeSpec();
+ TIMESPEC_TO_TIMEVAL(&timeout_val, &ts);
+ }
+ int res = HANDLE_EINTR(select(fd_ + 1, &read_fds, &write_fds, &error_fds,
+ timeout.is_max() ? nullptr : &timeout_val));
+ if (res > 0 && out_mode) {
+ *out_mode = stream_utils::MakeAccessMode(FD_ISSET(fd_, &read_fds),
+ FD_ISSET(fd_, &write_fds));
+ }
+ return res;
+ }
+
+ void CancelPendingAsyncOperations() override {
+ read_data_callback_.Reset();
+ if (read_watcher_ != MessageLoop::kTaskIdNull) {
+ MessageLoop::current()->CancelTask(read_watcher_);
+ read_watcher_ = MessageLoop::kTaskIdNull;
+ }
+
+ write_data_callback_.Reset();
+ if (write_watcher_ != MessageLoop::kTaskIdNull) {
+ MessageLoop::current()->CancelTask(write_watcher_);
+ write_watcher_ = MessageLoop::kTaskIdNull;
+ }
+ }
+
+ // Called from the brillo::MessageLoop when the file descriptor is available
+ // for reading.
+ void OnFileCanReadWithoutBlocking() {
+ CHECK(!read_data_callback_.is_null());
+ DataCallback cb = read_data_callback_;
+ read_data_callback_.Reset();
+ cb.Run(Stream::AccessMode::READ);
+ }
+
+ void OnFileCanWriteWithoutBlocking() {
+ CHECK(!write_data_callback_.is_null());
+ DataCallback cb = write_data_callback_;
+ write_data_callback_.Reset();
+ cb.Run(Stream::AccessMode::WRITE);
+ }
+
+ private:
+ // The actual file descriptor we are working with. Will contain -1 if the
+ // file stream has been closed.
+ int fd_;
+
+ // |own_| is set to true if the file stream owns the file descriptor |fd_| and
+ // must close it when the stream is closed. This will be false for file
+ // descriptors that shouldn't be closed (e.g. stdin, stdout, stderr).
+ bool own_;
+
+ // Stream callbacks to be called when read and/or write operations can be
+ // performed on the file descriptor without blocking.
+ DataCallback read_data_callback_;
+ DataCallback write_data_callback_;
+
+ // MessageLoop tasks monitoring read/write operations on the file descriptor.
+ MessageLoop::TaskId read_watcher_{MessageLoop::kTaskIdNull};
+ MessageLoop::TaskId write_watcher_{MessageLoop::kTaskIdNull};
+
+ DISALLOW_COPY_AND_ASSIGN(FileDescriptor);
+};
+
+StreamPtr FileStream::Open(const base::FilePath& path,
+ AccessMode mode,
+ Disposition disposition,
+ ErrorPtr* error) {
+ StreamPtr stream;
+ int open_flags = O_CLOEXEC;
+ switch (mode) {
+ case AccessMode::READ:
+ open_flags |= O_RDONLY;
+ break;
+ case AccessMode::WRITE:
+ open_flags |= O_WRONLY;
+ break;
+ case AccessMode::READ_WRITE:
+ open_flags |= O_RDWR;
+ break;
+ }
+
+ switch (disposition) {
+ case Disposition::OPEN_EXISTING:
+ // Nothing else to do.
+ break;
+ case Disposition::CREATE_ALWAYS:
+ open_flags |= O_CREAT | O_TRUNC;
+ break;
+ case Disposition::CREATE_NEW_ONLY:
+ open_flags |= O_CREAT | O_EXCL;
+ break;
+ case Disposition::TRUNCATE_EXISTING:
+ open_flags |= O_TRUNC;
+ break;
+ }
+
+ mode_t creation_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+ int fd = HANDLE_EINTR(open(path.value().c_str(), open_flags, creation_mode));
+ if (fd < 0) {
+ brillo::errors::system::AddSystemError(error, FROM_HERE, errno);
+ return stream;
+ }
+ if (HANDLE_EINTR(fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK)) < 0) {
+ brillo::errors::system::AddSystemError(error, FROM_HERE, errno);
+ IGNORE_EINTR(close(fd));
+ return stream;
+ }
+
+ std::unique_ptr<FileDescriptorInterface> fd_interface{
+ new FileDescriptor{fd, true}};
+
+ stream.reset(new FileStream{std::move(fd_interface), mode});
+ return stream;
+}
+
+StreamPtr FileStream::CreateTemporary(ErrorPtr* error) {
+ StreamPtr stream;
+ base::FilePath path;
+ // The "proper" solution would be here to add O_TMPFILE flag to |open_flags|
+ // below and pass just the temp directory path to open(), so the actual file
+ // name isn't even needed. However this is supported only as of Linux kernel
+ // 3.11 and not all our configurations have that. So, for now just create
+ // a temp file first and then open it.
+ if (!base::CreateTemporaryFile(&path)) {
+ brillo::errors::system::AddSystemError(error, FROM_HERE, errno);
+ return stream;
+ }
+ int open_flags = O_CLOEXEC | O_RDWR | O_CREAT | O_TRUNC;
+ mode_t creation_mode = S_IRUSR | S_IWUSR;
+ int fd = HANDLE_EINTR(open(path.value().c_str(), open_flags, creation_mode));
+ if (fd < 0) {
+ brillo::errors::system::AddSystemError(error, FROM_HERE, errno);
+ return stream;
+ }
+ unlink(path.value().c_str());
+
+ stream = FromFileDescriptor(fd, true, error);
+ if (!stream)
+ IGNORE_EINTR(close(fd));
+ return stream;
+}
+
+StreamPtr FileStream::FromFileDescriptor(int file_descriptor,
+ bool own_descriptor,
+ ErrorPtr* error) {
+ StreamPtr stream;
+ if (file_descriptor < 0 || file_descriptor >= FD_SETSIZE) {
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kInvalidParameter,
+ "Invalid file descriptor value");
+ return stream;
+ }
+
+ int fd_flags = HANDLE_EINTR(fcntl(file_descriptor, F_GETFL));
+ if (fd_flags < 0) {
+ brillo::errors::system::AddSystemError(error, FROM_HERE, errno);
+ return stream;
+ }
+ int file_access_mode = (fd_flags & O_ACCMODE);
+ AccessMode access_mode = AccessMode::READ_WRITE;
+ if (file_access_mode == O_RDONLY)
+ access_mode = AccessMode::READ;
+ else if (file_access_mode == O_WRONLY)
+ access_mode = AccessMode::WRITE;
+
+ // Make sure the file descriptor is set to perform non-blocking operations
+ // if not enabled already.
+ if ((fd_flags & O_NONBLOCK) == 0) {
+ fd_flags |= O_NONBLOCK;
+ if (HANDLE_EINTR(fcntl(file_descriptor, F_SETFL, fd_flags)) < 0) {
+ brillo::errors::system::AddSystemError(error, FROM_HERE, errno);
+ return stream;
+ }
+ }
+
+ std::unique_ptr<FileDescriptorInterface> fd_interface{
+ new FileDescriptor{file_descriptor, own_descriptor}};
+
+ stream.reset(new FileStream{std::move(fd_interface), access_mode});
+ return stream;
+}
+
+FileStream::FileStream(std::unique_ptr<FileDescriptorInterface> fd_interface,
+ AccessMode mode)
+ : fd_interface_(std::move(fd_interface)),
+ access_mode_(mode) {
+ switch (fd_interface_->GetFileMode() & S_IFMT) {
+ case S_IFCHR: // Character device
+ case S_IFSOCK: // Socket
+ case S_IFIFO: // FIFO/pipe
+ // We know that these devices are not seekable and stream size is unknown.
+ seekable_ = false;
+ can_get_size_ = false;
+ break;
+
+ case S_IFBLK: // Block device
+ case S_IFDIR: // Directory
+ case S_IFREG: // Normal file
+ case S_IFLNK: // Symbolic link
+ default:
+ // The above devices support seek. Also, if not sure/in doubt, err on the
+ // side of "allowable".
+ seekable_ = true;
+ can_get_size_ = true;
+ break;
+ }
+}
+
+bool FileStream::IsOpen() const {
+ return fd_interface_->IsOpen();
+}
+
+bool FileStream::CanRead() const {
+ return IsOpen() && stream_utils::IsReadAccessMode(access_mode_);
+}
+
+bool FileStream::CanWrite() const {
+ return IsOpen() && stream_utils::IsWriteAccessMode(access_mode_);
+}
+
+bool FileStream::CanSeek() const {
+ return IsOpen() && seekable_;
+}
+
+bool FileStream::CanGetSize() const {
+ return IsOpen() && can_get_size_;
+}
+
+uint64_t FileStream::GetSize() const {
+ return IsOpen() ? fd_interface_->GetSize() : 0;
+}
+
+bool FileStream::SetSizeBlocking(uint64_t size, ErrorPtr* error) {
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ if (!stream_utils::CheckInt64Overflow(FROM_HERE, size, 0, error))
+ return false;
+
+ if (fd_interface_->Truncate(size) >= 0)
+ return true;
+
+ errors::system::AddSystemError(error, FROM_HERE, errno);
+ return false;
+}
+
+uint64_t FileStream::GetRemainingSize() const {
+ if (!CanGetSize())
+ return 0;
+ uint64_t pos = GetPosition();
+ uint64_t size = GetSize();
+ return (pos < size) ? (size - pos) : 0;
+}
+
+uint64_t FileStream::GetPosition() const {
+ if (!CanSeek())
+ return 0;
+
+ off64_t pos = fd_interface_->Seek(0, SEEK_CUR);
+ const off64_t min_pos = 0;
+ return std::max(min_pos, pos);
+}
+
+bool FileStream::Seek(int64_t offset,
+ Whence whence,
+ uint64_t* new_position,
+ ErrorPtr* error) {
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ int raw_whence = 0;
+ switch (whence) {
+ case Whence::FROM_BEGIN:
+ raw_whence = SEEK_SET;
+ break;
+ case Whence::FROM_CURRENT:
+ raw_whence = SEEK_CUR;
+ break;
+ case Whence::FROM_END:
+ raw_whence = SEEK_END;
+ break;
+ default:
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kInvalidParameter, "Invalid whence");
+ return false;
+ }
+ off64_t pos = fd_interface_->Seek(offset, raw_whence);
+ if (pos < 0) {
+ errors::system::AddSystemError(error, FROM_HERE, errno);
+ return false;
+ }
+
+ if (new_position)
+ *new_position = static_cast<uint64_t>(pos);
+ return true;
+}
+
+bool FileStream::ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) {
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ ssize_t read = fd_interface_->Read(buffer, size_to_read);
+ if (read < 0) {
+ // If read() fails, check if this is due to no data being currently
+ // available and we do non-blocking I/O.
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ if (end_of_stream)
+ *end_of_stream = false;
+ *size_read = 0;
+ return true;
+ }
+ // Otherwise a real problem occurred.
+ errors::system::AddSystemError(error, FROM_HERE, errno);
+ return false;
+ }
+ if (end_of_stream)
+ *end_of_stream = (read == 0 && size_to_read != 0);
+ *size_read = read;
+ return true;
+}
+
+bool FileStream::WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) {
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ ssize_t written = fd_interface_->Write(buffer, size_to_write);
+ if (written < 0) {
+ // If write() fails, check if this is due to the fact that no data
+ // can be presently written and we do non-blocking I/O.
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ *size_written = 0;
+ return true;
+ }
+ // Otherwise a real problem occurred.
+ errors::system::AddSystemError(error, FROM_HERE, errno);
+ return false;
+ }
+ *size_written = written;
+ return true;
+}
+
+bool FileStream::FlushBlocking(ErrorPtr* error) {
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ // File descriptors don't have an internal buffer to flush.
+ return true;
+}
+
+bool FileStream::CloseBlocking(ErrorPtr* error) {
+ if (!IsOpen())
+ return true;
+
+ if (fd_interface_->Close() < 0) {
+ errors::system::AddSystemError(error, FROM_HERE, errno);
+ return false;
+ }
+
+ return true;
+}
+
+bool FileStream::WaitForData(
+ AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) {
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ return fd_interface_->WaitForData(mode, callback, error);
+}
+
+bool FileStream::WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) {
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ int ret = fd_interface_->WaitForDataBlocking(in_mode, timeout, out_mode);
+ if (ret < 0) {
+ errors::system::AddSystemError(error, FROM_HERE, errno);
+ return false;
+ }
+ if (ret == 0)
+ return stream_utils::ErrorOperationTimeout(FROM_HERE, error);
+
+ return true;
+}
+
+void FileStream::CancelPendingAsyncOperations() {
+ if (IsOpen()) {
+ fd_interface_->CancelPendingAsyncOperations();
+ }
+ Stream::CancelPendingAsyncOperations();
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/file_stream.h b/libbrillo/brillo/streams/file_stream.h
new file mode 100644
index 0000000..1cf39b5
--- /dev/null
+++ b/libbrillo/brillo/streams/file_stream.h
@@ -0,0 +1,175 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_FILE_STREAM_H_
+#define LIBBRILLO_BRILLO_STREAMS_FILE_STREAM_H_
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+
+// FileStream class provides the implementation of brillo::Stream for files
+// and file-descriptor-based streams, such as pipes and sockets.
+// The FileStream class cannot be instantiated by clients directly. However
+// they should use the static factory methods such as:
+// - FileStream::Open(): to open a file by name.
+// - FileStream::CreateTemporary(): to create a temporary file stream.
+// - FileStream::FromFileDescriptor(): to create a stream using an existing
+// file descriptor.
+class BRILLO_EXPORT FileStream : public Stream {
+ public:
+ // See comments for FileStream::Open() for detailed description of this enum.
+ enum class Disposition {
+ OPEN_EXISTING, // Open existing file only. Fail if doesn't exist.
+ CREATE_ALWAYS, // Create empty file, possibly overwriting existing file.
+ CREATE_NEW_ONLY, // Create new file if doesn't exist already.
+ TRUNCATE_EXISTING, // Open/truncate existing file. Fail if doesn't exist.
+ };
+
+ // Simple interface to wrap native library calls so that they can be mocked
+ // out for testing.
+ struct FileDescriptorInterface {
+ using DataCallback = base::Callback<void(Stream::AccessMode)>;
+
+ virtual ~FileDescriptorInterface() = default;
+
+ virtual bool IsOpen() const = 0;
+ virtual ssize_t Read(void* buf, size_t nbyte) = 0;
+ virtual ssize_t Write(const void* buf, size_t nbyte) = 0;
+ virtual off64_t Seek(off64_t offset, int whence) = 0;
+ virtual mode_t GetFileMode() const = 0;
+ virtual uint64_t GetSize() const = 0;
+ virtual int Truncate(off64_t length) const = 0;
+ virtual int Close() = 0;
+ virtual bool WaitForData(AccessMode mode,
+ const DataCallback& data_callback,
+ ErrorPtr* error) = 0;
+ virtual int WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode) = 0;
+ virtual void CancelPendingAsyncOperations() = 0;
+ };
+
+ // == Construction ==========================================================
+
+ // Opens a file at specified |path| for reading, writing or both as indicated
+ // by |mode|. The |disposition| specifies how the file must be opened/created:
+ // - OPEN_EXISTING - opens the existing file and keeps its content intact.
+ // The seek pointer is at the beginning of the file.
+ // - CREATE_ALWAYS - creates the file always. If it exists, the file is
+ // truncated.
+ // - CREATE_NEW_ONLY - creates a new file only if it doesn't exist. Fails
+ // otherwise. This can be useful for creating lock files.
+ // - TRUNCATE_EXISTING - opens existing file and truncates it to zero length.
+ // Fails if the file doesn't already exist.
+ // If successful, the open file stream is returned. Otherwise returns the
+ // stream pointer containing nullptr and fills in the details of the error
+ // in |error| object, if provided.
+ static StreamPtr Open(const base::FilePath& path,
+ AccessMode mode,
+ Disposition disposition,
+ ErrorPtr* error);
+
+ // Creates a temporary unnamed file and returns a stream to it. The file will
+ // be deleted when the stream is destroyed.
+ static StreamPtr CreateTemporary(ErrorPtr* error);
+
+ // Creates a file stream based on existing file descriptor. The file
+ // descriptor will be set into non-blocking mode and will be owned by the
+ // resulting stream (and closed when the stream is destroyed).
+ // If the function fails, returns a null stream pointer and sets the error
+ // details to |error| object. Also note that it is the caller's responsibility
+ // to close the file descriptor if this function fails, since the stream
+ // hasn't been created yet and didn't take ownership of the file descriptor.
+ // |own_descriptor| indicates whether the stream must close the underlying
+ // file descriptor when its CloseBlocking() method is called. This should be
+ // set to false for file descriptors that shouldn't be closed (e.g. stdin).
+ static StreamPtr FromFileDescriptor(int file_descriptor,
+ bool own_descriptor,
+ ErrorPtr* error);
+
+ // == Stream capabilities ===================================================
+ bool IsOpen() const override;
+ bool CanRead() const override;
+ bool CanWrite() const override;
+ bool CanSeek() const override;
+ bool CanGetSize() const override;
+
+ // == Stream size operations ================================================
+ uint64_t GetSize() const override;
+ bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override;
+ uint64_t GetRemainingSize() const override;
+
+ // == Seek operations =======================================================
+ uint64_t GetPosition() const override;
+ bool Seek(int64_t offset,
+ Whence whence,
+ uint64_t* new_position,
+ ErrorPtr* error) override;
+
+ // == Read operations =======================================================
+ bool ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) override;
+
+ // == Write operations ======================================================
+ bool WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) override;
+
+ // == Finalizing/closing streams ===========================================
+ bool FlushBlocking(ErrorPtr* error) override;
+ bool CloseBlocking(ErrorPtr* error) override;
+
+ // == Data availability monitoring ==========================================
+
+ // Override for Stream::WaitForData to start watching the associated file
+ // descriptor for non-blocking read/write operations.
+ bool WaitForData(AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) override;
+
+ // Runs select() on the file descriptor to wait until we can do non-blocking
+ // I/O on it.
+ bool WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) override;
+
+ // Cancels pending asynchronous read/write operations.
+ void CancelPendingAsyncOperations() override;
+
+ private:
+ friend class FileStreamTest;
+
+ // Internal constructor used by the factory methods Open(), CreateTemporary(),
+ // and FromFileDescriptor().
+ FileStream(std::unique_ptr<FileDescriptorInterface> fd_interface,
+ AccessMode mode);
+
+ // Wrapper for the file descriptor. Used in testing to mock out the real
+ // file system APIs.
+ std::unique_ptr<FileDescriptorInterface> fd_interface_;
+
+ // The access mode this stream is open with.
+ AccessMode access_mode_{AccessMode::READ_WRITE};
+
+ // Set to false for streams that are guaranteed non-seekable.
+ bool seekable_{true};
+
+ // Set to false for streams that have unknown size.
+ bool can_get_size_{false};
+
+ DISALLOW_COPY_AND_ASSIGN(FileStream);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_FILE_STREAM_H_
diff --git a/libbrillo/brillo/streams/file_stream_unittest.cc b/libbrillo/brillo/streams/file_stream_unittest.cc
new file mode 100644
index 0000000..4554ede
--- /dev/null
+++ b/libbrillo/brillo/streams/file_stream_unittest.cc
@@ -0,0 +1,1148 @@
+// Copyright 2015 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 <brillo/streams/file_stream.h>
+
+#include <limits>
+#include <numeric>
+#include <string>
+#include <sys/stat.h>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/message_loop/message_loop.h>
+#include <base/rand_util.h>
+#include <base/run_loop.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/errors/error_codes.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <brillo/streams/stream_errors.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::InSequence;
+using testing::Return;
+using testing::ReturnArg;
+using testing::SaveArg;
+using testing::SetErrnoAndReturn;
+using testing::_;
+
+namespace brillo {
+
+namespace {
+
+// gmock action that would return a blocking situation from a read() or write().
+ACTION(ReturnWouldBlock) {
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+// Helper function to read one byte from the stream.
+inline int ReadByte(Stream* stream) {
+ uint8_t byte = 0;
+ return stream->ReadAllBlocking(&byte, sizeof(byte), nullptr) ? byte : -1;
+}
+
+// Helper function to write one byte from the stream.
+inline bool WriteByte(Stream* stream, uint8_t byte) {
+ return stream->WriteAllBlocking(&byte, sizeof(byte), nullptr);
+}
+
+// Helper function to test file stream workflow on newly created file.
+void TestCreateFile(Stream* stream) {
+ ASSERT_TRUE(stream->IsOpen());
+
+ // Set up a sample data buffer.
+ std::vector<uint8_t> in_buffer(256);
+ std::iota(in_buffer.begin(), in_buffer.end(), 0);
+
+ // Initial assumptions about empty file stream.
+ EXPECT_TRUE(stream->CanRead());
+ EXPECT_TRUE(stream->CanWrite());
+ EXPECT_TRUE(stream->CanSeek());
+ EXPECT_TRUE(stream->CanGetSize());
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(0, stream->GetSize());
+
+ // Write sample data.
+ EXPECT_TRUE(stream->WriteAllBlocking(in_buffer.data(), in_buffer.size(),
+ nullptr));
+ EXPECT_EQ(in_buffer.size(), stream->GetPosition());
+ EXPECT_EQ(in_buffer.size(), stream->GetSize());
+
+ // Rewind the stream to the beginning.
+ uint64_t pos = 0;
+ EXPECT_TRUE(stream->Seek(0, Stream::Whence::FROM_BEGIN, &pos, nullptr));
+ EXPECT_EQ(0, pos);
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(in_buffer.size(), stream->GetSize());
+
+ // Read the file contents back.
+ std::vector<uint8_t> out_buffer(256);
+ EXPECT_TRUE(stream->ReadAllBlocking(out_buffer.data(), out_buffer.size(),
+ nullptr));
+ EXPECT_EQ(out_buffer.size(), stream->GetPosition());
+ EXPECT_EQ(out_buffer.size(), stream->GetSize());
+
+ // Make sure the data read matches those written.
+ EXPECT_EQ(in_buffer, out_buffer);
+
+ // Random read/write
+ EXPECT_TRUE(stream->Seek(10, Stream::Whence::FROM_BEGIN, &pos, nullptr));
+ EXPECT_EQ(10, pos);
+
+ // Since our data buffer contained values from 0 to 255, the byte at position
+ // 10 will contain the value of 10.
+ EXPECT_EQ(10, ReadByte(stream));
+ EXPECT_EQ(11, ReadByte(stream));
+ EXPECT_EQ(12, ReadByte(stream));
+ EXPECT_TRUE(stream->Seek(7, Stream::Whence::FROM_CURRENT, nullptr, nullptr));
+ EXPECT_EQ(20, ReadByte(stream));
+
+ EXPECT_EQ(21, stream->GetPosition());
+ EXPECT_TRUE(stream->Seek(-2, Stream::Whence::FROM_CURRENT, &pos, nullptr));
+ EXPECT_EQ(19, pos);
+ EXPECT_TRUE(WriteByte(stream, 100));
+ EXPECT_EQ(20, ReadByte(stream));
+ EXPECT_TRUE(stream->Seek(-2, Stream::Whence::FROM_CURRENT, nullptr, nullptr));
+ EXPECT_EQ(100, ReadByte(stream));
+ EXPECT_EQ(20, ReadByte(stream));
+ EXPECT_TRUE(stream->Seek(-1, Stream::Whence::FROM_END, &pos, nullptr));
+ EXPECT_EQ(255, pos);
+ EXPECT_EQ(255, ReadByte(stream));
+ EXPECT_EQ(-1, ReadByte(stream));
+}
+
+} // anonymous namespace
+
+// A mock file descriptor wrapper to test low-level file API used by FileStream.
+class MockFileDescriptor : public FileStream::FileDescriptorInterface {
+ public:
+ MOCK_CONST_METHOD0(IsOpen, bool());
+ MOCK_METHOD2(Read, ssize_t(void*, size_t));
+ MOCK_METHOD2(Write, ssize_t(const void*, size_t));
+ MOCK_METHOD2(Seek, off64_t(off64_t, int));
+ MOCK_CONST_METHOD0(GetFileMode, mode_t());
+ MOCK_CONST_METHOD0(GetSize, uint64_t());
+ MOCK_CONST_METHOD1(Truncate, int(off64_t));
+ MOCK_METHOD0(Flush, int());
+ MOCK_METHOD0(Close, int());
+ MOCK_METHOD3(WaitForData,
+ bool(Stream::AccessMode, const DataCallback&, ErrorPtr*));
+ MOCK_METHOD3(WaitForDataBlocking,
+ int(Stream::AccessMode, base::TimeDelta, Stream::AccessMode*));
+ MOCK_METHOD0(CancelPendingAsyncOperations, void());
+};
+
+class FileStreamTest : public testing::Test {
+ public:
+ void SetUp() override {
+ CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE);
+ }
+
+ MockFileDescriptor& fd_mock() {
+ return *static_cast<MockFileDescriptor*>(stream_->fd_interface_.get());
+ }
+
+ void CreateStream(mode_t file_mode, Stream::AccessMode access_mode) {
+ std::unique_ptr<MockFileDescriptor> fd{new MockFileDescriptor{}};
+ EXPECT_CALL(*fd, GetFileMode()).WillOnce(Return(file_mode));
+ stream_.reset(new FileStream(std::move(fd), access_mode));
+ EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(true));
+ }
+
+ void ExpectStreamClosed(const ErrorPtr& error) const {
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kStreamClosed, error->GetCode());
+ EXPECT_EQ("Stream is closed", error->GetMessage());
+ }
+
+ void ExpectStreamOffsetTooLarge(const ErrorPtr& error) const {
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode());
+ EXPECT_EQ("The stream offset value is out of range", error->GetMessage());
+ }
+
+ inline static char* IntToPtr(int addr) {
+ return reinterpret_cast<char*>(addr);
+ }
+
+ inline static const char* IntToConstPtr(int addr) {
+ return reinterpret_cast<const char*>(addr);
+ }
+
+ bool CallWaitForData(Stream::AccessMode mode, ErrorPtr* error) {
+ return stream_->WaitForData(mode, {}, error);
+ }
+
+ std::unique_ptr<FileStream> stream_;
+
+ const uint64_t kMaxSize = std::numeric_limits<int64_t>::max();
+ const uint64_t kTooLargeSize = std::numeric_limits<uint64_t>::max();
+
+ // Dummy buffer pointer values to make sure that input pointer values
+ // are delegated to the file interface without a change.
+ char* const test_read_buffer_ = IntToPtr(12345);
+ const char* const test_write_buffer_ = IntToConstPtr(67890);
+};
+
+TEST_F(FileStreamTest, IsOpen) {
+ EXPECT_TRUE(stream_->IsOpen());
+ EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false));
+ EXPECT_FALSE(stream_->IsOpen());
+}
+
+TEST_F(FileStreamTest, CanRead) {
+ CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->CanRead());
+ EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(false));
+ EXPECT_FALSE(stream_->CanRead());
+ CreateStream(S_IFREG, Stream::AccessMode::READ);
+ EXPECT_TRUE(stream_->CanRead());
+ CreateStream(S_IFREG, Stream::AccessMode::WRITE);
+ EXPECT_FALSE(stream_->CanRead());
+}
+
+TEST_F(FileStreamTest, CanWrite) {
+ CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->CanWrite());
+ EXPECT_CALL(fd_mock(), IsOpen()).WillRepeatedly(Return(false));
+ EXPECT_FALSE(stream_->CanWrite());
+ CreateStream(S_IFREG, Stream::AccessMode::READ);
+ EXPECT_FALSE(stream_->CanWrite());
+ CreateStream(S_IFREG, Stream::AccessMode::WRITE);
+ EXPECT_TRUE(stream_->CanWrite());
+}
+
+TEST_F(FileStreamTest, CanSeek) {
+ CreateStream(S_IFBLK, Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->CanSeek());
+ CreateStream(S_IFDIR, Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->CanSeek());
+ CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->CanSeek());
+ CreateStream(S_IFLNK, Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->CanSeek());
+ CreateStream(S_IFCHR, Stream::AccessMode::READ_WRITE);
+ EXPECT_FALSE(stream_->CanSeek());
+ CreateStream(S_IFSOCK, Stream::AccessMode::READ_WRITE);
+ EXPECT_FALSE(stream_->CanSeek());
+ CreateStream(S_IFIFO, Stream::AccessMode::READ_WRITE);
+ EXPECT_FALSE(stream_->CanSeek());
+
+ CreateStream(S_IFREG, Stream::AccessMode::READ);
+ EXPECT_TRUE(stream_->CanSeek());
+ CreateStream(S_IFREG, Stream::AccessMode::WRITE);
+ EXPECT_TRUE(stream_->CanSeek());
+}
+
+TEST_F(FileStreamTest, CanGetSize) {
+ CreateStream(S_IFBLK, Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->CanGetSize());
+ CreateStream(S_IFDIR, Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->CanGetSize());
+ CreateStream(S_IFREG, Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->CanGetSize());
+ CreateStream(S_IFLNK, Stream::AccessMode::READ_WRITE);
+ EXPECT_TRUE(stream_->CanGetSize());
+ CreateStream(S_IFCHR, Stream::AccessMode::READ_WRITE);
+ EXPECT_FALSE(stream_->CanGetSize());
+ CreateStream(S_IFSOCK, Stream::AccessMode::READ_WRITE);
+ EXPECT_FALSE(stream_->CanGetSize());
+ CreateStream(S_IFIFO, Stream::AccessMode::READ_WRITE);
+ EXPECT_FALSE(stream_->CanGetSize());
+
+ CreateStream(S_IFREG, Stream::AccessMode::READ);
+ EXPECT_TRUE(stream_->CanGetSize());
+ CreateStream(S_IFREG, Stream::AccessMode::WRITE);
+ EXPECT_TRUE(stream_->CanGetSize());
+}
+
+TEST_F(FileStreamTest, GetSize) {
+ EXPECT_CALL(fd_mock(), GetSize()).WillRepeatedly(Return(12345));
+ EXPECT_EQ(12345u, stream_->GetSize());
+ EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false));
+ EXPECT_EQ(0u, stream_->GetSize());
+}
+
+TEST_F(FileStreamTest, SetSizeBlocking) {
+ EXPECT_CALL(fd_mock(), Truncate(0)).WillOnce(Return(0));
+ EXPECT_TRUE(stream_->SetSizeBlocking(0, nullptr));
+
+ EXPECT_CALL(fd_mock(), Truncate(123)).WillOnce(Return(0));
+ EXPECT_TRUE(stream_->SetSizeBlocking(123, nullptr));
+
+ EXPECT_CALL(fd_mock(), Truncate(kMaxSize)).WillOnce(Return(0));
+ EXPECT_TRUE(stream_->SetSizeBlocking(kMaxSize, nullptr));
+}
+
+TEST_F(FileStreamTest, SetSizeBlocking_Fail) {
+ brillo::ErrorPtr error;
+
+ EXPECT_CALL(fd_mock(), Truncate(1235)).WillOnce(SetErrnoAndReturn(EIO, -1));
+ EXPECT_FALSE(stream_->SetSizeBlocking(1235, &error));
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("EIO", error->GetCode());
+
+ error.reset();
+ EXPECT_FALSE(stream_->SetSizeBlocking(kTooLargeSize, &error));
+ ExpectStreamOffsetTooLarge(error);
+
+ error.reset();
+ EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false));
+ EXPECT_FALSE(stream_->SetSizeBlocking(1235, &error));
+ ExpectStreamClosed(error);
+}
+
+TEST_F(FileStreamTest, GetRemainingSize) {
+ EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(234));
+ EXPECT_CALL(fd_mock(), GetSize()).WillOnce(Return(1234));
+ EXPECT_EQ(1000u, stream_->GetRemainingSize());
+
+ EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(1234));
+ EXPECT_CALL(fd_mock(), GetSize()).WillOnce(Return(1000));
+ EXPECT_EQ(0u, stream_->GetRemainingSize());
+}
+
+TEST_F(FileStreamTest, Seek_Set) {
+ uint64_t pos = 0;
+
+ EXPECT_CALL(fd_mock(), Seek(0, SEEK_SET)).WillOnce(Return(0));
+ EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, &pos, nullptr));
+ EXPECT_EQ(0u, pos);
+
+ EXPECT_CALL(fd_mock(), Seek(123456, SEEK_SET)).WillOnce(Return(123456));
+ EXPECT_TRUE(stream_->Seek(123456, Stream::Whence::FROM_BEGIN, &pos, nullptr));
+ EXPECT_EQ(123456u, pos);
+
+ EXPECT_CALL(fd_mock(), Seek(kMaxSize, SEEK_SET))
+ .WillRepeatedly(Return(kMaxSize));
+ EXPECT_TRUE(stream_->Seek(kMaxSize, Stream::Whence::FROM_BEGIN, &pos,
+ nullptr));
+ EXPECT_EQ(kMaxSize, pos);
+ EXPECT_TRUE(stream_->Seek(kMaxSize, Stream::Whence::FROM_BEGIN, nullptr,
+ nullptr));
+}
+
+TEST_F(FileStreamTest, Seek_Cur) {
+ uint64_t pos = 0;
+
+ EXPECT_CALL(fd_mock(), Seek(0, SEEK_CUR)).WillOnce(Return(100));
+ EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_CURRENT, &pos, nullptr));
+ EXPECT_EQ(100u, pos);
+
+ EXPECT_CALL(fd_mock(), Seek(234, SEEK_CUR)).WillOnce(Return(1234));
+ EXPECT_TRUE(stream_->Seek(234, Stream::Whence::FROM_CURRENT, &pos, nullptr));
+ EXPECT_EQ(1234u, pos);
+
+ EXPECT_CALL(fd_mock(), Seek(-100, SEEK_CUR)).WillOnce(Return(900));
+ EXPECT_TRUE(stream_->Seek(-100, Stream::Whence::FROM_CURRENT, &pos, nullptr));
+ EXPECT_EQ(900u, pos);
+}
+
+TEST_F(FileStreamTest, Seek_End) {
+ uint64_t pos = 0;
+
+ EXPECT_CALL(fd_mock(), Seek(0, SEEK_END)).WillOnce(Return(1000));
+ EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_END, &pos, nullptr));
+ EXPECT_EQ(1000u, pos);
+
+ EXPECT_CALL(fd_mock(), Seek(234, SEEK_END)).WillOnce(Return(10234));
+ EXPECT_TRUE(stream_->Seek(234, Stream::Whence::FROM_END, &pos, nullptr));
+ EXPECT_EQ(10234u, pos);
+
+ EXPECT_CALL(fd_mock(), Seek(-100, SEEK_END)).WillOnce(Return(9900));
+ EXPECT_TRUE(stream_->Seek(-100, Stream::Whence::FROM_END, &pos, nullptr));
+ EXPECT_EQ(9900u, pos);
+}
+
+TEST_F(FileStreamTest, Seek_Fail) {
+ brillo::ErrorPtr error;
+ EXPECT_CALL(fd_mock(), Seek(0, SEEK_SET))
+ .WillOnce(SetErrnoAndReturn(EPIPE, -1));
+ EXPECT_FALSE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, nullptr, &error));
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("EPIPE", error->GetCode());
+}
+
+TEST_F(FileStreamTest, ReadAsync) {
+ size_t read_size = 0;
+ bool failed = false;
+ auto success_callback = [](size_t* read_size, size_t size) {
+ *read_size = size;
+ };
+ auto error_callback = [](bool* failed, const Error* /* error */) {
+ *failed = true;
+ };
+ FileStream::FileDescriptorInterface::DataCallback data_callback;
+
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100))
+ .WillOnce(ReturnWouldBlock());
+ EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ, _, _))
+ .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
+ EXPECT_TRUE(stream_->ReadAsync(
+ test_read_buffer_,
+ 100,
+ base::Bind(success_callback, base::Unretained(&read_size)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ nullptr));
+ EXPECT_EQ(0u, read_size);
+ EXPECT_FALSE(failed);
+
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(83));
+ data_callback.Run(Stream::AccessMode::READ);
+ EXPECT_EQ(83u, read_size);
+ EXPECT_FALSE(failed);
+}
+
+TEST_F(FileStreamTest, ReadNonBlocking) {
+ size_t size = 0;
+ bool eos = false;
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _))
+ .WillRepeatedly(ReturnArg<1>());
+ EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos,
+ nullptr));
+ EXPECT_EQ(100u, size);
+ EXPECT_FALSE(eos);
+
+ EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 0, &size, &eos,
+ nullptr));
+ EXPECT_EQ(0u, size);
+ EXPECT_FALSE(eos);
+
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _)).WillOnce(Return(0));
+ EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos,
+ nullptr));
+ EXPECT_EQ(0u, size);
+ EXPECT_TRUE(eos);
+
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, &eos,
+ nullptr));
+ EXPECT_EQ(0u, size);
+ EXPECT_FALSE(eos);
+}
+
+TEST_F(FileStreamTest, ReadNonBlocking_Fail) {
+ size_t size = 0;
+ brillo::ErrorPtr error;
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, _))
+ .WillOnce(SetErrnoAndReturn(EACCES, -1));
+ EXPECT_FALSE(stream_->ReadNonBlocking(test_read_buffer_, 100, &size, nullptr,
+ &error));
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("EACCES", error->GetCode());
+}
+
+TEST_F(FileStreamTest, ReadBlocking) {
+ size_t size = 0;
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20));
+ EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 100, &size, nullptr));
+ EXPECT_EQ(20u, size);
+
+ {
+ InSequence seq;
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _))
+ .WillOnce(Return(1));
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80)).WillOnce(Return(45));
+ }
+ EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 80, &size, nullptr));
+ EXPECT_EQ(45u, size);
+
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 50)).WillOnce(Return(0));
+ EXPECT_TRUE(stream_->ReadBlocking(test_read_buffer_, 50, &size, nullptr));
+ EXPECT_EQ(0u, size);
+}
+
+TEST_F(FileStreamTest, ReadBlocking_Fail) {
+ {
+ InSequence seq;
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 80))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _))
+ .WillOnce(SetErrnoAndReturn(EBADF, -1));
+ }
+ brillo::ErrorPtr error;
+ size_t size = 0;
+ EXPECT_FALSE(stream_->ReadBlocking(test_read_buffer_, 80, &size, &error));
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("EBADF", error->GetCode());
+}
+
+TEST_F(FileStreamTest, ReadAllBlocking) {
+ {
+ InSequence seq;
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20));
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _))
+ .WillOnce(Return(1));
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80))
+ .WillOnce(Return(45));
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _))
+ .WillOnce(Return(1));
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::READ, _, _))
+ .WillOnce(Return(1));
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 65, 35))
+ .WillOnce(Return(35));
+ }
+ EXPECT_TRUE(stream_->ReadAllBlocking(test_read_buffer_, 100, nullptr));
+}
+
+TEST_F(FileStreamTest, ReadAllBlocking_Fail) {
+ {
+ InSequence seq;
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_, 100)).WillOnce(Return(20));
+ EXPECT_CALL(fd_mock(), Read(test_read_buffer_ + 20, 80))
+ .WillOnce(Return(0));
+ }
+ brillo::ErrorPtr error;
+ EXPECT_FALSE(stream_->ReadAllBlocking(test_read_buffer_, 100, &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kPartialData, error->GetCode());
+ EXPECT_EQ("Reading past the end of stream", error->GetMessage());
+}
+
+TEST_F(FileStreamTest, WriteAsync) {
+ size_t write_size = 0;
+ bool failed = false;
+ auto success_callback = [](size_t* write_size, size_t size) {
+ *write_size = size;
+ };
+ auto error_callback = [](bool* failed, const Error* /* error */) {
+ *failed = true;
+ };
+ FileStream::FileDescriptorInterface::DataCallback data_callback;
+
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100))
+ .WillOnce(ReturnWouldBlock());
+ EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::WRITE, _, _))
+ .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
+ EXPECT_TRUE(stream_->WriteAsync(
+ test_write_buffer_,
+ 100,
+ base::Bind(success_callback, base::Unretained(&write_size)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ nullptr));
+ EXPECT_EQ(0u, write_size);
+ EXPECT_FALSE(failed);
+
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(87));
+ data_callback.Run(Stream::AccessMode::WRITE);
+ EXPECT_EQ(87u, write_size);
+ EXPECT_FALSE(failed);
+}
+
+TEST_F(FileStreamTest, WriteNonBlocking) {
+ size_t size = 0;
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _))
+ .WillRepeatedly(ReturnArg<1>());
+ EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size,
+ nullptr));
+ EXPECT_EQ(100u, size);
+
+ EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 0, &size, nullptr));
+ EXPECT_EQ(0u, size);
+
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _)).WillOnce(Return(0));
+ EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size,
+ nullptr));
+ EXPECT_EQ(0u, size);
+
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size,
+ nullptr));
+ EXPECT_EQ(0u, size);
+}
+
+TEST_F(FileStreamTest, WriteNonBlocking_Fail) {
+ size_t size = 0;
+ brillo::ErrorPtr error;
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, _))
+ .WillOnce(SetErrnoAndReturn(EACCES, -1));
+ EXPECT_FALSE(stream_->WriteNonBlocking(test_write_buffer_, 100, &size,
+ &error));
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("EACCES", error->GetCode());
+}
+
+TEST_F(FileStreamTest, WriteBlocking) {
+ size_t size = 0;
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(20));
+ EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 100, &size, nullptr));
+ EXPECT_EQ(20u, size);
+
+ {
+ InSequence seq;
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _))
+ .WillOnce(Return(1));
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80)).WillOnce(Return(45));
+ }
+ EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 80, &size, nullptr));
+ EXPECT_EQ(45u, size);
+
+ {
+ InSequence seq;
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 50)).WillOnce(Return(0));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _))
+ .WillOnce(Return(1));
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 50)).WillOnce(Return(1));
+ }
+ EXPECT_TRUE(stream_->WriteBlocking(test_write_buffer_, 50, &size, nullptr));
+ EXPECT_EQ(1u, size);
+}
+
+TEST_F(FileStreamTest, WriteBlocking_Fail) {
+ {
+ InSequence seq;
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _))
+ .WillOnce(SetErrnoAndReturn(EBADF, -1));
+ }
+ brillo::ErrorPtr error;
+ size_t size = 0;
+ EXPECT_FALSE(stream_->WriteBlocking(test_write_buffer_, 80, &size, &error));
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("EBADF", error->GetCode());
+}
+
+TEST_F(FileStreamTest, WriteAllBlocking) {
+ {
+ InSequence seq;
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 100)).WillOnce(Return(20));
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 20, 80))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _))
+ .WillOnce(Return(1));
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 20, 80))
+ .WillOnce(Return(45));
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _))
+ .WillOnce(Return(1));
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _))
+ .WillOnce(Return(1));
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_ + 65, 35))
+ .WillOnce(Return(35));
+ }
+ EXPECT_TRUE(stream_->WriteAllBlocking(test_write_buffer_, 100, nullptr));
+}
+
+TEST_F(FileStreamTest, WriteAllBlocking_Fail) {
+ {
+ InSequence seq;
+ EXPECT_CALL(fd_mock(), Write(test_write_buffer_, 80))
+ .WillOnce(SetErrnoAndReturn(EAGAIN, -1));
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _))
+ .WillOnce(SetErrnoAndReturn(EBADF, -1));
+ }
+ brillo::ErrorPtr error;
+ EXPECT_FALSE(stream_->WriteAllBlocking(test_write_buffer_, 80, &error));
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("EBADF", error->GetCode());
+}
+
+TEST_F(FileStreamTest, WaitForDataBlocking_Timeout) {
+ EXPECT_CALL(fd_mock(), WaitForDataBlocking(Stream::AccessMode::WRITE, _, _))
+ .WillOnce(Return(0));
+ brillo::ErrorPtr error;
+ EXPECT_FALSE(stream_->WaitForDataBlocking(Stream::AccessMode::WRITE, {},
+ nullptr, &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kTimeout, error->GetCode());
+}
+
+TEST_F(FileStreamTest, FlushBlocking) {
+ EXPECT_TRUE(stream_->FlushBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, CloseBlocking) {
+ EXPECT_CALL(fd_mock(), Close()).WillOnce(Return(0));
+ EXPECT_TRUE(stream_->CloseBlocking(nullptr));
+
+ EXPECT_CALL(fd_mock(), IsOpen()).WillOnce(Return(false));
+ EXPECT_TRUE(stream_->CloseBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, CloseBlocking_Fail) {
+ brillo::ErrorPtr error;
+ EXPECT_CALL(fd_mock(), Close()).WillOnce(SetErrnoAndReturn(EFBIG, -1));
+ EXPECT_FALSE(stream_->CloseBlocking(&error));
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("EFBIG", error->GetCode());
+}
+
+TEST_F(FileStreamTest, WaitForData) {
+ EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ, _, _))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(CallWaitForData(Stream::AccessMode::READ, nullptr));
+
+ EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::WRITE, _, _))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(CallWaitForData(Stream::AccessMode::WRITE, nullptr));
+
+ EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ_WRITE, _, _))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(CallWaitForData(Stream::AccessMode::READ_WRITE, nullptr));
+
+ EXPECT_CALL(fd_mock(), WaitForData(Stream::AccessMode::READ_WRITE, _, _))
+ .WillOnce(Return(false));
+ EXPECT_FALSE(CallWaitForData(Stream::AccessMode::READ_WRITE, nullptr));
+}
+
+TEST_F(FileStreamTest, CreateTemporary) {
+ StreamPtr stream = FileStream::CreateTemporary(nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ TestCreateFile(stream.get());
+}
+
+TEST_F(FileStreamTest, OpenRead) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"});
+ std::vector<char> buffer(1024 * 1024);
+ base::RandBytes(buffer.data(), buffer.size());
+ int file_size = buffer.size(); // Stupid base::WriteFile taking "int" size.
+ ASSERT_EQ(file_size, base::WriteFile(path, buffer.data(), file_size));
+
+ StreamPtr stream = FileStream::Open(path,
+ Stream::AccessMode::READ,
+ FileStream::Disposition::OPEN_EXISTING,
+ nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ ASSERT_TRUE(stream->IsOpen());
+ EXPECT_TRUE(stream->CanRead());
+ EXPECT_FALSE(stream->CanWrite());
+ EXPECT_TRUE(stream->CanSeek());
+ EXPECT_TRUE(stream->CanGetSize());
+ EXPECT_EQ(0u, stream->GetPosition());
+ EXPECT_EQ(buffer.size(), stream->GetSize());
+
+ std::vector<char> buffer2(buffer.size());
+ EXPECT_TRUE(stream->ReadAllBlocking(buffer2.data(), buffer2.size(), nullptr));
+ EXPECT_EQ(buffer2, buffer);
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, OpenWrite) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"});
+ std::vector<char> buffer(1024 * 1024);
+ base::RandBytes(buffer.data(), buffer.size());
+
+ StreamPtr stream = FileStream::Open(path,
+ Stream::AccessMode::WRITE,
+ FileStream::Disposition::CREATE_ALWAYS,
+ nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ ASSERT_TRUE(stream->IsOpen());
+ EXPECT_FALSE(stream->CanRead());
+ EXPECT_TRUE(stream->CanWrite());
+ EXPECT_TRUE(stream->CanSeek());
+ EXPECT_TRUE(stream->CanGetSize());
+ EXPECT_EQ(0u, stream->GetPosition());
+ EXPECT_EQ(0u, stream->GetSize());
+
+ EXPECT_TRUE(stream->WriteAllBlocking(buffer.data(), buffer.size(), nullptr));
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+
+ std::vector<char> buffer2(buffer.size());
+ int file_size = buffer2.size(); // Stupid base::ReadFile taking "int" size.
+ ASSERT_EQ(file_size, base::ReadFile(path, buffer2.data(), file_size));
+ EXPECT_EQ(buffer2, buffer);
+}
+
+TEST_F(FileStreamTest, Open_OpenExisting) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"});
+ std::string data{"Lorem ipsum dolor sit amet ..."};
+ int data_size = data.size(); // I hate ints for data size...
+ ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size));
+
+ StreamPtr stream = FileStream::Open(path,
+ Stream::AccessMode::READ_WRITE,
+ FileStream::Disposition::OPEN_EXISTING,
+ nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ EXPECT_TRUE(stream->CanRead());
+ EXPECT_TRUE(stream->CanWrite());
+ EXPECT_TRUE(stream->CanSeek());
+ EXPECT_TRUE(stream->CanGetSize());
+ EXPECT_EQ(0u, stream->GetPosition());
+ EXPECT_EQ(data.size(), stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, Open_OpenExisting_Fail) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"});
+
+ ErrorPtr error;
+ StreamPtr stream = FileStream::Open(path,
+ Stream::AccessMode::READ_WRITE,
+ FileStream::Disposition::OPEN_EXISTING,
+ &error);
+ ASSERT_EQ(nullptr, stream.get());
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("ENOENT", error->GetCode());
+}
+
+TEST_F(FileStreamTest, Open_CreateAlways_New) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"});
+
+ StreamPtr stream = FileStream::Open(path,
+ Stream::AccessMode::READ_WRITE,
+ FileStream::Disposition::CREATE_ALWAYS,
+ nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ EXPECT_TRUE(stream->CanRead());
+ EXPECT_TRUE(stream->CanWrite());
+ EXPECT_TRUE(stream->CanSeek());
+ EXPECT_TRUE(stream->CanGetSize());
+ EXPECT_EQ(0u, stream->GetPosition());
+ EXPECT_EQ(0u, stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, Open_CreateAlways_Existing) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"});
+ std::string data{"Lorem ipsum dolor sit amet ..."};
+ int data_size = data.size(); // I hate ints for data size...
+ ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size));
+
+ StreamPtr stream = FileStream::Open(path,
+ Stream::AccessMode::READ_WRITE,
+ FileStream::Disposition::CREATE_ALWAYS,
+ nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ EXPECT_TRUE(stream->CanRead());
+ EXPECT_TRUE(stream->CanWrite());
+ EXPECT_TRUE(stream->CanSeek());
+ EXPECT_TRUE(stream->CanGetSize());
+ EXPECT_EQ(0u, stream->GetPosition());
+ EXPECT_EQ(0u, stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, Open_CreateNewOnly_New) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"});
+
+ StreamPtr stream = FileStream::Open(path,
+ Stream::AccessMode::READ_WRITE,
+ FileStream::Disposition::CREATE_NEW_ONLY,
+ nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ EXPECT_TRUE(stream->CanRead());
+ EXPECT_TRUE(stream->CanWrite());
+ EXPECT_TRUE(stream->CanSeek());
+ EXPECT_TRUE(stream->CanGetSize());
+ EXPECT_EQ(0u, stream->GetPosition());
+ EXPECT_EQ(0u, stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, Open_CreateNewOnly_Existing) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"});
+ std::string data{"Lorem ipsum dolor sit amet ..."};
+ int data_size = data.size(); // I hate ints for data size...
+ ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size));
+
+ ErrorPtr error;
+ StreamPtr stream = FileStream::Open(path,
+ Stream::AccessMode::READ_WRITE,
+ FileStream::Disposition::CREATE_NEW_ONLY,
+ &error);
+ ASSERT_EQ(nullptr, stream.get());
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("EEXIST", error->GetCode());
+}
+
+TEST_F(FileStreamTest, Open_TruncateExisting_New) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"});
+
+ ErrorPtr error;
+ StreamPtr stream = FileStream::Open(
+ path,
+ Stream::AccessMode::READ_WRITE,
+ FileStream::Disposition::TRUNCATE_EXISTING,
+ &error);
+ ASSERT_EQ(nullptr, stream.get());
+ EXPECT_EQ(errors::system::kDomain, error->GetDomain());
+ EXPECT_EQ("ENOENT", error->GetCode());
+}
+
+TEST_F(FileStreamTest, Open_TruncateExisting_Existing) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.path().Append(base::FilePath{"test.dat"});
+ std::string data{"Lorem ipsum dolor sit amet ..."};
+ int data_size = data.size(); // I hate ints for data size...
+ ASSERT_EQ(data_size, base::WriteFile(path, data.data(), data_size));
+
+ StreamPtr stream = FileStream::Open(
+ path,
+ Stream::AccessMode::READ_WRITE,
+ FileStream::Disposition::TRUNCATE_EXISTING,
+ nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ EXPECT_TRUE(stream->CanRead());
+ EXPECT_TRUE(stream->CanWrite());
+ EXPECT_TRUE(stream->CanSeek());
+ EXPECT_TRUE(stream->CanGetSize());
+ EXPECT_EQ(0u, stream->GetPosition());
+ EXPECT_EQ(0u, stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, FromFileDescriptor_StdIn) {
+ StreamPtr stream =
+ FileStream::FromFileDescriptor(STDIN_FILENO, false, nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ EXPECT_TRUE(stream->IsOpen());
+ EXPECT_TRUE(stream->CanRead());
+ EXPECT_FALSE(stream->CanSeek());
+ EXPECT_FALSE(stream->CanGetSize());
+}
+
+TEST_F(FileStreamTest, FromFileDescriptor_StdOut) {
+ StreamPtr stream =
+ FileStream::FromFileDescriptor(STDOUT_FILENO, false, nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ EXPECT_TRUE(stream->IsOpen());
+ EXPECT_TRUE(stream->CanWrite());
+ EXPECT_FALSE(stream->CanSeek());
+ EXPECT_FALSE(stream->CanGetSize());
+}
+
+TEST_F(FileStreamTest, FromFileDescriptor_StdErr) {
+ StreamPtr stream =
+ FileStream::FromFileDescriptor(STDERR_FILENO, false, nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ EXPECT_TRUE(stream->IsOpen());
+ EXPECT_TRUE(stream->CanWrite());
+ EXPECT_FALSE(stream->CanSeek());
+ EXPECT_FALSE(stream->CanGetSize());
+}
+
+TEST_F(FileStreamTest, FromFileDescriptor_ReadNonBlocking) {
+ int fds[2] = {-1, -1};
+ ASSERT_EQ(0, pipe(fds));
+
+ StreamPtr stream = FileStream::FromFileDescriptor(fds[0], true, nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ EXPECT_TRUE(stream->IsOpen());
+ EXPECT_TRUE(stream->CanRead());
+ EXPECT_FALSE(stream->CanWrite());
+ EXPECT_FALSE(stream->CanSeek());
+ EXPECT_FALSE(stream->CanGetSize());
+
+ char buf[10];
+ size_t read = 0;
+ bool eos = true;
+ EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr));
+ EXPECT_EQ(0, read);
+ EXPECT_FALSE(eos);
+
+ std::string data{"foo_bar"};
+ EXPECT_TRUE(base::WriteFileDescriptor(fds[1], data.data(), data.size()));
+ EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr));
+ EXPECT_EQ(data.size(), read);
+ EXPECT_FALSE(eos);
+ EXPECT_EQ(data, (std::string{buf, read}));
+
+ EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr));
+ EXPECT_EQ(0, read);
+ EXPECT_FALSE(eos);
+
+ close(fds[1]);
+
+ EXPECT_TRUE(stream->ReadNonBlocking(buf, sizeof(buf), &read, &eos, nullptr));
+ EXPECT_EQ(0, read);
+ EXPECT_TRUE(eos);
+
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, FromFileDescriptor_WriteNonBlocking) {
+ int fds[2] = {-1, -1};
+ ASSERT_EQ(0, pipe(fds));
+
+ StreamPtr stream = FileStream::FromFileDescriptor(fds[1], true, nullptr);
+ ASSERT_NE(nullptr, stream.get());
+ EXPECT_TRUE(stream->IsOpen());
+ EXPECT_FALSE(stream->CanRead());
+ EXPECT_TRUE(stream->CanWrite());
+ EXPECT_FALSE(stream->CanSeek());
+ EXPECT_FALSE(stream->CanGetSize());
+
+ // Pipe buffer is generally 64K, so 128K should be more than enough.
+ std::vector<char> buffer(128 * 1024);
+ base::RandBytes(buffer.data(), buffer.size());
+ size_t written = 0;
+ size_t total_size = 0;
+
+ // Fill the output buffer of the pipe until we can no longer write any data
+ // to it.
+ do {
+ ASSERT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written,
+ nullptr));
+ total_size += written;
+ } while (written == buffer.size());
+
+ EXPECT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written,
+ nullptr));
+ EXPECT_EQ(0, written);
+
+ std::vector<char> out_buffer(total_size);
+ EXPECT_TRUE(base::ReadFromFD(fds[0], out_buffer.data(), out_buffer.size()));
+
+ EXPECT_TRUE(stream->WriteNonBlocking(buffer.data(), buffer.size(), &written,
+ nullptr));
+ EXPECT_GT(written, 0);
+ out_buffer.resize(written);
+ EXPECT_TRUE(base::ReadFromFD(fds[0], out_buffer.data(), out_buffer.size()));
+ EXPECT_TRUE(std::equal(out_buffer.begin(), out_buffer.end(), buffer.begin()));
+
+ close(fds[0]);
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, FromFileDescriptor_ReadAsync) {
+ int fds[2] = {-1, -1};
+ bool succeeded = false;
+ bool failed = false;
+ char buffer[100];
+ base::MessageLoopForIO base_loop;
+ BaseMessageLoop brillo_loop{&base_loop};
+ brillo_loop.SetAsCurrent();
+
+ auto success_callback = [](bool* succeeded, char* buffer, size_t size) {
+ std::string data{buffer, buffer + size};
+ ASSERT_EQ("abracadabra", data);
+ *succeeded = true;
+ };
+
+ auto error_callback = [](bool* failed, const Error* /* error */) {
+ *failed = true;
+ };
+
+ auto write_data_callback = [](int write_fd) {
+ std::string data{"abracadabra"};
+ EXPECT_TRUE(base::WriteFileDescriptor(write_fd, data.data(), data.size()));
+ };
+
+ ASSERT_EQ(0, pipe(fds));
+
+ StreamPtr stream = FileStream::FromFileDescriptor(fds[0], true, nullptr);
+
+ // Write to the pipe with a bit of delay.
+ brillo_loop.PostDelayedTask(
+ FROM_HERE,
+ base::Bind(write_data_callback, fds[1]),
+ base::TimeDelta::FromMilliseconds(10));
+
+ EXPECT_TRUE(
+ stream->ReadAsync(buffer,
+ 100,
+ base::Bind(success_callback,
+ base::Unretained(&succeeded),
+ base::Unretained(buffer)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ nullptr));
+
+ auto end_condition = [](bool* failed, bool* succeeded) {
+ return *failed || *succeeded;
+ };
+ MessageLoopRunUntil(&brillo_loop,
+ base::TimeDelta::FromSeconds(1),
+ base::Bind(end_condition,
+ base::Unretained(&failed),
+ base::Unretained(&succeeded)));
+
+ EXPECT_TRUE(succeeded);
+ EXPECT_FALSE(failed);
+
+ close(fds[1]);
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+}
+
+TEST_F(FileStreamTest, FromFileDescriptor_WriteAsync) {
+ int fds[2] = {-1, -1};
+ bool succeeded = false;
+ bool failed = false;
+ const std::string data{"abracadabra"};
+ base::MessageLoopForIO base_loop;
+ BaseMessageLoop brillo_loop{&base_loop};
+ brillo_loop.SetAsCurrent();
+
+ ASSERT_EQ(0, pipe(fds));
+
+ auto success_callback = [](bool* succeeded,
+ const std::string& data,
+ int read_fd,
+ size_t /* size */) {
+ char buffer[100];
+ EXPECT_TRUE(base::ReadFromFD(read_fd, buffer, data.size()));
+ EXPECT_EQ(data, (std::string{buffer, buffer + data.size()}));
+ *succeeded = true;
+ };
+
+ auto error_callback = [](bool* failed, const Error* /* error */) {
+ *failed = true;
+ };
+
+ StreamPtr stream = FileStream::FromFileDescriptor(fds[1], true, nullptr);
+
+ EXPECT_TRUE(stream->WriteAsync(
+ data.data(),
+ data.size(),
+ base::Bind(success_callback, base::Unretained(&succeeded), data, fds[0]),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ nullptr));
+
+ auto end_condition = [](bool* failed, bool* succeeded) {
+ return *failed || *succeeded;
+ };
+ MessageLoopRunUntil(&brillo_loop,
+ base::TimeDelta::FromSeconds(1),
+ base::Bind(end_condition,
+ base::Unretained(&failed),
+ base::Unretained(&succeeded)));
+
+ EXPECT_TRUE(succeeded);
+ EXPECT_FALSE(failed);
+
+ close(fds[0]);
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/input_stream_set.cc b/libbrillo/brillo/streams/input_stream_set.cc
new file mode 100644
index 0000000..986efac
--- /dev/null
+++ b/libbrillo/brillo/streams/input_stream_set.cc
@@ -0,0 +1,205 @@
+// Copyright 2015 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 <brillo/streams/input_stream_set.h>
+
+#include <base/bind.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/streams/stream_errors.h>
+#include <brillo/streams/stream_utils.h>
+
+namespace brillo {
+
+InputStreamSet::InputStreamSet(
+ std::vector<Stream*> source_streams,
+ std::vector<StreamPtr> owned_source_streams,
+ uint64_t initial_stream_size)
+ : source_streams_{std::move(source_streams)},
+ owned_source_streams_{std::move(owned_source_streams)},
+ initial_stream_size_{initial_stream_size} {}
+
+StreamPtr InputStreamSet::Create(std::vector<Stream*> source_streams,
+ std::vector<StreamPtr> owned_source_streams,
+ ErrorPtr* error) {
+ StreamPtr stream;
+
+ if (source_streams.empty()) {
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kInvalidParameter,
+ "Source stream list is empty");
+ return stream;
+ }
+
+ // Make sure we have only readable streams.
+ for (Stream* src_stream : source_streams) {
+ if (!src_stream->CanRead()) {
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kInvalidParameter,
+ "The stream list must contain only readable streams");
+ return stream;
+ }
+ }
+
+ // We are using remaining size here because the multiplexed stream is not
+ // seekable and the bytes already read are essentially "lost" as far as this
+ // stream is concerned.
+ uint64_t initial_stream_size = 0;
+ for (const Stream* stream : source_streams)
+ initial_stream_size += stream->GetRemainingSize();
+
+ stream.reset(new InputStreamSet{std::move(source_streams),
+ std::move(owned_source_streams),
+ initial_stream_size});
+ return stream;
+}
+
+StreamPtr InputStreamSet::Create(std::vector<Stream*> source_streams,
+ ErrorPtr* error) {
+ return Create(std::move(source_streams), {}, error);
+}
+
+StreamPtr InputStreamSet::Create(std::vector<StreamPtr> owned_source_streams,
+ ErrorPtr* error) {
+ std::vector<Stream*> source_streams;
+ source_streams.reserve(owned_source_streams.size());
+ for (const StreamPtr& stream : owned_source_streams)
+ source_streams.push_back(stream.get());
+ return Create(std::move(source_streams), std::move(owned_source_streams),
+ error);
+}
+
+bool InputStreamSet::IsOpen() const {
+ return !closed_;
+}
+
+bool InputStreamSet::CanGetSize() const {
+ bool can_get_size = IsOpen();
+ for (const Stream* stream : source_streams_) {
+ if (!stream->CanGetSize()) {
+ can_get_size = false;
+ break;
+ }
+ }
+ return can_get_size;
+}
+
+uint64_t InputStreamSet::GetSize() const {
+ return initial_stream_size_;
+}
+
+bool InputStreamSet::SetSizeBlocking(uint64_t /* size */, ErrorPtr* error) {
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+}
+
+uint64_t InputStreamSet::GetRemainingSize() const {
+ uint64_t size = 0;
+ for (const Stream* stream : source_streams_)
+ size += stream->GetRemainingSize();
+ return size;
+}
+
+bool InputStreamSet::Seek(int64_t /* offset */,
+ Whence /* whence */,
+ uint64_t* /* new_position */,
+ ErrorPtr* error) {
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+}
+
+bool InputStreamSet::ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) {
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ while (!source_streams_.empty()) {
+ Stream* stream = source_streams_.front();
+ bool eos = false;
+ if (!stream->ReadNonBlocking(buffer, size_to_read, size_read, &eos, error))
+ return false;
+
+ if (*size_read > 0 || !eos) {
+ if (end_of_stream)
+ *end_of_stream = false;
+ return true;
+ }
+
+ source_streams_.erase(source_streams_.begin());
+ }
+ *size_read = 0;
+ if (end_of_stream)
+ *end_of_stream = true;
+ return true;
+}
+
+bool InputStreamSet::WriteNonBlocking(const void* /* buffer */,
+ size_t /* size_to_write */,
+ size_t* /* size_written */,
+ ErrorPtr* error) {
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+}
+
+bool InputStreamSet::CloseBlocking(ErrorPtr* error) {
+ bool success = true;
+ // We want to close only the owned streams.
+ for (StreamPtr& stream_ptr : owned_source_streams_) {
+ if (!stream_ptr->CloseBlocking(error))
+ success = false; // Keep going for other streams...
+ }
+ owned_source_streams_.clear();
+ source_streams_.clear();
+ initial_stream_size_ = 0;
+ closed_ = true;
+ return success;
+}
+
+bool InputStreamSet::WaitForData(
+ AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) {
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ if (stream_utils::IsWriteAccessMode(mode))
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+
+ if (!source_streams_.empty()) {
+ Stream* stream = source_streams_.front();
+ return stream->WaitForData(mode, callback, error);
+ }
+
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, mode));
+ return true;
+}
+
+bool InputStreamSet::WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) {
+ if (!IsOpen())
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+
+ if (stream_utils::IsWriteAccessMode(in_mode))
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+
+ if (!source_streams_.empty()) {
+ Stream* stream = source_streams_.front();
+ return stream->WaitForDataBlocking(in_mode, timeout, out_mode, error);
+ }
+
+ if (out_mode)
+ *out_mode = in_mode;
+ return true;
+}
+
+void InputStreamSet::CancelPendingAsyncOperations() {
+ if (IsOpen() && !source_streams_.empty()) {
+ Stream* stream = source_streams_.front();
+ stream->CancelPendingAsyncOperations();
+ }
+ Stream::CancelPendingAsyncOperations();
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/input_stream_set.h b/libbrillo/brillo/streams/input_stream_set.h
new file mode 100644
index 0000000..9111ff9
--- /dev/null
+++ b/libbrillo/brillo/streams/input_stream_set.h
@@ -0,0 +1,132 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_INPUT_STREAM_SET_H_
+#define LIBBRILLO_BRILLO_STREAMS_INPUT_STREAM_SET_H_
+
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+
+// Multiplexer stream allows to bundle a bunch of secondary streams in one
+// logical stream and simulate a read operation across data concatenated from
+// all those source streams.
+//
+// When created on a set of source streams like stream1, stream2, stream3, etc.,
+// reading from the multiplexer stream will read all the data from stream1 until
+// end-of-stream is reached, then keep reading from stream2, stream3 and so on.
+//
+// InputStreamSet has an option of owning the underlying source streams
+// or just referencing them. Owned streams are passed to InputStreamSet
+// with exclusive ownership transfer (using StreamPtr) and those streams will
+// be closed/destroyed when InputStreamSet is closed/destroyed.
+// Referenced source streams' life time is maintained elsewhere and they must
+// be valid for the duration of InputStreamSet's life. Closing the
+// muliplexer stream does not close the referenced streams.
+class BRILLO_EXPORT InputStreamSet : public Stream {
+ public:
+ // == Construction ==========================================================
+
+ // Generic method that constructs a multiplexer stream on a list of source
+ // streams. |source_streams| is the list of all source stream references
+ // in the order they need to be read from. |owned_source_streams| is a list
+ // of source stream instances that the multiplexer stream will own.
+ // Note that the streams from |owned_source_streams| should still be
+ // referenced in |source_streams| if you need their data to be read from.
+ // |owned_source_streams| could be empty (in which case none of the source
+ // streams are not owned), or contain fewer items than in |source_streams|.
+ static StreamPtr Create(std::vector<Stream*> source_streams,
+ std::vector<StreamPtr> owned_source_streams,
+ ErrorPtr* error);
+
+ // Simple helper method to create a multiplexer stream with a list of
+ // referenced streams. None of the streams will be owned.
+ // Effectively calls Create(source_streams, {}, error);
+ static StreamPtr Create(std::vector<Stream*> source_streams, ErrorPtr* error);
+
+ // Simple helper method to create a multiplexer stream with a list of
+ // referenced streams. None of the streams will be owned.
+ // Effectively calls Create(source_streams, owned_source_streams, error)
+ // with |source_streams| containing pointers to the streams from
+ // |owned_source_streams| list.
+ static StreamPtr Create(std::vector<StreamPtr> owned_source_streams,
+ ErrorPtr* error);
+
+ // == Stream capabilities ===================================================
+ bool IsOpen() const override;
+ bool CanRead() const override { return true; }
+ bool CanWrite() const override { return false; }
+ bool CanSeek() const override { return false; }
+ bool CanGetSize() const override;
+
+ // == Stream size operations ================================================
+ uint64_t GetSize() const override;
+ bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override;
+ uint64_t GetRemainingSize() const override;
+
+ // == Seek operations =======================================================
+ uint64_t GetPosition() const override { return 0; }
+ bool Seek(int64_t offset,
+ Whence whence,
+ uint64_t* new_position,
+ ErrorPtr* error) override;
+
+ // == Read operations =======================================================
+ bool ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) override;
+
+ // == Write operations ======================================================
+ bool WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) override;
+
+ // == Finalizing/closing streams ===========================================
+ bool FlushBlocking(ErrorPtr* /* error */) override { return true; }
+ bool CloseBlocking(ErrorPtr* error) override;
+
+ // == Data availability monitoring ==========================================
+ bool WaitForData(AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) override;
+
+ bool WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) override;
+
+ void CancelPendingAsyncOperations() override;
+
+ private:
+ friend class InputStreamSetTest;
+
+ // Internal constructor used by the Create() factory methods.
+ InputStreamSet(std::vector<Stream*> source_streams,
+ std::vector<StreamPtr> owned_source_streams,
+ uint64_t initial_stream_size);
+
+ // List of streams to read data from.
+ std::vector<Stream*> source_streams_;
+
+ // List of source streams this stream owns. Owned source streams will be
+ // closed when InputStreamSet::CloseBlocking() is called and will be
+ // destroyed when this stream is destroyed.
+ std::vector<StreamPtr> owned_source_streams_;
+
+ uint64_t initial_stream_size_{0};
+ bool closed_{false};
+
+ DISALLOW_COPY_AND_ASSIGN(InputStreamSet);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_INPUT_STREAM_SET_H_
diff --git a/libbrillo/brillo/streams/input_stream_set_unittest.cc b/libbrillo/brillo/streams/input_stream_set_unittest.cc
new file mode 100644
index 0000000..3268d96
--- /dev/null
+++ b/libbrillo/brillo/streams/input_stream_set_unittest.cc
@@ -0,0 +1,170 @@
+// Copyright 2015 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 <brillo/streams/input_stream_set.h>
+
+#include <brillo/errors/error_codes.h>
+#include <brillo/streams/mock_stream.h>
+#include <brillo/streams/stream_errors.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::An;
+using testing::DoAll;
+using testing::InSequence;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::StrictMock;
+using testing::_;
+
+namespace brillo {
+
+class InputStreamSetTest : public testing::Test {
+ public:
+ void SetUp() override {
+ itf1_.reset(new StrictMock<MockStream>{});
+ itf2_.reset(new StrictMock<MockStream>{});
+ stream_.reset(new InputStreamSet({itf1_.get(), itf2_.get()}, {}, 100));
+ }
+
+ void TearDown() override {
+ stream_.reset();
+ itf2_.reset();
+ itf1_.reset();
+ }
+
+ std::unique_ptr<StrictMock<MockStream>> itf1_;
+ std::unique_ptr<StrictMock<MockStream>> itf2_;
+ std::unique_ptr<InputStreamSet> stream_;
+
+ inline static void* IntToPtr(int addr) {
+ return reinterpret_cast<void*>(addr);
+ }
+};
+
+TEST_F(InputStreamSetTest, InitialFalseAssumptions) {
+ // Methods that should just succeed/fail without calling underlying streams.
+ EXPECT_TRUE(stream_->CanRead());
+ EXPECT_FALSE(stream_->CanWrite());
+ EXPECT_FALSE(stream_->CanSeek());
+ EXPECT_EQ(100, stream_->GetSize());
+ EXPECT_FALSE(stream_->SetSizeBlocking(0, nullptr));
+ EXPECT_FALSE(stream_->GetPosition());
+ EXPECT_FALSE(stream_->Seek(0, Stream::Whence::FROM_BEGIN, nullptr, nullptr));
+ char buffer[100];
+ size_t size = 0;
+ EXPECT_FALSE(stream_->WriteAsync(buffer, sizeof(buffer), {}, {}, nullptr));
+ EXPECT_FALSE(stream_->WriteAllAsync(buffer, sizeof(buffer), {}, {}, nullptr));
+ EXPECT_FALSE(stream_->WriteNonBlocking(buffer, sizeof(buffer), &size,
+ nullptr));
+ EXPECT_FALSE(stream_->WriteBlocking(buffer, sizeof(buffer), &size, nullptr));
+ EXPECT_FALSE(stream_->WriteAllBlocking(buffer, sizeof(buffer), nullptr));
+ EXPECT_TRUE(stream_->FlushBlocking(nullptr));
+ EXPECT_TRUE(stream_->CloseBlocking(nullptr));
+}
+
+TEST_F(InputStreamSetTest, InitialTrueAssumptions) {
+ // Methods that redirect calls to underlying streams.
+ EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(true));
+ EXPECT_CALL(*itf2_, CanGetSize()).WillOnce(Return(true));
+ EXPECT_TRUE(stream_->CanGetSize());
+
+ // Reading from the first stream fails, so the second one shouldn't be used.
+ EXPECT_CALL(*itf1_, ReadNonBlocking(_, _, _, _, _))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*itf2_, ReadNonBlocking(_, _, _, _, _)).Times(0);
+ char buffer[100];
+ size_t size = 0;
+ EXPECT_FALSE(stream_->ReadBlocking(buffer, sizeof(buffer), &size, nullptr));
+}
+
+TEST_F(InputStreamSetTest, CanGetSize) {
+ EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(true));
+ EXPECT_CALL(*itf2_, CanGetSize()).WillOnce(Return(true));
+ EXPECT_TRUE(stream_->CanGetSize());
+
+ EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(false));
+ EXPECT_FALSE(stream_->CanGetSize());
+
+ EXPECT_CALL(*itf1_, CanGetSize()).WillOnce(Return(true));
+ EXPECT_CALL(*itf2_, CanGetSize()).WillOnce(Return(false));
+ EXPECT_FALSE(stream_->CanGetSize());
+}
+
+TEST_F(InputStreamSetTest, GetRemainingSize) {
+ EXPECT_CALL(*itf1_, GetRemainingSize()).WillOnce(Return(10));
+ EXPECT_CALL(*itf2_, GetRemainingSize()).WillOnce(Return(32));
+ EXPECT_EQ(42, stream_->GetRemainingSize());
+}
+
+TEST_F(InputStreamSetTest, ReadNonBlocking) {
+ size_t read = 0;
+ bool eos = false;
+
+ InSequence s;
+ EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(10),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_TRUE(stream_->ReadNonBlocking(IntToPtr(1000), 100, &read, &eos,
+ nullptr));
+ EXPECT_EQ(10, read);
+ EXPECT_FALSE(eos);
+
+ EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), SetArgPointee<3>(true), Return(true)));
+ EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100 , _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(100),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_TRUE(stream_->ReadNonBlocking(IntToPtr(1000), 100, &read, &eos,
+ nullptr));
+ EXPECT_EQ(100, read);
+ EXPECT_FALSE(eos);
+
+ EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), SetArgPointee<3>(true), Return(true)));
+ EXPECT_TRUE(stream_->ReadNonBlocking(IntToPtr(1000), 100, &read, &eos,
+ nullptr));
+ EXPECT_EQ(0, read);
+ EXPECT_TRUE(eos);
+}
+
+TEST_F(InputStreamSetTest, ReadBlocking) {
+ size_t read = 0;
+
+ InSequence s;
+ EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(10),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_TRUE(stream_->ReadBlocking(IntToPtr(1000), 100, &read, nullptr));
+ EXPECT_EQ(10, read);
+
+ EXPECT_CALL(*itf1_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0),
+ SetArgPointee<3>(true),
+ Return(true)));
+ EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_CALL(*itf2_, WaitForDataBlocking(Stream::AccessMode::READ, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(100),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_TRUE(stream_->ReadBlocking(IntToPtr(1000), 100, &read, nullptr));
+ EXPECT_EQ(100, read);
+
+ EXPECT_CALL(*itf2_, ReadNonBlocking(IntToPtr(1000), 100, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0),
+ SetArgPointee<3>(true),
+ Return(true)));
+ EXPECT_TRUE(stream_->ReadBlocking(IntToPtr(1000), 100, &read, nullptr));
+ EXPECT_EQ(0, read);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/memory_containers.cc b/libbrillo/brillo/streams/memory_containers.cc
new file mode 100644
index 0000000..c1b842e
--- /dev/null
+++ b/libbrillo/brillo/streams/memory_containers.cc
@@ -0,0 +1,132 @@
+// Copyright 2015 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 <brillo/streams/memory_containers.h>
+
+#include <base/callback.h>
+#include <brillo/streams/stream_errors.h>
+
+namespace brillo {
+namespace data_container {
+
+namespace {
+
+bool ErrorStreamReadOnly(const tracked_objects::Location& location,
+ ErrorPtr* error) {
+ Error::AddTo(error,
+ location,
+ errors::stream::kDomain,
+ errors::stream::kOperationNotSupported,
+ "Stream is read-only");
+ return false;
+}
+
+} // anonymous namespace
+
+void ContiguousBufferBase::CopyMemoryBlock(void* dest,
+ const void* src,
+ size_t size) const {
+ memcpy(dest, src, size);
+}
+
+bool ContiguousBufferBase::Read(void* buffer,
+ size_t size_to_read,
+ size_t offset,
+ size_t* size_read,
+ ErrorPtr* error) {
+ size_t buf_size = GetSize();
+ if (offset < buf_size) {
+ size_t remaining = buf_size - offset;
+ if (size_to_read >= remaining) {
+ size_to_read = remaining;
+ }
+ const void* src_buffer = GetReadOnlyBuffer(offset, error);
+ if (!src_buffer)
+ return false;
+
+ CopyMemoryBlock(buffer, src_buffer, size_to_read);
+ } else {
+ size_to_read = 0;
+ }
+ if (size_read)
+ *size_read = size_to_read;
+ return true;
+}
+
+bool ContiguousBufferBase::Write(const void* buffer,
+ size_t size_to_write,
+ size_t offset,
+ size_t* size_written,
+ ErrorPtr* error) {
+ if (size_to_write) {
+ size_t new_size = offset + size_to_write;
+ if (GetSize() < new_size && !Resize(new_size, error))
+ return false;
+ void* ptr = GetBuffer(offset, error);
+ if (!ptr)
+ return false;
+ CopyMemoryBlock(ptr, buffer, size_to_write);
+ if (size_written)
+ *size_written = size_to_write;
+ }
+ return true;
+}
+
+bool ContiguousReadOnlyBufferBase::Write(const void* /* buffer */,
+ size_t /* size_to_write */,
+ size_t /* offset */,
+ size_t* /* size_written */,
+ ErrorPtr* error) {
+ return ErrorStreamReadOnly(FROM_HERE, error);
+}
+
+bool ContiguousReadOnlyBufferBase::Resize(size_t /* new_size */,
+ ErrorPtr* error) {
+ return ErrorStreamReadOnly(FROM_HERE, error);
+}
+
+void* ContiguousReadOnlyBufferBase::GetBuffer(size_t /* offset */,
+ ErrorPtr* error) {
+ ErrorStreamReadOnly(FROM_HERE, error);
+ return nullptr;
+}
+
+ByteBuffer::ByteBuffer(size_t reserve_size)
+ : VectorPtr(new std::vector<uint8_t>()) {
+ vector_ptr_->reserve(reserve_size);
+}
+
+ByteBuffer::~ByteBuffer() {
+ delete vector_ptr_;
+}
+
+StringPtr::StringPtr(std::string* string) : string_ptr_(string) {}
+
+bool StringPtr::Resize(size_t new_size, ErrorPtr* /* error */) {
+ string_ptr_->resize(new_size);
+ return true;
+}
+
+const void* StringPtr::GetReadOnlyBuffer(size_t offset,
+ ErrorPtr* /* error */) const {
+ return string_ptr_->data() + offset;
+}
+
+void* StringPtr::GetBuffer(size_t offset, ErrorPtr* /* error */) {
+ return &(*string_ptr_)[offset];
+}
+
+ReadOnlyStringRef::ReadOnlyStringRef(const std::string& string)
+ : string_ref_(string) {}
+
+const void* ReadOnlyStringRef::GetReadOnlyBuffer(size_t offset,
+ ErrorPtr* /* error */) const {
+ return string_ref_.data() + offset;
+}
+
+ReadOnlyStringCopy::ReadOnlyStringCopy(std::string string)
+ : ReadOnlyStringRef(string_copy_), string_copy_(std::move(string)) {}
+
+} // namespace data_container
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/memory_containers.h b/libbrillo/brillo/streams/memory_containers.h
new file mode 100644
index 0000000..d3cb205
--- /dev/null
+++ b/libbrillo/brillo/streams/memory_containers.h
@@ -0,0 +1,288 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_MEMORY_CONTAINERS_H_
+#define LIBBRILLO_BRILLO_STREAMS_MEMORY_CONTAINERS_H_
+
+#include <string>
+#include <vector>
+
+#include <brillo/brillo_export.h>
+#include <brillo/errors/error.h>
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+namespace data_container {
+
+// MemoryStream class relies on helper classes defined below to support data
+// storage in various types of containers.
+// A particular implementation of container type (e.g. based on raw memory
+// buffers, std::vector, std::string or others) need to implement the container
+// interface provided by data_container::DataContainerInterface.
+// Low-level functionality such as reading data from and writing data to the
+// container, getting and changing the buffer size, and so on, must be provided.
+// Not all methods must be provided. For example, for read-only containers, only
+// read operations can be provided.
+class BRILLO_EXPORT DataContainerInterface {
+ public:
+ DataContainerInterface() = default;
+ virtual ~DataContainerInterface() = default;
+
+ // Read the data from the container into |buffer|. Up to |size_to_read| bytes
+ // must be read at a time. The container can return fewer bytes. The actual
+ // size of data read is provided in |size_read|.
+ // If the read operation fails, the function must return false and provide
+ // additional information about the error in |error| object.
+ virtual bool Read(void* buffer,
+ size_t size_to_read,
+ size_t offset,
+ size_t* size_read,
+ ErrorPtr* error) = 0;
+
+ // Writes |size_to_write| bytes of data from |buffer| into the container.
+ // The container may accept fewer bytes of data. The actual size of data
+ // written is provided in |size_written|.
+ // If the read operation fails, the function must return false and provide
+ // additional information about the error in |error| object.
+ virtual bool Write(const void* buffer,
+ size_t size_to_write,
+ size_t offset,
+ size_t* size_written,
+ ErrorPtr* error) = 0;
+ // Resizes the container to the new size specified in |new_size|.
+ virtual bool Resize(size_t new_size, ErrorPtr* error) = 0;
+ // Returns the current size of the container.
+ virtual size_t GetSize() const = 0;
+ // Returns true if the container is read-only.
+ virtual bool IsReadOnly() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DataContainerInterface);
+};
+
+// ContiguousBufferBase is a helper base class for memory containers that
+// employ contiguous memory for all of their data. This class provides the
+// default implementation for Read() and Write() functions and requires the
+// implementations to provide GetBuffer() and/or ReadOnlyBuffer() functions.
+class BRILLO_EXPORT ContiguousBufferBase : public DataContainerInterface {
+ public:
+ ContiguousBufferBase() = default;
+ // Implementation of DataContainerInterface::Read().
+ bool Read(void* buffer,
+ size_t size_to_read,
+ size_t offset,
+ size_t* size_read,
+ ErrorPtr* error) override;
+ // Implementation of DataContainerInterface::Write().
+ bool Write(const void* buffer,
+ size_t size_to_write,
+ size_t offset,
+ size_t* size_written,
+ ErrorPtr* error) override;
+
+ // Overload to provide the pointer to the read-only data for the container at
+ // the specified |offset|. In case of an error, this function must return
+ // nullptr and provide error details in |error| object if provided.
+ virtual const void* GetReadOnlyBuffer(size_t offset,
+ ErrorPtr* error) const = 0;
+ // Overload to provide the pointer to the read/write data for the container at
+ // the specified |offset|. In case of an error, this function must return
+ // nullptr and provide error details in |error| object if provided.
+ virtual void* GetBuffer(size_t offset, ErrorPtr* error) = 0;
+
+ protected:
+ // Wrapper around memcpy which can be mocked out in tests.
+ virtual void CopyMemoryBlock(void* dest, const void* src, size_t size) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContiguousBufferBase);
+};
+
+// ContiguousReadOnlyBufferBase is a specialization of ContiguousBufferBase for
+// read-only containers.
+class BRILLO_EXPORT ContiguousReadOnlyBufferBase : public ContiguousBufferBase {
+ public:
+ ContiguousReadOnlyBufferBase() = default;
+ // Fails with an error "operation_not_supported" (Stream is read-only) error.
+ bool Write(const void* buffer,
+ size_t size_to_write,
+ size_t offset,
+ size_t* size_written,
+ ErrorPtr* error) override;
+ // Fails with an error "operation_not_supported" (Stream is read-only) error.
+ bool Resize(size_t new_size, ErrorPtr* error) override;
+ // Fails with an error "operation_not_supported" (Stream is read-only) error.
+ bool IsReadOnly() const override { return true; }
+ // Fails with an error "operation_not_supported" (Stream is read-only) error.
+ void* GetBuffer(size_t offset, ErrorPtr* error) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContiguousReadOnlyBufferBase);
+};
+
+// ReadOnlyBuffer implements a read-only container based on raw memory block.
+class BRILLO_EXPORT ReadOnlyBuffer : public ContiguousReadOnlyBufferBase {
+ public:
+ // Constructs the container based at the pointer to memory |buffer| and its
+ // |size|. The pointer to the memory must be valid throughout life-time of
+ // the stream using this container.
+ ReadOnlyBuffer(const void* buffer, size_t size)
+ : buffer_(buffer), size_(size) {}
+
+ // Returns the pointer to data at |offset|.
+ const void* GetReadOnlyBuffer(size_t offset,
+ ErrorPtr* /* error */) const override {
+ return reinterpret_cast<const uint8_t*>(buffer_) + offset;
+ }
+ // Returns the size of the container.
+ size_t GetSize() const override { return size_; }
+
+ private:
+ // Raw memory pointer to the data block and its size.
+ const void* buffer_;
+ size_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReadOnlyBuffer);
+};
+
+// VectorPtr<T> is a read/write container based on a vector<T> pointer.
+// This is a template class to allow usage of both vector<char> and
+// vector<uint8_t> without duplicating the implementation.
+template<typename T>
+class VectorPtr : public ContiguousBufferBase {
+ public:
+ static_assert(sizeof(T) == 1, "Only char/byte is supported");
+ explicit VectorPtr(std::vector<T>* vector) : vector_ptr_(vector) {}
+
+ bool Resize(size_t new_size, ErrorPtr* /* error */) override {
+ vector_ptr_->resize(new_size);
+ return true;
+ }
+ size_t GetSize() const override { return vector_ptr_->size(); }
+ bool IsReadOnly() const override { return false; }
+ const void* GetReadOnlyBuffer(size_t offset,
+ ErrorPtr* /* error */) const override {
+ return reinterpret_cast<const uint8_t*>(vector_ptr_->data()) + offset;
+ }
+ void* GetBuffer(size_t offset, ErrorPtr* /* error */) override {
+ return reinterpret_cast<uint8_t*>(vector_ptr_->data()) + offset;
+ }
+
+ protected:
+ std::vector<T>* vector_ptr_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VectorPtr);
+};
+
+// ReadOnlyVectorRef<T> is a read-only container based on a vector<T> reference.
+// This is a template class to allow usage of both vector<char> and
+// vector<uint8_t> without duplicating the implementation.
+template<typename T>
+class ReadOnlyVectorRef : public ContiguousReadOnlyBufferBase {
+ public:
+ static_assert(sizeof(T) == 1, "Only char/byte is supported");
+ explicit ReadOnlyVectorRef(const std::vector<T>& vector)
+ : vector_ref_(vector) {}
+
+ const void* GetReadOnlyBuffer(size_t offset,
+ ErrorPtr* /* error */) const override {
+ return reinterpret_cast<const uint8_t*>(vector_ref_.data()) + offset;
+ }
+ size_t GetSize() const override { return vector_ref_.size(); }
+
+ protected:
+ const std::vector<T>& vector_ref_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReadOnlyVectorRef);
+};
+
+// ReadOnlyVectorCopy<T> is a read-only container based on a copy of vector<T>.
+// This container actually owns the data stored in the vector.
+// This is a template class to allow usage of both vector<char> and
+// vector<uint8_t> without duplicating the implementation.
+template<typename T>
+class ReadOnlyVectorCopy : public ContiguousReadOnlyBufferBase {
+ public:
+ static_assert(sizeof(T) == 1, "Only char/byte is supported");
+ explicit ReadOnlyVectorCopy(std::vector<T> vector)
+ : vector_copy_(std::move(vector)) {}
+
+ ReadOnlyVectorCopy(const T* buffer, size_t size)
+ : vector_copy_(buffer, buffer + size) {}
+
+ const void* GetReadOnlyBuffer(size_t offset,
+ ErrorPtr* /* error */) const override {
+ return reinterpret_cast<const uint8_t*>(vector_copy_.data()) + offset;
+ }
+ size_t GetSize() const override { return vector_copy_.size(); }
+
+ protected:
+ std::vector<T> vector_copy_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReadOnlyVectorCopy);
+};
+
+// ByteBuffer is a read/write container that manages the data and underlying
+// storage.
+class BRILLO_EXPORT ByteBuffer : public VectorPtr<uint8_t> {
+ public:
+ explicit ByteBuffer(size_t reserve_size);
+ ~ByteBuffer() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ByteBuffer);
+};
+
+// StringPtr is a read/write container based on external std::string storage.
+class BRILLO_EXPORT StringPtr : public ContiguousBufferBase {
+ public:
+ explicit StringPtr(std::string* string);
+
+ bool Resize(size_t new_size, ErrorPtr* error) override;
+ size_t GetSize() const override { return string_ptr_->size(); }
+ bool IsReadOnly() const override { return false; }
+ const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override;
+ void* GetBuffer(size_t offset, ErrorPtr* error) override;
+
+ protected:
+ std::string* string_ptr_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StringPtr);
+};
+
+// ReadOnlyStringRef is a read-only container based on external std::string.
+class BRILLO_EXPORT ReadOnlyStringRef : public ContiguousReadOnlyBufferBase {
+ public:
+ explicit ReadOnlyStringRef(const std::string& string);
+ const void* GetReadOnlyBuffer(size_t offset, ErrorPtr* error) const override;
+ size_t GetSize() const override { return string_ref_.size(); }
+
+ protected:
+ const std::string& string_ref_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReadOnlyStringRef);
+};
+
+// ReadOnlyStringCopy is a read-only container based on a copy of a std::string.
+// This container actually owns the data stored in the string.
+class BRILLO_EXPORT ReadOnlyStringCopy : public ReadOnlyStringRef {
+ public:
+ explicit ReadOnlyStringCopy(std::string string);
+
+ protected:
+ std::string string_copy_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReadOnlyStringCopy);
+};
+
+} // namespace data_container
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_MEMORY_CONTAINERS_H_
diff --git a/libbrillo/brillo/streams/memory_containers_unittest.cc b/libbrillo/brillo/streams/memory_containers_unittest.cc
new file mode 100644
index 0000000..2f0bf38
--- /dev/null
+++ b/libbrillo/brillo/streams/memory_containers_unittest.cc
@@ -0,0 +1,214 @@
+// Copyright 2015 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 <brillo/streams/memory_containers.h>
+
+#include <limits>
+#include <memory>
+
+#include <brillo/streams/mock_stream.h>
+#include <brillo/streams/stream_errors.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::DoAll;
+using testing::Invoke;
+using testing::InSequence;
+using testing::Return;
+using testing::WithArgs;
+using testing::_;
+
+namespace brillo {
+
+namespace {
+class MockContiguousBuffer : public data_container::ContiguousBufferBase {
+ public:
+ MockContiguousBuffer() = default;
+
+ MOCK_METHOD2(Resize, bool(size_t, ErrorPtr*));
+ MOCK_CONST_METHOD0(GetSize, size_t());
+ MOCK_CONST_METHOD0(IsReadOnly, bool());
+
+ MOCK_CONST_METHOD2(GetReadOnlyBuffer, const void*(size_t, ErrorPtr*));
+ MOCK_METHOD2(GetBuffer, void*(size_t, ErrorPtr*));
+
+ MOCK_CONST_METHOD3(CopyMemoryBlock, void(void*, const void*, size_t));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockContiguousBuffer);
+};
+} // anonymous namespace
+
+class MemoryContainerTest : public testing::Test {
+ public:
+ inline static void* IntToPtr(int addr) {
+ return reinterpret_cast<void*>(addr);
+ }
+
+ inline static const void* IntToConstPtr(int addr) {
+ return reinterpret_cast<const void*>(addr);
+ }
+
+ // Dummy buffer pointer values used as external data source/destination for
+ // read/write operations.
+ void* const test_read_buffer_ = IntToPtr(12345);
+ const void* const test_write_buffer_ = IntToConstPtr(67890);
+
+ // Dummy buffer pointer values used for internal buffer owned by the
+ // memory buffer container class.
+ const void* const const_buffer_ = IntToConstPtr(123);
+ void* const buffer_ = IntToPtr(456);
+
+ MockContiguousBuffer container_;
+};
+
+TEST_F(MemoryContainerTest, Read_WithinBuffer) {
+ {
+ InSequence s;
+ EXPECT_CALL(container_, GetSize()).WillOnce(Return(100));
+ EXPECT_CALL(container_, GetReadOnlyBuffer(10, _))
+ .WillOnce(Return(const_buffer_));
+ EXPECT_CALL(container_,
+ CopyMemoryBlock(test_read_buffer_, const_buffer_, 50)).Times(1);
+ }
+ size_t read = 0;
+ ErrorPtr error;
+ EXPECT_TRUE(container_.Read(test_read_buffer_, 50, 10, &read, &error));
+ EXPECT_EQ(50, read);
+ EXPECT_EQ(nullptr, error.get());
+}
+
+TEST_F(MemoryContainerTest, Read_PastEndOfBuffer) {
+ {
+ InSequence s;
+ EXPECT_CALL(container_, GetSize()).WillOnce(Return(100));
+ EXPECT_CALL(container_, GetReadOnlyBuffer(80, _))
+ .WillOnce(Return(const_buffer_));
+ EXPECT_CALL(container_,
+ CopyMemoryBlock(test_read_buffer_, const_buffer_, 20)).Times(1);
+ }
+ size_t read = 0;
+ EXPECT_TRUE(container_.Read(test_read_buffer_, 50, 80, &read, nullptr));
+ EXPECT_EQ(20, read);
+}
+
+TEST_F(MemoryContainerTest, Read_OutsideBuffer) {
+ EXPECT_CALL(container_, GetSize()).WillOnce(Return(100));
+ size_t read = 0;
+ EXPECT_TRUE(container_.Read(test_read_buffer_, 50, 100, &read, nullptr));
+ EXPECT_EQ(0, read);
+}
+
+TEST_F(MemoryContainerTest, Read_Error) {
+ auto OnReadError = [](ErrorPtr* error) {
+ Error::AddTo(error, FROM_HERE, "domain", "read_error", "read error");
+ };
+
+ {
+ InSequence s;
+ EXPECT_CALL(container_, GetSize()).WillOnce(Return(100));
+ EXPECT_CALL(container_, GetReadOnlyBuffer(0, _))
+ .WillOnce(DoAll(WithArgs<1>(Invoke(OnReadError)), Return(nullptr)));
+ }
+ size_t read = 0;
+ ErrorPtr error;
+ EXPECT_FALSE(container_.Read(test_read_buffer_, 10, 0, &read, &error));
+ EXPECT_EQ(0, read);
+ EXPECT_NE(nullptr, error.get());
+ EXPECT_EQ("domain", error->GetDomain());
+ EXPECT_EQ("read_error", error->GetCode());
+ EXPECT_EQ("read error", error->GetMessage());
+}
+
+TEST_F(MemoryContainerTest, Write_WithinBuffer) {
+ {
+ InSequence s;
+ EXPECT_CALL(container_, GetSize()).WillOnce(Return(100));
+ EXPECT_CALL(container_, GetBuffer(10, _))
+ .WillOnce(Return(buffer_));
+ EXPECT_CALL(container_,
+ CopyMemoryBlock(buffer_, test_write_buffer_, 50)).Times(1);
+ }
+ size_t written = 0;
+ ErrorPtr error;
+ EXPECT_TRUE(container_.Write(test_write_buffer_, 50, 10, &written, &error));
+ EXPECT_EQ(50, written);
+ EXPECT_EQ(nullptr, error.get());
+}
+
+TEST_F(MemoryContainerTest, Write_PastEndOfBuffer) {
+ {
+ InSequence s;
+ EXPECT_CALL(container_, GetSize()).WillOnce(Return(100));
+ EXPECT_CALL(container_, Resize(130, _)).WillOnce(Return(true));
+ EXPECT_CALL(container_, GetBuffer(80, _))
+ .WillOnce(Return(buffer_));
+ EXPECT_CALL(container_,
+ CopyMemoryBlock(buffer_, test_write_buffer_, 50)).Times(1);
+ }
+ size_t written = 0;
+ EXPECT_TRUE(container_.Write(test_write_buffer_, 50, 80, &written, nullptr));
+ EXPECT_EQ(50, written);
+}
+
+TEST_F(MemoryContainerTest, Write_OutsideBuffer) {
+ {
+ InSequence s;
+ EXPECT_CALL(container_, GetSize()).WillOnce(Return(100));
+ EXPECT_CALL(container_, Resize(160, _)).WillOnce(Return(true));
+ EXPECT_CALL(container_, GetBuffer(110, _))
+ .WillOnce(Return(buffer_));
+ EXPECT_CALL(container_,
+ CopyMemoryBlock(buffer_, test_write_buffer_, 50)).Times(1);
+ }
+ size_t written = 0;
+ EXPECT_TRUE(container_.Write(test_write_buffer_, 50, 110, &written, nullptr));
+ EXPECT_EQ(50, written);
+}
+
+TEST_F(MemoryContainerTest, Write_Error_Resize) {
+ auto OnWriteError = [](ErrorPtr* error) {
+ Error::AddTo(error, FROM_HERE, "domain", "write_error", "resize error");
+ };
+
+ {
+ InSequence s;
+ EXPECT_CALL(container_, GetSize()).WillOnce(Return(100));
+ EXPECT_CALL(container_, Resize(160, _))
+ .WillOnce(DoAll(WithArgs<1>(Invoke(OnWriteError)), Return(false)));
+ }
+ size_t written = 0;
+ ErrorPtr error;
+ EXPECT_FALSE(container_.Write(test_write_buffer_, 50, 110, &written, &error));
+ EXPECT_EQ(0, written);
+ EXPECT_NE(nullptr, error.get());
+ EXPECT_EQ("domain", error->GetDomain());
+ EXPECT_EQ("write_error", error->GetCode());
+ EXPECT_EQ("resize error", error->GetMessage());
+}
+
+TEST_F(MemoryContainerTest, Write_Error) {
+ auto OnWriteError = [](ErrorPtr* error) {
+ Error::AddTo(error, FROM_HERE, "domain", "write_error", "write error");
+ };
+
+ {
+ InSequence s;
+ EXPECT_CALL(container_, GetSize()).WillOnce(Return(100));
+ EXPECT_CALL(container_, Resize(160, _)).WillOnce(Return(true));
+ EXPECT_CALL(container_, GetBuffer(110, _))
+ .WillOnce(DoAll(WithArgs<1>(Invoke(OnWriteError)), Return(nullptr)));
+ }
+ size_t written = 0;
+ ErrorPtr error;
+ EXPECT_FALSE(container_.Write(test_write_buffer_, 50, 110, &written, &error));
+ EXPECT_EQ(0, written);
+ EXPECT_NE(nullptr, error.get());
+ EXPECT_EQ("domain", error->GetDomain());
+ EXPECT_EQ("write_error", error->GetCode());
+ EXPECT_EQ("write error", error->GetMessage());
+}
+
+} // namespace brillo
+
diff --git a/libbrillo/brillo/streams/memory_stream.cc b/libbrillo/brillo/streams/memory_stream.cc
new file mode 100644
index 0000000..54f127a
--- /dev/null
+++ b/libbrillo/brillo/streams/memory_stream.cc
@@ -0,0 +1,201 @@
+// Copyright 2015 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 <brillo/streams/memory_stream.h>
+
+#include <limits>
+
+#include <base/bind.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/streams/stream_errors.h>
+#include <brillo/streams/stream_utils.h>
+
+namespace brillo {
+
+MemoryStream::MemoryStream(
+ std::unique_ptr<data_container::DataContainerInterface> container,
+ size_t stream_position)
+ : container_{std::move(container)}, stream_position_{stream_position} {}
+
+StreamPtr MemoryStream::OpenRef(const void* buffer,
+ size_t size,
+ ErrorPtr* error) {
+ std::unique_ptr<data_container::ReadOnlyBuffer> container{
+ new data_container::ReadOnlyBuffer{buffer, size}};
+ return CreateEx(std::move(container), 0, error);
+}
+
+StreamPtr MemoryStream::OpenCopyOf(const void* buffer,
+ size_t size,
+ ErrorPtr* error) {
+ std::unique_ptr<data_container::ReadOnlyVectorCopy<uint8_t>> container{
+ new data_container::ReadOnlyVectorCopy<uint8_t>{
+ reinterpret_cast<const uint8_t*>(buffer), size}};
+ return CreateEx(std::move(container), 0, error);
+}
+
+StreamPtr MemoryStream::OpenRef(const std::string& buffer, ErrorPtr* error) {
+ std::unique_ptr<data_container::ReadOnlyStringRef> container{
+ new data_container::ReadOnlyStringRef{buffer}};
+ return CreateEx(std::move(container), 0, error);
+}
+
+StreamPtr MemoryStream::OpenCopyOf(std::string buffer, ErrorPtr* error) {
+ std::unique_ptr<data_container::ReadOnlyStringCopy> container{
+ new data_container::ReadOnlyStringCopy{std::move(buffer)}};
+ return CreateEx(std::move(container), 0, error);
+}
+
+StreamPtr MemoryStream::OpenRef(const char* buffer, ErrorPtr* error) {
+ return OpenRef(buffer, std::strlen(buffer), error);
+}
+
+StreamPtr MemoryStream::OpenCopyOf(const char* buffer, ErrorPtr* error) {
+ return OpenCopyOf(buffer, std::strlen(buffer), error);
+}
+
+StreamPtr MemoryStream::Create(size_t reserve_size, ErrorPtr* error) {
+ std::unique_ptr<data_container::ByteBuffer> container{
+ new data_container::ByteBuffer{reserve_size}};
+ return CreateEx(std::move(container), 0, error);
+}
+
+StreamPtr MemoryStream::CreateRef(std::string* buffer, ErrorPtr* error) {
+ std::unique_ptr<data_container::StringPtr> container{
+ new data_container::StringPtr{buffer}};
+ return CreateEx(std::move(container), 0, error);
+}
+
+StreamPtr MemoryStream::CreateRefForAppend(std::string* buffer,
+ ErrorPtr* error) {
+ std::unique_ptr<data_container::StringPtr> container{
+ new data_container::StringPtr{buffer}};
+ return CreateEx(std::move(container), buffer->size(), error);
+}
+
+StreamPtr MemoryStream::CreateEx(
+ std::unique_ptr<data_container::DataContainerInterface> container,
+ size_t stream_position,
+ ErrorPtr* error) {
+ ignore_result(error); // Unused.
+ return StreamPtr{new MemoryStream(std::move(container), stream_position)};
+}
+
+bool MemoryStream::IsOpen() const { return container_ != nullptr; }
+bool MemoryStream::CanRead() const { return IsOpen(); }
+
+bool MemoryStream::CanWrite() const {
+ return IsOpen() && !container_->IsReadOnly();
+}
+
+bool MemoryStream::CanSeek() const { return IsOpen(); }
+bool MemoryStream::CanGetSize() const { return IsOpen(); }
+
+uint64_t MemoryStream::GetSize() const {
+ return IsOpen() ? container_->GetSize() : 0;
+}
+
+bool MemoryStream::SetSizeBlocking(uint64_t size, ErrorPtr* error) {
+ if (!CheckContainer(error))
+ return false;
+ return container_->Resize(size, error);
+}
+
+uint64_t MemoryStream::GetRemainingSize() const {
+ uint64_t pos = GetPosition();
+ uint64_t size = GetSize();
+ return (pos < size) ? size - pos : 0;
+}
+
+uint64_t MemoryStream::GetPosition() const {
+ return IsOpen() ? stream_position_ : 0;
+}
+
+bool MemoryStream::Seek(int64_t offset,
+ Whence whence,
+ uint64_t* new_position,
+ ErrorPtr* error) {
+ uint64_t pos = 0;
+ if (!CheckContainer(error) ||
+ !stream_utils::CalculateStreamPosition(FROM_HERE, offset, whence,
+ stream_position_, GetSize(), &pos,
+ error)) {
+ return false;
+ }
+ if (pos > static_cast<uint64_t>(std::numeric_limits<size_t>::max())) {
+ // This can only be the case on 32 bit systems.
+ brillo::Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kInvalidParameter,
+ "Stream pointer position is outside allowed limits");
+ return false;
+ }
+
+ stream_position_ = static_cast<size_t>(pos);
+ if (new_position)
+ *new_position = stream_position_;
+ return true;
+}
+
+bool MemoryStream::ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) {
+ if (!CheckContainer(error))
+ return false;
+ size_t read = 0;
+ if (!container_->Read(buffer, size_to_read, stream_position_, &read, error))
+ return false;
+ stream_position_ += read;
+ *size_read = read;
+ if (end_of_stream)
+ *end_of_stream = (read == 0) && (size_to_read != 0);
+ return true;
+}
+
+bool MemoryStream::WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) {
+ if (!CheckContainer(error))
+ return false;
+ if (!container_->Write(buffer, size_to_write, stream_position_, size_written,
+ error)) {
+ return false;
+ }
+ stream_position_ += *size_written;
+ return true;
+}
+
+bool MemoryStream::FlushBlocking(ErrorPtr* error) {
+ return CheckContainer(error);
+}
+
+bool MemoryStream::CloseBlocking(ErrorPtr* error) {
+ ignore_result(error); // Unused.
+ container_.reset();
+ return true;
+}
+
+bool MemoryStream::CheckContainer(ErrorPtr* error) const {
+ return container_ || stream_utils::ErrorStreamClosed(FROM_HERE, error);
+}
+
+bool MemoryStream::WaitForData(AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* /* error */) {
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, mode));
+ return true;
+}
+
+bool MemoryStream::WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta /* timeout */,
+ AccessMode* out_mode,
+ ErrorPtr* /* error */) {
+ if (out_mode)
+ *out_mode = in_mode;
+ return true;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/memory_stream.h b/libbrillo/brillo/streams/memory_stream.h
new file mode 100644
index 0000000..b4927a8
--- /dev/null
+++ b/libbrillo/brillo/streams/memory_stream.h
@@ -0,0 +1,212 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_MEMORY_STREAM_H_
+#define LIBBRILLO_BRILLO_STREAMS_MEMORY_STREAM_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <brillo/brillo_export.h>
+#include <brillo/streams/memory_containers.h>
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+
+// MemoryStream is a brillo::Stream implementation for memory buffer. A number
+// of memory containers are supported, such as raw memory pointers, data stored
+// in std::vector and std::string.
+// MemoryStream offers support for constant read-only memory buffers as well as
+// for writable buffers that can grow when needed.
+// A memory stream is created by using the OpenNNN and CreateNNN factory methods
+// to construct a read-only and writable streams respectively.
+// The following factory methods overloads are provided:
+// - OpenRef - overloads for constructing the stream on a constant read-only
+// memory buffer that is not owned by the stream. The buffer
+// pointer/reference must remain valid throughout the lifetime
+// of the constructed stream object. The benefit of this is that
+// no data copying is performed and the underlying container can
+// be manipulated outside of the stream.
+// - OpenCopyOf - overloads to construct a stream that copies the data from the
+// memory buffer and maintains the copied data until the stream
+// is closed or destroyed. This makes it possible to construct
+// a read-only streams on transient data or for cases where
+// it is not possible or necessary to maintain the lifetime of
+// the underlying memory buffer.
+// - Create - creates a new internal memory buffer that can be written to
+// or read from using the stream I/O interface.
+// - CreateRef - constructs a read/write stream on a reference of data
+// container such as std::vector or std::string which must
+// remain valid throughout the lifetime of the memory stream.
+// The data already stored in the container is maintained,
+// however the stream pointer is set to the beginning of the
+// data when the stream is created.
+// - CreateRefForAppend - similar to CreateRef except that it automatically
+// positions the stream seek pointer at the end of the data,
+// which makes it possible to append more data to the existing
+// container.
+class BRILLO_EXPORT MemoryStream : public Stream {
+ public:
+ // == Construction ==========================================================
+
+ // Constructs a read-only stream on a generic memory buffer. The data
+ // pointed to by |buffer| will be copied and owned by the stream object.
+ static StreamPtr OpenCopyOf(const void* buffer, size_t size, ErrorPtr* error);
+ static StreamPtr OpenCopyOf(std::string buffer, ErrorPtr* error);
+ static StreamPtr OpenCopyOf(const char* buffer, ErrorPtr* error);
+ // Only vectors of char and uint8_t are supported.
+ template<typename T>
+ inline static StreamPtr OpenCopyOf(std::vector<T> buffer, ErrorPtr* error) {
+ std::unique_ptr<data_container::ReadOnlyVectorCopy<T>> container{
+ new data_container::ReadOnlyVectorCopy<T>{std::move(buffer)}};
+ return CreateEx(std::move(container), 0, error);
+ }
+
+ // Constructs a read-only stream on a generic memory buffer which is owned
+ // by the caller.
+ // ***WARNING***: The |buffer| pointer must be valid for as long as the stream
+ // object is alive. The stream does not do any additional lifetime management
+ // for the data pointed to by |buffer| and destroying that buffer before
+ // the stream is closed will lead to unexpected behavior.
+ static StreamPtr OpenRef(const void* buffer, size_t size, ErrorPtr* error);
+ static StreamPtr OpenRef(const std::string& buffer, ErrorPtr* error);
+ static StreamPtr OpenRef(const char* buffer, ErrorPtr* error);
+ // Only vectors of char and uint8_t are supported.
+ template<typename T>
+ inline static StreamPtr OpenRef(const std::vector<T>& buffer,
+ ErrorPtr* error) {
+ std::unique_ptr<data_container::ReadOnlyVectorRef<T>> container{
+ new data_container::ReadOnlyVectorRef<T>{buffer}};
+ return CreateEx(std::move(container), 0, error);
+ }
+
+ ///------------------------------------------------------------------------
+ // Creates new stream for reading/writing. This method creates an internal
+ // memory buffer and maintains it until the stream is closed. |reserve_size|
+ // parameter is a hint of the buffer size to pre-allocate. This does not
+ // affect the memory buffer reported size. The buffer can grow past that
+ // amount if needed.
+ static StreamPtr Create(size_t reserve_size, ErrorPtr* error);
+
+ inline static StreamPtr Create(ErrorPtr* error) { return Create(0, error); }
+
+ // Creates new stream for reading/writing stored in a string. The string
+ // |buffer| must remain valid during the lifetime of the stream.
+ // The stream pointer will be at the beginning of the string and the string's
+ // content is preserved.
+ static StreamPtr CreateRef(std::string* buffer, ErrorPtr* error);
+
+ // Creates new stream for reading/writing stored in a vector. The vector
+ // |buffer| must remain valid during the lifetime of the stream.
+ // The stream pointer will be at the beginning of the data and the vector's
+ // content is preserved.
+ // Only vectors of char and uint8_t are supported.
+ template<typename T>
+ static StreamPtr CreateRef(std::vector<T>* buffer, ErrorPtr* error) {
+ std::unique_ptr<data_container::VectorPtr<T>> container{
+ new data_container::VectorPtr<T>{buffer}};
+ return CreateEx(std::move(container), 0, error);
+ }
+
+ // Creates new stream for reading/writing stored in a string. The string
+ // |buffer| must remain valid during the lifetime of the stream.
+ // The stream pointer will be at the end of the string and the string's
+ // content is preserved.
+ static StreamPtr CreateRefForAppend(std::string* buffer, ErrorPtr* error);
+
+ // Creates new stream for reading/writing stored in a vector. The vector
+ // |buffer| must remain valid during the lifetime of the stream.
+ // The stream pointer will be at the end of the data and the vector's
+ // content is preserved.
+ // Only vectors of char and uint8_t are supported.
+ template<typename T>
+ static StreamPtr CreateRefForAppend(std::vector<T>* buffer, ErrorPtr* error) {
+ std::unique_ptr<data_container::VectorPtr<T>> container{
+ new data_container::VectorPtr<T>{buffer}};
+ return CreateEx(std::move(container), buffer->size() * sizeof(T), error);
+ }
+
+ ///------------------------------------------------------------------------
+ // Generic stream creation on a data container. Takes an arbitrary |container|
+ // and constructs a stream using it. The container determines the traits of
+ // the stream (e.g. whether it is read-only, what operations are supported
+ // and so on). |stream_position| is the current stream pointer position at
+ // creation time.
+ static StreamPtr CreateEx(
+ std::unique_ptr<data_container::DataContainerInterface> container,
+ size_t stream_position,
+ ErrorPtr* error);
+
+ // == Stream capabilities ===================================================
+ bool IsOpen() const override;
+ bool CanRead() const override;
+ bool CanWrite() const override;
+ bool CanSeek() const override;
+ bool CanGetSize() const override;
+
+ // == Stream size operations ================================================
+ uint64_t GetSize() const override;
+ bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override;
+ uint64_t GetRemainingSize() const override;
+
+ // == Seek operations =======================================================
+ uint64_t GetPosition() const override;
+ bool Seek(int64_t offset,
+ Whence whence,
+ uint64_t* new_position,
+ ErrorPtr* error) override;
+
+ // == Read operations =======================================================
+ bool ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) override;
+
+ // == Write operations ======================================================
+ bool WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) override;
+
+ // == Finalizing/closing streams ===========================================
+ bool FlushBlocking(ErrorPtr* error) override;
+ bool CloseBlocking(ErrorPtr* error) override;
+
+ // == Data availability monitoring ==========================================
+ bool WaitForData(AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) override;
+
+ bool WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) override;
+
+ private:
+ friend class MemoryStreamTest;
+
+ // Private constructor used by MemoryStream::OpenNNNN() and
+ // MemoryStream::CreateNNNN() factory methods.
+ MemoryStream(
+ std::unique_ptr<data_container::DataContainerInterface> container,
+ size_t stream_position);
+
+ // Checks if the stream has a valid container.
+ bool CheckContainer(ErrorPtr* error) const;
+
+ // Data container the stream is using to write and/or read data.
+ std::unique_ptr<data_container::DataContainerInterface> container_;
+
+ // The current stream pointer position.
+ size_t stream_position_{0};
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryStream);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_MEMORY_STREAM_H_
diff --git a/libbrillo/brillo/streams/memory_stream_unittest.cc b/libbrillo/brillo/streams/memory_stream_unittest.cc
new file mode 100644
index 0000000..75278f7
--- /dev/null
+++ b/libbrillo/brillo/streams/memory_stream_unittest.cc
@@ -0,0 +1,382 @@
+// Copyright 2015 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 <brillo/streams/memory_stream.h>
+
+#include <algorithm>
+#include <limits>
+#include <numeric>
+#include <string>
+#include <vector>
+
+#include <brillo/streams/stream_errors.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace brillo {
+
+namespace {
+
+int ReadByte(Stream* stream, brillo::ErrorPtr* error) {
+ uint8_t byte = 0;
+ return stream->ReadAllBlocking(&byte, sizeof(byte), error) ? byte : -1;
+}
+
+class MockMemoryContainer : public data_container::DataContainerInterface {
+ public:
+ MockMemoryContainer() = default;
+
+ MOCK_METHOD5(Read, bool(void*, size_t, size_t, size_t*, ErrorPtr*));
+ MOCK_METHOD5(Write, bool(const void*, size_t, size_t, size_t*, ErrorPtr*));
+ MOCK_METHOD2(Resize, bool(size_t, ErrorPtr*));
+ MOCK_CONST_METHOD0(GetSize, size_t());
+ MOCK_CONST_METHOD0(IsReadOnly, bool());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockMemoryContainer);
+};
+
+} // anonymous namespace
+
+class MemoryStreamTest : public testing::Test {
+ public:
+ void SetUp() override {
+ std::unique_ptr<MockMemoryContainer> container{new MockMemoryContainer{}};
+ stream_.reset(new MemoryStream{std::move(container), 0});
+ }
+
+ MockMemoryContainer& container_mock() {
+ return *static_cast<MockMemoryContainer*>(stream_->container_.get());
+ }
+
+ inline static void* IntToPtr(int addr) {
+ return reinterpret_cast<void*>(addr);
+ }
+
+ inline static const void* IntToConstPtr(int addr) {
+ return reinterpret_cast<const void*>(addr);
+ }
+
+ std::unique_ptr<MemoryStream> stream_;
+ // Dummy buffer pointer values to make sure that input pointer values
+ // are delegated to the stream interface without a change.
+ void* const test_read_buffer_ = IntToPtr(12345);
+ const void* const test_write_buffer_ = IntToConstPtr(67890);
+ // We limit the size of memory streams to be the maximum size of either of
+ // size_t (on 32 bit platforms) or the size of signed 64 bit integer.
+ const size_t kSizeMax =
+ std::min<uint64_t>(std::numeric_limits<size_t>::max(),
+ std::numeric_limits<int64_t>::max());
+};
+
+TEST_F(MemoryStreamTest, CanRead) {
+ EXPECT_TRUE(stream_->CanRead());
+}
+
+TEST_F(MemoryStreamTest, CanWrite) {
+ EXPECT_CALL(container_mock(), IsReadOnly())
+ .WillOnce(Return(true))
+ .WillOnce(Return(false));
+
+ EXPECT_FALSE(stream_->CanWrite());
+ EXPECT_TRUE(stream_->CanWrite());
+}
+
+TEST_F(MemoryStreamTest, CanSeek) {
+ EXPECT_TRUE(stream_->CanSeek());
+}
+
+TEST_F(MemoryStreamTest, GetSize) {
+ EXPECT_CALL(container_mock(), GetSize())
+ .WillOnce(Return(0))
+ .WillOnce(Return(1234))
+ .WillOnce(Return(kSizeMax));
+
+ EXPECT_EQ(0, stream_->GetSize());
+ EXPECT_EQ(1234, stream_->GetSize());
+ EXPECT_EQ(kSizeMax, stream_->GetSize());
+}
+
+TEST_F(MemoryStreamTest, SetSizeBlocking) {
+ EXPECT_CALL(container_mock(), Resize(0, _)).WillOnce(Return(true));
+
+ ErrorPtr error;
+ EXPECT_TRUE(stream_->SetSizeBlocking(0, &error));
+ EXPECT_EQ(nullptr, error.get());
+
+ EXPECT_CALL(container_mock(), Resize(kSizeMax, nullptr))
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(stream_->SetSizeBlocking(kSizeMax, nullptr));
+}
+
+TEST_F(MemoryStreamTest, SeekAndGetPosition) {
+ EXPECT_EQ(0, stream_->GetPosition());
+
+ EXPECT_CALL(container_mock(), GetSize()).WillRepeatedly(Return(200));
+
+ ErrorPtr error;
+ uint64_t new_pos = 0;
+ EXPECT_TRUE(stream_->Seek(2, Stream::Whence::FROM_BEGIN, &new_pos, &error));
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_EQ(2, new_pos);
+ EXPECT_EQ(2, stream_->GetPosition());
+ EXPECT_TRUE(stream_->Seek(2, Stream::Whence::FROM_CURRENT, &new_pos, &error));
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_EQ(4, new_pos);
+ EXPECT_EQ(4, stream_->GetPosition());
+
+ EXPECT_TRUE(stream_->Seek(-2, Stream::Whence::FROM_END, nullptr, nullptr));
+ EXPECT_EQ(198, stream_->GetPosition());
+
+ EXPECT_CALL(container_mock(), GetSize()).WillOnce(Return(kSizeMax));
+ EXPECT_TRUE(stream_->Seek(0, Stream::Whence::FROM_END, nullptr, nullptr));
+ EXPECT_EQ(kSizeMax, stream_->GetPosition());
+}
+
+TEST_F(MemoryStreamTest, ReadNonBlocking) {
+ size_t read = 0;
+ bool eos = false;
+
+ EXPECT_CALL(container_mock(), Read(test_read_buffer_, 10, 0, _, nullptr))
+ .WillOnce(DoAll(SetArgPointee<3>(5), Return(true)));
+
+ EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 10, &read, &eos,
+ nullptr));
+ EXPECT_EQ(5, read);
+ EXPECT_EQ(5, stream_->GetPosition());
+ EXPECT_FALSE(eos);
+
+ EXPECT_CALL(container_mock(), Read(test_read_buffer_, 100, 5, _, nullptr))
+ .WillOnce(DoAll(SetArgPointee<3>(100), Return(true)));
+
+ EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 100, &read, &eos,
+ nullptr));
+ EXPECT_EQ(100, read);
+ EXPECT_EQ(105, stream_->GetPosition());
+ EXPECT_FALSE(eos);
+
+ EXPECT_CALL(container_mock(), Read(test_read_buffer_, 10, 105, _, nullptr))
+ .WillOnce(DoAll(SetArgPointee<3>(0), Return(true)));
+
+ EXPECT_TRUE(stream_->ReadNonBlocking(test_read_buffer_, 10, &read, &eos,
+ nullptr));
+ EXPECT_EQ(0, read);
+ EXPECT_EQ(105, stream_->GetPosition());
+ EXPECT_TRUE(eos);
+}
+
+TEST_F(MemoryStreamTest, WriteNonBlocking) {
+ size_t written = 0;
+
+ EXPECT_CALL(container_mock(), Write(test_write_buffer_, 10, 0, _, nullptr))
+ .WillOnce(DoAll(SetArgPointee<3>(5), Return(true)));
+
+ EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 10, &written,
+ nullptr));
+ EXPECT_EQ(5, written);
+ EXPECT_EQ(5, stream_->GetPosition());
+
+ EXPECT_CALL(container_mock(), Write(test_write_buffer_, 100, 5, _, nullptr))
+ .WillOnce(DoAll(SetArgPointee<3>(100), Return(true)));
+
+ EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 100, &written,
+ nullptr));
+ EXPECT_EQ(100, written);
+ EXPECT_EQ(105, stream_->GetPosition());
+
+ EXPECT_CALL(container_mock(), Write(test_write_buffer_, 10, 105, _, nullptr))
+ .WillOnce(DoAll(SetArgPointee<3>(10), Return(true)));
+
+ EXPECT_TRUE(stream_->WriteNonBlocking(test_write_buffer_, 10, &written,
+ nullptr));
+ EXPECT_EQ(115, stream_->GetPosition());
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Factory method tests.
+TEST(MemoryStream, OpenBinary) {
+ char buffer[] = {1, 2, 3};
+ StreamPtr stream = MemoryStream::OpenRef(buffer, sizeof(buffer), nullptr);
+ buffer[0] = 5;
+ EXPECT_EQ(3, stream->GetSize());
+ EXPECT_EQ(5, ReadByte(stream.get(), nullptr));
+ EXPECT_EQ(2, ReadByte(stream.get(), nullptr));
+ EXPECT_EQ(3, ReadByte(stream.get(), nullptr));
+ brillo::ErrorPtr error;
+ EXPECT_EQ(-1, ReadByte(stream.get(), &error));
+ EXPECT_EQ(errors::stream::kPartialData, error->GetCode());
+ EXPECT_EQ("Reading past the end of stream", error->GetMessage());
+}
+
+TEST(MemoryStream, OpenBinaryCopy) {
+ char buffer[] = {1, 2, 3};
+ StreamPtr stream = MemoryStream::OpenCopyOf(buffer, sizeof(buffer), nullptr);
+ buffer[0] = 5;
+ EXPECT_EQ(3, stream->GetSize());
+ EXPECT_EQ(1, ReadByte(stream.get(), nullptr));
+ EXPECT_EQ(2, ReadByte(stream.get(), nullptr));
+ EXPECT_EQ(3, ReadByte(stream.get(), nullptr));
+ brillo::ErrorPtr error;
+ EXPECT_EQ(-1, ReadByte(stream.get(), &error));
+ EXPECT_EQ(errors::stream::kPartialData, error->GetCode());
+ EXPECT_EQ("Reading past the end of stream", error->GetMessage());
+}
+
+TEST(MemoryStream, OpenString) {
+ std::string str("abcd");
+ StreamPtr stream = MemoryStream::OpenRef(str, nullptr);
+ str[0] = 'A';
+ EXPECT_EQ(4, stream->GetSize());
+ EXPECT_EQ('A', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('b', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('c', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('d', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ(-1, ReadByte(stream.get(), nullptr));
+}
+
+TEST(MemoryStream, OpenStringCopy) {
+ std::string str("abcd");
+ StreamPtr stream = MemoryStream::OpenCopyOf(str, nullptr);
+ str[0] = 'A';
+ EXPECT_EQ(4, stream->GetSize());
+ EXPECT_EQ('a', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('b', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('c', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('d', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ(-1, ReadByte(stream.get(), nullptr));
+}
+
+TEST(MemoryStream, OpenCharBuf) {
+ char str[] = "abcd";
+ StreamPtr stream = MemoryStream::OpenRef(str, nullptr);
+ str[0] = 'A';
+ EXPECT_EQ(4, stream->GetSize());
+ EXPECT_EQ('A', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('b', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('c', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('d', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ(-1, ReadByte(stream.get(), nullptr));
+}
+
+TEST(MemoryStream, OpenCharBufCopy) {
+ char str[] = "abcd";
+ StreamPtr stream = MemoryStream::OpenCopyOf(str, nullptr);
+ str[0] = 'A';
+ EXPECT_EQ(4, stream->GetSize());
+ EXPECT_EQ('a', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('b', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('c', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('d', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ(-1, ReadByte(stream.get(), nullptr));
+}
+
+TEST(MemoryStream, OpenVector) {
+ std::vector<char> data = {'a', 'b', 'c', 'd'};
+ StreamPtr stream = MemoryStream::OpenRef(data, nullptr);
+ data[0] = 'A';
+ EXPECT_EQ(4, stream->GetSize());
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(4, stream->GetRemainingSize());
+ EXPECT_EQ('A', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('b', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('c', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('d', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ(4, stream->GetPosition());
+ EXPECT_EQ(4, stream->GetSize());
+ EXPECT_EQ(0, stream->GetRemainingSize());
+}
+
+TEST(MemoryStream, OpenVectorCopy) {
+ std::vector<uint8_t> data = {'a', 'b', 'c', 'd'};
+ StreamPtr stream = MemoryStream::OpenCopyOf(data, nullptr);
+ data[0] = 'A';
+ EXPECT_EQ(4, stream->GetSize());
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(4, stream->GetRemainingSize());
+ EXPECT_EQ('a', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('b', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('c', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ('d', ReadByte(stream.get(), nullptr));
+ EXPECT_EQ(4, stream->GetPosition());
+ EXPECT_EQ(4, stream->GetSize());
+ EXPECT_EQ(0, stream->GetRemainingSize());
+}
+
+TEST(MemoryStream, CreateVector) {
+ std::vector<uint8_t> buffer;
+ StreamPtr stream = MemoryStream::CreateRef(&buffer, nullptr);
+ EXPECT_TRUE(buffer.empty());
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(0, stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+
+ buffer.resize(5);
+ std::iota(buffer.begin(), buffer.end(), 0);
+ stream = MemoryStream::CreateRef(&buffer, nullptr);
+ EXPECT_FALSE(buffer.empty());
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(5, stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+
+ stream = MemoryStream::CreateRefForAppend(&buffer, nullptr);
+ EXPECT_FALSE(buffer.empty());
+ EXPECT_EQ(5, stream->GetPosition());
+ EXPECT_EQ(5, stream->GetSize());
+ EXPECT_TRUE(stream->WriteAllBlocking("abcde", 5, nullptr));
+ EXPECT_FALSE(buffer.empty());
+ EXPECT_EQ(10, stream->GetPosition());
+ EXPECT_EQ(10, stream->GetSize());
+ EXPECT_TRUE(stream->SetPosition(0, nullptr));
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(10, stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+
+ EXPECT_EQ(10, buffer.size());
+ EXPECT_EQ((std::vector<uint8_t>{0, 1, 2, 3, 4, 'a', 'b', 'c', 'd', 'e'}),
+ buffer);
+
+ stream = MemoryStream::OpenRef(buffer, nullptr);
+ EXPECT_FALSE(buffer.empty());
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(10, stream->GetSize());
+}
+
+TEST(MemoryStream, CreateString) {
+ std::string buffer;
+ StreamPtr stream = MemoryStream::CreateRef(&buffer, nullptr);
+ EXPECT_TRUE(buffer.empty());
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(0, stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+
+ buffer = "abc";
+ stream = MemoryStream::CreateRef(&buffer, nullptr);
+ EXPECT_FALSE(buffer.empty());
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(3, stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+
+ stream = MemoryStream::CreateRefForAppend(&buffer, nullptr);
+ EXPECT_FALSE(buffer.empty());
+ EXPECT_EQ(3, stream->GetPosition());
+ EXPECT_EQ(3, stream->GetSize());
+ EXPECT_TRUE(stream->WriteAllBlocking("d_1234", 6, nullptr));
+ EXPECT_FALSE(buffer.empty());
+ EXPECT_EQ(9, stream->GetPosition());
+ EXPECT_EQ(9, stream->GetSize());
+ EXPECT_TRUE(stream->SetPosition(0, nullptr));
+ EXPECT_EQ(0, stream->GetPosition());
+ EXPECT_EQ(9, stream->GetSize());
+ EXPECT_TRUE(stream->CloseBlocking(nullptr));
+ EXPECT_EQ(9, buffer.size());
+ EXPECT_EQ("abcd_1234", buffer);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/mock_stream.h b/libbrillo/brillo/streams/mock_stream.h
new file mode 100644
index 0000000..934912a
--- /dev/null
+++ b/libbrillo/brillo/streams/mock_stream.h
@@ -0,0 +1,75 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_MOCK_STREAM_H_
+#define LIBBRILLO_BRILLO_STREAMS_MOCK_STREAM_H_
+
+#include <gmock/gmock.h>
+
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+
+// Mock Stream implementation for testing.
+class MockStream : public Stream {
+ public:
+ MockStream() = default;
+
+ MOCK_CONST_METHOD0(IsOpen, bool());
+ MOCK_CONST_METHOD0(CanRead, bool());
+ MOCK_CONST_METHOD0(CanWrite, bool());
+ MOCK_CONST_METHOD0(CanSeek, bool());
+ MOCK_CONST_METHOD0(CanGetSize, bool());
+
+ MOCK_CONST_METHOD0(GetSize, uint64_t());
+ MOCK_METHOD2(SetSizeBlocking, bool(uint64_t, ErrorPtr*));
+ MOCK_CONST_METHOD0(GetRemainingSize, uint64_t());
+
+ MOCK_CONST_METHOD0(GetPosition, uint64_t());
+ MOCK_METHOD4(Seek, bool(int64_t, Whence, uint64_t*, ErrorPtr*));
+
+ MOCK_METHOD5(ReadAsync, bool(void*,
+ size_t,
+ const base::Callback<void(size_t)>&,
+ const ErrorCallback&,
+ ErrorPtr*));
+ MOCK_METHOD5(ReadAllAsync, bool(void*,
+ size_t,
+ const base::Closure&,
+ const ErrorCallback&,
+ ErrorPtr*));
+ MOCK_METHOD5(ReadNonBlocking, bool(void*, size_t, size_t*, bool*, ErrorPtr*));
+ MOCK_METHOD4(ReadBlocking, bool(void*, size_t, size_t*, ErrorPtr*));
+ MOCK_METHOD3(ReadAllBlocking, bool(void*, size_t, ErrorPtr*));
+
+ MOCK_METHOD5(WriteAsync, bool(const void*,
+ size_t,
+ const base::Callback<void(size_t)>&,
+ const ErrorCallback&,
+ ErrorPtr*));
+ MOCK_METHOD5(WriteAllAsync, bool(const void*,
+ size_t,
+ const base::Closure&,
+ const ErrorCallback&,
+ ErrorPtr*));
+ MOCK_METHOD4(WriteNonBlocking, bool(const void*, size_t, size_t*, ErrorPtr*));
+ MOCK_METHOD4(WriteBlocking, bool(const void*, size_t, size_t*, ErrorPtr*));
+ MOCK_METHOD3(WriteAllBlocking, bool(const void*, size_t, ErrorPtr*));
+
+ MOCK_METHOD1(FlushBlocking, bool(ErrorPtr*));
+ MOCK_METHOD1(CloseBlocking, bool(ErrorPtr*));
+
+ MOCK_METHOD3(WaitForData, bool(AccessMode,
+ const base::Callback<void(AccessMode)>&,
+ ErrorPtr*));
+ MOCK_METHOD4(WaitForDataBlocking,
+ bool(AccessMode, base::TimeDelta, AccessMode*, ErrorPtr*));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockStream);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_MOCK_STREAM_H_
diff --git a/libbrillo/brillo/streams/openssl_stream_bio.cc b/libbrillo/brillo/streams/openssl_stream_bio.cc
new file mode 100644
index 0000000..a63d9c0
--- /dev/null
+++ b/libbrillo/brillo/streams/openssl_stream_bio.cc
@@ -0,0 +1,101 @@
+// Copyright 2015 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 <brillo/streams/openssl_stream_bio.h>
+
+#include <openssl/bio.h>
+
+#include <base/numerics/safe_conversions.h>
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+
+namespace {
+
+// Internal functions for implementing OpenSSL BIO on brillo::Stream.
+int stream_write(BIO* bio, const char* buf, int size) {
+ brillo::Stream* stream = static_cast<brillo::Stream*>(bio->ptr);
+ size_t written = 0;
+ BIO_clear_retry_flags(bio);
+ if (!stream->WriteNonBlocking(buf, size, &written, nullptr))
+ return -1;
+
+ if (written == 0) {
+ // Socket's output buffer is full, try again later.
+ BIO_set_retry_write(bio);
+ return -1;
+ }
+ return base::checked_cast<int>(written);
+}
+
+int stream_read(BIO* bio, char* buf, int size) {
+ brillo::Stream* stream = static_cast<brillo::Stream*>(bio->ptr);
+ size_t read = 0;
+ BIO_clear_retry_flags(bio);
+ bool eos = false;
+ if (!stream->ReadNonBlocking(buf, size, &read, &eos, nullptr))
+ return -1;
+
+ if (read == 0 && !eos) {
+ // If no data is available on the socket and it is still not closed,
+ // ask OpenSSL to try again later.
+ BIO_set_retry_read(bio);
+ return -1;
+ }
+ return base::checked_cast<int>(read);
+}
+
+// NOLINTNEXTLINE(runtime/int)
+long stream_ctrl(BIO* bio, int cmd, long /* num */, void* /* ptr */) {
+ if (cmd == BIO_CTRL_FLUSH) {
+ brillo::Stream* stream = static_cast<brillo::Stream*>(bio->ptr);
+ return stream->FlushBlocking(nullptr) ? 1 : 0;
+ }
+ return 0;
+}
+
+int stream_new(BIO* bio) {
+ bio->shutdown = 0; // By default do not close underlying stream on shutdown.
+ bio->init = 0;
+ bio->num = -1; // not used.
+ return 1;
+}
+
+int stream_free(BIO* bio) {
+ if (!bio)
+ return 0;
+
+ if (bio->init) {
+ bio->ptr = nullptr;
+ bio->init = 0;
+ }
+ return 1;
+}
+
+// BIO_METHOD structure describing the BIO built on top of brillo::Stream.
+BIO_METHOD stream_method = {
+ 0x7F | BIO_TYPE_SOURCE_SINK, // type: 0x7F is an arbitrary unused type ID.
+ "stream", // name
+ stream_write, // write function
+ stream_read, // read function
+ nullptr, // puts function, not implemented
+ nullptr, // gets function, not implemented
+ stream_ctrl, // control function
+ stream_new, // creation
+ stream_free, // free
+ nullptr, // callback function, not used
+};
+
+} // anonymous namespace
+
+BIO* BIO_new_stream(brillo::Stream* stream) {
+ BIO* bio = BIO_new(&stream_method);
+ if (bio) {
+ bio->ptr = stream;
+ bio->init = 1;
+ }
+ return bio;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/openssl_stream_bio.h b/libbrillo/brillo/streams/openssl_stream_bio.h
new file mode 100644
index 0000000..549c99c
--- /dev/null
+++ b/libbrillo/brillo/streams/openssl_stream_bio.h
@@ -0,0 +1,27 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_OPENSSL_STREAM_BIO_H_
+#define LIBBRILLO_BRILLO_STREAMS_OPENSSL_STREAM_BIO_H_
+
+#include <brillo/brillo_export.h>
+
+// Forward-declare BIO as an alias to OpenSSL's internal bio_st structure.
+using BIO = struct bio_st;
+
+namespace brillo {
+
+class Stream;
+
+// Creates a new BIO that uses the brillo::Stream as the back-end storage.
+// The created BIO does *NOT* own the |stream| and the stream must out-live
+// the BIO.
+// At the moment, only BIO_read and BIO_write operations are supported as well
+// as BIO_flush. More functionality could be added to this when/if needed.
+// The returned BIO performs *NON-BLOCKING* IO on the underlying stream.
+BRILLO_EXPORT BIO* BIO_new_stream(brillo::Stream* stream);
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_OPENSSL_STREAM_BIO_H_
diff --git a/libbrillo/brillo/streams/openssl_stream_bio_unittests.cc b/libbrillo/brillo/streams/openssl_stream_bio_unittests.cc
new file mode 100644
index 0000000..a80710d
--- /dev/null
+++ b/libbrillo/brillo/streams/openssl_stream_bio_unittests.cc
@@ -0,0 +1,125 @@
+// Copyright 2015 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 <brillo/streams/openssl_stream_bio.h>
+
+#include <memory>
+#include <openssl/bio.h>
+
+#include <brillo/streams/mock_stream.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::StrictMock;
+using testing::_;
+
+namespace brillo {
+
+class StreamBIOTest : public testing::Test {
+ public:
+ void SetUp() override {
+ stream_.reset(new StrictMock<MockStream>{});
+ bio_ = BIO_new_stream(stream_.get());
+ }
+
+ void TearDown() override {
+ BIO_free(bio_);
+ bio_ = nullptr;
+ stream_.reset();
+ }
+
+ std::unique_ptr<StrictMock<MockStream>> stream_;
+ BIO* bio_{nullptr};
+};
+
+TEST_F(StreamBIOTest, ReadFull) {
+ char buffer[10];
+ EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(10),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_EQ(10, BIO_read(bio_, buffer, sizeof(buffer)));
+}
+
+TEST_F(StreamBIOTest, ReadPartial) {
+ char buffer[10];
+ EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(3),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_EQ(3, BIO_read(bio_, buffer, sizeof(buffer)));
+}
+
+TEST_F(StreamBIOTest, ReadWouldBlock) {
+ char buffer[10];
+ EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_EQ(-1, BIO_read(bio_, buffer, sizeof(buffer)));
+ EXPECT_TRUE(BIO_should_retry(bio_));
+}
+
+TEST_F(StreamBIOTest, ReadEndOfStream) {
+ char buffer[10];
+ EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0),
+ SetArgPointee<3>(true),
+ Return(true)));
+ EXPECT_EQ(0, BIO_read(bio_, buffer, sizeof(buffer)));
+ EXPECT_FALSE(BIO_should_retry(bio_));
+}
+
+TEST_F(StreamBIOTest, ReadError) {
+ char buffer[10];
+ EXPECT_CALL(*stream_, ReadNonBlocking(buffer, 10, _, _, _))
+ .WillOnce(Return(false));
+ EXPECT_EQ(-1, BIO_read(bio_, buffer, sizeof(buffer)));
+ EXPECT_FALSE(BIO_should_retry(bio_));
+}
+
+TEST_F(StreamBIOTest, WriteFull) {
+ char buffer[10] = {};
+ EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(10), Return(true)));
+ EXPECT_EQ(10, BIO_write(bio_, buffer, sizeof(buffer)));
+}
+
+TEST_F(StreamBIOTest, WritePartial) {
+ char buffer[10] = {};
+ EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(3), Return(true)));
+ EXPECT_EQ(3, BIO_write(bio_, buffer, sizeof(buffer)));
+}
+
+TEST_F(StreamBIOTest, WriteWouldBlock) {
+ char buffer[10] = {};
+ EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+ EXPECT_EQ(-1, BIO_write(bio_, buffer, sizeof(buffer)));
+ EXPECT_TRUE(BIO_should_retry(bio_));
+}
+
+TEST_F(StreamBIOTest, WriteError) {
+ char buffer[10] = {};
+ EXPECT_CALL(*stream_, WriteNonBlocking(buffer, 10, _, _))
+ .WillOnce(Return(false));
+ EXPECT_EQ(-1, BIO_write(bio_, buffer, sizeof(buffer)));
+ EXPECT_FALSE(BIO_should_retry(bio_));
+}
+
+TEST_F(StreamBIOTest, FlushSuccess) {
+ EXPECT_CALL(*stream_, FlushBlocking(_)).WillOnce(Return(true));
+ EXPECT_EQ(1, BIO_flush(bio_));
+}
+
+TEST_F(StreamBIOTest, FlushError) {
+ EXPECT_CALL(*stream_, FlushBlocking(_)).WillOnce(Return(false));
+ EXPECT_EQ(0, BIO_flush(bio_));
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/stream.cc b/libbrillo/brillo/streams/stream.cc
new file mode 100644
index 0000000..6a40c00
--- /dev/null
+++ b/libbrillo/brillo/streams/stream.cc
@@ -0,0 +1,392 @@
+// Copyright 2015 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 <brillo/streams/stream.h>
+
+#include <algorithm>
+
+#include <base/bind.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/pointer_utils.h>
+#include <brillo/streams/stream_errors.h>
+#include <brillo/streams/stream_utils.h>
+
+namespace brillo {
+
+bool Stream::TruncateBlocking(ErrorPtr* error) {
+ return SetSizeBlocking(GetPosition(), error);
+}
+
+bool Stream::SetPosition(uint64_t position, ErrorPtr* error) {
+ if (!stream_utils::CheckInt64Overflow(FROM_HERE, position, 0, error))
+ return false;
+ return Seek(position, Whence::FROM_BEGIN, nullptr, error);
+}
+
+bool Stream::ReadAsync(void* buffer,
+ size_t size_to_read,
+ const base::Callback<void(size_t)>& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error) {
+ if (is_async_read_pending_) {
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kOperationNotSupported,
+ "Another asynchronous operation is still pending");
+ return false;
+ }
+
+ auto callback = base::Bind(&Stream::IgnoreEOSCallback, success_callback);
+ // If we can read some data right away non-blocking we should still run the
+ // callback from the main loop, so we pass true here for force_async_callback.
+ return ReadAsyncImpl(buffer, size_to_read, callback, error_callback, error,
+ true);
+}
+
+bool Stream::ReadAllAsync(void* buffer,
+ size_t size_to_read,
+ const base::Closure& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error) {
+ if (is_async_read_pending_) {
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kOperationNotSupported,
+ "Another asynchronous operation is still pending");
+ return false;
+ }
+
+ auto callback = base::Bind(&Stream::ReadAllAsyncCallback,
+ weak_ptr_factory_.GetWeakPtr(), buffer,
+ size_to_read, success_callback, error_callback);
+ return ReadAsyncImpl(buffer, size_to_read, callback, error_callback, error,
+ true);
+}
+
+bool Stream::ReadBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ ErrorPtr* error) {
+ for (;;) {
+ bool eos = false;
+ if (!ReadNonBlocking(buffer, size_to_read, size_read, &eos, error))
+ return false;
+
+ if (*size_read > 0 || eos)
+ break;
+
+ if (!WaitForDataBlocking(AccessMode::READ, base::TimeDelta::Max(), nullptr,
+ error)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Stream::ReadAllBlocking(void* buffer,
+ size_t size_to_read,
+ ErrorPtr* error) {
+ while (size_to_read > 0) {
+ size_t size_read = 0;
+ if (!ReadBlocking(buffer, size_to_read, &size_read, error))
+ return false;
+
+ if (size_read == 0)
+ return stream_utils::ErrorReadPastEndOfStream(FROM_HERE, error);
+
+ size_to_read -= size_read;
+ buffer = AdvancePointer(buffer, size_read);
+ }
+ return true;
+}
+
+bool Stream::WriteAsync(const void* buffer,
+ size_t size_to_write,
+ const base::Callback<void(size_t)>& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error) {
+ if (is_async_write_pending_) {
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kOperationNotSupported,
+ "Another asynchronous operation is still pending");
+ return false;
+ }
+ // If we can read some data right away non-blocking we should still run the
+ // callback from the main loop, so we pass true here for force_async_callback.
+ return WriteAsyncImpl(buffer, size_to_write, success_callback, error_callback,
+ error, true);
+}
+
+bool Stream::WriteAllAsync(const void* buffer,
+ size_t size_to_write,
+ const base::Closure& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error) {
+ if (is_async_write_pending_) {
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kOperationNotSupported,
+ "Another asynchronous operation is still pending");
+ return false;
+ }
+
+ auto callback = base::Bind(&Stream::WriteAllAsyncCallback,
+ weak_ptr_factory_.GetWeakPtr(), buffer,
+ size_to_write, success_callback, error_callback);
+ return WriteAsyncImpl(buffer, size_to_write, callback, error_callback, error,
+ true);
+}
+
+bool Stream::WriteBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) {
+ for (;;) {
+ if (!WriteNonBlocking(buffer, size_to_write, size_written, error))
+ return false;
+
+ if (*size_written > 0 || size_to_write == 0)
+ break;
+
+ if (!WaitForDataBlocking(AccessMode::WRITE, base::TimeDelta::Max(), nullptr,
+ error)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Stream::WriteAllBlocking(const void* buffer,
+ size_t size_to_write,
+ ErrorPtr* error) {
+ while (size_to_write > 0) {
+ size_t size_written = 0;
+ if (!WriteBlocking(buffer, size_to_write, &size_written, error))
+ return false;
+
+ if (size_written == 0) {
+ Error::AddTo(error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kPartialData,
+ "Failed to write all the data");
+ return false;
+ }
+ size_to_write -= size_written;
+ buffer = AdvancePointer(buffer, size_written);
+ }
+ return true;
+}
+
+bool Stream::FlushAsync(const base::Closure& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* /* error */) {
+ auto callback = base::Bind(&Stream::FlushAsyncCallback,
+ weak_ptr_factory_.GetWeakPtr(),
+ success_callback, error_callback);
+ MessageLoop::current()->PostTask(FROM_HERE, callback);
+ return true;
+}
+
+void Stream::IgnoreEOSCallback(
+ const base::Callback<void(size_t)>& success_callback,
+ size_t bytes,
+ bool /* eos */) {
+ success_callback.Run(bytes);
+}
+
+bool Stream::ReadAsyncImpl(
+ void* buffer,
+ size_t size_to_read,
+ const base::Callback<void(size_t, bool)>& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error,
+ bool force_async_callback) {
+ CHECK(!is_async_read_pending_);
+ // We set this value to true early in the function so calling others will
+ // prevent us from calling WaitForData() to make calls to
+ // ReadAsync() fail while we run WaitForData().
+ is_async_read_pending_ = true;
+
+ size_t read = 0;
+ bool eos = false;
+ if (!ReadNonBlocking(buffer, size_to_read, &read, &eos, error))
+ return false;
+
+ if (read > 0 || eos) {
+ if (force_async_callback) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Stream::OnReadAsyncDone, weak_ptr_factory_.GetWeakPtr(),
+ success_callback, read, eos));
+ } else {
+ is_async_read_pending_ = false;
+ success_callback.Run(read, eos);
+ }
+ return true;
+ }
+
+ is_async_read_pending_ = WaitForData(
+ AccessMode::READ,
+ base::Bind(&Stream::OnReadAvailable, weak_ptr_factory_.GetWeakPtr(),
+ buffer, size_to_read, success_callback, error_callback),
+ error);
+ return is_async_read_pending_;
+}
+
+void Stream::OnReadAsyncDone(
+ const base::Callback<void(size_t, bool)>& success_callback,
+ size_t bytes_read,
+ bool eos) {
+ is_async_read_pending_ = false;
+ success_callback.Run(bytes_read, eos);
+}
+
+void Stream::OnReadAvailable(
+ void* buffer,
+ size_t size_to_read,
+ const base::Callback<void(size_t, bool)>& success_callback,
+ const ErrorCallback& error_callback,
+ AccessMode mode) {
+ CHECK(stream_utils::IsReadAccessMode(mode));
+ CHECK(is_async_read_pending_);
+ is_async_read_pending_ = false;
+ ErrorPtr error;
+ // Just reschedule the read operation but don't need to run the callback from
+ // the main loop since we are already running on a callback.
+ if (!ReadAsyncImpl(buffer, size_to_read, success_callback, error_callback,
+ &error, false)) {
+ error_callback.Run(error.get());
+ }
+}
+
+bool Stream::WriteAsyncImpl(
+ const void* buffer,
+ size_t size_to_write,
+ const base::Callback<void(size_t)>& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error,
+ bool force_async_callback) {
+ CHECK(!is_async_write_pending_);
+ // We set this value to true early in the function so calling others will
+ // prevent us from calling WaitForData() to make calls to
+ // ReadAsync() fail while we run WaitForData().
+ is_async_write_pending_ = true;
+
+ size_t written = 0;
+ if (!WriteNonBlocking(buffer, size_to_write, &written, error))
+ return false;
+
+ if (written > 0) {
+ if (force_async_callback) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Stream::OnWriteAsyncDone, weak_ptr_factory_.GetWeakPtr(),
+ success_callback, written));
+ } else {
+ is_async_write_pending_ = false;
+ success_callback.Run(written);
+ }
+ return true;
+ }
+ is_async_write_pending_ = WaitForData(
+ AccessMode::WRITE,
+ base::Bind(&Stream::OnWriteAvailable, weak_ptr_factory_.GetWeakPtr(),
+ buffer, size_to_write, success_callback, error_callback),
+ error);
+ return is_async_write_pending_;
+}
+
+void Stream::OnWriteAsyncDone(
+ const base::Callback<void(size_t)>& success_callback,
+ size_t size_written) {
+ is_async_write_pending_ = false;
+ success_callback.Run(size_written);
+}
+
+void Stream::OnWriteAvailable(
+ const void* buffer,
+ size_t size,
+ const base::Callback<void(size_t)>& success_callback,
+ const ErrorCallback& error_callback,
+ AccessMode mode) {
+ CHECK(stream_utils::IsWriteAccessMode(mode));
+ CHECK(is_async_write_pending_);
+ is_async_write_pending_ = false;
+ ErrorPtr error;
+ // Just reschedule the read operation but don't need to run the callback from
+ // the main loop since we are already running on a callback.
+ if (!WriteAsyncImpl(buffer, size, success_callback, error_callback, &error,
+ false)) {
+ error_callback.Run(error.get());
+ }
+}
+
+void Stream::ReadAllAsyncCallback(void* buffer,
+ size_t size_to_read,
+ const base::Closure& success_callback,
+ const ErrorCallback& error_callback,
+ size_t size_read,
+ bool eos) {
+ ErrorPtr error;
+ size_to_read -= size_read;
+ if (size_to_read != 0 && eos) {
+ stream_utils::ErrorReadPastEndOfStream(FROM_HERE, &error);
+ error_callback.Run(error.get());
+ return;
+ }
+
+ if (size_to_read) {
+ buffer = AdvancePointer(buffer, size_read);
+ auto callback = base::Bind(&Stream::ReadAllAsyncCallback,
+ weak_ptr_factory_.GetWeakPtr(), buffer,
+ size_to_read, success_callback, error_callback);
+ if (!ReadAsyncImpl(buffer, size_to_read, callback, error_callback, &error,
+ false)) {
+ error_callback.Run(error.get());
+ }
+ } else {
+ success_callback.Run();
+ }
+}
+
+void Stream::WriteAllAsyncCallback(const void* buffer,
+ size_t size_to_write,
+ const base::Closure& success_callback,
+ const ErrorCallback& error_callback,
+ size_t size_written) {
+ ErrorPtr error;
+ if (size_to_write != 0 && size_written == 0) {
+ Error::AddTo(&error, FROM_HERE, errors::stream::kDomain,
+ errors::stream::kPartialData, "Failed to write all the data");
+ error_callback.Run(error.get());
+ return;
+ }
+ size_to_write -= size_written;
+ if (size_to_write) {
+ buffer = AdvancePointer(buffer, size_written);
+ auto callback = base::Bind(&Stream::WriteAllAsyncCallback,
+ weak_ptr_factory_.GetWeakPtr(), buffer,
+ size_to_write, success_callback, error_callback);
+ if (!WriteAsyncImpl(buffer, size_to_write, callback, error_callback, &error,
+ false)) {
+ error_callback.Run(error.get());
+ }
+ } else {
+ success_callback.Run();
+ }
+}
+
+void Stream::FlushAsyncCallback(const base::Closure& success_callback,
+ const ErrorCallback& error_callback) {
+ ErrorPtr error;
+ if (FlushBlocking(&error)) {
+ success_callback.Run();
+ } else {
+ error_callback.Run(error.get());
+ }
+}
+
+void Stream::CancelPendingAsyncOperations() {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ is_async_read_pending_ = false;
+ is_async_write_pending_ = false;
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/stream.h b/libbrillo/brillo/streams/stream.h
new file mode 100644
index 0000000..d7c58db
--- /dev/null
+++ b/libbrillo/brillo/streams/stream.h
@@ -0,0 +1,506 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_STREAM_H_
+#define LIBBRILLO_BRILLO_STREAMS_STREAM_H_
+
+#include <cstdint>
+#include <memory>
+
+#include <base/callback.h>
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <base/time/time.h>
+#include <brillo/brillo_export.h>
+#include <brillo/errors/error.h>
+
+namespace brillo {
+
+// Stream is a base class that specific stream storage implementations must
+// derive from to provide I/O facilities.
+// The stream class provides general streaming I/O primitives to read, write and
+// seek within a stream. It has methods for asynchronous (callback-based) as
+// well as synchronous (both blocking and non-blocking) operations.
+// The Stream class is abstract and cannot be created by itself.
+// In order to construct a stream, you must use one of the derived classes'
+// factory methods which return a stream smart pointer (StreamPtr):
+//
+// StreamPtr input_stream = FileStream::Open(path, AccessMode::READ);
+// StreamPtr output_stream = MemoryStream::Create();
+// uint8_t buf[1000];
+// size_t read = 0;
+// while (input_stream->ReadBlocking(buf, sizeof(buf), &read, nullptr)) {
+// if (read == 0) break;
+// output_stream->WriteAllBlocking(buf, read, nullptr);
+// }
+//
+// NOTE ABOUT ASYNCHRONOUS OPERATIONS: Asynchronous I/O relies on a MessageLoop
+// instance to be present on the current thread. Using Stream::ReadAsync(),
+// Stream::WriteAsync() and similar will call MessageLoop::current() to access
+// the current message loop and abort if there isn't one for the current thread.
+// Also, only one outstanding asynchronous operation of particular kind (reading
+// or writing) at a time is supported. Trying to call ReadAsync() while another
+// asynchronous read operation is pending will fail with an error
+// ("operation_not_supported").
+//
+// NOTE ABOUT READING FROM/WRITING TO STREAMS: In many cases underlying streams
+// use buffered I/O. Using all read/write methods other than ReadAllAsync(),
+// ReadAllBlocking(), WriteAllAsync(), WriteAllBlocking() will return
+// immediately if there is any data available in the underlying buffer. That is,
+// trying to read 1000 bytes while the internal buffer contains only 100 will
+// return immediately with just those 100 bytes and no blocking or other I/O
+// traffic will be incurred. This guarantee is important for efficient and
+// correct implementation of duplex communication over pipes and sockets.
+//
+// NOTE TO IMPLEMENTERS: When creating new stream types, you must derive
+// from this class and provide the implementation for its pure virtual methods.
+// For operations that do not apply to your stream, make sure the corresponding
+// methods return "false" and set the error to "operation_not_supported".
+// You should use stream_utils::ErrorOperationNotSupported() for this. Also
+// Make sure the stream capabilities functions like CanRead(), etc return
+// correct values:
+//
+// bool MyReadOnlyStream::CanRead() const { return true; }
+// bool MyReadOnlyStream::CanWrite() const { return false; }
+// bool MyReadOnlyStream::WriteBlocking(const void* buffer,
+// size_t size_to_write,
+// size_t* size_written,
+// ErrorPtr* error) {
+// return stream_utils::ErrorOperationNotSupported(error);
+// }
+//
+// The class should also provide a static factory methods to create/open
+// a new stream:
+//
+// static StreamPtr MyReadOnlyStream::Open(..., ErrorPtr* error) {
+// auto my_stream = std::make_unique<MyReadOnlyStream>(...);
+// if (!my_stream->Initialize(..., error))
+// my_stream.reset();
+// }
+// return my_stream;
+// }
+//
+class BRILLO_EXPORT Stream {
+ public:
+ // When seeking in streams, whence specifies the origin of the seek operation.
+ enum class Whence { FROM_BEGIN, FROM_CURRENT, FROM_END };
+ // Stream access mode for open operations (used in derived classes).
+ enum class AccessMode { READ, WRITE, READ_WRITE };
+
+ // Standard error callback for asynchronous operations.
+ using ErrorCallback = base::Callback<void(const Error*)>;
+
+ virtual ~Stream() = default;
+
+ // == Stream capabilities ===================================================
+
+ // Returns true while stream is open. Closing the last reference to the stream
+ // will make this method return false.
+ virtual bool IsOpen() const = 0;
+
+ // Called to determine if read operations are supported on the stream (stream
+ // is readable). This method does not check if there is actually any data to
+ // read, only the fact that the stream is open in read mode and can be read
+ // from in general.
+ // If CanRead() returns false, it is guaranteed that the stream can't be
+ // read from. However, if it returns true, there is no guarantee that the
+ // subsequent read operation will actually succeed (for example, the stream
+ // position could be at the end of the data stream, or the access mode of
+ // the stream is unknown beforehand).
+ virtual bool CanRead() const = 0;
+
+ // Called to determine if write operations are supported on the stream (stream
+ // is writable).
+ // If CanWrite() returns false, it is guaranteed that the stream can't be
+ // written to. However, if it returns true, the subsequent write operation
+ // is not guaranteed to succeed (e.g. the output media could be out of free
+ // space or a transport error could occur).
+ virtual bool CanWrite() const = 0;
+
+ // Called to determine if random access I/O operations are supported on
+ // the stream. Sequential streams should return false.
+ // If CanSeek() returns false, it is guaranteed that the stream can't use
+ // Seek(). However, if it returns true, it might be possible to seek, but this
+ // is not guaranteed since the actual underlying stream capabilities might
+ // not be known.
+ // Note that non-seekable streams might still maintain the current stream
+ // position and GetPosition method might still be used even if CanSeek()
+ // returns false. However SetPosition() will almost always fail in such
+ // a case.
+ virtual bool CanSeek() const = 0;
+
+ // Called to determine if the size of the stream is known. Size of some
+ // sequential streams (e.g. based on pipes) is unknown beforehand, so this
+ // method can be used to check how reliable a call to GetSize() is.
+ virtual bool CanGetSize() const = 0;
+
+ // == Stream size operations ================================================
+
+ // Returns the size of stream data.
+ // If the stream size is unavailable/unknown, it returns 0.
+ virtual uint64_t GetSize() const = 0;
+
+ // Resizes the stream storage to |size|. Stream must be writable and support
+ // this operation.
+ virtual bool SetSizeBlocking(uint64_t size, ErrorPtr* error) = 0;
+
+ // Truncates the stream at the current stream pointer.
+ // Calls SetSizeBlocking(GetPosition(), ...).
+ bool TruncateBlocking(ErrorPtr* error);
+
+ // Returns the amount of data remaining in the stream. If the size of the
+ // stream is unknown, or if the stream pointer is at or past the end of the
+ // stream, the function returns 0.
+ virtual uint64_t GetRemainingSize() const = 0;
+
+ // == Seek operations =======================================================
+
+ // Gets the position of the stream I/O pointer from the beginning of the
+ // stream. If the stream position is unavailable/unknown, it returns 0.
+ virtual uint64_t GetPosition() const = 0;
+
+ // Moves the stream pointer to the specified position, relative to the
+ // beginning of the stream. This calls Seek(position, Whence::FROM_BEGIN),
+ // however it also provides proper |position| validation to ensure that
+ // it doesn't overflow the range of signed int64_t used by Seek.
+ bool SetPosition(uint64_t position, ErrorPtr* error);
+
+ // Moves the stream pointer by |offset| bytes relative to |whence|.
+ // When successful, returns true and sets the new pointer position from the
+ // beginning of the stream to |new_position|. If |new_position| is nullptr,
+ // new stream position is not returned.
+ // On error, returns false and specifies additional details in |error| if it
+ // is not nullptr.
+ virtual bool Seek(int64_t offset,
+ Whence whence,
+ uint64_t* new_position,
+ ErrorPtr* error) = 0;
+
+ // == Read operations =======================================================
+
+ // -- Asynchronous ----------------------------------------------------------
+
+ // Reads up to |size_to_read| bytes from the stream asynchronously. It is not
+ // guaranteed that all requested data will be read. It is not an error for
+ // this function to read fewer bytes than requested. If the function reads
+ // zero bytes, it means that the end of stream is reached.
+ // Upon successful read, the |success_callback| will be invoked with the
+ // actual number of bytes read.
+ // If an error occurs during the asynchronous operation, the |error_callback|
+ // is invoked with the error details. The error object pointer passed in as a
+ // parameter to the |error_callback| is valid only for the duration of that
+ // callback.
+ // If this function successfully schedules an asynchronous operation, it
+ // returns true. If it fails immediately, it will return false and set the
+ // error details to |error| object and will not call the success or error
+ // callbacks.
+ // The |buffer| must be at least |size_to_read| in size and must remain
+ // valid for the duration of the asynchronous operation (until either
+ // |success_callback| or |error_callback| is called).
+ // Only one asynchronous operation at a time is allowed on the stream (read
+ // and/or write)
+ // Uses ReadNonBlocking() and MonitorDataAvailable().
+ virtual bool ReadAsync(void* buffer,
+ size_t size_to_read,
+ const base::Callback<void(size_t)>& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error);
+
+ // Similar to ReadAsync() operation above but reads exactly |size_to_read|
+ // bytes from the stream into the |buffer|. Attempt to read past the end of
+ // the stream is considered an error in this case and will trigger the
+ // |error_callback|. The rest of restrictions and conditions of ReadAsync()
+ // method applies to ReadAllAsync() as well.
+ // Uses ReadNonBlocking() and MonitorDataAvailable().
+ virtual bool ReadAllAsync(void* buffer,
+ size_t size_to_read,
+ const base::Closure& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error);
+
+ // -- Synchronous non-blocking ----------------------------------------------
+
+ // Reads up to |size_to_read| bytes from the stream without blocking.
+ // The |buffer| must be at least |size_to_read| in size. It is not an error
+ // for this function to return without reading all (or any) the data.
+ // The actual amount of data read (which could be 0 bytes) is returned in
+ // |size_read|.
+ // On error, the function returns false and specifies additional error details
+ // in |error|.
+ // If end of stream is reached or if no data is currently available to be read
+ // without blocking, |size_read| will contain 0 and the function will still
+ // return true (success). In case of end-of-stream scenario, |end_of_stream|
+ // will also be set to true to indicate that no more data is available.
+ virtual bool ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) = 0;
+
+ // -- Synchronous blocking --------------------------------------------------
+
+ // Reads up to |size_to_read| bytes from the stream. This function will block
+ // until at least one byte is read or the end of stream is reached or until
+ // the stream is closed.
+ // The |buffer| must be at least |size_to_read| in size. It is not an error
+ // for this function to return without reading all the data. The actual amount
+ // of data read (which could be 0 bytes) is returned in |size_read|.
+ // On error, the function returns false and specifies additional error details
+ // in |error|. In this case, the state of the stream pointer is undefined,
+ // since some bytes might have been read successfully (and the pointer moved)
+ // before the error has occurred and |size_read| is not updated.
+ // If end of stream is reached, |size_read| will contain 0 and the function
+ // will still return true (success).
+ virtual bool ReadBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ ErrorPtr* error);
+
+ // Reads exactly |size_to_read| bytes to |buffer|. Returns false on error
+ // (reading fewer than requested bytes is treated as an error as well).
+ // Calls ReadAllBlocking() repeatedly until all the data is read.
+ virtual bool ReadAllBlocking(void* buffer,
+ size_t size_to_read,
+ ErrorPtr* error);
+
+ // == Write operations ======================================================
+
+ // -- Asynchronous ----------------------------------------------------------
+
+ // Writes up to |size_to_write| bytes from |buffer| to the stream
+ // asynchronously. It is not guaranteed that all requested data will be
+ // written. It is not an error for this function to write fewer bytes than
+ // requested.
+ // Upon successful write, the |success_callback| will be invoked with the
+ // actual number of bytes written.
+ // If an error occurs during the asynchronous operation, the |error_callback|
+ // is invoked with the error details. The error object pointer is valid only
+ // for the duration of the error callback.
+ // If this function successfully schedules an asynchronous operation, it
+ // returns true. If it fails immediately, it will return false and set the
+ // error details to |error| object and will not call the success or error
+ // callbacks.
+ // The |buffer| must be at least |size_to_write| in size and must remain
+ // valid for the duration of the asynchronous operation (until either
+ // |success_callback| or |error_callback| is called).
+ // Only one asynchronous operation at a time is allowed on the stream (read
+ // and/or write).
+ // Uses WriteNonBlocking() and MonitorDataAvailable().
+ virtual bool WriteAsync(const void* buffer,
+ size_t size_to_write,
+ const base::Callback<void(size_t)>& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error);
+
+ // Similar to WriteAsync() operation above but writes exactly |size_to_write|
+ // bytes from |buffet| to the stream. When all the data is written
+ // successfully, the |success_callback| is invoked.
+ // The rest of restrictions and conditions of WriteAsync() method applies to
+ // WriteAllAsync() as well.
+ // Uses WriteNonBlocking() and MonitorDataAvailable().
+ virtual bool WriteAllAsync(const void* buffer,
+ size_t size_to_write,
+ const base::Closure& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error);
+
+ // -- Synchronous non-blocking ----------------------------------------------
+
+ // Writes up to |size_to_write| bytes to the stream. The |buffer| must be at
+ // least |size_to_write| in size. It is not an error for this function to
+ // return without writing all the data requested (or any data at all).
+ // The actual amount of data written is returned in |size_written|.
+ // On error, the function returns false and specifies additional error details
+ // in |error|.
+ virtual bool WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) = 0;
+
+ // -- Synchronous blocking --------------------------------------------------
+
+ // Writes up to |size_to_write| bytes to the stream. The |buffer| must be at
+ // least |size_to_write| in size. It is not an error for this function to
+ // return without writing all the data requested. The actual amount of data
+ // written is returned in |size_written|.
+ // On error, the function returns false and specifies additional error details
+ // in |error|.
+ virtual bool WriteBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error);
+
+ // Writes exactly |size_to_write| bytes to |buffer|. Returns false on error
+ // (writing fewer than requested bytes is treated as an error as well).
+ // Calls WriteBlocking() repeatedly until all the data is written.
+ virtual bool WriteAllBlocking(const void* buffer,
+ size_t size_to_write,
+ ErrorPtr* error);
+
+ // == Finalizing/closing streams ===========================================
+
+ // Flushes all the user-space data from cache output buffers to storage
+ // medium. For read-only streams this is a no-op, however it is still valid
+ // to call this method on read-only streams.
+ // If an error occurs, the function returns false and specifies additional
+ // error details in |error|.
+ virtual bool FlushBlocking(ErrorPtr* error) = 0;
+
+ // Flushes all the user-space data from the cache output buffer
+ // asynchronously. When all the data is successfully flushed, the
+ // |success_callback| is invoked. If an error occurs while flushing, partial
+ // data might be flushed and |error_callback| is invoked. If there's an error
+ // scheduling the flush operation, it returns false and neither callback will
+ // be called.
+ virtual bool FlushAsync(const base::Closure& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error);
+
+ // Closes the underlying stream. The stream is also automatically closed
+ // when the stream object is destroyed, but since closing a stream is
+ // an operation that may fail, in situations when it is important to detect
+ // the failure to close the stream, CloseBlocking() should be used explicitly
+ // before destroying the stream object.
+ virtual bool CloseBlocking(ErrorPtr* error) = 0;
+
+ // == Data availability monitoring ==========================================
+
+ // Overloaded by derived classes to provide stream monitoring for read/write
+ // data availability for the stream. Calls |callback| when data can be read
+ // and/or written without blocking.
+ // |mode| specifies the type of operation to monitor for (read, write, both).
+ virtual bool WaitForData(AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) = 0;
+
+ // Helper function for implementing blocking I/O. Blocks until the
+ // non-blocking operation specified by |in_mode| can be performed.
+ // If |out_mode| is not nullptr, it receives the actual operation that can be
+ // performed. For example, watching a stream for READ_WRITE while only
+ // READ can be performed, |out_mode| would contain READ even though |in_mode|
+ // was set to READ_WRITE.
+ // |timeout| is the maximum amount of time to wait. Set it to TimeDelta::Max()
+ // to wait indefinitely.
+ virtual bool WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) = 0;
+
+ // Cancels pending asynchronous read/write operations.
+ virtual void CancelPendingAsyncOperations();
+
+ protected:
+ Stream() = default;
+
+ private:
+ // Simple wrapper to call the externally exposed |success_callback| that only
+ // receives a size_t.
+ BRILLO_PRIVATE static void IgnoreEOSCallback(
+ const base::Callback<void(size_t)>& success_callback,
+ size_t read,
+ bool eos);
+
+ // The internal implementation of ReadAsync() and ReadAllAsync().
+ // Calls ReadNonBlocking and if there's no data available waits for it calling
+ // WaitForData(). The extra |force_async_callback| tell whether the success
+ // callback should be called from the main loop instead of directly from this
+ // method. This method only calls WaitForData() if ReadNonBlocking() returns a
+ // situation in which it would block (bytes_read = 0 and eos = false),
+ // preventing us from calling WaitForData() on streams that don't support such
+ // feature.
+ BRILLO_PRIVATE bool ReadAsyncImpl(
+ void* buffer,
+ size_t size_to_read,
+ const base::Callback<void(size_t, bool)>& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error,
+ bool force_async_callback);
+
+ // Called from the main loop when the ReadAsyncImpl finished right away
+ // without waiting for data. We use this callback to call the
+ // |sucess_callback| but invalidate the callback if the Stream is destroyed
+ // while this call is waiting in the main loop.
+ BRILLO_PRIVATE void OnReadAsyncDone(
+ const base::Callback<void(size_t, bool)>& success_callback,
+ size_t bytes_read,
+ bool eos);
+
+ // Called from WaitForData() when read operations can be performed
+ // without blocking (the type of operation is provided in |mode|).
+ BRILLO_PRIVATE void OnReadAvailable(
+ void* buffer,
+ size_t size_to_read,
+ const base::Callback<void(size_t, bool)>& success_callback,
+ const ErrorCallback& error_callback,
+ AccessMode mode);
+
+ // The internal implementation of WriteAsync() and WriteAllAsync().
+ // Calls WriteNonBlocking and if the write would block for it to not block
+ // calling WaitForData(). The extra |force_async_callback| tell whether the
+ // success callback should be called from the main loop instead of directly
+ // from this method. This method only calls WaitForData() if
+ // WriteNonBlocking() returns a situation in which it would block
+ // (size_written = 0 and eos = false), preventing us from calling
+ // WaitForData() on streams that don't support such feature.
+ BRILLO_PRIVATE bool WriteAsyncImpl(
+ const void* buffer,
+ size_t size_to_write,
+ const base::Callback<void(size_t)>& success_callback,
+ const ErrorCallback& error_callback,
+ ErrorPtr* error,
+ bool force_async_callback);
+
+ // Called from the main loop when the WriteAsyncImpl finished right away
+ // without waiting for data. We use this callback to call the
+ // |sucess_callback| but invalidate the callback if the Stream is destroyed
+ // while this call is waiting in the main loop.
+ BRILLO_PRIVATE void OnWriteAsyncDone(
+ const base::Callback<void(size_t)>& success_callback,
+ size_t size_written);
+
+ // Called from WaitForData() when write operations can be performed
+ // without blocking (the type of operation is provided in |mode|).
+ BRILLO_PRIVATE void OnWriteAvailable(
+ const void* buffer,
+ size_t size,
+ const base::Callback<void(size_t)>& success_callback,
+ const ErrorCallback& error_callback,
+ AccessMode mode);
+
+ // Helper callbacks to implement ReadAllAsync/WriteAllAsync.
+ BRILLO_PRIVATE void ReadAllAsyncCallback(
+ void* buffer,
+ size_t size_to_read,
+ const base::Closure& success_callback,
+ const ErrorCallback& error_callback,
+ size_t size_read,
+ bool eos);
+ BRILLO_PRIVATE void WriteAllAsyncCallback(
+ const void* buffer,
+ size_t size_to_write,
+ const base::Closure& success_callback,
+ const ErrorCallback& error_callback,
+ size_t size_written);
+
+ // Helper callbacks to implement FlushAsync().
+ BRILLO_PRIVATE void FlushAsyncCallback(
+ const base::Closure& success_callback,
+ const ErrorCallback& error_callback);
+
+ // Data members for asynchronous read operations.
+ bool is_async_read_pending_{false};
+
+ // Data members for asynchronous write operations.
+ bool is_async_write_pending_{false};
+
+ base::WeakPtrFactory<Stream> weak_ptr_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(Stream);
+};
+
+// A smart pointer to the stream used to pass the stream object around.
+using StreamPtr = std::unique_ptr<Stream>;
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_STREAM_H_
diff --git a/libbrillo/brillo/streams/stream_errors.cc b/libbrillo/brillo/streams/stream_errors.cc
new file mode 100644
index 0000000..e7c3dcd
--- /dev/null
+++ b/libbrillo/brillo/streams/stream_errors.cc
@@ -0,0 +1,21 @@
+// Copyright 2015 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 <brillo/streams/stream_errors.h>
+
+namespace brillo {
+namespace errors {
+namespace stream {
+
+const char kDomain[] = "stream.io";
+
+const char kStreamClosed[] = "stream_closed";
+const char kOperationNotSupported[] = "operation_not_supported";
+const char kPartialData[] = "partial_data";
+const char kInvalidParameter[] = "invalid_parameter";
+const char kTimeout[] = "time_out";
+
+} // namespace stream
+} // namespace errors
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/stream_errors.h b/libbrillo/brillo/streams/stream_errors.h
new file mode 100644
index 0000000..1eacc5e
--- /dev/null
+++ b/libbrillo/brillo/streams/stream_errors.h
@@ -0,0 +1,27 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_STREAM_ERRORS_H_
+#define LIBBRILLO_BRILLO_STREAMS_STREAM_ERRORS_H_
+
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+namespace errors {
+namespace stream {
+
+// Error domain for generic stream-based errors.
+BRILLO_EXPORT extern const char kDomain[];
+
+BRILLO_EXPORT extern const char kStreamClosed[];
+BRILLO_EXPORT extern const char kOperationNotSupported[];
+BRILLO_EXPORT extern const char kPartialData[];
+BRILLO_EXPORT extern const char kInvalidParameter[];
+BRILLO_EXPORT extern const char kTimeout[];
+
+} // namespace stream
+} // namespace errors
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_STREAM_ERRORS_H_
diff --git a/libbrillo/brillo/streams/stream_unittest.cc b/libbrillo/brillo/streams/stream_unittest.cc
new file mode 100644
index 0000000..625b569
--- /dev/null
+++ b/libbrillo/brillo/streams/stream_unittest.cc
@@ -0,0 +1,523 @@
+// Copyright 2015 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 <brillo/streams/stream.h>
+
+#include <limits>
+
+#include <base/callback.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/streams/stream_errors.h>
+
+using testing::DoAll;
+using testing::InSequence;
+using testing::Return;
+using testing::SaveArg;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace brillo {
+
+using AccessMode = Stream::AccessMode;
+using Whence = Stream::Whence;
+
+// To verify "non-trivial" methods implemented in Stream, mock out the
+// "trivial" methods to make sure the ones we are interested in testing
+// actually end up calling the expected methods with right parameters.
+class MockStreamImpl : public Stream {
+ public:
+ MockStreamImpl() = default;
+
+ MOCK_CONST_METHOD0(IsOpen, bool());
+ MOCK_CONST_METHOD0(CanRead, bool());
+ MOCK_CONST_METHOD0(CanWrite, bool());
+ MOCK_CONST_METHOD0(CanSeek, bool());
+ MOCK_CONST_METHOD0(CanGetSize, bool());
+
+ MOCK_CONST_METHOD0(GetSize, uint64_t());
+ MOCK_METHOD2(SetSizeBlocking, bool(uint64_t, ErrorPtr*));
+ MOCK_CONST_METHOD0(GetRemainingSize, uint64_t());
+
+ MOCK_CONST_METHOD0(GetPosition, uint64_t());
+ MOCK_METHOD4(Seek, bool(int64_t, Whence, uint64_t*, ErrorPtr*));
+
+ // Omitted: ReadAsync
+ // Omitted: ReadAllAsync
+ MOCK_METHOD5(ReadNonBlocking, bool(void*, size_t, size_t*, bool*, ErrorPtr*));
+ // Omitted: ReadBlocking
+ // Omitted: ReadAllBlocking
+
+ // Omitted: WriteAsync
+ // Omitted: WriteAllAsync
+ MOCK_METHOD4(WriteNonBlocking, bool(const void*, size_t, size_t*, ErrorPtr*));
+ // Omitted: WriteBlocking
+ // Omitted: WriteAllBlocking
+
+ MOCK_METHOD1(FlushBlocking, bool(ErrorPtr*));
+ MOCK_METHOD1(CloseBlocking, bool(ErrorPtr*));
+
+ MOCK_METHOD3(WaitForData, bool(AccessMode,
+ const base::Callback<void(AccessMode)>&,
+ ErrorPtr*));
+ MOCK_METHOD4(WaitForDataBlocking,
+ bool(AccessMode, base::TimeDelta, AccessMode*, ErrorPtr*));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockStreamImpl);
+};
+
+TEST(Stream, TruncateBlocking) {
+ MockStreamImpl stream_mock;
+ EXPECT_CALL(stream_mock, GetPosition()).WillOnce(Return(123));
+ EXPECT_CALL(stream_mock, SetSizeBlocking(123, _)).WillOnce(Return(true));
+ EXPECT_TRUE(stream_mock.TruncateBlocking(nullptr));
+}
+
+TEST(Stream, SetPosition) {
+ MockStreamImpl stream_mock;
+ EXPECT_CALL(stream_mock, Seek(12345, Whence::FROM_BEGIN, _, _))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(stream_mock.SetPosition(12345, nullptr));
+
+ // Test too large an offset (that doesn't fit in signed 64 bit value).
+ ErrorPtr error;
+ uint64_t max_offset = std::numeric_limits<int64_t>::max();
+ EXPECT_CALL(stream_mock, Seek(max_offset, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(stream_mock.SetPosition(max_offset, nullptr));
+
+ EXPECT_FALSE(stream_mock.SetPosition(max_offset + 1, &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode());
+}
+
+TEST(Stream, ReadAsync) {
+ size_t read_size = 0;
+ bool succeeded = false;
+ bool failed = false;
+ auto success_callback = [](size_t* read_size, bool* succeeded,size_t size) {
+ *read_size = size;
+ *succeeded = true;
+ };
+ auto error_callback = [](bool* failed, const Error* /* error */) {
+ *failed = true;
+ };
+
+ MockStreamImpl stream_mock;
+ base::Callback<void(AccessMode)> data_callback;
+ char buf[10];
+
+ // This sets up an initial non blocking read that would block, so ReadAsync()
+ // should wait for more data.
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
+ .WillOnce(
+ DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _))
+ .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
+ EXPECT_TRUE(stream_mock.ReadAsync(
+ buf,
+ sizeof(buf),
+ base::Bind(success_callback,
+ base::Unretained(&read_size),
+ base::Unretained(&succeeded)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ nullptr));
+ EXPECT_EQ(0u, read_size);
+ EXPECT_FALSE(succeeded);
+ EXPECT_FALSE(failed);
+
+ // Since the previous call is waiting for the data to be available, we can't
+ // schedule another read.
+ ErrorPtr error;
+ EXPECT_FALSE(stream_mock.ReadAsync(
+ buf,
+ sizeof(buf),
+ base::Bind(success_callback,
+ base::Unretained(&read_size),
+ base::Unretained(&succeeded)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode());
+ EXPECT_EQ("Another asynchronous operation is still pending",
+ error->GetMessage());
+
+ // Making the data available via data_callback should not schedule the
+ // success callback from the main loop and run it directly instead.
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(7),
+ SetArgPointee<3>(false),
+ Return(true)));
+ data_callback.Run(AccessMode::READ);
+ EXPECT_EQ(7u, read_size);
+ EXPECT_FALSE(failed);
+}
+
+TEST(Stream, ReadAsync_DontWaitForData) {
+ bool succeeded = false;
+ bool failed = false;
+ auto success_callback = [](bool* succeeded, size_t /* size */) {
+ *succeeded = true;
+ };
+ auto error_callback = [](bool* failed, const Error* /* error */) {
+ *failed = true;
+ };
+
+ MockStreamImpl stream_mock;
+ char buf[10];
+ FakeMessageLoop fake_loop_{nullptr};
+ fake_loop_.SetAsCurrent();
+
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
+ .WillOnce(
+ DoAll(SetArgPointee<2>(5), SetArgPointee<3>(false), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForData(_, _, _)).Times(0);
+ EXPECT_TRUE(stream_mock.ReadAsync(
+ buf,
+ sizeof(buf),
+ base::Bind(success_callback, base::Unretained(&succeeded)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ nullptr));
+ // Even if ReadNonBlocking() returned some data without waiting, the
+ // |success_callback| should not run yet.
+ EXPECT_TRUE(fake_loop_.PendingTasks());
+ EXPECT_FALSE(succeeded);
+ EXPECT_FALSE(failed);
+
+ // Since the previous callback is still waiting in the main loop, we can't
+ // schedule another read yet.
+ ErrorPtr error;
+ EXPECT_FALSE(stream_mock.ReadAsync(
+ buf,
+ sizeof(buf),
+ base::Bind(success_callback, base::Unretained(&succeeded)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode());
+ EXPECT_EQ("Another asynchronous operation is still pending",
+ error->GetMessage());
+
+ fake_loop_.Run();
+ EXPECT_TRUE(succeeded);
+ EXPECT_FALSE(failed);
+}
+
+TEST(Stream, ReadAllAsync) {
+ bool succeeded = false;
+ bool failed = false;
+ auto success_callback = [](bool* succeeded) { *succeeded = true; };
+ auto error_callback = [](bool* failed, const Error* /* error */) {
+ *failed = true;
+ };
+
+ MockStreamImpl stream_mock;
+ base::Callback<void(AccessMode)> data_callback;
+ char buf[10];
+
+ // This sets up an initial non blocking read that would block, so
+ // ReadAllAsync() should wait for more data.
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
+ .WillOnce(
+ DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _))
+ .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
+ EXPECT_TRUE(stream_mock.ReadAllAsync(
+ buf,
+ sizeof(buf),
+ base::Bind(success_callback, base::Unretained(&succeeded)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ nullptr));
+ EXPECT_FALSE(succeeded);
+ EXPECT_FALSE(failed);
+ testing::Mock::VerifyAndClearExpectations(&stream_mock);
+
+ // ReadAllAsync() will try to read non blocking until the read would block
+ // before it waits for the data to be available again.
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(7),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf + 7, 3, _, _, _))
+ .WillOnce(
+ DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _))
+ .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
+ data_callback.Run(AccessMode::READ);
+ EXPECT_FALSE(succeeded);
+ EXPECT_FALSE(failed);
+ testing::Mock::VerifyAndClearExpectations(&stream_mock);
+
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf + 7, 3, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(3),
+ SetArgPointee<3>(true),
+ Return(true)));
+ data_callback.Run(AccessMode::READ);
+ EXPECT_TRUE(succeeded);
+ EXPECT_FALSE(failed);
+}
+
+TEST(Stream, ReadAllAsync_EOS) {
+ bool succeeded = false;
+ bool failed = false;
+ auto success_callback = [](bool* succeeded) { *succeeded = true; };
+ auto error_callback = [](bool* failed, const Error* error) {
+ ASSERT_EQ(errors::stream::kDomain, error->GetDomain());
+ ASSERT_EQ(errors::stream::kPartialData, error->GetCode());
+ *failed = true;
+ };
+
+ MockStreamImpl stream_mock;
+ base::Callback<void(AccessMode)> data_callback;
+ char buf[10];
+
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
+ .WillOnce(
+ DoAll(SetArgPointee<2>(0), SetArgPointee<3>(false), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForData(AccessMode::READ, _, _))
+ .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
+ EXPECT_TRUE(stream_mock.ReadAllAsync(
+ buf,
+ sizeof(buf),
+ base::Bind(success_callback, base::Unretained(&succeeded)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ nullptr));
+
+ // ReadAsyncAll() should finish and fail once ReadNonBlocking() returns an
+ // end-of-stream condition.
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 10, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(7),
+ SetArgPointee<3>(true),
+ Return(true)));
+ data_callback.Run(AccessMode::READ);
+ EXPECT_FALSE(succeeded);
+ EXPECT_TRUE(failed);
+}
+
+TEST(Stream, ReadBlocking) {
+ MockStreamImpl stream_mock;
+ char buf[1024];
+ size_t read = 0;
+
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(24),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr));
+ EXPECT_EQ(24, read);
+
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0),
+ SetArgPointee<3>(true),
+ Return(true)));
+ EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr));
+ EXPECT_EQ(0, read);
+
+ {
+ InSequence seq;
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(124),
+ SetArgPointee<3>(false),
+ Return(true)));
+ }
+ EXPECT_TRUE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr));
+ EXPECT_EQ(124, read);
+
+ {
+ InSequence seq;
+ EXPECT_CALL(stream_mock, ReadNonBlocking(buf, 1024, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0),
+ SetArgPointee<3>(false),
+ Return(true)));
+ EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::READ, _, _, _))
+ .WillOnce(Return(false));
+ }
+ EXPECT_FALSE(stream_mock.ReadBlocking(buf, sizeof(buf), &read, nullptr));
+}
+
+TEST(Stream, ReadAllBlocking) {
+ class MockReadBlocking : public MockStreamImpl {
+ public:
+ MOCK_METHOD4(ReadBlocking, bool(void*, size_t, size_t*, ErrorPtr*));
+ } stream_mock;
+
+ char buf[1024];
+
+ EXPECT_CALL(stream_mock, ReadBlocking(buf, 1024, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(24), Return(true)));
+ EXPECT_CALL(stream_mock, ReadBlocking(buf + 24, 1000, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(1000), Return(true)));
+ EXPECT_TRUE(stream_mock.ReadAllBlocking(buf, sizeof(buf), nullptr));
+
+ ErrorPtr error;
+ EXPECT_CALL(stream_mock, ReadBlocking(buf, 1024, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(24), Return(true)));
+ EXPECT_CALL(stream_mock, ReadBlocking(buf + 24, 1000, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+ EXPECT_FALSE(stream_mock.ReadAllBlocking(buf, sizeof(buf), &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kPartialData, error->GetCode());
+}
+
+TEST(Stream, WriteAsync) {
+ size_t write_size = 0;
+ bool failed = false;
+ auto success_callback = [](size_t* write_size, size_t size) {
+ *write_size = size;
+ };
+ auto error_callback = [](bool* failed, const Error* /* error */) {
+ *failed = true;
+ };
+
+ MockStreamImpl stream_mock;
+ InSequence s;
+ base::Callback<void(AccessMode)> data_callback;
+ char buf[10] = {};
+
+ // WriteNonBlocking returns a blocking situation (size_written = 0) so the
+ // WaitForData() is run.
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _))
+ .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
+ EXPECT_TRUE(stream_mock.WriteAsync(
+ buf,
+ sizeof(buf),
+ base::Bind(success_callback, base::Unretained(&write_size)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ nullptr));
+ EXPECT_EQ(0u, write_size);
+ EXPECT_FALSE(failed);
+
+ ErrorPtr error;
+ EXPECT_FALSE(stream_mock.WriteAsync(
+ buf,
+ sizeof(buf),
+ base::Bind(success_callback, base::Unretained(&write_size)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode());
+ EXPECT_EQ("Another asynchronous operation is still pending",
+ error->GetMessage());
+
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(7), Return(true)));
+ data_callback.Run(AccessMode::WRITE);
+ EXPECT_EQ(7u, write_size);
+ EXPECT_FALSE(failed);
+}
+
+TEST(Stream, WriteAllAsync) {
+ bool succeeded = false;
+ bool failed = false;
+ auto success_callback = [](bool* succeeded) { *succeeded = true; };
+ auto error_callback = [](bool* failed, const Error* /* error */) {
+ *failed = true;
+ };
+
+ MockStreamImpl stream_mock;
+ base::Callback<void(AccessMode)> data_callback;
+ char buf[10] = {};
+
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _))
+ .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
+ EXPECT_TRUE(stream_mock.WriteAllAsync(
+ buf,
+ sizeof(buf),
+ base::Bind(success_callback, base::Unretained(&succeeded)),
+ base::Bind(error_callback, base::Unretained(&failed)),
+ nullptr));
+ testing::Mock::VerifyAndClearExpectations(&stream_mock);
+ EXPECT_FALSE(succeeded);
+ EXPECT_FALSE(failed);
+
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 10, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(7), Return(true)));
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf + 7, 3, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForData(AccessMode::WRITE, _, _))
+ .WillOnce(DoAll(SaveArg<1>(&data_callback), Return(true)));
+ data_callback.Run(AccessMode::WRITE);
+ testing::Mock::VerifyAndClearExpectations(&stream_mock);
+ EXPECT_FALSE(succeeded);
+ EXPECT_FALSE(failed);
+
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf + 7, 3, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(3), Return(true)));
+ data_callback.Run(AccessMode::WRITE);
+ EXPECT_TRUE(succeeded);
+ EXPECT_FALSE(failed);
+}
+
+TEST(Stream, WriteBlocking) {
+ MockStreamImpl stream_mock;
+ char buf[1024];
+ size_t written = 0;
+
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(24), Return(true)));
+ EXPECT_TRUE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr));
+ EXPECT_EQ(24, written);
+
+ {
+ InSequence seq;
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(124), Return(true)));
+ }
+ EXPECT_TRUE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr));
+ EXPECT_EQ(124, written);
+
+ {
+ InSequence seq;
+ EXPECT_CALL(stream_mock, WriteNonBlocking(buf, 1024, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(0), Return(true)));
+ EXPECT_CALL(stream_mock, WaitForDataBlocking(AccessMode::WRITE, _, _, _))
+ .WillOnce(Return(false));
+ }
+ EXPECT_FALSE(stream_mock.WriteBlocking(buf, sizeof(buf), &written, nullptr));
+}
+
+TEST(Stream, WriteAllBlocking) {
+ class MockWritelocking : public MockStreamImpl {
+ public:
+ MOCK_METHOD4(WriteBlocking, bool(const void*, size_t, size_t*, ErrorPtr*));
+ } stream_mock;
+
+ char buf[1024];
+
+ EXPECT_CALL(stream_mock, WriteBlocking(buf, 1024, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(24), Return(true)));
+ EXPECT_CALL(stream_mock, WriteBlocking(buf + 24, 1000, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(1000), Return(true)));
+ EXPECT_TRUE(stream_mock.WriteAllBlocking(buf, sizeof(buf), nullptr));
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/stream_utils.cc b/libbrillo/brillo/streams/stream_utils.cc
new file mode 100644
index 0000000..5f3be24
--- /dev/null
+++ b/libbrillo/brillo/streams/stream_utils.cc
@@ -0,0 +1,216 @@
+// Copyright 2015 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 <brillo/streams/stream_utils.h>
+
+#include <limits>
+
+#include <base/bind.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/streams/stream_errors.h>
+
+namespace brillo {
+namespace stream_utils {
+
+namespace {
+
+// Status of asynchronous CopyData operation.
+struct CopyDataState {
+ brillo::StreamPtr in_stream;
+ brillo::StreamPtr out_stream;
+ std::vector<uint8_t> buffer;
+ uint64_t remaining_to_copy;
+ uint64_t size_copied;
+ CopyDataSuccessCallback success_callback;
+ CopyDataErrorCallback error_callback;
+};
+
+// Async CopyData I/O error callback.
+void OnCopyDataError(const std::shared_ptr<CopyDataState>& state,
+ const brillo::Error* error) {
+ state->error_callback.Run(std::move(state->in_stream),
+ std::move(state->out_stream), error);
+}
+
+// Forward declaration.
+void PerformRead(const std::shared_ptr<CopyDataState>& state);
+
+// Callback from read operation for CopyData. Writes the read data to the output
+// stream and invokes PerformRead when done to restart the copy cycle.
+void PerformWrite(const std::shared_ptr<CopyDataState>& state, size_t size) {
+ if (size == 0) {
+ state->success_callback.Run(std::move(state->in_stream),
+ std::move(state->out_stream),
+ state->size_copied);
+ return;
+ }
+ state->size_copied += size;
+ CHECK_GE(state->remaining_to_copy, size);
+ state->remaining_to_copy -= size;
+
+ brillo::ErrorPtr error;
+ bool success = state->out_stream->WriteAllAsync(
+ state->buffer.data(), size, base::Bind(&PerformRead, state),
+ base::Bind(&OnCopyDataError, state), &error);
+
+ if (!success)
+ OnCopyDataError(state, error.get());
+}
+
+// Performs the read part of asynchronous CopyData operation. Reads the data
+// from input stream and invokes PerformWrite when done to write the data to
+// the output stream.
+void PerformRead(const std::shared_ptr<CopyDataState>& state) {
+ brillo::ErrorPtr error;
+ const uint64_t buffer_size = state->buffer.size();
+ // |buffer_size| is guaranteed to fit in size_t, so |size_to_read| value will
+ // also not overflow size_t, so the static_cast below is safe.
+ size_t size_to_read =
+ static_cast<size_t>(std::min(buffer_size, state->remaining_to_copy));
+ if (size_to_read == 0)
+ return PerformWrite(state, 0); // Nothing more to read. Finish operation.
+ bool success = state->in_stream->ReadAsync(
+ state->buffer.data(), size_to_read, base::Bind(PerformWrite, state),
+ base::Bind(OnCopyDataError, state), &error);
+
+ if (!success)
+ OnCopyDataError(state, error.get());
+}
+
+} // anonymous namespace
+
+bool ErrorStreamClosed(const tracked_objects::Location& location,
+ ErrorPtr* error) {
+ Error::AddTo(error,
+ location,
+ errors::stream::kDomain,
+ errors::stream::kStreamClosed,
+ "Stream is closed");
+ return false;
+}
+
+bool ErrorOperationNotSupported(const tracked_objects::Location& location,
+ ErrorPtr* error) {
+ Error::AddTo(error,
+ location,
+ errors::stream::kDomain,
+ errors::stream::kOperationNotSupported,
+ "Stream operation not supported");
+ return false;
+}
+
+bool ErrorReadPastEndOfStream(const tracked_objects::Location& location,
+ ErrorPtr* error) {
+ Error::AddTo(error,
+ location,
+ errors::stream::kDomain,
+ errors::stream::kPartialData,
+ "Reading past the end of stream");
+ return false;
+}
+
+bool ErrorOperationTimeout(const tracked_objects::Location& location,
+ ErrorPtr* error) {
+ Error::AddTo(error,
+ location,
+ errors::stream::kDomain,
+ errors::stream::kTimeout,
+ "Operation timed out");
+ return false;
+}
+
+bool CheckInt64Overflow(const tracked_objects::Location& location,
+ uint64_t position,
+ int64_t offset,
+ ErrorPtr* error) {
+ if (offset < 0) {
+ // Subtracting the offset. Make sure we do not underflow.
+ uint64_t unsigned_offset = static_cast<uint64_t>(-offset);
+ if (position >= unsigned_offset)
+ return true;
+ } else {
+ // Adding the offset. Make sure we do not overflow unsigned 64 bits first.
+ if (position <= std::numeric_limits<uint64_t>::max() - offset) {
+ // We definitely will not overflow the unsigned 64 bit integer.
+ // Now check that we end up within the limits of signed 64 bit integer.
+ uint64_t new_position = position + offset;
+ uint64_t max = std::numeric_limits<int64_t>::max();
+ if (new_position <= max)
+ return true;
+ }
+ }
+ Error::AddTo(error,
+ location,
+ errors::stream::kDomain,
+ errors::stream::kInvalidParameter,
+ "The stream offset value is out of range");
+ return false;
+}
+
+bool CalculateStreamPosition(const tracked_objects::Location& location,
+ int64_t offset,
+ Stream::Whence whence,
+ uint64_t current_position,
+ uint64_t stream_size,
+ uint64_t* new_position,
+ ErrorPtr* error) {
+ uint64_t pos = 0;
+ switch (whence) {
+ case Stream::Whence::FROM_BEGIN:
+ pos = 0;
+ break;
+
+ case Stream::Whence::FROM_CURRENT:
+ pos = current_position;
+ break;
+
+ case Stream::Whence::FROM_END:
+ pos = stream_size;
+ break;
+
+ default:
+ Error::AddTo(error,
+ location,
+ errors::stream::kDomain,
+ errors::stream::kInvalidParameter,
+ "Invalid stream position whence");
+ return false;
+ }
+
+ if (!CheckInt64Overflow(location, pos, offset, error))
+ return false;
+
+ *new_position = static_cast<uint64_t>(pos + offset);
+ return true;
+}
+
+void CopyData(StreamPtr in_stream,
+ StreamPtr out_stream,
+ const CopyDataSuccessCallback& success_callback,
+ const CopyDataErrorCallback& error_callback) {
+ CopyData(std::move(in_stream), std::move(out_stream),
+ std::numeric_limits<uint64_t>::max(), 4096, success_callback,
+ error_callback);
+}
+
+void CopyData(StreamPtr in_stream,
+ StreamPtr out_stream,
+ uint64_t max_size_to_copy,
+ size_t buffer_size,
+ const CopyDataSuccessCallback& success_callback,
+ const CopyDataErrorCallback& error_callback) {
+ auto state = std::make_shared<CopyDataState>();
+ state->in_stream = std::move(in_stream);
+ state->out_stream = std::move(out_stream);
+ state->buffer.resize(buffer_size);
+ state->remaining_to_copy = max_size_to_copy;
+ state->size_copied = 0;
+ state->success_callback = success_callback;
+ state->error_callback = error_callback;
+ brillo::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(&PerformRead, state));
+}
+
+} // namespace stream_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/stream_utils.h b/libbrillo/brillo/streams/stream_utils.h
new file mode 100644
index 0000000..981a6d5
--- /dev/null
+++ b/libbrillo/brillo/streams/stream_utils.h
@@ -0,0 +1,114 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_STREAM_UTILS_H_
+#define LIBBRILLO_BRILLO_STREAMS_STREAM_UTILS_H_
+
+#include <base/location.h>
+#include <brillo/brillo_export.h>
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+namespace stream_utils {
+
+// Generates "Stream closed" error and returns false.
+BRILLO_EXPORT bool ErrorStreamClosed(
+ const tracked_objects::Location& location, ErrorPtr* error);
+
+// Generates "Not supported" error and returns false.
+BRILLO_EXPORT bool ErrorOperationNotSupported(
+ const tracked_objects::Location& location, ErrorPtr* error);
+
+// Generates "Read past end of stream" error and returns false.
+BRILLO_EXPORT bool ErrorReadPastEndOfStream(
+ const tracked_objects::Location& location, ErrorPtr* error);
+
+// Generates "Operation time out" error and returns false.
+BRILLO_EXPORT bool ErrorOperationTimeout(
+ const tracked_objects::Location& location, ErrorPtr* error);
+
+// Checks if |position| + |offset| fit within the constraint of positive
+// signed int64_t type. We use uint64_t for absolute stream pointer positions,
+// however many implementations, including file-descriptor-based I/O do not
+// support the full extent of unsigned 64 bit numbers. So we restrict the file
+// positions to what can fit in the signed 64 bit value (that is, we support
+// "only" up to 9 exabytes, instead of the possible 18).
+// The |location| parameter will be used to report the origin of the error
+// if one is generated/triggered.
+BRILLO_EXPORT bool CheckInt64Overflow(
+ const tracked_objects::Location& location,
+ uint64_t position,
+ int64_t offset,
+ ErrorPtr* error);
+
+// Helper function to calculate the stream position based on the current
+// stream position and offset. Returns true and the new calculated stream
+// position in |new_position| if successful. In case of invalid stream
+// position (negative values or out of range of signed 64 bit values), returns
+// false and "invalid_parameter" |error|.
+// The |location| parameter will be used to report the origin of the error
+// if one is generated/triggered.
+BRILLO_EXPORT bool CalculateStreamPosition(
+ const tracked_objects::Location& location,
+ int64_t offset,
+ Stream::Whence whence,
+ uint64_t current_position,
+ uint64_t stream_size,
+ uint64_t* new_position,
+ ErrorPtr* error);
+
+// Checks if |mode| allows read access.
+inline bool IsReadAccessMode(Stream::AccessMode mode) {
+ return mode == Stream::AccessMode::READ ||
+ mode == Stream::AccessMode::READ_WRITE;
+}
+
+// Checks if |mode| allows write access.
+inline bool IsWriteAccessMode(Stream::AccessMode mode) {
+ return mode == Stream::AccessMode::WRITE ||
+ mode == Stream::AccessMode::READ_WRITE;
+}
+
+// Make the access mode based on read/write rights requested.
+inline Stream::AccessMode MakeAccessMode(bool read, bool write) {
+ CHECK(read || write); // Either read or write (or both) must be specified.
+ if (read && write)
+ return Stream::AccessMode::READ_WRITE;
+ return write ? Stream::AccessMode::WRITE : Stream::AccessMode::READ;
+}
+
+using CopyDataSuccessCallback =
+ base::Callback<void(StreamPtr, StreamPtr, uint64_t)>;
+using CopyDataErrorCallback =
+ base::Callback<void(StreamPtr, StreamPtr, const brillo::Error*)>;
+
+// Asynchronously copies data from input stream to output stream until all the
+// data from the input stream is read. The function takes ownership of both
+// streams for the duration of the operation and then gives them back when
+// either the |success_callback| or |error_callback| is called.
+// |success_callback| also provides the number of bytes actually copied.
+// This variant of CopyData uses internal buffer of 4 KiB for the operation.
+BRILLO_EXPORT void CopyData(StreamPtr in_stream,
+ StreamPtr out_stream,
+ const CopyDataSuccessCallback& success_callback,
+ const CopyDataErrorCallback& error_callback);
+
+// Asynchronously copies data from input stream to output stream until the
+// maximum amount of data specified in |max_size_to_copy| is copied or the end
+// of the input stream is encountered. The function takes ownership of both
+// streams for the duration of the operation and then gives them back when
+// either the |success_callback| or |error_callback| is called.
+// |success_callback| also provides the number of bytes actually copied.
+// |buffer_size| specifies the size of the read buffer to use for the operation.
+BRILLO_EXPORT void CopyData(StreamPtr in_stream,
+ StreamPtr out_stream,
+ uint64_t max_size_to_copy,
+ size_t buffer_size,
+ const CopyDataSuccessCallback& success_callback,
+ const CopyDataErrorCallback& error_callback);
+
+} // namespace stream_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_STREAM_UTILS_H_
diff --git a/libbrillo/brillo/streams/stream_utils_unittest.cc b/libbrillo/brillo/streams/stream_utils_unittest.cc
new file mode 100644
index 0000000..f27d233
--- /dev/null
+++ b/libbrillo/brillo/streams/stream_utils_unittest.cc
@@ -0,0 +1,300 @@
+// Copyright 2015 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 <brillo/streams/stream_utils.h>
+
+#include <limits>
+
+#include <base/bind.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/streams/mock_stream.h>
+#include <brillo/streams/stream_errors.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::DoAll;
+using testing::InSequence;
+using testing::Return;
+using testing::StrictMock;
+using testing::_;
+
+ACTION_TEMPLATE(InvokeAsyncCallback,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_1_VALUE_PARAMS(size)) {
+ brillo::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(std::get<k>(args), size));
+ return true;
+}
+
+ACTION_TEMPLATE(InvokeAsyncCallback,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_0_VALUE_PARAMS()) {
+ brillo::MessageLoop::current()->PostTask(FROM_HERE, std::get<k>(args));
+ return true;
+}
+
+ACTION_TEMPLATE(InvokeAsyncErrorCallback,
+ HAS_1_TEMPLATE_PARAMS(int, k),
+ AND_1_VALUE_PARAMS(code)) {
+ brillo::ErrorPtr error;
+ brillo::Error::AddTo(&error, FROM_HERE, "test", code, "message");
+ brillo::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(std::get<k>(args), base::Owned(error.release())));
+ return true;
+}
+
+namespace brillo {
+
+TEST(StreamUtils, ErrorStreamClosed) {
+ ErrorPtr error;
+ EXPECT_FALSE(stream_utils::ErrorStreamClosed(FROM_HERE, &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kStreamClosed, error->GetCode());
+ EXPECT_EQ("Stream is closed", error->GetMessage());
+}
+
+TEST(StreamUtils, ErrorOperationNotSupported) {
+ ErrorPtr error;
+ EXPECT_FALSE(stream_utils::ErrorOperationNotSupported(FROM_HERE, &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kOperationNotSupported, error->GetCode());
+ EXPECT_EQ("Stream operation not supported", error->GetMessage());
+}
+
+TEST(StreamUtils, ErrorReadPastEndOfStream) {
+ ErrorPtr error;
+ EXPECT_FALSE(stream_utils::ErrorReadPastEndOfStream(FROM_HERE, &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kPartialData, error->GetCode());
+ EXPECT_EQ("Reading past the end of stream", error->GetMessage());
+}
+
+TEST(StreamUtils, CheckInt64Overflow) {
+ const int64_t max_int64 = std::numeric_limits<int64_t>::max();
+ const uint64_t max_uint64 = std::numeric_limits<uint64_t>::max();
+ EXPECT_TRUE(stream_utils::CheckInt64Overflow(FROM_HERE, 0, 0, nullptr));
+ EXPECT_TRUE(stream_utils::CheckInt64Overflow(
+ FROM_HERE, 0, max_int64, nullptr));
+ EXPECT_TRUE(stream_utils::CheckInt64Overflow(
+ FROM_HERE, max_int64, 0, nullptr));
+ EXPECT_TRUE(stream_utils::CheckInt64Overflow(FROM_HERE, 100, -90, nullptr));
+ EXPECT_TRUE(stream_utils::CheckInt64Overflow(
+ FROM_HERE, 1000, -1000, nullptr));
+
+ ErrorPtr error;
+ EXPECT_FALSE(stream_utils::CheckInt64Overflow(FROM_HERE, 100, -101, &error));
+ EXPECT_EQ(errors::stream::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode());
+ EXPECT_EQ("The stream offset value is out of range", error->GetMessage());
+
+ EXPECT_FALSE(stream_utils::CheckInt64Overflow(
+ FROM_HERE, max_int64, 1, nullptr));
+ EXPECT_FALSE(stream_utils::CheckInt64Overflow(
+ FROM_HERE, max_uint64, 0, nullptr));
+ EXPECT_FALSE(stream_utils::CheckInt64Overflow(
+ FROM_HERE, max_uint64, max_int64, nullptr));
+}
+
+TEST(StreamUtils, CalculateStreamPosition) {
+ using Whence = Stream::Whence;
+ const uint64_t current_pos = 1234;
+ const uint64_t end_pos = 2000;
+ uint64_t pos = 0;
+
+ EXPECT_TRUE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, 0, Whence::FROM_BEGIN, current_pos, end_pos, &pos, nullptr));
+ EXPECT_EQ(0u, pos);
+
+ EXPECT_TRUE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, 0, Whence::FROM_CURRENT, current_pos, end_pos, &pos, nullptr));
+ EXPECT_EQ(current_pos, pos);
+
+ EXPECT_TRUE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, 0, Whence::FROM_END, current_pos, end_pos, &pos, nullptr));
+ EXPECT_EQ(end_pos, pos);
+
+ EXPECT_TRUE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, 10, Whence::FROM_BEGIN, current_pos, end_pos, &pos, nullptr));
+ EXPECT_EQ(10u, pos);
+
+ EXPECT_TRUE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, 10, Whence::FROM_CURRENT, current_pos, end_pos, &pos,
+ nullptr));
+ EXPECT_EQ(current_pos + 10, pos);
+
+ EXPECT_TRUE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, 10, Whence::FROM_END, current_pos, end_pos, &pos, nullptr));
+ EXPECT_EQ(end_pos + 10, pos);
+
+ EXPECT_TRUE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, -10, Whence::FROM_CURRENT, current_pos, end_pos, &pos,
+ nullptr));
+ EXPECT_EQ(current_pos - 10, pos);
+
+ EXPECT_TRUE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, -10, Whence::FROM_END, current_pos, end_pos, &pos, nullptr));
+ EXPECT_EQ(end_pos - 10, pos);
+
+ ErrorPtr error;
+ EXPECT_FALSE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, -1, Whence::FROM_BEGIN, current_pos, end_pos, &pos, &error));
+ EXPECT_EQ(errors::stream::kInvalidParameter, error->GetCode());
+ EXPECT_EQ("The stream offset value is out of range", error->GetMessage());
+
+ EXPECT_FALSE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, -1001, Whence::FROM_CURRENT, 1000, end_pos, &pos, nullptr));
+
+ const uint64_t max_int64 = std::numeric_limits<int64_t>::max();
+ EXPECT_FALSE(stream_utils::CalculateStreamPosition(
+ FROM_HERE, 1, Whence::FROM_CURRENT, max_int64, end_pos, &pos, nullptr));
+}
+
+class CopyStreamDataTest : public testing::Test {
+ public:
+ void SetUp() override {
+ fake_loop_.SetAsCurrent();
+ in_stream_.reset(new StrictMock<MockStream>{});
+ out_stream_.reset(new StrictMock<MockStream>{});
+ }
+
+ FakeMessageLoop fake_loop_{nullptr};
+ std::unique_ptr<StrictMock<MockStream>> in_stream_;
+ std::unique_ptr<StrictMock<MockStream>> out_stream_;
+ bool succeeded_{false};
+ bool failed_{false};
+
+ void OnSuccess(uint64_t expected,
+ StreamPtr /* in_stream */,
+ StreamPtr /* out_stream */,
+ uint64_t copied) {
+ EXPECT_EQ(expected, copied);
+ succeeded_ = true;
+ }
+
+ void OnError(const std::string& expected_error,
+ StreamPtr /* in_stream */,
+ StreamPtr /* out_stream */,
+ const Error* error) {
+ EXPECT_EQ(expected_error, error->GetCode());
+ failed_ = true;
+ }
+
+ void ExpectSuccess() {
+ EXPECT_TRUE(succeeded_);
+ EXPECT_FALSE(failed_);
+ }
+
+ void ExpectFailure() {
+ EXPECT_FALSE(succeeded_);
+ EXPECT_TRUE(failed_);
+ }
+};
+
+TEST_F(CopyStreamDataTest, CopyAllAtOnce) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*in_stream_, ReadAsync(_, 100, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>(100));
+ EXPECT_CALL(*out_stream_, WriteAllAsync(_, 100, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>());
+ }
+ stream_utils::CopyData(
+ std::move(in_stream_), std::move(out_stream_), 100, 4096,
+ base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 100),
+ base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), ""));
+ fake_loop_.Run();
+ ExpectSuccess();
+}
+
+TEST_F(CopyStreamDataTest, CopyInBlocks) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*in_stream_, ReadAsync(_, 100, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>(60));
+ EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>());
+ EXPECT_CALL(*in_stream_, ReadAsync(_, 40, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>(40));
+ EXPECT_CALL(*out_stream_, WriteAllAsync(_, 40, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>());
+ }
+ stream_utils::CopyData(
+ std::move(in_stream_), std::move(out_stream_), 100, 4096,
+ base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 100),
+ base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), ""));
+ fake_loop_.Run();
+ ExpectSuccess();
+}
+
+TEST_F(CopyStreamDataTest, CopyTillEndOfStream) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*in_stream_, ReadAsync(_, 100, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>(60));
+ EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>());
+ EXPECT_CALL(*in_stream_, ReadAsync(_, 40, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>(0));
+ }
+ stream_utils::CopyData(
+ std::move(in_stream_), std::move(out_stream_), 100, 4096,
+ base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 60),
+ base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), ""));
+ fake_loop_.Run();
+ ExpectSuccess();
+}
+
+TEST_F(CopyStreamDataTest, CopyInSmallBlocks) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*in_stream_, ReadAsync(_, 60, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>(60));
+ EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>());
+ EXPECT_CALL(*in_stream_, ReadAsync(_, 40, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>(40));
+ EXPECT_CALL(*out_stream_, WriteAllAsync(_, 40, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>());
+ }
+ stream_utils::CopyData(
+ std::move(in_stream_), std::move(out_stream_), 100, 60,
+ base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 100),
+ base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), ""));
+ fake_loop_.Run();
+ ExpectSuccess();
+}
+
+TEST_F(CopyStreamDataTest, ErrorRead) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*in_stream_, ReadAsync(_, 60, _, _, _))
+ .WillOnce(InvokeAsyncErrorCallback<3>("read"));
+ }
+ stream_utils::CopyData(
+ std::move(in_stream_), std::move(out_stream_), 100, 60,
+ base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 0),
+ base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this), "read"));
+ fake_loop_.Run();
+ ExpectFailure();
+}
+
+TEST_F(CopyStreamDataTest, ErrorWrite) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*in_stream_, ReadAsync(_, 60, _, _, _))
+ .WillOnce(InvokeAsyncCallback<2>(60));
+ EXPECT_CALL(*out_stream_, WriteAllAsync(_, 60, _, _, _))
+ .WillOnce(InvokeAsyncErrorCallback<3>("write"));
+ }
+ stream_utils::CopyData(
+ std::move(in_stream_), std::move(out_stream_), 100, 60,
+ base::Bind(&CopyStreamDataTest::OnSuccess, base::Unretained(this), 0),
+ base::Bind(&CopyStreamDataTest::OnError, base::Unretained(this),
+ "write"));
+ fake_loop_.Run();
+ ExpectFailure();
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/tls_stream.cc b/libbrillo/brillo/streams/tls_stream.cc
new file mode 100644
index 0000000..ac116a4
--- /dev/null
+++ b/libbrillo/brillo/streams/tls_stream.cc
@@ -0,0 +1,545 @@
+// Copyright 2015 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 <brillo/streams/tls_stream.h>
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#include <base/bind.h>
+#include <base/memory/weak_ptr.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/secure_blob.h>
+#include <brillo/streams/openssl_stream_bio.h>
+#include <brillo/streams/stream_utils.h>
+#include <brillo/strings/string_utils.h>
+
+namespace {
+
+// SSL info callback which is called by OpenSSL when we enable logging level of
+// at least 3. This logs the information about the internal TLS handshake.
+void TlsInfoCallback(const SSL* /* ssl */, int where, int ret) {
+ std::string reason;
+ std::vector<std::string> info;
+ if (where & SSL_CB_LOOP)
+ info.push_back("loop");
+ if (where & SSL_CB_EXIT)
+ info.push_back("exit");
+ if (where & SSL_CB_READ)
+ info.push_back("read");
+ if (where & SSL_CB_WRITE)
+ info.push_back("write");
+ if (where & SSL_CB_ALERT) {
+ info.push_back("alert");
+ reason = ", reason: ";
+ reason += SSL_alert_type_string_long(ret);
+ reason += "/";
+ reason += SSL_alert_desc_string_long(ret);
+ }
+ if (where & SSL_CB_HANDSHAKE_START)
+ info.push_back("handshake_start");
+ if (where & SSL_CB_HANDSHAKE_DONE)
+ info.push_back("handshake_done");
+
+ VLOG(3) << "TLS progress info: " << brillo::string_utils::Join(",", info)
+ << ", with status: " << ret << reason;
+}
+
+// Static variable to store the index of TlsStream private data in SSL context
+// used to store custom data for OnCertVerifyResults().
+int ssl_ctx_private_data_index = -1;
+
+// Default trusted certificate store location.
+const char kCACertificatePath[] =
+#ifdef __ANDROID__
+ "/system/etc/security/cacerts_google";
+#else
+ "/usr/share/chromeos-ca-certificates";
+#endif
+
+} // anonymous namespace
+
+namespace brillo {
+
+// Helper implementation of TLS stream used to hide most of OpenSSL inner
+// workings from the users of brillo::TlsStream.
+class TlsStream::TlsStreamImpl {
+ public:
+ TlsStreamImpl();
+ ~TlsStreamImpl();
+
+ bool Init(StreamPtr socket,
+ const std::string& host,
+ const base::Closure& success_callback,
+ const Stream::ErrorCallback& error_callback,
+ ErrorPtr* error);
+
+ bool ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error);
+
+ bool WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error);
+
+ bool Flush(ErrorPtr* error);
+ bool Close(ErrorPtr* error);
+ bool WaitForData(AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error);
+ bool WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error);
+ void CancelPendingAsyncOperations();
+
+ private:
+ bool ReportError(ErrorPtr* error,
+ const tracked_objects::Location& location,
+ const std::string& message);
+ void DoHandshake(const base::Closure& success_callback,
+ const Stream::ErrorCallback& error_callback);
+ void RetryHandshake(const base::Closure& success_callback,
+ const Stream::ErrorCallback& error_callback,
+ Stream::AccessMode mode);
+
+ int OnCertVerifyResults(int ok, X509_STORE_CTX* ctx);
+ static int OnCertVerifyResultsStatic(int ok, X509_STORE_CTX* ctx);
+
+ StreamPtr socket_;
+ std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)> ctx_{nullptr, SSL_CTX_free};
+ std::unique_ptr<SSL, decltype(&SSL_free)> ssl_{nullptr, SSL_free};
+ BIO* stream_bio_{nullptr};
+ bool need_more_read_{false};
+ bool need_more_write_{false};
+
+ base::WeakPtrFactory<TlsStreamImpl> weak_ptr_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(TlsStreamImpl);
+};
+
+TlsStream::TlsStreamImpl::TlsStreamImpl() {
+ SSL_load_error_strings();
+ SSL_library_init();
+ if (ssl_ctx_private_data_index < 0) {
+ ssl_ctx_private_data_index =
+ SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+ }
+}
+
+TlsStream::TlsStreamImpl::~TlsStreamImpl() {
+ ssl_.reset();
+ ctx_.reset();
+}
+
+bool TlsStream::TlsStreamImpl::ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) {
+ const size_t max_int = std::numeric_limits<int>::max();
+ int size_int = static_cast<int>(std::min(size_to_read, max_int));
+ int ret = SSL_read(ssl_.get(), buffer, size_int);
+ if (ret > 0) {
+ *size_read = static_cast<size_t>(ret);
+ if (end_of_stream)
+ *end_of_stream = false;
+ return true;
+ }
+
+ int err = SSL_get_error(ssl_.get(), ret);
+ if (err == SSL_ERROR_ZERO_RETURN) {
+ *size_read = 0;
+ if (end_of_stream)
+ *end_of_stream = true;
+ return true;
+ }
+
+ if (err == SSL_ERROR_WANT_READ) {
+ need_more_read_ = true;
+ } else if (err == SSL_ERROR_WANT_WRITE) {
+ // Writes might be required for SSL_read() because of possible TLS
+ // re-negotiations which can happen at any time.
+ need_more_write_ = true;
+ } else {
+ return ReportError(error, FROM_HERE, "Error reading from TLS socket");
+ }
+ *size_read = 0;
+ if (end_of_stream)
+ *end_of_stream = false;
+ return true;
+}
+
+bool TlsStream::TlsStreamImpl::WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) {
+ const size_t max_int = std::numeric_limits<int>::max();
+ int size_int = static_cast<int>(std::min(size_to_write, max_int));
+ int ret = SSL_write(ssl_.get(), buffer, size_int);
+ if (ret > 0) {
+ *size_written = static_cast<size_t>(ret);
+ return true;
+ }
+
+ int err = SSL_get_error(ssl_.get(), ret);
+ if (err == SSL_ERROR_WANT_READ) {
+ // Reads might be required for SSL_write() because of possible TLS
+ // re-negotiations which can happen at any time.
+ need_more_read_ = true;
+ } else if (err == SSL_ERROR_WANT_WRITE) {
+ need_more_write_ = true;
+ } else {
+ return ReportError(error, FROM_HERE, "Error writing to TLS socket");
+ }
+ *size_written = 0;
+ return true;
+}
+
+bool TlsStream::TlsStreamImpl::Flush(ErrorPtr* error) {
+ return socket_->FlushBlocking(error);
+}
+
+bool TlsStream::TlsStreamImpl::Close(ErrorPtr* error) {
+ // 2 seconds should be plenty here.
+ const base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(2);
+ // The retry count of 4 below is just arbitrary, to ensure we don't get stuck
+ // here forever. We should rarely need to repeat SSL_shutdown anyway.
+ for (int retry_count = 0; retry_count < 4; retry_count++) {
+ int ret = SSL_shutdown(ssl_.get());
+ // We really don't care for bi-directional shutdown here.
+ // Just make sure we only send the "close notify" alert to the remote peer.
+ if (ret >= 0)
+ break;
+
+ int err = SSL_get_error(ssl_.get(), ret);
+ if (err == SSL_ERROR_WANT_READ) {
+ if (!socket_->WaitForDataBlocking(AccessMode::READ, kTimeout, nullptr,
+ error)) {
+ break;
+ }
+ } else if (err == SSL_ERROR_WANT_WRITE) {
+ if (!socket_->WaitForDataBlocking(AccessMode::WRITE, kTimeout, nullptr,
+ error)) {
+ break;
+ }
+ } else {
+ LOG(ERROR) << "SSL_shutdown returned error #" << err;
+ ReportError(error, FROM_HERE, "Failed to shut down TLS socket");
+ break;
+ }
+ }
+ return socket_->CloseBlocking(error);
+}
+
+bool TlsStream::TlsStreamImpl::WaitForData(
+ AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) {
+ bool is_read = stream_utils::IsReadAccessMode(mode);
+ bool is_write = stream_utils::IsWriteAccessMode(mode);
+ is_read |= need_more_read_;
+ is_write |= need_more_write_;
+ need_more_read_ = false;
+ need_more_write_ = false;
+ if (is_read && SSL_pending(ssl_.get()) > 0) {
+ callback.Run(AccessMode::READ);
+ return true;
+ }
+ mode = stream_utils::MakeAccessMode(is_read, is_write);
+ return socket_->WaitForData(mode, callback, error);
+}
+
+bool TlsStream::TlsStreamImpl::WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) {
+ bool is_read = stream_utils::IsReadAccessMode(in_mode);
+ bool is_write = stream_utils::IsWriteAccessMode(in_mode);
+ is_read |= need_more_read_;
+ is_write |= need_more_write_;
+ need_more_read_ = need_more_write_ = false;
+ if (is_read && SSL_pending(ssl_.get()) > 0) {
+ if (out_mode)
+ *out_mode = AccessMode::READ;
+ return true;
+ }
+ in_mode = stream_utils::MakeAccessMode(is_read, is_write);
+ return socket_->WaitForDataBlocking(in_mode, timeout, out_mode, error);
+}
+
+void TlsStream::TlsStreamImpl::CancelPendingAsyncOperations() {
+ socket_->CancelPendingAsyncOperations();
+ weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+bool TlsStream::TlsStreamImpl::ReportError(
+ ErrorPtr* error,
+ const tracked_objects::Location& location,
+ const std::string& message) {
+ const char* file = nullptr;
+ int line = 0;
+ const char* data = 0;
+ int flags = 0;
+ while (auto errnum = ERR_get_error_line_data(&file, &line, &data, &flags)) {
+ char buf[256];
+ ERR_error_string_n(errnum, buf, sizeof(buf));
+ tracked_objects::Location ssl_location{"Unknown", file, line, nullptr};
+ std::string ssl_message = buf;
+ if (flags & ERR_TXT_STRING) {
+ ssl_message += ": ";
+ ssl_message += data;
+ }
+ Error::AddTo(error, ssl_location, "openssl", std::to_string(errnum),
+ ssl_message);
+ }
+ Error::AddTo(error, location, "tls_stream", "failed", message);
+ return false;
+}
+
+int TlsStream::TlsStreamImpl::OnCertVerifyResults(int ok, X509_STORE_CTX* ctx) {
+ // OpenSSL already performs a comprehensive check of the certificate chain
+ // (using X509_verify_cert() function) and calls back with the result of its
+ // verification.
+ // |ok| is set to 1 if the verification passed and 0 if an error was detected.
+ // Here we can perform some additional checks if we need to, or simply log
+ // the issues found.
+
+ // For now, just log an error if it occurred.
+ if (!ok) {
+ LOG(ERROR) << "Server certificate validation failed: "
+ << X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx));
+ }
+ return ok;
+}
+
+int TlsStream::TlsStreamImpl::OnCertVerifyResultsStatic(int ok,
+ X509_STORE_CTX* ctx) {
+ // Obtain the pointer to the instance of TlsStream::TlsStreamImpl from the
+ // SSL CTX object referenced by |ctx|.
+ SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(
+ ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
+ SSL_CTX* ssl_ctx = ssl ? SSL_get_SSL_CTX(ssl) : nullptr;
+ TlsStream::TlsStreamImpl* self = nullptr;
+ if (ssl_ctx) {
+ self = static_cast<TlsStream::TlsStreamImpl*>(SSL_CTX_get_ex_data(
+ ssl_ctx, ssl_ctx_private_data_index));
+ }
+ return self ? self->OnCertVerifyResults(ok, ctx) : ok;
+}
+
+bool TlsStream::TlsStreamImpl::Init(StreamPtr socket,
+ const std::string& host,
+ const base::Closure& success_callback,
+ const Stream::ErrorCallback& error_callback,
+ ErrorPtr* error) {
+ ctx_.reset(SSL_CTX_new(TLSv1_2_client_method()));
+ if (!ctx_)
+ return ReportError(error, FROM_HERE, "Cannot create SSL_CTX");
+
+ // Top cipher suites supported by both Google GFEs and OpenSSL (in server
+ // preferred order).
+ int res = SSL_CTX_set_cipher_list(ctx_.get(),
+ "ECDHE-ECDSA-AES128-GCM-SHA256:"
+ "ECDHE-ECDSA-AES256-GCM-SHA384:"
+ "ECDHE-RSA-AES128-GCM-SHA256:"
+ "ECDHE-RSA-AES256-GCM-SHA384");
+ if (res != 1)
+ return ReportError(error, FROM_HERE, "Cannot set the cipher list");
+
+ res = SSL_CTX_load_verify_locations(ctx_.get(), nullptr, kCACertificatePath);
+ if (res != 1) {
+ return ReportError(error, FROM_HERE,
+ "Failed to specify trusted certificate location");
+ }
+
+ // Store a pointer to "this" into SSL_CTX instance.
+ SSL_CTX_set_ex_data(ctx_.get(), ssl_ctx_private_data_index, this);
+
+ // Ask OpenSSL to validate the server host from the certificate to match
+ // the expected host name we are given:
+ X509_VERIFY_PARAM* param = SSL_CTX_get0_param(ctx_.get());
+ X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size());
+
+ SSL_CTX_set_verify(ctx_.get(), SSL_VERIFY_PEER,
+ &TlsStreamImpl::OnCertVerifyResultsStatic);
+
+ socket_ = std::move(socket);
+ ssl_.reset(SSL_new(ctx_.get()));
+
+ // Enable TLS progress callback if VLOG level is >=3.
+ if (VLOG_IS_ON(3))
+ SSL_set_info_callback(ssl_.get(), TlsInfoCallback);
+
+ stream_bio_ = BIO_new_stream(socket_.get());
+ SSL_set_bio(ssl_.get(), stream_bio_, stream_bio_);
+ SSL_set_connect_state(ssl_.get());
+
+ // We might have no message loop (e.g. we are in unit tests).
+ if (MessageLoop::ThreadHasCurrent()) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&TlsStreamImpl::DoHandshake,
+ weak_ptr_factory_.GetWeakPtr(),
+ success_callback,
+ error_callback));
+ } else {
+ DoHandshake(success_callback, error_callback);
+ }
+ return true;
+}
+
+void TlsStream::TlsStreamImpl::RetryHandshake(
+ const base::Closure& success_callback,
+ const Stream::ErrorCallback& error_callback,
+ Stream::AccessMode /* mode */) {
+ VLOG(1) << "Retrying TLS handshake";
+ DoHandshake(success_callback, error_callback);
+}
+
+void TlsStream::TlsStreamImpl::DoHandshake(
+ const base::Closure& success_callback,
+ const Stream::ErrorCallback& error_callback) {
+ VLOG(1) << "Begin TLS handshake";
+ int res = SSL_do_handshake(ssl_.get());
+ if (res == 1) {
+ VLOG(1) << "Handshake successful";
+ success_callback.Run();
+ return;
+ }
+ ErrorPtr error;
+ int err = SSL_get_error(ssl_.get(), res);
+ if (err == SSL_ERROR_WANT_READ) {
+ VLOG(1) << "Waiting for read data...";
+ bool ok = socket_->WaitForData(
+ Stream::AccessMode::READ,
+ base::Bind(&TlsStreamImpl::RetryHandshake,
+ weak_ptr_factory_.GetWeakPtr(),
+ success_callback, error_callback),
+ &error);
+ if (ok)
+ return;
+ } else if (err == SSL_ERROR_WANT_WRITE) {
+ VLOG(1) << "Waiting for write data...";
+ bool ok = socket_->WaitForData(
+ Stream::AccessMode::WRITE,
+ base::Bind(&TlsStreamImpl::RetryHandshake,
+ weak_ptr_factory_.GetWeakPtr(),
+ success_callback, error_callback),
+ &error);
+ if (ok)
+ return;
+ } else {
+ ReportError(&error, FROM_HERE, "TLS handshake failed.");
+ }
+ error_callback.Run(error.get());
+}
+
+/////////////////////////////////////////////////////////////////////////////
+TlsStream::TlsStream(std::unique_ptr<TlsStreamImpl> impl)
+ : impl_{std::move(impl)} {}
+
+TlsStream::~TlsStream() {
+ if (impl_) {
+ impl_->Close(nullptr);
+ }
+}
+
+void TlsStream::Connect(StreamPtr socket,
+ const std::string& host,
+ const base::Callback<void(StreamPtr)>& success_callback,
+ const Stream::ErrorCallback& error_callback) {
+ std::unique_ptr<TlsStreamImpl> impl{new TlsStreamImpl};
+ std::unique_ptr<TlsStream> stream{new TlsStream{std::move(impl)}};
+
+ TlsStreamImpl* pimpl = stream->impl_.get();
+ ErrorPtr error;
+ bool success = pimpl->Init(std::move(socket), host,
+ base::Bind(success_callback,
+ base::Passed(std::move(stream))),
+ error_callback, &error);
+
+ if (!success)
+ error_callback.Run(error.get());
+}
+
+bool TlsStream::IsOpen() const {
+ return impl_ ? true : false;
+}
+
+bool TlsStream::SetSizeBlocking(uint64_t /* size */, ErrorPtr* error) {
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+}
+
+bool TlsStream::Seek(int64_t /* offset */,
+ Whence /* whence */,
+ uint64_t* /* new_position*/,
+ ErrorPtr* error) {
+ return stream_utils::ErrorOperationNotSupported(FROM_HERE, error);
+}
+
+bool TlsStream::ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) {
+ if (!impl_)
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+ return impl_->ReadNonBlocking(buffer, size_to_read, size_read, end_of_stream,
+ error);
+}
+
+bool TlsStream::WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) {
+ if (!impl_)
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+ return impl_->WriteNonBlocking(buffer, size_to_write, size_written, error);
+}
+
+bool TlsStream::FlushBlocking(ErrorPtr* error) {
+ if (!impl_)
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+ return impl_->Flush(error);
+}
+
+bool TlsStream::CloseBlocking(ErrorPtr* error) {
+ if (impl_ && !impl_->Close(error))
+ return false;
+ impl_.reset();
+ return true;
+}
+
+bool TlsStream::WaitForData(AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) {
+ if (!impl_)
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+ return impl_->WaitForData(mode, callback, error);
+}
+
+bool TlsStream::WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) {
+ if (!impl_)
+ return stream_utils::ErrorStreamClosed(FROM_HERE, error);
+ return impl_->WaitForDataBlocking(in_mode, timeout, out_mode, error);
+}
+
+void TlsStream::CancelPendingAsyncOperations() {
+ if (impl_)
+ impl_->CancelPendingAsyncOperations();
+ Stream::CancelPendingAsyncOperations();
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/streams/tls_stream.h b/libbrillo/brillo/streams/tls_stream.h
new file mode 100644
index 0000000..1431eec
--- /dev/null
+++ b/libbrillo/brillo/streams/tls_stream.h
@@ -0,0 +1,84 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_STREAMS_TLS_STREAM_H_
+#define LIBBRILLO_BRILLO_STREAMS_TLS_STREAM_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+#include <brillo/errors/error.h>
+#include <brillo/streams/stream.h>
+
+namespace brillo {
+
+// This class provides client-side TLS stream that performs handshake with the
+// server and established a secure communication channel which can be used
+// by performing read/write operations on this stream. Both synchronous and
+// asynchronous I/O is supported.
+// The underlying socket stream must already be created and connected to the
+// destination server and passed in TlsStream::Connect() method as |socket|.
+class BRILLO_EXPORT TlsStream : public Stream {
+ public:
+ ~TlsStream() override;
+
+ // Perform a TLS handshake and establish secure connection over |socket|.
+ // Calls |callback| when successful and passes the instance of TlsStream
+ // as an argument. In case of an error, |error_callback| is called.
+ // |host| must specify the expected remote host (server) name.
+ static void Connect(
+ StreamPtr socket,
+ const std::string& host,
+ const base::Callback<void(StreamPtr)>& success_callback,
+ const Stream::ErrorCallback& error_callback);
+
+ // Overrides from Stream:
+ bool IsOpen() const override;
+ bool CanRead() const override { return true; }
+ bool CanWrite() const override { return true; }
+ bool CanSeek() const override { return false; }
+ bool CanGetSize() const override { return false; }
+ uint64_t GetSize() const override { return 0; }
+ bool SetSizeBlocking(uint64_t size, ErrorPtr* error) override;
+ uint64_t GetRemainingSize() const override { return 0; }
+ uint64_t GetPosition() const override { return 0; }
+ bool Seek(int64_t offset,
+ Whence whence,
+ uint64_t* new_position,
+ ErrorPtr* error) override;
+ bool ReadNonBlocking(void* buffer,
+ size_t size_to_read,
+ size_t* size_read,
+ bool* end_of_stream,
+ ErrorPtr* error) override;
+ bool WriteNonBlocking(const void* buffer,
+ size_t size_to_write,
+ size_t* size_written,
+ ErrorPtr* error) override;
+ bool FlushBlocking(ErrorPtr* error) override;
+ bool CloseBlocking(ErrorPtr* error) override;
+ bool WaitForData(AccessMode mode,
+ const base::Callback<void(AccessMode)>& callback,
+ ErrorPtr* error) override;
+ bool WaitForDataBlocking(AccessMode in_mode,
+ base::TimeDelta timeout,
+ AccessMode* out_mode,
+ ErrorPtr* error) override;
+ void CancelPendingAsyncOperations() override;
+
+ private:
+ class TlsStreamImpl;
+
+ // Private constructor called from TlsStream::Connect() factory method.
+ explicit TlsStream(std::unique_ptr<TlsStreamImpl> impl);
+
+ std::unique_ptr<TlsStreamImpl> impl_;
+ DISALLOW_COPY_AND_ASSIGN(TlsStream);
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STREAMS_TLS_STREAM_H_
diff --git a/libbrillo/brillo/strings/string_utils.cc b/libbrillo/brillo/strings/string_utils.cc
new file mode 100644
index 0000000..8279a0e
--- /dev/null
+++ b/libbrillo/brillo/strings/string_utils.cc
@@ -0,0 +1,89 @@
+// 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 <brillo/strings/string_utils.h>
+
+#include <algorithm>
+#include <string.h>
+#include <utility>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+namespace brillo {
+namespace string_utils {
+
+std::vector<std::string> Split(const std::string& str,
+ const std::string& delimiter,
+ bool trim_whitespaces,
+ bool purge_empty_strings) {
+ std::vector<std::string> tokens;
+ if (str.empty())
+ return tokens;
+
+ for (std::string::size_type i = 0;;) {
+ const std::string::size_type pos =
+ delimiter.empty() ? (i + 1) : str.find(delimiter, i);
+ std::string tmp_str{str.substr(i, pos - i)};
+ if (trim_whitespaces)
+ base::TrimWhitespaceASCII(tmp_str, base::TRIM_ALL, &tmp_str);
+ if (!tmp_str.empty() || !purge_empty_strings)
+ tokens.emplace_back(std::move(tmp_str));
+ if (pos >= str.size())
+ break;
+ i = pos + delimiter.size();
+ }
+ return tokens;
+}
+
+bool SplitAtFirst(const std::string& str,
+ const std::string& delimiter,
+ std::string* left_part,
+ std::string* right_part,
+ bool trim_whitespaces) {
+ bool delimiter_found = false;
+ std::string::size_type pos = str.find(delimiter);
+ if (pos != std::string::npos) {
+ *left_part = str.substr(0, pos);
+ *right_part = str.substr(pos + delimiter.size());
+ delimiter_found = true;
+ } else {
+ *left_part = str;
+ right_part->clear();
+ }
+
+ if (trim_whitespaces) {
+ base::TrimWhitespaceASCII(*left_part, base::TRIM_ALL, left_part);
+ base::TrimWhitespaceASCII(*right_part, base::TRIM_ALL, right_part);
+ }
+
+ return delimiter_found;
+}
+
+std::pair<std::string, std::string> SplitAtFirst(const std::string& str,
+ const std::string& delimiter,
+ bool trim_whitespaces) {
+ std::pair<std::string, std::string> pair;
+ SplitAtFirst(str, delimiter, &pair.first, &pair.second, trim_whitespaces);
+ return pair;
+}
+
+std::string ToString(double value) {
+ return base::StringPrintf("%g", value);
+}
+
+std::string ToString(bool value) {
+ return value ? "true" : "false";
+}
+
+std::string GetBytesAsString(const std::vector<uint8_t>& buffer) {
+ return std::string(buffer.begin(), buffer.end());
+}
+
+std::vector<uint8_t> GetStringAsBytes(const std::string& str) {
+ return std::vector<uint8_t>(str.begin(), str.end());
+}
+
+} // namespace string_utils
+} // namespace brillo
diff --git a/libbrillo/brillo/strings/string_utils.h b/libbrillo/brillo/strings/string_utils.h
new file mode 100644
index 0000000..6f1a934
--- /dev/null
+++ b/libbrillo/brillo/strings/string_utils.h
@@ -0,0 +1,131 @@
+// 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 LIBBRILLO_BRILLO_STRINGS_STRING_UTILS_H_
+#define LIBBRILLO_BRILLO_STRINGS_STRING_UTILS_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+namespace string_utils {
+
+// Treats the string as a delimited list of substrings and returns the array
+// of original elements of the list.
+// |trim_whitespaces| causes each element to have all whitespaces trimmed off.
+// |purge_empty_strings| specifies whether empty elements from the original
+// string should be omitted.
+BRILLO_EXPORT std::vector<std::string> Split(const std::string& str,
+ const std::string& delimiter,
+ bool trim_whitespaces,
+ bool purge_empty_strings);
+// Splits the string, trims all whitespaces, omits empty string parts.
+inline std::vector<std::string> Split(const std::string& str,
+ const std::string& delimiter) {
+ return Split(str, delimiter, true, true);
+}
+// Splits the string, omits empty string parts.
+inline std::vector<std::string> Split(const std::string& str,
+ const std::string& delimiter,
+ bool trim_whitespaces) {
+ return Split(str, delimiter, trim_whitespaces, true);
+}
+
+// Splits the string into two pieces at the first position of the specified
+// delimiter.
+BRILLO_EXPORT std::pair<std::string, std::string> SplitAtFirst(
+ const std::string& str,
+ const std::string& delimiter,
+ bool trim_whitespaces);
+// Splits the string into two pieces at the first position of the specified
+// delimiter. Both parts have all whitespaces trimmed off.
+inline std::pair<std::string, std::string> SplitAtFirst(
+ const std::string& str,
+ const std::string& delimiter) {
+ return SplitAtFirst(str, delimiter, true);
+}
+
+// The following overload returns false if the delimiter was not found in the
+// source string. In this case, |left_part| will be set to |str| and
+// |right_part| will be empty.
+BRILLO_EXPORT bool SplitAtFirst(const std::string& str,
+ const std::string& delimiter,
+ std::string* left_part,
+ std::string* right_part,
+ bool trim_whitespaces);
+// Always trims the white spaces in the split parts.
+inline bool SplitAtFirst(const std::string& str,
+ const std::string& delimiter,
+ std::string* left_part,
+ std::string* right_part) {
+ return SplitAtFirst(str, delimiter, left_part, right_part, true);
+}
+
+// Joins strings into a single string separated by |delimiter|.
+template <class InputIterator>
+std::string JoinRange(const std::string& delimiter,
+ InputIterator first,
+ InputIterator last) {
+ std::string result;
+ if (first == last)
+ return result;
+ result = *first;
+ for (++first; first != last; ++first) {
+ result += delimiter;
+ result += *first;
+ }
+ return result;
+}
+
+template <class Container>
+std::string Join(const std::string& delimiter, const Container& strings) {
+ using std::begin;
+ using std::end;
+ return JoinRange(delimiter, begin(strings), end(strings));
+}
+
+inline std::string Join(const std::string& delimiter,
+ std::initializer_list<std::string> strings) {
+ return JoinRange(delimiter, strings.begin(), strings.end());
+}
+
+inline std::string Join(const std::string& delimiter,
+ const std::string& str1,
+ const std::string& str2) {
+ return str1 + delimiter + 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.
+BRILLO_EXPORT std::string ToString(double value);
+// And the bool to be converted as true/false instead of 1/0.
+BRILLO_EXPORT std::string ToString(bool value);
+
+// Converts a byte-array into a string. This method doesn't perform any
+// data re-encoding. It just takes every byte from the buffer and appends it
+// to the string as a character.
+BRILLO_EXPORT std::string GetBytesAsString(const std::vector<uint8_t>& buf);
+
+// Converts a string into a byte-array. Opposite of GetBytesAsString().
+BRILLO_EXPORT std::vector<uint8_t> GetStringAsBytes(const std::string& str);
+
+} // namespace string_utils
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_STRINGS_STRING_UTILS_H_
diff --git a/libbrillo/brillo/strings/string_utils_unittest.cc b/libbrillo/brillo/strings/string_utils_unittest.cc
new file mode 100644
index 0000000..c554e74
--- /dev/null
+++ b/libbrillo/brillo/strings/string_utils_unittest.cc
@@ -0,0 +1,163 @@
+// 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 <brillo/strings/string_utils.h>
+
+#include <list>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+TEST(StringUtils, Split) {
+ std::vector<std::string> parts;
+
+ parts = string_utils::Split("", ",", false, false);
+ EXPECT_EQ(0, parts.size());
+
+ parts = string_utils::Split("abc", ",", false, false);
+ EXPECT_EQ(1, parts.size());
+ EXPECT_EQ("abc", parts[0]);
+
+ 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(6, parts.size());
+ EXPECT_EQ("a", parts[0]);
+ EXPECT_EQ("bc ", parts[1]);
+ EXPECT_EQ(" d", parts[2]);
+ EXPECT_EQ(" ", parts[3]);
+ EXPECT_EQ("e", parts[4]);
+ EXPECT_EQ(" ", parts[5]);
+
+ 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]);
+
+ parts = string_utils::Split("abc:=xyz", ":=", false, false);
+ EXPECT_EQ(2, parts.size());
+ EXPECT_EQ("abc", parts[0]);
+ EXPECT_EQ("xyz", parts[1]);
+
+ parts = string_utils::Split("abc", "", false, false);
+ EXPECT_EQ(3, parts.size());
+ EXPECT_EQ("a", parts[0]);
+ EXPECT_EQ("b", parts[1]);
+ EXPECT_EQ("c", parts[2]);
+}
+
+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);
+
+ pair = string_utils::SplitAtFirst("abc:=xyz", ":=");
+ EXPECT_EQ("abc", pair.first);
+ EXPECT_EQ("xyz", pair.second);
+
+ pair = string_utils::SplitAtFirst("abc", "");
+ EXPECT_EQ("", pair.first);
+ EXPECT_EQ("abc", pair.second);
+}
+
+TEST(StringUtils, Join_String) {
+ EXPECT_EQ("", string_utils::Join(",", {}));
+ EXPECT_EQ("abc", string_utils::Join(",", {"abc"}));
+ EXPECT_EQ("abc,,xyz", string_utils::Join(",", {"abc", "", "xyz"}));
+ EXPECT_EQ("abc,defg", string_utils::Join(",", {"abc", "defg"}));
+ EXPECT_EQ("1 : 2 : 3", string_utils::Join(" : ", {"1", "2", "3"}));
+ EXPECT_EQ("1:2", string_utils::Join(":", std::set<std::string>{"1", "2"}));
+ EXPECT_EQ("1:2", string_utils::Join(":", std::vector<std::string>{"1", "2"}));
+ EXPECT_EQ("1:2", string_utils::Join(":", std::list<std::string>{"1", "2"}));
+ 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"));
+}
+
+TEST(StringUtils, GetBytesAsString) {
+ EXPECT_EQ("abc", string_utils::GetBytesAsString({'a', 'b', 'c'}));
+ EXPECT_TRUE(string_utils::GetBytesAsString({}).empty());
+ auto str = string_utils::GetBytesAsString({0xFF, 0x00, 0x01, 0x7F, 0x80});
+ ASSERT_EQ(5, str.size());
+ EXPECT_EQ('\xFF', str[0]);
+ EXPECT_EQ('\x00', str[1]);
+ EXPECT_EQ('\x01', str[2]);
+ EXPECT_EQ('\x7F', str[3]);
+ EXPECT_EQ('\x80', str[4]);
+}
+
+TEST(StringUtils, GetStringAsBytes) {
+ EXPECT_EQ((std::vector<uint8_t>{'a', 'b', 'c'}),
+ string_utils::GetStringAsBytes("abc"));
+ EXPECT_TRUE(string_utils::GetStringAsBytes("").empty());
+ auto buf = string_utils::GetStringAsBytes(std::string{"\x80\0\1\xFF", 4});
+ ASSERT_EQ(4, buf.size());
+ EXPECT_EQ(128, buf[0]);
+ EXPECT_EQ(0, buf[1]);
+ EXPECT_EQ(1, buf[2]);
+ EXPECT_EQ(255, buf[3]);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/syslog_logging.cc b/libbrillo/brillo/syslog_logging.cc
new file mode 100644
index 0000000..40b064a
--- /dev/null
+++ b/libbrillo/brillo/syslog_logging.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "brillo/syslog_logging.h"
+
+#include <syslog.h>
+
+#include <string>
+
+// syslog.h and base/logging.h both try to #define LOG_INFO and LOG_WARNING.
+// We need to #undef at least these two before including base/logging.h. The
+// others are included to be consistent.
+namespace {
+const int kSyslogDebug = LOG_DEBUG;
+const int kSyslogInfo = LOG_INFO;
+const int kSyslogWarning = LOG_WARNING;
+const int kSyslogError = LOG_ERR;
+const int kSyslogCritical = LOG_CRIT;
+
+#undef LOG_INFO
+#undef LOG_WARNING
+#undef LOG_ERR
+#undef LOG_CRIT
+} // namespace
+
+#include <base/logging.h>
+
+static std::string s_ident;
+static std::string s_accumulated;
+static bool s_accumulate;
+static bool s_log_to_syslog;
+static bool s_log_to_stderr;
+static bool s_log_header;
+
+static bool HandleMessage(int severity,
+ const char* /* file */,
+ int /* line */,
+ size_t message_start,
+ const std::string& message) {
+ switch (severity) {
+ case logging::LOG_INFO:
+ severity = kSyslogInfo;
+ break;
+
+ case logging::LOG_WARNING:
+ severity = kSyslogWarning;
+ break;
+
+ case logging::LOG_ERROR:
+ severity = kSyslogError;
+ break;
+
+ case logging::LOG_FATAL:
+ severity = kSyslogCritical;
+ break;
+
+ default:
+ severity = kSyslogDebug;
+ break;
+ }
+
+ const char* str;
+ if (s_log_header) {
+ str = message.c_str();
+ } else {
+ str = message.c_str() + message_start;
+ }
+
+ if (s_log_to_syslog)
+ syslog(severity, "%s", str);
+ if (s_accumulate)
+ s_accumulated.append(str);
+ return !s_log_to_stderr && severity != kSyslogCritical;
+}
+
+namespace brillo {
+void SetLogFlags(int log_flags) {
+ s_log_to_syslog = (log_flags & kLogToSyslog) != 0;
+ s_log_to_stderr = (log_flags & kLogToStderr) != 0;
+ s_log_header = (log_flags & kLogHeader) != 0;
+}
+int GetLogFlags() {
+ int flags = 0;
+ flags |= (s_log_to_syslog) ? kLogToSyslog : 0;
+ flags |= (s_log_to_stderr) ? kLogToStderr : 0;
+ flags |= (s_log_header) ? kLogHeader : 0;
+ return flags;
+}
+void InitLog(int init_flags) {
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ logging::InitLogging(settings);
+
+ const bool kOptionPID = false;
+ const bool kOptionTID = false;
+ const bool kOptionTimestamp = false;
+ const bool kOptionTickcount = false;
+ logging::SetLogItems(
+ kOptionPID, kOptionTID, kOptionTimestamp, kOptionTickcount);
+ logging::SetLogMessageHandler(HandleMessage);
+ SetLogFlags(init_flags);
+}
+void OpenLog(const char* ident, bool log_pid) {
+ s_ident = ident;
+ openlog(s_ident.c_str(), log_pid ? LOG_PID : 0, LOG_USER);
+}
+void LogToString(bool enabled) {
+ s_accumulate = enabled;
+}
+std::string GetLog() {
+ return s_accumulated;
+}
+void ClearLog() {
+ s_accumulated.clear();
+}
+bool FindLog(const char* string) {
+ return s_accumulated.find(string) != std::string::npos;
+}
+} // namespace brillo
diff --git a/libbrillo/brillo/syslog_logging.h b/libbrillo/brillo/syslog_logging.h
new file mode 100644
index 0000000..b054259
--- /dev/null
+++ b/libbrillo/brillo/syslog_logging.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_BRILLO_SYSLOG_LOGGING_H_
+#define LIBBRILLO_BRILLO_SYSLOG_LOGGING_H_
+
+#include <string>
+
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+enum InitFlags {
+ kLogToSyslog = 1,
+ kLogToStderr = 2,
+ kLogHeader = 4,
+};
+
+// Initialize logging subsystem. |init_flags| is a bitfield, with bits defined
+// in InitFlags above.
+BRILLO_EXPORT void InitLog(int init_flags);
+// Gets the current logging flags.
+BRILLO_EXPORT int GetLogFlags();
+// Sets the current logging flags.
+BRILLO_EXPORT void SetLogFlags(int log_flags);
+// Convenience function for configuring syslog via openlog. Users
+// could call openlog directly except for naming collisions between
+// base/logging.h and syslog.h. Similarly users cannot pass the
+// normal parameters so we pick a representative set. |log_pid|
+// causes pid to be logged with |ident|.
+BRILLO_EXPORT void OpenLog(const char* ident, bool log_pid);
+// Start accumulating the logs to a string. This is inefficient, so
+// do not set to true if large numbers of log messages are coming.
+// Accumulated logs are only ever cleared when the clear function ings
+// called.
+BRILLO_EXPORT void LogToString(bool enabled);
+// Get the accumulated logs as a string.
+BRILLO_EXPORT std::string GetLog();
+// Clear the accumulated logs.
+BRILLO_EXPORT void ClearLog();
+// Returns true if the accumulated log contains the given string. Useful
+// for testing.
+BRILLO_EXPORT bool FindLog(const char* string);
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_SYSLOG_LOGGING_H_
diff --git a/libbrillo/brillo/syslog_logging_unittest.cc b/libbrillo/brillo/syslog_logging_unittest.cc
new file mode 100644
index 0000000..e852e50
--- /dev/null
+++ b/libbrillo/brillo/syslog_logging_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <base/logging.h>
+#include <brillo/syslog_logging.h>
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+class SyslogLoggingDeathTest : public ::testing::Test {
+ public:
+ SyslogLoggingDeathTest() {}
+ virtual ~SyslogLoggingDeathTest() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SyslogLoggingDeathTest);
+};
+
+TEST_F(SyslogLoggingDeathTest, FatalLoggingIsFatal) {
+ int old_flags = GetLogFlags();
+ SetLogFlags(kLogToStderr);
+ EXPECT_DEATH({ LOG(FATAL) << "First Fatality!"; }, "First Fatality!");
+ // No flags == don't log to syslog, stderr, or accumulated string.
+ SetLogFlags(0);
+ // Still a fatal log message
+ EXPECT_DEATH({ LOG(FATAL) << "Second Fatality!"; }, "Second Fatality!");
+ SetLogFlags(old_flags);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/test_helpers.h b/libbrillo/brillo/test_helpers.h
new file mode 100644
index 0000000..815eede
--- /dev/null
+++ b/libbrillo/brillo/test_helpers.h
@@ -0,0 +1,32 @@
+// 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.
+
+#ifndef LIBBRILLO_BRILLO_TEST_HELPERS_H_
+#define LIBBRILLO_BRILLO_TEST_HELPERS_H_
+
+#include "gtest/gtest.h"
+
+#include <string>
+
+#include <base/command_line.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+
+#include "brillo/syslog_logging.h"
+
+inline void ExpectFileEquals(const char* golden, const char* file_path) {
+ std::string contents;
+ EXPECT_TRUE(base::ReadFileToString(base::FilePath(file_path), &contents));
+ EXPECT_EQ(golden, contents);
+}
+
+inline void SetUpTests(int* argc, char** argv, bool log_to_stderr) {
+ base::CommandLine::Init(*argc, argv);
+ ::brillo::InitLog(log_to_stderr ? brillo::kLogToStderr : 0);
+ ::brillo::LogToString(true);
+ ::testing::InitGoogleTest(argc, argv);
+}
+
+#endif // LIBBRILLO_BRILLO_TEST_HELPERS_H_
diff --git a/libbrillo/brillo/type_name_undecorate.cc b/libbrillo/brillo/type_name_undecorate.cc
new file mode 100644
index 0000000..b588170
--- /dev/null
+++ b/libbrillo/brillo/type_name_undecorate.cc
@@ -0,0 +1,68 @@
+// 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 <brillo/type_name_undecorate.h>
+
+#include <cstring>
+
+#ifdef __GNUG__
+#include <cstdlib>
+#include <cxxabi.h>
+#include <memory>
+#endif // __GNUG__
+
+namespace brillo {
+
+std::string UndecorateTypeName(const char* type_name) {
+#ifdef __GNUG__
+ // Under g++ use abi::__cxa_demangle() to undecorate the type name.
+ int status = 0;
+
+ std::unique_ptr<char, decltype(&std::free)> res{
+ abi::__cxa_demangle(type_name, nullptr, nullptr, &status),
+ std::free
+ };
+
+ return (status == 0) ? res.get() : type_name;
+#else
+ // If not compiled with g++, do nothing...
+ // E.g. MSVC's type_info::name() already contains undecorated name.
+ return type_name;
+#endif
+}
+
+std::string GetUndecoratedTypeNameForTag(const char* type_tag) {
+#if defined(USE_RTTI_FOR_TYPE_TAGS) && \
+ (defined(__cpp_rtti) || defined(__GXX_RTTI))
+ return UndecorateTypeName(type_tag);
+#else
+ // The signature of type tag for, say, 'int' would be the following:
+ // const char *brillo::GetTypeTag() [T = int]
+ // So we just need to extract the type name between '[T = ' and ']'.
+ const char* token = " = ";
+ const char* pos = std::strstr(type_tag, token);
+ if (!pos)
+ return type_tag;
+ std::string name = pos + std::strlen(token);
+ if (!name.empty() && name.back() == ']')
+ name.pop_back();
+ return name;
+#endif
+}
+
+// Implementations of the explicitly instantiated GetTypeTag<T>() for common
+// types.
+template const char* GetTypeTag<int8_t>();
+template const char* GetTypeTag<uint8_t>();
+template const char* GetTypeTag<int16_t>();
+template const char* GetTypeTag<uint16_t>();
+template const char* GetTypeTag<int32_t>();
+template const char* GetTypeTag<uint32_t>();
+template const char* GetTypeTag<int64_t>();
+template const char* GetTypeTag<uint64_t>();
+template const char* GetTypeTag<bool>();
+template const char* GetTypeTag<double>();
+template const char* GetTypeTag<std::string>();
+
+} // namespace brillo
diff --git a/libbrillo/brillo/type_name_undecorate.h b/libbrillo/brillo/type_name_undecorate.h
new file mode 100644
index 0000000..c750e58
--- /dev/null
+++ b/libbrillo/brillo/type_name_undecorate.h
@@ -0,0 +1,69 @@
+// 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 LIBBRILLO_BRILLO_TYPE_NAME_UNDECORATE_H_
+#define LIBBRILLO_BRILLO_TYPE_NAME_UNDECORATE_H_
+
+#include <string>
+#include <typeinfo>
+
+#include <brillo/brillo_export.h>
+
+#if !defined(USE_RTTI_FOR_TYPE_TAGS) && !defined(__clang__)
+// When type information is used with RTTI disabled, we rely on
+// __PRETTY_FUNCTION__ macro for type tags. Unfortunately gcc and clang produce
+// different signatures for types that have optional template parameters, such
+// as std::vector and std::map. The problem arises when inter-operating between
+// libraries that are compiled with different compilers.
+// Since most of Brillo is compiled with clang, we choose clang here exclusively
+// and prevent this code from compiling with GCC to avoid hidden runtime errors.
+#error TypeInfo/Any with RTTI disabled is supported on clang compiler only.
+#endif
+
+namespace brillo {
+
+template<typename T>
+const char* GetTypeTag() {
+#if defined(USE_RTTI_FOR_TYPE_TAGS) && \
+ (defined(__cpp_rtti) || defined(__GXX_RTTI))
+ return typeid(T).name();
+#else
+ // __PRETTY_FUNCTION__ would include the type T signature and therefore each
+ // instance of brillo::internal_details::GetTypeTag<T>() will have a different
+ // tag string.
+ return __PRETTY_FUNCTION__;
+#endif
+}
+
+// Explicitly instantiate GetTypeTag<T>() for common types to minimize static
+// data segment pollution.
+extern template BRILLO_EXPORT const char* GetTypeTag<int8_t>();
+extern template BRILLO_EXPORT const char* GetTypeTag<uint8_t>();
+extern template BRILLO_EXPORT const char* GetTypeTag<int16_t>();
+extern template BRILLO_EXPORT const char* GetTypeTag<uint16_t>();
+extern template BRILLO_EXPORT const char* GetTypeTag<int32_t>();
+extern template BRILLO_EXPORT const char* GetTypeTag<uint32_t>();
+extern template BRILLO_EXPORT const char* GetTypeTag<int64_t>();
+extern template BRILLO_EXPORT const char* GetTypeTag<uint64_t>();
+extern template BRILLO_EXPORT const char* GetTypeTag<bool>();
+extern template BRILLO_EXPORT const char* GetTypeTag<double>();
+extern template BRILLO_EXPORT const char* GetTypeTag<std::string>();
+
+// Use brillo::UndecorateTypeName() to obtain human-readable type from
+// the decorated/mangled type name returned by std::type_info::name().
+BRILLO_EXPORT std::string UndecorateTypeName(const char* type_name);
+
+// Returns undecorated type name for the given type tag. This will extract the
+// actual type name from the type tag string.
+BRILLO_EXPORT std::string GetUndecoratedTypeNameForTag(const char* type_tag);
+
+// A template helper function that returns the undecorated type name for type T.
+template<typename T>
+inline std::string GetUndecoratedTypeName() {
+ return GetUndecoratedTypeNameForTag(GetTypeTag<T>());
+}
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_TYPE_NAME_UNDECORATE_H_
diff --git a/libbrillo/brillo/type_name_undecorate_unittest.cc b/libbrillo/brillo/type_name_undecorate_unittest.cc
new file mode 100644
index 0000000..604c0fb
--- /dev/null
+++ b/libbrillo/brillo/type_name_undecorate_unittest.cc
@@ -0,0 +1,67 @@
+// 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 <brillo/type_name_undecorate.h>
+
+#include <brillo/variant_dictionary.h>
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+// Tests using tags from the __PRETTY_FUNCTION__ don't work when using RTTI
+// to get the type name.
+#ifndef USE_RTTI_FOR_TYPE_TAGS
+TEST(TypeTags, GetTypeTag) {
+ EXPECT_STREQ("const char *brillo::GetTypeTag() [T = int]", GetTypeTag<int>());
+ EXPECT_STREQ("const char *brillo::GetTypeTag() [T = std::__1::map<std::__1::"
+ "basic_string<char, std::__1::char_traits<char>, "
+ "std::__1::allocator<char> >, brillo::Any, std::__1::less<"
+ "std::__1::basic_string<char, std::__1::char_traits<char>, "
+ "std::__1::allocator<char> > >, std::__1::allocator<std::__1::"
+ "pair<const std::__1::basic_string<char, std::__1::char_traits"
+ "<char>, std::__1::allocator<char> >, brillo::Any> > >]",
+ GetTypeTag<VariantDictionary>());
+ EXPECT_STREQ("const char *brillo::GetTypeTag() [T = int []]",
+ GetTypeTag<int[]>());
+}
+#endif // USE_RTTI_FOR_TYPE_TAGS
+
+TEST(TypeDecoration, UndecorateTypeName) {
+ EXPECT_EQ("int", UndecorateTypeName("i"));
+ EXPECT_EQ("char const* brillo::GetTypeTag<unsigned long long>()",
+ UndecorateTypeName("_ZN6brillo10GetTypeTagIyEEPKcv"));
+ EXPECT_EQ("std::__1::to_string(int)",
+ UndecorateTypeName("_ZNSt3__19to_stringEi"));
+}
+
+#ifndef USE_RTTI_FOR_TYPE_TAGS
+TEST(TypeDecoration, GetUndecoratedTypeNameForTag) {
+ EXPECT_EQ("int",
+ GetUndecoratedTypeNameForTag(
+ "const char *brillo::GetTypeTag() [T = int]"));
+ EXPECT_EQ("int []",
+ GetUndecoratedTypeNameForTag(
+ "const char *brillo::GetTypeTag() [T = int []]"));
+ EXPECT_EQ("foo::bar<int []>()",
+ GetUndecoratedTypeNameForTag(
+ "const char *brillo::GetTypeTag() [T = foo::bar<int []>()]"));
+}
+
+TEST(TypeDecoration, GetUndecoratedTypeName) {
+ EXPECT_EQ("int", GetUndecoratedTypeName<int>());
+ EXPECT_EQ("int *", GetUndecoratedTypeName<int*>());
+ EXPECT_EQ("const int *", GetUndecoratedTypeName<const int*>());
+ EXPECT_EQ("int []", GetUndecoratedTypeName<int[]>());
+ EXPECT_EQ("bool", GetUndecoratedTypeName<bool>());
+ EXPECT_EQ("char", GetUndecoratedTypeName<char>());
+ EXPECT_EQ("float", GetUndecoratedTypeName<float>());
+ EXPECT_EQ("double", GetUndecoratedTypeName<double>());
+ EXPECT_EQ("long", GetUndecoratedTypeName<long>());
+ EXPECT_EQ("std::__1::map<int, double, std::__1::less<int>, "
+ "std::__1::allocator<std::__1::pair<const int, double> > >",
+ (GetUndecoratedTypeName<std::map<int, double>>()));
+}
+#endif // USE_RTTI_FOR_TYPE_TAGS
+
+} // namespace brillo
diff --git a/libbrillo/brillo/unittest_utils.cc b/libbrillo/brillo/unittest_utils.cc
new file mode 100644
index 0000000..7cace5d
--- /dev/null
+++ b/libbrillo/brillo/unittest_utils.cc
@@ -0,0 +1,53 @@
+// 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 <brillo/unittest_utils.h>
+
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+const int ScopedPipe::kPipeSize = 4096;
+
+ScopedPipe::ScopedPipe() {
+ int fds[2];
+ if (pipe(fds) != 0) {
+ PLOG(FATAL) << "Creating a pipe()";
+ }
+ reader = fds[0];
+ writer = fds[1];
+ EXPECT_EQ(kPipeSize, fcntl(writer, F_SETPIPE_SZ, kPipeSize));
+}
+
+ScopedPipe::~ScopedPipe() {
+ if (reader != -1)
+ close(reader);
+ if (writer != -1)
+ close(writer);
+}
+
+
+ScopedSocketPair::ScopedSocketPair() {
+ int fds[2];
+ if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) != 0) {
+ PLOG(FATAL) << "Creating a socketpair()";
+ }
+ left = fds[0];
+ right = fds[1];
+}
+
+ScopedSocketPair::~ScopedSocketPair() {
+ if (left != -1)
+ close(left);
+ if (right != -1)
+ close(right);
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/unittest_utils.h b/libbrillo/brillo/unittest_utils.h
new file mode 100644
index 0000000..a7f92db
--- /dev/null
+++ b/libbrillo/brillo/unittest_utils.h
@@ -0,0 +1,43 @@
+// Copyright 2015 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 brillo::Any class.
+
+#ifndef LIBBRILLO_BRILLO_UNITTEST_UTILS_H_
+#define LIBBRILLO_BRILLO_UNITTEST_UTILS_H_
+
+namespace brillo {
+
+// Helper class to create and close a unidirectional pipe. The file descriptors
+// will be closed on destruction, unless set to -1.
+class ScopedPipe {
+ public:
+ // The internal pipe size.
+ static const int kPipeSize;
+
+ ScopedPipe();
+ ~ScopedPipe();
+
+ // The reader and writer end of the pipe.
+ int reader{-1};
+ int writer{-1};
+};
+
+// Helper class to create and close a bi-directional pair of sockets. The
+// sockets will be closed on destruction, unless set to -1.
+class ScopedSocketPair {
+ public:
+ ScopedSocketPair();
+ ~ScopedSocketPair();
+
+ // The left and right sockets are bi-directional connected and
+ // indistinguishable file descriptor. We named them left/right for easier
+ // reading.
+ int left{-1};
+ int right{-1};
+};
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_UNITTEST_UTILS_H_
diff --git a/libbrillo/brillo/url_utils.cc b/libbrillo/brillo/url_utils.cc
new file mode 100644
index 0000000..eba7db8
--- /dev/null
+++ b/libbrillo/brillo/url_utils.cc
@@ -0,0 +1,166 @@
+// 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 <brillo/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 brillo {
+
+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 brillo
diff --git a/libbrillo/brillo/url_utils.h b/libbrillo/brillo/url_utils.h
new file mode 100644
index 0000000..fd66dd6
--- /dev/null
+++ b/libbrillo/brillo/url_utils.h
@@ -0,0 +1,85 @@
+// 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 LIBBRILLO_BRILLO_URL_UTILS_H_
+#define LIBBRILLO_BRILLO_URL_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include <base/compiler_specific.h>
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+#include <brillo/data_encoding.h>
+
+namespace brillo {
+
+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.
+BRILLO_EXPORT std::string Combine(
+ const std::string& url,
+ const std::string& subpath) WARN_UNUSED_RESULT;
+BRILLO_EXPORT 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"
+BRILLO_EXPORT 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.
+BRILLO_EXPORT std::string GetQueryString(const std::string& url,
+ bool remove_fragment);
+
+// Parses the query string into a set of key-value pairs.
+BRILLO_EXPORT data_encoding::WebParamList GetQueryStringParameters(
+ const std::string& url);
+
+// Returns a value of the specified query parameter, or empty string if missing.
+BRILLO_EXPORT std::string GetQueryStringValue(
+ const std::string& url,
+ const std::string& name);
+BRILLO_EXPORT 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
+BRILLO_EXPORT std::string RemoveQueryString(
+ const std::string& url,
+ bool remove_fragment) WARN_UNUSED_RESULT;
+
+// Appends a single query parameter to the URL.
+BRILLO_EXPORT 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.
+BRILLO_EXPORT std::string AppendQueryParams(
+ const std::string& url,
+ const data_encoding::WebParamList& params) WARN_UNUSED_RESULT;
+
+// Checks if the URL has query parameters.
+BRILLO_EXPORT bool HasQueryString(const std::string& url);
+
+} // namespace url
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_URL_UTILS_H_
diff --git a/libbrillo/brillo/url_utils_unittest.cc b/libbrillo/brillo/url_utils_unittest.cc
new file mode 100644
index 0000000..a2603cb
--- /dev/null
+++ b/libbrillo/brillo/url_utils_unittest.cc
@@ -0,0 +1,146 @@
+// 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 <brillo/url_utils.h>
+
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+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¶m2=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¶m2=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"));
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/userdb_utils.cc b/libbrillo/brillo/userdb_utils.cc
new file mode 100644
index 0000000..55c964c
--- /dev/null
+++ b/libbrillo/brillo/userdb_utils.cc
@@ -0,0 +1,56 @@
+// Copyright 2015 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 "brillo/userdb_utils.h"
+
+#include <grp.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include <base/logging.h>
+
+namespace brillo {
+namespace userdb {
+
+bool GetUserInfo(const std::string& user, uid_t* uid, gid_t* gid) {
+ ssize_t buf_len = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (buf_len < 0)
+ buf_len = 16384; // 16K should be enough?...
+ passwd pwd_buf;
+ passwd* pwd = nullptr;
+ std::vector<char> buf(buf_len);
+ if (getpwnam_r(user.c_str(), &pwd_buf, buf.data(), buf_len, &pwd) || !pwd) {
+ PLOG(ERROR) << "Unable to find user " << user;
+ return false;
+ }
+
+ if (uid)
+ *uid = pwd->pw_uid;
+ if (gid)
+ *gid = pwd->pw_gid;
+ return true;
+}
+
+bool GetGroupInfo(const std::string& group, gid_t* gid) {
+ ssize_t buf_len = sysconf(_SC_GETGR_R_SIZE_MAX);
+ if (buf_len < 0)
+ buf_len = 16384; // 16K should be enough?...
+ struct group grp_buf;
+ struct group* grp = nullptr;
+ std::vector<char> buf(buf_len);
+ if (getgrnam_r(group.c_str(), &grp_buf, buf.data(), buf_len, &grp) || !grp) {
+ PLOG(ERROR) << "Unable to find group " << group;
+ return false;
+ }
+
+ if (gid)
+ *gid = grp->gr_gid;
+ return true;
+}
+
+} // namespace userdb
+} // namespace brillo
diff --git a/libbrillo/brillo/userdb_utils.h b/libbrillo/brillo/userdb_utils.h
new file mode 100644
index 0000000..8d6dc20
--- /dev/null
+++ b/libbrillo/brillo/userdb_utils.h
@@ -0,0 +1,32 @@
+// Copyright 2015 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 LIBBRILLO_BRILLO_USERDB_UTILS_H_
+#define LIBBRILLO_BRILLO_USERDB_UTILS_H_
+
+#include <sys/types.h>
+
+#include <string>
+
+#include <base/compiler_specific.h>
+#include <base/macros.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+namespace userdb {
+
+// Looks up the UID and GID corresponding to |user|. Returns true on success.
+// Passing nullptr for |uid| or |gid| causes them to be ignored.
+BRILLO_EXPORT bool GetUserInfo(
+ const std::string& user, uid_t* uid, gid_t* gid) WARN_UNUSED_RESULT;
+
+// Looks up the GID corresponding to |group|. Returns true on success.
+// Passing nullptr for |gid| causes it to be ignored.
+BRILLO_EXPORT bool GetGroupInfo(
+ const std::string& group, gid_t* gid) WARN_UNUSED_RESULT;
+
+} // namespace userdb
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_USERDB_UTILS_H_
diff --git a/libbrillo/brillo/value_conversion.cc b/libbrillo/brillo/value_conversion.cc
new file mode 100644
index 0000000..aa5135b
--- /dev/null
+++ b/libbrillo/brillo/value_conversion.cc
@@ -0,0 +1,60 @@
+// Copyright 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <brillo/value_conversion.h>
+
+#include <string>
+#include <vector>
+
+namespace brillo {
+
+bool FromValue(const base::Value& in_value,
+ std::unique_ptr<base::ListValue>* out_value) {
+ const base::ListValue* list = nullptr;
+ if (!in_value.GetAsList(&list))
+ return false;
+ out_value->reset(list->DeepCopy());
+ return true;
+}
+
+bool FromValue(const base::Value& in_value,
+ std::unique_ptr<base::DictionaryValue>* out_value) {
+ const base::DictionaryValue* dict = nullptr;
+ if (!in_value.GetAsDictionary(&dict))
+ return false;
+ out_value->reset(dict->DeepCopy());
+ return true;
+}
+
+std::unique_ptr<base::Value> ToValue(int value) {
+ return std::unique_ptr<base::Value>{new base::FundamentalValue{value}};
+}
+
+std::unique_ptr<base::Value> ToValue(bool value) {
+ return std::unique_ptr<base::Value>{new base::FundamentalValue{value}};
+}
+
+std::unique_ptr<base::Value> ToValue(double value) {
+ return std::unique_ptr<base::Value>{new base::FundamentalValue{value}};
+}
+
+std::unique_ptr<base::Value> ToValue(const char* value) {
+ return std::unique_ptr<base::Value>{new base::StringValue{value}};
+}
+
+std::unique_ptr<base::Value> ToValue(const std::string& value) {
+ return std::unique_ptr<base::Value>{new base::StringValue{value}};
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/value_conversion.h b/libbrillo/brillo/value_conversion.h
new file mode 100644
index 0000000..d681f76
--- /dev/null
+++ b/libbrillo/brillo/value_conversion.h
@@ -0,0 +1,137 @@
+// Copyright 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BRILLO_VALUE_CONVERSION_H_
+#define BRILLO_VALUE_CONVERSION_H_
+
+// This file provides a set of helper functions to convert between base::Value
+// and native types. Apart from handling standard types such as 'int' and
+// 'std::string' it also provides conversion to/from std::vector<T> (which
+// converts to Base::listValue) and std::map<std::string, T> (convertible to
+// base::DictionaryValue).
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/values.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+inline bool FromValue(const base::Value& in_value, bool* out_value) {
+ return in_value.GetAsBoolean(out_value);
+}
+
+inline bool FromValue(const base::Value& in_value, int* out_value) {
+ return in_value.GetAsInteger(out_value);
+}
+
+inline bool FromValue(const base::Value& in_value, double* out_value) {
+ return in_value.GetAsDouble(out_value);
+}
+
+inline bool FromValue(const base::Value& in_value, std::string* out_value) {
+ return in_value.GetAsString(out_value);
+}
+
+inline bool FromValue(const base::Value& in_value,
+ const base::ListValue** out_value) {
+ return in_value.GetAsList(out_value);
+}
+
+inline bool FromValue(const base::Value& in_value,
+ const base::DictionaryValue** out_value) {
+ return in_value.GetAsDictionary(out_value);
+}
+
+BRILLO_EXPORT bool FromValue(const base::Value& in_value,
+ std::unique_ptr<base::ListValue>* out_value);
+BRILLO_EXPORT bool FromValue(const base::Value& in_value,
+ std::unique_ptr<base::DictionaryValue>* out_value);
+
+template <typename T, typename Pred, typename Alloc>
+bool FromValue(const base::Value& in_value,
+ std::map<std::string, T, Pred, Alloc>* out_value);
+
+template <typename T, typename Alloc>
+bool FromValue(const base::Value& in_value, std::vector<T, Alloc>* out_value) {
+ const base::ListValue* list = nullptr;
+ if (!in_value.GetAsList(&list))
+ return false;
+ out_value->clear();
+ out_value->reserve(list->GetSize());
+ for (const auto& item : *list) {
+ T value{};
+ if (!FromValue(*item, &value))
+ return false;
+ out_value->push_back(std::move(value));
+ }
+ return true;
+}
+
+template <typename T, typename Pred, typename Alloc>
+bool FromValue(const base::Value& in_value,
+ std::map<std::string, T, Pred, Alloc>* out_value) {
+ const base::DictionaryValue* dict = nullptr;
+ if (!in_value.GetAsDictionary(&dict))
+ return false;
+ out_value->clear();
+ for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
+ if (!FromValue(it.value(), &(*out_value)[it.key()]))
+ return false;
+ }
+ return true;
+}
+
+template <typename T>
+T FromValue(const base::Value& value) {
+ T out_value{};
+ CHECK(FromValue(value, &out_value));
+ return out_value;
+}
+
+BRILLO_EXPORT std::unique_ptr<base::Value> ToValue(int value);
+BRILLO_EXPORT std::unique_ptr<base::Value> ToValue(bool value);
+BRILLO_EXPORT std::unique_ptr<base::Value> ToValue(double value);
+BRILLO_EXPORT std::unique_ptr<base::Value> ToValue(const std::string& value);
+// Implicit conversion of char* to 'bool' has precedence over the user-defined
+// std::string conversion. Override this behavior explicitly.
+BRILLO_EXPORT std::unique_ptr<base::Value> ToValue(const char* value);
+
+template <typename T, typename Pred, typename Alloc>
+std::unique_ptr<base::Value> ToValue(
+ const std::map<std::string, T, Pred, Alloc>& dictionary);
+
+template <typename T, typename Alloc>
+std::unique_ptr<base::Value> ToValue(const std::vector<T, Alloc>& list) {
+ std::unique_ptr<base::ListValue> result{new base::ListValue};
+ for (const auto& value : list)
+ result->Append(ToValue(value));
+ return std::move(result);
+}
+
+template <typename T, typename Pred, typename Alloc>
+std::unique_ptr<base::Value> ToValue(
+ const std::map<std::string, T, Pred, Alloc>& dictionary) {
+ std::unique_ptr<base::DictionaryValue> result{new base::DictionaryValue};
+ for (const auto& pair : dictionary)
+ result->Set(pair.first, ToValue(pair.second));
+ return std::move(result);
+}
+
+} // namespace brillo
+
+#endif // BRILLO_VALUE_CONVERSION_H_
diff --git a/libbrillo/brillo/value_conversion_unittest.cc b/libbrillo/brillo/value_conversion_unittest.cc
new file mode 100644
index 0000000..aa1be2a
--- /dev/null
+++ b/libbrillo/brillo/value_conversion_unittest.cc
@@ -0,0 +1,265 @@
+// Copyright 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <brillo/value_conversion.h>
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <gtest/gtest.h>
+
+namespace brillo {
+
+namespace {
+
+std::unique_ptr<base::Value> ParseValue(std::string json) {
+ std::replace(json.begin(), json.end(), '\'', '"');
+ std::string message;
+ auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC,
+ nullptr, &message);
+ CHECK(value) << "Failed to load JSON: " << message << ", " << json;
+ return value;
+}
+
+inline bool IsEqualValue(const base::Value& val1, const base::Value& val2) {
+ return val1.Equals(&val2);
+}
+
+#define EXPECT_JSON_EQ(expected, actual) \
+ EXPECT_PRED2(IsEqualValue, *ParseValue(expected), actual)
+
+} // namespace
+
+TEST(ValueConversionTest, FromValueInt) {
+ int actual;
+ EXPECT_TRUE(FromValue(*ParseValue("123"), &actual));
+ EXPECT_EQ(123, actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("-123"), &actual));
+ EXPECT_EQ(-123, actual);
+
+ EXPECT_FALSE(FromValue(*ParseValue("true"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueBool) {
+ bool actual;
+ EXPECT_TRUE(FromValue(*ParseValue("false"), &actual));
+ EXPECT_FALSE(actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("true"), &actual));
+ EXPECT_TRUE(actual);
+
+ EXPECT_FALSE(FromValue(*ParseValue("0"), &actual));
+ EXPECT_FALSE(FromValue(*ParseValue("1"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueDouble) {
+ double actual;
+ EXPECT_TRUE(FromValue(*ParseValue("12.5"), &actual));
+ EXPECT_DOUBLE_EQ(12.5, actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("-0.1"), &actual));
+ EXPECT_DOUBLE_EQ(-0.1, actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("17"), &actual));
+ EXPECT_DOUBLE_EQ(17.0, actual);
+
+ EXPECT_FALSE(FromValue(*ParseValue("'1.0'"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueString) {
+ std::string actual;
+ EXPECT_TRUE(FromValue(*ParseValue("'foo'"), &actual));
+ EXPECT_EQ("foo", actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("'bar'"), &actual));
+ EXPECT_EQ("bar", actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("''"), &actual));
+ EXPECT_TRUE(actual.empty());
+
+ EXPECT_FALSE(FromValue(*ParseValue("1"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueListValue) {
+ const base::ListValue* list = nullptr;
+ auto in_value = ParseValue("[1, 2, 'foo']");
+ EXPECT_TRUE(FromValue(*in_value, &list));
+ EXPECT_JSON_EQ("[1, 2, 'foo']", *list);
+}
+
+TEST(ValueConversionTest, FromValueDictValue) {
+ const base::DictionaryValue* dict = nullptr;
+ auto in_value = ParseValue("{'foo':'bar','baz': 1}");
+ EXPECT_TRUE(FromValue(*in_value, &dict));
+ EXPECT_JSON_EQ("{'foo':'bar','baz': 1}", *dict);
+}
+
+TEST(ValueConversionTest, FromValueListValueUniquePtr) {
+ std::unique_ptr<base::ListValue> list;
+ EXPECT_TRUE(FromValue(*ParseValue("[1, 2, 'bar']"), &list));
+ EXPECT_JSON_EQ("[1, 2, 'bar']", *list);
+}
+
+TEST(ValueConversionTest, FromValueDictValueUniquePtr) {
+ std::unique_ptr<base::DictionaryValue> dict;
+ EXPECT_TRUE(FromValue(*ParseValue("{'foo':'bar','baz': 1}"), &dict));
+ EXPECT_JSON_EQ("{'foo':'bar','baz': 1}", *dict);
+}
+
+TEST(ValueConversionTest, FromValueVectorOfInt) {
+ std::vector<int> actual;
+ EXPECT_TRUE(FromValue(*ParseValue("[1, 2, 3, 4]"), &actual));
+ EXPECT_EQ((std::vector<int>{1, 2, 3, 4}), actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("[]"), &actual));
+ EXPECT_TRUE(actual.empty());
+
+ EXPECT_FALSE(FromValue(*ParseValue("[1, 2, 3, '4']"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueVectorOfBool) {
+ std::vector<bool> actual;
+ EXPECT_TRUE(FromValue(*ParseValue("[true, true, false]"), &actual));
+ EXPECT_EQ((std::vector<bool>{true, true, false}), actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("[]"), &actual));
+ EXPECT_TRUE(actual.empty());
+
+ EXPECT_FALSE(FromValue(*ParseValue("[true, 0]"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueVectorOfDouble) {
+ std::vector<double> actual;
+ EXPECT_TRUE(FromValue(*ParseValue("[1, 2.0, 6.5, -11.2]"), &actual));
+ EXPECT_EQ((std::vector<double>{1.0, 2.0, 6.5, -11.2}), actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("[]"), &actual));
+ EXPECT_TRUE(actual.empty());
+
+ EXPECT_FALSE(FromValue(*ParseValue("['s']"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueVectorOfString) {
+ std::vector<std::string> actual;
+ EXPECT_TRUE(FromValue(*ParseValue("['', 'foo', 'bar']"), &actual));
+ EXPECT_EQ((std::vector<std::string>{"", "foo", "bar"}), actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("[]"), &actual));
+ EXPECT_TRUE(actual.empty());
+
+ EXPECT_FALSE(FromValue(*ParseValue("[100]"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueVectorOfVectors) {
+ std::vector<std::vector<int>> actual;
+ EXPECT_TRUE(FromValue(*ParseValue("[[1,2], [], [3]]"), &actual));
+ EXPECT_EQ((std::vector<std::vector<int>>{{1,2}, {}, {3}}), actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("[]"), &actual));
+ EXPECT_TRUE(actual.empty());
+
+ EXPECT_FALSE(FromValue(*ParseValue("[100]"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueMap) {
+ std::map<std::string, int> actual;
+ EXPECT_TRUE(FromValue(*ParseValue("{'foo':1, 'bar':2, 'baz':3}"), &actual));
+ EXPECT_EQ((std::map<std::string, int>{{"foo", 1}, {"bar", 2}, {"baz", 3}}),
+ actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("{}"), &actual));
+ EXPECT_TRUE(actual.empty());
+
+ EXPECT_FALSE(FromValue(*ParseValue("{'foo':1, 'bar':'2'}"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueMapOfVectors) {
+ std::map<std::string, std::vector<int>> actual;
+ EXPECT_TRUE(FromValue(*ParseValue("{'foo':[1,2], 'bar':[]}"), &actual));
+ std::map<std::string, std::vector<int>> expected{
+ {"foo", {1, 2}}, {"bar", {}}};
+ EXPECT_EQ(expected, actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("{}"), &actual));
+ EXPECT_TRUE(actual.empty());
+
+ EXPECT_FALSE(FromValue(*ParseValue("{'foo':[1], 'bar':[2,'3']}"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueVectorOfMaps) {
+ std::vector<std::map<std::string, int>> actual;
+ EXPECT_TRUE(FromValue(*ParseValue("[{'foo':1,'bar':2},{'baz':3}]"), &actual));
+ std::vector<std::map<std::string, int>> expected{
+ {{"foo", 1}, {"bar", 2}}, {{"baz", 3}}};
+ EXPECT_EQ(expected, actual);
+
+ EXPECT_TRUE(FromValue(*ParseValue("[]"), &actual));
+ EXPECT_TRUE(actual.empty());
+
+ EXPECT_FALSE(FromValue(*ParseValue("[{'foo':1}, 'bar']"), &actual));
+}
+
+TEST(ValueConversionTest, FromValueVectorOfLists) {
+ std::vector<std::unique_ptr<base::ListValue>> actual;
+ EXPECT_TRUE(FromValue(*ParseValue("[['foo',1],['bar',2],[true]]"), &actual));
+ ASSERT_EQ(3, actual.size());
+ EXPECT_JSON_EQ("['foo', 1]", *actual[0]);
+ EXPECT_JSON_EQ("['bar', 2]", *actual[1]);
+ EXPECT_JSON_EQ("[true]", *actual[2]);
+}
+
+TEST(ValueConversionTest, FromValueVectorOfDicts) {
+ std::vector<std::unique_ptr<base::DictionaryValue>> actual;
+ EXPECT_TRUE(FromValue(*ParseValue("[{'foo': 1}, {'bar': 2}]"), &actual));
+ ASSERT_EQ(2, actual.size());
+ EXPECT_JSON_EQ("{'foo': 1}", *actual[0]);
+ EXPECT_JSON_EQ("{'bar': 2}", *actual[1]);
+}
+
+TEST(ValueConversionTest, ToValueScalar) {
+ EXPECT_JSON_EQ("1234", *ToValue(1234));
+ EXPECT_JSON_EQ("true", *ToValue(true));
+ EXPECT_JSON_EQ("false", *ToValue(false));
+ EXPECT_JSON_EQ("12.5", *ToValue(12.5));
+ EXPECT_JSON_EQ("'foobar'", *ToValue("foobar"));
+}
+
+TEST(ValueConversionTest, ToValueVector) {
+ EXPECT_JSON_EQ("[1, 2, 3]", *ToValue(std::vector<int>{1, 2, 3}));
+ EXPECT_JSON_EQ("[]", *ToValue(std::vector<int>{}));
+ EXPECT_JSON_EQ("[true, false]", *ToValue(std::vector<bool>{true, false}));
+ EXPECT_JSON_EQ("['foo', 'bar']",
+ *ToValue(std::vector<std::string>{"foo", "bar"}));
+ EXPECT_JSON_EQ("[[1,2],[3]]",
+ *ToValue(std::vector<std::vector<int>>{{1, 2}, {3}}));
+}
+
+TEST(ValueConversionTest, ToValueMap) {
+ EXPECT_JSON_EQ("{'foo': 1, 'bar': 2}",
+ *ToValue(std::map<std::string, int>{{"foo", 1}, {"bar", 2}}));
+ EXPECT_JSON_EQ("{}", *ToValue(std::map<std::string, int>{}));
+ EXPECT_JSON_EQ("{'foo': true}",
+ *ToValue(std::map<std::string, bool>{{"foo", true}}));
+ EXPECT_JSON_EQ("{'foo': 1.1, 'bar': 2.2}",
+ *ToValue(std::map<std::string, double>{{"foo", 1.1},
+ {"bar", 2.2}}));
+}
+
+} // namespace brillo
diff --git a/libbrillo/brillo/variant_dictionary.h b/libbrillo/brillo/variant_dictionary.h
new file mode 100644
index 0000000..0b71e9c
--- /dev/null
+++ b/libbrillo/brillo/variant_dictionary.h
@@ -0,0 +1,33 @@
+// 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 LIBBRILLO_BRILLO_VARIANT_DICTIONARY_H_
+#define LIBBRILLO_BRILLO_VARIANT_DICTIONARY_H_
+
+#include <map>
+#include <string>
+
+#include <brillo/any.h>
+#include <brillo/brillo_export.h>
+
+namespace brillo {
+
+using VariantDictionary = std::map<std::string, brillo::Any>;
+
+// GetVariantValueOrDefault tries to retrieve the named key from the dictionary
+// and convert it to the type T. If the value does not exist, or the type
+// conversion fails, the default value of type T is returned.
+template<typename T>
+const T GetVariantValueOrDefault(const VariantDictionary& dictionary,
+ const std::string& key) {
+ VariantDictionary::const_iterator it = dictionary.find(key);
+ if (it == dictionary.end()) {
+ return T();
+ }
+ return it->second.TryGet<T>();
+}
+
+} // namespace brillo
+
+#endif // LIBBRILLO_BRILLO_VARIANT_DICTIONARY_H_
diff --git a/libbrillo/brillo/variant_dictionary_unittest.cc b/libbrillo/brillo/variant_dictionary_unittest.cc
new file mode 100644
index 0000000..73ead2c
--- /dev/null
+++ b/libbrillo/brillo/variant_dictionary_unittest.cc
@@ -0,0 +1,26 @@
+// Copyright 2015 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 <brillo/any.h>
+#include <brillo/variant_dictionary.h>
+#include <gtest/gtest.h>
+
+using brillo::VariantDictionary;
+using brillo::GetVariantValueOrDefault;
+
+TEST(VariantDictionary, GetVariantValueOrDefault) {
+ VariantDictionary dictionary;
+ dictionary.emplace("a", 1);
+ dictionary.emplace("b", "string");
+
+ // Test values that are present in the VariantDictionary.
+ EXPECT_EQ(1, GetVariantValueOrDefault<int>(dictionary, "a"));
+ EXPECT_EQ("string", GetVariantValueOrDefault<const char*>(dictionary, "b"));
+
+ // Test that missing keys result in defaults.
+ EXPECT_EQ("", GetVariantValueOrDefault<std::string>(dictionary, "missing"));
+ EXPECT_EQ(0, GetVariantValueOrDefault<int>(dictionary, "missing"));
+}
diff --git a/libbrillo/gen_coverage_html.sh b/libbrillo/gen_coverage_html.sh
new file mode 100755
index 0000000..9faf1a9
--- /dev/null
+++ b/libbrillo/gen_coverage_html.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+# Copyright (c) 2009 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.
+
+set -ex
+
+scons debug=1 -c
+scons debug=1
+lcov -d . --zerocounters
+./unittests
+lcov --base-directory . --directory . --capture --output-file app.info
+
+# some versions of genhtml support the --no-function-coverage argument,
+# which we want. The problem w/ function coverage is that every template
+# instantiation of a method counts as a different method, so if we
+# instantiate a method twice, once for testing and once for prod, the method
+# is tested, but it shows only 50% function coverage b/c it thinks we didn't
+# test the prod version.
+
+genhtml --no-function-coverage -o html ./app.info || genhtml -o html ./app.info
diff --git a/libbrillo/libbrillo-395517.gypi b/libbrillo/libbrillo-395517.gypi
new file mode 100644
index 0000000..a846c70
--- /dev/null
+++ b/libbrillo/libbrillo-395517.gypi
@@ -0,0 +1,8 @@
+{
+ 'variables': {
+ 'libbase_ver': 395517,
+ },
+ 'includes': [
+ '../libbrillo/libbrillo.gypi',
+ ],
+}
diff --git a/libbrillo/libbrillo-glib.pc.in b/libbrillo/libbrillo-glib.pc.in
new file mode 100644
index 0000000..cfd9fc8
--- /dev/null
+++ b/libbrillo/libbrillo-glib.pc.in
@@ -0,0 +1,8 @@
+bslot=@BSLOT@
+
+Name: libbrillo-glib
+Description: brillo glib wrapper library
+Version: ${bslot}
+Requires.private: @PRIVATE_PC@
+Libs: -lbrillo-glib-${bslot}
+
diff --git a/libbrillo/libbrillo-test.pc.in b/libbrillo/libbrillo-test.pc.in
new file mode 100644
index 0000000..4fece7c
--- /dev/null
+++ b/libbrillo/libbrillo-test.pc.in
@@ -0,0 +1,8 @@
+bslot=@BSLOT@
+
+Name: libbrillo-test
+Description: brillo test library
+Version: ${bslot}
+# Because libbrillo-test is static, we have to depend directly on everything.
+Requires: @PRIVATE_PC@
+Libs: -lbrillo-test-${bslot}
diff --git a/libbrillo/libbrillo.gyp b/libbrillo/libbrillo.gyp
new file mode 100644
index 0000000..487e99d
--- /dev/null
+++ b/libbrillo/libbrillo.gyp
@@ -0,0 +1,6 @@
+{
+ 'includes': [
+ 'libbrillo-395517.gypi',
+ 'libpolicy.gypi',
+ ]
+}
diff --git a/libbrillo/libbrillo.gypi b/libbrillo/libbrillo.gypi
new file mode 100644
index 0000000..0a641ba
--- /dev/null
+++ b/libbrillo/libbrillo.gypi
@@ -0,0 +1,382 @@
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'libchrome-<(libbase_ver)'
+ ],
+ 'USE_dbus%': '1',
+ },
+ 'include_dirs': [
+ '../libbrillo',
+ ],
+ 'defines': [
+ 'USE_DBUS=<(USE_dbus)',
+ 'USE_RTTI_FOR_TYPE_TAGS',
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'libbrillo-<(libbase_ver)',
+ 'type': 'none',
+ 'dependencies': [
+ 'libbrillo-core-<(libbase_ver)',
+ 'libbrillo-cryptohome-<(libbase_ver)',
+ 'libbrillo-http-<(libbase_ver)',
+ 'libbrillo-minijail-<(libbase_ver)',
+ 'libbrillo-streams-<(libbase_ver)',
+ 'libpolicy-<(libbase_ver)',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '../libbrillo',
+ ],
+ },
+ 'includes': ['../common-mk/deps.gypi'],
+ },
+ {
+ 'target_name': 'libbrillo-core-<(libbase_ver)',
+ 'type': 'shared_library',
+ 'variables': {
+ 'exported_deps': [
+ 'dbus-1',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'libraries': ['-lmodp_b64'],
+ #TODO(deymo): Split DBus code from libbrillo-core the same way is split in
+ # the Android.mk, based on the <(USE_dbus) variable.
+ 'sources': [
+ 'brillo/any.cc',
+ 'brillo/asynchronous_signal_handler.cc',
+ 'brillo/backoff_entry.cc',
+ 'brillo/daemons/dbus_daemon.cc',
+ 'brillo/daemons/daemon.cc',
+ 'brillo/data_encoding.cc',
+ 'brillo/dbus/async_event_sequencer.cc',
+ 'brillo/dbus/data_serialization.cc',
+ 'brillo/dbus/dbus_connection.cc',
+ 'brillo/dbus/dbus_method_invoker.cc',
+ 'brillo/dbus/dbus_method_response.cc',
+ 'brillo/dbus/dbus_object.cc',
+ 'brillo/dbus/dbus_service_watcher.cc',
+ 'brillo/dbus/dbus_signal.cc',
+ 'brillo/dbus/exported_object_manager.cc',
+ 'brillo/dbus/exported_property_set.cc',
+ 'brillo/dbus/utils.cc',
+ 'brillo/errors/error.cc',
+ 'brillo/errors/error_codes.cc',
+ 'brillo/file_utils.cc',
+ 'brillo/flag_helper.cc',
+ 'brillo/key_value_store.cc',
+ 'brillo/message_loops/base_message_loop.cc',
+ 'brillo/message_loops/message_loop.cc',
+ 'brillo/message_loops/message_loop_utils.cc',
+ 'brillo/mime_utils.cc',
+ 'brillo/osrelease_reader.cc',
+ 'brillo/process.cc',
+ 'brillo/process_reaper.cc',
+ 'brillo/process_information.cc',
+ 'brillo/secure_blob.cc',
+ 'brillo/strings/string_utils.cc',
+ 'brillo/syslog_logging.cc',
+ 'brillo/type_name_undecorate.cc',
+ 'brillo/url_utils.cc',
+ 'brillo/userdb_utils.cc',
+ 'brillo/value_conversion.cc',
+ ],
+ },
+ {
+ 'target_name': 'libbrillo-http-<(libbase_ver)',
+ 'type': 'shared_library',
+ 'dependencies': [
+ 'libbrillo-core-<(libbase_ver)',
+ 'libbrillo-streams-<(libbase_ver)',
+ ],
+ 'variables': {
+ 'exported_deps': [
+ 'libcurl',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'brillo/http/curl_api.cc',
+ 'brillo/http/http_connection_curl.cc',
+ 'brillo/http/http_form_data.cc',
+ 'brillo/http/http_request.cc',
+ 'brillo/http/http_transport.cc',
+ 'brillo/http/http_transport_curl.cc',
+ 'brillo/http/http_utils.cc',
+ ],
+ },
+ {
+ 'target_name': 'libbrillo-streams-<(libbase_ver)',
+ 'type': 'shared_library',
+ 'dependencies': [
+ 'libbrillo-core-<(libbase_ver)',
+ ],
+ 'variables': {
+ 'exported_deps': [
+ 'openssl',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'brillo/streams/file_stream.cc',
+ 'brillo/streams/input_stream_set.cc',
+ 'brillo/streams/memory_containers.cc',
+ 'brillo/streams/memory_stream.cc',
+ 'brillo/streams/openssl_stream_bio.cc',
+ 'brillo/streams/stream.cc',
+ 'brillo/streams/stream_errors.cc',
+ 'brillo/streams/stream_utils.cc',
+ 'brillo/streams/tls_stream.cc',
+ ],
+ },
+ {
+ 'target_name': 'libbrillo-test-<(libbase_ver)',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'dependencies': [
+ 'libbrillo-http-<(libbase_ver)',
+ ],
+ 'sources': [
+ 'brillo/http/http_connection_fake.cc',
+ 'brillo/http/http_transport_fake.cc',
+ 'brillo/message_loops/fake_message_loop.cc',
+ 'brillo/streams/fake_stream.cc',
+ 'brillo/unittest_utils.cc',
+ ],
+ 'includes': ['../common-mk/deps.gypi'],
+ },
+ {
+ 'target_name': 'libbrillo-cryptohome-<(libbase_ver)',
+ 'type': 'shared_library',
+ 'variables': {
+ 'exported_deps': [
+ 'openssl',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'brillo/cryptohome.cc',
+ ],
+ },
+ {
+ 'target_name': 'libbrillo-minijail-<(libbase_ver)',
+ 'type': 'shared_library',
+ 'variables': {
+ 'exported_deps': [
+ 'libminijail',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'cflags': [
+ '-fvisibility=default',
+ ],
+ 'sources': [
+ 'brillo/minijail/minijail.cc',
+ ],
+ },
+ {
+ 'target_name': 'libpolicy-<(libbase_ver)',
+ 'type': 'shared_library',
+ 'dependencies': [
+ 'libpolicy-includes',
+ '../common-mk/external_dependencies.gyp:policy-protos',
+ ],
+ 'variables': {
+ 'exported_deps': [
+ 'openssl',
+ 'protobuf-lite',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'ldflags': [
+ '-Wl,--version-script,<(platform2_root)/libbrillo/libpolicy.ver',
+ ],
+ 'sources': [
+ 'policy/device_policy.cc',
+ 'policy/device_policy_impl.cc',
+ 'policy/libpolicy.cc',
+ ],
+ },
+ {
+ 'target_name': 'libbrillo-glib-<(libbase_ver)',
+ 'type': 'shared_library',
+ 'dependencies': [
+ 'libbrillo-<(libbase_ver)',
+ ],
+ 'variables': {
+ 'exported_deps': [
+ 'dbus-1',
+ 'dbus-glib-1',
+ 'glib-2.0',
+ 'gobject-2.0',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'cflags': [
+ # glib uses the deprecated "register" attribute in some header files.
+ '-Wno-deprecated-register',
+ ],
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'brillo/glib/abstract_dbus_service.cc',
+ 'brillo/glib/dbus.cc',
+ 'brillo/message_loops/glib_message_loop.cc',
+ ],
+ 'includes': ['../common-mk/deps.gypi'],
+ },
+ ],
+ 'conditions': [
+ ['USE_test == 1', {
+ 'targets': [
+ {
+ 'target_name': 'libbrillo-<(libbase_ver)_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ 'libbrillo-<(libbase_ver)',
+ 'libbrillo-test-<(libbase_ver)',
+ 'libbrillo-glib-<(libbase_ver)',
+ ],
+ 'variables': {
+ 'deps': [
+ 'libchrome-test-<(libbase_ver)',
+ ],
+ 'proto_in_dir': 'brillo/dbus',
+ 'proto_out_dir': 'include/brillo/dbus',
+ },
+ 'includes': [
+ '../common-mk/common_test.gypi',
+ '../common-mk/protoc.gypi',
+ ],
+ 'cflags': [
+ '-Wno-format-zero-length',
+ ],
+ 'conditions': [
+ ['debug == 1', {
+ 'cflags': [
+ '-fprofile-arcs',
+ '-ftest-coverage',
+ '-fno-inline',
+ ],
+ 'libraries': [
+ '-lgcov',
+ ],
+ }],
+ ],
+ 'sources': [
+ 'brillo/any_unittest.cc',
+ 'brillo/any_internal_impl_unittest.cc',
+ 'brillo/asynchronous_signal_handler_unittest.cc',
+ 'brillo/backoff_entry_unittest.cc',
+ 'brillo/data_encoding_unittest.cc',
+ 'brillo/dbus/async_event_sequencer_unittest.cc',
+ 'brillo/dbus/data_serialization_unittest.cc',
+ 'brillo/dbus/dbus_method_invoker_unittest.cc',
+ 'brillo/dbus/dbus_object_unittest.cc',
+ 'brillo/dbus/dbus_param_reader_unittest.cc',
+ 'brillo/dbus/dbus_param_writer_unittest.cc',
+ 'brillo/dbus/dbus_signal_handler_unittest.cc',
+ 'brillo/dbus/exported_object_manager_unittest.cc',
+ 'brillo/dbus/exported_property_set_unittest.cc',
+ 'brillo/errors/error_codes_unittest.cc',
+ 'brillo/errors/error_unittest.cc',
+ 'brillo/file_utils_unittest.cc',
+ 'brillo/flag_helper_unittest.cc',
+ 'brillo/glib/object_unittest.cc',
+ 'brillo/http/http_connection_curl_unittest.cc',
+ 'brillo/http/http_form_data_unittest.cc',
+ 'brillo/http/http_request_unittest.cc',
+ 'brillo/http/http_transport_curl_unittest.cc',
+ 'brillo/http/http_utils_unittest.cc',
+ 'brillo/key_value_store_unittest.cc',
+ 'brillo/map_utils_unittest.cc',
+ 'brillo/message_loops/base_message_loop_unittest.cc',
+ 'brillo/message_loops/fake_message_loop_unittest.cc',
+ 'brillo/message_loops/glib_message_loop_unittest.cc',
+ 'brillo/message_loops/message_loop_unittest.cc',
+ 'brillo/mime_utils_unittest.cc',
+ 'brillo/osrelease_reader_unittest.cc',
+ 'brillo/process_reaper_unittest.cc',
+ 'brillo/process_unittest.cc',
+ 'brillo/secure_blob_unittest.cc',
+ 'brillo/streams/fake_stream_unittest.cc',
+ 'brillo/streams/file_stream_unittest.cc',
+ 'brillo/streams/input_stream_set_unittest.cc',
+ 'brillo/streams/memory_containers_unittest.cc',
+ 'brillo/streams/memory_stream_unittest.cc',
+ 'brillo/streams/openssl_stream_bio_unittests.cc',
+ 'brillo/streams/stream_unittest.cc',
+ 'brillo/streams/stream_utils_unittest.cc',
+ 'brillo/strings/string_utils_unittest.cc',
+ 'brillo/type_name_undecorate_unittest.cc',
+ 'brillo/unittest_utils.cc',
+ 'brillo/url_utils_unittest.cc',
+ 'brillo/variant_dictionary_unittest.cc',
+ 'brillo/value_conversion_unittest.cc',
+ 'testrunner.cc',
+ '<(proto_in_dir)/test.proto',
+ ]
+ },
+ {
+ 'target_name': 'libpolicy-<(libbase_ver)_unittests',
+ 'type': 'executable',
+ 'dependencies': ['libpolicy-<(libbase_ver)'],
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'policy/tests/libpolicy_unittest.cc',
+ ]
+ },
+ ],
+ }],
+ ],
+}
diff --git a/libbrillo/libbrillo.pc.in b/libbrillo/libbrillo.pc.in
new file mode 100644
index 0000000..a3a9e07
--- /dev/null
+++ b/libbrillo/libbrillo.pc.in
@@ -0,0 +1,8 @@
+bslot=@BSLOT@
+
+Name: libbrillo
+Description: brillo base library
+Version: ${bslot}
+Requires.private: @PRIVATE_PC@
+Cflags: -DUSE_RTTI_FOR_TYPE_TAGS
+Libs: -lbrillo-${bslot}
diff --git a/libbrillo/libpolicy.gypi b/libbrillo/libpolicy.gypi
new file mode 100644
index 0000000..25b251d
--- /dev/null
+++ b/libbrillo/libpolicy.gypi
@@ -0,0 +1,20 @@
+{
+ 'targets': [
+ {
+ 'target_name': 'libpolicy-includes',
+ 'type': 'none',
+ 'copies': [
+ {
+ 'destination': '<(SHARED_INTERMEDIATE_DIR)/include/policy',
+ 'files': [
+ 'policy/device_policy.h',
+ 'policy/device_policy_impl.h',
+ 'policy/libpolicy.h',
+ 'policy/mock_libpolicy.h',
+ 'policy/mock_device_policy.h',
+ ],
+ },
+ ],
+ },
+ ],
+}
diff --git a/libbrillo/libpolicy.ver b/libbrillo/libpolicy.ver
new file mode 100644
index 0000000..4acb71c
--- /dev/null
+++ b/libbrillo/libpolicy.ver
@@ -0,0 +1,19 @@
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ global:
+ extern "C++" {
+ enterprise_management::*;
+ typeinfo*for*enterprise_management::*;
+ vtable*for*enterprise_management*;
+
+ policy::*;
+ typeinfo*for*policy::*;
+ vtable*for*policy*;
+ _VTV*::__vtable_map;
+ };
+
+ local: *;
+};
diff --git a/libbrillo/platform2_preinstall.sh b/libbrillo/platform2_preinstall.sh
new file mode 100755
index 0000000..448a31a
--- /dev/null
+++ b/libbrillo/platform2_preinstall.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+# Copyright (c) 2013 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.
+
+set -e
+
+OUT=$1
+shift
+for v; do
+ # Extract all the libbrillo sublibs from 'dependencies' section of
+ # 'libbrillo-<(libbase_ver)' target in libbrillo.gypi and convert them
+ # into an array of "-lbrillo-<sublib>-<v>" flags.
+ sublibs=($(sed -n "
+ /'target_name': 'libbrillo-<(libbase_ver)'/,/target_name/ {
+ /dependencies/,/],/ {
+ /libbrillo/ {
+ s:[',]::g
+ s:<(libbase_ver):${v}:g
+ s:libbrillo:-lbrillo:
+ p
+ }
+ }
+ }" libbrillo.gypi))
+
+ echo "GROUP ( AS_NEEDED ( ${sublibs[@]} ) )" > "${OUT}"/lib/libbrillo-${v}.so
+
+ deps=$(<"${OUT}"/gen/libbrillo-${v}-deps.txt)
+ pc="${OUT}"/lib/libbrillo-${v}.pc
+
+ sed \
+ -e "s/@BSLOT@/${v}/g" \
+ -e "s/@PRIVATE_PC@/${deps}/g" \
+ "libbrillo.pc.in" > "${pc}"
+
+ deps_test=$(<"${OUT}"/gen/libbrillo-test-${v}-deps.txt)
+ deps_test+=" libbrillo-${v}"
+ sed \
+ -e "s/@BSLOT@/${v}/g" \
+ -e "s/@PRIVATE_PC@/${deps_test}/g" \
+ "libbrillo-test.pc.in" > "${OUT}/lib/libbrillo-test-${v}.pc"
+
+
+ deps_glib=$(<"${OUT}"/gen/libbrillo-glib-${v}-deps.txt)
+ pc_glib="${OUT}"/lib/libbrillo-glib-${v}.pc
+
+ sed \
+ -e "s/@BSLOT@/${v}/g" \
+ -e "s/@PRIVATE_PC@/${deps_glib}/g" \
+ "libbrillo-glib.pc.in" > "${pc_glib}"
+done
diff --git a/libbrillo/policy/WATCHLISTS b/libbrillo/policy/WATCHLISTS
new file mode 100644
index 0000000..c806539
--- /dev/null
+++ b/libbrillo/policy/WATCHLISTS
@@ -0,0 +1,14 @@
+# See http://dev.chromium.org/developers/contributing-code/watchlists for
+# a description of this file's format.
+# Please keep these keys in alphabetical order.
+
+{
+ 'WATCHLIST_DEFINITIONS': {
+ 'all': {
+ 'filepath': '.',
+ },
+ },
+ 'WATCHLISTS': {
+ 'all': ['pastarmovj@chromium.org']
+ },
+}
diff --git a/libbrillo/policy/device_policy.cc b/libbrillo/policy/device_policy.cc
new file mode 100644
index 0000000..e9445e7
--- /dev/null
+++ b/libbrillo/policy/device_policy.cc
@@ -0,0 +1,16 @@
+// 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 "policy/device_policy.h"
+
+namespace policy {
+
+DevicePolicy::DevicePolicy() {
+}
+
+DevicePolicy::~DevicePolicy() {
+}
+
+} // namespace policy
+
diff --git a/libbrillo/policy/device_policy.h b/libbrillo/policy/device_policy.h
new file mode 100644
index 0000000..840e7e5
--- /dev/null
+++ b/libbrillo/policy/device_policy.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_POLICY_DEVICE_POLICY_H_
+#define LIBBRILLO_POLICY_DEVICE_POLICY_H_
+
+#include <stdint.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#pragma GCC visibility push(default)
+
+namespace policy {
+
+// This class holds device settings that are to be enforced across all users.
+// It is also responsible for loading the policy blob from disk and verifying
+// the signature against the owner's key.
+//
+// This class defines the interface for querying device policy on ChromeOS.
+// The implementation is hidden in DevicePolicyImpl to prevent protobuf
+// definition from leaking into the libraries using this interface.
+class DevicePolicy {
+ public:
+ // Identifiers of a USB device or device family.
+ struct UsbDeviceId {
+ // USB Vendor Identifier (aka idVendor).
+ uint16_t vendor_id;
+
+ // USB Product Identifier (aka idProduct).
+ uint16_t product_id;
+ };
+
+ DevicePolicy();
+ virtual ~DevicePolicy();
+
+ // Load the signed policy off of disk into |policy_|.
+ // Returns true unless there is a policy on disk and loading it fails.
+ virtual bool LoadPolicy() = 0;
+
+ // Writes the value of the DevicePolicyRefreshRate policy in |rate|. Returns
+ // true on success.
+ virtual bool GetPolicyRefreshRate(int* rate) const = 0;
+
+ // Writes the value of the UserWhitelist policy in |user_whitelist|. Returns
+ // true on success.
+ virtual bool GetUserWhitelist(
+ std::vector<std::string>* user_whitelist) const = 0;
+
+ // Writes the value of the GuestModeEnabled policy in |guest_mode_enabled|.
+ // Returns true on success.
+ virtual bool GetGuestModeEnabled(bool* guest_mode_enabled) const = 0;
+
+ // Writes the value of the CameraEnabled policy in |camera_enabled|. Returns
+ // true on success.
+ virtual bool GetCameraEnabled(bool* camera_enabled) const = 0;
+
+ // Writes the value of the ShowUserNamesOnSignIn policy in |show_user_names|.
+ // Returns true on success.
+ virtual bool GetShowUserNames(bool* show_user_names) const = 0;
+
+ // Writes the value of the DataRoamingEnabled policy in |data_roaming_enabled|
+ // Returns true on success.
+ virtual bool GetDataRoamingEnabled(bool* data_roaming_enabled) const = 0;
+
+ // Writes the value of the AllowNewUsers policy in |allow_new_users|. Returns
+ // true on success.
+ virtual bool GetAllowNewUsers(bool* allow_new_users) const = 0;
+
+ // Writes the value of MetricEnabled policy in |metrics_enabled|. Returns true
+ // on success.
+ virtual bool GetMetricsEnabled(bool* metrics_enabled) const = 0;
+
+ // Writes the value of ReportVersionInfo policy in |report_version_info|.
+ // Returns true on success.
+ virtual bool GetReportVersionInfo(bool* report_version_info) const = 0;
+
+ // Writes the value of ReportActivityTimes policy in |report_activity_times|.
+ // Returns true on success.
+ virtual bool GetReportActivityTimes(bool* report_activity_times) const = 0;
+
+ // Writes the value of ReportBootMode policy in |report_boot_mode|. Returns
+ // true on success.
+ virtual bool GetReportBootMode(bool* report_boot_mode) const = 0;
+
+ // Writes the value of the EphemeralUsersEnabled policy in
+ // |ephemeral_users_enabled|. Returns true on success.
+ virtual bool GetEphemeralUsersEnabled(
+ bool* ephemeral_users_enabled) const = 0;
+
+ // Writes the value of the release channel policy in |release_channel|.
+ // Returns true on success.
+ virtual bool GetReleaseChannel(std::string* release_channel) const = 0;
+
+ // Writes the value of the release_channel_delegated policy in
+ // |release_channel_delegated|. Returns true on success.
+ virtual bool GetReleaseChannelDelegated(
+ bool* release_channel_delegated) const = 0;
+
+ // Writes the value of the update_disabled policy in |update_disabled|.
+ // Returns true on success.
+ virtual bool GetUpdateDisabled(bool* update_disabled) const = 0;
+
+ // Writes the value of the target_version_prefix policy in
+ // |target_version_prefix|. Returns true on success.
+ virtual bool GetTargetVersionPrefix(
+ std::string* target_version_prefix) const = 0;
+
+ // Writes the value of the scatter_factor_in_seconds policy in
+ // |scatter_factor_in_seconds|. Returns true on success.
+ virtual bool GetScatterFactorInSeconds(
+ int64_t* scatter_factor_in_seconds) const = 0;
+
+ // Writes the connection types on which updates are allowed to
+ // |connection_types|. The identifiers returned are intended to be consistent
+ // with what the connection manager users: ethernet, wifi, wimax, bluetooth,
+ // cellular.
+ virtual bool GetAllowedConnectionTypesForUpdate(
+ std::set<std::string>* connection_types) const = 0;
+
+ // Writes the value of the OpenNetworkConfiguration policy in
+ // |open_network_configuration|. Returns true on success.
+ virtual bool GetOpenNetworkConfiguration(
+ std::string* open_network_configuration) const = 0;
+
+ // Writes the name of the device owner in |owner|. For enterprise enrolled
+ // devices, this will be an empty string.
+ // Returns true on success.
+ virtual bool GetOwner(std::string* owner) const = 0;
+
+ // Write the value of http_downloads_enabled policy in
+ // |http_downloads_enabled|. Returns true on success.
+ virtual bool GetHttpDownloadsEnabled(bool* http_downloads_enabled) const = 0;
+
+ // Writes the value of au_p2p_enabled policy in
+ // |au_p2p_enabled|. Returns true on success.
+ virtual bool GetAuP2PEnabled(bool* au_p2p_enabled) const = 0;
+
+ // Writes the value of allow_kiosk_app_control_chrome_version policy in
+ // |allow_kiosk_app_control_chrome_version|. Returns true on success.
+ virtual bool GetAllowKioskAppControlChromeVersion(
+ bool* allow_kiosk_app_control_chrome_version) const = 0;
+
+ // Writes the value of the UsbDetachableWhitelist policy in |usb_whitelist|.
+ // Returns true on success.
+ virtual bool GetUsbDetachableWhitelist(
+ std::vector<UsbDeviceId>* usb_whitelist) const = 0;
+
+ private:
+ // Verifies that the policy files are owned by root and exist.
+ virtual bool VerifyPolicyFiles() = 0;
+
+ // Verifies that the policy signature is correct.
+ virtual bool VerifyPolicySignature() = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(DevicePolicy);
+};
+} // namespace policy
+
+#pragma GCC visibility pop
+
+#endif // LIBBRILLO_POLICY_DEVICE_POLICY_H_
diff --git a/libbrillo/policy/device_policy_impl.cc b/libbrillo/policy/device_policy_impl.cc
new file mode 100644
index 0000000..784cace
--- /dev/null
+++ b/libbrillo/policy/device_policy_impl.cc
@@ -0,0 +1,465 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "policy/device_policy_impl.h"
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+
+#include <set>
+#include <string>
+
+#include "bindings/chrome_device_policy.pb.h"
+#include "bindings/device_management_backend.pb.h"
+
+namespace policy {
+
+namespace {
+const char kPolicyPath[] = "/var/lib/whitelist/policy";
+const char kPublicKeyPath[] = "/var/lib/whitelist/owner.key";
+
+// Reads the public key used to sign the policy from |key_file| and stores it
+// in |public_key|. Returns true on success.
+bool ReadPublicKeyFromFile(const base::FilePath& key_file,
+ std::string* public_key) {
+ if (!base::PathExists(key_file))
+ return false;
+ public_key->clear();
+ if (!base::ReadFileToString(key_file, public_key) || public_key->empty()) {
+ LOG(ERROR) << "Could not read public key off disk";
+ return false;
+ }
+ return true;
+}
+
+// Verifies that the |signed_data| has correct |signature| with |public_key|.
+bool VerifySignature(const std::string& signed_data,
+ const std::string& signature,
+ const std::string& public_key) {
+ EVP_MD_CTX ctx;
+ EVP_MD_CTX_init(&ctx);
+
+ const EVP_MD* digest = EVP_sha1();
+
+ char* key = const_cast<char*>(public_key.data());
+ BIO* bio = BIO_new_mem_buf(key, public_key.length());
+ if (!bio) {
+ EVP_MD_CTX_cleanup(&ctx);
+ return false;
+ }
+
+ EVP_PKEY* public_key_ssl = d2i_PUBKEY_bio(bio, nullptr);
+ if (!public_key_ssl) {
+ BIO_free_all(bio);
+ EVP_MD_CTX_cleanup(&ctx);
+ return false;
+ }
+
+ const unsigned char* sig =
+ reinterpret_cast<const unsigned char*>(signature.data());
+ int rv = EVP_VerifyInit_ex(&ctx, digest, nullptr);
+ if (rv == 1) {
+ EVP_VerifyUpdate(&ctx, signed_data.data(), signed_data.length());
+ rv = EVP_VerifyFinal(&ctx,
+ sig, signature.length(),
+ public_key_ssl);
+ }
+
+ EVP_PKEY_free(public_key_ssl);
+ BIO_free_all(bio);
+ EVP_MD_CTX_cleanup(&ctx);
+
+ return rv == 1;
+}
+
+// Decodes the connection type enum from the device settings protobuf to string
+// representations. The strings must match the connection manager definitions.
+std::string DecodeConnectionType(int type) {
+ static const char* const kConnectionTypes[] = {
+ "ethernet",
+ "wifi",
+ "wimax",
+ "bluetooth",
+ "cellular",
+ };
+
+ if (type < 0 || type >= static_cast<int>(arraysize(kConnectionTypes)))
+ return std::string();
+
+ return kConnectionTypes[type];
+}
+
+} // namespace
+
+DevicePolicyImpl::DevicePolicyImpl()
+ : policy_path_(kPolicyPath),
+ keyfile_path_(kPublicKeyPath) {
+}
+
+DevicePolicyImpl::~DevicePolicyImpl() {
+}
+
+bool DevicePolicyImpl::LoadPolicy() {
+ if (!VerifyPolicyFiles()) {
+ return false;
+ }
+
+ std::string polstr;
+ if (!base::ReadFileToString(policy_path_, &polstr) || polstr.empty()) {
+ LOG(ERROR) << "Could not read policy off disk";
+ return false;
+ }
+ if (!policy_.ParseFromString(polstr) || !policy_.has_policy_data()) {
+ LOG(ERROR) << "Policy on disk could not be parsed!";
+ return false;
+ }
+ policy_data_.ParseFromString(policy_.policy_data());
+ if (!policy_data_.has_policy_value()) {
+ LOG(ERROR) << "Policy on disk could not be parsed!";
+ return false;
+ }
+
+ // Make sure the signature is still valid.
+ if (!VerifyPolicySignature()) {
+ LOG(ERROR) << "Policy signature verification failed!";
+ return false;
+ }
+
+ device_policy_.ParseFromString(policy_data_.policy_value());
+ return true;
+}
+
+bool DevicePolicyImpl::GetPolicyRefreshRate(int* rate) const {
+ if (!device_policy_.has_device_policy_refresh_rate())
+ return false;
+ *rate = static_cast<int>(
+ device_policy_.device_policy_refresh_rate().device_policy_refresh_rate());
+ return true;
+}
+
+bool DevicePolicyImpl::GetUserWhitelist(
+ std::vector<std::string>* user_whitelist) const {
+ if (!device_policy_.has_user_whitelist())
+ return false;
+ const enterprise_management::UserWhitelistProto& proto =
+ device_policy_.user_whitelist();
+ user_whitelist->clear();
+ for (int i = 0; i < proto.user_whitelist_size(); i++)
+ user_whitelist->push_back(proto.user_whitelist(i));
+ return true;
+}
+
+bool DevicePolicyImpl::GetGuestModeEnabled(bool* guest_mode_enabled) const {
+ if (!device_policy_.has_guest_mode_enabled())
+ return false;
+ *guest_mode_enabled =
+ device_policy_.guest_mode_enabled().guest_mode_enabled();
+ return true;
+}
+
+bool DevicePolicyImpl::GetCameraEnabled(bool* camera_enabled) const {
+ if (!device_policy_.has_camera_enabled())
+ return false;
+ *camera_enabled = device_policy_.camera_enabled().camera_enabled();
+ return true;
+}
+
+bool DevicePolicyImpl::GetShowUserNames(bool* show_user_names) const {
+ if (!device_policy_.has_show_user_names())
+ return false;
+ *show_user_names = device_policy_.show_user_names().show_user_names();
+ return true;
+}
+
+bool DevicePolicyImpl::GetDataRoamingEnabled(bool* data_roaming_enabled) const {
+ if (!device_policy_.has_data_roaming_enabled())
+ return false;
+ *data_roaming_enabled =
+ device_policy_.data_roaming_enabled().data_roaming_enabled();
+ return true;
+}
+
+bool DevicePolicyImpl::GetAllowNewUsers(bool* allow_new_users) const {
+ if (!device_policy_.has_allow_new_users())
+ return false;
+ *allow_new_users = device_policy_.allow_new_users().allow_new_users();
+ return true;
+}
+
+bool DevicePolicyImpl::GetMetricsEnabled(bool* metrics_enabled) const {
+ if (!device_policy_.has_metrics_enabled())
+ return false;
+ *metrics_enabled = device_policy_.metrics_enabled().metrics_enabled();
+ return true;
+}
+
+bool DevicePolicyImpl::GetReportVersionInfo(bool* report_version_info) const {
+ if (!device_policy_.has_device_reporting())
+ return false;
+
+ const enterprise_management::DeviceReportingProto& proto =
+ device_policy_.device_reporting();
+ if (!proto.has_report_version_info())
+ return false;
+
+ *report_version_info = proto.report_version_info();
+ return true;
+}
+
+bool DevicePolicyImpl::GetReportActivityTimes(
+ bool* report_activity_times) const {
+ if (!device_policy_.has_device_reporting())
+ return false;
+
+ const enterprise_management::DeviceReportingProto& proto =
+ device_policy_.device_reporting();
+ if (!proto.has_report_activity_times())
+ return false;
+
+ *report_activity_times = proto.report_activity_times();
+ return true;
+}
+
+bool DevicePolicyImpl::GetReportBootMode(bool* report_boot_mode) const {
+ if (!device_policy_.has_device_reporting())
+ return false;
+
+ const enterprise_management::DeviceReportingProto& proto =
+ device_policy_.device_reporting();
+ if (!proto.has_report_boot_mode())
+ return false;
+
+ *report_boot_mode = proto.report_boot_mode();
+ return true;
+}
+
+bool DevicePolicyImpl::GetEphemeralUsersEnabled(
+ bool* ephemeral_users_enabled) const {
+ if (!device_policy_.has_ephemeral_users_enabled())
+ return false;
+ *ephemeral_users_enabled =
+ device_policy_.ephemeral_users_enabled().ephemeral_users_enabled();
+ return true;
+}
+
+bool DevicePolicyImpl::GetReleaseChannel(
+ std::string* release_channel) const {
+ if (!device_policy_.has_release_channel())
+ return false;
+
+ const enterprise_management::ReleaseChannelProto& proto =
+ device_policy_.release_channel();
+ if (!proto.has_release_channel())
+ return false;
+
+ *release_channel = proto.release_channel();
+ return true;
+}
+
+bool DevicePolicyImpl::GetReleaseChannelDelegated(
+ bool* release_channel_delegated) const {
+ if (!device_policy_.has_release_channel())
+ return false;
+
+ const enterprise_management::ReleaseChannelProto& proto =
+ device_policy_.release_channel();
+ if (!proto.has_release_channel_delegated())
+ return false;
+
+ *release_channel_delegated = proto.release_channel_delegated();
+ return true;
+}
+
+bool DevicePolicyImpl::GetUpdateDisabled(
+ bool* update_disabled) const {
+ if (!device_policy_.has_auto_update_settings())
+ return false;
+
+ const enterprise_management::AutoUpdateSettingsProto& proto =
+ device_policy_.auto_update_settings();
+ if (!proto.has_update_disabled())
+ return false;
+
+ *update_disabled = proto.update_disabled();
+ return true;
+}
+
+bool DevicePolicyImpl::GetTargetVersionPrefix(
+ std::string* target_version_prefix) const {
+ if (!device_policy_.has_auto_update_settings())
+ return false;
+
+ const enterprise_management::AutoUpdateSettingsProto& proto =
+ device_policy_.auto_update_settings();
+ if (!proto.has_target_version_prefix())
+ return false;
+
+ *target_version_prefix = proto.target_version_prefix();
+ return true;
+}
+
+bool DevicePolicyImpl::GetScatterFactorInSeconds(
+ int64_t* scatter_factor_in_seconds) const {
+ if (!device_policy_.has_auto_update_settings())
+ return false;
+
+ const enterprise_management::AutoUpdateSettingsProto& proto =
+ device_policy_.auto_update_settings();
+ if (!proto.has_scatter_factor_in_seconds())
+ return false;
+
+ *scatter_factor_in_seconds = proto.scatter_factor_in_seconds();
+ return true;
+}
+
+bool DevicePolicyImpl::GetAllowedConnectionTypesForUpdate(
+ std::set<std::string>* connection_types) const {
+ if (!device_policy_.has_auto_update_settings())
+ return false;
+
+ const enterprise_management::AutoUpdateSettingsProto& proto =
+ device_policy_.auto_update_settings();
+ if (proto.allowed_connection_types_size() <= 0)
+ return false;
+
+ for (int i = 0; i < proto.allowed_connection_types_size(); i++) {
+ std::string type = DecodeConnectionType(proto.allowed_connection_types(i));
+ if (!type.empty())
+ connection_types->insert(type);
+ }
+ return true;
+}
+
+bool DevicePolicyImpl::GetOpenNetworkConfiguration(
+ std::string* open_network_configuration) const {
+ if (!device_policy_.has_open_network_configuration())
+ return false;
+
+ const enterprise_management::DeviceOpenNetworkConfigurationProto& proto =
+ device_policy_.open_network_configuration();
+ if (!proto.has_open_network_configuration())
+ return false;
+
+ *open_network_configuration = proto.open_network_configuration();
+ return true;
+}
+
+bool DevicePolicyImpl::GetOwner(std::string* owner) const {
+ // The device is enterprise enrolled iff a request token exists.
+ if (policy_data_.has_request_token()) {
+ *owner = "";
+ return true;
+ }
+ if (!policy_data_.has_username())
+ return false;
+ *owner = policy_data_.username();
+ return true;
+}
+
+bool DevicePolicyImpl::GetHttpDownloadsEnabled(
+ bool* http_downloads_enabled) const {
+ if (!device_policy_.has_auto_update_settings())
+ return false;
+
+ const enterprise_management::AutoUpdateSettingsProto& proto =
+ device_policy_.auto_update_settings();
+
+ if (!proto.has_http_downloads_enabled())
+ return false;
+
+ *http_downloads_enabled = proto.http_downloads_enabled();
+ return true;
+}
+
+bool DevicePolicyImpl::GetAuP2PEnabled(bool* au_p2p_enabled) const {
+ if (!device_policy_.has_auto_update_settings())
+ return false;
+
+ const enterprise_management::AutoUpdateSettingsProto& proto =
+ device_policy_.auto_update_settings();
+
+ if (!proto.has_p2p_enabled())
+ return false;
+
+ *au_p2p_enabled = proto.p2p_enabled();
+ return true;
+}
+
+bool DevicePolicyImpl::GetAllowKioskAppControlChromeVersion(
+ bool* allow_kiosk_app_control_chrome_version) const {
+ if (!device_policy_.has_allow_kiosk_app_control_chrome_version())
+ return false;
+
+ const enterprise_management::AllowKioskAppControlChromeVersionProto& proto =
+ device_policy_.allow_kiosk_app_control_chrome_version();
+
+ if (!proto.has_allow_kiosk_app_control_chrome_version())
+ return false;
+
+ *allow_kiosk_app_control_chrome_version =
+ proto.allow_kiosk_app_control_chrome_version();
+ return true;
+}
+
+bool DevicePolicyImpl::GetUsbDetachableWhitelist(
+ std::vector<UsbDeviceId>* usb_whitelist) const {
+ if (!device_policy_.has_usb_detachable_whitelist())
+ return false;
+ const enterprise_management::UsbDetachableWhitelistProto& proto =
+ device_policy_.usb_detachable_whitelist();
+ usb_whitelist->clear();
+ for (int i = 0; i < proto.id_size(); i++) {
+ const ::enterprise_management::UsbDeviceIdProto& id = proto.id(i);
+ UsbDeviceId dev_id;
+ dev_id.vendor_id = id.has_vendor_id() ? id.vendor_id() : 0;
+ dev_id.product_id = id.has_product_id() ? id.product_id() : 0;
+ usb_whitelist->push_back(dev_id);
+ }
+ return true;
+}
+
+bool DevicePolicyImpl::VerifyPolicyFiles() {
+ // Both the policy and its signature have to exist.
+ if (!base::PathExists(policy_path_) || !base::PathExists(keyfile_path_)) {
+ return false;
+ }
+
+ // Check if the policy and signature file is owned by root.
+ struct stat file_stat;
+ stat(policy_path_.value().c_str(), &file_stat);
+ if (file_stat.st_uid != 0) {
+ LOG(ERROR) << "Policy file is not owned by root!";
+ return false;
+ }
+ stat(keyfile_path_.value().c_str(), &file_stat);
+ if (file_stat.st_uid != 0) {
+ LOG(ERROR) << "Policy signature file is not owned by root!";
+ return false;
+ }
+ return true;
+}
+
+bool DevicePolicyImpl::VerifyPolicySignature() {
+ if (policy_.has_policy_data_signature()) {
+ std::string policy_data = policy_.policy_data();
+ std::string policy_data_signature = policy_.policy_data_signature();
+ std::string public_key;
+ if (!ReadPublicKeyFromFile(base::FilePath(keyfile_path_), &public_key)) {
+ LOG(ERROR) << "Could not read owner key off disk";
+ return false;
+ }
+ if (!VerifySignature(policy_data, policy_data_signature, public_key)) {
+ LOG(ERROR) << "Signature does not match the data or can not be verified!";
+ return false;
+ }
+ return true;
+ }
+ LOG(ERROR) << "The policy blob is not signed!";
+ return false;
+}
+
+} // namespace policy
diff --git a/libbrillo/policy/device_policy_impl.h b/libbrillo/policy/device_policy_impl.h
new file mode 100644
index 0000000..db22475
--- /dev/null
+++ b/libbrillo/policy/device_policy_impl.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_POLICY_DEVICE_POLICY_IMPL_H_
+#define LIBBRILLO_POLICY_DEVICE_POLICY_IMPL_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+
+#include "bindings/chrome_device_policy.pb.h"
+#include "bindings/device_management_backend.pb.h"
+#include "policy/device_policy.h"
+
+#pragma GCC visibility push(default)
+
+namespace policy {
+
+// This class holds device settings that are to be enforced across all users.
+//
+// Before serving it to the users this class verifies that the policy is valid
+// against its signature and the owner's key and also that the policy files
+// are owned by root.
+class DevicePolicyImpl : public DevicePolicy {
+ public:
+ DevicePolicyImpl();
+ virtual ~DevicePolicyImpl();
+
+ virtual bool LoadPolicy();
+ virtual bool GetPolicyRefreshRate(int* rate) const;
+ virtual bool GetUserWhitelist(std::vector<std::string>* user_whitelist) const;
+ virtual bool GetGuestModeEnabled(bool* guest_mode_enabled) const;
+ virtual bool GetCameraEnabled(bool* camera_enabled) const;
+ virtual bool GetShowUserNames(bool* show_user_names) const;
+ virtual bool GetDataRoamingEnabled(bool* data_roaming_enabled) const;
+ virtual bool GetAllowNewUsers(bool* allow_new_users) const;
+ virtual bool GetMetricsEnabled(bool* metrics_enabled) const;
+ virtual bool GetReportVersionInfo(bool* report_version_info) const;
+ virtual bool GetReportActivityTimes(bool* report_activity_times) const;
+ virtual bool GetReportBootMode(bool* report_boot_mode) const;
+ virtual bool GetEphemeralUsersEnabled(bool* ephemeral_users_enabled) const;
+ virtual bool GetReleaseChannel(std::string* release_channel) const;
+ virtual bool GetReleaseChannelDelegated(
+ bool* release_channel_delegated) const;
+ virtual bool GetUpdateDisabled(bool* update_disabled) const;
+ virtual bool GetTargetVersionPrefix(
+ std::string* target_version_prefix) const;
+ virtual bool GetScatterFactorInSeconds(
+ int64_t* scatter_factor_in_seconds) const;
+ virtual bool GetAllowedConnectionTypesForUpdate(
+ std::set<std::string>* connection_types) const;
+ virtual bool GetOpenNetworkConfiguration(
+ std::string* open_network_configuration) const;
+ virtual bool GetOwner(std::string* owner) const;
+ virtual bool GetHttpDownloadsEnabled(bool* http_downloads_enabled) const;
+ virtual bool GetAuP2PEnabled(bool* au_p2p_enabled) const;
+ virtual bool GetAllowKioskAppControlChromeVersion(
+ bool* allow_kiosk_app_control_chrome_version) const;
+ virtual bool GetUsbDetachableWhitelist(
+ std::vector<UsbDeviceId>* usb_whitelist) const;
+
+ protected:
+ // Verifies that the policy files are owned by root and exist.
+ virtual bool VerifyPolicyFiles();
+
+ base::FilePath policy_path_;
+ base::FilePath keyfile_path_;
+
+ private:
+ // Verifies that the policy signature is correct.
+ virtual bool VerifyPolicySignature();
+
+ enterprise_management::PolicyFetchResponse policy_;
+ enterprise_management::PolicyData policy_data_;
+ enterprise_management::ChromeDeviceSettingsProto device_policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(DevicePolicyImpl);
+};
+} // namespace policy
+
+#pragma GCC visibility pop
+
+#endif // LIBBRILLO_POLICY_DEVICE_POLICY_IMPL_H_
diff --git a/libbrillo/policy/libpolicy.cc b/libbrillo/policy/libpolicy.cc
new file mode 100644
index 0000000..c075c84
--- /dev/null
+++ b/libbrillo/policy/libpolicy.cc
@@ -0,0 +1,53 @@
+// 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 "policy/libpolicy.h"
+
+#include <base/logging.h>
+
+#include "policy/device_policy.h"
+#ifndef __ANDROID__
+#include "policy/device_policy_impl.h"
+#endif
+
+namespace policy {
+
+PolicyProvider::PolicyProvider()
+ : device_policy_(nullptr),
+ device_policy_is_loaded_(false) {
+#ifndef __ANDROID__
+ device_policy_.reset(new DevicePolicyImpl());
+#endif
+}
+
+PolicyProvider::PolicyProvider(DevicePolicy* device_policy)
+ : device_policy_(device_policy),
+ device_policy_is_loaded_(true) {
+}
+
+PolicyProvider::~PolicyProvider() {
+}
+
+bool PolicyProvider::Reload() {
+ if (!device_policy_)
+ return false;
+ device_policy_is_loaded_ = device_policy_->LoadPolicy();
+ if (!device_policy_is_loaded_) {
+ LOG(WARNING) << "Could not load the device policy file.";
+ }
+ return device_policy_is_loaded_;
+}
+
+bool PolicyProvider::device_policy_is_loaded() const {
+ return device_policy_is_loaded_;
+}
+
+const DevicePolicy& PolicyProvider::GetDevicePolicy() const {
+ if (!device_policy_is_loaded_)
+ DCHECK("Trying to get policy data but policy was not loaded!");
+
+ return *device_policy_;
+}
+
+} // namespace policy
diff --git a/libbrillo/policy/libpolicy.h b/libbrillo/policy/libpolicy.h
new file mode 100644
index 0000000..eeca189
--- /dev/null
+++ b/libbrillo/policy/libpolicy.h
@@ -0,0 +1,50 @@
+// 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.
+
+#ifndef LIBBRILLO_POLICY_LIBPOLICY_H_
+#define LIBBRILLO_POLICY_LIBPOLICY_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+
+#pragma GCC visibility push(default)
+
+namespace policy {
+
+class DevicePolicy;
+
+// This class holds device settings that are to be enforced across all users.
+//
+// If there is a policy on disk at creation time, we will load it at verify
+// its signature.
+class PolicyProvider {
+ public:
+ PolicyProvider();
+ virtual ~PolicyProvider();
+
+ // Constructor for tests only!
+ explicit PolicyProvider(DevicePolicy* device_policy);
+
+ // This function will ensure the freshness of the contents that the getters
+ // are delivering. Normally contents are cached to prevent unnecessary load.
+ virtual bool Reload();
+
+ virtual bool device_policy_is_loaded() const;
+
+ // Returns a value from the device policy cache.
+ virtual const DevicePolicy& GetDevicePolicy() const;
+
+ private:
+ std::unique_ptr<DevicePolicy> device_policy_;
+ bool device_policy_is_loaded_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyProvider);
+};
+} // namespace policy
+
+#pragma GCC visibility pop
+
+#endif // LIBBRILLO_POLICY_LIBPOLICY_H_
diff --git a/libbrillo/policy/mock_device_policy.h b/libbrillo/policy/mock_device_policy.h
new file mode 100644
index 0000000..cabba91
--- /dev/null
+++ b/libbrillo/policy/mock_device_policy.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBBRILLO_POLICY_MOCK_DEVICE_POLICY_H_
+#define LIBBRILLO_POLICY_MOCK_DEVICE_POLICY_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+#include "policy/device_policy.h"
+
+#pragma GCC visibility push(default)
+
+namespace policy {
+
+// This is a generic mock class for the DevicePolicy that can be used by other
+// subsystems for tests. It allows to mock out the reading of a real policy
+// file and to simulate different policy values.
+// The test that needs policies would then do something like this :
+// // Prepare the action that would return a predefined policy value:
+// ACTION_P(SetMetricsPolicy, enabled) {
+// *arg0 = enabled;
+// return true;
+// }
+// ...
+// // Initialize the Mock class
+// policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+// // We should expect calls to LoadPolicy almost always and return true.
+// EXPECT_CALL(*device_policy_, LoadPolicy())
+// .Times(AnyNumber())
+// .WillRepeatedly(Return(true));
+// // This example needs to simulate the Metrics Enabled policy being set.
+// EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+// .Times(AnyNumber())
+// .WillRepeatedly(SetMetricsPolicy(true));
+// policy::PolicyProvider provider(device_policy);
+// ...
+// // In a test that needs other value of that policy we can do that:
+// EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+// .WillOnce(SetMetricsPolicy(false));
+//
+// See metrics_library_test.cc for example.
+class MockDevicePolicy : public DevicePolicy {
+ public:
+ MockDevicePolicy() {
+ ON_CALL(*this, LoadPolicy()).WillByDefault(testing::Return(true));
+ }
+ ~MockDevicePolicy() override = default;
+
+ MOCK_METHOD0(LoadPolicy, bool(void));
+
+ MOCK_CONST_METHOD1(GetPolicyRefreshRate,
+ bool(int*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetUserWhitelist, bool(std::vector<std::string>*));
+ MOCK_CONST_METHOD1(GetGuestModeEnabled,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetCameraEnabled,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetShowUserNames,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetDataRoamingEnabled,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetAllowNewUsers,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetMetricsEnabled,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetReportVersionInfo,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetReportActivityTimes,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetReportBootMode,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetEphemeralUsersEnabled,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetReleaseChannel, bool(std::string*));
+ MOCK_CONST_METHOD1(GetReleaseChannelDelegated,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetUpdateDisabled,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetTargetVersionPrefix, bool(std::string*));
+ MOCK_CONST_METHOD1(GetScatterFactorInSeconds,
+ bool(int64_t*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetAllowedConnectionTypesForUpdate,
+ bool(std::set<std::string>*));
+ MOCK_CONST_METHOD1(GetOpenNetworkConfiguration, bool(std::string*));
+ MOCK_CONST_METHOD1(GetOwner, bool(std::string*));
+ MOCK_CONST_METHOD1(GetHttpDownloadsEnabled,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetAuP2PEnabled,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetAllowKioskAppControlChromeVersion,
+ bool(bool*)); // NOLINT(readability/function)
+ MOCK_CONST_METHOD1(GetUsbDetachableWhitelist,
+ bool(std::vector<DevicePolicy::UsbDeviceId>*));
+
+ MOCK_METHOD0(VerifyPolicyFiles, bool(void));
+ MOCK_METHOD0(VerifyPolicySignature, bool(void));
+};
+} // namespace policy
+
+#pragma GCC visibility pop
+
+#endif // LIBBRILLO_POLICY_MOCK_DEVICE_POLICY_H_
diff --git a/libbrillo/policy/mock_libpolicy.h b/libbrillo/policy/mock_libpolicy.h
new file mode 100644
index 0000000..acb9b8b
--- /dev/null
+++ b/libbrillo/policy/mock_libpolicy.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 LIBBRILLO_POLICY_MOCK_LIBPOLICY_H_
+#define LIBBRILLO_POLICY_MOCK_LIBPOLICY_H_
+
+#include <gmock/gmock.h>
+#include <set>
+
+#include "policy/libpolicy.h"
+
+#pragma GCC visibility push(default)
+
+namespace policy {
+
+// This is a generic mock of the PolicyProvider class.
+class MockPolicyProvider : public PolicyProvider {
+ public:
+ MockPolicyProvider() = default;
+ ~MockPolicyProvider() override = default;
+
+ MOCK_METHOD0(Reload, bool(void));
+ MOCK_CONST_METHOD0(device_policy_is_loaded, bool(void));
+ MOCK_CONST_METHOD0(GetDevicePolicy, const DevicePolicy&(void));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockPolicyProvider);
+};
+
+} // namespace policy
+
+#pragma GCC visibility pop
+
+#endif // LIBBRILLO_POLICY_MOCK_LIBPOLICY_H_
diff --git a/libbrillo/policy/tests/libpolicy_unittest.cc b/libbrillo/policy/tests/libpolicy_unittest.cc
new file mode 100644
index 0000000..2946ee7
--- /dev/null
+++ b/libbrillo/policy/tests/libpolicy_unittest.cc
@@ -0,0 +1,233 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "policy/libpolicy.h"
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "policy/device_policy_impl.h"
+
+namespace policy {
+
+static const char kPolicyFileAllSet[] =
+ "policy/tests/whitelist/policy_all";
+static const char kPolicyFileNoneSet[] =
+ "policy/tests/whitelist/policy_none";
+static const char kKeyFile[] = "policy/tests/whitelist/owner.key";
+
+// This class mocks only the minimally needed functionionality to run tests
+// that would otherwise fail because of hard restrictions like root file
+// ownership. Otherwise, it preserves all the functionallity of the original
+// class.
+class MockDevicePolicyImpl : public DevicePolicyImpl {
+ public:
+ MockDevicePolicyImpl(const base::FilePath& policy_path,
+ const base::FilePath& keyfile_path,
+ bool verify_files)
+ : verify_files_(verify_files) {
+ policy_path_ = policy_path;
+ keyfile_path_ = keyfile_path;
+ }
+
+ private:
+ // We don't care if files are owned by root for most tests.
+ virtual bool VerifyPolicyFiles() {
+ return !verify_files_ || DevicePolicyImpl::VerifyPolicyFiles();
+ }
+
+ bool verify_files_;
+};
+
+// Test that a policy file can be verified and parsed correctly. The file
+// contains all possible fields, so reading should succeed for all.
+TEST(PolicyTest, DevicePolicyAllSetTest) {
+ base::FilePath policy_file(kPolicyFileAllSet);
+ base::FilePath key_file(kKeyFile);
+ MockDevicePolicyImpl* device_policy =
+ new MockDevicePolicyImpl(policy_file, key_file, false);
+ PolicyProvider provider(device_policy);
+ provider.Reload();
+
+ // Ensure we successfully loaded the device policy file.
+ ASSERT_TRUE(provider.device_policy_is_loaded());
+
+ const DevicePolicy& policy = provider.GetDevicePolicy();
+
+ // Check that we can read out all fields of the sample protobuf.
+ int int_value = -1;
+ ASSERT_TRUE(policy.GetPolicyRefreshRate(&int_value));
+ ASSERT_EQ(100, int_value);
+
+ std::vector<std::string> list_value;
+ ASSERT_TRUE(policy.GetUserWhitelist(&list_value));
+ ASSERT_EQ(3, list_value.size());
+ ASSERT_EQ("me@here.com", list_value[0]);
+ ASSERT_EQ("you@there.com", list_value[1]);
+ ASSERT_EQ("*@monsters.com", list_value[2]);
+
+ bool bool_value = true;
+ ASSERT_TRUE(policy.GetGuestModeEnabled(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetCameraEnabled(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetShowUserNames(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetDataRoamingEnabled(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetAllowNewUsers(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetMetricsEnabled(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetReportVersionInfo(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetReportActivityTimes(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetReportBootMode(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetEphemeralUsersEnabled(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ std::string string_value;
+ ASSERT_TRUE(policy.GetReleaseChannel(&string_value));
+ ASSERT_EQ("stable-channel", string_value);
+
+ bool_value = false;
+ ASSERT_TRUE(policy.GetReleaseChannelDelegated(&bool_value));
+ ASSERT_TRUE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetUpdateDisabled(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ int64_t int64_value = -1LL;
+ ASSERT_TRUE(policy.GetScatterFactorInSeconds(&int64_value));
+ ASSERT_EQ(17LL, int64_value);
+
+ ASSERT_TRUE(policy.GetTargetVersionPrefix(&string_value));
+ ASSERT_EQ("42.0.", string_value);
+
+ std::set<std::string> types;
+ ASSERT_TRUE(policy.GetAllowedConnectionTypesForUpdate(&types));
+ ASSERT_TRUE(types.end() != types.find("ethernet"));
+ ASSERT_TRUE(types.end() != types.find("wifi"));
+ ASSERT_EQ(2, types.size());
+
+ ASSERT_TRUE(policy.GetOpenNetworkConfiguration(&string_value));
+ ASSERT_EQ("{}", string_value);
+
+ ASSERT_TRUE(policy.GetOwner(&string_value));
+ ASSERT_EQ("", string_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetHttpDownloadsEnabled(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetAuP2PEnabled(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ bool_value = true;
+ ASSERT_TRUE(policy.GetAllowKioskAppControlChromeVersion(&bool_value));
+ ASSERT_FALSE(bool_value);
+
+ std::vector<DevicePolicy::UsbDeviceId> list_device;
+ ASSERT_TRUE(policy.GetUsbDetachableWhitelist(&list_device));
+ ASSERT_EQ(2, list_device.size());
+ ASSERT_EQ(0x413c, list_device[0].vendor_id);
+ ASSERT_EQ(0x2105, list_device[0].product_id);
+ ASSERT_EQ(0x0403, list_device[1].vendor_id);
+ ASSERT_EQ(0x6001, list_device[1].product_id);
+
+ // Reloading the protobuf should succeed.
+ ASSERT_TRUE(provider.Reload());
+}
+
+// Test that a policy file can be verified and parsed correctly. The file
+// contains none of the possible fields, so reading should fail for all.
+TEST(PolicyTest, DevicePolicyNoneSetTest) {
+ base::FilePath policy_file(kPolicyFileNoneSet);
+ base::FilePath key_file(kKeyFile);
+ MockDevicePolicyImpl* device_policy =
+ new MockDevicePolicyImpl(policy_file, key_file, false);
+ PolicyProvider provider(device_policy);
+ provider.Reload();
+
+ // Ensure we successfully loaded the device policy file.
+ ASSERT_TRUE(provider.device_policy_is_loaded());
+
+ const DevicePolicy& policy = provider.GetDevicePolicy();
+
+ // Check that we cannot read any fields out of the sample protobuf.
+ int int_value;
+ int64_t int64_value;
+ std::vector<std::string> list_value;
+ bool bool_value;
+ std::string string_value;
+ std::vector<DevicePolicy::UsbDeviceId> list_device;
+
+ ASSERT_FALSE(policy.GetPolicyRefreshRate(&int_value));
+ ASSERT_FALSE(policy.GetUserWhitelist(&list_value));
+ ASSERT_FALSE(policy.GetGuestModeEnabled(&bool_value));
+ ASSERT_FALSE(policy.GetCameraEnabled(&bool_value));
+ ASSERT_FALSE(policy.GetShowUserNames(&bool_value));
+ ASSERT_FALSE(policy.GetDataRoamingEnabled(&bool_value));
+ ASSERT_FALSE(policy.GetAllowNewUsers(&bool_value));
+ ASSERT_FALSE(policy.GetMetricsEnabled(&bool_value));
+ ASSERT_FALSE(policy.GetReportVersionInfo(&bool_value));
+ ASSERT_FALSE(policy.GetReportActivityTimes(&bool_value));
+ ASSERT_FALSE(policy.GetReportBootMode(&bool_value));
+ ASSERT_FALSE(policy.GetEphemeralUsersEnabled(&bool_value));
+ ASSERT_FALSE(policy.GetReleaseChannel(&string_value));
+ ASSERT_FALSE(policy.GetUpdateDisabled(&bool_value));
+ ASSERT_FALSE(policy.GetTargetVersionPrefix(&string_value));
+ ASSERT_FALSE(policy.GetScatterFactorInSeconds(&int64_value));
+ ASSERT_FALSE(policy.GetOpenNetworkConfiguration(&string_value));
+ ASSERT_FALSE(policy.GetHttpDownloadsEnabled(&bool_value));
+ ASSERT_FALSE(policy.GetAuP2PEnabled(&bool_value));
+ ASSERT_FALSE(policy.GetAllowKioskAppControlChromeVersion(&bool_value));
+ ASSERT_FALSE(policy.GetUsbDetachableWhitelist(&list_device));
+}
+
+// Verify that the library will correctly recognize and signal missing files.
+TEST(PolicyTest, DevicePolicyFailure) {
+ LOG(INFO) << "Errors expected.";
+ // Try loading non-existing protobuf should fail.
+ base::FilePath non_existing("this_file_is_doof");
+ MockDevicePolicyImpl* device_policy =
+ new MockDevicePolicyImpl(non_existing, non_existing, true);
+ PolicyProvider provider(device_policy);
+ // Even after reload the policy should still be not loaded.
+ ASSERT_FALSE(provider.Reload());
+ ASSERT_FALSE(provider.device_policy_is_loaded());
+}
+
+} // namespace policy
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/libbrillo/policy/tests/whitelist/README b/libbrillo/policy/tests/whitelist/README
new file mode 100644
index 0000000..34d63d0
--- /dev/null
+++ b/libbrillo/policy/tests/whitelist/README
@@ -0,0 +1,4 @@
+The files in this directory are sample Device Policy File. To modify them please
+refer to the following instructions:
+
+ http://www.chromium.org/developers/how-tos/enterprise/protobuf-encoded-policy-blobs
diff --git a/libbrillo/policy/tests/whitelist/owner.key b/libbrillo/policy/tests/whitelist/owner.key
new file mode 100644
index 0000000..352c42c
--- /dev/null
+++ b/libbrillo/policy/tests/whitelist/owner.key
Binary files differ
diff --git a/libbrillo/policy/tests/whitelist/policy_all b/libbrillo/policy/tests/whitelist/policy_all
new file mode 100644
index 0000000..b2b8f10
--- /dev/null
+++ b/libbrillo/policy/tests/whitelist/policy_all
Binary files differ
diff --git a/libbrillo/policy/tests/whitelist/policy_none b/libbrillo/policy/tests/whitelist/policy_none
new file mode 100644
index 0000000..5d1cf1f
--- /dev/null
+++ b/libbrillo/policy/tests/whitelist/policy_none
Binary files differ
diff --git a/libbrillo/testrunner.cc b/libbrillo/testrunner.cc
new file mode 100644
index 0000000..7122b9c
--- /dev/null
+++ b/libbrillo/testrunner.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2009 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.
+
+// based on pam_google_testrunner.cc
+
+#include <glib-object.h>
+#include <gtest/gtest.h>
+
+#include <base/at_exit.h>
+#include <brillo/test_helpers.h>
+
+int main(int argc, char** argv) {
+ base::AtExitManager at_exit_manager;
+ ::g_type_init();
+ SetUpTests(&argc, argv, true);
+ return RUN_ALL_TESTS();
+}
diff --git a/libbrillo/testrunner_android.cc b/libbrillo/testrunner_android.cc
new file mode 100644
index 0000000..93377bf
--- /dev/null
+++ b/libbrillo/testrunner_android.cc
@@ -0,0 +1,26 @@
+//Copyright (C) 2015 The Android Open Source Project
+//
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+
+// based on testrunner.cc with the glib bits removed.
+
+#include <gtest/gtest.h>
+
+#include <base/at_exit.h>
+#include <brillo/test_helpers.h>
+
+int main(int argc, char** argv) {
+ base::AtExitManager at_exit_manager;
+ SetUpTests(&argc, argv, true);
+ return RUN_ALL_TESTS();
+}