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 <string_view>
13 #include <tuple>
14 #include <type_traits>
15 #include <variant>
16
17 namespace sdbusplus
18 {
19
20 namespace message
21 {
22
23 /** @brief Append data into an sdbus message.
24 *
25 * (This is an empty no-op function that is useful in some cases for
26 * variadic template reasons.)
27 */
append(sdbusplus::SdBusInterface *,sd_bus_message *)28 inline void append(sdbusplus::SdBusInterface* /*intf*/, sd_bus_message* /*m*/)
29 {}
30 /** @brief Append data into an sdbus message.
31 *
32 * @param[in] m - The message to append to.
33 * @tparam Args - C++ types of arguments to append to message.
34 * @param[in] args - values to append to message.
35 *
36 * This function will, at compile-time, deduce the DBus types of the passed
37 * C++ values and call the sd_bus_message_append functions with the
38 * appropriate type parameters. It may also do conversions, where needed,
39 * to convert C++ types into C representations (eg. string, vector).
40 */
41 template <typename... Args>
42 void append(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args);
43
44 namespace details
45 {
46
47 /** @brief Utility to identify C++ types that may not be grouped into a
48 * single sd_bus_message_append call and instead need special
49 * handling.
50 *
51 * @tparam T - Type for identification.
52 *
53 * User-defined types are expected to inherit from std::false_type.
54 * Enums are converted to strings, so must be done one at a time.
55 */
56 template <typename T, typename Enable = void>
57 struct can_append_multiple :
58 std::conditional_t<std::is_enum_v<T>, std::false_type, std::true_type>
59 {};
60 // unix_fd's int needs to be wrapped.
61 template <>
62 struct can_append_multiple<unix_fd> : std::false_type
63 {};
64 // std::string needs a c_str() call.
65 template <>
66 struct can_append_multiple<std::string> : std::false_type
67 {};
68 // object_path needs a c_str() call.
69 template <>
70 struct can_append_multiple<object_path> : std::false_type
71 {};
72 // signature needs a c_str() call.
73 template <>
74 struct can_append_multiple<signature> : std::false_type
75 {};
76 // bool needs to be resized to int, per sdbus documentation.
77 template <>
78 struct can_append_multiple<bool> : std::false_type
79 {};
80 // std::vector/map/unordered_map/set need loops
81 template <typename T>
82 struct can_append_multiple<
83 T, typename std::enable_if_t<utility::has_const_iterator_v<T>>> :
84 std::false_type
85 {};
86 // std::pair needs to be broken down into components.
87 template <typename T1, typename T2>
88 struct can_append_multiple<std::pair<T1, T2>> : std::false_type
89 {};
90 // std::tuple needs to be broken down into components.
91 template <typename... Args>
92 struct can_append_multiple<std::tuple<Args...>> : std::false_type
93 {};
94 // variant needs to be broken down into components.
95 template <typename... Args>
96 struct can_append_multiple<std::variant<Args...>> : std::false_type
97 {};
98
99 template <typename... Args>
100 inline constexpr bool can_append_multiple_v =
101 can_append_multiple<Args...>::value;
102
103 /** @brief Utility to append a single C++ element into a sd_bus_message.
104 *
105 * User-defined types are expected to specialize this template in order to
106 * get their functionality.
107 *
108 * @tparam S - Type of element to append.
109 */
110 template <typename S, typename Enable = void>
111 struct append_single
112 {
113 // Downcast
114 template <typename T>
115 using Td = types::details::type_id_downcast_t<T>;
116
117 // sd_bus_message_append_basic expects a T* (cast to void*) for most types,
118 // so t& is appropriate. In the case of char*, it expects the void* is
119 // the char*. If we use &t, that is a char** and not a char*.
120 //
121 // Use these helper templates 'address_of(t)' in place of '&t' to
122 // handle both cases.
123 template <typename T>
address_of_helpersdbusplus::message::details::append_single124 static auto address_of_helper(T&& t, std::false_type)
125 {
126 return &t;
127 }
128 template <typename T>
address_of_helpersdbusplus::message::details::append_single129 static auto address_of_helper(T&& t, std::true_type)
130 {
131 return t;
132 }
133
134 template <typename T>
address_ofsdbusplus::message::details::append_single135 static auto address_of(T&& t)
136 {
137 return address_of_helper(std::forward<T>(t),
138 std::is_pointer<std::remove_reference_t<T>>());
139 }
140
141 /** @brief Do the operation to append element.
142 *
143 * @tparam T - Type of element to append.
144 *
145 * Template parameters T (function) and S (class) are different
146 * to allow the function to be utilized for many variants of S:
147 * S&, S&&, const S&, volatile S&, etc. The type_id_downcast is used
148 * to ensure T and S are equivalent. For 'char*', this also allows
149 * use for 'char[N]' types.
150 *
151 * @param[in] m - sd_bus_message to append into.
152 * @param[in] t - The item to append.
153 */
154 template <typename T>
155 static std::enable_if_t<std::is_same_v<S, Td<T>> && !std::is_enum_v<Td<T>>>
opsdbusplus::message::details::append_single156 op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
157 {
158 // For this default implementation, we need to ensure that only
159 // basic types are used.
160 static_assert(std::is_fundamental_v<Td<T>> ||
161 std::is_convertible_v<Td<T>, const char*>,
162 "Non-basic types are not allowed.");
163
164 constexpr auto dbusType = std::get<0>(types::type_id<T>());
165 intf->sd_bus_message_append_basic(m, dbusType,
166 address_of(std::forward<T>(t)));
167 }
168
169 template <typename T>
170 static std::enable_if_t<std::is_same_v<S, Td<T>> && std::is_enum_v<Td<T>>>
opsdbusplus::message::details::append_single171 op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
172 {
173 auto value = sdbusplus::message::convert_to_string<Td<T>>(t);
174 sdbusplus::message::append(intf, m, value);
175 }
176 };
177
178 template <typename T>
179 using append_single_t = append_single<types::details::type_id_downcast_t<T>>;
180
181 /** @brief Specialization of append_single for details::unix_fd. */
182 template <>
183 struct append_single<details::unix_fd_type>
184 {
185 template <typename T>
sanitizesdbusplus::message::details::append_single186 static void sanitize(const T&)
187 {}
188
189 template <typename T>
sanitizesdbusplus::message::details::append_single190 static void sanitize(T& s)
191 {
192 s.fd = -1;
193 }
194
195 template <typename T>
opsdbusplus::message::details::append_single196 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
197 {
198 constexpr auto dbusType = std::get<0>(types::type_id<T>());
199 intf->sd_bus_message_append_basic(m, dbusType, &s.fd);
200
201 // sd-bus now owns the file descriptor
202 sanitize(s);
203 }
204 };
205
206 /** @brief Specialization of append_single for std::strings. */
207 template <>
208 struct append_single<std::string>
209 {
210 template <typename T>
opsdbusplus::message::details::append_single211 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
212 {
213 constexpr auto dbusType = std::get<0>(types::type_id<T>());
214 intf->sd_bus_message_append_basic(m, dbusType, s.c_str());
215 }
216 };
217
218 /** @brief Specialization of append_single for std::string_views. */
219 template <>
220 struct append_single<std::string_view>
221 {
222 template <typename T>
opsdbusplus::message::details::append_single223 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
224 {
225 iovec iov{std::bit_cast<void*>(s.data()), s.size()};
226 intf->sd_bus_message_append_string_iovec(m, &iov, 1);
227 }
228 };
229
230 /** @brief Specialization of append_single for details::string_wrapper. */
231 template <>
232 struct append_single<details::string_wrapper>
233 {
234 template <typename S>
opsdbusplus::message::details::append_single235 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
236 {
237 constexpr auto dbusType = std::get<0>(types::type_id<S>());
238 intf->sd_bus_message_append_basic(m, dbusType, s.str.c_str());
239 }
240 };
241
242 /** @brief Specialization of append_single for details::string_wrapper. */
243 template <>
244 struct append_single<details::string_path_wrapper>
245 {
246 template <typename S>
opsdbusplus::message::details::append_single247 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
248 {
249 constexpr auto dbusType = std::get<0>(types::type_id<S>());
250 intf->sd_bus_message_append_basic(m, dbusType, s.str.c_str());
251 }
252 };
253
254 /** @brief Specialization of append_single for bool. */
255 template <>
256 struct append_single<bool>
257 {
258 template <typename T>
opsdbusplus::message::details::append_single259 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& b)
260 {
261 constexpr auto dbusType = std::get<0>(types::type_id<T>());
262 int i = b;
263 intf->sd_bus_message_append_basic(m, dbusType, &i);
264 }
265 };
266
267 /** @brief Specialization of append_single for containers (ie vector, array,
268 * set, map, etc) */
269 template <typename T>
270 struct append_single<T, std::enable_if_t<utility::has_const_iterator_v<T>>>
271 {
272 template <typename S>
opsdbusplus::message::details::append_single273 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
274 {
275 constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>());
276
277 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY,
278 dbusType.data() + 1);
279 for (auto&& i : s)
280 {
281 sdbusplus::message::append(intf, m, i);
282 }
283 intf->sd_bus_message_close_container(m);
284 }
285 };
286
287 /** @brief Specialization of append_single for std::pairs. */
288 template <typename T1, typename T2>
289 struct append_single<std::pair<T1, T2>>
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(
295 std::tuple_cat(types::type_id_nonull<T1>(), types::type_id<T2>()));
296
297 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY,
298 dbusType.data());
299 sdbusplus::message::append(intf, m, s.first, s.second);
300 intf->sd_bus_message_close_container(m);
301 }
302 };
303
304 /** @brief Specialization of append_single for std::tuples. */
305 template <typename... Args>
306 struct append_single<std::tuple<Args...>>
307 {
308 template <typename S, std::size_t... I>
_opsdbusplus::message::details::append_single309 static void _op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s,
310 std::integer_sequence<std::size_t, I...>)
311 {
312 sdbusplus::message::append(intf, m, std::get<I>(s)...);
313 }
314
315 template <typename S>
opsdbusplus::message::details::append_single316 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
317 {
318 constexpr auto dbusType = utility::tuple_to_array(std::tuple_cat(
319 types::type_id_nonull<Args...>(),
320 std::make_tuple('\0') /* null terminator for C-string */));
321
322 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT,
323 dbusType.data());
324 _op(intf, m, std::forward<S>(s),
325 std::make_index_sequence<sizeof...(Args)>());
326 intf->sd_bus_message_close_container(m);
327 }
328 };
329
330 /** @brief Specialization of append_single for std::variant. */
331 template <typename... Args>
332 struct append_single<std::variant<Args...>>
333 {
334 template <typename S, typename = std::enable_if_t<0 < sizeof...(Args)>>
opsdbusplus::message::details::append_single335 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
336 {
337 auto apply = [intf, m](auto&& arg) {
338 constexpr auto dbusType =
339 utility::tuple_to_array(types::type_id<decltype(arg)>());
340
341 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT,
342 dbusType.data());
343 sdbusplus::message::append(intf, m, arg);
344 intf->sd_bus_message_close_container(m);
345 };
346
347 std::visit(apply, s);
348 }
349 };
350
351 template <typename T>
tuple_item_append(sdbusplus::SdBusInterface * intf,sd_bus_message * m,T && t)352 void tuple_item_append(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
353 T&& t)
354 {
355 sdbusplus::message::append(intf, m, t);
356 }
357
358 template <int Index>
359 struct AppendHelper
360 {
361 template <typename... Fields>
opsdbusplus::message::details::AppendHelper362 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
363 std::tuple<Fields...> field_tuple)
364 {
365 auto field = std::get<Index - 1>(field_tuple);
366
367 AppendHelper<Index - 1>::op(intf, m, std::move(field_tuple));
368
369 tuple_item_append(intf, m, field);
370 }
371 };
372
373 template <>
374 struct AppendHelper<1>
375 {
376 template <typename... Fields>
opsdbusplus::message::details::AppendHelper377 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
378 std::tuple<Fields...> field_tuple)
379 {
380 tuple_item_append(intf, m, std::get<0>(field_tuple));
381 }
382 };
383
384 /** @brief Append a tuple of 2 or more entries into the sd_bus_message.
385 *
386 * @tparam Tuple - The tuple type to append.
387 * @param[in] t - The tuple to append.
388 *
389 * A tuple of 2 or more entries can be added as a set with
390 * sd_bus_message_append.
391 */
392 template <typename Tuple>
393 std::enable_if_t<2 <= std::tuple_size_v<Tuple>>
append_tuple(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t)394 append_tuple(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t)
395 {
396 // This was called because the tuple had at least 2 items in it.
397 AppendHelper<std::tuple_size_v<Tuple>>::op(intf, m, std::move(t));
398 }
399
400 /** @brief Append a tuple of exactly 1 entry into the sd_bus_message.
401 *
402 * @tparam Tuple - The tuple type to append.
403 * @param[in] t - The tuple to append.
404 *
405 * A tuple of 1 entry can be added with sd_bus_message_append_basic.
406 *
407 * Note: Some 1-entry tuples may need special handling due to
408 * can_append_multiple_v == false.
409 */
410 template <typename Tuple>
411 std::enable_if_t<1 == std::tuple_size_v<Tuple>>
append_tuple(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t)412 append_tuple(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t)
413 {
414 using itemType = decltype(std::get<0>(t));
415 append_single_t<itemType>::op(intf, m,
416 std::forward<itemType>(std::get<0>(t)));
417 }
418
419 /** @brief Append a tuple of 0 entries - no-op.
420 *
421 * This a no-op function that is useful due to variadic templates.
422 */
423 template <typename Tuple>
append_tuple(sdbusplus::SdBusInterface *,sd_bus_message *,Tuple &&)424 std::enable_if_t<0 == std::tuple_size_v<Tuple>> inline append_tuple(
425 sdbusplus::SdBusInterface* /*intf*/, sd_bus_message* /*m*/, Tuple&& /*t*/)
426 {}
427
428 /** @brief Group a sequence of C++ types for appending into an sd_bus_message.
429 * @tparam Tuple - A tuple of previously analyzed types.
430 * @tparam Arg - The argument to analyze for grouping.
431 *
432 * Specialization for when can_append_multiple_v<Arg> is true.
433 */
434 template <typename Tuple, typename Arg>
435 std::enable_if_t<can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
436 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
437 Tuple&& t, Arg&& arg);
438 /** @brief Group a sequence of C++ types for appending into an sd_bus_message.
439 * @tparam Tuple - A tuple of previously analyzed types.
440 * @tparam Arg - The argument to analyze for grouping.
441 *
442 * Specialization for when can_append_multiple_v<Arg> is false.
443 */
444 template <typename Tuple, typename Arg>
445 std::enable_if_t<
446 !can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
447 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
448 Tuple&& t, Arg&& arg);
449 /** @brief Group a sequence of C++ types for appending into an sd_bus_message.
450 * @tparam Tuple - A tuple of previously analyzed types.
451 * @tparam Arg - The argument to analyze for grouping.
452 * @tparam Rest - The remaining arguments left to analyze.
453 *
454 * Specialization for when can_append_multiple_v<Arg> is true.
455 */
456 template <typename Tuple, typename Arg, typename... Rest>
457 std::enable_if_t<can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
458 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
459 Tuple&& t, Arg&& arg, Rest&&... rest);
460 /** @brief Group a sequence of C++ types for appending into an sd_bus_message.
461 * @tparam Tuple - A tuple of previously analyzed types.
462 * @tparam Arg - The argument to analyze for grouping.
463 * @tparam Rest - The remaining arguments left to analyze.
464 *
465 * Specialization for when can_append_multiple_v<Arg> is false.
466 */
467 template <typename Tuple, typename Arg, typename... Rest>
468 std::enable_if_t<
469 !can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
470 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
471 Tuple&& t, Arg&& arg, Rest&&... rest);
472
473 template <typename Tuple, typename Arg>
474 std::enable_if_t<can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
append_grouping(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t,Arg && arg)475 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
476 Tuple&& t, Arg&& arg)
477 {
478 // Last element of a sequence and can_append_multiple, so add it to
479 // the tuple and call append_tuple.
480
481 append_tuple(intf, m,
482 std::tuple_cat(std::forward<Tuple>(t),
483 std::forward_as_tuple(std::forward<Arg>(arg))));
484 }
485
486 template <typename Tuple, typename Arg>
487 std::enable_if_t<
488 !can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
append_grouping(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t,Arg && arg)489 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
490 Tuple&& t, Arg&& arg)
491 {
492 // Last element of a sequence but !can_append_multiple, so call
493 // append_tuple on the previous elements and separately this single
494 // element.
495
496 append_tuple(intf, m, std::forward<Tuple>(t));
497 append_tuple(intf, m, std::forward_as_tuple(std::forward<Arg>(arg)));
498 }
499
500 template <typename Tuple, typename Arg, typename... Rest>
501 std::enable_if_t<can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
append_grouping(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t,Arg && arg,Rest &&...rest)502 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
503 Tuple&& t, Arg&& arg, Rest&&... rest)
504 {
505 // Not the last element of a sequence and can_append_multiple, so add it
506 // to the tuple and keep grouping.
507
508 append_grouping(
509 intf, m,
510 std::tuple_cat(std::forward<Tuple>(t),
511 std::forward_as_tuple(std::forward<Arg>(arg))),
512 std::forward<Rest>(rest)...);
513 }
514
515 template <typename Tuple, typename Arg, typename... Rest>
516 std::enable_if_t<
517 !can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
append_grouping(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t,Arg && arg,Rest &&...rest)518 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
519 Tuple&& t, Arg&& arg, Rest&&... rest)
520 {
521 // Not the last element of a sequence but !can_append_multiple, so call
522 // append_tuple on the previous elements and separately this single
523 // element and then group the remaining elements.
524
525 append_tuple(intf, m, std::forward<Tuple>(t));
526 append_tuple(intf, m, std::forward_as_tuple(std::forward<Arg>(arg)));
527 append_grouping(intf, m, std::make_tuple(), std::forward<Rest>(rest)...);
528 }
529
530 } // namespace details
531
532 template <typename... Args>
append(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Args &&...args)533 void append(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args)
534 {
535 details::append_grouping(intf, m, std::make_tuple(),
536 std::forward<Args>(args)...);
537 }
538
539 } // namespace message
540
541 } // namespace sdbusplus
542