1// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This file provides generic method to parse function call arguments from
6// D-Bus message buffer and subsequently invokes a provided native C++ callback
7// with the parameter values passed as the callback arguments.
8
9// This functionality is achieved by parsing method arguments one by one,
10// left to right from the C++ callback's type signature, and moving the parsed
11// arguments to the back to the next call to DBusInvoke::Invoke's arguments as
12// const refs.  Each iteration has one fewer template specialization arguments,
13// until there is only the return type remaining and we fall through to either
14// the void or the non-void final specialization.
15
16#ifndef LIBBRILLO_BRILLO_DBUS_DBUS_PARAM_READER_H_
17#define LIBBRILLO_BRILLO_DBUS_DBUS_PARAM_READER_H_
18
19#include <type_traits>
20
21#include <brillo/dbus/data_serialization.h>
22#include <brillo/dbus/utils.h>
23#include <brillo/errors/error.h>
24#include <brillo/errors/error_codes.h>
25#include <dbus/message.h>
26
27namespace brillo {
28namespace dbus_utils {
29
30// A generic DBusParamReader stub class which allows us to specialize on
31// a variable list of expected function parameters later on.
32// This struct in itself is not used. But its concrete template specializations
33// defined below are.
34// |allow_out_params| controls whether DBusParamReader allows the parameter
35// list to contain OUT parameters (pointers).
36template<bool allow_out_params, typename...>
37struct DBusParamReader;
38
39// A generic specialization of DBusParamReader to handle variable function
40// parameters. This specialization pops one parameter off the D-Bus message
41// buffer and calls other specializations of DBusParamReader with fewer
42// parameters to pop the remaining parameters.
43//  CurrentParam  - the type of the current method parameter we are processing.
44//  RestOfParams  - the types of remaining parameters to be processed.
45template<bool allow_out_params, typename CurrentParam, typename... RestOfParams>
46struct DBusParamReader<allow_out_params, CurrentParam, RestOfParams...> {
47  // DBusParamReader::Invoke() is a member function that actually extracts the
48  // current parameter from the message buffer.
49  //  handler     - the C++ callback functor to be called when all the
50  //                parameters are processed.
51  //  method_call - D-Bus method call object we are processing.
52  //  reader      - D-Bus message reader to pop the current argument value from.
53  //  args...     - the callback parameters processed so far.
54  template<typename CallbackType, typename... Args>
55  static bool Invoke(const CallbackType& handler,
56                     dbus::MessageReader* reader,
57                     ErrorPtr* error,
58                     const Args&... args) {
59    return InvokeHelper<CurrentParam, CallbackType, Args...>(
60        handler, reader, error, static_cast<const Args&>(args)...);
61  }
62
63  //
64  // There are two specializations of this function:
65  //  1. For the case where ParamType is a value type (D-Bus IN parameter).
66  //  2. For the case where ParamType is a pointer (D-Bus OUT parameter).
67  // In the second case, the parameter is not popped off the message reader,
68  // since we do not expect the client to provide any data for it.
69  // However after the final handler is called, the values for the OUT
70  // parameters should be sent back in the method call response message.
71
72  // Overload 1: ParamType is not a pointer.
73  template<typename ParamType, typename CallbackType, typename... Args>
74  static typename std::enable_if<!std::is_pointer<ParamType>::value, bool>::type
75  InvokeHelper(const CallbackType& handler,
76               dbus::MessageReader* reader,
77               ErrorPtr* error,
78               const Args&... args) {
79    if (!reader->HasMoreData()) {
80      Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
81                   DBUS_ERROR_INVALID_ARGS,
82                   "Too few parameters in a method call");
83      return false;
84    }
85    // ParamType could be a reference type (e.g. 'const std::string&').
86    // Here we need a value type so we can create an object of this type and
87    // pop the value off the message buffer into. Using std::decay<> to get
88    // the value type. If ParamType is already a value type, ParamValueType will
89    // be the same as ParamType.
90    using ParamValueType = typename std::decay<ParamType>::type;
91    // The variable to hold the value of the current parameter we reading from
92    // the message buffer.
93    ParamValueType current_param;
94    if (!DBusType<ParamValueType>::Read(reader, &current_param)) {
95      Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
96                   DBUS_ERROR_INVALID_ARGS,
97                   "Method parameter type mismatch");
98      return false;
99    }
100    // Call DBusParamReader::Invoke() to process the rest of parameters.
101    // Note that this is not a recursive call because it is calling a different
102    // method of a different class. We exclude the current parameter type
103    // (ParamType) from DBusParamReader<> template parameter list and forward
104    // all the parameters to the arguments of Invoke() and append the current
105    // parameter to the end of the parameter list. We pass it as a const
106    // reference to allow to use move-only types such as std::unique_ptr<> and
107    // to eliminate unnecessarily copying data.
108    return DBusParamReader<allow_out_params, RestOfParams...>::Invoke(
109        handler, reader, error,
110        static_cast<const Args&>(args)...,
111        static_cast<const ParamValueType&>(current_param));
112  }
113
114  // Overload 2: ParamType is a pointer.
115  template<typename ParamType, typename CallbackType, typename... Args>
116  static typename std::enable_if<allow_out_params &&
117                                 std::is_pointer<ParamType>::value, bool>::type
118      InvokeHelper(const CallbackType& handler,
119                   dbus::MessageReader* reader,
120                   ErrorPtr* error,
121                   const Args&... args) {
122    // ParamType is a pointer. This is expected to be an output parameter.
123    // Create storage for it and the handler will provide a value for it.
124    using ParamValueType = typename std::remove_pointer<ParamType>::type;
125    // The variable to hold the value of the current parameter we are passing
126    // to the handler.
127    ParamValueType current_param{};  // Default-initialize the value.
128    // Call DBusParamReader::Invoke() to process the rest of parameters.
129    // Note that this is not a recursive call because it is calling a different
130    // method of a different class. We exclude the current parameter type
131    // (ParamType) from DBusParamReader<> template parameter list and forward
132    // all the parameters to the arguments of Invoke() and append the current
133    // parameter to the end of the parameter list.
134    return DBusParamReader<allow_out_params, RestOfParams...>::Invoke(
135        handler, reader, error,
136        static_cast<const Args&>(args)...,
137        &current_param);
138  }
139};  // struct DBusParamReader<ParamType, RestOfParams...>
140
141// The final specialization of DBusParamReader<> used when no more parameters
142// are expected in the message buffer. Actually dispatches the call to the
143// handler with all the accumulated arguments.
144template<bool allow_out_params>
145struct DBusParamReader<allow_out_params> {
146  template<typename CallbackType, typename... Args>
147  static bool Invoke(const CallbackType& handler,
148                     dbus::MessageReader* reader,
149                     ErrorPtr* error,
150                     const Args&... args) {
151    if (reader->HasMoreData()) {
152      Error::AddTo(error, FROM_HERE, errors::dbus::kDomain,
153                   DBUS_ERROR_INVALID_ARGS,
154                   "Too many parameters in a method call");
155      return false;
156    }
157    handler(args...);
158    return true;
159  }
160};  // struct DBusParamReader<>
161
162}  // namespace dbus_utils
163}  // namespace brillo
164
165#endif  // LIBBRILLO_BRILLO_DBUS_DBUS_PARAM_READER_H_
166