improves websockets
[src/app-framework-binder.git] / src / afb-ws.c
1 /*
2  * Copyright 2016 IoT.bzh
3  * Author: José Bollo <jose.bollo@iot.bzh>
4  *
5  * Inspired by the work of 
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 #define _GNU_SOURCE
21 #include <assert.h>
22 #include <errno.h>
23 #include <sys/uio.h>
24 #include <string.h>
25
26 #include <json.h>
27
28 #include "websock.h"
29
30 #include "utils-upoll.h"
31
32 static ssize_t aws_writev(struct afb_ws *ws, const struct iovec *iov, int iovcnt);
33 static ssize_t aws_readv(struct afb_ws *ws, const struct iovec *iov, int iovcnt);
34 static void aws_disconnect(struct afb_ws *ws);
35 static void aws_on_close(struct afb_ws *ws, uint16_t code, size_t size);
36 static void aws_on_content(struct afb_ws *ws, int last, size_t size);
37 static void aws_on_readable(struct afb_ws *ws);
38
39 static struct websock_itf aws_itf = {
40         .writev = (void*)aws_writev,
41         .readv = (void*)aws_readv,
42         .disconnect = (void*)aws_disconnect,
43
44         .on_ping = NULL,
45         .on_pong = NULL,
46         .on_close = (void*)aws_on_close,
47         .on_text = (void*)aws_on_content,
48         .on_binary = (void*)aws_on_content,
49         .on_continue = (void*)aws_on_content,
50         .on_extension = NULL,
51 };
52
53 struct afb_wsreq
54 {
55         struct afb_ws *aws;
56         struct afb_wsreq *next;
57         struct json_object *id;
58         struct json_object *name;
59         struct json_object *token;
60         struct json_object *request;
61 };
62
63 struct afb_ws
64 {
65         int fd;
66         struct upoll *up;
67         struct websock *ws;
68         void (*cleanup)(void*);
69         void *cleanup_closure;
70         struct AFB_clientCtx *context;
71         struct afb_wsreq *requests;
72 };
73
74 static struct afb_arg wsreq_get(struct afb_wsreq *wsreq, const char *name);
75 static void wsreq_iterate(struct afb_wsreq *wsreq, int (*iterator)(void *closure, struct afb_arg arg), void *closure);
76 static void wsreq_fail(struct afb_wsreq *wsreq, const char *status, const char *info);
77 static void wsreq_success(struct afb_wsreq *wsreq, struct json_object *obj, const char *info);
78 static int wsreq_session_create(struct afb_wsreq *wsreq);
79 static int wsreq_session_check(struct afb_wsreq *wsreq, int refresh);
80 static void wsreq_session_close(struct afb_wsreq *wsreq);
81
82 static const struct afb_req_itf wsreq_itf = {
83         .get = (void*)wsreq_get,
84         .iterate = (void*)wsreq_iterate,
85         .fail = (void*)wsreq_fail,
86         .success = (void*)wsreq_success,
87         .session_create = (void*)wsreq_session_create,
88         .session_check = (void*)wsreq_session_check,
89         .session_close = (void*)wsreq_session_close
90 };
91
92 struct afb_ws *afb_ws_create(int fd, struct AFB_clientCtx *context, void (*cleanup)(void*), void *closure)
93 {
94         struct afb_ws *result;
95
96         assert(fd >= 0);
97         assert(context != NULL);
98
99         result = malloc(sizeof * result);
100         if (result == NULL)
101                 goto error;
102
103         result->fd = fd;
104         result->cleanup = cleanup;
105         result->cleanup_closure = closure;
106         result->context = ctxClientGet(context);
107         if (result->context == NULL)
108                 goto error2;
109
110         result->ws = websock_create_v13(&aws_itf, result);
111         if (result->ws == NULL)
112                 goto error3;
113
114         result->up = upoll_open(result->fd, result);
115         if (result->up == NULL)
116                 goto error4;
117
118         upoll_on_readable(result->up, (void*)aws_on_readable);
119         upoll_on_hangup(result->up, (void*)aws_disconnect);
120         return result;
121 error4:
122         websock_destroy(result->ws);
123 error3:
124         ctxClientPut(result->context);
125 error2:
126         free(result);
127 error:
128         close(fd);
129         return NULL;
130 }
131
132 static ssize_t aws_writev(struct afb_ws *ws, const struct iovec *iov, int iovcnt)
133 {
134         ssize_t rc;
135         do {
136                 rc = writev(ws->fd, iov, iovcnt);
137         } while(rc == -1 && errno == EINTR);
138         return rc;
139 }
140
141 static ssize_t aws_readv(struct afb_ws *ws, const struct iovec *iov, int iovcnt)
142 {
143         ssize_t rc;
144         do {
145                 rc = readv(ws->fd, iov, iovcnt);
146         } while(rc == -1 && errno == EINTR);
147         return rc;
148 }
149
150 static void aws_disconnect(struct afb_ws *ws)
151 {
152         upoll_close(ws->up);
153         websock_destroy(ws->ws);
154         close(ws->fd);
155         MHD_resume_connection (ws->connection);
156         ctxClientPut(ws->context);
157         json_tokener_free(ws->tokener);
158         free(ws);
159 }
160
161 static void aws_on_close(struct afb_ws *ws, uint16_t code, size_t size)
162 {
163         /* do nothing */
164 }
165
166 static void aws_on_readable(struct afb_ws *ws)
167 {
168         websock_dispatch(ws->ws);
169 }
170
171 static int aws_handle_json(struct afb_ws *aws, struct json_object *obj)
172 {
173         struct afb_req r;
174         int count, num;
175         struct json_object *type, *id, *name, *req, *token;
176         struct afb_wsreq *wsreq;
177         const char *api, *verb;
178         size_t lenapi, lenverb;
179
180         /* protocol inspired by http://www.gir.fr/ocppjs/ocpp_srpc_spec.shtml */
181
182         /* the object must be an array of 4 or 5 elements */
183         if (!json_object_is_type(obj, json_type_array))
184                 goto error;
185         count = json_object_array_length(obj);
186         if (count < 4 || count > 5)
187                 goto error;
188
189         /* get the 5 elements: type id name request token */
190         type = json_object_array_get_idx(obj, 0);
191         id = json_object_array_get_idx(obj, 1);
192         name = json_object_array_get_idx(obj, 2);
193         req = json_object_array_get_idx(obj, 3);
194         token = json_object_array_get_idx(obj, 4);
195
196         /* check the types: int string string object string */
197         if (!json_object_is_type(type, json_type_int))
198                 goto error;
199         if (!json_object_is_type(id, json_type_string))
200                 goto error;
201         if (!json_object_is_type(name, json_type_string))
202                 goto error;
203         if (!json_object_is_type(req, json_type_object))
204                 goto error;
205         if (token != NULL && !json_object_is_type(token, json_type_string))
206                 goto error;
207
208         /* the type is only 2 */
209         num = json_object_get_int(type);
210         if (num != 2)
211                 goto error;
212
213         /* checks the api/verb structure of name */
214         api = json_object_get_string(name);
215         for (lenapi = 0 ; api[lenapi] && api[lenapi] != '/' ; lenapi++);
216         if (!lenapi || !api[lenapi])
217                 goto error;
218         verb = &api[lenapi+1];
219         for (lenverb = 0 ; verb[lenverb] && verb[lenverb] != '/' ; lenverb++);
220         if (!lenverb || verb[lenverb])
221                 goto error;
222
223         /* allocates the request data */
224         wsreq = malloc(sizeof *wsreq);
225         if (wsreq == NULL)
226                 goto error;
227
228         /* fill and record the request */
229         wsreq->aws = aws;
230         wsreq->id = json_object_get(id);
231         wsreq->name = json_object_get(name);
232         wsreq->token = json_object_get(token);
233         wsreq->request = json_object_get(req);
234         wsreq->next = aws->requests;
235         aws->requests = wsreq;
236         json_object_put(obj);
237
238         r.data = wsreq;
239         r.itf = &wsreq_itf;
240         afb_apis_call(r, aws->context, api, lenapi, verb, lenverb);
241         return 1;
242
243 error:
244         json_object_put(obj);
245         return 0;
246 }
247
248 static void aws_on_content(struct afb_ws *ws, int last, size_t size)
249 {
250         ssize_t rrc;
251         char buffer[8000];
252         struct json_object *obj;
253
254         json_tokener_reset(ws->tokener);
255         while(size) {
256                 rrc = websock_read(ws->ws, buffer,
257                                 size > sizeof buffer ? sizeof buffer : size);
258                 if (rrc < 0) {
259                         websock_close(ws->ws);
260                         return;
261                 }
262                 size -= (size_t)rrc;
263                 obj = json_tokener_parse_ex(ws->tokener, buffer, (int)rrc);
264                 if (obj != NULL) {
265                         if (!aws_handle_json(ws, obj)) {
266                                 websock_close(ws->ws);
267                                 return;
268                         }
269                 } else if (json_tokener_get_error(ws->tokener) != json_tokener_continue) {
270                         websock_close(ws->ws);
271                         return;
272                 }
273         }
274 }
275
276 static struct afb_arg wsreq_get(struct afb_wsreq *wsreq, const char *name)
277 {
278         struct afb_arg arg;
279         struct json_object *value;
280
281         if (json_object_object_get_ex(wsreq->request, name, &value)) {
282                 arg.name = name;
283                 arg.value = json_object_get_string(value);
284                 arg.size = strlen(arg.value);
285         } else {
286                 arg.name = NULL;
287                 arg.value = NULL;
288                 arg.size = 0;
289         }
290         arg.path = NULL;
291         return arg;
292 }
293
294 static void wsreq_iterate(struct afb_wsreq *wsreq, int (*iterator)(void *closure, struct afb_arg arg), void *closure)
295 {
296         struct afb_arg arg;
297         struct json_object_iterator it = json_object_iter_begin(wsreq->request);
298         struct json_object_iterator end = json_object_iter_end(wsreq->request);
299
300         arg.size = 0;
301         arg.path = NULL;
302         while(!json_object_iter_equal(&it, &end)) {
303                 arg.name = json_object_iter_peek_name(&it);
304                 arg.value = json_object_get_string(json_object_iter_peek_value(&it));
305                 if (!iterator(closure, arg))
306                         break;
307                 json_object_iter_next(&it);
308         }
309 }
310
311 static int wsreq_session_create(struct afb_wsreq *wsreq)
312 {
313         struct AFB_clientCtx *context = wsreq->aws->context;
314         if (context->created)
315                 return 0;
316         return wsreq_session_check(wsreq, 1);
317 }
318
319 static int wsreq_session_check(struct afb_wsreq *wsreq, int refresh)
320 {
321         const char *token;
322         struct AFB_clientCtx *context = wsreq->aws->context;
323
324         if (wsreq->token == NULL)
325                 return 0;
326
327         token = json_object_get_string(wsreq->token);
328         if (token == NULL)
329                 return 0;
330
331         if (!ctxTokenCheck (context, token))
332                 return 0;
333
334         if (refresh) {
335                 ctxTokenNew (context);
336         }
337
338         return 1;
339 }
340
341 static void wsreq_session_close(struct afb_wsreq *wsreq)
342 {
343         struct AFB_clientCtx *context = wsreq->aws->context;
344         ctxClientClose(context);
345 }
346
347
348 static void wsreq_reply(struct afb_wsreq *wsreq, int retcode, const char *status, const char *info, json_object *resp)
349 {
350         json_object *root, *request, *reply;
351         const char *message;
352
353         /* builds the answering structure */
354         root = json_object_new_object();
355         json_object_object_add(root, "jtype", json_object_new_string("afb-reply"));
356         request = json_object_new_object();
357         json_object_object_add(root, "request", request);
358         json_object_object_add(request, "status", json_object_new_string(status));
359         if (info)
360                 json_object_object_add(request, "info", json_object_new_string(info));
361         if (resp)
362                 json_object_object_add(root, "response", resp);
363
364         /* make the reply */
365         reply = json_object_new_array();
366         json_object_array_add(reply, json_object_new_int(retcode));
367         json_object_array_add(reply, wsreq->id);
368         json_object_array_add(reply, root);
369         json_object_array_add(reply, json_object_new_string(wsreq->aws->context->token));
370
371         /* emits the reply */
372         message = json_object_to_json_string(reply);
373         websock_text(wsreq->aws->ws, message, strlen(message));
374         json_object_put(reply);
375
376         /* TODO eliminates the wsreq */
377 }
378
379 static void wsreq_fail(struct afb_wsreq *wsreq, const char *status, const char *info)
380 {
381         wsreq_reply(wsreq, 4, status, info, NULL);
382 }
383
384 static void wsreq_success(struct afb_wsreq *wsreq, json_object *obj, const char *info)
385 {
386         wsreq_reply(wsreq, 3, "success", info, obj);
387 }
388