1 #pragma once
2
3 #include <systemd/sd-bus.h>
4
5 #include <sdbusplus/exception.hpp>
6 #include <sdbusplus/message/append.hpp>
7 #include <sdbusplus/message/native_types.hpp>
8 #include <sdbusplus/message/read.hpp>
9 #include <sdbusplus/sdbus.hpp>
10 #include <sdbusplus/slot.hpp>
11
12 #include <exception>
13 #include <memory>
14 #include <optional>
15 #include <tuple>
16 #include <type_traits>
17 #include <utility>
18
19 #ifdef __clang__
20 #pragma clang diagnostic push
21 #pragma clang diagnostic ignored "-Wc99-extensions"
22 #endif
23
24 namespace sdbusplus
25 {
26
27 // Forward declare sdbusplus::bus::bus for 'friend'ship.
28 namespace bus
29 {
30 struct bus;
31 }
32
33 namespace message
34 {
35
36 using msgp_t = sd_bus_message*;
37 class message;
38
39 namespace details
40 {
41
42 /** @brief unique_ptr functor to release a msg reference. */
43 // TODO(venture): Consider using template <SdBusInterfaceType> for this so that
44 // it doesn't require creating a specific instance of it, unless that's ok --
45 struct MsgDeleter
46 {
operator ()sdbusplus::message::details::MsgDeleter47 void operator()(msgp_t ptr) const
48 {
49 sd_bus_message_unref(ptr);
50 }
51 };
52
53 /* @brief Alias 'msg' to a unique_ptr type for auto-release. */
54 using msg = std::unique_ptr<sd_bus_message, MsgDeleter>;
55
56 template <typename CbT>
57 int call_async_cb(sd_bus_message* m, void* userdata, sd_bus_error*) noexcept;
58
59 template <typename CbT>
call_async_del(void * userdata)60 void call_async_del(void* userdata) noexcept
61 {
62 delete reinterpret_cast<CbT*>(userdata);
63 }
64
65 } // namespace details
66
67 /** @class message
68 * @brief Provides C++ bindings to the sd_bus_message_* class functions.
69 */
70 class message : private sdbusplus::slot::details::slot_friend
71 {
72 /* Define all of the basic class operations:
73 * Allowed:
74 * - Default constructor
75 * - Copy operations.
76 * - Move operations.
77 * - Destructor.
78 */
79 public:
80 message(message&&) = default;
81 message& operator=(message&&) = default;
82 ~message() = default;
83
message(msgp_t m,sdbusplus::SdBusInterface * intf)84 message(msgp_t m, sdbusplus::SdBusInterface* intf) :
85 _intf(std::move(intf)), _msg(_intf->sd_bus_message_ref(m))
86 {}
87
88 /** @brief Conversion constructor for 'msgp_t'.
89 *
90 * Takes increment ref-count of the msg-pointer and release when
91 * destructed.
92 */
message(msgp_t m=nullptr)93 explicit message(msgp_t m = nullptr) : message(m, &sdbus_impl) {}
94
message(msgp_t m,sdbusplus::SdBusInterface * intf,std::false_type)95 message(msgp_t m, sdbusplus::SdBusInterface* intf, std::false_type) :
96 _intf(intf), _msg(m)
97 {}
98
99 /** @brief Constructor for 'msgp_t'.
100 *
101 * Takes ownership of the msg-pointer and releases it when done.
102 */
message(msgp_t m,std::false_type)103 message(msgp_t m, std::false_type) : _intf(&sdbus_impl), _msg(m) {}
104
105 /** @brief Copy constructor for 'message'.
106 *
107 * Copies the message class and increments the ref on _msg
108 */
message(const message & other)109 message(const message& other) :
110 _intf(other._intf), _msg(sd_bus_message_ref(other._msg.get()))
111 {}
112
113 /** @brief Assignment operator for 'message'.
114 *
115 * Copies the message class and increments the ref on _msg
116 */
operator =(const message & other)117 message& operator=(const message& other)
118 {
119 _msg.reset(sd_bus_message_ref(other._msg.get()));
120 _intf = other._intf;
121 return *this;
122 }
123
124 /** @brief Release ownership of the stored msg-pointer. */
release()125 msgp_t release()
126 {
127 return _msg.release();
128 }
129
130 /** @brief Check if message contains a real pointer. (non-nullptr). */
operator bool() const131 explicit operator bool() const
132 {
133 return bool(_msg);
134 }
135
136 /** @brief Perform sd_bus_message_append, with automatic type deduction.
137 *
138 * @tparam Args - Type of items to append to message.
139 * @param[in] args - Items to append to message.
140 */
141 template <typename... Args>
append(Args &&...args)142 void append(Args&&... args)
143 {
144 sdbusplus::message::append(_intf, _msg.get(),
145 std::forward<Args>(args)...);
146 }
147
148 /** @brief Perform sd_bus_message_read, with automatic type deduction.
149 *
150 * @tparam Args - Type of items to read from message.
151 * @param[out] args - Items to read from message.
152 */
153 template <typename... Args>
read(Args &&...args)154 void read(Args&&... args)
155 {
156 sdbusplus::message::read(_intf, _msg.get(),
157 std::forward<Args>(args)...);
158 }
159
160 /** @brief Perform sd_bus_message_read with results returned.
161 *
162 * @tparam Args - Type of items to read from the message.
163 * @return One of { void, Args, std::tuple<Args...> }.
164 */
165 template <typename... Args>
unpack()166 auto unpack()
167 {
168 if constexpr (sizeof...(Args) == 0)
169 {
170 return;
171 }
172 else if constexpr (sizeof...(Args) == 1)
173 {
174 std::tuple_element_t<0, std::tuple<Args...>> r{};
175 read(r);
176 return r;
177 }
178 else
179 {
180 std::tuple<Args...> r{};
181 std::apply([this](auto&&... v) { this->read(v...); }, r);
182 return r;
183 }
184 }
185
186 /** @brief Get the dbus bus from the message. */
187 // Forward declare.
188 bus::bus get_bus() const;
189
190 /** @brief Get the signature of a message.
191 *
192 * @return A [weak] pointer to the signature of the message.
193 */
get_signature() const194 const char* get_signature() const
195 {
196 return _intf->sd_bus_message_get_signature(_msg.get(), true);
197 }
198
199 /** @brief Get the path of a message.
200 *
201 * @return A [weak] pointer to the path of the message.
202 */
get_path() const203 const char* get_path() const
204 {
205 return _intf->sd_bus_message_get_path(_msg.get());
206 }
207
208 /** @brief Get the interface of a message.
209 *
210 * @return A [weak] pointer to the interface of the message.
211 */
get_interface() const212 const char* get_interface() const
213 {
214 return _intf->sd_bus_message_get_interface(_msg.get());
215 }
216
217 /** @brief Get the member of a message.
218 *
219 * @return A [weak] pointer to the member of the message.
220 */
get_member() const221 const char* get_member() const
222 {
223 return _intf->sd_bus_message_get_member(_msg.get());
224 }
225
226 /** @brief Get the destination of a message.
227 *
228 * @return A [weak] pointer to the destination of the message.
229 */
get_destination() const230 const char* get_destination() const
231 {
232 return _intf->sd_bus_message_get_destination(_msg.get());
233 }
234
235 /** @brief Get the sender of a message.
236 *
237 * @return A [weak] pointer to the sender of the message.
238 */
get_sender() const239 const char* get_sender() const
240 {
241 return _intf->sd_bus_message_get_sender(_msg.get());
242 }
243
244 /** @brief Check if message is a method error.
245 *
246 * @return True - if message is a method error.
247 */
is_method_error() const248 bool is_method_error() const
249 {
250 return _intf->sd_bus_message_is_method_error(_msg.get(), nullptr);
251 }
252
253 /** @brief Get the errno from the message.
254 *
255 * @return The errno of the message.
256 */
get_errno() const257 int get_errno() const
258 {
259 return _intf->sd_bus_message_get_errno(_msg.get());
260 }
261
262 /** @brief Get the error from the message.
263 *
264 * @return The error of the message.
265 */
get_error() const266 const sd_bus_error* get_error() const
267 {
268 return _intf->sd_bus_message_get_error(_msg.get());
269 }
270
271 /** @brief Get the type of a message.
272 *
273 * @return The type of message.
274 */
get_type() const275 auto get_type() const
276 {
277 uint8_t type;
278 int r = _intf->sd_bus_message_get_type(_msg.get(), &type);
279 if (r < 0)
280 {
281 throw exception::SdBusError(-r, "sd_bus_message_get_type");
282 }
283 return type;
284 }
285
286 /** @brief Get the transaction cookie of a message.
287 *
288 * @return The transaction cookie of a message.
289 */
get_cookie() const290 auto get_cookie() const
291 {
292 uint64_t cookie;
293 int r = _intf->sd_bus_message_get_cookie(_msg.get(), &cookie);
294 if (r < 0)
295 {
296 throw exception::SdBusError(-r, "sd_bus_message_get_cookie");
297 }
298 return cookie;
299 }
300
301 /** @brief Get the reply cookie of a message.
302 *
303 * @return The reply cookie of a message.
304 */
get_reply_cookie() const305 auto get_reply_cookie() const
306 {
307 uint64_t cookie;
308 int r = _intf->sd_bus_message_get_reply_cookie(_msg.get(), &cookie);
309 if (r < 0)
310 {
311 throw exception::SdBusError(-r, "sd_bus_message_get_reply_cookie");
312 }
313 return cookie;
314 }
315
316 /** @brief Check if message is a method call for an interface/method.
317 *
318 * @param[in] interface - The interface to match.
319 * @param[in] method - The method to match.
320 *
321 * @return True - if message is a method call for interface/method.
322 */
is_method_call(const char * interface,const char * method) const323 bool is_method_call(const char* interface, const char* method) const
324 {
325 return _intf->sd_bus_message_is_method_call(_msg.get(), interface,
326 method);
327 }
328
329 /** @brief Check if message is a signal for an interface/member.
330 *
331 * @param[in] interface - The interface to match.
332 * @param[in] member - The member to match.
333 */
is_signal(const char * interface,const char * member) const334 bool is_signal(const char* interface, const char* member) const
335 {
336 return _intf->sd_bus_message_is_signal(_msg.get(), interface, member);
337 }
338
339 /** @brief Create a 'method_return' type message from an existing message.
340 *
341 * @return method-return message.
342 */
new_method_return()343 message new_method_return()
344 {
345 msgp_t reply = nullptr;
346 int r = _intf->sd_bus_message_new_method_return(this->get(), &reply);
347 if (r < 0)
348 {
349 throw exception::SdBusError(-r, "sd_bus_message_new_method_return");
350 }
351
352 return message(reply, _intf, std::false_type());
353 }
354
355 /** @brief Create a 'method_error' type message from an existing message.
356 *
357 * @param[in] e - The exception we are returning
358 * @return method-return message.
359 */
new_method_error(const sdbusplus::exception::exception & e)360 message new_method_error(const sdbusplus::exception::exception& e)
361 {
362 msgp_t reply = nullptr;
363 int r = _intf->sd_bus_message_new_method_error(
364 this->get(), &reply, e.name(), e.description());
365 if (r < 0)
366 {
367 throw exception::SdBusError(-r, "sd_bus_message_new_method_error");
368 }
369
370 return message(reply, _intf, std::false_type());
371 }
372
373 /** @brief Create a 'method_error' type message from an existing message.
374 *
375 * @param[in] error - integer error number
376 * @param[in] e - optional pointer to preformatted sd_bus_error
377 * @return method-error message.
378 */
new_method_errno(int error,const sd_bus_error * e=nullptr)379 message new_method_errno(int error, const sd_bus_error* e = nullptr)
380 {
381 msgp_t reply = nullptr;
382 int r = _intf->sd_bus_message_new_method_errno(this->get(), &reply,
383 error, e);
384 if (r < 0)
385 {
386 throw exception::SdBusError(-r, "sd_bus_message_new_method_errno");
387 }
388
389 return message(reply, _intf, std::false_type());
390 }
391
392 /** @brief Perform a 'method-return' response call. */
method_return()393 void method_return()
394 {
395 auto b = _intf->sd_bus_message_get_bus(this->get());
396 int r = _intf->sd_bus_send(b, this->get(), nullptr);
397 if (r < 0)
398 {
399 throw exception::SdBusError(-r, "sd_bus_send");
400 }
401 }
402
403 /** @brief Perform a 'signal-send' call. */
signal_send()404 void signal_send()
405 {
406 method_return();
407 }
408
409 /** @brief Perform a message call.
410 * Errors generated by this call come from underlying dbus
411 * related errors *AND* from any method call that results
412 * in a METHOD_ERROR. This means you do not need to check
413 * is_method_error() on the returned message.
414 *
415 * @param[in] timeout - The timeout for the method call.
416 *
417 * @return The response message.
418 */
call(std::optional<SdBusDuration> timeout=std::nullopt)419 auto call(std::optional<SdBusDuration> timeout = std::nullopt)
420 {
421 sd_bus_error error = SD_BUS_ERROR_NULL;
422 sd_bus_message* reply = nullptr;
423 auto timeout_us = timeout ? timeout->count() : 0;
424 int r = _intf->sd_bus_call(nullptr, get(), timeout_us, &error, &reply);
425 if (r < 0)
426 {
427 throw exception::SdBusError(&error, "sd_bus_call");
428 }
429
430 return message(reply, _intf, std::false_type());
431 }
432
433 /** @brief Perform an async message call.
434 *
435 * @param[in] cb - The callback to run when the response is available.
436 * @param[in] timeout - The timeout for the method call.
437 *
438 * @return The slot handle that manages the lifetime of the call object.
439 */
440 template <typename Cb>
441 [[nodiscard]] slot_t
call_async(Cb && cb,std::optional<SdBusDuration> timeout=std::nullopt)442 call_async(Cb&& cb, std::optional<SdBusDuration> timeout = std::nullopt)
443 {
444 sd_bus_slot* slot;
445 auto timeout_us = timeout ? timeout->count() : 0;
446 using CbT = std::remove_cv_t<std::remove_reference_t<Cb>>;
447 int r = _intf->sd_bus_call_async(nullptr, &slot, get(),
448 details::call_async_cb<CbT>, nullptr,
449 timeout_us);
450 if (r < 0)
451 {
452 throw exception::SdBusError(-r, "sd_bus_call_async");
453 }
454 slot_t ret(slot, _intf);
455
456 if constexpr (std::is_pointer_v<CbT>)
457 {
458 _intf->sd_bus_slot_set_userdata(get_slotp(ret),
459 reinterpret_cast<void*>(cb));
460 }
461 else if constexpr (std::is_function_v<CbT>)
462 {
463 _intf->sd_bus_slot_set_userdata(get_slotp(ret),
464 reinterpret_cast<void*>(&cb));
465 }
466 else
467 {
468 r = _intf->sd_bus_slot_set_destroy_callback(
469 get_slotp(ret), details::call_async_del<CbT>);
470 if (r < 0)
471 {
472 throw exception::SdBusError(-r,
473 "sd_bus_slot_set_destroy_callback");
474 }
475 _intf->sd_bus_slot_set_userdata(get_slotp(ret),
476 new CbT(std::forward<Cb>(cb)));
477 }
478 return ret;
479 }
480
481 friend struct sdbusplus::bus::bus;
482
483 /** @brief Get a pointer to the owned 'msgp_t'.
484 * This api should be used sparingly and carefully, as it opens a number of
485 * possibilities for race conditions, RAII destruction issues, and runtime
486 * problems when using the sd-bus c api. Here be dragons. */
get()487 msgp_t get()
488 {
489 return _msg.get();
490 }
491
492 private:
493 sdbusplus::SdBusInterface* _intf;
494 details::msg _msg;
495 };
496
497 namespace details
498 {
499
500 template <typename CbT>
call_async_cb(sd_bus_message * m,void * userdata,sd_bus_error *)501 int call_async_cb(sd_bus_message* m, void* userdata, sd_bus_error*) noexcept
502 {
503 try
504 {
505 if constexpr (std::is_pointer_v<CbT>)
506 {
507 (*reinterpret_cast<CbT>(userdata))(message(m));
508 }
509 else
510 {
511 (*reinterpret_cast<CbT*>(userdata))(message(m));
512 }
513 }
514 catch (...)
515 {
516 std::terminate();
517 }
518 return 1;
519 }
520
521 } // namespace details
522
523 } // namespace message
524
525 using message_t = message::message;
526
527 } // namespace sdbusplus
528
529 #ifdef __clang__
530 #pragma clang diagnostic pop
531 #endif
532