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 identify C++ types that may not be grouped into a
49 * single sd_bus_message_append call and instead need special
50 * handling.
51 *
52 * @tparam T - Type for identification.
53 *
54 * User-defined types are expected to inherit from std::false_type.
55 * Enums are converted to strings, so must be done one at a time.
56 */
57 template <typename T, typename Enable = void>
58 struct can_append_multiple :
59 std::conditional_t<std::is_enum_v<T>, std::false_type, std::true_type>
60 {};
61 // unix_fd's int needs to be wrapped.
62 template <>
63 struct can_append_multiple<unix_fd> : std::false_type
64 {};
65 // std::string needs a c_str() call.
66 template <>
67 struct can_append_multiple<std::string> : std::false_type
68 {};
69 // object_path needs a c_str() call.
70 template <>
71 struct can_append_multiple<object_path> : std::false_type
72 {};
73 // signature needs a c_str() call.
74 template <>
75 struct can_append_multiple<signature> : std::false_type
76 {};
77 // bool needs to be resized to int, per sdbus documentation.
78 template <>
79 struct can_append_multiple<bool> : std::false_type
80 {};
81 // std::vector/map/unordered_map/set need loops
82 template <utility::is_dbus_array T>
83 struct can_append_multiple<T> : std::false_type
84 {};
85 // std::pair needs to be broken down into components.
86 template <typename T1, typename T2>
87 struct can_append_multiple<std::pair<T1, T2>> : std::false_type
88 {};
89 // std::tuple needs to be broken down into components.
90 template <typename... Args>
91 struct can_append_multiple<std::tuple<Args...>> : std::false_type
92 {};
93 // variant needs to be broken down into components.
94 template <typename... Args>
95 struct can_append_multiple<std::variant<Args...>> : std::false_type
96 {};
97
98 template <typename... Args>
99 inline constexpr bool can_append_multiple_v =
100 can_append_multiple<Args...>::value;
101
102 /** @brief Utility to append a single C++ element into a sd_bus_message.
103 *
104 * User-defined types are expected to specialize this template in order to
105 * get their functionality.
106 *
107 * @tparam S - Type of element to append.
108 */
109 template <typename S, typename Enable = void>
110 struct append_single
111 {
112 // Downcast
113 template <typename T>
114 using Td = types::details::type_id_downcast_t<T>;
115
116 // sd_bus_message_append_basic expects a T* (cast to void*) for most types,
117 // so t& is appropriate. In the case of char*, it expects the void* is
118 // the char*. If we use &t, that is a char** and not a char*.
119 //
120 // Use these helper templates 'address_of(t)' in place of '&t' to
121 // handle both cases.
122 template <typename T>
address_of_helpersdbusplus::message::details::append_single123 static auto address_of_helper(T&& t, std::false_type)
124 {
125 return &t;
126 }
127 template <typename T>
address_of_helpersdbusplus::message::details::append_single128 static auto address_of_helper(T&& t, std::true_type)
129 {
130 return t;
131 }
132
133 template <typename T>
address_ofsdbusplus::message::details::append_single134 static auto address_of(T&& t)
135 {
136 return address_of_helper(std::forward<T>(t),
137 std::is_pointer<std::remove_reference_t<T>>());
138 }
139
140 /** @brief Do the operation to append element.
141 *
142 * @tparam T - Type of element to append.
143 *
144 * Template parameters T (function) and S (class) are different
145 * to allow the function to be utilized for many variants of S:
146 * S&, S&&, const S&, volatile S&, etc. The type_id_downcast is used
147 * to ensure T and S are equivalent. For 'char*', this also allows
148 * use for 'char[N]' types.
149 *
150 * @param[in] m - sd_bus_message to append into.
151 * @param[in] t - The item to append.
152 */
153 template <typename T>
154 static std::enable_if_t<std::is_same_v<S, Td<T>> && !std::is_enum_v<Td<T>>>
opsdbusplus::message::details::append_single155 op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
156 {
157 // For this default implementation, we need to ensure that only
158 // basic types are used.
159 static_assert(std::is_fundamental_v<Td<T>> ||
160 std::is_convertible_v<Td<T>, const char*>,
161 "Non-basic types are not allowed.");
162
163 constexpr auto dbusType = std::get<0>(types::type_id<T>());
164 intf->sd_bus_message_append_basic(m, dbusType,
165 address_of(std::forward<T>(t)));
166 }
167
168 template <typename T>
169 static std::enable_if_t<std::is_same_v<S, Td<T>> && std::is_enum_v<Td<T>>>
opsdbusplus::message::details::append_single170 op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
171 {
172 auto value = sdbusplus::message::convert_to_string<Td<T>>(t);
173 sdbusplus::message::append(intf, m, value);
174 }
175 };
176
177 template <typename T>
178 using append_single_t = append_single<types::details::type_id_downcast_t<T>>;
179
180 /** @brief Specialization of append_single for details::unix_fd. */
181 template <>
182 struct append_single<details::unix_fd_type>
183 {
184 template <typename T>
sanitizesdbusplus::message::details::append_single185 static void sanitize(const T&)
186 {}
187
188 template <typename T>
sanitizesdbusplus::message::details::append_single189 static void sanitize(T& s)
190 {
191 s.fd = -1;
192 }
193
194 template <typename T>
opsdbusplus::message::details::append_single195 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
196 {
197 constexpr auto dbusType = std::get<0>(types::type_id<T>());
198 intf->sd_bus_message_append_basic(m, dbusType, &s.fd);
199
200 // sd-bus now owns the file descriptor
201 sanitize(s);
202 }
203 };
204
205 /** @brief Specialization of append_single for std::strings. */
206 template <>
207 struct append_single<std::string>
208 {
209 template <typename T>
opsdbusplus::message::details::append_single210 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
211 {
212 constexpr auto dbusType = std::get<0>(types::type_id<T>());
213 intf->sd_bus_message_append_basic(m, dbusType, s.c_str());
214 }
215 };
216
217 /** @brief Specialization of append_single for std::string_views. */
218 template <>
219 struct append_single<std::string_view>
220 {
221 template <typename T>
opsdbusplus::message::details::append_single222 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
223 {
224 iovec iov{std::bit_cast<void*>(s.data()), s.size()};
225 intf->sd_bus_message_append_string_iovec(m, &iov, 1);
226 }
227 };
228
229 /** @brief Specialization of append_single for details::string_wrapper. */
230 template <>
231 struct append_single<details::string_wrapper>
232 {
233 template <typename S>
opsdbusplus::message::details::append_single234 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
235 {
236 constexpr auto dbusType = std::get<0>(types::type_id<S>());
237 intf->sd_bus_message_append_basic(m, dbusType, s.str.c_str());
238 }
239 };
240
241 /** @brief Specialization of append_single for details::string_wrapper. */
242 template <>
243 struct append_single<details::string_path_wrapper>
244 {
245 template <typename S>
opsdbusplus::message::details::append_single246 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
247 {
248 constexpr auto dbusType = std::get<0>(types::type_id<S>());
249 intf->sd_bus_message_append_basic(m, dbusType, s.str.c_str());
250 }
251 };
252
253 /** @brief Specialization of append_single for bool. */
254 template <>
255 struct append_single<bool>
256 {
257 template <typename T>
opsdbusplus::message::details::append_single258 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& b)
259 {
260 constexpr auto dbusType = std::get<0>(types::type_id<T>());
261 int i = b;
262 intf->sd_bus_message_append_basic(m, dbusType, &i);
263 }
264 };
265
266 // Determines if fallback to normal iteration and append is required (can't use
267 // sd_bus_message_append_array)
268 template <typename T>
269 concept can_append_array_non_contigious =
270 utility::is_dbus_array<T> && !utility::can_append_array_value<T>;
271
272 /** @brief Specialization of append_single for containers
273 * (ie vector, array, etc), where the contiguous elements can be loaded in a
274 * single operation
275 */
276 template <can_append_array_non_contigious T>
277 struct append_single<T>
278 {
279 template <typename S>
opsdbusplus::message::details::append_single280 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
281 {
282 constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>());
283
284 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY,
285 dbusType.data() + 1);
286 for (auto&& i : s)
287 {
288 sdbusplus::message::append(intf, m, i);
289 }
290 intf->sd_bus_message_close_container(m);
291 }
292 };
293
294 // Determines if the iterable type (vector, array) meets the requirements for
295 // using sd_bus_message_append_array
296 template <typename T>
297 concept can_append_array_contigious =
298 utility::is_dbus_array<T> && utility::can_append_array_value<T>;
299
300 /** @brief Specialization of append_single for vector and array T,
301 * with its elements is trivially copyable, and is an integral type,
302 * Bool is explicitly disallowed by sd-bus, so avoid it here */
303 template <can_append_array_contigious T>
304 struct append_single<T>
305 {
306 template <typename S>
opsdbusplus::message::details::append_single307 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
308 {
309 constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>());
310 intf->sd_bus_message_append_array(
311 m, dbusType[1], s.data(),
312 s.size() * sizeof(typename T::value_type));
313 }
314 };
315
316 /** @brief Specialization of append_single for std::pairs. */
317 template <typename T1, typename T2>
318 struct append_single<std::pair<T1, T2>>
319 {
320 template <typename S>
opsdbusplus::message::details::append_single321 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
322 {
323 constexpr auto dbusType = utility::tuple_to_array(
324 std::tuple_cat(types::type_id_nonull<T1>(), types::type_id<T2>()));
325
326 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY,
327 dbusType.data());
328 sdbusplus::message::append(intf, m, s.first, s.second);
329 intf->sd_bus_message_close_container(m);
330 }
331 };
332
333 /** @brief Specialization of append_single for std::tuples. */
334 template <typename... Args>
335 struct append_single<std::tuple<Args...>>
336 {
337 template <typename S, std::size_t... I>
_opsdbusplus::message::details::append_single338 static void _op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s,
339 std::integer_sequence<std::size_t, I...>)
340 {
341 sdbusplus::message::append(intf, m, std::get<I>(s)...);
342 }
343
344 template <typename S>
opsdbusplus::message::details::append_single345 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
346 {
347 constexpr auto dbusType = utility::tuple_to_array(std::tuple_cat(
348 types::type_id_nonull<Args...>(),
349 std::make_tuple('\0') /* null terminator for C-string */));
350
351 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT,
352 dbusType.data());
353 _op(intf, m, std::forward<S>(s),
354 std::make_index_sequence<sizeof...(Args)>());
355 intf->sd_bus_message_close_container(m);
356 }
357 };
358
359 /** @brief Specialization of append_single for std::variant. */
360 template <typename... Args>
361 struct append_single<std::variant<Args...>>
362 {
363 template <typename S, typename = std::enable_if_t<0 < sizeof...(Args)>>
opsdbusplus::message::details::append_single364 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
365 {
366 auto apply = [intf, m](auto&& arg) {
367 constexpr auto dbusType =
368 utility::tuple_to_array(types::type_id<decltype(arg)>());
369
370 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT,
371 dbusType.data());
372 sdbusplus::message::append(intf, m, arg);
373 intf->sd_bus_message_close_container(m);
374 };
375
376 std::visit(apply, s);
377 }
378 };
379
380 template <typename T>
tuple_item_append(sdbusplus::SdBusInterface * intf,sd_bus_message * m,T && t)381 void tuple_item_append(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
382 T&& t)
383 {
384 sdbusplus::message::append(intf, m, t);
385 }
386
387 template <int Index>
388 struct AppendHelper
389 {
390 template <typename... Fields>
opsdbusplus::message::details::AppendHelper391 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
392 std::tuple<Fields...> field_tuple)
393 {
394 auto field = std::get<Index - 1>(field_tuple);
395
396 AppendHelper<Index - 1>::op(intf, m, std::move(field_tuple));
397
398 tuple_item_append(intf, m, field);
399 }
400 };
401
402 template <>
403 struct AppendHelper<1>
404 {
405 template <typename... Fields>
opsdbusplus::message::details::AppendHelper406 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
407 std::tuple<Fields...> field_tuple)
408 {
409 tuple_item_append(intf, m, std::get<0>(field_tuple));
410 }
411 };
412
413 /** @brief Append a tuple of 2 or more entries into the sd_bus_message.
414 *
415 * @tparam Tuple - The tuple type to append.
416 * @param[in] t - The tuple to append.
417 *
418 * A tuple of 2 or more entries can be added as a set with
419 * sd_bus_message_append.
420 */
421 template <typename Tuple>
append_tuple(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t)422 std::enable_if_t<2 <= std::tuple_size_v<Tuple>> append_tuple(
423 sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t)
424 {
425 // This was called because the tuple had at least 2 items in it.
426 AppendHelper<std::tuple_size_v<Tuple>>::op(intf, m, std::move(t));
427 }
428
429 /** @brief Append a tuple of exactly 1 entry into the sd_bus_message.
430 *
431 * @tparam Tuple - The tuple type to append.
432 * @param[in] t - The tuple to append.
433 *
434 * A tuple of 1 entry can be added with sd_bus_message_append_basic.
435 *
436 * Note: Some 1-entry tuples may need special handling due to
437 * can_append_multiple_v == false.
438 */
439 template <typename Tuple>
append_tuple(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t)440 std::enable_if_t<1 == std::tuple_size_v<Tuple>> append_tuple(
441 sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t)
442 {
443 using itemType = decltype(std::get<0>(t));
444 append_single_t<itemType>::op(intf, m,
445 std::forward<itemType>(std::get<0>(t)));
446 }
447
448 /** @brief Append a tuple of 0 entries - no-op.
449 *
450 * This a no-op function that is useful due to variadic templates.
451 */
452 template <typename Tuple>
append_tuple(sdbusplus::SdBusInterface *,sd_bus_message *,Tuple &&)453 std::enable_if_t<0 == std::tuple_size_v<Tuple>> inline append_tuple(
454 sdbusplus::SdBusInterface* /*intf*/, sd_bus_message* /*m*/, Tuple&& /*t*/)
455 {}
456
457 /** @brief Group a sequence of C++ types for appending into an sd_bus_message.
458 * @tparam Tuple - A tuple of previously analyzed types.
459 * @tparam Arg - The argument to analyze for grouping.
460 *
461 * Specialization for when can_append_multiple_v<Arg> is true.
462 */
463 template <typename Tuple, typename Arg>
464 std::enable_if_t<can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
465 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
466 Tuple&& t, Arg&& arg);
467 /** @brief Group a sequence of C++ types for appending into an sd_bus_message.
468 * @tparam Tuple - A tuple of previously analyzed types.
469 * @tparam Arg - The argument to analyze for grouping.
470 *
471 * Specialization for when can_append_multiple_v<Arg> is false.
472 */
473 template <typename Tuple, typename Arg>
474 std::enable_if_t<
475 !can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
476 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
477 Tuple&& t, Arg&& arg);
478 /** @brief Group a sequence of C++ types for appending into an sd_bus_message.
479 * @tparam Tuple - A tuple of previously analyzed types.
480 * @tparam Arg - The argument to analyze for grouping.
481 * @tparam Rest - The remaining arguments left to analyze.
482 *
483 * Specialization for when can_append_multiple_v<Arg> is true.
484 */
485 template <typename Tuple, typename Arg, typename... Rest>
486 std::enable_if_t<can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
487 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
488 Tuple&& t, Arg&& arg, Rest&&... rest);
489 /** @brief Group a sequence of C++ types for appending into an sd_bus_message.
490 * @tparam Tuple - A tuple of previously analyzed types.
491 * @tparam Arg - The argument to analyze for grouping.
492 * @tparam Rest - The remaining arguments left to analyze.
493 *
494 * Specialization for when can_append_multiple_v<Arg> is false.
495 */
496 template <typename Tuple, typename Arg, typename... Rest>
497 std::enable_if_t<
498 !can_append_multiple_v<types::details::type_id_downcast_t<Arg>>>
499 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
500 Tuple&& t, Arg&& arg, Rest&&... rest);
501
502 template <typename Tuple, typename Arg>
503 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)504 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
505 Tuple&& t, Arg&& arg)
506 {
507 // Last element of a sequence and can_append_multiple, so add it to
508 // the tuple and call append_tuple.
509
510 append_tuple(intf, m,
511 std::tuple_cat(std::forward<Tuple>(t),
512 std::forward_as_tuple(std::forward<Arg>(arg))));
513 }
514
515 template <typename Tuple, typename Arg>
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)518 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
519 Tuple&& t, Arg&& arg)
520 {
521 // Last element of a sequence but !can_append_multiple, so call
522 // append_tuple on the previous elements and separately this single
523 // element.
524
525 append_tuple(intf, m, std::forward<Tuple>(t));
526 append_tuple(intf, m, std::forward_as_tuple(std::forward<Arg>(arg)));
527 }
528
529 template <typename Tuple, typename Arg, typename... Rest>
530 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)531 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
532 Tuple&& t, Arg&& arg, Rest&&... rest)
533 {
534 // Not the last element of a sequence and can_append_multiple, so add it
535 // to the tuple and keep grouping.
536
537 append_grouping(
538 intf, m,
539 std::tuple_cat(std::forward<Tuple>(t),
540 std::forward_as_tuple(std::forward<Arg>(arg))),
541 std::forward<Rest>(rest)...);
542 }
543
544 template <typename Tuple, typename Arg, typename... Rest>
545 std::enable_if_t<
546 !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)547 append_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
548 Tuple&& t, Arg&& arg, Rest&&... rest)
549 {
550 // Not the last element of a sequence but !can_append_multiple, so call
551 // append_tuple on the previous elements and separately this single
552 // element and then group the remaining elements.
553
554 append_tuple(intf, m, std::forward<Tuple>(t));
555 append_tuple(intf, m, std::forward_as_tuple(std::forward<Arg>(arg)));
556 append_grouping(intf, m, std::make_tuple(), std::forward<Rest>(rest)...);
557 }
558
559 } // namespace details
560
561 template <typename... Args>
append(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Args &&...args)562 void append(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args)
563 {
564 details::append_grouping(intf, m, std::make_tuple(),
565 std::forward<Args>(args)...);
566 }
567
568 } // namespace message
569
570 } // namespace sdbusplus
571