1 #pragma once
2 
3 #include <systemd/sd-bus.h>
4 
5 #include <sdbusplus/async/execution.hpp>
6 #include <sdbusplus/message.hpp>
7 
8 #include <type_traits>
9 
10 namespace sdbusplus::async
11 {
12 
13 namespace callback_ns
14 {
15 
16 template <typename Fn>
17 concept takes_msg_handler =
18     std::is_invocable_r_v<int, Fn, sd_bus_message_handler_t, void*>;
19 
20 template <takes_msg_handler Init>
21 struct callback_sender;
22 
23 } // namespace callback_ns
24 
25 /** Create a sd_bus-callback Sender.
26  *
27  *  In the sd_bus library there are many functions named `*_async` that take
28  *  a `sd_bus_message_handler_t` as the callback.  This function turns them
29  *  into a Sender.
30  *
31  *  The `Init` function is a simple indirect so that the library sd_bus
32  *  function can be called, but with the callback handler (and data) placed
33  *  in the right call positions.
34  *
35  *  For example, `sd_bus_call_async` could be turned into a Sender with a
36  *  call to this and a small lambda such as:
37  *  ```
38  *      callback([bus = get_busp(ctx),
39  *                msg = std::move(msg)](auto cb, auto data) {
40  *          return sd_bus_call_async(bus, nullptr, msg.get(), cb, data, 0);
41  *      })
42  *  ```
43  *
44  *  @param[in] i - A function which calls the underlying sd_bus library
45  *                 function.
46  *
47  *  @returns A Sender which completes when sd-bus calls the callback and yields
48  *           a `sdbusplus::message_t`.
49  */
50 template <callback_ns::takes_msg_handler Init>
51 auto callback(Init i)
52 {
53     return callback_ns::callback_sender<Init>(std::move(i));
54 }
55 
56 namespace callback_ns
57 {
58 
59 /** The operation which handles the Sender completion. */
60 template <takes_msg_handler Init, execution::receiver R>
61 struct callback_operation
62 {
63     callback_operation() = delete;
64     callback_operation(callback_operation&&) = delete;
65 
66     callback_operation(Init&& init, R&& r) :
67         init(std::move(init)), receiver(std::move(r))
68     {}
69 
70     // Handle the call from sd-bus by ensuring there were no errors
71     // and setting the completion value to the resulting message.
72     static int handler(sd_bus_message* m, void* cb, sd_bus_error* e) noexcept
73     {
74         callback_operation& self = *static_cast<callback_operation*>(cb);
75         try
76         {
77             // Check 'e' for error.
78             if ((nullptr != e) && (sd_bus_error_is_set(e)))
79             {
80                 throw exception::SdBusError(e, "callback");
81             }
82 
83             message_t msg{m};
84 
85             // Check the message response for error.
86             if (msg.is_method_error())
87             {
88                 auto err = *msg.get_error();
89                 throw exception::SdBusError(&err, "method");
90             }
91 
92             execution::set_value(std::move(self.receiver), std::move(msg));
93         }
94         catch (...)
95         {
96             execution::set_error(std::move(self.receiver),
97                                  std::current_exception());
98         }
99         return 0;
100     }
101 
102     // Call the init function upon Sender start.
103     friend void tag_invoke(execution::start_t,
104                            callback_operation& self) noexcept
105     {
106         try
107         {
108             auto rc = self.init(handler, &self);
109             if (rc < 0)
110             {
111                 throw exception::SdBusError(-rc, __PRETTY_FUNCTION__);
112             }
113         }
114         catch (...)
115         {
116             execution::set_error(std::move(self.receiver),
117                                  std::current_exception());
118         }
119     }
120 
121   private:
122     Init init;
123     R receiver;
124 };
125 
126 /** The Sender for a callback.
127  *
128  *  The basically just holds the Init function until the Sender is connected
129  *  to (co_awaited on for co-routines), when it is turned into a pending
130  *  operation.
131  */
132 template <takes_msg_handler Init>
133 struct callback_sender
134 {
135     using is_sender = void;
136 
137     explicit callback_sender(Init init) : init(std::move(init)) {};
138 
139     // This Sender yields a message_t.
140     friend auto tag_invoke(execution::get_completion_signatures_t,
141                            const callback_sender&, auto)
142         -> execution::completion_signatures<execution::set_value_t(message_t),
143                                             execution::set_stopped_t()>;
144 
145     template <execution::receiver R>
146     friend auto tag_invoke(execution::connect_t, callback_sender&& self,
147                            R r) -> callback_operation<Init, R>
148     {
149         return {std::move(self.init), std::move(r)};
150     }
151 
152   private:
153     Init init;
154 };
155 
156 } // namespace callback_ns
157 
158 } // namespace sdbusplus::async
159