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