diff options
author | Alex Vakulenko <avakulenko@chromium.org> | 2014-08-29 14:36:56 -0700 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2014-09-03 09:23:51 +0000 |
commit | 6cd7f815cb865ac1a82429c5088147283595c3fe (patch) | |
tree | 80ba7282da2c81a1631ca8caeb808e2475f33b07 | |
parent | 46dabd12e53e8a48017ac7153df0bbe0e846346e (diff) | |
download | platform_external_libbrillo-6cd7f815cb865ac1a82429c5088147283595c3fe.tar.gz platform_external_libbrillo-6cd7f815cb865ac1a82429c5088147283595c3fe.tar.bz2 platform_external_libbrillo-6cd7f815cb865ac1a82429c5088147283595c3fe.zip |
libchromeos: Add D-Bus method invoker helpers
Add helpers that allow to invoke D-Bus methods with a single
C++ function call and parse/validate return values and handle
possible errors.
BUG=None
TEST=FEATURES=test emerge-link libchromeos
Change-Id: I975879de3a6ca622321ed37a4652465fa4f85475
Reviewed-on: https://chromium-review.googlesource.com/215684
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
-rw-r--r-- | chromeos/dbus/dbus_method_invoker.h | 136 | ||||
-rw-r--r-- | chromeos/dbus/dbus_method_invoker_unittest.cc | 121 | ||||
-rw-r--r-- | libchromeos.gypi | 1 |
3 files changed, 258 insertions, 0 deletions
diff --git a/chromeos/dbus/dbus_method_invoker.h b/chromeos/dbus/dbus_method_invoker.h new file mode 100644 index 0000000..dff5868 --- /dev/null +++ b/chromeos/dbus/dbus_method_invoker.h @@ -0,0 +1,136 @@ +// Copyright 2014 The Chromium OS 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 and pass all the required parameters as C++ function +// arguments. CallMethodAndBlock relies on automatic C++ to D-Bus data +// serialization implemented in chromeos/dbus/data_serialization.h. +// CallMethodAndBlock invokes the D-Bus method and returns the Response. + +// The method call response can (and should) be parsed with +// ExtractMethodCallResults(). The method takes an optional list of pointers +// to the expected return values of the D-Bus method. + +// Here is an example of usage. +// Call "std::string MyInterface::MyMethod(int, double)" over D-Bus: + +// using chromeos::dbus_utils::CallMethodAndBlock; +// using chromeos::dbus_utils::ExtractMethodCallResults; +// +// auto resp = CallMethodAndBlock(obj, +// "org.chromium.MyService.MyInterface", +// "MyMethod", +// 2, 8.7); +// +// chromeos::ErrorPtr error; +// std::string return_value; +// if (ExtractMethodCallResults(resp, &error, &return_value)) { +// // Use the |return_value|. +// } else { +// // An error occurred. Use |error| to get details. +// } + +#ifndef LIBCHROMEOS_CHROMEOS_DBUS_DBUS_METHOD_INVOKER_H_ +#define LIBCHROMEOS_CHROMEOS_DBUS_DBUS_METHOD_INVOKER_H_ + +#include <memory> +#include <string> +#include <tuple> + +#include <chromeos/dbus/dbus_param_reader.h> +#include <chromeos/dbus/dbus_param_writer.h> +#include <chromeos/errors/error.h> +#include <chromeos/errors/error_codes.h> +#include <dbus/message.h> +#include <dbus/object_proxy.h> + +namespace chromeos { +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]). +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, + 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...); + auto response = object->CallMethodAndBlock(&method_call, timeout_ms); + return std::unique_ptr<dbus::Response>(response.release()); +} + +// 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, + const Args&... args) { + return CallMethodAndBlockWithTimeout(dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + object, + interface_name, + method_name, + args...); +} + +// 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 returned values. Just do not specify any output |results|. In this case, +// ExtractMethodCallResults() will verify that the method didn't return any +// data in the response message. +// +// If |response| contains an error message, it is extracted and set to |error| +// object and ExtractMethodCallResults() will return false. +template<typename... ResultTypes> +inline bool ExtractMethodCallResults(dbus::Response* response, + ErrorPtr* error, + ResultTypes*... results) { + dbus::MessageReader reader(response); + // If response contain an error message, extract the error information + // into the |error| object. + if (response->GetMessageType() == dbus::Message::MESSAGE_ERROR) { + dbus::ErrorResponse* error_response = + static_cast<dbus::ErrorResponse*>(response); + std::string error_code = error_response->GetErrorName(); + std::string error_message; + reader.PopString(&error_message); + Error::AddTo(error, errors::dbus::kDomain, error_code, error_message); + return false; + } + // gcc 4.8 does not allow to pass the parameter packs via lambda captures + // so using a tuple instead. Tie the results to a tuple and pass the tuple + // to the lambda callback. + // Apparently this is fixed in gcc 4.9, but this is a reasonable work-around + // and might be used after upgrade to v4.9. + // |results_ref_tuple| will contain lvalue references to the output objects + // passed in as pointers in arguments to ExtractMethodCallResults(). + auto results_ref_tuple = std::tie(*results...); + auto callback = [&results_ref_tuple](const ResultTypes&... params) { + results_ref_tuple = std::tie(params...); + }; + return DBusParamReader<ResultTypes...>::Invoke(callback, error, &reader); +} + +} // namespace dbus_utils +} // namespace chromeos + +#endif // LIBCHROMEOS_CHROMEOS_DBUS_DBUS_METHOD_INVOKER_H_ diff --git a/chromeos/dbus/dbus_method_invoker_unittest.cc b/chromeos/dbus/dbus_method_invoker_unittest.cc new file mode 100644 index 0000000..b423e3a --- /dev/null +++ b/chromeos/dbus/dbus_method_invoker_unittest.cc @@ -0,0 +1,121 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <chromeos/dbus/dbus_method_invoker.h> + +#include <string> + +#include <dbus/mock_bus.h> +#include <dbus/mock_object_proxy.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 chromeos { +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"; + +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_, MockCallMethodAndBlock(_, def_timeout_ms)) + .WillRepeatedly(Invoke(this, &DBusMethodInvokerTest::CreateResponse)); + } + + void TearDown() override { + bus_ = nullptr; + } + + Response* CreateResponse(dbus::MethodCall* method_call, int timeout_ms) { + 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); + auto response = dbus::ErrorResponse::FromMethodCall(method_call, + "org.MyError", + "My error message"); + 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 = + chromeos::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(), + kTestInterface, + kTestMethod1, + v1, v2); + EXPECT_NE(nullptr, response.get()); + std::string result; + using chromeos::dbus_utils::ExtractMethodCallResults; + EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &result)); + return result; + } + + 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) { + std::unique_ptr<dbus::Response> response = + chromeos::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(), + kTestInterface, + kTestMethod2); + EXPECT_NE(nullptr, response.get()); + chromeos::ErrorPtr error; + using chromeos::dbus_utils::ExtractMethodCallResults; + EXPECT_FALSE(ExtractMethodCallResults(response.get(), &error)); + EXPECT_EQ(chromeos::errors::dbus::kDomain, error->GetDomain()); + EXPECT_EQ("org.MyError", error->GetCode()); + EXPECT_EQ("My error message", error->GetMessage()); +} + +} // namespace dbus_utils +} // namespace chromeos diff --git a/libchromeos.gypi b/libchromeos.gypi index 3991eb2..b677447 100644 --- a/libchromeos.gypi +++ b/libchromeos.gypi @@ -243,6 +243,7 @@ 'chromeos/data_encoding_unittest.cc', 'chromeos/dbus/async_event_sequencer_unittest.cc', 'chromeos/dbus/data_serialization_unittest.cc', + 'chromeos/dbus/dbus_method_invoker_unittest.cc', 'chromeos/dbus/dbus_object_unittest.cc', 'chromeos/dbus/dbus_param_reader_unittest.cc', 'chromeos/dbus/dbus_param_writer_unittest.cc', |