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