1 #pragma once
2 
3 #include <systemd/sd-bus.h>
4 
5 #include <sdbusplus/exception.hpp>
6 #include <sdbusplus/message/types.hpp>
7 #include <sdbusplus/utility/tuple_to_array.hpp>
8 #include <sdbusplus/utility/type_traits.hpp>
9 
10 #include <string>
11 #include <tuple>
12 #include <type_traits>
13 #include <utility>
14 #include <variant>
15 
16 namespace sdbusplus
17 {
18 
19 namespace message
20 {
21 
22 /** @brief Read data from an sdbus message.
23  *
24  *  (This is an empty no-op function that is useful in some cases for
25  *   variadic template reasons.)
26  */
read(sdbusplus::SdBusInterface *,sd_bus_message *)27 inline void read(sdbusplus::SdBusInterface* /*intf*/, sd_bus_message* /*m*/) {}
28 /** @brief Read data from an sdbus message.
29  *
30  *  @param[in] m - The message to read from.
31  *  @tparam Args - C++ types of arguments to read from message.
32  *  @param[out] args - References to place contents read from message.
33  *
34  *  This function will, at compile-time, deduce the DBus types of the passed
35  *  C++ values and call the sd_bus_message_read functions with the
36  *  appropriate type parameters.  It may also do conversions, where needed,
37  *  to convert C++ types into C representations (eg. string, vector).
38  */
39 template <typename... Args>
40 void read(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args);
41 
42 namespace details
43 {
44 
45 /** @brief Utility to identify C++ types that may not be grouped into a
46  *         single sd_bus_message_read call and instead need special
47  *         handling.
48  *
49  *  @tparam T - Type for identification.
50  *
51  *  User-defined types are expected to inherit from std::false_type.
52  *  Enums are converted from strings, so must be done one at a time.
53  *
54  */
55 template <typename T, typename Enable = void>
56 struct can_read_multiple :
57     std::conditional_t<std::is_enum_v<T>, std::false_type, std::true_type>
58 {};
59 // unix_fd's int needs to be wrapped
60 template <>
61 struct can_read_multiple<unix_fd> : std::false_type
62 {};
63 // std::string needs a char* conversion.
64 template <>
65 struct can_read_multiple<std::string> : std::false_type
66 {};
67 // object_path needs a char* conversion.
68 template <>
69 struct can_read_multiple<object_path> : std::false_type
70 {};
71 // signature needs a char* conversion.
72 template <>
73 struct can_read_multiple<signature> : std::false_type
74 {};
75 // bool needs to be resized to int, per sdbus documentation.
76 template <>
77 struct can_read_multiple<bool> : std::false_type
78 {};
79 
80 // std::vector/map/unordered_vector/set need loops
81 template <typename T>
82 struct can_read_multiple<
83     T, typename std::enable_if_t<utility::has_emplace_method_v<T> ||
84                                  utility::has_emplace_back_method_v<T>>> :
85     std::false_type
86 {};
87 
88 // std::pair needs to be broken down into components.
89 template <typename T1, typename T2>
90 struct can_read_multiple<std::pair<T1, T2>> : std::false_type
91 {};
92 
93 // std::tuple needs to be broken down into components.
94 template <typename... Args>
95 struct can_read_multiple<std::tuple<Args...>> : std::false_type
96 {};
97 // variant needs to be broken down into components.
98 template <typename... Args>
99 struct can_read_multiple<std::variant<Args...>> : std::false_type
100 {};
101 
102 template <typename... Args>
103 inline constexpr bool can_read_multiple_v = can_read_multiple<Args...>::value;
104 
105 /** @brief Utility to read a single C++ element from a sd_bus_message.
106  *
107  *  User-defined types are expected to specialize this template in order to
108  *  get their functionality.
109  *
110  *  @tparam S - Type of element to read.
111  */
112 template <typename S>
113 struct read_single
114 {
115     // Downcast
116     template <typename T>
117     using Td = types::details::type_id_downcast_t<T>;
118 
119     /** @brief Do the operation to read element.
120      *
121      *  @tparam T - Type of element to read.
122      *
123      *  Template parameters T (function) and S (class) are different
124      *  to allow the function to be utilized for many variants of S:
125      *  S&, S&&, const S&, volatile S&, etc. The type_id_downcast is used
126      *  to ensure T and S are equivalent.  For 'char*', this also allows
127      *  use for 'char[N]' types.
128      *
129      *  @param[in] m - sd_bus_message to read from.
130      *  @param[out] t - The reference to read item into.
131      */
132     template <typename T>
opsdbusplus::message::details::read_single133     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
134         requires(!std::is_enum_v<Td<T>>)
135     {
136         // For this default implementation, we need to ensure that only
137         // basic types are used.
138         static_assert(std::is_fundamental_v<Td<T>> ||
139                           std::is_convertible_v<Td<T>, const char*> ||
140                           std::is_convertible_v<Td<T>, details::unix_fd_type>,
141                       "Non-basic types are not allowed.");
142 
143         constexpr auto dbusType = std::get<0>(types::type_id<T>());
144         int r = intf->sd_bus_message_read_basic(m, dbusType, &t);
145         if (r < 0)
146         {
147             throw exception::SdBusError(
148                 -r, "sd_bus_message_read_basic fundamental");
149         }
150     }
151 
152     template <typename T>
opsdbusplus::message::details::read_single153     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
154         requires(std::is_enum_v<Td<T>>)
155     {
156         std::string value{};
157         sdbusplus::message::read(intf, m, value);
158 
159         auto r = sdbusplus::message::convert_from_string<Td<T>>(value);
160         if (!r)
161         {
162             throw sdbusplus::exception::InvalidEnumString();
163         }
164         t = *r;
165     }
166 };
167 
168 template <typename T>
169 using read_single_t = read_single<types::details::type_id_downcast_t<T>>;
170 
171 /** @brief Specialization of read_single for various string class types.
172  *
173  *  Supports std::strings, details::string_wrapper and
174  *  details::string_path_wrapper.
175  */
176 template <typename S>
177     requires(std::is_same_v<S, std::string> ||
178              std::is_same_v<S, details::string_wrapper> ||
179              std::is_same_v<S, details::string_path_wrapper>)
180 struct read_single<S>
181 {
182     template <typename T>
opsdbusplus::message::details::read_single183     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
184     {
185         constexpr auto dbusType = std::get<0>(types::type_id<T>());
186         const char* str = nullptr;
187         int r = intf->sd_bus_message_read_basic(m, dbusType, &str);
188         if (r < 0)
189         {
190             throw exception::SdBusError(-r, "sd_bus_message_read_basic string");
191         }
192         t = S(str);
193     }
194 };
195 
196 /** @brief Specialization of read_single for bools. */
197 template <typename S>
198     requires(std::is_same_v<S, bool>)
199 struct read_single<S>
200 {
201     template <typename T>
opsdbusplus::message::details::read_single202     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
203     {
204         constexpr auto dbusType = std::get<0>(types::type_id<T>());
205         int i = 0;
206         int r = intf->sd_bus_message_read_basic(m, dbusType, &i);
207         if (r < 0)
208         {
209             throw exception::SdBusError(-r, "sd_bus_message_read_basic bool");
210         }
211         t = (i != 0);
212     }
213 };
214 
215 /** @brief Specialization of read_single for std::vectors. */
216 template <typename S>
217     requires(utility::has_emplace_back_method_v<S>)
218 struct read_single<S>
219 {
220     template <typename T>
opsdbusplus::message::details::read_single221     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
222     {
223         constexpr auto dbusType = utility::tuple_to_array(types::type_id<S>());
224         int r = intf->sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
225                                                      dbusType.data() + 1);
226         if (r < 0)
227         {
228             throw exception::SdBusError(
229                 -r, "sd_bus_message_enter_container emplace_back_container");
230         }
231 
232         while (!(r = intf->sd_bus_message_at_end(m, false)))
233         {
234             types::details::type_id_downcast_t<typename S::value_type> s;
235             sdbusplus::message::read(intf, m, s);
236             t.emplace_back(std::move(s));
237         }
238         if (r < 0)
239         {
240             throw exception::SdBusError(
241                 -r, "sd_bus_message_at_end emplace_back_container");
242         }
243 
244         r = intf->sd_bus_message_exit_container(m);
245         if (r < 0)
246         {
247             throw exception::SdBusError(
248                 -r, "sd_bus_message_exit_container emplace_back_container");
249         }
250     }
251 };
252 
253 /** @brief Specialization of read_single for std::map. */
254 template <typename S>
255     requires(utility::has_emplace_method_v<S>)
256 struct read_single<S>
257 {
258     template <typename T>
opsdbusplus::message::details::read_single259     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
260     {
261         constexpr auto dbusType = utility::tuple_to_array(types::type_id<S>());
262         int r = intf->sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
263                                                      dbusType.data() + 1);
264         if (r < 0)
265         {
266             throw exception::SdBusError(
267                 -r, "sd_bus_message_enter_container emplace_container");
268         }
269 
270         while (!(r = intf->sd_bus_message_at_end(m, false)))
271         {
272             types::details::type_id_downcast_t<typename S::value_type> s;
273             sdbusplus::message::read(intf, m, s);
274             t.emplace(std::move(s));
275         }
276         if (r < 0)
277         {
278             throw exception::SdBusError(
279                 -r, "sd_bus_message_at_end emplace_container");
280         }
281 
282         r = intf->sd_bus_message_exit_container(m);
283         if (r < 0)
284         {
285             throw exception::SdBusError(
286                 -r, "sd_bus_message_exit_container emplace_container");
287         }
288     }
289 };
290 
291 /** @brief Specialization of read_single for std::tuples and std::pairs. */
292 template <typename S>
293     requires requires(S& s) { std::get<0>(s); }
294 struct read_single<S>
295 {
296     template <typename T>
opsdbusplus::message::details::read_single297     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
298     {
299         constexpr auto dbusType =
300             utility::tuple_to_array(types::type_id_tuple<S>());
301 
302         // Tuples use TYPE_STRUCT, pair uses TYPE_DICT_ENTRY.
303         // Use the presence of `t.first` to determine if it is a pair.
304         constexpr auto tupleType = [&]() {
305             if constexpr (requires { t.first; })
306             {
307                 return SD_BUS_TYPE_DICT_ENTRY;
308             }
309             return SD_BUS_TYPE_STRUCT;
310         }();
311 
312         int r =
313             intf->sd_bus_message_enter_container(m, tupleType, dbusType.data());
314         if (r < 0)
315         {
316             throw exception::SdBusError(-r,
317                                         "sd_bus_message_enter_container tuple");
318         }
319 
320         std::apply(
321             [&](auto&... args) { sdbusplus::message::read(intf, m, args...); },
322             t);
323 
324         r = intf->sd_bus_message_exit_container(m);
325         if (r < 0)
326         {
327             throw exception::SdBusError(-r,
328                                         "sd_bus_message_exit_container tuple");
329         }
330     }
331 };
332 
333 /** @brief Specialization of read_single for std::variant. */
334 template <typename... Args>
335 struct read_single<std::variant<Args...>>
336 {
337     // Downcast
338     template <typename T>
339     using Td = types::details::type_id_downcast_t<T>;
340 
341     template <typename T, typename T1, typename... Args1>
readsdbusplus::message::details::read_single342     static void read(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
343     {
344         constexpr auto dbusType = utility::tuple_to_array(types::type_id<T1>());
345 
346         int r = intf->sd_bus_message_verify_type(m, SD_BUS_TYPE_VARIANT,
347                                                  dbusType.data());
348         if (r < 0)
349         {
350             throw exception::SdBusError(-r,
351                                         "sd_bus_message_verify_type variant");
352         }
353         if (!r)
354         {
355             if constexpr (sizeof...(Args1) == 0)
356             {
357                 r = intf->sd_bus_message_skip(m, "v");
358                 if (r < 0)
359                 {
360                     throw exception::SdBusError(-r,
361                                                 "sd_bus_message_skip variant");
362                 }
363                 t = std::remove_reference_t<T>{};
364             }
365             else
366             {
367                 read<T, Args1...>(intf, m, t);
368             }
369             return;
370         }
371 
372         r = intf->sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT,
373                                                  dbusType.data());
374         if (r < 0)
375         {
376             throw exception::SdBusError(
377                 -r, "sd_bus_message_enter_container variant");
378         }
379 
380         // If this type is an enum or string, we don't know which is the
381         // valid parsing.  Delegate to 'convert_from_string' so we do the
382         // correct conversion.
383         if constexpr (std::is_enum_v<Td<T1>> ||
384                       std::is_same_v<std::string, Td<T1>>)
385         {
386             std::string str{};
387             sdbusplus::message::read(intf, m, str);
388             auto ret =
389                 sdbusplus::message::convert_from_string<std::variant<Args...>>(
390                     str);
391 
392             if (!ret)
393             {
394                 throw sdbusplus::exception::InvalidEnumString();
395             }
396 
397             t = std::move(*ret);
398         }
399         else // otherwise, read it out directly.
400         {
401             std::remove_reference_t<T1> t1;
402             sdbusplus::message::read(intf, m, t1);
403             t = std::move(t1);
404         }
405 
406         r = intf->sd_bus_message_exit_container(m);
407         if (r < 0)
408         {
409             throw exception::SdBusError(
410                 -r, "sd_bus_message_exit_container variant");
411         }
412     }
413 
414     template <typename T>
opsdbusplus::message::details::read_single415     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
416     {
417         read<T, Args...>(intf, m, t);
418     }
419 };
420 
421 /** @brief Specialization of read_single for std::monostate. */
422 template <typename S>
423     requires(std::is_same_v<S, std::monostate>)
424 struct read_single<S>
425 {
426     template <typename T>
opsdbusplus::message::details::read_single427     static void op(sdbusplus::SdBusInterface*, sd_bus_message*, T&&)
428     {}
429 };
430 
431 template <typename T>
tuple_item_read(sdbusplus::SdBusInterface * intf,sd_bus_message * m,T && t)432 void tuple_item_read(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
433 {
434     sdbusplus::message::read(intf, m, t);
435 }
436 
437 template <int Index>
438 struct ReadHelper
439 {
440     template <typename... Fields>
opsdbusplus::message::details::ReadHelper441     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
442                    std::tuple<Fields...> field_tuple)
443     {
444         auto& field = std::get<Index - 1>(field_tuple);
445 
446         if constexpr (Index > 1)
447         {
448             ReadHelper<Index - 1>::op(intf, m, std::move(field_tuple));
449         }
450 
451         tuple_item_read(intf, m, field);
452     }
453 };
454 
455 /** @brief Read a tuple from the sd_bus_message.
456  *
457  *  @tparam Tuple - The tuple type to read.
458  *  @param[out] t - The tuple to read into.
459  *
460  *  A tuple of 2 or more entries can be read as a set with
461  *  sd_bus_message_read.
462  *
463  *  A tuple of 1 entry can be read with sd_bus_message_read_basic.
464  *
465  *  A tuple of 0 entries is a no-op.
466  *
467  */
468 template <typename Tuple>
read_tuple(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t)469 void read_tuple(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t)
470 {
471     if constexpr (std::tuple_size_v<Tuple> >= 2)
472     {
473         ReadHelper<std::tuple_size_v<Tuple>>::op(intf, m, std::move(t));
474     }
475     else if constexpr (std::tuple_size_v<Tuple> == 1)
476     {
477         using itemType = decltype(std::get<0>(t));
478         read_single_t<itemType>::op(intf, m,
479                                     std::forward<itemType>(std::get<0>(t)));
480     }
481 }
482 
483 /** @brief Group a sequence of C++ types for reading from an sd_bus_message.
484  *  @tparam Tuple - A tuple of previously analyzed types.
485  *  @tparam Arg - The argument to analyze for grouping.
486  *
487  *  Specialization for when can_read_multiple_v<Arg> is true.
488  */
489 template <typename Tuple, typename Arg>
490 std::enable_if_t<can_read_multiple_v<types::details::type_id_downcast_t<Arg>>>
491     read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t,
492                   Arg&& arg);
493 /** @brief Group a sequence of C++ types for reading from an sd_bus_message.
494  *  @tparam Tuple - A tuple of previously analyzed types.
495  *  @tparam Arg - The argument to analyze for grouping.
496  *
497  *  Specialization for when can_read_multiple_v<Arg> is false.
498  */
499 template <typename Tuple, typename Arg>
500 std::enable_if_t<!can_read_multiple_v<types::details::type_id_downcast_t<Arg>>>
501     read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t,
502                   Arg&& arg);
503 /** @brief Group a sequence of C++ types for reading from an sd_bus_message.
504  *  @tparam Tuple - A tuple of previously analyzed types.
505  *  @tparam Arg - The argument to analyze for grouping.
506  *  @tparam Rest - The remaining arguments left to analyze.
507  *
508  *  Specialization for when can_read_multiple_v<Arg> is true.
509  */
510 template <typename Tuple, typename Arg, typename... Rest>
511 std::enable_if_t<can_read_multiple_v<types::details::type_id_downcast_t<Arg>>>
512     read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t,
513                   Arg&& arg, Rest&&... rest);
514 /** @brief Group a sequence of C++ types for reading from an sd_bus_message.
515  *  @tparam Tuple - A tuple of previously analyzed types.
516  *  @tparam Arg - The argument to analyze for grouping.
517  *  @tparam Rest - The remaining arguments left to analyze.
518  *
519  *  Specialization for when can_read_multiple_v<Arg> is false.
520  */
521 template <typename Tuple, typename Arg, typename... Rest>
522 std::enable_if_t<!can_read_multiple_v<types::details::type_id_downcast_t<Arg>>>
523     read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t,
524                   Arg&& arg, Rest&&... rest);
525 
526 template <typename Tuple, typename Arg>
527 std::enable_if_t<can_read_multiple_v<types::details::type_id_downcast_t<Arg>>>
read_grouping(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t,Arg && arg)528     read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t,
529                   Arg&& arg)
530 {
531     // Last element of a sequence and can_read_multiple, so add it to
532     // the tuple and call read_tuple.
533 
534     read_tuple(intf, m,
535                std::tuple_cat(std::forward<Tuple>(t),
536                               std::forward_as_tuple(std::forward<Arg>(arg))));
537 }
538 
539 template <typename Tuple, typename Arg>
540 std::enable_if_t<!can_read_multiple_v<types::details::type_id_downcast_t<Arg>>>
read_grouping(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t,Arg && arg)541     read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t,
542                   Arg&& arg)
543 {
544     // Last element of a sequence but !can_read_multiple, so call
545     // read_tuple on the previous elements and separately this single
546     // element.
547 
548     read_tuple(intf, m, std::forward<Tuple>(t));
549     read_tuple(intf, m, std::forward_as_tuple(std::forward<Arg>(arg)));
550 }
551 
552 template <typename Tuple, typename Arg, typename... Rest>
553 std::enable_if_t<can_read_multiple_v<types::details::type_id_downcast_t<Arg>>>
read_grouping(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t,Arg && arg,Rest &&...rest)554     read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t,
555                   Arg&& arg, Rest&&... rest)
556 {
557     // Not the last element of a sequence and can_read_multiple, so add it
558     // to the tuple and keep grouping.
559 
560     read_grouping(intf, m,
561                   std::tuple_cat(std::forward<Tuple>(t),
562                                  std::forward_as_tuple(std::forward<Arg>(arg))),
563                   std::forward<Rest>(rest)...);
564 }
565 
566 template <typename Tuple, typename Arg, typename... Rest>
567 std::enable_if_t<!can_read_multiple_v<types::details::type_id_downcast_t<Arg>>>
read_grouping(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Tuple && t,Arg && arg,Rest &&...rest)568     read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t,
569                   Arg&& arg, Rest&&... rest)
570 {
571     // Not the last element of a sequence but !can_read_multiple, so call
572     // read_tuple on the previous elements and separately this single
573     // element and then group the remaining elements.
574 
575     read_tuple(intf, m, std::forward<Tuple>(t));
576     read_tuple(intf, m, std::forward_as_tuple(std::forward<Arg>(arg)));
577     read_grouping(intf, m, std::make_tuple(), std::forward<Rest>(rest)...);
578 }
579 
580 } // namespace details
581 
582 template <typename... Args>
read(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Args &&...args)583 void read(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args)
584 {
585     details::read_grouping(intf, m, std::make_tuple(),
586                            std::forward<Args>(args)...);
587 }
588 
589 } // namespace message
590 
591 } // namespace sdbusplus
592