adding 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 int send_reply(struct jreq *jreq, const char *reply)
64 {
65         int rc = -1;
66         if (dbus_message_append_args(jreq->reply, DBUS_TYPE_STRING, &reply, DBUS_TYPE_INVALID)) {
67                 if (dbus_connection_send(jreq->connection, jreq->reply, NULL))
68                         rc = 0;
69         }
70         dbus_message_unref(jreq->reply);
71         dbus_connection_unref(jreq->connection);
72         free(jreq);
73         return rc;
74 }
75
76 static DBusHandlerResult incoming_resp(DBusConnection *connection, DBusMessage *message, struct jbus *jbus)
77 {
78         int status;
79         const char *str;
80         struct jrespw *jrw, **prv;
81         struct json_object *reply;
82         dbus_uint32_t serial;
83
84         /* search for the waiter */
85         serial = dbus_message_get_serial(message);
86         prv = &jbus->waiters;
87         while ((jrw = *prv) != NULL && jrw->serial != serial)
88                 prv = &jrw->next;
89         if (jrw == NULL)
90                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
91         *prv = jrw->next;
92
93         /* retrieve the json value */
94         if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID)) {
95                 status = -1;
96                 reply = NULL;
97         } else {
98                 reply = json_tokener_parse(str);
99                 status = reply ? 0 : -1;
100         }
101
102         /* treat it */
103         jrw->onresp(status, reply, jrw->data);
104         free(jrw);
105         return DBUS_HANDLER_RESULT_HANDLED;
106 }
107
108 static int matchitf(DBusMessage *message)
109 {
110         const char *itf = dbus_message_get_interface(message);
111         return itf != NULL && !strcmp(itf, interface_jbus);
112 }
113
114 static DBusHandlerResult incoming_call(DBusConnection *connection, DBusMessage *message, struct jbus *jbus)
115 {
116         struct jservice *srv;
117         struct jreq *jreq;
118         const char *str;
119         const char *method;
120         struct json_object *query;
121
122         /* search for the service */
123         if (!matchitf(message))
124                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
125         method = dbus_message_get_member(message);
126         if (method == NULL)
127                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
128         srv = jbus->services;
129         while(srv != NULL && strcmp(method, srv->method))
130                 srv = srv->next;
131         if (srv == NULL)
132                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
133
134         /* handle the message */
135         jreq = malloc(sizeof * jreq);
136         if (jreq == NULL)
137                 return DBUS_HANDLER_RESULT_NEED_MEMORY;
138         jreq->reply = dbus_message_new_method_return(message);
139         if (jreq->reply == NULL) {
140                 free(jreq);
141                 return DBUS_HANDLER_RESULT_NEED_MEMORY;
142         }
143         jreq->connection = dbus_connection_ref(jbus->connection);
144         
145         /* retrieve the json value */
146         if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID)) {
147                 send_reply(jreq, reply_invalid);
148                 return DBUS_HANDLER_RESULT_HANDLED;
149         }
150         query = json_tokener_parse(str);
151         if (query == NULL) {
152                 send_reply(jreq, reply_invalid);
153                 return DBUS_HANDLER_RESULT_HANDLED;
154         }
155
156         /* treat it */
157         srv->oncall(jreq, query);
158         return DBUS_HANDLER_RESULT_HANDLED;
159 }
160
161 static DBusHandlerResult incoming(DBusConnection *connection, DBusMessage *message, void *data)
162 {
163         switch(dbus_message_get_type(message)) {
164         case DBUS_MESSAGE_TYPE_METHOD_CALL:
165                 return incoming_call(connection, message, (struct jbus*)data);
166         case  DBUS_MESSAGE_TYPE_METHOD_RETURN:
167                 return incoming_resp(connection, message, (struct jbus*)data);
168         }
169         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
170 }
171
172
173 struct jbus *create_jbus(int session, const char *path)
174 {
175         struct jbus *jbus;
176         char *name;
177
178         /* create the context and connect */
179         jbus = calloc(1, sizeof * jbus);
180         if (jbus == NULL) {
181                 errno = ENOMEM;
182                 goto error;
183         }
184         jbus->refcount = 1;
185         jbus->path = strdup(path);
186         jbus->name = NULL;
187         if (jbus->path == NULL) {
188                 errno = ENOMEM;
189                 goto error2;
190         }
191         while(*path == '/') path++;
192         jbus->name = name = strdup(path);
193         if (name == NULL) {
194                 errno = ENOMEM;
195                 goto error2;
196         }
197         while(*name) {
198                 if (*name == '/')
199                         *name = '.';
200                 name++;
201         }
202         name--;
203         while (name >= jbus->name && *name == '.')
204                 *name-- = 0;
205         if (!*jbus->name) {
206                 errno = EINVAL;
207                 goto error2;
208         }
209
210         /* connect */
211         jbus->connection = dbus_bus_get(session ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, NULL);
212         if (jbus->connection == NULL) {
213                 goto error2;
214         }
215         if (!dbus_connection_add_filter(jbus->connection, incoming, jbus, NULL)) {
216                 goto error2;
217         }
218
219         return jbus;
220
221 error2:
222         jbus_unref(jbus);
223 error:
224         return NULL;
225 }
226
227 void jbus_addref(struct jbus *jbus)
228 {
229         jbus->refcount++;
230 }
231
232 void jbus_unref(struct jbus *jbus)
233 {
234         struct jservice *srv;
235         if (!--jbus->refcount) {
236                 dbus_connection_unref(jbus->connection);
237                 while((srv = jbus->services) != NULL) {
238                         jbus->services = srv->next;
239                         free(srv->method);
240                         free(srv);
241                 }
242                 free(jbus->name);
243                 free(jbus->path);
244                 free(jbus);
245         }
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 send_reply(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