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  */
connection(boost::asio::io_service & io,const string & address)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
connection(boost::asio::io_service & io,const int bus)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  */
request_name(const string & name)65   void request_name(const string& name) {
66     this->get_implementation().request_name(name);
67   }
68 
get_unique_name()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  */
reply(message & m)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  */
send(message & m)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>
send(message & m,const Duration & t)112   message send(message& m, const Duration& t) {
113     return this->get_service().send(this->get_implementation(), m, t);
114   }
115 
116   template <typename... InputArgs>
method_call(const dbus::endpoint & e,const InputArgs &...a)117   message method_call(const dbus::endpoint& e, const InputArgs&... a) {
118     message m = dbus::message::new_call(e);
119     m.pack(a...);
120     return this->get_service().send(this->get_implementation(), m,
121                                     std::chrono::seconds(30));
122   }
123 
124   /// Send a message asynchronously.
125   /**
126  * @param m The message to send.
127  *
128  * @param handler Handler for the reply.
129  *
130  * @return Asynchronous result
131  */
132 
133   template <typename MessageHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(MessageHandler,void (boost::system::error_code,message))134   inline BOOST_ASIO_INITFN_RESULT_TYPE(MessageHandler,
135                                        void(boost::system::error_code, message))
136       async_send(message& m, BOOST_ASIO_MOVE_ARG(MessageHandler) handler) {
137     return this->get_service().async_send(
138         this->get_implementation(), m,
139         BOOST_ASIO_MOVE_CAST(MessageHandler)(handler));
140   }
141 
142   // Small helper class for stipping off the error code from the function
143   // agrument definitions so unpack can be called appriately
144   template <typename T>
145   struct strip_first_arg {};
146 
147   template <typename FirstArg, typename... Rest>
148   struct strip_first_arg<std::tuple<FirstArg, Rest...>> {
149     typedef std::tuple<Rest...> type;
150   };
151 
152   template <typename MessageHandler, typename... InputArgs>
async_method_call(MessageHandler handler,const dbus::endpoint & e,const InputArgs &...a)153   auto async_method_call(MessageHandler handler, const dbus::endpoint& e,
154                          const InputArgs&... a) {
155     message m = dbus::message::new_call(e);
156     if (!m.pack(a...)) {
157       // TODO(ed) Set error code?
158       std::cerr << "Pack error\n";
159     } else {
160       // explicit copy of handler here.  At this time, not clear why a copy is
161       // needed
162       return async_send(
163           m, [handler](boost::system::error_code ec, dbus::message r) {
164             // Make a copy of the error code so we can modify it
165             typedef typename function_traits<MessageHandler>::decayed_arg_types
166                 function_tuple;
167             typedef typename strip_first_arg<function_tuple>::type unpack_type;
168             unpack_type response_args;
169             if (!ec) {
170               if (!unpack_into_tuple(response_args, r)) {
171                 // Set error code
172                 ec = boost::system::errc::make_error_code(
173                     boost::system::errc::invalid_argument);
174               }
175             }
176             // Should this (the callback) be done in a try catch block?
177             // should throwing from a handler flow all the way to the
178             // io_service?
179 
180             // Note.  Callback is called whether or not the unpack was sucessful
181             // to allow the user to implement their own handling
182             index_apply<std::tuple_size<unpack_type>{}>([&](auto... Is) {
183               handler(ec, std::get<Is>(response_args)...);
184             });
185           });
186     }
187   }
188 
flush(void)189   void flush(void) { this->get_implementation().flush(); };
190 
191   /// Create a new match.
new_match(match & m)192   void new_match(match& m) {
193     this->get_service().new_match(this->get_implementation(), m);
194   }
195 
196   /// Destroy a match.
delete_match(match & m)197   void delete_match(match& m) {
198     this->get_service().delete_match(this->get_implementation(), m);
199   }
200 
201   /// Create a new filter.
new_filter(filter & f)202   void new_filter(filter& f) {
203     this->get_service().new_filter(this->get_implementation(), f);
204   }
205 
206   /// Destroy a filter.
delete_filter(filter & f)207   void delete_filter(filter& f) {
208     this->get_service().delete_filter(this->get_implementation(), f);
209   }
210 
211   // FIXME the only way around this I see is to expose start() here, which seems
212   // ugly
213   friend class filter;
214 };
215 
216 typedef std::shared_ptr<connection> connection_ptr;
217 
218 }  // namespace dbus
219 
220 #endif  // DBUS_CONNECTION_HPP
221