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