1 /*
2  * Copyright (C) 2015-2019 Alibaba Group Holding Limited
3  */
4 
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <stdarg.h>
9 
10 #include "amp_config.h"
11 #include "amp_platform.h"
12 #include "amp_defines.h"
13 #include "amp_task.h"
14 #include "amp_memory.h"
15 #include "aos_system.h"
16 #include "aos_tcp.h"
17 #include "quickjs.h"
18 #include "quickjs_addon_common.h"
19 
20 #define MOD_STR "TCP"
21 #define MAX_TCP_RECV_LEN 256
22 #define MAX_TCP_RECV_TIMEOUT 200
23 
24 typedef struct {
25     int sock_id;
26     char *msg;
27     int msg_len;
28     JSValue js_cb_ref;
29 } tcp_send_param_t;
30 
31 typedef struct {
32     int ret;
33     JSValue js_cb_ref;
34 } tcp_send_notify_param_t;
35 
36 typedef struct {
37     int sock_id;
38     JSValue js_cb_ref;
39 } tcp_recv_param_t;
40 
41 typedef struct {
42     char *host;
43     int port;
44     JSValue js_cb_ref;
45 } tcp_create_param_t;
46 
47 typedef struct {
48     int ret;
49     JSValue js_cb_ref;
50 } tcp_create_notify_param_t;
51 
52 typedef struct {
53     char buf[MAX_TCP_RECV_LEN];
54     int recv_len;
55     JSValue js_cb_ref;
56 } tcp_recv_notify_param_t;
57 
58 static char g_tcp_close_flag = 0;
59 static char g_tcp_recv_flag = 0;
60 static aos_sem_t g_tcp_close_sem = NULL;
61 static JSClassID js_tcp_class_id;
62 
tcp_create_notify(void * pdata)63 static void tcp_create_notify(void *pdata)
64 {
65     tcp_create_notify_param_t *p = (tcp_create_notify_param_t *)pdata;
66     JSContext *ctx = js_get_context();
67     JSValue args = JS_NewInt32(ctx, p->ret);
68     JSValue val = JS_Call(ctx, p->js_cb_ref, JS_UNDEFINED, 1, &args);
69     JS_FreeValue(ctx, args);
70     if (JS_IsException(val)) {
71         js_std_dump_error(ctx);
72     }
73     JS_FreeValue(ctx, val);
74     JS_FreeValue(ctx, p->js_cb_ref);
75     amp_free(p);
76 }
77 
tcp_create_routine(tcp_create_param_t * create_param)78 static int tcp_create_routine(tcp_create_param_t *create_param)
79 {
80     int ret = -1;
81 
82     JSContext *ctx = js_get_context();
83     tcp_create_notify_param_t *p = amp_calloc(1, sizeof(tcp_create_notify_param_t));
84     if (!p) {
85         amp_warn(MOD_STR, "allocate memory failed");
86         JS_FreeValue(ctx, create_param->js_cb_ref);
87         goto out;
88     }
89 
90     ret = aos_tcp_establish(create_param->host, create_param->port);
91     if (ret < 0) {
92         amp_warn(MOD_STR, "tcp establish failed");
93         amp_free(p);
94         JS_FreeValue(ctx, create_param->js_cb_ref);
95         goto out;
96     }
97 
98     amp_debug(MOD_STR, "sock_id = %d", ret);
99     p->js_cb_ref = create_param->js_cb_ref;
100     p->ret       = ret;
101     amp_task_schedule_call(tcp_create_notify, p);
102 
103 out:
104     JS_FreeCString(ctx, create_param->host);
105     amp_free(create_param);
106     return ret;
107 }
108 
109 /*************************************************************************************
110  * Function:    native_udp_create_socket
111  * Description: js native addon for TCP.createSocket();
112  * Called by:   js api
113  * Input:       none
114  * Output:      return socket fd when create socket success,
115  *            return error number when create socket fail
116  **************************************************************************************/
native_tcp_create_socket(JSContext * ctx,JSValueConst this_val,int argc,JSValueConst * argv)117 static JSValue native_tcp_create_socket(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
118 {
119     int err;
120     int ret  = -1;
121     int port = 0;
122     char *host = NULL;
123     tcp_create_param_t *create_param = NULL;
124 
125     amp_debug(MOD_STR, "native_tcp_create_socket");
126     if (!JS_IsObject(argv[0]) || !JS_IsFunction(ctx, argv[1])) {
127         amp_warn(MOD_STR, "parameter must be (object, function)");
128         goto out;
129     }
130 
131     JSValue j_host = JS_GetPropertyStr(ctx, argv[0], "host");
132     host = JS_ToCString(ctx, j_host);
133     JS_FreeValue(ctx, j_host);
134 
135     JSValue j_port = JS_GetPropertyStr(ctx, argv[0], "port");
136     JS_ToInt32(ctx, &port, j_port);
137     JS_FreeValue(ctx, j_port);
138 
139     amp_debug(MOD_STR, "host: %s, port: %d", host, port);
140     create_param = (tcp_create_param_t *)amp_malloc(sizeof(*create_param));
141     if (!create_param) {
142         amp_error(MOD_STR, "allocate memory failed");
143         JS_FreeCString(ctx, host);
144         goto out;
145     }
146 
147     create_param->host = host;
148     create_param->port = port;
149     create_param->js_cb_ref = JS_DupValue(ctx, argv[1]);
150 
151     ret = tcp_create_routine(create_param);
152     if (ret < 0) {
153         amp_warn(MOD_STR, "tcp create socket failed");
154         goto out;
155     }
156 
157 out:
158     return JS_NewInt32(ctx, ret);
159 }
160 
tcp_send_notify(void * pdata)161 static void tcp_send_notify(void *pdata)
162 {
163     tcp_send_notify_param_t *p = (tcp_send_notify_param_t *)pdata;
164     JSContext *ctx = js_get_context();
165     JSValue args = JS_NewInt32(ctx, p->ret);
166     JSValue val = JS_Call(ctx, p->js_cb_ref, JS_UNDEFINED, 1, &args);
167     if (JS_IsException(val)) {
168         js_std_dump_error(ctx);
169     }
170     JS_FreeValue(ctx, args);
171     JS_FreeValue(ctx, val);
172     JS_FreeValue(ctx, p->js_cb_ref);
173     amp_free(p);
174 }
175 
176 /*************************************************************************************
177  * Function:    udp_send_routin
178  * Description: create a task for blocking sendto call
179  * Called by:
180  **************************************************************************************/
tcp_send_routine(tcp_send_param_t * send_param)181 static int tcp_send_routine(tcp_send_param_t *send_param)
182 {
183     JSContext *ctx = js_get_context();
184     int sock_id = send_param->sock_id;
185     int ret = aos_tcp_write(sock_id, send_param->msg, send_param->msg_len, 0);
186     if (ret < 0) {
187         JS_FreeValue(ctx, send_param->js_cb_ref);
188         goto out;
189     }
190 
191     tcp_send_notify_param_t *p = amp_calloc(1, sizeof(tcp_send_notify_param_t));
192     if (!p) {
193         amp_warn(MOD_STR, "allocate memory failed");
194         JS_FreeValue(ctx, send_param->js_cb_ref);
195         goto out;
196     }
197 
198     p->js_cb_ref = send_param->js_cb_ref;
199     p->ret       = ret;
200     amp_task_schedule_call(tcp_send_notify, p);
201     ret = 0;
202 
203 out:
204     JS_FreeCString(ctx, send_param->msg);
205     amp_free(send_param);
206     return ret;
207 }
208 
209 /*************************************************************************************
210  * Function:    native_udp_sendto
211  * Description: js native addon for
212  *TCP.send(sock_id,option,buffer_array,function(ret){}) Called by:   js api
213  * Input:       sock_id: interger
214  *            options: is a object include options.ip and options.port
215  *            buffer_array: is a array which include message to send
216  *            function(ret): is the callback function which has a ret input
217  *param Output:      return send msg length when send success return error
218  *number when send fail
219  **************************************************************************************/
220 
native_tcp_send(JSContext * ctx,JSValueConst this_val,int argc,JSValueConst * argv)221 static JSValue native_tcp_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
222 {
223     int ret = -1;
224     int sock_id;
225     int i;
226     int msg_len;
227     char *msg;
228 
229     if (!JS_IsNumber(argv[0]) || !JS_IsString(argv[1]) ||
230             !JS_IsFunction(ctx, argv[2])) {
231         amp_warn(MOD_STR, "parameter must be (number, string, function)");
232         goto out;
233     }
234 
235     JS_ToInt32(ctx, &sock_id, argv[0]);
236     if (sock_id <= 0) {
237         amp_warn(MOD_STR, "socket id[%d] is invalid", sock_id);
238         goto out;
239     }
240 
241     msg = JS_ToCString(ctx, argv[1]);
242     msg_len = strlen(msg);
243     amp_debug(MOD_STR, "msg is:%s, length is: %d", msg, msg_len);
244 
245     tcp_send_param_t *send_param = (tcp_send_param_t *)amp_malloc(sizeof(*send_param));
246     if (!send_param) {
247         amp_error(MOD_STR, "allocate memory failed");
248         JS_FreeCString(ctx, msg);
249         goto out;
250     }
251     send_param->sock_id   = sock_id;
252     send_param->msg       = msg;
253     send_param->msg_len   = msg_len;
254     send_param->js_cb_ref = JS_DupValue(ctx, argv[2]);
255     tcp_send_routine(send_param);
256 
257 out:
258     return JS_NewInt32(ctx, ret);
259 }
260 
tcp_recv_notify(void * pdata)261 static void tcp_recv_notify(void *pdata)
262 {
263     int i = 0;
264     tcp_recv_notify_param_t *p = (tcp_recv_notify_param_t *)pdata;
265     JSContext *ctx = js_get_context();
266 
267     JSValue args[2];
268     args[0] = JS_NewInt32(ctx, p->recv_len);
269     args[1] = JS_NewStringLen(ctx, p->buf, p->recv_len);
270 
271     JSValue val = JS_Call(ctx, p->js_cb_ref, JS_UNDEFINED, 2, args);
272     JS_FreeValue(ctx, args[0]);
273     JS_FreeValue(ctx, args[1]);
274     if (JS_IsException(val)) {
275         js_std_dump_error(ctx);
276     }
277     JS_FreeValue(ctx, val);
278 }
279 
280 /*************************************************************************************
281  * Function:    udp_recv_routine
282  * Description: create a task for blocking recvfrom call
283  * Called by:
284  **************************************************************************************/
tcp_recv_routine(void * arg)285 static void tcp_recv_routine(void *arg)
286 {
287     tcp_recv_param_t *recv_param = (tcp_recv_param_t *)arg;
288     int sock_id;
289     JSContext *ctx = js_get_context();
290 
291     g_tcp_recv_flag = 1;
292     sock_id  = recv_param->sock_id;
293     tcp_recv_notify_param_t *p = amp_calloc(1, sizeof(*p));
294     if (!p) {
295         amp_warn(MOD_STR, "allocate memory failed");
296         goto out;
297     }
298 
299     p->js_cb_ref = recv_param->js_cb_ref;
300 
301     while (1) {
302         p->recv_len = aos_tcp_read(sock_id, p->buf, sizeof(p->buf), MAX_TCP_RECV_TIMEOUT);
303         if (p->recv_len > 0) {
304             amp_task_schedule_call(tcp_recv_notify, p);
305         }
306 
307         if (p->recv_len < 0) {
308             amp_warn(MOD_STR, "connection closed: %d", p->recv_len);
309             break;
310         }
311 
312         if (g_tcp_close_flag) {
313             amp_warn(MOD_STR, "close tcp connect");
314             break;
315         }
316     }
317     aos_tcp_destroy(sock_id);
318 
319 out:
320     JS_FreeValue(ctx, recv_param->js_cb_ref);
321     amp_free(recv_param);
322     if (p)
323         amp_free(p);
324 
325     g_tcp_recv_flag = 0;
326     aos_sem_signal(&g_tcp_close_sem);
327     aos_task_exit(0);
328     return;
329 }
330 
331 /*************************************************************************************
332  * Function:    native_udp_recvfrom
333  * Description: js native addon for
334  *            TCP.recv(sock_id,function(length, buffer_array, src_ip,
335  *src_port){}) Called by:   js api Input:       sock_id: interger
336  *            function(length, buffer_array, src_ip, src_port): the callback
337  *function length: the recv msg length buffer_array: the recv msg buffer src_ip:
338  *the peer ip string src_port: the peer port which is a interger
339  *
340  * Output:      return 0 when TCP.recv call ok
341  *            return error number TCP.recv call fail
342  **************************************************************************************/
native_tcp_receive(JSContext * ctx,JSValueConst this_val,int argc,JSValueConst * argv)343 static JSValue native_tcp_receive(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
344 {
345     int ret     = -1;
346     int sock_id = 0;
347     aos_task_t tcp_recv_task;
348     tcp_recv_param_t *recv_param;
349 
350     if (!JS_IsNumber(argv[0]) || !JS_IsFunction(ctx, argv[1])) {
351         amp_warn(MOD_STR, "parameter must be number and function");
352         goto out;
353     }
354 
355     JS_ToInt32(ctx, &sock_id, argv[0]);
356     if (sock_id < 0) {
357         amp_warn(MOD_STR, "socket id[%d] is invalid", sock_id);
358         goto out;
359     }
360 
361     amp_debug(MOD_STR, "sock_id: %d", sock_id);
362     recv_param = (tcp_recv_param_t *)amp_calloc(1, sizeof(*recv_param));
363     if (!recv_param) {
364         amp_warn(MOD_STR, "allocate memory failed");
365         goto out;
366     }
367 
368     recv_param->sock_id = sock_id;
369     recv_param->js_cb_ref = JS_DupValue(ctx, argv[1]);
370 
371     ret = aos_task_new_ext(&tcp_recv_task, "amp_tcp_recv_task", tcp_recv_routine, recv_param, 1024 * 4, ADDON_TSK_PRIORRITY);
372     if (ret != 0) {
373         amp_warn(MOD_STR, "tcp recv task error");
374         goto out;
375     }
376 
377 out:
378     return JS_NewInt32(ctx, ret);
379 }
380 
381 /*************************************************************************************
382  * Function:    native_tcp_close
383  * Description: js native addon for
384  *            TCP.close(sock_id)
385  * Called by:   js api
386  * Input:       sock_id: interger
387  *
388  * Output:      return 0 when TCP.close call ok
389  *            return error number TCP.close call fail
390  **************************************************************************************/
native_tcp_close(JSContext * ctx,JSValueConst this_val,int argc,JSValueConst * argv)391 static JSValue native_tcp_close(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
392 {
393     int ret = -1;
394     int sock_id = 0;
395 
396     if ((argc < 1) || (!JS_IsNumber(argv[0]))) {
397         amp_warn(MOD_STR, "parameter must be number");
398         goto out;
399     }
400 
401     JS_ToInt32(ctx, &sock_id, argv[0]);
402     if (sock_id <= 0) {
403         amp_warn(MOD_STR, "socket id[%d] is invalid", sock_id);
404         goto out;
405     }
406 
407     g_tcp_close_flag = 1;
408     aos_sem_wait(&g_tcp_close_sem, MAX_TCP_RECV_TIMEOUT + 50);
409     g_tcp_close_flag = 0;
410     ret = 0;
411 
412 out:
413     return JS_NewInt32(ctx, ret);
414 }
415 
module_tcp_source_clean(void)416 static void module_tcp_source_clean(void)
417 {
418     if (g_tcp_recv_flag) {
419         g_tcp_close_flag = 1;
420         aos_sem_wait(&g_tcp_close_sem, MAX_TCP_RECV_TIMEOUT + 50);
421         g_tcp_close_flag = 0;
422     }
423 }
424 
425 
426 static JSClassDef js_tcp_class = {
427     "TCP",
428 };
429 
430 static const JSCFunctionListEntry js_tcp_funcs[] = {
431     JS_CFUNC_DEF("createSocket", 2, native_tcp_create_socket),
432     JS_CFUNC_DEF("send", 3, native_tcp_send),
433     JS_CFUNC_DEF("recv", 2, native_tcp_receive),
434     JS_CFUNC_DEF("close", 1, native_tcp_close)
435 };
436 
js_tcp_init(JSContext * ctx,JSModuleDef * m)437 static int js_tcp_init(JSContext *ctx, JSModuleDef *m)
438 {
439     JSValue proto;
440 
441     JS_NewClassID(&js_tcp_class_id);
442 
443     JS_NewClass(JS_GetRuntime(ctx), js_tcp_class_id, &js_tcp_class);
444     proto = JS_NewObject(ctx);
445     JS_SetPropertyFunctionList(ctx, proto, js_tcp_funcs, countof(js_tcp_funcs));
446     JS_SetClassProto(ctx, js_tcp_class_id, proto);
447     return JS_SetModuleExportList(ctx, m, js_tcp_funcs, countof(js_tcp_funcs));
448 }
449 
js_init_module_tcp(JSContext * ctx,const char * module_name)450 JSModuleDef *js_init_module_tcp(JSContext *ctx, const char *module_name)
451 {
452     JSModuleDef *m;
453     m = JS_NewCModule(ctx, module_name, js_tcp_init);
454     if (!m)
455         return NULL;
456     JS_AddModuleExportList(ctx, m, js_tcp_funcs, countof(js_tcp_funcs));
457     return m;
458 }
459 
module_tcp_register(void)460 void module_tcp_register(void)
461 {
462     amp_debug(MOD_STR, "module_tcp_register");
463     JSContext *ctx = js_get_context();
464 
465     if (!g_tcp_close_sem) {
466         if (aos_sem_new(&g_tcp_close_sem, 0) != 0) {
467             amp_error(MOD_STR, "create tcp sem fail");
468             return;
469         }
470     }
471 
472     amp_module_free_register(module_tcp_source_clean);
473     js_init_module_tcp(ctx, "TCP");
474 }
475