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.get_bus()), 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 explicit callback_sender(Init init) : init(std::move(init)){}; 136 137 // This Sender yields a message_t. 138 friend auto tag_invoke(execution::get_completion_signatures_t, 139 const callback_sender&, auto) 140 -> execution::completion_signatures<execution::set_value_t(message_t), 141 execution::set_stopped_t()>; 142 143 template <execution::receiver R> 144 friend auto tag_invoke(execution::connect_t, callback_sender&& self, R r) 145 -> callback_operation<Init, R> 146 { 147 return {std::move(self.init), std::move(r)}; 148 } 149 150 private: 151 Init init; 152 }; 153 154 } // namespace callback_ns 155 156 } // namespace sdbusplus::async 157