xref: /openbmc/sdbusplus/include/sdbusplus/message/append.hpp (revision 47ac18da9347b199df9be04250b2a987fc4c30a7)
1 #pragma once
2 
3 #include <systemd/sd-bus.h>
4 
5 #include <sdbusplus/message/types.hpp>
6 #include <sdbusplus/sdbus.hpp>
7 #include <sdbusplus/utility/container_traits.hpp>
8 #include <sdbusplus/utility/tuple_to_array.hpp>
9 #include <sdbusplus/utility/type_traits.hpp>
10 
11 #include <bit>
12 #include <iterator>
13 #include <string_view>
14 #include <tuple>
15 #include <type_traits>
16 #include <variant>
17 
18 namespace sdbusplus
19 {
20 
21 namespace message
22 {
23 
24 /** @brief Append data into an sdbus message.
25  *
26  *  (This is an empty no-op function that is useful in some cases for
27  *   variadic template reasons.)
28  */
append(sdbusplus::SdBusInterface *,sd_bus_message *)29 inline void append(sdbusplus::SdBusInterface* /*intf*/, sd_bus_message* /*m*/)
30 {}
31 /** @brief Append data into an sdbus message.
32  *
33  *  @param[in] m - The message to append to.
34  *  @tparam Args - C++ types of arguments to append to message.
35  *  @param[in] args - values to append to message.
36  *
37  *  This function will, at compile-time, deduce the DBus types of the passed
38  *  C++ values and call the sd_bus_message_append functions with the
39  *  appropriate type parameters.  It may also do conversions, where needed,
40  *  to convert C++ types into C representations (eg. string, vector).
41  */
42 template <typename... Args>
43 void append(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args);
44 
45 namespace details
46 {
47 
48 /** @brief Utility to append a single C++ element into a sd_bus_message.
49  *
50  *  User-defined types are expected to specialize this template in order to
51  *  get their functionality.
52  *
53  *  @tparam S - Type of element to append.
54  */
55 template <typename S, typename Enable = void>
56 struct append_single
57 {
58     // Downcast
59     template <typename T>
60     using Td = types::details::type_id_downcast_t<T>;
61 
62     // sd_bus_message_append_basic expects a T* (cast to void*) for most types,
63     // so t& is appropriate.  In the case of char*, it expects the void* is
64     // the char*.  If we use &t, that is a char** and not a char*.
65     //
66     // Use these helper templates 'address_of(t)' in place of '&t' to
67     // handle both cases.
68     template <typename T>
address_of_helpersdbusplus::message::details::append_single69     static auto address_of_helper(T&& t, std::false_type)
70     {
71         return &t;
72     }
73     template <typename T>
address_of_helpersdbusplus::message::details::append_single74     static auto address_of_helper(T&& t, std::true_type)
75     {
76         return t;
77     }
78 
79     template <typename T>
address_ofsdbusplus::message::details::append_single80     static auto address_of(T&& t)
81     {
82         return address_of_helper(std::forward<T>(t),
83                                  std::is_pointer<std::remove_reference_t<T>>());
84     }
85 
86     /** @brief Do the operation to append element.
87      *
88      *  @tparam T - Type of element to append.
89      *
90      *  Template parameters T (function) and S (class) are different
91      *  to allow the function to be utilized for many variants of S:
92      *  S&, S&&, const S&, volatile S&, etc. The type_id_downcast is used
93      *  to ensure T and S are equivalent.  For 'char*', this also allows
94      *  use for 'char[N]' types.
95      *
96      *  @param[in] m - sd_bus_message to append into.
97      *  @param[in] t - The item to append.
98      */
99     template <typename T>
100     static std::enable_if_t<std::is_same_v<S, Td<T>> && !std::is_enum_v<Td<T>>>
opsdbusplus::message::details::append_single101         op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
102     {
103         // For this default implementation, we need to ensure that only
104         // basic types are used.
105         static_assert(std::is_fundamental_v<Td<T>> ||
106                           std::is_convertible_v<Td<T>, const char*>,
107                       "Non-basic types are not allowed.");
108 
109         constexpr auto dbusType = std::get<0>(types::type_id<T>());
110         intf->sd_bus_message_append_basic(m, dbusType,
111                                           address_of(std::forward<T>(t)));
112     }
113 
114     template <typename T>
115     static std::enable_if_t<std::is_same_v<S, Td<T>> && std::is_enum_v<Td<T>>>
opsdbusplus::message::details::append_single116         op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
117     {
118         auto value = sdbusplus::message::convert_to_string<Td<T>>(t);
119         sdbusplus::message::append(intf, m, value);
120     }
121 };
122 
123 template <typename T>
124 using append_single_t = append_single<types::details::type_id_downcast_t<T>>;
125 
126 /** @brief Specialization of append_single for details::unix_fd. */
127 template <>
128 struct append_single<details::unix_fd_type>
129 {
130     template <typename T>
sanitizesdbusplus::message::details::append_single131     static void sanitize(const T&)
132     {}
133 
134     template <typename T>
sanitizesdbusplus::message::details::append_single135     static void sanitize(T& s)
136     {
137         s.fd = -1;
138     }
139 
140     template <typename T>
opsdbusplus::message::details::append_single141     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
142     {
143         constexpr auto dbusType = std::get<0>(types::type_id<T>());
144         intf->sd_bus_message_append_basic(m, dbusType, &s.fd);
145 
146         // sd-bus now owns the file descriptor
147         sanitize(s);
148     }
149 };
150 
151 /** @brief Specialization of append_single for std::strings. */
152 template <>
153 struct append_single<std::string>
154 {
155     template <typename T>
opsdbusplus::message::details::append_single156     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
157     {
158         constexpr auto dbusType = std::get<0>(types::type_id<T>());
159         intf->sd_bus_message_append_basic(m, dbusType, s.c_str());
160     }
161 };
162 
163 /** @brief Specialization of append_single for std::string_views. */
164 template <>
165 struct append_single<std::string_view>
166 {
167     template <typename T>
opsdbusplus::message::details::append_single168     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
169     {
170         iovec iov{std::bit_cast<void*>(s.data()), s.size()};
171         intf->sd_bus_message_append_string_iovec(m, &iov, 1);
172     }
173 };
174 
175 /** @brief Specialization of append_single for details::string_wrapper. */
176 template <>
177 struct append_single<details::string_wrapper>
178 {
179     template <typename S>
opsdbusplus::message::details::append_single180     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
181     {
182         constexpr auto dbusType = std::get<0>(types::type_id<S>());
183         intf->sd_bus_message_append_basic(m, dbusType, s.str.c_str());
184     }
185 };
186 
187 /** @brief Specialization of append_single for details::string_wrapper. */
188 template <>
189 struct append_single<details::string_path_wrapper>
190 {
191     template <typename S>
opsdbusplus::message::details::append_single192     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
193     {
194         constexpr auto dbusType = std::get<0>(types::type_id<S>());
195         intf->sd_bus_message_append_basic(m, dbusType, s.str.c_str());
196     }
197 };
198 
199 /** @brief Specialization of append_single for bool. */
200 template <>
201 struct append_single<bool>
202 {
203     template <typename T>
opsdbusplus::message::details::append_single204     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& b)
205     {
206         constexpr auto dbusType = std::get<0>(types::type_id<T>());
207         int i = b;
208         intf->sd_bus_message_append_basic(m, dbusType, &i);
209     }
210 };
211 
212 // Determines if fallback to normal iteration and append is required (can't use
213 // sd_bus_message_append_array)
214 template <typename T>
215 concept can_append_array_non_contigious =
216     utility::is_dbus_array<T> && !utility::can_append_array_value<T>;
217 
218 /** @brief Specialization of append_single for containers
219  * (ie vector, array, etc), where the contiguous elements can be loaded in a
220  * single operation
221  */
222 template <can_append_array_non_contigious T>
223 struct append_single<T>
224 {
225     template <typename S>
opsdbusplus::message::details::append_single226     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
227     {
228         constexpr auto dbusType =
229             utility::tuple_to_array(types::type_id<typename T::value_type>());
230 
231         intf->sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY,
232                                             dbusType.data());
233         for (const typename T::value_type& i : s)
234         {
235             sdbusplus::message::append(intf, m, i);
236         }
237         intf->sd_bus_message_close_container(m);
238     }
239 };
240 
241 // Determines if the iterable type (vector, array) meets the requirements for
242 // using sd_bus_message_append_array
243 template <typename T>
244 concept can_append_array_contigious =
245     utility::is_dbus_array<T> && utility::can_append_array_value<T>;
246 
247 /** @brief Specialization of append_single for vector and array T,
248  * with its elements is trivially copyable, and is an integral type,
249  * Bool is explicitly disallowed by sd-bus, so avoid it here */
250 template <can_append_array_contigious T>
251 struct append_single<T>
252 {
253     template <typename S>
opsdbusplus::message::details::append_single254     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
255     {
256         constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>());
257         intf->sd_bus_message_append_array(
258             m, dbusType[1], s.data(),
259             s.size() * sizeof(typename T::value_type));
260     }
261 };
262 
263 /** @brief Specialization of append_single for std::pairs. */
264 template <typename T1, typename T2>
265 struct append_single<std::pair<T1, T2>>
266 {
267     template <typename S>
opsdbusplus::message::details::append_single268     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
269     {
270         constexpr auto dbusType = utility::tuple_to_array(
271             std::tuple_cat(types::type_id_nonull<T1>(), types::type_id<T2>()));
272 
273         intf->sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY,
274                                             dbusType.data());
275         sdbusplus::message::append(intf, m, s.first, s.second);
276         intf->sd_bus_message_close_container(m);
277     }
278 };
279 
280 /** @brief Specialization of append_single for std::tuples. */
281 template <typename... Args>
282 struct append_single<std::tuple<Args...>>
283 {
284     template <typename S, std::size_t... I>
_opsdbusplus::message::details::append_single285     static void _op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s,
286                     std::integer_sequence<std::size_t, I...>)
287     {
288         sdbusplus::message::append(intf, m, std::get<I>(s)...);
289     }
290 
291     template <typename S>
opsdbusplus::message::details::append_single292     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
293     {
294         constexpr auto dbusType = utility::tuple_to_array(std::tuple_cat(
295             types::type_id_nonull<Args...>(),
296             std::make_tuple('\0') /* null terminator for C-string */));
297 
298         intf->sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT,
299                                             dbusType.data());
300         _op(intf, m, std::forward<S>(s),
301             std::make_index_sequence<sizeof...(Args)>());
302         intf->sd_bus_message_close_container(m);
303     }
304 };
305 
306 /** @brief Specialization of append_single for std::variant. */
307 template <typename... Args>
308 struct append_single<std::variant<Args...>>
309 {
310     template <typename S, typename = std::enable_if_t<0 < sizeof...(Args)>>
opsdbusplus::message::details::append_single311     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
312     {
313         auto apply = [intf, m](auto&& arg) {
314             constexpr auto dbusType =
315                 utility::tuple_to_array(types::type_id<decltype(arg)>());
316 
317             intf->sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT,
318                                                 dbusType.data());
319             sdbusplus::message::append(intf, m, arg);
320             intf->sd_bus_message_close_container(m);
321         };
322 
323         std::visit(apply, s);
324     }
325 };
326 } // namespace details
327 
328 template <typename... Args>
append(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Args &&...args)329 void append(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args)
330 {
331     (details::append_single_t<Args>::op(intf, m, args), ...);
332 }
333 
334 } // namespace message
335 
336 } // namespace sdbusplus
337