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 append a single C++ element into a sd_bus_message.
49 *
50 * User-defined types are expected to specialize this template in order to
51 * get their functionality.
52 *
53 * @tparam S - Type of element to append.
54 */
55 template <typename S, typename Enable = void>
56 struct append_single
57 {
58 // Downcast
59 template <typename T>
60 using Td = types::details::type_id_downcast_t<T>;
61
62 // sd_bus_message_append_basic expects a T* (cast to void*) for most types,
63 // so t& is appropriate. In the case of char*, it expects the void* is
64 // the char*. If we use &t, that is a char** and not a char*.
65 //
66 // Use these helper templates 'address_of(t)' in place of '&t' to
67 // handle both cases.
68 template <typename T>
address_of_helpersdbusplus::message::details::append_single69 static auto address_of_helper(T&& t, std::false_type)
70 {
71 return &t;
72 }
73 template <typename T>
address_of_helpersdbusplus::message::details::append_single74 static auto address_of_helper(T&& t, std::true_type)
75 {
76 return t;
77 }
78
79 template <typename T>
address_ofsdbusplus::message::details::append_single80 static auto address_of(T&& t)
81 {
82 return address_of_helper(std::forward<T>(t),
83 std::is_pointer<std::remove_reference_t<T>>());
84 }
85
86 /** @brief Do the operation to append element.
87 *
88 * @tparam T - Type of element to append.
89 *
90 * Template parameters T (function) and S (class) are different
91 * to allow the function to be utilized for many variants of S:
92 * S&, S&&, const S&, volatile S&, etc. The type_id_downcast is used
93 * to ensure T and S are equivalent. For 'char*', this also allows
94 * use for 'char[N]' types.
95 *
96 * @param[in] m - sd_bus_message to append into.
97 * @param[in] t - The item to append.
98 */
99 template <typename T>
100 static std::enable_if_t<std::is_same_v<S, Td<T>> && !std::is_enum_v<Td<T>>>
opsdbusplus::message::details::append_single101 op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
102 {
103 // For this default implementation, we need to ensure that only
104 // basic types are used.
105 static_assert(std::is_fundamental_v<Td<T>> ||
106 std::is_convertible_v<Td<T>, const char*>,
107 "Non-basic types are not allowed.");
108
109 constexpr auto dbusType = std::get<0>(types::type_id<T>());
110 intf->sd_bus_message_append_basic(m, dbusType,
111 address_of(std::forward<T>(t)));
112 }
113
114 template <typename T>
115 static std::enable_if_t<std::is_same_v<S, Td<T>> && std::is_enum_v<Td<T>>>
opsdbusplus::message::details::append_single116 op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t)
117 {
118 auto value = sdbusplus::message::convert_to_string<Td<T>>(t);
119 sdbusplus::message::append(intf, m, value);
120 }
121 };
122
123 template <typename T>
124 using append_single_t = append_single<types::details::type_id_downcast_t<T>>;
125
126 /** @brief Specialization of append_single for details::unix_fd. */
127 template <>
128 struct append_single<details::unix_fd_type>
129 {
130 template <typename T>
sanitizesdbusplus::message::details::append_single131 static void sanitize(const T&)
132 {}
133
134 template <typename T>
sanitizesdbusplus::message::details::append_single135 static void sanitize(T& s)
136 {
137 s.fd = -1;
138 }
139
140 template <typename T>
opsdbusplus::message::details::append_single141 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
142 {
143 constexpr auto dbusType = std::get<0>(types::type_id<T>());
144 intf->sd_bus_message_append_basic(m, dbusType, &s.fd);
145
146 // sd-bus now owns the file descriptor
147 sanitize(s);
148 }
149 };
150
151 /** @brief Specialization of append_single for std::strings. */
152 template <>
153 struct append_single<std::string>
154 {
155 template <typename T>
opsdbusplus::message::details::append_single156 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
157 {
158 constexpr auto dbusType = std::get<0>(types::type_id<T>());
159 intf->sd_bus_message_append_basic(m, dbusType, s.c_str());
160 }
161 };
162
163 /** @brief Specialization of append_single for std::string_views. */
164 template <>
165 struct append_single<std::string_view>
166 {
167 template <typename T>
opsdbusplus::message::details::append_single168 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& s)
169 {
170 iovec iov{std::bit_cast<void*>(s.data()), s.size()};
171 intf->sd_bus_message_append_string_iovec(m, &iov, 1);
172 }
173 };
174
175 /** @brief Specialization of append_single for details::string_wrapper. */
176 template <>
177 struct append_single<details::string_wrapper>
178 {
179 template <typename S>
opsdbusplus::message::details::append_single180 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
181 {
182 constexpr auto dbusType = std::get<0>(types::type_id<S>());
183 intf->sd_bus_message_append_basic(m, dbusType, s.str.c_str());
184 }
185 };
186
187 /** @brief Specialization of append_single for details::string_wrapper. */
188 template <>
189 struct append_single<details::string_path_wrapper>
190 {
191 template <typename S>
opsdbusplus::message::details::append_single192 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
193 {
194 constexpr auto dbusType = std::get<0>(types::type_id<S>());
195 intf->sd_bus_message_append_basic(m, dbusType, s.str.c_str());
196 }
197 };
198
199 /** @brief Specialization of append_single for bool. */
200 template <>
201 struct append_single<bool>
202 {
203 template <typename T>
opsdbusplus::message::details::append_single204 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& b)
205 {
206 constexpr auto dbusType = std::get<0>(types::type_id<T>());
207 int i = b;
208 intf->sd_bus_message_append_basic(m, dbusType, &i);
209 }
210 };
211
212 // Determines if fallback to normal iteration and append is required (can't use
213 // sd_bus_message_append_array)
214 template <typename T>
215 concept can_append_array_non_contigious =
216 utility::is_dbus_array<T> && !utility::can_append_array_value<T>;
217
218 /** @brief Specialization of append_single for containers
219 * (ie vector, array, etc), where the contiguous elements can be loaded in a
220 * single operation
221 */
222 template <can_append_array_non_contigious T>
223 struct append_single<T>
224 {
225 template <typename S>
opsdbusplus::message::details::append_single226 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
227 {
228 constexpr auto dbusType =
229 utility::tuple_to_array(types::type_id<typename T::value_type>());
230
231 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY,
232 dbusType.data());
233 for (const typename T::value_type& i : s)
234 {
235 sdbusplus::message::append(intf, m, i);
236 }
237 intf->sd_bus_message_close_container(m);
238 }
239 };
240
241 // Determines if the iterable type (vector, array) meets the requirements for
242 // using sd_bus_message_append_array
243 template <typename T>
244 concept can_append_array_contigious =
245 utility::is_dbus_array<T> && utility::can_append_array_value<T>;
246
247 /** @brief Specialization of append_single for vector and array T,
248 * with its elements is trivially copyable, and is an integral type,
249 * Bool is explicitly disallowed by sd-bus, so avoid it here */
250 template <can_append_array_contigious T>
251 struct append_single<T>
252 {
253 template <typename S>
opsdbusplus::message::details::append_single254 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
255 {
256 constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>());
257 intf->sd_bus_message_append_array(
258 m, dbusType[1], s.data(),
259 s.size() * sizeof(typename T::value_type));
260 }
261 };
262
263 /** @brief Specialization of append_single for std::pairs. */
264 template <typename T1, typename T2>
265 struct append_single<std::pair<T1, T2>>
266 {
267 template <typename S>
opsdbusplus::message::details::append_single268 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
269 {
270 constexpr auto dbusType = utility::tuple_to_array(
271 std::tuple_cat(types::type_id_nonull<T1>(), types::type_id<T2>()));
272
273 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY,
274 dbusType.data());
275 sdbusplus::message::append(intf, m, s.first, s.second);
276 intf->sd_bus_message_close_container(m);
277 }
278 };
279
280 /** @brief Specialization of append_single for std::tuples. */
281 template <typename... Args>
282 struct append_single<std::tuple<Args...>>
283 {
284 template <typename S, std::size_t... I>
_opsdbusplus::message::details::append_single285 static void _op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s,
286 std::integer_sequence<std::size_t, I...>)
287 {
288 sdbusplus::message::append(intf, m, std::get<I>(s)...);
289 }
290
291 template <typename S>
opsdbusplus::message::details::append_single292 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
293 {
294 constexpr auto dbusType = utility::tuple_to_array(std::tuple_cat(
295 types::type_id_nonull<Args...>(),
296 std::make_tuple('\0') /* null terminator for C-string */));
297
298 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT,
299 dbusType.data());
300 _op(intf, m, std::forward<S>(s),
301 std::make_index_sequence<sizeof...(Args)>());
302 intf->sd_bus_message_close_container(m);
303 }
304 };
305
306 /** @brief Specialization of append_single for std::variant. */
307 template <typename... Args>
308 struct append_single<std::variant<Args...>>
309 {
310 template <typename S, typename = std::enable_if_t<0 < sizeof...(Args)>>
opsdbusplus::message::details::append_single311 static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, S&& s)
312 {
313 auto apply = [intf, m](auto&& arg) {
314 constexpr auto dbusType =
315 utility::tuple_to_array(types::type_id<decltype(arg)>());
316
317 intf->sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT,
318 dbusType.data());
319 sdbusplus::message::append(intf, m, arg);
320 intf->sd_bus_message_close_container(m);
321 };
322
323 std::visit(apply, s);
324 }
325 };
326 } // namespace details
327
328 template <typename... Args>
append(sdbusplus::SdBusInterface * intf,sd_bus_message * m,Args &&...args)329 void append(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args)
330 {
331 (details::append_single_t<Args>::op(intf, m, args), ...);
332 }
333
334 } // namespace message
335
336 } // namespace sdbusplus
337