afb-api-js: first step for javascript bindings
[src/app-framework-binder.git] / src / afb-api-js.c
1 /*
2  * Copyright (C) 2016, 2017 "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 <stdio.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27
28 #include <json-c/json.h>
29 #include "duktape.h"
30
31 #include "afb-common.h"
32 #include "afb-api.h"
33 #include "afb-apiset.h"
34 #include "afb-api-js.h"
35 #include "afb-xreq.h"
36 #include "jobs.h"
37 #include "verbose.h"
38
39 /********************************************************************/
40 struct jsapi
41 {
42         int logmask;
43         duk_context *context;
44         char api[1];
45 };
46
47 /********************************************************************/
48 static void jsapi_call(void *closure, struct afb_xreq *xreq);
49 static int jsapi_service_start(void *closure);
50 static int jsapi_get_logmask(void *closure);
51 static void jsapi_set_logmask(void *closure, int level);
52 static struct json_object *jsapi_describe(void *closure);
53
54 static struct afb_api_itf jsapi_itf =
55 {
56         .call = jsapi_call,
57         .service_start = jsapi_service_start,
58         .set_logmask = jsapi_set_logmask,
59         .get_logmask = jsapi_get_logmask,
60         .describe = jsapi_describe
61 };
62
63 /********************************************************************/
64 static duk_ret_t do_success(duk_context *ctx);
65 static duk_ret_t do_fail(duk_context *ctx);
66 static duk_ret_t do_subcall_sync(duk_context *ctx);
67 static duk_ret_t do_error(duk_context *ctx);
68 static duk_ret_t do_require(duk_context *ctx);
69
70 static const duk_function_list_entry funcs[] =
71 {
72         { "afb_req_success", do_success, 3 },
73         { "afb_req_fail", do_fail, 3 },
74         { "afb_req_subcall_sync", do_subcall_sync, 4 },
75         { "afb_error", do_error, 1 },
76         { "require", do_require, 1 },
77         { NULL, NULL, 0 }
78 };
79
80 /********************************************************************/
81
82 static void on_heap_fatal(void *udata, const char *msg)
83 {
84         ERROR("Got fatal from duktape: %s", msg);
85         abort();
86 }
87
88 static int jsapi_load(duk_context *ctx, const char *path)
89 {
90         static const char prefix[] = "function(exports){";
91         static const char suffix[] = "}";
92
93         int fd, rc;
94         struct stat st;
95         char *buffer;
96         ssize_t s;
97
98         fd = afb_common_rootdir_open_locale(path, O_RDONLY, NULL);
99         if (fd < 0) {
100                 fd = open(path, O_RDONLY);
101                 if (fd < 0) {
102                         ERROR("Can't open %s: %m", path);
103                         duk_push_error_object(ctx, DUK_ERR_ERROR, "Can't open file %s: %m", path);
104                         goto error;
105                 }
106         }
107
108         rc = fstat(fd, &st);
109         if (rc < 0) {
110                 goto error2;
111         }
112
113         buffer = alloca(st.st_size + sizeof prefix + sizeof suffix);
114         s = read(fd, &buffer[sizeof prefix - 1], st.st_size);
115         if (s < 0)
116                 goto error2;
117         if (s != st.st_size)
118                 goto error2;
119
120         memcpy(buffer, prefix, sizeof prefix - 1);
121         memcpy(&buffer[sizeof prefix - 1 + st.st_size], suffix, sizeof suffix);
122         close(fd);
123
124         duk_push_object(ctx); /* exports */
125         duk_push_string(ctx, path); /* exports path */
126         rc = duk_pcompile_string_filename(ctx, DUK_COMPILE_FUNCTION|DUK_COMPILE_STRICT, buffer); /* exports func */
127         if (rc) {
128                 duk_dup_top(ctx); /* exports error error */
129                 ERROR("compiling of %s failed: %s", path, duk_safe_to_string(ctx, -1)); /* exports error error */
130                 duk_pop(ctx); /* exports error */
131                 duk_replace(ctx, -2); /* error */
132                 goto error;
133         }
134         duk_dup(ctx, -2); /* exports func exports */
135         rc = duk_pcall(ctx, 1); /* exports ret */
136         if (rc) {
137                 duk_dup_top(ctx); /* exports error error */
138                 if (!duk_is_error(ctx, -1)) {
139                         duk_push_error_object(ctx, DUK_ERR_ERROR, "%s", duk_safe_to_string(ctx, -1)); /* exports error error error */
140                         duk_replace(ctx, -3); /* exports error error */
141                 }
142                 ERROR("initialisation of %s failed: %s", path, duk_safe_to_string(ctx, -1)); /* exports error */
143                 duk_pop(ctx); /* exports error */
144                 duk_replace(ctx, -2); /* error */
145                 goto error;
146         }
147         duk_pop(ctx); /* exports */
148         return 1;
149 error2:
150         ERROR("can't process file %s: %m", path);
151         duk_push_error_object(ctx, DUK_ERR_ERROR, "Can't process file %s: %m", path);
152         close(fd);
153 error:
154         return duk_throw(ctx);
155 }
156
157 static duk_ret_t do_require(duk_context *ctx)
158 {
159         int rc;
160         const char *path;
161
162         path = duk_require_string(ctx, -1); /* path */
163         duk_push_global_stash(ctx); /* path gstash */
164         duk_dup(ctx, -2); /* path gstash path */
165         rc = duk_get_prop(ctx, -2); /* path gstash ? */
166         if (!rc) {
167                 /* path gstash error (ELSE: path gstash exports) */
168                 duk_pop(ctx); /* path gstash */
169                 rc = jsapi_load(ctx, path); /* path gstash ? */
170                 if (rc > 0) {
171                         /* path gstash exports (ELSE: path gstash error) */
172                         duk_dup_top(ctx); /* path gstash exports exports */
173                         duk_swap(ctx, -2, -4); /* exports gstash path exports */
174                         duk_put_prop(ctx, -3); /* exports gstash */
175                         duk_pop(ctx); /* exports */
176                 }
177         }
178         return 1;
179 }
180
181 static void jsapi_destroy(struct jsapi *jsapi)
182 {
183         duk_destroy_heap(jsapi->context);
184         free(jsapi);
185 }
186
187 static struct jsapi *jsapi_create(const char *path)
188 {
189         struct jsapi *jsapi;
190         duk_context *ctx;
191         const char *api, *ext;
192
193         /* allocate and initialise names */
194         api = strrchr(path, '/');
195         api = api ? api + 1 : path;
196         ext = strrchr(api, '.') ?: &api[strlen(api)];
197         jsapi = malloc(sizeof *jsapi + 1 + (ext - api));
198         if (!jsapi)
199                 goto error;
200         memcpy(jsapi->api, api, ext - api);
201         jsapi->api[ext - api] = 0;
202         jsapi->logmask = logmask;
203
204         /* create the duktape context */
205         ctx = duk_create_heap(NULL, NULL, NULL, NULL, on_heap_fatal);
206         if (!ctx)
207                 goto error;
208
209         jsapi->context = ctx;
210
211         /* populate global with functions */
212         duk_push_global_object(ctx);
213         duk_put_function_list(ctx, -1, funcs);
214         duk_pop(ctx);
215
216         /* call the require path */
217         ctx = jsapi->context;
218         duk_get_global_string(ctx, "require");
219         duk_push_string(ctx, path);
220         duk_pcall(ctx, 1);
221         if (duk_is_error(ctx, -1)) {
222                 const char *message, *file, *stack;
223                 int line;
224                 duk_get_prop_string(ctx, -1, "message");
225                 message = duk_get_string(ctx, -1);
226                 duk_get_prop_string(ctx, -2, "fileName");
227                 file = duk_get_string(ctx, -1);
228                 duk_get_prop_string(ctx, -3, "lineNumber");
229                 line = (int)duk_get_int(ctx, -1);
230                 duk_get_prop_string(ctx, -4, "stack");
231                 stack = duk_get_string(ctx, -1);
232                 ERROR("Initialisation of API %s from jsapi %s failed file %s (file %s, line %d) stack:\n%s\n", jsapi->api, path, message, file, line, stack);
233                 jsapi_destroy(jsapi);
234                 return NULL;
235         }
236         duk_put_global_string(ctx, "exports");
237         return jsapi;
238
239 error:
240         ERROR("out of memory");
241         free(jsapi);
242         errno = ENOMEM;
243         return NULL;
244 }
245
246 int afb_api_js_add(const char *path, struct afb_apiset *declare_set, struct afb_apiset* call_set)
247 {
248         int rc;
249         struct jsapi *jsapi;
250         struct afb_api_item api;
251
252         jsapi = jsapi_create(path);
253         if (!jsapi)
254                 goto error;
255
256         api.closure = jsapi;
257         api.itf = &jsapi_itf;
258         api.group = jsapi;
259         rc = afb_apiset_add(declare_set, jsapi->api, api);
260         if (!rc)
261                 return 0;
262
263         duk_destroy_heap(jsapi->context);
264         free(jsapi);
265 error:
266         return -1;
267 }
268
269 /********************************************************************/
270
271 static duk_ret_t do_success(duk_context *ctx)
272 {
273         struct afb_xreq *xreq;
274         const char *json, *info;
275
276         xreq = duk_get_pointer(ctx, -3);
277         duk_json_encode(ctx, -2);
278         json = duk_get_string(ctx, -2);
279         info = duk_get_string(ctx, -1);
280
281         afb_xreq_reply(xreq, json ? json_tokener_parse(json) : NULL, NULL, info);
282         return 0;
283 }
284
285 static duk_ret_t do_fail(duk_context *ctx)
286 {
287         struct afb_xreq *xreq;
288         const char *status, *info;
289
290         xreq = duk_get_pointer(ctx, -3);
291         status = duk_get_string(ctx, -2);
292         info = duk_get_string(ctx, -1);
293
294         afb_xreq_reply(xreq, NULL, status ?: "error", info);
295         return 0;
296 }
297
298 static duk_ret_t do_subcall_sync(duk_context *ctx)
299 {
300         int rc;
301         struct afb_xreq *xreq;
302         const char *api, *verb, *json;
303         struct json_object *resu;
304
305         xreq = duk_get_pointer(ctx, -4);
306         api = duk_get_string(ctx, -3);
307         verb = duk_get_string(ctx, -2);
308         duk_json_decode(ctx, -1);
309         json = duk_get_string(ctx, -1);
310
311         resu = NULL;
312         rc = afb_xreq_legacy_subcall_sync(xreq, api, verb, json ? json_tokener_parse(json) : NULL, &resu);
313         if (rc)
314                 duk_push_null(ctx);
315         else {
316                 duk_push_string(ctx, json_object_to_json_string(resu));
317                 duk_json_decode(ctx, -1);
318         }
319         json_object_put(resu);
320         return 1;
321 }
322
323 static duk_ret_t do_error(duk_context *ctx)
324 {
325         const char *message;
326
327         message = duk_get_string(ctx, -1);
328
329         ERROR("%s", message ? : "null");
330         return 0;
331 }
332
333 /********************************************************************/
334
335 static void jsapi_call(void *closure, struct afb_xreq *xreq)
336 {
337         duk_idx_t top;
338         duk_context *ctx;
339         json_object *args;
340         const char *json;
341         struct jsapi *jsapi = closure;
342
343         ctx = jsapi->context;
344         top = duk_get_top(ctx);
345         duk_get_global_string(ctx, "exports");
346         if (!duk_is_object(ctx, -1)) {
347                 afb_xreq_reply(xreq, NULL, "internal-error", "no exports!?");
348                 goto end;
349         }
350         duk_get_prop_string(ctx, -1, xreq->request.called_verb);
351         if (!duk_is_function(ctx, -1)) {
352                 afb_xreq_reply_unknown_verb(xreq);
353                 goto end;
354         }
355         duk_push_pointer(ctx, xreq);
356         args = afb_xreq_json(xreq);
357         json = json_object_to_json_string(args);
358         duk_push_string(ctx, json);
359         duk_json_decode(ctx, -1);
360         duk_pcall(ctx, 2);
361 end:
362         duk_pop_n(ctx, duk_get_top(ctx) - top);
363 }
364
365 static int jsapi_service_start(void *closure)
366 {
367         struct jsapi *jsapi = closure;
368         return 0;
369 }
370
371 static int jsapi_get_logmask(void *closure)
372 {
373         struct jsapi *jsapi = closure;
374         return jsapi->logmask;
375 }
376
377 static void jsapi_set_logmask(void *closure, int level)
378 {
379         struct jsapi *jsapi = closure;
380         jsapi->logmask = level;
381 }
382
383 static struct json_object *jsapi_describe(void *closure)
384 {
385         struct jsapi *jsapi = closure;
386         return NULL;
387 }
388