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,
141                                             0);
142                }) |
143                execution::then([](message_t&& m) { return m.unpack<Rs...>(); });
144     }
145 
146     /** Get a property.
147      *
148      *  @tparam T - The type of the property.
149      *
150      *  @param[in] ctx - The context to use.
151      *  @param[in] property - The property name.
152      *
153      *  @return A Sender which completes with T as the property value.
154      */
155     template <typename T>
get_propertysdbusplus::async::proxy_ns::proxy156     auto get_property(context& ctx, sv_ref property) const
157         requires((S) && (P) && (I))
158     {
159         using result_t = std::variant<T>;
160         auto prop_intf = proxy(s, p, dbus_prop_intf);
161 
162         return prop_intf.template call<result_t>(ctx, "Get", c_str(i),
163                                                  property.data()) |
164                execution::then([](result_t&& v) { return std::get<T>(v); });
165     }
166 
167     /** Get all properties.
168      *
169      * @tparam V - The variant type of all possible properties.
170      *
171      * @param[in] ctx - The context to use.
172      *
173      * @return A Sender which completes with unordered_map<string, V>.
174      */
175     template <typename V>
get_all_propertiessdbusplus::async::proxy_ns::proxy176     auto get_all_properties(context& ctx) const
177         requires((S) && (P) && (I))
178     {
179         using result_t = std::unordered_map<std::string, V>;
180         auto prop_intf = proxy(s, p, dbus_prop_intf);
181 
182         return prop_intf.template call<result_t>(ctx, "GetAll", c_str(i));
183     }
184 
185     /** Set a property.
186      *
187      * @tparam T - The type of the property (usually deduced by the compiler).
188      *
189      * @param[in] ctx - The context to use.
190      * @param[in] property - The property name.
191      * @param[in] value - The value to set.
192      *
193      * @return A Sender which completes void when the property is set.
194      */
195     template <typename T>
set_propertysdbusplus::async::proxy_ns::proxy196     auto set_property(context& ctx, sv_ref property, T&& value) const
197         requires((S) && (P) && (I))
198     {
199         auto prop_intf = proxy(s, p, dbus_prop_intf);
200         return prop_intf.template call<>(
201             ctx, "Set", c_str(i), property.data(),
202             std::variant<std::decay_t<T>>{std::forward<T>(value)});
203     }
204 
205   private:
206     static constexpr auto dbus_prop_intf = "org.freedesktop.DBus.Properties";
207 
208     // Helper to get the underlying c-string of a string_view or string.
c_strsdbusplus::async::proxy_ns::proxy209     static auto c_str(string_ref v)
210     {
211         if constexpr (Preserved)
212         {
213             return v.c_str();
214         }
215         else
216         {
217             return v.data();
218         }
219     }
220 
221     value_t<S> s = {};
222     value_t<P> p = {};
223     value_t<I> i = {};
224 };
225 
226 } // namespace proxy_ns
227 
228 // clang currently has problems with the intersect of default template
229 // parameters and concepts.  I've opened llvm/llvm-project#57646 and added
230 // this indirect.
231 using proxy = proxy_ns::proxy<>;
232 
233 // Sometimes it is useful to hold onto a proxy, such as in a class member, so
234 // define a type alias for one which can be safely held.
235 using finalized_proxy = proxy_ns::proxy<true, true, true, true>;
236 
237 } // namespace sdbusplus::async
238