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>
callback(Init i)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
callback_operationsdbusplus::async::callback_ns::callback_operation66 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.
handlersdbusplus::async::callback_ns::callback_operation72 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.
tag_invoke(execution::start_t,callback_operation & self)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
callback_sendersdbusplus::async::callback_ns::callback_sender137 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>
tag_invoke(execution::connect_t,callback_sender && self,R 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