Fix TIVI-{1191,1192,2108} by implementing a connman 'Agent'.
[profile/ivi/settings-daemon.git] / plugins / connman / agent.cpp
1 /**
2  * @file agent.cpp
3  *
4  * @brief Settings daemon D-Bus connection adapter.
5  *
6  * @author Michael Leibowitz @<michael.leibowitz@@intel.com@>
7  * @author Ossama Othman @<ossama.othman@@intel.com@>
8  *
9  * @copyright @par
10  * Copyright 2013 Intel Corporation All Rights Reserved.
11  * @par
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation;
15  * version 2.1 of the License.
16  * @par
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  * @par
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
25  * Boston, MA  02110-1301  USA
26  */
27
28 #include "agent.hpp"
29 #include "../../lib/config.hpp"
30
31 #include <settingsd/glib_traits.hpp>
32 #include <settingsd/unique_ptr.hpp>
33
34 #include <cstring>
35 #include <string>
36 #include <algorithm>
37 #include <stdexcept>
38
39
40 namespace
41 {
42   // --------------------------------------------------------------
43   //                  D-Bus Name Acquisition
44   // --------------------------------------------------------------
45   /**
46    * @brief Callback function invoked when a D-Bus name is acquired.
47    *
48    * @param[in] connection The D-Bus connection on which the name was
49    *                       acquired.
50    * @param[in] name       The name being owned.
51    * @param[in] user_data  User-provided data.
52    */
53   void
54   on_name_acquired(GDBusConnection * /* connection */,
55                    gchar const * name,
56                    gpointer /* user_data */)
57   {
58     g_debug("Name \"%s\" acquired on D-Bus.\n", name);
59   }
60
61   /**
62    * @brief Callback function invoked when a D-Bus name is no longer
63    *        owned or the D-Bus connection has been closed.
64    *
65    * @param[in] connection The D-Bus connection on which the name was
66    *                       acquired.
67    * @param[in] name       The name being owned.
68    * @param[in] user_data  User-provided data.
69    *
70    */
71   void
72   on_name_lost(GDBusConnection * /* connection */,
73                gchar const * name,
74                gpointer /* user_data */)
75   {
76     // This can happen if the appropriate D-Bus policy is not
77     // installed, for example.
78     throw std::runtime_error(std::string("Lost name \"")
79                              + name + "\" on D-Bus!");
80   }
81
82   // --------------------------------------------------------------
83   //                   Connman Agent Handlers
84   // --------------------------------------------------------------
85   /**
86    * @class agent_operation_handler
87    *
88    * @brief Handle agent operations through given callback.
89    *
90    * This class factors out common connection data retrieval code, and
91    * performs Agent operations through the given callback.
92    */
93   class agent_operation_handler
94   {
95     typedef ivi::settings::agent agent;
96     typedef agent::user_data data_type;
97
98     // Prevent copying.
99     agent_operation_handler(agent_operation_handler const &) = delete;
100     void operator=(agent_operation_handler const &) = delete;
101
102   public:
103
104     /**
105      * Constructor
106      *
107      * @param[in] service   D-Bus object path for connman service
108      * @param[in] user_data User/connection data
109      * @param[in] callback  Callback that implements Agent operation
110      */
111     agent_operation_handler(
112       gchar const * service,
113       gpointer user_data,
114       std::function<void(ivi::settings::agent::connect_data const &)>
115         callback)
116       : data_(static_cast<data_type *>(user_data))
117       , guard_(data_->lock)
118       , iterator_(data_->info.find(service))
119     {
120       // data_ should never be null!
121
122       if (iterator_ != data_->info.end())
123         callback(iterator_->second);
124     }
125
126     /// Destructor
127     ~agent_operation_handler()
128     {
129       // Done with the connection data.  Remove it from the map.
130       if (iterator_ != data_->info.end())
131         data_->info.erase(iterator_);
132     }
133
134   private:
135     /// User/connection data.
136     data_type * const data_;
137
138     /**
139      * Synchronize access to the user/connection data map.
140      *
141      * @note This lock will be held for the life of the
142      *       @c agent_operation_handler object, i.e. for the duration
143      *       of the operation.  This is needed to synchronize the map
144      *       @c erase() operation in the destructor.  The erase()
145      *       operation is performed in the destructor to ensure
146      *       element removal occurs if an exception is thrown by the
147      *       callback.
148      */
149     std::lock_guard<std::mutex> guard_;
150
151     /// Map iterator that points to the connection data.
152     agent::user_data::map_type::iterator const iterator_;
153   };
154
155   bool
156   on_handle_release(Agent * object,
157                     GDBusMethodInvocation * invocation,
158                     gpointer /* user_data */)
159   {
160     agent_complete_release(object, invocation);
161     return true;
162   }
163
164   bool
165   on_handle_report_error(Agent * object,
166                          GDBusMethodInvocation * invocation,
167                          gchar const * service,
168                          gchar const * error,
169                          gpointer user_data)
170   {
171     using ivi::settings::agent;
172
173     agent_operation_handler(service,
174                             user_data,
175                             [error](agent::connect_data const & d){
176                               d.response.send_error(std::string(error));
177                             });
178
179     agent_complete_report_error(object, invocation);
180
181     return true;
182   }
183
184   bool
185   on_handle_request_browser(Agent * object,
186                             GDBusMethodInvocation * invocation,
187                             gchar const * /* service */,
188                             gchar const * /* url */,
189                             gpointer /* user_data */)
190   {
191     agent_complete_request_browser(object, invocation);
192     return true;
193   }
194
195   bool
196   on_handle_request_input(Agent * object,
197                           GDBusMethodInvocation * invocation,
198                           gchar const * service,
199                           GVariant * fields,
200                           gpointer user_data)
201   {
202     using ivi::settings::agent;
203
204     agent_operation_handler(
205       service,
206       user_data,
207       [object, invocation, fields](agent::connect_data const & d){
208         using ivi::settings::unique_ptr;
209
210         // Prepare to build a dictionary, i.e. an array of
211         // dictionary entries, "a{sv}".
212         GVariantBuilder builder;
213         g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
214
215         bool error_occurred = false;
216         bool found_field = false;
217
218         // Extract requested mandatory and alternate fields.
219         GVariantIter * vi = nullptr;
220         g_variant_get(fields, "a{sv}", &vi);
221         unique_ptr<GVariantIter> const iter(vi);
222
223         gchar    * fname  = nullptr;
224         GVariant * fvalue = nullptr;
225
226         /**
227          * @todo Refactor this GVariant dictionary iteration code.
228          */
229         while (g_variant_iter_next(vi, "{sv}", &fname, &fvalue)) {
230           //
231           unique_ptr<gchar>    const field(fname);
232           unique_ptr<GVariant> const arguments(fvalue);
233
234           GVariantIter * ai = nullptr;
235           g_variant_get(arguments.get(), "a{sv}", &ai);
236           unique_ptr<GVariantIter> const iter(ai);
237
238           gchar    * argname  = nullptr;
239           GVariant * argvalue = nullptr;
240
241           while (g_variant_iter_next(ai, "{sv}", &argname, &argvalue)) {
242             unique_ptr<gchar>    const name(argname);
243             unique_ptr<GVariant> const value(argvalue);
244
245             // Determine which requested fields are mandatory.
246             if (strcmp(argname, "Requirement") == 0
247                 && strcmp(g_variant_get_string(argvalue, nullptr),
248                           "mandatory") == 0) {
249               // Check if we were supplied the required field, and add
250               // it to the dictionary result.
251               auto const i = d.info.find(fname);
252               if (i != d.info.end()) {
253                 // Note that the dictionary is of the form a{sv} so
254                 // we wrap the dictionary string value in a variant.
255                 g_variant_builder_add_value(
256                   &builder,
257                   g_variant_new_dict_entry(
258                     g_variant_new_string(fname),
259                     g_variant_new_variant(
260                       g_variant_new_string(i->second.c_str()))));
261
262                 /**
263                  * @todo Is there any way to tell if an entry was
264                  *       added to the indefinite array being
265                  *       constructed through the glib GVariant builder
266                  *       interface?  We can't get rid of this flag if
267                  *       that is the case.
268                  */
269                 found_field = true;
270               } else {
271                 std::string err(
272                   std::string("Required connection field \"")
273                   + fname
274                   + "\" not supplied.");
275
276                 /**
277                  * @todo This send_error() call is probably
278                  *       redundant.  Look into removing it.
279                  */
280                 d.response.send_error(err);
281
282                 /**
283                  * @bug Register a settingsd-specific error during
284                  *      initialization via
285                  *      @c g_dbus_error_register_error(), and use it
286                  *      in this call instead.
287                  */
288                 g_dbus_method_invocation_return_dbus_error(
289                   invocation,
290                   "net.connman.Agent.Error.Canceled",
291                   err.c_str());
292
293                 error_occurred = true;
294
295                 return;
296               }
297             }
298           }
299         }
300
301         if (!error_occurred) {
302           GVariant * dictionary = nullptr;
303
304           // We can't call g_variant_builder_end() if no children
305           // (dictionary entries) were added to the indefinite array
306           // being constructed through the builder.
307           if (found_field)
308             dictionary = g_variant_builder_end(&builder);
309
310           // The method return value will contain a dictionary,
311           // i.e. "a{sv}", of the requested input fields.
312           agent_complete_request_input(object, invocation, dictionary);
313         }
314       });
315
316     return true;
317   }
318
319   bool
320   on_handle_cancel(Agent * object,
321                    GDBusMethodInvocation * invocation,
322                    gpointer /* user_data */)
323   {
324     agent_complete_cancel(object, invocation);
325     return true;
326   }
327 }
328
329 // ----------------------------------------------------------------
330
331 ivi::settings::agent::agent(GDBusConnection * connection)
332   : owner_id_(
333       // The bus name will be of the form:
334       //     "org.tizen.settingsd.connman.Agent"
335       g_bus_own_name_on_connection(connection,
336                                    IVI_SETTINGS_CONNMAN_AGENT_DBUS_NAME,
337                                    G_BUS_NAME_OWNER_FLAGS_NONE,
338                                    on_name_acquired,
339                                    on_name_lost,
340                                    nullptr, // user_data,
341                                    nullptr))
342   , interface_(agent_skeleton_new())
343   , object_path_("/" IVI_SETTINGS_DBUS_NAME "/connman/Agent")
344   , data_()
345 {
346   // Construct object path of the form:
347   //     /org/tizen/settingsd/connman/Agent"
348   std::replace(object_path_.begin(), object_path_.end(), '.', '/');
349
350   // Connect the signal handlers corresponding to the Agent
351   // interface.
352   g_signal_connect(interface_,
353                    "handle-release",
354                    G_CALLBACK(on_handle_release),
355                    &data_);
356
357   g_signal_connect(interface_,
358                    "handle-report-error",
359                    G_CALLBACK(on_handle_report_error),
360                    &data_);
361
362   g_signal_connect(interface_,
363                    "handle-request-browser",
364                    G_CALLBACK(on_handle_request_browser),
365                    &data_);
366
367   g_signal_connect(interface_,
368                    "handle-request-input",
369                    G_CALLBACK(on_handle_request_input),
370                    &data_);
371
372   g_signal_connect(interface_,
373                    "handle-cancel",
374                    G_CALLBACK(on_handle_cancel),
375                    &data_);
376
377   // Export the interface on the bus.
378   GError * error = nullptr;
379   if (!g_dbus_interface_skeleton_export(
380         G_DBUS_INTERFACE_SKELETON(interface_),
381         connection,
382         object_path_.c_str(),
383         &error)) {
384     unique_ptr<GError> safe_error(error);
385     throw std::runtime_error(error->message);
386   }
387 }
388
389 ivi::settings::agent::~agent()
390 {
391   if (interface_ != nullptr) {
392     g_dbus_interface_skeleton_unexport(
393       G_DBUS_INTERFACE_SKELETON(interface_));
394     g_object_unref(interface_);
395   }
396
397   g_bus_unown_name(owner_id_);
398 }
399
400 bool
401 ivi::settings::agent::register_connect_data(
402   char const * service_path,
403   std::map<std::string, std::string> && info,
404   response_callback const & response)
405 {
406   connect_data data(std::move(info), response);
407
408   std::lock_guard<std::mutex> guard(data_.lock);
409
410   auto const result =
411     data_.info.insert(
412       std::make_pair(service_path, std::move(data)));
413
414   return result.second;
415 }
416
417 bool
418 ivi::settings::agent::deregister_connect_data(char const * service_path)
419 {
420   std::lock_guard<std::mutex> guard(data_.lock);
421
422   return data_.info.erase(service_path) == 1;
423 }
424
425
426 // Local Variables:
427 // mode:c++
428 // c-basic-offset:2
429 // indent-tabs-mode: nil
430 // End: