8c144ad817688d1a524c5b1d698362b05d17856d
[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         default:
295                 errno = EADDRINUSE;
296                 return -1;
297         }
298 }
299
300 int jbus_read_write_dispatch(struct jbus *jbus, int toms)
301 {
302         if (dbus_connection_read_write_dispatch(jbus->connection, toms));
303                 return 0;
304         errno = EPIPE;
305         return -1;
306 }
307
308 int jbus_callj(struct jbus *jbus, const char *method, const char *query, void (*onresp)(int status, struct json_object *response, void *data), void *data)
309 {
310         DBusMessage *msg;
311         struct jrespw *resp;
312
313         resp = malloc(sizeof * resp);
314         if (resp == NULL) {
315                 errno = ENOMEM;
316                 goto error;
317         }
318
319         msg = dbus_message_new_method_call(jbus->name, jbus->path, interface_jbus, method);
320         if (msg == NULL) {
321                 errno = ENOMEM;
322                 goto error2;
323         }
324
325         if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &query, DBUS_TYPE_INVALID)) {
326                 errno = ENOMEM;
327                 goto error3;
328         }
329
330         if (!dbus_connection_send(jbus->connection, msg, &resp->serial)) {
331                 goto error3;
332         }
333
334         dbus_message_unref(msg);
335         resp->data = data;
336         resp->onresp = onresp;
337         resp->next = jbus->waiters;
338         jbus->waiters = resp;
339         return 0;
340
341 error3:
342         dbus_message_unref(msg);
343 error2:
344         free(resp);
345 error:
346         return -1;
347 }
348
349
350 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)
351 {
352         const char *str = json_object_to_json_string(query);
353         if (str == NULL) {
354                 errno = ENOMEM;
355                 return -1;
356         }
357         return jbus_callj(jbus, method, str, onresp, data);
358 }
359
360 #ifdef SERVER
361 #include <stdio.h>
362 #include <unistd.h>
363 void ping(struct jreq *jreq, struct json_object *request)
364 {
365 printf("ping(%s) -> %s\n",json_object_to_json_string(request),json_object_to_json_string(request));
366         jbus_reply(jreq, request);
367         json_object_put(request);
368 }
369 void incr(struct jreq *jreq, struct json_object *request)
370 {
371         static int counter = 0;
372         struct json_object *res = json_object_new_int(++counter);
373 printf("incr(%s) -> %s\n",json_object_to_json_string(request),json_object_to_json_string(res));
374         jbus_reply(jreq, res);
375         json_object_put(res);
376         json_object_put(request);
377 }
378 int main()
379 {
380         struct jbus *jbus = create_jbus(1, "/bzh/iot/jdbus");
381         int s1 = jbus_add_service(jbus, "ping", ping);
382         int s2 = jbus_add_service(jbus, "incr", incr);
383         int s3 = jbus_start_serving(jbus);
384         printf("started %d %d %d\n", s1, s2, s3);
385         while (!jbus_read_write_dispatch (jbus, -1))
386                 ;
387 }
388 #endif
389 #ifdef CLIENT
390 #include <stdio.h>
391 #include <unistd.h>
392 void onresp(int status, struct json_object *response, void *data)
393 {
394         printf("resp: %d, %s, %s\n",status,(char*)data,json_object_to_json_string(response));
395         json_object_put(response);
396 }
397 int main()
398 {
399         struct jbus *jbus = create_jbus(1, "/bzh/iot/jdbus");
400         int i = 10;
401         while(i--) {
402                 jbus_callj(jbus, "ping", "{\"toto\":[1,2,3,4,true,\"toto\"]}", onresp, "ping");
403                 jbus_callj(jbus, "incr", "{\"doit\":\"for-me\"}", onresp, "incr");
404                 jbus_read_write_dispatch (jbus, 1);
405         }
406         while (!jbus_read_write_dispatch (jbus, -1))
407                 ;
408 }
409 #endif
410