mirror of
https://dev.iopsys.eu/feed/iopsys.git
synced 2025-12-10 07:44:50 +01:00
958 lines
30 KiB
C
958 lines
30 KiB
C
/*
|
|
* Copyright (c) 2020 Genexis B.V.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use, copy,
|
|
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
* of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <quickjs/quickjs.h>
|
|
#include <quickjs/quickjs-libc.h>
|
|
#include <libwebsockets.h>
|
|
|
|
#define countof(x) (sizeof(x) / sizeof((x)[0]))
|
|
|
|
#define CDEF(name) JS_PROP_INT32_DEF(#name, name, JS_PROP_CONFIGURABLE)
|
|
|
|
#if LWS_LIBRARY_VERSION_NUMBER < 3002000
|
|
#define MAX_WAIT 1000
|
|
#else
|
|
#define MAX_WAIT INT32_MAX
|
|
#ifndef LWS_WITH_EXTERNAL_POLL
|
|
#error "LWS_WITH_EXTERNAL_POLL is needed for LWS versions >= 3.2.0"
|
|
#endif
|
|
#if LWS_LIBRARY_VERSION_NUMBER < 4001002
|
|
#error "External poll is broken for 3.2.0 <= LWS version < 4.1.2"
|
|
#endif
|
|
#endif
|
|
|
|
#define WSI_DATA_USE_OBJECT (1 << 0)
|
|
#define WSI_DATA_USE_LINKED (1 << 1)
|
|
|
|
typedef struct js_lws_wsi_data {
|
|
struct js_lws_wsi_data *next;
|
|
struct lws *wsi;
|
|
JSValue object;
|
|
JSValue context;
|
|
JSValue user;
|
|
uint8_t in_use;
|
|
} js_lws_wsi_data_t;
|
|
|
|
typedef struct {
|
|
struct lws_context *context;
|
|
JSContext *ctx;
|
|
JSValue callback;
|
|
js_lws_wsi_data_t *wsi_list;
|
|
} js_lws_context_data_t;
|
|
|
|
static JSClassID js_lws_context_class_id;
|
|
static JSClassID js_lws_wsi_class_id;
|
|
|
|
static void free_wsi_data_rt(JSRuntime *rt, js_lws_wsi_data_t *data)
|
|
{
|
|
JS_FreeValueRT(rt, data->object);
|
|
JS_FreeValueRT(rt, data->context);
|
|
JS_FreeValueRT(rt, data->user);
|
|
js_free_rt(rt, data);
|
|
}
|
|
|
|
static void unlink_wsi_rt(JSRuntime *rt, js_lws_context_data_t *data,
|
|
js_lws_wsi_data_t *wsi_data)
|
|
{
|
|
js_lws_wsi_data_t **p;
|
|
for (p = &data->wsi_list; *p; p = &(*p)->next) {
|
|
if (*p == wsi_data) {
|
|
*p = (*p)->next;
|
|
break;
|
|
}
|
|
}
|
|
wsi_data->next = NULL;
|
|
wsi_data->wsi = NULL;
|
|
JS_FreeValueRT(rt, wsi_data->object);
|
|
wsi_data->object = JS_UNDEFINED;
|
|
wsi_data->in_use &= ~WSI_DATA_USE_LINKED;
|
|
if (wsi_data->in_use == 0)
|
|
free_wsi_data_rt(rt, wsi_data);
|
|
}
|
|
|
|
static void unlink_wsi(JSContext *ctx, js_lws_context_data_t *data,
|
|
js_lws_wsi_data_t *wsi_data)
|
|
{
|
|
unlink_wsi_rt(JS_GetRuntime(ctx), data, wsi_data);
|
|
}
|
|
|
|
static JSValue convert_pollargs(JSContext *ctx, const struct lws_pollargs *pa)
|
|
{
|
|
JSValue obj;
|
|
|
|
if (pa == NULL)
|
|
return JS_NULL;
|
|
|
|
obj = JS_NewObject(ctx);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
|
|
if (JS_SetPropertyStr(ctx, obj, "fd", JS_NewInt32(ctx, pa->fd)) < 0
|
|
|| JS_SetPropertyStr(ctx, obj, "events",
|
|
JS_NewInt32(ctx, pa->events)) < 0
|
|
|| JS_SetPropertyStr(ctx, obj, "prev_events",
|
|
JS_NewInt32(ctx, pa->prev_events)) < 0) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
static JSValue convert_close_payload(JSContext *ctx,
|
|
uint8_t *payload, size_t len)
|
|
{
|
|
JSValue array;
|
|
uint16_t status;
|
|
JSValue reason;
|
|
|
|
if (payload == NULL || len < 2)
|
|
return JS_NULL;
|
|
|
|
array = JS_NewArray(ctx);
|
|
if (JS_IsException(array))
|
|
return array;
|
|
|
|
status = (payload[0] << 8) | payload[1];
|
|
if (JS_SetPropertyUint32(ctx, array, 0, JS_NewInt32(ctx, status)) < 0) {
|
|
JS_FreeValue(ctx, array);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
reason = JS_NewStringLen(ctx, (const char *)(payload + 2), len - 2);
|
|
if (JS_IsException(reason)
|
|
|| JS_SetPropertyUint32(ctx, array, 1, reason) < 0) {
|
|
JS_FreeValue(ctx, array);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
static int client_callback(struct lws *wsi, enum lws_callback_reasons reason,
|
|
void *user, void *in, size_t len)
|
|
{
|
|
js_lws_context_data_t *data = lws_context_user(lws_get_context(wsi));
|
|
js_lws_wsi_data_t *wsi_data = lws_wsi_user(wsi);
|
|
JSContext *ctx;
|
|
JSValue args[3];
|
|
JSValue ret;
|
|
int32_t ret_int;
|
|
int i;
|
|
|
|
if (data == NULL || data->ctx == NULL || JS_IsUndefined(data->callback))
|
|
return 0;
|
|
|
|
ctx = data->ctx;
|
|
args[0] = wsi_data ? JS_DupValue(ctx, wsi_data->object) : JS_NULL;
|
|
args[1] = JS_NewInt32(ctx, reason);
|
|
args[2] = JS_NULL;
|
|
|
|
switch (reason) {
|
|
case LWS_CALLBACK_ADD_POLL_FD:
|
|
case LWS_CALLBACK_DEL_POLL_FD:
|
|
case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
|
|
args[2] = convert_pollargs(ctx, in);
|
|
break;
|
|
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
|
if (in)
|
|
args[2] = JS_NewStringLen(ctx, in, len);
|
|
break;
|
|
case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE:
|
|
args[2] = convert_close_payload(ctx, in, len);
|
|
break;
|
|
case LWS_CALLBACK_RECEIVE:
|
|
case LWS_CALLBACK_CLIENT_RECEIVE:
|
|
if (in)
|
|
args[2] = JS_NewArrayBufferCopy(ctx, in, len);
|
|
break;
|
|
case LWS_CALLBACK_WSI_DESTROY:
|
|
if (wsi_data)
|
|
unlink_wsi(ctx, data, wsi_data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (JS_IsException(args[2]))
|
|
ret = JS_EXCEPTION;
|
|
else
|
|
ret = JS_Call(ctx, data->callback, JS_UNDEFINED, countof(args), args);
|
|
|
|
if (JS_IsException(ret) || JS_ToInt32(ctx, &ret_int, ret) < 0) {
|
|
js_std_dump_error(ctx);
|
|
ret_int = -1;
|
|
}
|
|
|
|
JS_FreeValue(ctx, ret);
|
|
for (i = 0; i < countof(args); i++) {
|
|
JS_FreeValue(ctx, args[i]);
|
|
}
|
|
|
|
return ret_int;
|
|
}
|
|
|
|
static const struct lws_protocols client_protocols[] = {
|
|
{ "lws-client", client_callback, 0, 0, 0, NULL, 0 },
|
|
{ NULL, NULL, 0, 0, 0, NULL, 0 }
|
|
};
|
|
|
|
static JSValue js_decode_utf8(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
size_t size;
|
|
uint8_t *ptr = JS_GetArrayBuffer(ctx, &size, argv[0]);
|
|
if (ptr == NULL)
|
|
return JS_EXCEPTION;
|
|
return JS_NewStringLen(ctx, (const char *)ptr, size);
|
|
}
|
|
|
|
static JSValue js_lws_set_log_level(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int32_t level;
|
|
if (JS_ToInt32(ctx, &level, argv[0]) < 0)
|
|
return JS_EXCEPTION;
|
|
lws_set_log_level(level, NULL);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_lws_create_context(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int secure;
|
|
JSValue obj;
|
|
js_lws_context_data_t *data;
|
|
struct lws_context_creation_info info;
|
|
struct lws_context *context;
|
|
|
|
if (!JS_IsFunction(ctx, argv[0]))
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
|
|
secure = JS_ToBool(ctx, argv[1]);
|
|
if (secure < 0)
|
|
return JS_EXCEPTION;
|
|
|
|
obj = JS_NewObjectClass(ctx, js_lws_context_class_id);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
|
|
data = js_mallocz(ctx, sizeof(js_lws_context_data_t));
|
|
if (data == NULL) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
data->callback = JS_UNDEFINED;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.port = CONTEXT_PORT_NO_LISTEN;
|
|
info.protocols = client_protocols;
|
|
info.gid = -1;
|
|
info.uid = -1;
|
|
info.options = secure ? LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT : 0;
|
|
info.user = data;
|
|
context = lws_create_context(&info);
|
|
if (context == NULL) {
|
|
JS_FreeValue(ctx, obj);
|
|
js_free(ctx, data);
|
|
return JS_ThrowOutOfMemory(ctx);
|
|
}
|
|
|
|
data->context = context;
|
|
data->ctx = JS_DupContext(ctx);
|
|
data->callback = JS_DupValue(ctx, argv[0]);
|
|
JS_SetOpaque(obj, data);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static void js_lws_context_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
js_lws_context_data_t *data = JS_GetOpaque(val, js_lws_context_class_id);
|
|
if (data) {
|
|
JS_FreeContext(data->ctx);
|
|
data->ctx = NULL;
|
|
JS_FreeValueRT(rt, data->callback);
|
|
data->callback = JS_UNDEFINED;
|
|
|
|
lws_context_destroy(data->context);
|
|
|
|
while (data->wsi_list) {
|
|
unlink_wsi_rt(rt, data, data->wsi_list);
|
|
}
|
|
|
|
js_free_rt(rt, data);
|
|
}
|
|
}
|
|
|
|
static void js_lws_context_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
js_lws_context_data_t *data = JS_GetOpaque(val, js_lws_context_class_id);
|
|
if (data) {
|
|
js_lws_wsi_data_t *wd;
|
|
mark_func(rt, (JSGCObjectHeader *)data->ctx);
|
|
JS_MarkValue(rt, data->callback, mark_func);
|
|
for (wd = data->wsi_list; wd; wd = wd->next) {
|
|
JS_MarkValue(rt, wd->object, mark_func);
|
|
}
|
|
}
|
|
}
|
|
|
|
static JSValue js_lws_context_get_connections(JSContext *ctx,
|
|
JSValueConst this_val)
|
|
{
|
|
js_lws_context_data_t *data = JS_GetOpaque2(ctx, this_val,
|
|
js_lws_context_class_id);
|
|
int32_t connections = 0;
|
|
js_lws_wsi_data_t *wd;
|
|
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
|
|
for (wd = data->wsi_list; wd; wd = wd->next) {
|
|
connections++;
|
|
}
|
|
|
|
return JS_NewInt32(ctx, connections);
|
|
}
|
|
|
|
static JSValue js_lws_client_connect(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
js_lws_context_data_t *data = JS_GetOpaque2(ctx, this_val,
|
|
js_lws_context_class_id);
|
|
const char *address = NULL;
|
|
int32_t port;
|
|
int secure;
|
|
const char *path = NULL, *host = NULL, *origin = NULL, *protocol = NULL;
|
|
JSValue obj = JS_UNDEFINED, ret = JS_EXCEPTION;
|
|
js_lws_wsi_data_t *wsi_data;
|
|
struct lws_client_connect_info info;
|
|
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
|
|
address = JS_ToCString(ctx, argv[0]);
|
|
if (address == NULL)
|
|
goto exception;
|
|
|
|
if (JS_ToInt32(ctx, &port, argv[1]) < 0)
|
|
goto exception;
|
|
|
|
if (port < 1 || port > 65535) {
|
|
JS_ThrowRangeError(ctx, "port must be between 1 and 65535");
|
|
goto exception;
|
|
}
|
|
|
|
secure = JS_ToBool(ctx, argv[2]);
|
|
if (secure < 0)
|
|
goto exception;
|
|
|
|
path = JS_ToCString(ctx, argv[3]);
|
|
if (path == NULL)
|
|
goto exception;
|
|
|
|
host = JS_ToCString(ctx, argv[4]);
|
|
if (host == NULL)
|
|
goto exception;
|
|
|
|
if (!JS_IsUndefined(argv[5]) && !JS_IsNull(argv[5])) {
|
|
origin = JS_ToCString(ctx, argv[5]);
|
|
if (origin == NULL)
|
|
goto exception;
|
|
}
|
|
|
|
if (!JS_IsUndefined(argv[6]) && !JS_IsNull(argv[6])) {
|
|
protocol = JS_ToCString(ctx, argv[6]);
|
|
if (protocol == NULL)
|
|
goto exception;
|
|
}
|
|
|
|
obj = JS_NewObjectClass(ctx, js_lws_wsi_class_id);
|
|
if (JS_IsException(obj))
|
|
goto exception;
|
|
|
|
wsi_data = js_mallocz(ctx, sizeof(js_lws_wsi_data_t));
|
|
if (wsi_data == NULL)
|
|
goto exception;
|
|
wsi_data->next = data->wsi_list;
|
|
wsi_data->object = JS_DupValue(ctx, obj);
|
|
wsi_data->context = JS_DupValue(ctx, this_val);
|
|
wsi_data->user = JS_DupValue(ctx, argv[7]);
|
|
wsi_data->in_use = WSI_DATA_USE_OBJECT | WSI_DATA_USE_LINKED;
|
|
data->wsi_list = wsi_data;
|
|
JS_SetOpaque(obj, wsi_data);
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.context = data->context;
|
|
info.address = address;
|
|
info.port = port;
|
|
info.ssl_connection = secure ? LCCSCF_USE_SSL : 0;
|
|
info.local_protocol_name = "lws-client";
|
|
info.path = path;
|
|
info.host = host;
|
|
info.origin = origin;
|
|
info.protocol = protocol;
|
|
info.ietf_version_or_minus_one = -1;
|
|
info.userdata = wsi_data;
|
|
info.pwsi = &wsi_data->wsi;
|
|
lws_client_connect_via_info(&info);
|
|
|
|
if (wsi_data->wsi) {
|
|
ret = JS_DupValue(ctx, obj);
|
|
} else {
|
|
unlink_wsi(ctx, data, wsi_data);
|
|
JS_ThrowReferenceError(ctx, "cannot connect to [%s]:%d (%s)",
|
|
address, port, secure ? "wss" : "ws");
|
|
}
|
|
|
|
exception:
|
|
JS_FreeCString(ctx, address);
|
|
JS_FreeCString(ctx, path);
|
|
JS_FreeCString(ctx, host);
|
|
JS_FreeCString(ctx, origin);
|
|
JS_FreeCString(ctx, protocol);
|
|
JS_FreeValue(ctx, obj);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_lws_service_fd(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
js_lws_context_data_t *data = JS_GetOpaque2(ctx, this_val,
|
|
js_lws_context_class_id);
|
|
int32_t fd, events, revents;
|
|
struct lws_pollfd pfd;
|
|
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
|
|
if (JS_ToInt32(ctx, &fd, argv[0]) < 0)
|
|
return JS_EXCEPTION;
|
|
|
|
if (JS_ToInt32(ctx, &events, argv[1]) < 0)
|
|
return JS_EXCEPTION;
|
|
|
|
if (JS_ToInt32(ctx, &revents, argv[2]) < 0)
|
|
return JS_EXCEPTION;
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = events;
|
|
pfd.revents = revents;
|
|
lws_service_fd(data->context, &pfd);
|
|
|
|
return JS_NewInt32(ctx,
|
|
lws_service_adjust_timeout(data->context, MAX_WAIT, 0));
|
|
}
|
|
|
|
static JSValue js_lws_service_periodic(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
js_lws_context_data_t *data = JS_GetOpaque2(ctx, this_val,
|
|
js_lws_context_class_id);
|
|
int timeout;
|
|
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
|
|
#if LWS_LIBRARY_VERSION_NUMBER < 3002000
|
|
lws_service_fd(data->context, NULL);
|
|
#endif
|
|
|
|
timeout = lws_service_adjust_timeout(data->context, MAX_WAIT, 0);
|
|
if (timeout == 0) {
|
|
lws_service(data->context, -1);
|
|
timeout = lws_service_adjust_timeout(data->context, MAX_WAIT, 0);
|
|
}
|
|
|
|
return JS_NewInt32(ctx, timeout);
|
|
}
|
|
|
|
static void js_lws_wsi_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque(val, js_lws_wsi_class_id);
|
|
if (data) {
|
|
JS_FreeValueRT(rt, data->context);
|
|
data->context = JS_UNDEFINED;
|
|
JS_FreeValueRT(rt, data->user);
|
|
data->user = JS_UNDEFINED;
|
|
|
|
data->in_use &= ~WSI_DATA_USE_OBJECT;
|
|
if (data->in_use == 0)
|
|
free_wsi_data_rt(rt, data);
|
|
}
|
|
}
|
|
|
|
static void js_lws_wsi_mark(JSRuntime *rt, JSValue val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque(val, js_lws_wsi_class_id);
|
|
if (data) {
|
|
JS_MarkValue(rt, data->context, mark_func);
|
|
JS_MarkValue(rt, data->user, mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue js_lws_wsi_get_context(JSContext *ctx, JSValueConst this_val)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
return JS_DupValue(ctx, data->context);
|
|
}
|
|
|
|
static JSValue js_lws_wsi_get_user(JSContext *ctx, JSValueConst this_val)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
return JS_DupValue(ctx, data->user);
|
|
}
|
|
|
|
static JSValue js_lws_wsi_get_hdr(JSContext *ctx, JSValueConst this_val,
|
|
int magic)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
|
int len;
|
|
char *str;
|
|
JSValue ret;
|
|
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
|
|
if (data->wsi == NULL)
|
|
return JS_ThrowTypeError(ctx, "defunct WSI");
|
|
|
|
len = lws_hdr_total_length(data->wsi, magic);
|
|
if (len < 0)
|
|
return JS_ThrowReferenceError(ctx, "HTTP headers unavailable");
|
|
|
|
len++;
|
|
str = js_mallocz(ctx, len);
|
|
if (str == NULL)
|
|
return JS_EXCEPTION;
|
|
|
|
len = lws_hdr_copy(data->wsi, str, len, magic);
|
|
if (len < 0)
|
|
ret = JS_ThrowReferenceError(ctx, "HTTP headers unavailable");
|
|
else
|
|
ret = JS_NewStringLen(ctx, str, len);
|
|
|
|
js_free(ctx, str);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static JSValue js_lws_is_final_fragment(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
if (data->wsi == NULL)
|
|
return JS_ThrowTypeError(ctx, "defunct WSI");
|
|
return JS_NewBool(ctx, lws_is_final_fragment(data->wsi));
|
|
}
|
|
|
|
static JSValue js_lws_is_first_fragment(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
if (data->wsi == NULL)
|
|
return JS_ThrowTypeError(ctx, "defunct WSI");
|
|
return JS_NewBool(ctx, lws_is_first_fragment(data->wsi));
|
|
}
|
|
|
|
static JSValue js_lws_frame_is_binary(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
if (data->wsi == NULL)
|
|
return JS_ThrowTypeError(ctx, "defunct WSI");
|
|
return JS_NewBool(ctx, lws_frame_is_binary(data->wsi));
|
|
}
|
|
|
|
static JSValue js_lws_callback_on_writable(JSContext *ctx,
|
|
JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
if (data->wsi == NULL)
|
|
return JS_ThrowTypeError(ctx, "defunct WSI");
|
|
lws_callback_on_writable(data->wsi);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_lws_write(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
|
const char *str = NULL;
|
|
const uint8_t *ptr;
|
|
uint8_t *buf;
|
|
size_t size;
|
|
enum lws_write_protocol protocol;
|
|
int ret;
|
|
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
|
|
if (data->wsi == NULL)
|
|
return JS_ThrowTypeError(ctx, "defunct WSI");
|
|
|
|
if (JS_IsString(argv[0])) {
|
|
str = JS_ToCStringLen(ctx, &size, argv[0]);
|
|
if (str == NULL)
|
|
return JS_EXCEPTION;
|
|
ptr = (const uint8_t *)str;
|
|
protocol = LWS_WRITE_TEXT;
|
|
} else {
|
|
ptr = JS_GetArrayBuffer(ctx, &size, argv[0]);
|
|
if (ptr == NULL)
|
|
return JS_EXCEPTION;
|
|
protocol = LWS_WRITE_BINARY;
|
|
}
|
|
|
|
buf = js_malloc(ctx, LWS_PRE + size);
|
|
if (buf)
|
|
memcpy(buf + LWS_PRE, ptr, size);
|
|
if (str)
|
|
JS_FreeCString(ctx, str);
|
|
if (buf == NULL)
|
|
return JS_EXCEPTION;
|
|
ret = lws_write(data->wsi, buf + LWS_PRE, size, protocol);
|
|
js_free(ctx, buf);
|
|
|
|
if (ret < 0)
|
|
return JS_ThrowTypeError(ctx, "WSI not writable");
|
|
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_lws_close_reason(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
js_lws_wsi_data_t *data = JS_GetOpaque2(ctx, this_val, js_lws_wsi_class_id);
|
|
int32_t status;
|
|
const char *reason = NULL;
|
|
size_t len = 0;
|
|
|
|
if (data == NULL)
|
|
return JS_EXCEPTION;
|
|
|
|
if (data->wsi == NULL)
|
|
return JS_ThrowTypeError(ctx, "defunct WSI");
|
|
|
|
if (JS_ToInt32(ctx, &status, argv[0]) < 0)
|
|
return JS_EXCEPTION;
|
|
|
|
if (status < 0 || status > 65535)
|
|
return JS_ThrowRangeError(ctx, "status must be between 0 and 65535");
|
|
|
|
if (!JS_IsUndefined(argv[1])) {
|
|
reason = JS_ToCStringLen(ctx, &len, argv[1]);
|
|
if (reason == NULL)
|
|
return JS_EXCEPTION;
|
|
if (len > 123) {
|
|
JS_FreeCString(ctx, reason);
|
|
return JS_ThrowTypeError(ctx, "reason too long (%zu > 123)", len);
|
|
}
|
|
}
|
|
|
|
lws_close_reason(data->wsi, status, (uint8_t *)reason, len);
|
|
|
|
if (reason)
|
|
JS_FreeCString(ctx, reason);
|
|
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_lws_funcs[] = {
|
|
CDEF(LLL_ERR),
|
|
CDEF(LLL_WARN),
|
|
CDEF(LLL_NOTICE),
|
|
CDEF(LLL_INFO),
|
|
CDEF(LLL_DEBUG),
|
|
CDEF(LLL_PARSER),
|
|
CDEF(LLL_HEADER),
|
|
CDEF(LLL_EXT),
|
|
CDEF(LLL_CLIENT),
|
|
CDEF(LLL_LATENCY),
|
|
CDEF(LLL_USER),
|
|
CDEF(LLL_THREAD),
|
|
CDEF(LLL_COUNT),
|
|
CDEF(LWS_CALLBACK_PROTOCOL_INIT),
|
|
CDEF(LWS_CALLBACK_PROTOCOL_DESTROY),
|
|
CDEF(LWS_CALLBACK_WSI_CREATE),
|
|
CDEF(LWS_CALLBACK_WSI_DESTROY),
|
|
CDEF(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS),
|
|
CDEF(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS),
|
|
CDEF(LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION),
|
|
CDEF(LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY),
|
|
CDEF(LWS_CALLBACK_SSL_INFO),
|
|
CDEF(LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION),
|
|
CDEF(LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED),
|
|
CDEF(LWS_CALLBACK_HTTP),
|
|
CDEF(LWS_CALLBACK_HTTP_BODY),
|
|
CDEF(LWS_CALLBACK_HTTP_BODY_COMPLETION),
|
|
CDEF(LWS_CALLBACK_HTTP_FILE_COMPLETION),
|
|
CDEF(LWS_CALLBACK_HTTP_WRITEABLE),
|
|
CDEF(LWS_CALLBACK_CLOSED_HTTP),
|
|
CDEF(LWS_CALLBACK_FILTER_HTTP_CONNECTION),
|
|
CDEF(LWS_CALLBACK_ADD_HEADERS),
|
|
CDEF(LWS_CALLBACK_CHECK_ACCESS_RIGHTS),
|
|
CDEF(LWS_CALLBACK_PROCESS_HTML),
|
|
CDEF(LWS_CALLBACK_HTTP_BIND_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_HTTP_DROP_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_HTTP_CONFIRM_UPGRADE),
|
|
CDEF(LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP),
|
|
CDEF(LWS_CALLBACK_CLOSED_CLIENT_HTTP),
|
|
CDEF(LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ),
|
|
CDEF(LWS_CALLBACK_RECEIVE_CLIENT_HTTP),
|
|
CDEF(LWS_CALLBACK_COMPLETED_CLIENT_HTTP),
|
|
CDEF(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE),
|
|
CDEF(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_ESTABLISHED),
|
|
CDEF(LWS_CALLBACK_CLOSED),
|
|
CDEF(LWS_CALLBACK_SERVER_WRITEABLE),
|
|
CDEF(LWS_CALLBACK_RECEIVE),
|
|
CDEF(LWS_CALLBACK_RECEIVE_PONG),
|
|
CDEF(LWS_CALLBACK_WS_PEER_INITIATED_CLOSE),
|
|
CDEF(LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION),
|
|
CDEF(LWS_CALLBACK_CONFIRM_EXTENSION_OKAY),
|
|
CDEF(LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_CLIENT_CONNECTION_ERROR),
|
|
CDEF(LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH),
|
|
CDEF(LWS_CALLBACK_CLIENT_ESTABLISHED),
|
|
CDEF(LWS_CALLBACK_CLIENT_CLOSED),
|
|
CDEF(LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER),
|
|
CDEF(LWS_CALLBACK_CLIENT_RECEIVE),
|
|
CDEF(LWS_CALLBACK_CLIENT_RECEIVE_PONG),
|
|
CDEF(LWS_CALLBACK_CLIENT_WRITEABLE),
|
|
CDEF(LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED),
|
|
CDEF(LWS_CALLBACK_WS_EXT_DEFAULTS),
|
|
CDEF(LWS_CALLBACK_FILTER_NETWORK_CONNECTION),
|
|
CDEF(LWS_CALLBACK_WS_CLIENT_BIND_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_GET_THREAD_ID),
|
|
CDEF(LWS_CALLBACK_ADD_POLL_FD),
|
|
CDEF(LWS_CALLBACK_DEL_POLL_FD),
|
|
CDEF(LWS_CALLBACK_CHANGE_MODE_POLL_FD),
|
|
CDEF(LWS_CALLBACK_LOCK_POLL),
|
|
CDEF(LWS_CALLBACK_UNLOCK_POLL),
|
|
CDEF(LWS_CALLBACK_CGI),
|
|
CDEF(LWS_CALLBACK_CGI_TERMINATED),
|
|
CDEF(LWS_CALLBACK_CGI_STDIN_DATA),
|
|
CDEF(LWS_CALLBACK_CGI_STDIN_COMPLETED),
|
|
CDEF(LWS_CALLBACK_CGI_PROCESS_ATTACH),
|
|
CDEF(LWS_CALLBACK_SESSION_INFO),
|
|
CDEF(LWS_CALLBACK_GS_EVENT),
|
|
CDEF(LWS_CALLBACK_HTTP_PMO),
|
|
CDEF(LWS_CALLBACK_RAW_RX),
|
|
CDEF(LWS_CALLBACK_RAW_CLOSE),
|
|
CDEF(LWS_CALLBACK_RAW_WRITEABLE),
|
|
CDEF(LWS_CALLBACK_RAW_ADOPT),
|
|
CDEF(LWS_CALLBACK_RAW_SKT_BIND_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_RAW_SKT_DROP_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_RAW_ADOPT_FILE),
|
|
CDEF(LWS_CALLBACK_RAW_RX_FILE),
|
|
CDEF(LWS_CALLBACK_RAW_WRITEABLE_FILE),
|
|
CDEF(LWS_CALLBACK_RAW_CLOSE_FILE),
|
|
CDEF(LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL),
|
|
CDEF(LWS_CALLBACK_TIMER),
|
|
CDEF(LWS_CALLBACK_EVENT_WAIT_CANCELLED),
|
|
CDEF(LWS_CALLBACK_CHILD_CLOSING),
|
|
CDEF(LWS_CALLBACK_VHOST_CERT_AGING),
|
|
CDEF(LWS_CALLBACK_VHOST_CERT_UPDATE),
|
|
CDEF(LWS_CALLBACK_USER),
|
|
CDEF(LWS_POLLHUP),
|
|
CDEF(LWS_POLLIN),
|
|
CDEF(LWS_POLLOUT),
|
|
JS_CFUNC_DEF("decode_utf8", 1, js_decode_utf8),
|
|
JS_CFUNC_DEF("set_log_level", 1, js_lws_set_log_level),
|
|
JS_CFUNC_DEF("create_context", 2, js_lws_create_context),
|
|
};
|
|
|
|
static const JSClassDef js_lws_context_class = {
|
|
"Context",
|
|
.finalizer = js_lws_context_finalizer,
|
|
.gc_mark = js_lws_context_mark,
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_lws_context_proto_funcs[] = {
|
|
JS_CGETSET_DEF("connections", js_lws_context_get_connections, NULL),
|
|
JS_CFUNC_DEF("client_connect", 8, js_lws_client_connect),
|
|
JS_CFUNC_DEF("service_fd", 3, js_lws_service_fd),
|
|
JS_CFUNC_DEF("service_periodic", 0, js_lws_service_periodic),
|
|
};
|
|
|
|
static const JSClassDef js_lws_wsi_class = {
|
|
"WSI",
|
|
.finalizer = js_lws_wsi_finalizer,
|
|
.gc_mark = js_lws_wsi_mark,
|
|
};
|
|
|
|
#define HDRGET(name) JS_CGETSET_MAGIC_DEF(#name, js_lws_wsi_get_hdr, NULL, name)
|
|
|
|
static const JSCFunctionListEntry js_lws_wsi_proto_funcs[] = {
|
|
JS_CGETSET_DEF("context", js_lws_wsi_get_context, NULL),
|
|
JS_CGETSET_DEF("user", js_lws_wsi_get_user, NULL),
|
|
HDRGET(WSI_TOKEN_GET_URI),
|
|
HDRGET(WSI_TOKEN_POST_URI),
|
|
HDRGET(WSI_TOKEN_OPTIONS_URI),
|
|
HDRGET(WSI_TOKEN_HOST),
|
|
HDRGET(WSI_TOKEN_CONNECTION),
|
|
HDRGET(WSI_TOKEN_UPGRADE),
|
|
HDRGET(WSI_TOKEN_ORIGIN),
|
|
HDRGET(WSI_TOKEN_DRAFT),
|
|
HDRGET(WSI_TOKEN_CHALLENGE),
|
|
HDRGET(WSI_TOKEN_EXTENSIONS),
|
|
HDRGET(WSI_TOKEN_KEY1),
|
|
HDRGET(WSI_TOKEN_KEY2),
|
|
HDRGET(WSI_TOKEN_PROTOCOL),
|
|
HDRGET(WSI_TOKEN_ACCEPT),
|
|
HDRGET(WSI_TOKEN_NONCE),
|
|
HDRGET(WSI_TOKEN_HTTP),
|
|
HDRGET(WSI_TOKEN_HTTP2_SETTINGS),
|
|
HDRGET(WSI_TOKEN_HTTP_ACCEPT),
|
|
HDRGET(WSI_TOKEN_HTTP_AC_REQUEST_HEADERS),
|
|
HDRGET(WSI_TOKEN_HTTP_IF_MODIFIED_SINCE),
|
|
HDRGET(WSI_TOKEN_HTTP_IF_NONE_MATCH),
|
|
HDRGET(WSI_TOKEN_HTTP_ACCEPT_ENCODING),
|
|
HDRGET(WSI_TOKEN_HTTP_ACCEPT_LANGUAGE),
|
|
HDRGET(WSI_TOKEN_HTTP_PRAGMA),
|
|
HDRGET(WSI_TOKEN_HTTP_CACHE_CONTROL),
|
|
HDRGET(WSI_TOKEN_HTTP_AUTHORIZATION),
|
|
HDRGET(WSI_TOKEN_HTTP_COOKIE),
|
|
HDRGET(WSI_TOKEN_HTTP_CONTENT_LENGTH),
|
|
HDRGET(WSI_TOKEN_HTTP_CONTENT_TYPE),
|
|
HDRGET(WSI_TOKEN_HTTP_DATE),
|
|
HDRGET(WSI_TOKEN_HTTP_RANGE),
|
|
HDRGET(WSI_TOKEN_HTTP_REFERER),
|
|
HDRGET(WSI_TOKEN_KEY),
|
|
HDRGET(WSI_TOKEN_VERSION),
|
|
HDRGET(WSI_TOKEN_SWORIGIN),
|
|
HDRGET(WSI_TOKEN_HTTP_COLON_AUTHORITY),
|
|
HDRGET(WSI_TOKEN_HTTP_COLON_METHOD),
|
|
HDRGET(WSI_TOKEN_HTTP_COLON_PATH),
|
|
HDRGET(WSI_TOKEN_HTTP_COLON_SCHEME),
|
|
HDRGET(WSI_TOKEN_HTTP_COLON_STATUS),
|
|
HDRGET(WSI_TOKEN_HTTP_ACCEPT_CHARSET),
|
|
HDRGET(WSI_TOKEN_HTTP_ACCEPT_RANGES),
|
|
HDRGET(WSI_TOKEN_HTTP_ACCESS_CONTROL_ALLOW_ORIGIN),
|
|
HDRGET(WSI_TOKEN_HTTP_AGE),
|
|
HDRGET(WSI_TOKEN_HTTP_ALLOW),
|
|
HDRGET(WSI_TOKEN_HTTP_CONTENT_DISPOSITION),
|
|
HDRGET(WSI_TOKEN_HTTP_CONTENT_ENCODING),
|
|
HDRGET(WSI_TOKEN_HTTP_CONTENT_LANGUAGE),
|
|
HDRGET(WSI_TOKEN_HTTP_CONTENT_LOCATION),
|
|
HDRGET(WSI_TOKEN_HTTP_CONTENT_RANGE),
|
|
HDRGET(WSI_TOKEN_HTTP_ETAG),
|
|
HDRGET(WSI_TOKEN_HTTP_EXPECT),
|
|
HDRGET(WSI_TOKEN_HTTP_EXPIRES),
|
|
HDRGET(WSI_TOKEN_HTTP_FROM),
|
|
HDRGET(WSI_TOKEN_HTTP_IF_MATCH),
|
|
HDRGET(WSI_TOKEN_HTTP_IF_RANGE),
|
|
HDRGET(WSI_TOKEN_HTTP_IF_UNMODIFIED_SINCE),
|
|
HDRGET(WSI_TOKEN_HTTP_LAST_MODIFIED),
|
|
HDRGET(WSI_TOKEN_HTTP_LINK),
|
|
HDRGET(WSI_TOKEN_HTTP_LOCATION),
|
|
HDRGET(WSI_TOKEN_HTTP_MAX_FORWARDS),
|
|
HDRGET(WSI_TOKEN_HTTP_PROXY_AUTHENTICATE),
|
|
HDRGET(WSI_TOKEN_HTTP_PROXY_AUTHORIZATION),
|
|
HDRGET(WSI_TOKEN_HTTP_REFRESH),
|
|
HDRGET(WSI_TOKEN_HTTP_RETRY_AFTER),
|
|
HDRGET(WSI_TOKEN_HTTP_SERVER),
|
|
HDRGET(WSI_TOKEN_HTTP_SET_COOKIE),
|
|
HDRGET(WSI_TOKEN_HTTP_STRICT_TRANSPORT_SECURITY),
|
|
HDRGET(WSI_TOKEN_HTTP_TRANSFER_ENCODING),
|
|
HDRGET(WSI_TOKEN_HTTP_USER_AGENT),
|
|
HDRGET(WSI_TOKEN_HTTP_VARY),
|
|
HDRGET(WSI_TOKEN_HTTP_VIA),
|
|
HDRGET(WSI_TOKEN_HTTP_WWW_AUTHENTICATE),
|
|
HDRGET(WSI_TOKEN_PATCH_URI),
|
|
HDRGET(WSI_TOKEN_PUT_URI),
|
|
HDRGET(WSI_TOKEN_DELETE_URI),
|
|
HDRGET(WSI_TOKEN_HTTP_URI_ARGS),
|
|
HDRGET(WSI_TOKEN_PROXY),
|
|
HDRGET(WSI_TOKEN_HTTP_X_REAL_IP),
|
|
HDRGET(WSI_TOKEN_HTTP1_0),
|
|
HDRGET(WSI_TOKEN_X_FORWARDED_FOR),
|
|
HDRGET(WSI_TOKEN_CONNECT),
|
|
HDRGET(WSI_TOKEN_HEAD_URI),
|
|
HDRGET(WSI_TOKEN_TE),
|
|
HDRGET(WSI_TOKEN_REPLAY_NONCE),
|
|
HDRGET(WSI_TOKEN_COLON_PROTOCOL),
|
|
HDRGET(WSI_TOKEN_X_AUTH_TOKEN),
|
|
JS_CFUNC_DEF("is_final_fragment", 0, js_lws_is_final_fragment),
|
|
JS_CFUNC_DEF("is_first_fragment", 0, js_lws_is_first_fragment),
|
|
JS_CFUNC_DEF("frame_is_binary", 0, js_lws_frame_is_binary),
|
|
JS_CFUNC_DEF("callback_on_writable", 0, js_lws_callback_on_writable),
|
|
JS_CFUNC_DEF("write", 1, js_lws_write),
|
|
JS_CFUNC_DEF("close_reason", 2, js_lws_close_reason),
|
|
};
|
|
|
|
static int js_lws_init(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
JSValue proto;
|
|
|
|
JS_NewClassID(&js_lws_context_class_id);
|
|
JS_NewClass(JS_GetRuntime(ctx), js_lws_context_class_id,
|
|
&js_lws_context_class);
|
|
proto = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, proto, js_lws_context_proto_funcs,
|
|
countof(js_lws_context_proto_funcs));
|
|
JS_SetClassProto(ctx, js_lws_context_class_id, proto);
|
|
|
|
JS_NewClassID(&js_lws_wsi_class_id);
|
|
JS_NewClass(JS_GetRuntime(ctx), js_lws_wsi_class_id, &js_lws_wsi_class);
|
|
proto = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, proto, js_lws_wsi_proto_funcs,
|
|
countof(js_lws_wsi_proto_funcs));
|
|
JS_SetClassProto(ctx, js_lws_wsi_class_id, proto);
|
|
|
|
return JS_SetModuleExportList(ctx, m, js_lws_funcs, countof(js_lws_funcs));
|
|
}
|
|
|
|
JSModuleDef *js_init_module(JSContext *ctx, const char *module_name)
|
|
{
|
|
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_lws_init);
|
|
if (m == NULL)
|
|
return NULL;
|
|
JS_AddModuleExportList(ctx, m, js_lws_funcs, countof(js_lws_funcs));
|
|
return m;
|
|
}
|