bug fix for jbus
[src/app-framework-main.git] / src / utils-jbus.c
1 /*
2  Copyright 2015 IoT.bzh
3
4  Licensed under the Apache License, Version 2.0 (the "License");
5  you may not use this file except in compliance with the License.
6  You may obtain a copy of the License at
7
8      http://www.apache.org/licenses/LICENSE-2.0
9
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15 */
16 #define _GNU_SOURCE
17
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <errno.h>
21 #include <string.h>
22
23 #include <json.h>
24 #include <dbus/dbus.h>
25
26 #include "utils-jbus.h"
27
28 struct jreq;
29 struct jservice;
30 struct jbus;
31
32 struct jreq {
33         DBusConnection *connection;
34         DBusMessage *reply;
35 };
36
37 struct jservice {
38         struct jservice *next;
39         char *method;
40         void (*oncall)(struct jreq *, struct json_object *);
41 };
42
43 struct jrespw {
44         struct jrespw *next;
45         dbus_uint32_t serial;
46         void *data;
47         void (*onresp)(int status, struct json_object *response, void *data);
48 };
49
50 struct jbus {
51         int refcount;
52         struct jservice *services;
53         DBusConnection *connection;
54         struct jrespw *waiters;
55         char *path;
56         char *name;
57 };
58
59 static const char reply_out_of_memory[] = "{\"status\":\"out of memory\"}";
60 static const char reply_invalid[] = "{\"status\":\"invalid request\"}";
61 static const char interface_jbus[] = "org.jbus";
62
63 static DBusHandlerResult incoming_resp(DBusConnection *connection, DBusMessage *message, struct jbus *jbus)
64 {
65         int status;
66         const char *str;
67         struct jrespw *jrw, **prv;
68         struct json_object *reply;
69         dbus_uint32_t serial;
70
71         /* search for the waiter */
72         serial = dbus_message_get_reply_serial(message);
73         prv = &jbus->waiters;
74         while ((jrw = *prv) != NULL && jrw->serial != serial)
75                 prv = &jrw->next;
76         if (jrw == NULL)
77                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
78         *prv = jrw->next;
79
80         /* retrieve the json value */
81         if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID)) {
82                 status = -1;
83                 reply = NULL;
84         } else {
85                 reply = json_tokener_parse(str);
86                 status = reply ? 0 : -1;
87         }
88
89         /* treat it */
90         jrw->onresp(status, reply, jrw->data);
91         free(jrw);
92         return DBUS_HANDLER_RESULT_HANDLED;
93 }
94
95 static int matchitf(DBusMessage *message)
96 {
97         const char *itf = dbus_message_get_interface(message);
98         return itf != NULL && !strcmp(itf, interface_jbus);
99 }
100
101 static DBusHandlerResult incoming_call(DBusConnection *connection, DBusMessage *message, struct jbus *jbus)
102 {
103         struct jservice *srv;
104         struct jreq *jreq;
105         const char *str;
106         const char *method;
107         struct json_object *query;
108
109         /* search for the service */
110         if (!matchitf(message))
111                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
112         method = dbus_message_get_member(message);
113         if (method == NULL)
114                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
115         srv = jbus->services;
116         while(srv != NULL && strcmp(method, srv->method))
117                 srv = srv->next;
118         if (srv == NULL)
119                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
120
121         /* handle the message */
122         jreq = malloc(sizeof * jreq);
123         if (jreq == NULL)
124                 return DBUS_HANDLER_RESULT_NEED_MEMORY;
125         jreq->reply = dbus_message_new_method_return(message);
126         if (jreq->reply == NULL) {
127                 free(jreq);
128                 return DBUS_HANDLER_RESULT_NEED_MEMORY;
129         }
130         jreq->connection = dbus_connection_ref(jbus->connection);
131         
132         /* retrieve the json value */
133         if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID)) {
134                 jbus_replyj(jreq, reply_invalid);
135                 return DBUS_HANDLER_RESULT_HANDLED;
136         }
137         query = json_tokener_parse(str);
138         if (query == NULL) {
139                 jbus_replyj(jreq, reply_invalid);
140                 return DBUS_HANDLER_RESULT_HANDLED;
141         }
142
143         /* treat it */
144         srv->oncall(jreq, query);
145         return DBUS_HANDLER_RESULT_HANDLED;
146 }
147
148 static DBusHandlerResult incoming(DBusConnection *connection, DBusMessage *message, void *data)
149 {
150         switch(dbus_message_get_type(message)) {
151         case DBUS_MESSAGE_TYPE_METHOD_CALL:
152                 return incoming_call(connection, message, (struct jbus*)data);
153         case  DBUS_MESSAGE_TYPE_METHOD_RETURN:
154                 return incoming_resp(connection, message, (struct jbus*)data);
155         }
156         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
157 }
158
159
160 struct jbus *create_jbus(int session, const char *path)
161 {
162         struct jbus *jbus;
163         char *name;
164
165         /* create the context and connect */
166         jbus = calloc(1, sizeof * jbus);
167         if (jbus == NULL) {
168                 errno = ENOMEM;
169                 goto error;
170         }
171         jbus->refcount = 1;
172         jbus->path = strdup(path);
173         jbus->name = NULL;
174         if (jbus->path == NULL) {
175                 errno = ENOMEM;
176                 goto error2;
177         }
178         while(*path == '/') path++;
179         jbus->name = name = strdup(path);
180         if (name == NULL) {
181                 errno = ENOMEM;
182                 goto error2;
183         }
184         while(*name) {
185                 if (*name == '/')
186                         *name = '.';
187                 name++;
188         }
189         name--;
190         while (name >= jbus->name && *name == '.')
191                 *name-- = 0;
192         if (!*jbus->name) {
193                 errno = EINVAL;
194                 goto error2;
195         }
196
197         /* connect */
198         jbus->connection = dbus_bus_get(session ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, NULL);
199         if (jbus->connection == NULL) {
200                 goto error2;
201         }
202         if (!dbus_connection_add_filter(jbus->connection, incoming, jbus, NULL)) {
203                 goto error2;
204         }
205
206         return jbus;
207
208 error2:
209         jbus_unref(jbus);
210 error:
211         return NULL;
212 }
213
214 void jbus_addref(struct jbus *jbus)
215 {
216         jbus->refcount++;
217 }
218
219 void jbus_unref(struct jbus *jbus)
220 {
221         struct jservice *srv;
222         if (!--jbus->refcount) {
223                 dbus_connection_unref(jbus->connection);
224                 while((srv = jbus->services) != NULL) {
225                         jbus->services = srv->next;
226                         free(srv->method);
227                         free(srv);
228                 }
229                 free(jbus->name);
230                 free(jbus->path);
231                 free(jbus);
232         }
233 }
234
235 int jbus_replyj(struct jreq *jreq, const char *reply)
236 {
237         int rc = -1;
238         if (dbus_message_append_args(jreq->reply, DBUS_TYPE_STRING, &reply, DBUS_TYPE_INVALID)) {
239                 if (dbus_connection_send(jreq->connection, jreq->reply, NULL))
240                         rc = 0;
241         }
242         dbus_message_unref(jreq->reply);
243         dbus_connection_unref(jreq->connection);
244         free(jreq);
245         return rc;
246 }
247
248 int jbus_reply(struct jreq *jreq, struct json_object *reply)
249 {
250         const char *str = json_object_to_json_string(reply);
251         return jbus_replyj(jreq, str ? str : reply_out_of_memory);
252 }
253
254 int jbus_add_service(struct jbus *jbus, const char *method, void (*oncall)(struct jreq *jreq, struct json_object *request))
255 {
256         struct jservice *srv;
257
258         /* allocation */
259         srv = malloc(sizeof * srv);
260         if (srv == NULL) {
261                 errno = ENOMEM;
262                 goto error;
263         }
264         srv->method = strdup(method);
265         if (!srv->method) {
266                 errno = ENOMEM;
267                 goto error2;
268         }
269
270         /* record the service */
271         srv->oncall = oncall;
272         srv->next = jbus->services;
273         jbus->services = srv;
274
275         return 0;
276
277 error3:
278         free(srv->method);
279 error2:
280         free(srv);
281 error:
282         return -1;
283 }
284
285 int jbus_start_serving(struct jbus *jbus)
286 {
287         int status = dbus_bus_request_name(jbus->connection, jbus->name, DBUS_NAME_FLAG_DO_NOT_QUEUE, NULL);
288         switch (status) {
289         case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
290         case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
291                 return 0;
292         case DBUS_REQUEST_NAME_REPLY_EXISTS:
293         case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
294                 errno = EADDRINUSE;
295                 return -1;
296         }
297 }
298
299 int jbus_callj(struct jbus *jbus, const char *method, const char *query, void (*onresp)(int status, struct json_object *response, void *data), void *data)
300 {
301         int rc;
302         DBusMessage *msg;
303         struct jrespw *resp;
304
305         resp = malloc(sizeof * resp);
306         if (resp == NULL) {
307                 errno = ENOMEM;
308                 goto error;
309         }
310
311         msg = dbus_message_new_method_call(jbus->name, jbus->path, interface_jbus, method);
312         if (msg == NULL) {
313                 errno = ENOMEM;
314                 goto error2;
315         }
316
317         if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &query, DBUS_TYPE_INVALID)) {
318                 errno = ENOMEM;
319                 goto error3;
320         }
321
322         if (!dbus_connection_send(jbus->connection, msg, &resp->serial)) {
323                 goto error3;
324         }
325
326         dbus_message_unref(msg);
327         resp->data = data;
328         resp->onresp = onresp;
329         resp->next = jbus->waiters;
330         jbus->waiters = resp;
331         return 0;
332
333 error3:
334         dbus_message_unref(msg);
335 error2:
336         free(resp);
337 error:
338         return -1;
339 }
340
341
342 int jbus_call(struct jbus *jbus, const char *method, struct json_object *query, void (*onresp)(int status, struct json_object *response, void *data), void *data)
343 {
344         const char *str = json_object_to_json_string(query);
345         if (str == NULL) {
346                 errno = ENOMEM;
347                 return -1;
348         }
349         return jbus_callj(jbus, method, str, onresp, data);
350 }
351
352 #ifdef SERVER
353 #include <stdio.h>
354 #include <unistd.h>
355 void ping(struct jreq *jreq, struct json_object *request)
356 {
357 printf("ping(%s) -> %s\n",json_object_to_json_string(request),json_object_to_json_string(request));
358         jbus_reply(jreq, request);
359         json_object_put(request);
360 }
361 void incr(struct jreq *jreq, struct json_object *request)
362 {
363         static int counter = 0;
364         struct json_object *res = json_object_new_int(++counter);
365 printf("incr(%s) -> %s\n",json_object_to_json_string(request),json_object_to_json_string(res));
366         jbus_reply(jreq, res);
367         json_object_put(res);
368         json_object_put(request);
369 }
370 int main()
371 {
372         struct jbus *jbus = create_jbus(1, "/bzh/iot/jdbus");
373         int s1 = jbus_add_service(jbus, "ping", ping);
374         int s2 = jbus_add_service(jbus, "incr", incr);
375         int s3 = jbus_start_serving(jbus);
376         printf("started %d %d %d\n", s1, s2, s3);
377         while (dbus_connection_read_write_dispatch (jbus->connection, -1))
378                 ;
379 }
380 #endif
381 #ifdef CLIENT
382 #include <stdio.h>
383 #include <unistd.h>
384 void onresp(int status, struct json_object *response, void *data)
385 {
386         printf("resp: %d, %s, %s\n",status,(char*)data,json_object_to_json_string(response));
387         json_object_put(response);
388 }
389 int main()
390 {
391         struct jbus *jbus = create_jbus(1, "/bzh/iot/jdbus");
392         int i = 10;
393         while(i--) {
394                 jbus_callj(jbus, "ping", "{\"toto\":[1,2,3,4,true,\"toto\"]}", onresp, "ping");
395                 jbus_callj(jbus, "incr", "{\"doit\":\"for-me\"}", onresp, "incr");
396                 dbus_connection_read_write_dispatch (jbus->connection, 1);
397         }
398         while (dbus_connection_read_write_dispatch (jbus->connection, -1))
399                 ;
400 }
401 #endif
402