Merge branch 'master' into p2p
[platform/core/uifw/at-spi2-atk.git] / droute / droute.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2008 Novell, Inc.
6  * Copyright 2008 Codethink Ltd.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "droute.h"
28 #include "droute-pairhash.h"
29
30 #define CHUNKS_DEFAULT (512)
31
32 #define oom() g_error ("D-Bus out of memory, this message will fail anyway")
33
34 #if defined DROUTE_DEBUG
35     #define _DROUTE_DEBUG(format, args...) g_print (format , ## args)
36 #else
37     #define _DROUTE_DEBUG(format, args...)
38 #endif
39
40 struct _DRouteContext
41 {
42     GPtrArray            *registered_paths;
43
44     gchar                *introspect_string;
45 };
46
47 struct _DRoutePath
48 {
49     DRouteContext        *cnx;
50     gchar *path;
51     gboolean prefix;
52     GStringChunk         *chunks;
53     GPtrArray            *interfaces;
54     GPtrArray            *introspection;
55     GHashTable           *methods;
56     GHashTable           *properties;
57
58     DRouteIntrospectChildrenFunction introspect_children_cb;
59     void *introspect_children_data;
60     void                   *user_data;
61     DRouteGetDatumFunction  get_datum;
62 };
63
64 /*---------------------------------------------------------------------------*/
65
66 typedef struct PropertyPair
67 {
68     DRoutePropertyFunction get;
69     DRoutePropertyFunction set;
70 } PropertyPair;
71
72 /*---------------------------------------------------------------------------*/
73
74 static DBusHandlerResult
75 handle_message (DBusConnection *bus, DBusMessage *message, void *user_data);
76
77 static DBusMessage *
78 droute_object_does_not_exist_error (DBusMessage *message);
79
80 /*---------------------------------------------------------------------------*/
81
82 static DRoutePath *
83 path_new (DRouteContext *cnx,
84           const char *path,
85           gboolean prefix,
86           void    *user_data,
87           DRouteIntrospectChildrenFunction introspect_children_cb,
88           void *introspect_children_data,
89           DRouteGetDatumFunction get_datum)
90 {
91     DRoutePath *new_path;
92
93     new_path = g_new0 (DRoutePath, 1);
94     new_path->cnx = cnx;
95     new_path->path = g_strdup (path);
96     new_path->prefix = prefix;
97     new_path->chunks = g_string_chunk_new (CHUNKS_DEFAULT);
98     new_path->interfaces = g_ptr_array_new ();
99     new_path->introspection = g_ptr_array_new ();
100
101     new_path->methods = g_hash_table_new_full ((GHashFunc)str_pair_hash,
102                                                str_pair_equal,
103                                                g_free,
104                                                NULL);
105
106     new_path->properties = g_hash_table_new_full ((GHashFunc)str_pair_hash,
107                                                   str_pair_equal,
108                                                   g_free,
109                                                   NULL);
110
111     new_path->introspect_children_cb = introspect_children_cb;
112     new_path->introspect_children_data = introspect_children_data;
113     new_path->user_data = user_data;
114     new_path->get_datum = get_datum;
115
116     return new_path;
117 }
118
119 static void
120 path_free (DRoutePath *path, gpointer user_data)
121 {
122     g_free (path->path);
123     g_string_chunk_free  (path->chunks);
124     g_ptr_array_free     (path->interfaces, TRUE);
125     g_ptr_array_free     (path->introspection, FALSE);
126     g_hash_table_destroy (path->methods);
127     g_hash_table_destroy (path->properties);
128 }
129
130 static void *
131 path_get_datum (DRoutePath *path, const gchar *pathstr)
132 {
133     if (path->get_datum != NULL)
134         return (path->get_datum) (pathstr, path->user_data);
135     else
136         return path->user_data;
137 }
138
139 /*---------------------------------------------------------------------------*/
140
141 DRouteContext *
142 droute_new ()
143 {
144     DRouteContext *cnx;
145
146     cnx = g_new0 (DRouteContext, 1);
147     cnx->registered_paths = g_ptr_array_new ();
148
149     return cnx;
150 }
151
152 void
153 droute_free (DRouteContext *cnx)
154 {
155     g_ptr_array_foreach (cnx->registered_paths, (GFunc) path_free, NULL);
156     g_free (cnx);
157 }
158
159 /*---------------------------------------------------------------------------*/
160
161 /*---------------------------------------------------------------------------*/
162
163 static DBusObjectPathVTable droute_vtable =
164 {
165   NULL,
166   &handle_message,
167   NULL, NULL, NULL, NULL
168 };
169
170 DRoutePath *
171 droute_add_one (DRouteContext *cnx,
172                 const char    *path,
173                 const void    *data)
174 {
175     DRoutePath *new_path;
176     gboolean registered;
177
178     new_path = path_new (cnx, path, FALSE, (void *)data, NULL, NULL, NULL);
179
180     g_ptr_array_add (cnx->registered_paths, new_path);
181     return new_path;
182 }
183
184 DRoutePath *
185 droute_add_many (DRouteContext *cnx,
186                  const char    *path,
187                  const void    *data,
188                  DRouteIntrospectChildrenFunction introspect_children_cb,
189                  void *introspect_children_data,
190                  const DRouteGetDatumFunction get_datum)
191 {
192     DRoutePath *new_path;
193
194     new_path = path_new (cnx, path, TRUE, (void *) data,
195                          introspect_children_cb, introspect_children_data,
196                          get_datum);
197
198     g_ptr_array_add (cnx->registered_paths, new_path);
199     return new_path;
200 }
201
202 /*---------------------------------------------------------------------------*/
203
204 void
205 droute_path_add_interface(DRoutePath *path,
206                           const char *name,
207                           const char *introspect,
208                           const DRouteMethod   *methods,
209                           const DRouteProperty *properties)
210 {
211     gchar *itf;
212
213     g_return_if_fail (name != NULL);
214
215     itf = g_string_chunk_insert (path->chunks, name);
216     g_ptr_array_add (path->interfaces, itf);
217     g_ptr_array_add (path->introspection, introspect);
218
219     for (; methods != NULL && methods->name != NULL; methods++)
220       {
221         gchar *meth;
222
223         meth = g_string_chunk_insert (path->chunks, methods->name);
224         g_hash_table_insert (path->methods, str_pair_new (itf, meth), methods->func);
225       }
226
227     for (; properties != NULL && properties->name != NULL; properties++)
228       {
229         gchar *prop;
230         PropertyPair *pair;
231
232         prop = g_string_chunk_insert (path->chunks, properties->name);
233         pair = g_new (PropertyPair, 1);
234         pair->get = properties->get;
235         pair->set = properties->set;
236         g_hash_table_insert (path->properties, str_pair_new (itf, prop), pair);
237       }
238 }
239
240 /*---------------------------------------------------------------------------*/
241
242 /* The data structures don't support an efficient implementation of GetAll
243  * and I don't really care.
244  */
245 static DBusMessage *
246 impl_prop_GetAll (DBusMessage *message,
247                   DRoutePath  *path,
248                   const char  *pathstr)
249 {
250     DBusMessageIter iter, iter_dict, iter_dict_entry;
251     DBusMessage *reply;
252     DBusError error;
253     GHashTableIter prop_iter;
254
255     StrPair *key;
256     PropertyPair *value;
257     gchar *iface;
258
259     void  *datum = path_get_datum (path, pathstr);
260     if (!datum)
261         return droute_object_does_not_exist_error (message);
262
263     dbus_error_init (&error);
264     if (!dbus_message_get_args
265                 (message, &error, DBUS_TYPE_STRING, &iface, DBUS_TYPE_INVALID))
266         return dbus_message_new_error (message, DBUS_ERROR_FAILED, error.message);
267
268     reply = dbus_message_new_method_return (message);
269     if (!reply)
270         oom ();
271
272     dbus_message_iter_init_append (reply, &iter);
273     if (!dbus_message_iter_open_container
274                 (&iter, DBUS_TYPE_ARRAY, "{sv}", &iter_dict))
275         oom ();
276
277     g_hash_table_iter_init (&prop_iter, path->properties);
278     while (g_hash_table_iter_next (&prop_iter, (gpointer*)&key, (gpointer*)&value))
279       {
280         if (!g_strcmp0 (key->one, iface))
281          {
282            if (!value->get)
283               continue;
284            if (!dbus_message_iter_open_container
285                         (&iter_dict, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict_entry))
286               oom ();
287            dbus_message_iter_append_basic (&iter_dict_entry, DBUS_TYPE_STRING,
288                                            &key->two);
289            (value->get) (&iter_dict_entry, datum);
290            if (!dbus_message_iter_close_container (&iter_dict, &iter_dict_entry))
291                oom ();
292          }
293       }
294
295     if (!dbus_message_iter_close_container (&iter, &iter_dict))
296         oom ();
297     return reply;
298 }
299
300 static DBusMessage *
301 impl_prop_GetSet (DBusMessage *message,
302                   DRoutePath  *path,
303                   const char  *pathstr,
304                   gboolean     get)
305 {
306     DBusMessage *reply = NULL;
307     DBusError error;
308
309     StrPair pair;
310     PropertyPair *prop_funcs = NULL;
311
312     void *datum;
313
314     dbus_error_init (&error);
315     if (!dbus_message_get_args (message,
316                                 &error,
317                                 DBUS_TYPE_STRING,
318                                 &(pair.one),
319                                 DBUS_TYPE_STRING,
320                                 &(pair.two),
321                                 DBUS_TYPE_INVALID))
322         return dbus_message_new_error (message, DBUS_ERROR_FAILED, error.message);
323
324     _DROUTE_DEBUG ("DRoute (handle prop): %s|%s on %s\n", pair.one, pair.two, pathstr);
325
326     prop_funcs = (PropertyPair *) g_hash_table_lookup (path->properties, &pair);
327     if (!prop_funcs)
328         return dbus_message_new_error (message, DBUS_ERROR_FAILED, "Property unavailable");
329
330     datum = path_get_datum (path, pathstr);
331     if (!datum)
332         return droute_object_does_not_exist_error (message);
333
334     if (get && prop_funcs->get)
335       {
336         
337         DBusMessageIter iter;
338
339         _DROUTE_DEBUG ("DRoute (handle prop Get): %s|%s on %s\n", pair.one, pair.two, pathstr);
340
341         reply = dbus_message_new_method_return (message);
342         dbus_message_iter_init_append (reply, &iter);
343         if (!(prop_funcs->get) (&iter, datum))
344           {
345             dbus_message_unref (reply);
346             reply = dbus_message_new_error (message, DBUS_ERROR_FAILED, "Get failed");
347           }
348       }
349     else if (!get && prop_funcs->set)
350       {
351         DBusMessageIter iter;
352
353         _DROUTE_DEBUG ("DRoute (handle prop Get): %s|%s on %s\n", pair.one, pair.two, pathstr);
354
355         dbus_message_iter_init (message, &iter);
356         /* Skip the interface and property name */
357         dbus_message_iter_next(&iter);
358         dbus_message_iter_next(&iter);
359         (prop_funcs->set) (&iter, datum);
360
361         reply = dbus_message_new_method_return (message);
362       }
363     else
364       {
365         reply = dbus_message_new_error (message, DBUS_ERROR_FAILED, "Getter or setter unavailable");
366       }
367
368     return reply;
369 }
370
371 static DBusHandlerResult
372 handle_dbus (DBusConnection *bus,
373                    DBusMessage    *message,
374                    const gchar    *iface,
375                    const gchar    *member,
376                    const gchar    *pathstr)
377 {
378   static int id = 1;
379   char *id_str = (char *) g_malloc(40);
380   DBusMessage *reply;
381
382     if (strcmp (iface, DBUS_INTERFACE_DBUS) != 0 ||
383         strcmp (member, "Hello") != 0)
384     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
385
386     /* TODO: Fix this hack (we don't handle wrap-around, for instance) */
387     sprintf (id_str, ":1.%d", id++);
388     reply = dbus_message_new_method_return (message);
389     dbus_message_append_args (reply, DBUS_TYPE_STRING, &id_str, DBUS_TYPE_INVALID);
390     dbus_connection_send (bus, reply, NULL);
391   dbus_connection_flush (bus);
392     dbus_message_unref (reply);
393   g_free (id_str);
394     return DBUS_HANDLER_RESULT_HANDLED;
395 }
396
397 static DBusHandlerResult
398 handle_properties (DBusConnection *bus,
399                    DBusMessage    *message,
400                    DRoutePath     *path,
401                    const gchar    *iface,
402                    const gchar    *member,
403                    const gchar    *pathstr)
404 {
405     DBusMessage *reply;
406     DBusHandlerResult result = DBUS_HANDLER_RESULT_HANDLED;
407
408     if (!g_strcmp0(member, "GetAll"))
409        reply = impl_prop_GetAll (message, path, pathstr);
410     else if (!g_strcmp0 (member, "Get"))
411        reply = impl_prop_GetSet (message, path, pathstr, TRUE);
412     else if (!g_strcmp0 (member, "Set"))
413        reply = impl_prop_GetSet (message, path, pathstr, FALSE);
414     else
415        result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
416
417     if (reply)
418       {
419         dbus_connection_send (bus, reply, NULL);
420         dbus_message_unref (reply);
421       }
422
423     return result;
424 }
425
426 /*---------------------------------------------------------------------------*/
427
428 static const char *introspection_header =
429 "<?xml version=\"1.0\"?>\n";
430
431 static const char *introspection_node_element =
432 "<node name=\"%s\">\n";
433
434 static const char *introspection_footer =
435 "</node>";
436
437 static DBusHandlerResult
438 handle_introspection (DBusConnection *bus,
439                       DBusMessage    *message,
440                       DRoutePath     *path,
441                       const gchar    *iface,
442                       const gchar    *member,
443                       const gchar    *pathstr)
444 {
445     GString *output;
446     gchar *final;
447     gint i;
448
449     DBusMessage *reply;
450
451     _DROUTE_DEBUG ("DRoute (handle introspection): %s\n", pathstr);
452
453     if (g_strcmp0 (member, "Introspect"))
454         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
455
456     output = g_string_new(introspection_header);
457
458     g_string_append_printf(output, introspection_node_element, pathstr);
459
460     if (!path->get_datum || path_get_datum (path, pathstr))
461       {
462         for (i=0; i < path->introspection->len; i++)
463           {
464             gchar *introspect = (gchar *) g_ptr_array_index (path->introspection, i);
465             g_string_append (output, introspect);
466           }
467       }
468
469     if (path->introspect_children_cb)
470       {
471         gchar *children = (*path->introspect_children_cb) (pathstr, path->introspect_children_data);
472         if (children)
473           {
474             g_string_append (output, children);
475             g_free (children);
476           }
477       }
478
479     g_string_append(output, introspection_footer);
480     final = g_string_free(output, FALSE);
481
482     reply = dbus_message_new_method_return (message);
483     if (!reply)
484         oom ();
485     dbus_message_append_args(reply, DBUS_TYPE_STRING, &final,
486                              DBUS_TYPE_INVALID);
487     dbus_connection_send (bus, reply, NULL);
488
489     dbus_message_unref (reply);
490     g_free(final);
491     return DBUS_HANDLER_RESULT_HANDLED;
492 }
493
494 /*---------------------------------------------------------------------------*/
495
496 static DBusHandlerResult
497 handle_other (DBusConnection *bus,
498               DBusMessage    *message,
499               DRoutePath     *path,
500               const gchar    *iface,
501               const gchar    *member,
502               const gchar    *pathstr)
503 {
504     gint result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
505
506     StrPair pair;
507     DRouteFunction func;
508     DBusMessage *reply = NULL;
509
510     void *datum;
511
512     pair.one = iface;
513     pair.two = member;
514
515     _DROUTE_DEBUG ("DRoute (handle other): %s|%s on %s\n", member, iface, pathstr);
516
517     func = (DRouteFunction) g_hash_table_lookup (path->methods, &pair);
518     if (func != NULL)
519       {
520         datum = path_get_datum (path, pathstr);
521         if (!datum)
522             reply = droute_object_does_not_exist_error (message);
523         else
524             reply = (func) (bus, message, datum);
525
526         if (!reply)
527           {
528             /* All D-Bus method calls must have a reply.
529              * If one is not provided presume that the call has a void
530              * return and no error has occured.
531              */
532             reply = dbus_message_new_method_return (message);
533           }
534         dbus_connection_send (bus, reply, NULL);
535         dbus_message_unref (reply);
536         result = DBUS_HANDLER_RESULT_HANDLED;
537       }
538
539     _DROUTE_DEBUG ("DRoute (handle other) (reply): type %d\n",
540                    dbus_message_get_type(reply));
541     return result;
542 }
543
544 /*---------------------------------------------------------------------------*/
545
546 static DBusHandlerResult
547 handle_message (DBusConnection *bus, DBusMessage *message, void *user_data)
548 {
549     DRoutePath *path = (DRoutePath *) user_data;
550     const gchar *iface   = dbus_message_get_interface (message);
551     const gchar *member  = dbus_message_get_member (message);
552     const gint   type    = dbus_message_get_type (message);
553     const gchar *pathstr = dbus_message_get_path (message);
554
555     DBusHandlerResult result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
556
557     _DROUTE_DEBUG ("DRoute (handle message): %s|%s of type %d on %s\n", member, iface, type, pathstr);
558
559     /* Check for basic reasons not to handle */
560     if (type   != DBUS_MESSAGE_TYPE_METHOD_CALL ||
561         member == NULL ||
562         iface  == NULL)
563         return result;
564
565     if (!strcmp (pathstr, DBUS_PATH_DBUS))
566         result = handle_dbus (bus, message, iface, member, pathstr);
567     else if (!strcmp (iface, "org.freedesktop.DBus.Properties"))
568         result = handle_properties (bus, message, path, iface, member, pathstr);
569     else if (!strcmp (iface, "org.freedesktop.DBus.Introspectable"))
570         result = handle_introspection (bus, message, path, iface, member, pathstr);
571     else
572         result = handle_other (bus, message, path, iface, member, pathstr);
573 #if 0
574     if (result == DBUS_HANDLER_RESULT_NOT_YET_HANDLED)
575         g_print ("DRoute | Unhandled message: %s|%s of type %d on %s\n", member, iface, type, pathstr);
576 #endif
577       
578     return result;
579 }
580
581 /*---------------------------------------------------------------------------*/
582
583 static DBusMessage *
584 droute_object_does_not_exist_error (DBusMessage *message)
585 {
586     DBusMessage *reply;
587     gchar       *errmsg;
588
589     errmsg= g_strdup_printf (
590             "Method \"%s\" with signature \"%s\" on interface \"%s\" could not be processed as object %s does not exist\n",
591             dbus_message_get_member (message),
592             dbus_message_get_signature (message),
593             dbus_message_get_interface (message),
594             dbus_message_get_path (message));
595     reply = dbus_message_new_error (message,
596                                     DBUS_ERROR_FAILED,
597                                     errmsg);
598     g_free (errmsg);
599     return reply;
600 }
601
602 /*---------------------------------------------------------------------------*/
603
604 DBusMessage *
605 droute_not_yet_handled_error (DBusMessage *message)
606 {
607     DBusMessage *reply;
608     gchar       *errmsg;
609
610     errmsg= g_strdup_printf (
611             "Method \"%s\" with signature \"%s\" on interface \"%s\" doesn't exist\n",
612             dbus_message_get_member (message),
613             dbus_message_get_signature (message),
614             dbus_message_get_interface (message));
615     reply = dbus_message_new_error (message,
616                                     DBUS_ERROR_UNKNOWN_METHOD,
617                                     errmsg);
618     g_free (errmsg);
619     return reply;
620 }
621
622 DBusMessage *
623 droute_out_of_memory_error (DBusMessage *message)
624 {
625     DBusMessage *reply;
626     gchar       *errmsg;
627
628     errmsg= g_strdup_printf (
629             "Method \"%s\" with signature \"%s\" on interface \"%s\" could not be processed due to lack of memory\n",
630             dbus_message_get_member (message),
631             dbus_message_get_signature (message),
632             dbus_message_get_interface (message));
633     reply = dbus_message_new_error (message,
634                                     DBUS_ERROR_NO_MEMORY,
635                                     errmsg);
636     g_free (errmsg);
637     return reply;
638 }
639
640 DBusMessage *
641 droute_invalid_arguments_error (DBusMessage *message)
642 {
643     DBusMessage *reply;
644     gchar       *errmsg;
645
646     errmsg= g_strdup_printf (
647             "Method \"%s\" with signature \"%s\" on interface \"%s\" was supplied with invalid arguments\n",
648             dbus_message_get_member (message),
649             dbus_message_get_signature (message),
650             dbus_message_get_interface (message));
651     reply = dbus_message_new_error (message,
652                                     DBUS_ERROR_INVALID_ARGS,
653                                     errmsg);
654     g_free (errmsg);
655     return reply;
656 }
657
658 void
659 droute_path_register (DRoutePath *path, DBusConnection *bus)
660 {
661     if (path->prefix)
662       dbus_connection_register_fallback (bus, path->path, &droute_vtable, path);
663     else
664       dbus_connection_register_object_path (bus, path->path,
665                                             &droute_vtable, path);
666 }
667
668 void
669 droute_context_register (DRouteContext *cnx, DBusConnection *bus)
670 {
671     g_ptr_array_foreach (cnx->registered_paths, (GFunc) droute_path_register,
672                          bus);
673 }
674
675 void
676 droute_intercept_dbus (DBusConnection *bus)
677 {
678     dbus_connection_register_object_path (bus, DBUS_PATH_DBUS,
679                                           &droute_vtable, NULL);
680 }
681 /*END------------------------------------------------------------------------*/