moving to cmake
[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 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