1 #pragma once
2 
3 #include <sdbusplus/async/callback.hpp>
4 #include <sdbusplus/async/context.hpp>
5 #include <sdbusplus/message.hpp>
6 
7 #include <string>
8 #include <string_view>
9 #include <type_traits>
10 #include <unordered_map>
11 #include <variant>
12 
13 namespace sdbusplus::async
14 {
15 namespace proxy_ns
16 {
17 /** A (client-side) proxy to a dbus object.
18  *
19  *  A dbus object is referenced by 3 address pieces:
20  *      - The service hosting the object.
21  *      - The path the object resides at.
22  *      - The interface the object implements.
23  *  The proxy is a holder of these 3 addresses.
24  *
25  *  One all 3 pieces of the address are known by the proxy, the proxy
26  *  can be used to perform normal dbus operations: method-call, get-property
27  *  set-property, get-all-properties.
28  *
29  *  Addresses are supplied to the object by calling the appropriate method:
30  *  service, path, interface.  These methods return a _new_ object with the
31  *  new information filled in.
32  *
33  *  If all pieces are known at compile-time it is suggested to be constructed
34  *  similar to the following:
35  *
36  *  ```
37  *  constexpr auto systemd = sdbusplus::async::proxy()
38  *                              .service("org.freedesktop.systemd1")
39  *                              .path("/org/freedesktop/systemd1")
40  *                              .interface("org.freedesktop.systemd1.Manager");
41  *  ```
42  *
43  *  The proxy object can be filled as information is available and attempts
44  *  to be as efficient as possible (supporting constexpr construction and
45  *  using std::string_view mostly).  In some cases it is necessary for the
46  *  proxy to leave a scope where it would be no longer safe to use the
47  *  previously-supplied string_views.  The `preserve` operation can be used
48  *  to transform an existing proxy into one which is safe to leave (because
49  *  it uses less-efficient but safe std::string values).
50  */
51 template <bool S = false, bool P = false, bool I = false,
52           bool Preserved = false>
53 struct proxy : private sdbusplus::bus::details::bus_friend
54 {
55     // Some typedefs to reduce template noise...
56 
57     using string_t =
58         std::conditional_t<Preserved, std::string, std::string_view>;
59     using string_ref = const string_t&;
60     using sv_ref = const std::string_view&;
61 
62     template <bool V>
63     using value_t = std::conditional_t<V, string_t, std::monostate>;
64     template <bool V>
65     using value_ref = const value_t<V>&;
66 
67     // Default constructor should only work for the "all empty" case.
68     constexpr proxy()
69         requires(!S && !P && !I)
70     = default;
71     constexpr proxy()
72         requires(S || P || I)
73     = delete;
74 
75     // Constructor allowing all 3 to be passed in.
proxysdbusplus::async::proxy_ns::proxy76     constexpr proxy(value_ref<S> s, value_ref<P> p, value_ref<I> i) :
77         s(s), p(p), i(i){};
78 
79     // Functions to assign address fields.
servicesdbusplus::async::proxy_ns::proxy80     constexpr auto service(string_ref s) const noexcept
81         requires(!S)
82     {
83         return proxy<true, P, I, Preserved>{s, this->p, this->i};
84     }
pathsdbusplus::async::proxy_ns::proxy85     constexpr auto path(string_ref p) const noexcept
86         requires(!P)
87     {
88         return proxy<S, true, I, Preserved>{this->s, p, this->i};
89     }
interfacesdbusplus::async::proxy_ns::proxy90     constexpr auto interface(string_ref i) const noexcept
91         requires(!I)
92     {
93         return proxy<S, P, true, Preserved>{this->s, this->p, i};
94     }
95 
96     /** Make a copyable / returnable proxy.
97      *
98      *  Since proxy deals with string_view by default, for efficiency,
99      *  there are cases where it would be dangerous for a proxy object to
100      *  leave a scope either by a return or a pass into a lambda.  This
101      *  function will convert an existing proxy into one backed by
102      *  `std::string` so that it can safely leave a scope.
103      */
preservesdbusplus::async::proxy_ns::proxy104     auto preserve() const noexcept
105         requires(!Preserved)
106     {
107         using result_t = proxy<S, P, I, true>;
108         return result_t(typename result_t::template value_t<S>(this->s),
109                         typename result_t::template value_t<P>(this->p),
110                         typename result_t::template value_t<I>(this->i));
111     }
112 
113     /** Perform a method call.
114      *
115      *  @tparam Rs - The return type(s) of the method call.
116      *  @tparam Ss - The parameter type(s) of the method call.
117      *
118      *  @param[in] ctx - The context to use.
119      *  @param[in] method - The method name.
120      *  @param[in] ss - The calling parameters.
121      *
122      *  @return A Sender which completes with either { void, Rs, tuple<Rs...> }.
123      */
124     template <typename... Rs, typename... Ss>
callsdbusplus::async::proxy_ns::proxy125     auto call(context& ctx, sv_ref method, Ss&&... ss) const
126         requires((S) && (P) && (I))
127     {
128         // Create the method_call message.
129         auto msg = ctx.get_bus().new_method_call(c_str(s), c_str(p), c_str(i),
130                                                  method.data());
131         if constexpr (sizeof...(Ss) > 0)
132         {
133             msg.append(std::forward<Ss>(ss)...);
134         }
135 
136         // Use 'callback' to perform the operation and "then" "unpack" the
137         // contents.
138         return callback([bus = get_busp(ctx),
139                          msg = std::move(msg)](auto cb, auto data) mutable {
140             return sd_bus_call_async(bus, nullptr, msg.get(), cb, data, 0);
141         }) | execution::then([](message_t&& m) { return m.unpack<Rs...>(); });
142     }
143 
144     /** Get a property.
145      *
146      *  @tparam T - The type of the property.
147      *
148      *  @param[in] ctx - The context to use.
149      *  @param[in] property - The property name.
150      *
151      *  @return A Sender which completes with T as the property value.
152      */
153     template <typename T>
get_propertysdbusplus::async::proxy_ns::proxy154     auto get_property(context& ctx, sv_ref property) const
155         requires((S) && (P) && (I))
156     {
157         using result_t = std::variant<T>;
158         auto prop_intf = proxy(s, p, dbus_prop_intf);
159 
160         return prop_intf.template call<result_t>(ctx, "Get", c_str(i),
161                                                  property.data()) |
162                execution::then([](result_t&& v) { return std::get<T>(v); });
163     }
164 
165     /** Get all properties.
166      *
167      * @tparam V - The variant type of all possible properties.
168      *
169      * @param[in] ctx - The context to use.
170      *
171      * @return A Sender which completes with unordered_map<string, V>.
172      */
173     template <typename V>
get_all_propertiessdbusplus::async::proxy_ns::proxy174     auto get_all_properties(context& ctx) const
175         requires((S) && (P) && (I))
176     {
177         using result_t = std::unordered_map<std::string, V>;
178         auto prop_intf = proxy(s, p, dbus_prop_intf);
179 
180         return prop_intf.template call<result_t>(ctx, "GetAll", c_str(i));
181     }
182 
183     /** Set a property.
184      *
185      * @tparam T - The type of the property (usually deduced by the compiler).
186      *
187      * @param[in] ctx - The context to use.
188      * @param[in] property - The property name.
189      * @param[in] value - The value to set.
190      *
191      * @return A Sender which completes void when the property is set.
192      */
193     template <typename T>
set_propertysdbusplus::async::proxy_ns::proxy194     auto set_property(context& ctx, sv_ref property, T&& value) const
195         requires((S) && (P) && (I))
196     {
197         auto prop_intf = proxy(s, p, dbus_prop_intf);
198         return prop_intf.template call<>(
199             ctx, "Set", c_str(i), property.data(),
200             std::variant<std::decay_t<T>>{std::forward<T>(value)});
201     }
202 
203   private:
204     static constexpr auto dbus_prop_intf = "org.freedesktop.DBus.Properties";
205 
206     // Helper to get the underlying c-string of a string_view or string.
c_strsdbusplus::async::proxy_ns::proxy207     static auto c_str(string_ref v)
208     {
209         if constexpr (Preserved)
210         {
211             return v.c_str();
212         }
213         else
214         {
215             return v.data();
216         }
217     }
218 
219     value_t<S> s = {};
220     value_t<P> p = {};
221     value_t<I> i = {};
222 };
223 
224 } // namespace proxy_ns
225 
226 // clang currently has problems with the intersect of default template
227 // parameters and concepts.  I've opened llvm/llvm-project#57646 and added
228 // this indirect.
229 using proxy = proxy_ns::proxy<>;
230 
231 // Sometimes it is useful to hold onto a proxy, such as in a class member, so
232 // define a type alias for one which can be safely held.
233 using finalized_proxy = proxy_ns::proxy<true, true, true, true>;
234 
235 } // namespace sdbusplus::async
236