1 // Copyright (c) Benjamin Kietzman (github.com/bkietz)
2 //
3 // Distributed under the Boost Software License, Version 1.0. (See accompanying
4 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5 
6 #ifndef DBUS_CONNECTION_HPP
7 #define DBUS_CONNECTION_HPP
8 
9 #include <dbus/connection_service.hpp>
10 #include <dbus/element.hpp>
11 #include <dbus/message.hpp>
12 #include <chrono>
13 #include <string>
14 #include <boost/asio.hpp>
15 
16 namespace dbus {
17 
18 class filter;
19 class match;
20 
21 /// Root D-Bus IO object
22 /**
23  * A connection to a bus, through which messages may be sent or received.
24  */
25 class connection : public boost::asio::basic_io_object<connection_service> {
26  public:
27   /// Open a connection to a specified address.
28   /**
29  * @param io_service The io_service object that the connection will use to
30  * wire D-Bus for asynchronous operation.
31  *
32  * @param address The address of the bus to connect to.
33  *
34  * @throws boost::system::system_error When opening the connection failed.
35  */
36   connection(boost::asio::io_service& io, const string& address)
37       : basic_io_object<connection_service>(io) {
38     this->get_service().open(this->get_implementation(), address);
39   }
40 
41   /// Open a connection to a well-known bus.
42   /**
43  * D-Bus connections are usually opened to well-known buses like the
44  * system or session bus.
45  *
46  * @param bus The well-known bus to connect to.
47  *
48  * @throws boost::system::system_error When opening the connection failed.
49  */
50   // TODO: change this unsigned to an enumeration
51   connection(boost::asio::io_service& io, const int bus)
52       : basic_io_object<connection_service>(io) {
53     this->get_service().open(this->get_implementation(), bus);
54   }
55 
56   /// Request a name on the bus.
57   /**
58  * @param name The name requested on the bus
59  *
60  * @return
61  *
62  * @throws boost::system::system_error When the response timed out or
63  * there was some other error.
64  */
65   void request_name(const string& name) {
66     this->get_implementation().request_name(name);
67   }
68 
69   std::string get_unique_name() {
70     return this->get_implementation().get_unique_name();
71   }
72 
73   /// Reply to a message.
74   /**
75  * @param m The message from which to create the reply
76  *
77  * @return The new reply message
78  *
79  * @throws boost::system::system_error When the response timed out or
80  * there was some other error.
81  */
82   message reply(message& m) {
83     return this->get_implementation().new_method_return(m);
84   }
85 
86   /// Send a message.
87   /**
88  * @param m The message to send.
89  *
90  * @return The reply received.
91  *
92  * @throws boost::system::system_error When the response timed out or
93  * there was some other error.
94  */
95   message send(message& m) {
96     return this->get_service().send(this->get_implementation(), m);
97   }
98 
99   /// Send a message.
100   /**
101  * @param m The message to send.
102  *
103  * @param t Time to wait for a reply. Passing 0 as the timeout means
104  * that you wish to ignore the reply. (Or catch it later somehow...)
105  *
106  * @return The reply received.
107  *
108  * @throws boost::system::system_error When the response timed out (if
109  * timeout was not 0), or there was some other error.
110  */
111   template <typename Duration>
112   message send(message& m, const Duration& t) {
113     return this->get_service().send(this->get_implementation(), m, t);
114   }
115 
116   /// Send a message asynchronously.
117   /**
118  * @param m The message to send.
119  *
120  * @param handler Handler for the reply.
121  *
122  * @return Asynchronous result
123  */
124 
125   template <typename MessageHandler>
126   inline BOOST_ASIO_INITFN_RESULT_TYPE(MessageHandler,
127                                        void(boost::system::error_code, message))
128       async_send(message& m, BOOST_ASIO_MOVE_ARG(MessageHandler) handler) {
129     return this->get_service().async_send(
130         this->get_implementation(), m,
131         BOOST_ASIO_MOVE_CAST(MessageHandler)(handler));
132   }
133 
134   // Small helper class for stipping off the error code from the function
135   // agrument definitions so unpack can be called appriately
136   template <typename T>
137   struct strip_first_arg {};
138 
139   template <typename FirstArg, typename... Rest>
140   struct strip_first_arg<std::tuple<FirstArg, Rest...>> {
141     typedef std::tuple<Rest...> type;
142   };
143 
144   template <typename MessageHandler, typename... InputArgs>
145   void async_method_call(MessageHandler handler, const dbus::endpoint& e,
146                          const InputArgs&... a) {
147     message m = dbus::message::new_call(e);
148     if (!m.pack(a...)) {
149       // TODO(ed) Set error code?
150 
151     } else {
152       async_send(m, [&](boost::system::error_code ec, dbus::message r) {
153         // Make a copy of the error code so we can modify it
154         typedef typename function_traits<MessageHandler>::decayed_arg_types
155             function_tuple;
156         typedef typename strip_first_arg<function_tuple>::type unpack_type;
157         unpack_type response_args;
158         if (!ec) {
159           if (!unpack_into_tuple(response_args, r)) {
160             // Set error code
161             ec = boost::system::errc::make_error_code(
162                 boost::system::errc::invalid_argument);
163           }
164         }
165         // Should this (the callback) be done in a try catch block?
166         // should throwing from a handler flow all the way to the io_service?
167 
168         // Note.  Callback is called whether or not the unpack was sucessful
169         // to allow the user to implement their own handling
170         index_apply<std::tuple_size<unpack_type>{}>(
171             [&](auto... Is) { handler(ec, std::get<Is>(response_args)...); });
172       });
173     }
174   }
175 
176   /// Create a new match.
177   void new_match(match& m) {
178     this->get_service().new_match(this->get_implementation(), m);
179   }
180 
181   /// Destroy a match.
182   void delete_match(match& m) {
183     this->get_service().delete_match(this->get_implementation(), m);
184   }
185 
186   /// Create a new filter.
187   void new_filter(filter& f) {
188     this->get_service().new_filter(this->get_implementation(), f);
189   }
190 
191   /// Destroy a filter.
192   void delete_filter(filter& f) {
193     this->get_service().delete_filter(this->get_implementation(), f);
194   }
195 
196   // FIXME the only way around this I see is to expose start() here, which seems
197   // ugly
198   friend class filter;
199 };
200 
201 typedef std::shared_ptr<connection> connection_ptr;
202 
203 }  // namespace dbus
204 
205 #endif  // DBUS_CONNECTION_HPP
206