xref: /openbmc/sdbusplus/include/sdbusplus/message/append.hpp (revision 242677a2b3dafdb3b79464eb64e0ae8bc7c5a7d5)
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