xref: /openbmc/sdbusplus/include/sdbusplus/message/read.hpp (revision 7413ccc1740397239eee9ba79973c285bf6b3e5d)
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 read a single C++ element from a sd_bus_message.
46  *
47  *  User-defined types are expected to specialize this template in order to
48  *  get their functionality.
49  *
50  *  @tparam S - Type of element to read.
51  */
52 template <typename S>
53 struct read_single
54 {
55     // Downcast
56     template <typename T>
57     using Td = types::details::type_id_downcast_t<T>;
58 
59     /** @brief Do the operation to read element.
60      *
61      *  @tparam T - Type of element to read.
62      *
63      *  Template parameters T (function) and S (class) are different
64      *  to allow the function to be utilized for many variants of S:
65      *  S&, S&&, const S&, volatile S&, etc. The type_id_downcast is used
66      *  to ensure T and S are equivalent.  For 'char*', this also allows
67      *  use for 'char[N]' types.
68      *
69      *  @param[in] m - sd_bus_message to read from.
70      *  @param[out] t - The reference to read item into.
71      */
72     template <typename T>
opsdbusplus::message::details::read_single73     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
74         requires(!std::is_enum_v<Td<T>>)
75     {
76         // For this default implementation, we need to ensure that only
77         // basic types are used.
78         static_assert(std::is_fundamental_v<Td<T>> ||
79                           std::is_convertible_v<Td<T>, const char*> ||
80                           std::is_convertible_v<Td<T>, details::unix_fd_type>,
81                       "Non-basic types are not allowed.");
82 
83         constexpr auto dbusType = std::get<0>(types::type_id<T>());
84         int r = intf->sd_bus_message_read_basic(m, dbusType, &t);
85         if (r < 0)
86         {
87             throw exception::SdBusError(
88                 -r, "sd_bus_message_read_basic fundamental");
89         }
90     }
91 
92     template <typename T>
opsdbusplus::message::details::read_single93     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
94         requires(std::is_enum_v<Td<T>>)
95     {
96         std::string value{};
97         sdbusplus::message::read(intf, m, value);
98 
99         auto r = sdbusplus::message::convert_from_string<Td<T>>(value);
100         if (!r)
101         {
102             throw sdbusplus::exception::InvalidEnumString();
103         }
104         t = *r;
105     }
106 };
107 
108 template <typename T>
109 using read_single_t = read_single<types::details::type_id_downcast_t<T>>;
110 
111 /** @brief Specialization of read_single for various string class types.
112  *
113  *  Supports std::strings, details::string_wrapper and
114  *  details::string_path_wrapper.
115  */
116 template <typename S>
117     requires(std::is_same_v<S, std::string> ||
118              std::is_same_v<S, details::string_wrapper> ||
119              std::is_same_v<S, details::string_path_wrapper>)
120 struct read_single<S>
121 {
opsdbusplus::message::details::read_single122     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S& t)
123     {
124         constexpr auto dbusType = std::get<0>(types::type_id<S>());
125         const char* str = nullptr;
126         int r = intf->sd_bus_message_read_basic(m, dbusType, &str);
127         if (r < 0)
128         {
129             throw exception::SdBusError(-r, "sd_bus_message_read_basic string");
130         }
131         t = S(str);
132     }
133 };
134 
135 /** @brief Specialization of read_single for bools. */
136 template <typename S>
137     requires(std::is_same_v<S, bool>)
138 struct read_single<S>
139 {
opsdbusplus::message::details::read_single140     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S& t)
141     {
142         constexpr auto dbusType = std::get<0>(types::type_id<S>());
143         int i = 0;
144         int r = intf->sd_bus_message_read_basic(m, dbusType, &i);
145         if (r < 0)
146         {
147             throw exception::SdBusError(-r, "sd_bus_message_read_basic bool");
148         }
149         t = (i != 0);
150     }
151 };
152 
153 template <typename T>
154 concept can_read_array_non_contigious =
155     utility::has_emplace_back<T> && !utility::can_append_array_value<T>;
156 
157 /** @brief Specialization of read_single for std::vectors, with elements that
158  * are not an integral type.
159  */
160 template <can_read_array_non_contigious S>
161 struct read_single<S>
162 {
opsdbusplus::message::details::read_single163     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S& t)
164     {
165         constexpr auto dbusType = utility::tuple_to_array(types::type_id<S>());
166         int r = intf->sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
167                                                      dbusType.data() + 1);
168         if (r < 0)
169         {
170             throw exception::SdBusError(
171                 -r, "sd_bus_message_enter_container emplace_back_container");
172         }
173 
174         while (!(r = intf->sd_bus_message_at_end(m, false)))
175         {
176             types::details::type_id_downcast_t<typename S::value_type> s;
177             sdbusplus::message::read(intf, m, s);
178             t.emplace_back(std::move(s));
179         }
180         if (r < 0)
181         {
182             throw exception::SdBusError(
183                 -r, "sd_bus_message_at_end emplace_back_container");
184         }
185 
186         r = intf->sd_bus_message_exit_container(m);
187         if (r < 0)
188         {
189             throw exception::SdBusError(
190                 -r, "sd_bus_message_exit_container emplace_back_container");
191         }
192     }
193 };
194 
195 // Determines if fallback to normal iteration and append is required (can't use
196 // sd_bus_message_append_array)
197 template <typename T>
198 concept CanReadArrayOneShot =
199     utility::has_emplace_back<T> && utility::can_append_array_value<T>;
200 
201 /** @brief Specialization of read_single for std::vectors, when elements are
202  * an integral type
203  */
204 template <CanReadArrayOneShot S>
205 struct read_single<S>
206 {
207     template <typename T>
opsdbusplus::message::details::read_single208     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
209     {
210         size_t sizeInBytes = 0;
211         const void* p = nullptr;
212         constexpr auto dbusType = utility::tuple_to_array(types::type_id<S>());
213         int r =
214             intf->sd_bus_message_read_array(m, dbusType[1], &p, &sizeInBytes);
215         if (r < 0)
216         {
217             throw exception::SdBusError(-r, "sd_bus_message_read_array");
218         }
219         using ContainedType = typename S::value_type;
220         const ContainedType* begin = static_cast<const ContainedType*>(p);
221         t.insert(t.end(), begin, begin + (sizeInBytes / sizeof(ContainedType)));
222     }
223 };
224 
225 /** @brief Specialization of read_single for std::map. */
226 template <utility::has_emplace S>
227 struct read_single<S>
228 {
opsdbusplus::message::details::read_single229     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S& t)
230     {
231         constexpr auto dbusType = utility::tuple_to_array(types::type_id<S>());
232         int r = intf->sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
233                                                      dbusType.data() + 1);
234         if (r < 0)
235         {
236             throw exception::SdBusError(
237                 -r, "sd_bus_message_enter_container emplace_container");
238         }
239 
240         while (!(r = intf->sd_bus_message_at_end(m, false)))
241         {
242             types::details::type_id_downcast_t<typename S::value_type> s;
243             sdbusplus::message::read(intf, m, s);
244             t.emplace(std::move(s));
245         }
246         if (r < 0)
247         {
248             throw exception::SdBusError(
249                 -r, "sd_bus_message_at_end emplace_container");
250         }
251 
252         r = intf->sd_bus_message_exit_container(m);
253         if (r < 0)
254         {
255             throw exception::SdBusError(
256                 -r, "sd_bus_message_exit_container emplace_container");
257         }
258     }
259 };
260 
261 /** @brief Specialization of read_single for std::tuples and std::pairs. */
262 template <typename S>
263     requires requires(S& s) { std::get<0>(s); }
264 struct read_single<S>
265 {
opsdbusplus::message::details::read_single266     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S& t)
267     {
268         constexpr auto dbusType =
269             utility::tuple_to_array(types::type_id_tuple<S>());
270 
271         // Tuples use TYPE_STRUCT, pair uses TYPE_DICT_ENTRY.
272         // Use the presence of `t.first` to determine if it is a pair.
273         constexpr auto tupleType = [&]() {
274             if constexpr (requires { t.first; })
275             {
276                 return SD_BUS_TYPE_DICT_ENTRY;
277             }
278             return SD_BUS_TYPE_STRUCT;
279         }();
280 
281         int r =
282             intf->sd_bus_message_enter_container(m, tupleType, dbusType.data());
283         if (r < 0)
284         {
285             throw exception::SdBusError(-r,
286                                         "sd_bus_message_enter_container tuple");
287         }
288 
289         std::apply(
290             [&](auto&... args) { sdbusplus::message::read(intf, m, args...); },
291             t);
292 
293         r = intf->sd_bus_message_exit_container(m);
294         if (r < 0)
295         {
296             throw exception::SdBusError(-r,
297                                         "sd_bus_message_exit_container tuple");
298         }
299     }
300 };
301 
302 /** @brief Specialization of read_single for std::variant. */
303 template <typename... Args>
304 struct read_single<std::variant<Args...>>
305 {
306     // Downcast
307     template <typename T>
308     using Td = types::details::type_id_downcast_t<T>;
309 
310     template <typename T, typename T1, typename... Args1>
readsdbusplus::message::details::read_single311     static void read(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T& t)
312     {
313         constexpr auto dbusType = utility::tuple_to_array(types::type_id<T1>());
314 
315         int r = intf->sd_bus_message_verify_type(m, SD_BUS_TYPE_VARIANT,
316                                                  dbusType.data());
317         if (r < 0)
318         {
319             throw exception::SdBusError(-r,
320                                         "sd_bus_message_verify_type variant");
321         }
322         if (!r)
323         {
324             if constexpr (sizeof...(Args1) == 0)
325             {
326                 r = intf->sd_bus_message_skip(m, "v");
327                 if (r < 0)
328                 {
329                     throw exception::SdBusError(-r,
330                                                 "sd_bus_message_skip variant");
331                 }
332                 t = std::remove_reference_t<T>{};
333             }
334             else
335             {
336                 read<T, Args1...>(intf, m, t);
337             }
338             return;
339         }
340 
341         r = intf->sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT,
342                                                  dbusType.data());
343         if (r < 0)
344         {
345             throw exception::SdBusError(
346                 -r, "sd_bus_message_enter_container variant");
347         }
348 
349         // If this type is an enum or string, we don't know which is the
350         // valid parsing.  Delegate to 'convert_from_string' so we do the
351         // correct conversion.
352         if constexpr (std::is_enum_v<Td<T1>> ||
353                       std::is_same_v<std::string, Td<T1>>)
354         {
355             std::string str{};
356             sdbusplus::message::read(intf, m, str);
357             auto ret =
358                 sdbusplus::message::convert_from_string<std::variant<Args...>>(
359                     str);
360 
361             if (!ret)
362             {
363                 throw sdbusplus::exception::InvalidEnumString();
364             }
365 
366             t = std::move(*ret);
367         }
368         else // otherwise, read it out directly.
369         {
370             std::remove_reference_t<T1> t1;
371             sdbusplus::message::read(intf, m, t1);
372             t = std::move(t1);
373         }
374 
375         r = intf->sd_bus_message_exit_container(m);
376         if (r < 0)
377         {
378             throw exception::SdBusError(
379                 -r, "sd_bus_message_exit_container variant");
380         }
381     }
382 
opsdbusplus::message::details::read_single383     static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m,
384                    std::variant<Args...>& t)
385     {
386         read<std::variant<Args...>, Args...>(intf, m, t);
387     }
388 };
389 
390 /** @brief Specialization of read_single for std::monostate. */
391 template <typename S>
392     requires(std::is_same_v<S, std::monostate>)
393 struct read_single<S>
394 {
opsdbusplus::message::details::read_single395     static void op(sdbusplus::SdBusInterface*, sd_bus_message*, S&) {}
396 };
397 
398 } // namespace details
399 
400 template <typename... Args>
read(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Args &&...args)401 void read(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args)
402 {
403     (details::read_single_t<Args>::op(intf, m, args), ...);
404 }
405 
406 } // namespace message
407 
408 } // namespace sdbusplus
409