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