refactoring context handling
[src/app-framework-binder.git] / src / afb-ws-json.c
1 /*
2  * Copyright 2016 IoT.bzh
3  * Author: José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <assert.h>
23 #include <errno.h>
24 #include <string.h>
25
26 #include <json.h>
27
28 #include "afb-ws.h"
29 #include "afb-ws-json.h"
30 #include "session.h"
31 #include "afb-req-itf.h"
32 #include "afb-apis.h"
33
34 static void aws_on_close(struct afb_ws_json *ws, uint16_t code, char *text, size_t size);
35 static void aws_on_text(struct afb_ws_json *ws, char *text, size_t size);
36
37 static struct afb_ws_itf aws_itf = {
38         .on_close = (void*)aws_on_close,
39         .on_text = (void*)aws_on_text,
40         .on_binary = NULL,
41 };
42
43 struct afb_wsreq;
44
45 struct afb_ws_json
46 {
47         void (*cleanup)(void*);
48         void *cleanup_closure;
49         struct afb_wsreq *requests;
50         struct AFB_clientCtx *context;
51         struct json_tokener *tokener;
52         struct afb_ws *ws;
53 };
54
55 struct afb_ws_json *afb_ws_json_create(int fd, struct AFB_clientCtx *context, void (*cleanup)(void*), void *closure)
56 {
57         struct afb_ws_json *result;
58
59         assert(fd >= 0);
60         assert(context != NULL);
61
62         result = malloc(sizeof * result);
63         if (result == NULL)
64                 goto error;
65
66         result->cleanup = cleanup;
67         result->cleanup_closure = closure;
68         result->requests = NULL;
69         result->context = ctxClientGet(context);
70         if (result->context == NULL)
71                 goto error2;
72
73         result->tokener = json_tokener_new();
74         if (result->tokener == NULL)
75                 goto error3;
76
77         result->ws = afb_ws_create(fd, &aws_itf, result);
78         if (result->ws == NULL)
79                 goto error4;
80
81         return result;
82
83 error4:
84         json_tokener_free(result->tokener);
85 error3:
86         ctxClientPut(result->context);
87 error2:
88         free(result);
89 error:
90         close(fd);
91         return NULL;
92 }
93
94 static void aws_on_close(struct afb_ws_json *ws, uint16_t code, char *text, size_t size)
95 {
96         /* do nothing but free the text */
97         free(text);
98 }
99
100 #define CALL 2
101 #define RETOK 3
102 #define RETERR 4
103
104 struct afb_wsreq
105 {
106         struct afb_ws_json *aws;
107         struct afb_wsreq *next;
108         char *text;
109         size_t size;
110         int code;
111         char *id;
112         size_t idlen;
113         char *api;
114         size_t apilen;
115         char *verb;
116         size_t verblen;
117         char *obj;
118         size_t objlen;
119         char *tok;
120         size_t toklen;
121         struct json_object *root;
122 };
123
124 static struct json_object *wsreq_json(struct afb_wsreq *wsreq);
125 static struct afb_arg wsreq_get(struct afb_wsreq *wsreq, const char *name);
126 static void wsreq_fail(struct afb_wsreq *wsreq, const char *status, const char *info);
127 static void wsreq_success(struct afb_wsreq *wsreq, struct json_object *obj, const char *info);
128 static const char *wsreq_raw(struct afb_wsreq *wsreq, size_t *size);
129 static void wsreq_send(struct afb_wsreq *wsreq, char *buffer, size_t size);
130 static int wsreq_session_create(struct afb_wsreq *wsreq);
131 static int wsreq_session_check(struct afb_wsreq *wsreq, int refresh);
132 static void wsreq_session_close(struct afb_wsreq *wsreq);
133
134
135 static const struct afb_req_itf wsreq_itf = {
136         .json = (void*)wsreq_json,
137         .get = (void*)wsreq_get,
138         .success = (void*)wsreq_success,
139         .fail = (void*)wsreq_fail,
140         .raw = (void*)wsreq_raw,
141         .send = (void*)wsreq_send,
142         .session_create = (void*)wsreq_session_create,
143         .session_check = (void*)wsreq_session_check,
144         .session_close = (void*)wsreq_session_close,
145         .context_get = (void*)afb_context_get,
146         .context_set = (void*)afb_context_set
147
148 };
149
150 static int aws_wsreq_parse(struct afb_wsreq *r, char *text, size_t size)
151 {
152         char *pos, *end, c;
153         int aux;
154
155         /* scan */
156         pos = text;
157         end = text + size;
158
159         /* scans: [ */
160         while(pos < end && *pos == ' ') pos++;
161         if (pos == end) goto bad_header;
162         if (*pos++ != '[') goto bad_header;
163
164         /* scans code: 2|3|4 */
165         while(pos < end && *pos == ' ') pos++;
166         if (pos == end) goto bad_header;
167         switch (*pos++) {
168         case '2': r->code = CALL; break;
169         case '3': r->code = RETOK; break;
170         case '4': r->code = RETERR; break;
171         default: goto bad_header;
172         }
173
174         /* scans: , */
175         while(pos < end && *pos == ' ') pos++;
176         if (pos == end) goto bad_header;
177         if (*pos++ != ',') goto bad_header;
178
179         /* scans id: "id" */
180         while(pos < end && *pos == ' ') pos++;
181         if (pos == end) goto bad_header;
182         if (*pos++ != '"') goto bad_header;
183         r->id = pos;
184         while(pos < end && *pos != '"') pos++;
185         if (pos == end) goto bad_header;
186         r->idlen = (size_t)(pos++ - r->id);
187
188         /* scans: , */
189         while(pos < end && *pos == ' ') pos++;
190         if (pos == end) goto bad_header;
191         if (*pos++ != ',') goto bad_header;
192
193         /* scans the method if needed */
194         if (r->code == CALL) {
195                 /* scans: " */
196                 while(pos < end && *pos == ' ') pos++;
197                 if (pos == end) goto bad_header;
198                 if (*pos++ != '"') goto bad_header;
199
200                 /* scans: api/ */
201                 r->api = pos;
202                 while(pos < end && *pos != '"' && *pos != '/') pos++;
203                 if (pos == end) goto bad_header;
204                 if (*pos != '/') goto bad_header;
205                 r->apilen = (size_t)(pos++ - r->api);
206                 if (r->apilen && r->api[r->apilen - 1] == '\\')
207                         r->apilen--;
208
209                 /* scans: verb" */
210                 r->verb = pos;
211                 while(pos < end && *pos != '"') pos++;
212                 if (pos == end) goto bad_header;
213                 r->verblen = (size_t)(pos++ - r->verb);
214
215                 /* scans: , */
216                 while(pos < end && *pos == ' ') pos++;
217                 if (pos == end) goto bad_header;
218                 if (*pos++ != ',') goto bad_header;
219         }
220
221         /* scan obj */
222         while(pos < end && *pos == ' ') pos++;
223         if (pos == end) goto bad_header;
224         aux = 0;
225         r->obj = pos;
226         while (pos < end && (aux != 0 || (*pos != ',' && *pos != ']'))) {
227                 if (pos == end) goto bad_header;
228                 switch(*pos) {
229                 case '{': case '[': aux++; break;
230                 case '}': case ']': if (!aux--) goto bad_header; break;
231                 case '"':
232                         do {
233                                 pos += 1 + (*pos == '\\');
234                         } while(pos < end && *pos != '"');
235                 default:
236                         break;
237                 }
238                 pos++;
239         }
240         if (pos > end) goto bad_header;
241         if (pos == end && aux != 0) goto bad_header;
242         c = *pos;
243         r->objlen = (size_t)(pos++ - r->obj);
244         while (r->objlen && r->obj[r->objlen - 1] == ' ')
245                 r->objlen--;
246
247         /* scan the token (if any) */
248         if (c == ',') {
249                 /* scans token: "token" */
250                 while(pos < end && *pos == ' ') pos++;
251                 if (pos == end) goto bad_header;
252                 if (*pos++ != '"') goto bad_header;
253                 r->tok = pos;
254                 while(pos < end && *pos != '"') pos++;
255                 if (pos == end) goto bad_header;
256                 r->toklen = (size_t)(pos++ - r->tok);
257                 while(pos < end && *pos == ' ') pos++;
258                 if (pos == end) goto bad_header;
259                 c = *pos++;
260         }
261
262         /* scan: ] */
263         if (c != ']') goto bad_header;
264         while(pos < end && *pos == ' ') pos++;
265         if (pos != end) goto bad_header;
266
267         /* done */
268         r->text = text;
269         r->size = size;
270 fprintf(stderr, "\n\nONTEXT([%d, %.*s, %.*s/%.*s, %.*s, %.*s])\n\n",
271         r->code,
272         (int)r->idlen, r->id,
273         (int)r->apilen, r->api,
274         (int)r->verblen, r->verb,
275         (int)r->objlen, r->obj,
276         (int)r->toklen, r->tok
277 );
278         return 1;
279
280 bad_header:
281         return 0;
282 }
283
284 static void aws_on_text(struct afb_ws_json *ws, char *text, size_t size)
285 {
286         struct afb_req r;
287         struct afb_wsreq *wsreq;
288
289         /* allocate */
290         wsreq = calloc(1, sizeof *wsreq);
291         if (wsreq == NULL)
292                 goto alloc_error;
293
294         /* init */
295         if (!aws_wsreq_parse(wsreq, text, size))
296                 goto bad_header;
297
298         /* fill and record the request */
299         wsreq->aws = ws;
300         wsreq->next = ws->requests;
301         ws->requests = wsreq;
302
303         r.req_closure = wsreq;
304         r.itf = &wsreq_itf;
305         afb_apis_call(r, ws->context, wsreq->api, wsreq->apilen, wsreq->verb, wsreq->verblen);
306         return;
307
308 bad_header:
309         free(wsreq);
310 alloc_error:
311         free(text);
312         afb_ws_close(ws->ws, 1008);
313         return;
314 }
315
316 static struct json_object *wsreq_json(struct afb_wsreq *wsreq)
317 {
318         struct json_object *root = wsreq->root;
319         if (root == NULL) {
320                 json_tokener_reset(wsreq->aws->tokener);
321                 root = json_tokener_parse_ex(wsreq->aws->tokener, wsreq->obj, (int)wsreq->objlen);
322                 if (root == NULL) {
323                         /* lazy discovering !!!! not good TODO improve*/
324                         root = json_object_new_object();
325                 }
326                 wsreq->root = root;
327         }
328         return root;
329 }
330
331 static struct afb_arg wsreq_get(struct afb_wsreq *wsreq, const char *name)
332 {
333         struct afb_arg arg;
334         struct json_object *value, *root;
335
336         root = wsreq_json(wsreq);
337         if (json_object_object_get_ex(root, name, &value)) {
338                 arg.name = name;
339                 arg.value = json_object_get_string(value);
340         } else {
341                 arg.name = NULL;
342                 arg.value = NULL;
343         }
344         arg.path = NULL;
345         return arg;
346 }
347
348 static int wsreq_session_create(struct afb_wsreq *wsreq)
349 {
350         struct AFB_clientCtx *context = wsreq->aws->context;
351         if (context->created)
352                 return 0;
353         return wsreq_session_check(wsreq, 1);
354 }
355
356 static int wsreq_session_check(struct afb_wsreq *wsreq, int refresh)
357 {
358         struct AFB_clientCtx *context = wsreq->aws->context;
359
360         if (wsreq->tok == NULL)
361                 return 0;
362
363         if (!ctxTokenCheckLen (context, wsreq->tok, wsreq->toklen))
364                 return 0;
365
366         if (refresh) {
367                 ctxTokenNew (context);
368         }
369
370         return 1;
371 }
372
373 static void wsreq_session_close(struct afb_wsreq *wsreq)
374 {
375         struct AFB_clientCtx *context = wsreq->aws->context;
376         ctxClientClose(context);
377 }
378
379
380 static void wsreq_reply(struct afb_wsreq *wsreq, int retcode, const char *status, const char *info, json_object *resp)
381 {
382         json_object *root, *request, *reply;
383         const char *message;
384
385         /* builds the answering structure */
386         root = json_object_new_object();
387         json_object_object_add(root, "jtype", json_object_new_string("afb-reply"));
388         request = json_object_new_object();
389         json_object_object_add(root, "request", request);
390         json_object_object_add(request, "status", json_object_new_string(status));
391         if (info)
392                 json_object_object_add(request, "info", json_object_new_string(info));
393         if (resp)
394                 json_object_object_add(root, "response", resp);
395
396         /* make the reply */
397         reply = json_object_new_array();
398         json_object_array_add(reply, json_object_new_int(retcode));
399         json_object_array_add(reply, json_object_new_string_len(wsreq->id, (int)wsreq->idlen));
400         json_object_array_add(reply, root);
401         json_object_array_add(reply, json_object_new_string(wsreq->aws->context->token));
402
403         /* emits the reply */
404         message = json_object_to_json_string(reply);
405         afb_ws_text(wsreq->aws->ws, message, strlen(message));
406         json_object_put(reply);
407
408         /* TODO eliminates the wsreq */
409 }
410
411 static void wsreq_fail(struct afb_wsreq *wsreq, const char *status, const char *info)
412 {
413         wsreq_reply(wsreq, RETERR, status, info, NULL);
414 }
415
416 static void wsreq_success(struct afb_wsreq *wsreq, json_object *obj, const char *info)
417 {
418         wsreq_reply(wsreq, RETOK, "success", info, obj);
419 }
420
421 static const char *wsreq_raw(struct afb_wsreq *wsreq, size_t *size)
422 {
423         *size = wsreq->objlen;
424         return wsreq->obj;
425 }
426
427 static void wsreq_send(struct afb_wsreq *wsreq, char *buffer, size_t size)
428 {
429         afb_ws_text(wsreq->aws->ws, buffer, size);
430 }
431