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             // No args
171             return;
172         }
173         else if constexpr (sizeof...(Args) == 1 &&
174                            std::is_void_v<
175                                std::tuple_element_t<0, std::tuple<Args...>>>)
176         {
177             // Args is void
178             return;
179         }
180         else if constexpr (sizeof...(Args) == 1)
181         {
182             std::tuple_element_t<0, std::tuple<Args...>> r{};
183             read(r);
184             return r;
185         }
186         else
187         {
188             std::tuple<Args...> r{};
189             std::apply([this](auto&&... v) { this->read(v...); }, r);
190             return r;
191         }
192     }
193 
194     /** @brief Get the dbus bus from the message. */
195     // Forward declare.
196     bus::bus get_bus() const;
197 
198     /** @brief Get the signature of a message.
199      *
200      *  @return A [weak] pointer to the signature of the message.
201      */
get_signature() const202     const char* get_signature() const
203     {
204         return _intf->sd_bus_message_get_signature(_msg.get(), true);
205     }
206 
207     /** @brief Get the path of a message.
208      *
209      *  @return A [weak] pointer to the path of the message.
210      */
get_path() const211     const char* get_path() const
212     {
213         return _intf->sd_bus_message_get_path(_msg.get());
214     }
215 
216     /** @brief Get the interface of a message.
217      *
218      *  @return A [weak] pointer to the interface of the message.
219      */
get_interface() const220     const char* get_interface() const
221     {
222         return _intf->sd_bus_message_get_interface(_msg.get());
223     }
224 
225     /** @brief Get the member of a message.
226      *
227      *  @return A [weak] pointer to the member of the message.
228      */
get_member() const229     const char* get_member() const
230     {
231         return _intf->sd_bus_message_get_member(_msg.get());
232     }
233 
234     /** @brief Get the destination of a message.
235      *
236      *  @return A [weak] pointer to the destination of the message.
237      */
get_destination() const238     const char* get_destination() const
239     {
240         return _intf->sd_bus_message_get_destination(_msg.get());
241     }
242 
243     /** @brief Get the sender of a message.
244      *
245      *  @return A [weak] pointer to the sender of the message.
246      */
get_sender() const247     const char* get_sender() const
248     {
249         return _intf->sd_bus_message_get_sender(_msg.get());
250     }
251 
252     /** @brief Check if message is a method error.
253      *
254      *  @return True - if message is a method error.
255      */
is_method_error() const256     bool is_method_error() const
257     {
258         return _intf->sd_bus_message_is_method_error(_msg.get(), nullptr);
259     }
260 
261     /** @brief Get the errno from the message.
262      *
263      *  @return The errno of the message.
264      */
get_errno() const265     int get_errno() const
266     {
267         return _intf->sd_bus_message_get_errno(_msg.get());
268     }
269 
270     /** @brief Get the error from the message.
271      *
272      *  @return The error of the message.
273      */
get_error() const274     const sd_bus_error* get_error() const
275     {
276         return _intf->sd_bus_message_get_error(_msg.get());
277     }
278 
279     /** @brief Get the type of a message.
280      *
281      * @return The type of message.
282      */
get_type() const283     auto get_type() const
284     {
285         uint8_t type;
286         int r = _intf->sd_bus_message_get_type(_msg.get(), &type);
287         if (r < 0)
288         {
289             throw exception::SdBusError(-r, "sd_bus_message_get_type");
290         }
291         return type;
292     }
293 
294     /** @brief Get the transaction cookie of a message.
295      *
296      * @return The transaction cookie of a message.
297      */
get_cookie() const298     auto get_cookie() const
299     {
300         uint64_t cookie;
301         int r = _intf->sd_bus_message_get_cookie(_msg.get(), &cookie);
302         if (r < 0)
303         {
304             throw exception::SdBusError(-r, "sd_bus_message_get_cookie");
305         }
306         return cookie;
307     }
308 
309     /** @brief Get the reply cookie of a message.
310      *
311      * @return The reply cookie of a message.
312      */
get_reply_cookie() const313     auto get_reply_cookie() const
314     {
315         uint64_t cookie;
316         int r = _intf->sd_bus_message_get_reply_cookie(_msg.get(), &cookie);
317         if (r < 0)
318         {
319             throw exception::SdBusError(-r, "sd_bus_message_get_reply_cookie");
320         }
321         return cookie;
322     }
323 
324     /** @brief Check if message is a method call for an interface/method.
325      *
326      *  @param[in] interface - The interface to match.
327      *  @param[in] method - The method to match.
328      *
329      *  @return True - if message is a method call for interface/method.
330      */
is_method_call(const char * interface,const char * method) const331     bool is_method_call(const char* interface, const char* method) const
332     {
333         return _intf->sd_bus_message_is_method_call(_msg.get(), interface,
334                                                     method);
335     }
336 
337     /** @brief Check if message is a signal for an interface/member.
338      *
339      *  @param[in] interface - The interface to match.
340      *  @param[in] member - The member to match.
341      */
is_signal(const char * interface,const char * member) const342     bool is_signal(const char* interface, const char* member) const
343     {
344         return _intf->sd_bus_message_is_signal(_msg.get(), interface, member);
345     }
346 
347     /** @brief Create a 'method_return' type message from an existing message.
348      *
349      *  @return method-return message.
350      */
new_method_return()351     message new_method_return()
352     {
353         msgp_t reply = nullptr;
354         int r = _intf->sd_bus_message_new_method_return(this->get(), &reply);
355         if (r < 0)
356         {
357             throw exception::SdBusError(-r, "sd_bus_message_new_method_return");
358         }
359 
360         return message(reply, _intf, std::false_type());
361     }
362 
363     /** @brief Create a 'method_error' type message from an existing message.
364      *
365      *  @param[in] e - The exception we are returning
366      *  @return method-return message.
367      */
new_method_error(const sdbusplus::exception::exception & e)368     message new_method_error(const sdbusplus::exception::exception& e)
369     {
370         msgp_t reply = nullptr;
371         sd_bus_error error = SD_BUS_ERROR_NULL;
372         e.set_error(_intf, &error);
373         int r =
374             _intf->sd_bus_message_new_method_error(this->get(), &reply, &error);
375         sd_bus_error_free(&error);
376         if (r < 0)
377         {
378             throw exception::SdBusError(-r, "sd_bus_message_new_method_error");
379         }
380 
381         return message(reply, _intf, std::false_type());
382     }
383 
384     /** @brief Create a 'method_error' type message from an existing message.
385      *
386      *  @param[in] error - integer error number
387      *  @param[in] e - optional pointer to preformatted sd_bus_error
388      *  @return method-error message.
389      */
new_method_errno(int error,const sd_bus_error * e=nullptr)390     message new_method_errno(int error, const sd_bus_error* e = nullptr)
391     {
392         msgp_t reply = nullptr;
393         int r = _intf->sd_bus_message_new_method_errno(this->get(), &reply,
394                                                        error, e);
395         if (r < 0)
396         {
397             throw exception::SdBusError(-r, "sd_bus_message_new_method_errno");
398         }
399 
400         return message(reply, _intf, std::false_type());
401     }
402 
403     /** @brief Perform a 'method-return' response call. */
method_return()404     void method_return()
405     {
406         auto b = _intf->sd_bus_message_get_bus(this->get());
407         int r = _intf->sd_bus_send(b, this->get(), nullptr);
408         if (r < 0)
409         {
410             throw exception::SdBusError(-r, "sd_bus_send");
411         }
412     }
413 
414     /** @brief Perform a 'signal-send' call. */
signal_send()415     void signal_send()
416     {
417         method_return();
418     }
419 
420     /** @brief Perform a message call.
421      *         Errors generated by this call come from underlying dbus
422      *         related errors *AND* from any method call that results
423      *         in a METHOD_ERROR. This means you do not need to check
424      *         is_method_error() on the returned message.
425      *
426      *  @param[in] timeout - The timeout for the method call.
427      *
428      *  @return The response message.
429      */
call(std::optional<SdBusDuration> timeout=std::nullopt)430     auto call(std::optional<SdBusDuration> timeout = std::nullopt)
431     {
432         sd_bus_error error = SD_BUS_ERROR_NULL;
433         sd_bus_message* reply = nullptr;
434         auto timeout_us = timeout ? timeout->count() : 0;
435         int r = _intf->sd_bus_call(nullptr, get(), timeout_us, &error, &reply);
436         if (r < 0)
437         {
438             throw exception::SdBusError(&error, "sd_bus_call");
439         }
440 
441         return message(reply, _intf, std::false_type());
442     }
443 
444     /** @brief Perform an async message call.
445      *
446      *  @param[in] cb - The callback to run when the response is available.
447      *  @param[in] timeout - The timeout for the method call.
448      *
449      *  @return The slot handle that manages the lifetime of the call object.
450      */
451     template <typename Cb>
452     [[nodiscard]] slot_t
call_async(Cb && cb,std::optional<SdBusDuration> timeout=std::nullopt)453         call_async(Cb&& cb, std::optional<SdBusDuration> timeout = std::nullopt)
454     {
455         sd_bus_slot* slot;
456         auto timeout_us = timeout ? timeout->count() : 0;
457         using CbT = std::remove_cv_t<std::remove_reference_t<Cb>>;
458         int r = _intf->sd_bus_call_async(nullptr, &slot, get(),
459                                          details::call_async_cb<CbT>, nullptr,
460                                          timeout_us);
461         if (r < 0)
462         {
463             throw exception::SdBusError(-r, "sd_bus_call_async");
464         }
465         slot_t ret(slot, _intf);
466 
467         if constexpr (std::is_pointer_v<CbT>)
468         {
469             _intf->sd_bus_slot_set_userdata(get_slotp(ret),
470                                             reinterpret_cast<void*>(cb));
471         }
472         else if constexpr (std::is_function_v<CbT>)
473         {
474             _intf->sd_bus_slot_set_userdata(get_slotp(ret),
475                                             reinterpret_cast<void*>(&cb));
476         }
477         else
478         {
479             r = _intf->sd_bus_slot_set_destroy_callback(
480                 get_slotp(ret), details::call_async_del<CbT>);
481             if (r < 0)
482             {
483                 throw exception::SdBusError(-r,
484                                             "sd_bus_slot_set_destroy_callback");
485             }
486             _intf->sd_bus_slot_set_userdata(get_slotp(ret),
487                                             new CbT(std::forward<Cb>(cb)));
488         }
489         return ret;
490     }
491 
492     friend struct sdbusplus::bus::bus;
493 
494     /** @brief Get a pointer to the owned 'msgp_t'.
495      * This api should be used sparingly and carefully, as it opens a number of
496      * possibilities for race conditions, RAII destruction issues, and runtime
497      * problems when using the sd-bus c api.  Here be dragons. */
get()498     msgp_t get()
499     {
500         return _msg.get();
501     }
502 
503   private:
504     sdbusplus::SdBusInterface* _intf;
505     details::msg _msg;
506 };
507 
508 namespace details
509 {
510 
511 template <typename CbT>
call_async_cb(sd_bus_message * m,void * userdata,sd_bus_error *)512 int call_async_cb(sd_bus_message* m, void* userdata, sd_bus_error*) noexcept
513 {
514     try
515     {
516         if constexpr (std::is_pointer_v<CbT>)
517         {
518             (*reinterpret_cast<CbT>(userdata))(message(m));
519         }
520         else
521         {
522             (*reinterpret_cast<CbT*>(userdata))(message(m));
523         }
524     }
525     catch (...)
526     {
527         std::terminate();
528     }
529     return 1;
530 }
531 
532 } // namespace details
533 
534 } // namespace message
535 
536 using message_t = message::message;
537 
538 } // namespace sdbusplus
539 
540 #ifdef __clang__
541 #pragma clang diagnostic pop
542 #endif
543