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(); +}