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