aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Vakulenko <avakulenko@chromium.org>2014-08-29 14:36:56 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-09-03 09:23:51 +0000
commit6cd7f815cb865ac1a82429c5088147283595c3fe (patch)
tree80ba7282da2c81a1631ca8caeb808e2475f33b07
parent46dabd12e53e8a48017ac7153df0bbe0e846346e (diff)
downloadplatform_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.h136
-rw-r--r--chromeos/dbus/dbus_method_invoker_unittest.cc121
-rw-r--r--libchromeos.gypi1
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',