Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F9901595
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
83 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/modules/timer.c b/src/modules/timer.c
index a2e21a8..ae27b6d 100644
--- a/src/modules/timer.c
+++ b/src/modules/timer.c
@@ -1,953 +1,967 @@
#include <compat.h> // IWYU pragma: keep
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <uv.h>
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "silver/engine.h"
#include "gc/roots.h"
#include "gc/modules.h"
#include "modules/abort.h"
#include "modules/timer.h"
#include "modules/symbol.h"
typedef struct timer_entry {
uv_timer_t handle;
+ ant_value_t obj;
ant_value_t callback;
ant_value_t *args;
int nargs;
int timer_id;
int active;
int closed;
int is_interval;
uint64_t timeout_ms;
struct timer_entry *next;
struct timer_entry *prev;
} timer_entry_t;
typedef struct microtask_entry {
ant_value_t callback;
ant_value_t promise;
struct microtask_entry *next;
uint8_t argc;
ant_value_t argv[];
} microtask_entry_t;
typedef struct immediate_entry {
ant_value_t callback;
int immediate_id;
int active;
struct immediate_entry *next;
} immediate_entry_t;
static struct {
ant_t *js;
timer_entry_t *timers;
microtask_entry_t *next_ticks;
microtask_entry_t *next_ticks_tail;
microtask_entry_t *next_ticks_processing;
microtask_entry_t *microtasks;
microtask_entry_t *microtasks_tail;
microtask_entry_t *microtasks_processing;
immediate_entry_t *immediates;
immediate_entry_t *immediates_tail;
int next_timer_id;
int next_immediate_id;
int active_timer_count;
} timer_state = {
.js = NULL,
.timers = NULL,
.next_ticks = NULL,
.next_ticks_tail = NULL,
.next_ticks_processing = NULL,
.microtasks = NULL,
.microtasks_tail = NULL,
.microtasks_processing = NULL,
.immediates = NULL,
.immediates_tail = NULL,
.next_timer_id = 1,
.next_immediate_id = 1,
.active_timer_count = 0,
};
static ant_value_t g_timeout_proto = 0;
static ant_value_t g_interval_proto = 0;
static void add_timer_entry(timer_entry_t *entry) {
entry->next = timer_state.timers;
entry->prev = NULL;
if (timer_state.timers) timer_state.timers->prev = entry;
timer_state.timers = entry;
}
static void remove_timer_entry(timer_entry_t *entry) {
if (entry->prev) entry->prev->next = entry->next;
else timer_state.timers = entry->next;
if (entry->next) entry->next->prev = entry->prev;
+ entry->next = NULL;
+ entry->prev = NULL;
}
static int timer_entry_is_registered(timer_entry_t *entry) {
for (timer_entry_t *it = timer_state.timers; it != NULL; it = it->next)
if (it == entry) return 1;
return 0;
}
static timer_entry_t *find_timer_entry_by_id(int timer_id) {
for (timer_entry_t *entry = timer_state.timers; entry != NULL; entry = entry->next)
if (entry->timer_id == timer_id) return entry;
return NULL;
}
static int timer_copy_args(timer_entry_t *entry, ant_value_t *args, int nargs) {
entry->nargs = nargs > 2 ? nargs - 2 : 0;
if (entry->nargs > 0) {
entry->args = ant_calloc(sizeof(ant_value_t) * entry->nargs);
if (!entry->args) return -1;
memcpy(entry->args, args + 2, sizeof(ant_value_t) * entry->nargs);
} else entry->args = NULL;
return 0;
}
static void timer_release_args(timer_entry_t *entry) {
if (!entry) return;
if (entry->args) {
free(entry->args);
entry->args = NULL;
}
entry->nargs = 0;
}
static void timer_release_callback_args(timer_entry_t *entry) {
if (!entry) return;
entry->callback = js_mkundef();
timer_release_args(entry);
}
static int timer_copy_args_from_object(ant_t *js, timer_entry_t *entry, ant_value_t obj) {
ant_value_t args_arr = js_get_slot(obj, SLOT_AUX);
ant_offset_t len = vtype(args_arr) == T_ARR ? js_arr_len(js, args_arr) : 0;
timer_release_args(entry);
if (len == 0) return 0;
entry->args = ant_calloc(sizeof(ant_value_t) * (size_t)len);
if (!entry->args) return -1;
entry->nargs = (int)len;
for (ant_offset_t i = 0; i < len; i++) entry->args[i] = js_arr_get(js, args_arr, i);
return 0;
}
static ant_value_t timer_make_args_array(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t arr = js_mkundef();
int arg_count = nargs > 2 ? nargs - 2 : 0;
if (arg_count <= 0) return arr;
arr = js_mkarr(js);
for (int i = 0; i < arg_count; i++) js_arr_push(js, arr, args[i + 2]);
return arr;
}
static ant_value_t timer_to_primitive(ant_t *js, ant_value_t *args, int nargs) {
return js_get_slot(js_getthis(js), SLOT_DATA);
}
static ant_value_t timer_inspect(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_obj = js_getthis(js);
ant_value_t id_val = js_get_slot(this_obj, SLOT_DATA);
int timer_id = vtype(id_val) == T_NUM ? (int)js_getnum(id_val) : 0;
ant_value_t tag_val = js_get_sym(js, this_obj, get_toStringTag_sym());
const char *tag = vtype(tag_val) == T_STR ? js_getstr(js, tag_val, NULL) : "Timeout";
js_inspect_builder_t builder;
if (!js_inspect_builder_init_dynamic(&builder, js, 128)) {
return js_mkerr(js, "out of memory");
}
bool ok = js_inspect_header(&builder, "%s (%d)", tag, timer_id);
if (ok) ok = js_inspect_object_body(&builder, this_obj);
if (ok) ok = js_inspect_close(&builder);
if (!ok) {
js_inspect_builder_dispose(&builder);
return js_mkerr(js, "out of memory");
}
return js_inspect_builder_result(&builder);
}
static ant_value_t js_timer_ref(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_obj = js_getthis(js);
timer_entry_t *entry = find_timer_entry_by_id((int)js_getnum(js_get_slot(this_obj, SLOT_DATA)));
if (entry && !entry->closed && !uv_is_closing((uv_handle_t *)&entry->handle))
uv_ref((uv_handle_t *)&entry->handle);
return this_obj;
}
static ant_value_t js_timer_unref(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_obj = js_getthis(js);
timer_entry_t *entry = find_timer_entry_by_id((int)js_getnum(js_get_slot(this_obj, SLOT_DATA)));
if (entry && !entry->closed && !uv_is_closing((uv_handle_t *)&entry->handle))
uv_unref((uv_handle_t *)&entry->handle);
return this_obj;
}
static ant_value_t js_timer_has_ref(ant_t *js, ant_value_t *args, int nargs) {
timer_entry_t *entry = find_timer_entry_by_id((int)js_getnum(js_get_slot(js_getthis(js), SLOT_DATA)));
if (!entry || entry->closed || uv_is_closing((uv_handle_t *)&entry->handle)) return js_false;
return js_bool(uv_has_ref((const uv_handle_t *)&entry->handle) != 0);
}
+static int timer_id_from_arg(ant_t *js, ant_value_t arg) {
+ if (vtype(arg) == T_NUM) return (int)js_getnum(arg);
+ return (int)js_getnum(js_get_slot(arg, SLOT_DATA));
+}
+
+static void timer_close_cb(uv_handle_t *h) {
+ timer_entry_t *entry = (timer_entry_t *)h->data;
+
+ if (!entry) return;
+ if (timer_entry_is_registered(entry)) remove_timer_entry(entry);
+
+ entry->closed = 1;
+ entry->active = 0;
+ entry->obj = js_mkundef();
+ timer_release_callback_args(entry);
+ free(entry);
+}
+
+static void timer_close_entry(timer_entry_t *entry) {
+ if (!entry || entry->closed) return;
+ if (entry->active) {
+ entry->active = 0;
+ timer_state.active_timer_count--;
+ }
+ if (!uv_is_closing((uv_handle_t *)&entry->handle))
+ uv_close((uv_handle_t *)&entry->handle, timer_close_cb);
+}
+
+static void timer_object_finalize(ant_t *js, ant_object_t *obj) {
+ ant_value_t timer_obj = js_obj_from_ptr(obj);
+ int timer_id = (int)js_getnum(js_get_slot(timer_obj, SLOT_DATA));
+ timer_entry_t *entry = find_timer_entry_by_id(timer_id);
+ if (entry) timer_close_entry(entry);
+}
+
static ant_value_t timer_make_object(
- ant_t *js,
- int id,
- double delay_ms,
- int is_interval,
- ant_value_t callback,
- ant_value_t timer_args
+ ant_t *js, timer_entry_t *entry,
+ double delay_ms, int is_interval, ant_value_t timer_args
) {
ant_value_t obj = js_mkobj(js);
ant_value_t proto = is_interval ? g_interval_proto : g_timeout_proto;
-
if (is_object_type(proto)) js_set_proto_init(obj, proto);
js_set(js, obj, "delay", js_mknum(delay_ms));
js_set(js, obj, "repeat", is_interval ? js_mknum(delay_ms) : js_mknull());
-
- js_set(js, obj, "callback", callback);
+ js_set(js, obj, "callback", entry->callback);
js_set_descriptor(js, obj, "callback", 8, JS_DESC_W | JS_DESC_C);
- js_set_slot(obj, SLOT_DATA, js_mknum((double)id));
+ js_set_slot(obj, SLOT_DATA, js_mknum((double)entry->timer_id));
js_set_slot_wb(js, obj, SLOT_AUX, timer_args);
js_set_sym(js, obj, get_toPrimitive_sym(), js_mkfun(timer_to_primitive));
+ js_set_finalizer(obj, timer_object_finalize);
+ entry->obj = obj;
return obj;
}
-static int timer_id_from_arg(ant_t *js, ant_value_t arg) {
- if (vtype(arg) == T_NUM) return (int)js_getnum(arg);
- return (int)js_getnum(js_get_slot(arg, SLOT_DATA));
-}
-
-static void timer_close_cb(uv_handle_t *h) {
- timer_entry_t *entry = (timer_entry_t *)h->data;
- if (!entry) return;
- if (entry->closed) return;
- if (timer_entry_is_registered(entry)) remove_timer_entry(entry);
- entry->closed = 1;
- entry->active = 0;
- timer_release_callback_args(entry);
- entry->next = NULL;
- entry->prev = NULL;
-}
-
static void timer_callback(uv_timer_t *handle) {
timer_entry_t *entry = (timer_entry_t *)handle->data;
if (!entry || entry->closed || !timer_entry_is_registered(entry) || !entry->active) return;
ant_t *js = timer_state.js;
ant_value_t callback = entry->callback;
if (!entry->is_interval) {
entry->active = 0;
timer_state.active_timer_count--;
}
GC_ROOT_SAVE(root_mark, js);
GC_ROOT_PIN(js, callback);
for (int i = 0; i < entry->nargs; i++) GC_ROOT_PIN(js, entry->args[i]);
sv_vm_call(js->vm, js, callback, js_mkundef(), entry->args, entry->nargs, NULL, false);
GC_ROOT_RESTORE(js, root_mark);
if (!entry->is_interval && !entry->active) timer_release_callback_args(entry);
process_microtasks(js);
}
static ant_value_t js_timer_refresh(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t this_obj = js_getthis(js);
timer_entry_t *entry = find_timer_entry_by_id((int)js_getnum(js_get_slot(this_obj, SLOT_DATA)));
if (!entry || entry->closed || uv_is_closing((uv_handle_t *)&entry->handle)) return this_obj;
if (!entry->active) {
if (vtype(entry->callback) == T_UNDEF) {
entry->callback = js_get(js, this_obj, "callback");
if (!is_callable(entry->callback)) return this_obj;
if (timer_copy_args_from_object(js, entry, this_obj) != 0)
return js_mkerr(js, "failed to allocate timer args");
}
entry->active = 1;
timer_state.active_timer_count++;
}
uv_timer_start(
&entry->handle,
timer_callback,
entry->timeout_ms,
entry->is_interval ? entry->timeout_ms : 0
);
return this_obj;
}
// setTimeout(callback, delay, ...args)
static ant_value_t js_set_timeout(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) {
return js_mkerr(js, "setTimeout requires at least 1 argument (callback)");
}
ant_value_t callback = args[0];
double delay_ms = nargs > 1 ? js_getnum(args[1]) : 0;
uint64_t ms = delay_ms >= 1 ? (uint64_t)delay_ms : 0;
ant_value_t timer_args = timer_make_args_array(js, args, nargs);
timer_entry_t *entry = ant_calloc(sizeof(timer_entry_t));
if (entry == NULL) return js_mkerr(js, "failed to allocate timer");
if (timer_copy_args(entry, args, nargs) < 0) {
free(entry);
return js_mkerr(js, "failed to allocate timer args");
}
uv_timer_init(uv_default_loop(), &entry->handle);
entry->handle.data = entry;
entry->callback = callback;
entry->timer_id = timer_state.next_timer_id++;
entry->active = 1;
entry->closed = 0;
entry->is_interval = 0;
entry->timeout_ms = ms;
add_timer_entry(entry);
timer_state.active_timer_count++;
uv_timer_start(&entry->handle, timer_callback, ms, 0);
- return timer_make_object(js, entry->timer_id, delay_ms, 0, callback, timer_args);
+ return timer_make_object(js, entry, delay_ms, 0, timer_args);
}
// setInterval(callback, delay, ...args)
static ant_value_t js_set_interval(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) {
return js_mkerr(js, "setInterval requires at least 1 argument (callback)");
}
ant_value_t callback = args[0];
double delay_ms = nargs > 1 ? js_getnum(args[1]) : 0;
uint64_t ms = delay_ms >= 1 ? (uint64_t)delay_ms : 1;
ant_value_t timer_args = timer_make_args_array(js, args, nargs);
timer_entry_t *entry = ant_calloc(sizeof(timer_entry_t));
if (entry == NULL) return js_mkerr(js, "failed to allocate timer");
if (timer_copy_args(entry, args, nargs) < 0) {
free(entry);
return js_mkerr(js, "failed to allocate timer args");
}
uv_timer_init(uv_default_loop(), &entry->handle);
entry->handle.data = entry;
entry->callback = callback;
entry->timer_id = timer_state.next_timer_id++;
entry->active = 1;
entry->closed = 0;
entry->is_interval = 1;
entry->timeout_ms = ms;
add_timer_entry(entry);
timer_state.active_timer_count++;
uv_timer_start(&entry->handle, timer_callback, ms, ms);
- return timer_make_object(js, entry->timer_id, delay_ms, 1, callback, timer_args);
+ return timer_make_object(js, entry, delay_ms, 1, timer_args);
}
// clearTimeout(timerId | timerObject)
static ant_value_t js_clear_timeout(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
int timer_id = timer_id_from_arg(js, args[0]);
for (timer_entry_t *entry = timer_state.timers; entry != NULL; entry = entry->next) {
if (entry->timer_id == timer_id && !entry->closed) {
- if (entry->active) {
- entry->active = 0;
- timer_state.active_timer_count--;
- }
- if (!uv_is_closing((uv_handle_t *)&entry->handle)) uv_close((uv_handle_t *)&entry->handle, timer_close_cb);
+ timer_close_entry(entry);
break;
}}
return js_mkundef();
}
// setImmediate(callback)
static ant_value_t js_set_immediate(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) {
return js_mkerr(js, "setImmediate requires 1 argument (callback)");
}
ant_value_t callback = args[0];
immediate_entry_t *entry = ant_calloc(sizeof(immediate_entry_t));
if (entry == NULL) {
return js_mkerr(js, "failed to allocate immediate");
}
entry->callback = callback;
entry->immediate_id = timer_state.next_immediate_id++;
entry->active = 1;
entry->next = NULL;
if (timer_state.immediates_tail == NULL) {
timer_state.immediates = entry;
timer_state.immediates_tail = entry;
} else {
timer_state.immediates_tail->next = entry;
timer_state.immediates_tail = entry;
}
ant_value_t obj = js_mkobj(js);
js_set(js, obj, "id", js_mknum((double)entry->immediate_id));
js_set(js, obj, "callback", callback);
return obj;
}
// clearImmediate(immediateId | immediateObject)
static ant_value_t js_clear_immediate(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
int immediate_id = timer_id_from_arg(js, args[0]);
for (immediate_entry_t *entry = timer_state.immediates; entry != NULL; entry = entry->next) {
if (entry->immediate_id == immediate_id) { entry->active = 0; break; }
}
return js_mkundef();
}
// queueMicrotask(callback)
static ant_value_t js_queue_microtask(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) {
return js_mkerr(js, "queueMicrotask requires 1 argument (callback)");
}
queue_microtask(js, args[0]);
return js_mkundef();
}
static ant_value_t timers_promises_get_state(ant_t *js) {
return js_get_slot(js->current_func, SLOT_DATA);
}
static ant_value_t timers_promises_abort_reason(ant_t *js, ant_value_t signal) {
ant_value_t reason = abort_signal_get_reason(signal);
if (vtype(reason) != T_UNDEF && vtype(reason) != T_NULL) return reason;
return js_mkerr_typed(js, JS_ERR_TYPE, "The operation was aborted");
}
static void timers_promises_remove_abort_listener(ant_t *js, ant_value_t state) {
ant_value_t signal = 0;
ant_value_t listener = 0;
if (!is_object_type(state)) return;
signal = js_get(js, state, "signal");
listener = js_get(js, state, "abortListener");
if (abort_signal_is_signal(signal) && is_callable(listener))
abort_signal_remove_listener(js, signal, listener);
js_set(js, state, "abortListener", js_mkundef());
}
static void timers_promises_settle(ant_t *js, ant_value_t state, bool reject, ant_value_t value) {
ant_value_t settled = 0;
ant_value_t promise = 0;
if (!is_object_type(state)) return;
settled = js_get(js, state, "settled");
if (js_truthy(js, settled)) return;
js_set(js, state, "settled", js_true);
timers_promises_remove_abort_listener(js, state);
js_set(js, state, "handle", js_mkundef());
promise = js_get(js, state, "promise");
if (reject) js_reject_promise(js, promise, value);
else js_resolve_promise(js, promise, value);
}
static ant_value_t timers_promises_resolve(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = timers_promises_get_state(js);
ant_value_t value = js_mkundef();
if (!is_object_type(state)) return js_mkundef();
value = js_get(js, state, "value");
timers_promises_settle(js, state, false, value);
return js_mkundef();
}
static ant_value_t timers_promises_on_abort(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state = timers_promises_get_state(js);
ant_value_t signal = 0;
ant_value_t handle = 0;
ant_value_t is_immediate = 0;
ant_value_t reason = js_mkundef();
ant_value_t clear_args[1];
if (!is_object_type(state)) return js_mkundef();
signal = js_get(js, state, "signal");
handle = js_get(js, state, "handle");
is_immediate = js_get(js, state, "isImmediate");
if (vtype(handle) != T_UNDEF && vtype(handle) != T_NULL) {
clear_args[0] = handle;
if (js_truthy(js, is_immediate)) js_clear_immediate(js, clear_args, 1);
else js_clear_timeout(js, clear_args, 1);
}
if (abort_signal_is_signal(signal)) reason = timers_promises_abort_reason(js, signal);
else reason = js_mkerr_typed(js, JS_ERR_TYPE, "The operation was aborted");
timers_promises_settle(js, state, true, reason);
return js_mkundef();
}
static bool timers_promises_parse_options(
ant_t *js,
ant_value_t value,
ant_value_t *signal_out,
ant_value_t *error_out
) {
ant_value_t signal = js_mkundef();
if (signal_out) *signal_out = js_mkundef();
if (error_out) *error_out = js_mkundef();
if (vtype(value) == T_UNDEF || vtype(value) == T_NULL) return true;
if (vtype(value) != T_OBJ) {
if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Timer options must be an object");
return false;
}
signal = js_get(js, value, "signal");
if (vtype(signal) != T_UNDEF && vtype(signal) != T_NULL && !abort_signal_is_signal(signal)) {
if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "options.signal must be an AbortSignal");
return false;
}
if (signal_out) *signal_out = signal;
return true;
}
static ant_value_t timers_promises_schedule(
ant_t *js,
double delay_ms,
ant_value_t value,
ant_value_t signal,
bool is_immediate
) {
ant_value_t promise = js_mkpromise(js);
ant_value_t state = js_mkobj(js);
ant_value_t callback = 0;
ant_value_t handle = 0;
ant_value_t args[2];
if (abort_signal_is_signal(signal) && abort_signal_is_aborted(signal)) {
js_reject_promise(js, promise, timers_promises_abort_reason(js, signal));
return promise;
}
js_set(js, state, "promise", promise);
js_set(js, state, "value", value);
js_set(js, state, "signal", signal);
js_set(js, state, "abortListener", js_mkundef());
js_set(js, state, "handle", js_mkundef());
js_set(js, state, "settled", js_false);
js_set(js, state, "isImmediate", js_bool(is_immediate));
callback = js_heavy_mkfun(js, timers_promises_resolve, state);
if (is_immediate) handle = js_set_immediate(js, &callback, 1);
else {
args[0] = callback;
args[1] = js_mknum(delay_ms);
handle = js_set_timeout(js, args, 2);
}
if (is_err(handle)) {
js_reject_promise(js, promise, handle);
return promise;
}
js_set(js, state, "handle", handle);
if (abort_signal_is_signal(signal)) {
ant_value_t listener = js_heavy_mkfun(js, timers_promises_on_abort, state);
js_set(js, state, "abortListener", listener);
abort_signal_add_listener(js, signal, listener);
}
return promise;
}
static ant_value_t js_timers_promises_setTimeout(ant_t *js, ant_value_t *args, int nargs) {
double delay_ms = nargs > 0 ? js_getnum(args[0]) : 0;
ant_value_t value = nargs > 1 ? args[1] : js_mkundef();
ant_value_t options = nargs > 2 ? args[2] : js_mkundef();
ant_value_t signal = js_mkundef();
ant_value_t error = js_mkundef();
if (!timers_promises_parse_options(js, options, &signal, &error)) return error;
return timers_promises_schedule(js, delay_ms, value, signal, false);
}
static ant_value_t js_timers_promises_setImmediate(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t value = nargs > 0 ? args[0] : js_mkundef();
ant_value_t options = nargs > 1 ? args[1] : js_mkundef();
ant_value_t signal = js_mkundef();
ant_value_t error = js_mkundef();
if (!timers_promises_parse_options(js, options, &signal, &error)) return error;
return timers_promises_schedule(js, 0, value, signal, true);
}
static ant_value_t js_timers_promises_setInterval(ant_t *js, ant_value_t *args, int nargs) {
return js_mkerr_typed(js, JS_ERR_TYPE, "node:timers/promises setInterval() is not implemented yet");
}
static ant_value_t js_timers_promises_scheduler_wait(ant_t *js, ant_value_t *args, int nargs) {
return js_timers_promises_setTimeout(js, args, nargs);
}
static ant_value_t js_timers_promises_scheduler_yield(ant_t *js, ant_value_t *args, int nargs) {
return js_timers_promises_setImmediate(js, args, nargs);
}
static void queue_microtask_entry(
microtask_entry_t **head,
microtask_entry_t **tail,
microtask_entry_t *entry
) {
if (*tail == NULL) goto empty;
(*tail)->next = entry;
*tail = entry;
return;
empty:
*head = entry;
*tail = entry;
}
void queue_microtask(ant_t *js, ant_value_t callback) {
microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t));
if (entry == NULL) return;
entry->callback = callback;
entry->promise = js_mkundef();
entry->next = NULL;
entry->argc = 0;
queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry);
}
void queue_microtask_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs) {
if (nargs <= 0) { queue_microtask(js, callback); return; }
microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t) + (size_t)nargs * sizeof(ant_value_t));
if (entry == NULL) return;
entry->callback = callback;
entry->promise = js_mkundef();
entry->next = NULL;
entry->argc = (uint8_t)nargs;
for (int i = 0; i < nargs; i++) entry->argv[i] = args[i];
queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry);
}
void queue_next_tick(ant_t *js, ant_value_t callback) {
microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t));
if (entry == NULL) return;
entry->callback = callback;
entry->promise = js_mkundef();
entry->next = NULL;
entry->argc = 0;
queue_microtask_entry(&timer_state.next_ticks, &timer_state.next_ticks_tail, entry);
}
void queue_next_tick_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs) {
if (nargs <= 0) { queue_next_tick(js, callback); return; }
microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t) + (size_t)nargs * sizeof(ant_value_t));
if (entry == NULL) return;
entry->callback = callback;
entry->promise = js_mkundef();
entry->next = NULL;
entry->argc = (uint8_t)nargs;
for (int i = 0; i < nargs; i++) entry->argv[i] = args[i];
queue_microtask_entry(&timer_state.next_ticks, &timer_state.next_ticks_tail, entry);
}
void queue_promise_trigger(ant_t *js, ant_value_t promise) {
if (!js_mark_promise_trigger_queued(js, promise)) return;
microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t));
if (entry == NULL) {
js_mark_promise_trigger_dequeued(js, promise);
return;
}
entry->callback = js_mkundef();
entry->promise = promise;
entry->next = NULL;
queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry);
}
static inline void process_microtask_entry(ant_t *js, microtask_entry_t *entry) {
if (!entry) return;
if (vtype(entry->promise) == T_PROMISE) {
GC_ROOT_SAVE(root_mark, js);
ant_value_t promise = entry->promise;
GC_ROOT_PIN(js, promise);
js_mark_promise_trigger_dequeued(js, promise);
js_process_promise_handlers(js, promise);
GC_ROOT_RESTORE(js, root_mark);
return;
}
GC_ROOT_SAVE(root_mark, js);
ant_value_t callback = entry->callback;
GC_ROOT_PIN(js, callback);
for (uint8_t i = 0; i < entry->argc; i++) GC_ROOT_PIN(js, entry->argv[i]);
sv_vm_call(js->vm, js, callback, js_mkundef(), entry->argv, entry->argc, NULL, false);
GC_ROOT_RESTORE(js, root_mark);
}
static inline microtask_entry_t *take_microtask_batch(void) {
microtask_entry_t *batch = timer_state.microtasks;
timer_state.microtasks = NULL;
timer_state.microtasks_tail = NULL;
timer_state.microtasks_processing = batch;
return batch;
}
static inline microtask_entry_t *take_next_tick_batch(void) {
microtask_entry_t *batch = timer_state.next_ticks;
timer_state.next_ticks = NULL;
timer_state.next_ticks_tail = NULL;
timer_state.next_ticks_processing = batch;
return batch;
}
static inline void process_microtask_batch(ant_t *js, microtask_entry_t *batch) {
while (batch != NULL) {
microtask_entry_t *entry = batch;
batch = entry->next;
timer_state.microtasks_processing = batch;
process_microtask_entry(js, entry);
free(entry);
}}
static inline void process_next_tick_batch(ant_t *js, microtask_entry_t *batch) {
while (batch != NULL) {
microtask_entry_t *entry = batch;
batch = entry->next;
timer_state.next_ticks_processing = batch;
process_microtask_entry(js, entry);
free(entry);
}}
static void process_microtasks_internal(ant_t *js, bool check_unhandled_rejections) {
microtask_entry_t *batch = NULL;
if (!js || js->microtasks_draining) return;
js->microtasks_draining = true;
while (timer_state.next_ticks != NULL || timer_state.microtasks != NULL) {
while ((batch = timer_state.next_ticks) != NULL) {
batch = take_next_tick_batch();
process_next_tick_batch(js, batch);
}
while ((batch = timer_state.microtasks) != NULL) {
batch = take_microtask_batch();
process_microtask_batch(js, batch);
}}
timer_state.next_ticks_processing = NULL;
timer_state.microtasks_processing = NULL;
if (check_unhandled_rejections) js_check_unhandled_rejections(js);
js->microtasks_draining = false;
reap_retired_coroutines();
}
void process_microtasks(ant_t *js) {
process_microtasks_internal(js, true);
}
bool js_maybe_drain_microtasks(ant_t *js) {
if (!js) return false;
if (js->microtasks_draining) return false;
if (js->vm_exec_depth != 0) return false;
if (!has_pending_microtasks()) return false;
process_microtasks_internal(js, true);
return true;
}
bool js_maybe_drain_microtasks_after_async_settle(ant_t *js) {
if (!js) return false;
if (js->microtasks_draining) return false;
if (!has_pending_microtasks()) return false;
process_microtasks_internal(js, false);
return true;
}
void process_immediates(ant_t *js) {
while (timer_state.immediates != NULL) {
immediate_entry_t *entry = timer_state.immediates;
timer_state.immediates = entry->next;
if (timer_state.immediates == NULL) {
timer_state.immediates_tail = NULL;
}
if (entry->active) {
ant_value_t args[0];
sv_vm_call(js->vm, js, entry->callback, js_mkundef(), args, 0, NULL, false);
process_microtasks(js);
}
free(entry);
}}
int has_pending_immediates(void) {
for (
immediate_entry_t *entry = timer_state.immediates;
entry != NULL; entry = entry->next
) if (entry->active) return 1;
return 0;
}
int has_pending_timers(void) {
return timer_state.active_timer_count > 0;
}
int has_pending_microtasks(void) {
return (timer_state.next_ticks != NULL || timer_state.microtasks != NULL) ? 1 : 0;
}
static void timers_define_common(ant_t *js, ant_value_t obj) {
js_set(js, obj, "setTimeout", js_mkfun_flags(js_set_timeout, CFUNC_HAS_PROTOTYPE));
js_set(js, obj, "clearTimeout", js_mkfun_flags(js_clear_timeout, CFUNC_HAS_PROTOTYPE));
js_set(js, obj, "setInterval", js_mkfun_flags(js_set_interval, CFUNC_HAS_PROTOTYPE));
js_set(js, obj, "clearInterval", js_mkfun_flags(js_clear_timeout, CFUNC_HAS_PROTOTYPE));
js_set(js, obj, "setImmediate", js_mkfun_flags(js_set_immediate, CFUNC_HAS_PROTOTYPE));
js_set(js, obj, "clearImmediate", js_mkfun_flags(js_clear_immediate, CFUNC_HAS_PROTOTYPE));
js_set(js, obj, "queueMicrotask", js_mkfun(js_queue_microtask));
}
void init_timer_module() {
ant_t *js = rt->js;
timer_state.js = js;
g_timeout_proto = js_mkobj(js);
g_interval_proto = js_mkobj(js);
gc_register_root(&g_timeout_proto);
gc_register_root(&g_interval_proto);
js_set_proto_init(g_timeout_proto, js->sym.object_proto);
js_set(js, g_timeout_proto, "ref", js_mkfun(js_timer_ref));
js_set(js, g_timeout_proto, "unref", js_mkfun(js_timer_unref));
js_set(js, g_timeout_proto, "hasRef", js_mkfun(js_timer_has_ref));
js_set(js, g_timeout_proto, "refresh", js_mkfun(js_timer_refresh));
js_set_sym(js, g_timeout_proto, get_toStringTag_sym(), js_mkstr(js, "Timeout", 7));
js_set_sym(js, g_timeout_proto, get_inspect_sym(), js_mkfun(timer_inspect));
js_set_proto_init(g_interval_proto, js->sym.object_proto);
js_set(js, g_interval_proto, "ref", js_mkfun(js_timer_ref));
js_set(js, g_interval_proto, "unref", js_mkfun(js_timer_unref));
js_set(js, g_interval_proto, "hasRef", js_mkfun(js_timer_has_ref));
js_set(js, g_interval_proto, "refresh", js_mkfun(js_timer_refresh));
js_set_sym(js, g_interval_proto, get_toStringTag_sym(), js_mkstr(js, "Interval", 8));
js_set_sym(js, g_interval_proto, get_inspect_sym(), js_mkfun(timer_inspect));
timers_define_common(js, js_glob(js));
}
ant_value_t timers_library(ant_t *js) {
ant_value_t lib = js_mkobj(js);
timers_define_common(js, lib);
js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "timers", 6));
return lib;
}
// TODO: mostly stubbed
ant_value_t timers_promises_library(ant_t *js) {
ant_value_t lib = js_mkobj(js);
ant_value_t scheduler = js_mkobj(js);
js_set(js, lib, "scheduler", scheduler);
js_set(js, lib, "setTimeout", js_mkfun(js_timers_promises_setTimeout));
js_set(js, lib, "setImmediate", js_mkfun(js_timers_promises_setImmediate));
js_set(js, lib, "setInterval", js_mkfun(js_timers_promises_setInterval));
js_set(js, scheduler, "wait", js_mkfun(js_timers_promises_scheduler_wait));
js_set(js, scheduler, "yield", js_mkfun(js_timers_promises_scheduler_yield));
js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "timers/promises", 16));
return lib;
}
void gc_mark_timers(ant_t *js, gc_mark_fn mark) {
if (is_object_type(g_timeout_proto)) mark(js, g_timeout_proto);
if (is_object_type(g_interval_proto)) mark(js, g_interval_proto);
for (timer_entry_t *t = timer_state.timers; t; t = t->next) {
if (!t->active) continue;
+ if (is_object_type(t->obj)) mark(js, t->obj);
mark(js, t->callback);
for (int i = 0; i < t->nargs; i++) mark(js, t->args[i]);
}
for (microtask_entry_t *m = timer_state.microtasks; m; m = m->next) {
mark(js, m->callback);
mark(js, m->promise);
for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]);
}
for (microtask_entry_t *m = timer_state.microtasks_processing; m; m = m->next) {
mark(js, m->callback);
mark(js, m->promise);
for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]);
}
for (microtask_entry_t *m = timer_state.next_ticks; m; m = m->next) {
mark(js, m->callback);
mark(js, m->promise);
for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]);
}
for (microtask_entry_t *m = timer_state.next_ticks_processing; m; m = m->next) {
mark(js, m->callback);
mark(js, m->promise);
for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]);
}
for (immediate_entry_t *i = timer_state.immediates; i; i = i->next) {
mark(js, i->callback);
}
}
diff --git a/src/modules/tls.c b/src/modules/tls.c
index cf8e0f6..244b1fe 100644
--- a/src/modules/tls.c
+++ b/src/modules/tls.c
@@ -1,1313 +1,1327 @@
// stub: node:tls implementation
// just enough for tls routing
#include <compat.h> // IWYU pragma: keep
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <tlsuv/tlsuv.h>
#include <tlsuv/tls_engine.h>
#include "ant.h"
#include "internal.h"
#include "ptr.h"
#include "errors.h"
#include "gc/modules.h"
#include "modules/tls.h"
#include "modules/buffer.h"
#include "modules/events.h"
#include "modules/net.h"
#include "modules/symbol.h"
#include "modules/timer.h"
#include "silver/engine.h"
typedef struct
ant_tls_socket_s
ant_tls_socket_t;
typedef struct ant_tls_context_wrap_s {
ant_value_t obj;
tls_context *ctx;
tlsuv_private_key_t key;
tlsuv_certificate_t cert;
unsigned refs;
bool closed;
} ant_tls_context_wrap_t;
typedef struct tls_read_chunk_s {
struct tls_read_chunk_s *next;
char *data;
size_t len;
size_t off;
} tls_read_chunk_t;
typedef struct tls_write_req_s {
uv_write_t req;
ant_tls_socket_t *socket;
struct tls_write_req_s *next;
ant_value_t callback;
char *data;
size_t len;
} tls_write_req_t;
typedef struct ant_tls_socket_s {
ant_t *js;
ant_value_t obj;
ant_value_t encoding;
tlsuv_stream_t stream;
uv_connect_t connect_req;
tls_context *ctx;
ant_tls_context_wrap_t *ctx_wrap;
ant_value_t secure_context;
char *host;
char *servername;
int port;
char **alpn_protocols;
int alpn_count;
tls_read_chunk_t *read_head;
tls_read_chunk_t *read_tail;
size_t read_len;
tls_write_req_t *writes;
struct ant_tls_socket_s *next_active;
uint64_t timeout_ms;
uint64_t bytes_read;
uint64_t bytes_written;
bool owns_ctx;
bool active;
bool connecting;
bool destroyed;
bool closing;
bool had_error;
bool ended;
bool read_drain_scheduled;
} ant_tls_socket_t;
enum {
TLS_CONTEXT_NATIVE_TAG = 0x544c5343u, // TLSC
TLS_SOCKET_NATIVE_TAG = 0x544c534bu, // TLSK
};
static ant_value_t g_tls_context_proto = 0;
static ant_value_t g_tls_context_ctor = 0;
static ant_value_t g_tls_socket_proto = 0;
static ant_value_t g_tls_socket_ctor = 0;
static ant_tls_socket_t *g_active_tls_sockets = NULL;
static void tls_context_dispose(ant_tls_context_wrap_t *wrap) {
if (!wrap) return;
if (wrap->cert && wrap->cert->free) wrap->cert->free(wrap->cert);
if (wrap->key && wrap->key->free) wrap->key->free(wrap->key);
if (wrap->ctx && wrap->ctx->free_ctx) wrap->ctx->free_ctx(wrap->ctx);
wrap->cert = NULL;
wrap->key = NULL;
wrap->ctx = NULL;
}
static void tls_context_free(ant_tls_context_wrap_t *wrap) {
if (!wrap || wrap->closed) return;
wrap->closed = true;
if (wrap->refs > 0) return;
tls_context_dispose(wrap);
}
static void tls_context_retain(ant_tls_context_wrap_t *wrap) {
if (wrap) wrap->refs++;
}
static void tls_context_release(ant_tls_context_wrap_t *wrap) {
if (!wrap || wrap->refs == 0) return;
wrap->refs--;
if (wrap->refs == 0 && wrap->closed) tls_context_dispose(wrap);
}
static ant_tls_context_wrap_t *tls_context_data(ant_value_t value) {
return (ant_tls_context_wrap_t *)js_get_native(value, TLS_CONTEXT_NATIVE_TAG);
}
static ant_tls_socket_t *tls_socket_data(ant_value_t value) {
return (ant_tls_socket_t *)js_get_native(value, TLS_SOCKET_NATIVE_TAG);
}
static void tls_add_active_socket(ant_tls_socket_t *socket) {
if (!socket || socket->active) return;
socket->active = true;
socket->next_active = g_active_tls_sockets;
g_active_tls_sockets = socket;
}
static void tls_remove_active_socket(ant_tls_socket_t *socket) {
ant_tls_socket_t **it = NULL;
for (it = &g_active_tls_sockets; *it; it = &(*it)->next_active) {
if (*it == socket) {
*it = socket->next_active;
socket->next_active = NULL;
socket->active = false;
return;
}}
}
static ant_value_t tls_call_value(
ant_t *js,
ant_value_t fn,
ant_value_t this_val,
ant_value_t *args,
int nargs
) {
ant_value_t saved_this = js->this_val;
ant_value_t result = js_mkundef();
js->this_val = this_val;
if (vtype(fn) == T_CFUNC) result = js_as_cfunc(fn)(js, args, nargs);
else result = sv_vm_call(js->vm, js, fn, this_val, args, nargs, NULL, false);
js->this_val = saved_this;
return result;
}
static bool tls_emit(ant_t *js, ant_value_t target, const char *event, ant_value_t *args, int nargs) {
return eventemitter_emit_args(js, target, event, args, nargs);
}
static ant_tls_socket_t *tls_require_socket(ant_t *js, ant_value_t this_val) {
ant_tls_socket_t *socket = tls_socket_data(this_val);
if (!socket) {
js->thrown_exists = true;
js->thrown_value = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS socket");
return NULL;
}
return socket;
}
static ant_value_t tls_make_buffer_chunk(ant_t *js, const char *data, size_t len) {
ArrayBufferData *ab = create_array_buffer_data(len);
if (!ab) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
if (len > 0 && data) memcpy(ab->data, data, len);
return create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Buffer");
}
static void tls_socket_sync_state(ant_tls_socket_t *socket) {
ant_t *js = NULL;
const char *ready_state = "open";
if (!socket || !is_object_type(socket->obj)) return;
js = socket->js;
if (socket->destroyed) ready_state = "closed";
else if (socket->connecting) ready_state = "opening";
js_set(js, socket->obj, "encrypted", js_true);
js_set(js, socket->obj, "authorized", js_true);
js_set(js, socket->obj, "authorizationError", js_mknull());
js_set(js, socket->obj, "secureConnecting", js_bool(socket->connecting));
js_set(js, socket->obj, "connecting", js_bool(socket->connecting));
js_set(js, socket->obj, "destroyed", js_bool(socket->destroyed));
js_set(js, socket->obj, "readable", js_bool(!socket->destroyed));
js_set(js, socket->obj, "writable", js_bool(!socket->destroyed));
js_set(js, socket->obj, "writableNeedDrain", js_false);
js_set(js, socket->obj, "readyState", js_mkstr(js, ready_state, strlen(ready_state)));
js_set(js, socket->obj, "timeout", js_mknum((double)socket->timeout_ms));
js_set(js, socket->obj, "bytesRead", js_mknum((double)socket->bytes_read));
js_set(js, socket->obj, "bytesWritten", js_mknum((double)socket->bytes_written));
js_set(js, socket->obj, "remoteAddress", socket->host ? js_mkstr(js, socket->host, strlen(socket->host)) : js_mkundef());
js_set(js, socket->obj, "remotePort", socket->port > 0 ? js_mknum((double)socket->port) : js_mkundef());
js_set(js, socket->obj, "remoteFamily", js_mkstr(js, "IPv4", 4));
}
static void tls_define_default_socket_state(ant_t *js, ant_value_t obj) {
if (!is_object_type(obj)) return;
if (vtype(js_get(js, obj, "encrypted")) == T_UNDEF) js_set(js, obj, "encrypted", js_true);
if (vtype(js_get(js, obj, "authorized")) == T_UNDEF) js_set(js, obj, "authorized", js_true);
if (vtype(js_get(js, obj, "authorizationError")) == T_UNDEF) js_set(js, obj, "authorizationError", js_mknull());
if (vtype(js_get(js, obj, "secureConnecting")) == T_UNDEF) js_set(js, obj, "secureConnecting", js_false);
}
static bool tls_socket_push_read(
ant_tls_socket_t *socket,
const char *data,
size_t len,
bool front
) {
tls_read_chunk_t *chunk = NULL;
if (!socket || !data || len == 0) return true;
chunk = calloc(1, sizeof(*chunk));
if (!chunk) return false;
chunk->data = malloc(len);
if (!chunk->data) {
free(chunk);
return false;
}
memcpy(chunk->data, data, len);
chunk->len = len;
if (front) {
chunk->next = socket->read_head;
socket->read_head = chunk;
if (!socket->read_tail) socket->read_tail = chunk;
} else {
if (socket->read_tail) socket->read_tail->next = chunk;
else socket->read_head = chunk;
socket->read_tail = chunk;
}
socket->read_len += len;
return true;
}
static void tls_socket_free_read_queue(ant_tls_socket_t *socket) {
tls_read_chunk_t *chunk = socket ? socket->read_head : NULL;
while (chunk) {
tls_read_chunk_t *next = chunk->next;
free(chunk->data);
free(chunk);
chunk = next;
}
if (socket) {
socket->read_head = NULL;
socket->read_tail = NULL;
socket->read_len = 0;
}
}
+static ant_value_t tls_make_error(ant_t *js, tls_context *ctx, long code, const char *fallback) {
+ const char *message = fallback;
+ if (ctx && ctx->strerror) {
+ const char *detail = ctx->strerror(code);
+ if (detail && *detail) message = detail;
+ }
+ return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message ? message : "TLS error");
+}
+
+static void tls_socket_free_alpn(ant_tls_socket_t *socket) {
+ if (!socket || !socket->alpn_protocols) return;
+ for (int i = 0; i < socket->alpn_count; i++) free(socket->alpn_protocols[i]);
+ free(socket->alpn_protocols);
+ socket->alpn_protocols = NULL;
+ socket->alpn_count = 0;
+}
+
+static void tls_socket_free(ant_tls_socket_t *socket) {
+ if (!socket) return;
+ tls_remove_active_socket(socket);
+
+ if (is_object_type(socket->obj))
+ js_clear_native(socket->obj, TLS_SOCKET_NATIVE_TAG);
+ tls_socket_free_read_queue(socket);
+ tls_socket_free_alpn(socket);
+
+ if (socket->ctx_wrap) tls_context_release(socket->ctx_wrap);
+ if (socket->owns_ctx && socket->ctx && socket->ctx->free_ctx)
+ socket->ctx->free_ctx(socket->ctx);
+
+ free(socket->host);
+ free(socket->servername);
+ free(socket);
+}
+
+static void tls_socket_close_cb(uv_handle_t *handle) {
+ tlsuv_stream_t *tls_stream = (tlsuv_stream_t *)handle;
+ ant_tls_socket_t *socket = tls_stream ? (ant_tls_socket_t *)tls_stream->data : NULL;
+
+ ant_t *js = socket ? socket->js : NULL;
+ ant_value_t had_error = 0;
+ if (!socket || !js) return;
+
+ socket->destroyed = true;
+ socket->connecting = false;
+ socket->closing = false;
+
+ tls_socket_sync_state(socket);
+ had_error = js_bool(socket->had_error);
+ tls_emit(js, socket->obj, "close", &had_error, 1);
+ tls_socket_free(socket);
+}
+
+static void tls_socket_close(ant_tls_socket_t *socket) {
+ if (!socket || socket->closing || socket->destroyed) return;
+ socket->closing = true;
+ tlsuv_stream_close(&socket->stream, tls_socket_close_cb);
+}
+
+static void tls_socket_maybe_emit_end(ant_tls_socket_t *socket) {
+ ant_t *js = socket ? socket->js : NULL;
+ if (!socket || !js || !socket->ended || socket->read_len != 0 || socket->read_head) return;
+ tls_emit(js, socket->obj, "end", NULL, 0);
+ tls_socket_close(socket);
+}
+
static ant_value_t tls_socket_drain_read_queue(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t obj = js_get_slot(js_getcurrentfunc(js), SLOT_DATA);
ant_tls_socket_t *socket = tls_socket_data(obj);
if (!socket) return js_mkundef();
socket->read_drain_scheduled = false;
while (
!socket->destroyed &&
socket->read_head &&
eventemitter_listener_count(js, obj, "data") > 0
) {
tls_read_chunk_t *chunk = socket->read_head;
size_t len = chunk->len - chunk->off;
ant_value_t data = vtype(socket->encoding) == T_STR
? js_mkstr(js, chunk->data + chunk->off, len)
: tls_make_buffer_chunk(js, chunk->data + chunk->off, len);
- socket->read_head = chunk->next;
- if (socket->read_tail == chunk) socket->read_tail = NULL;
- socket->read_len -= len;
- free(chunk->data);
- free(chunk);
-
if (is_err(data)) {
socket->had_error = true;
tls_emit(js, obj, "error", &data, 1);
return data;
}
+ socket->read_head = chunk->next;
+ if (socket->read_tail == chunk) socket->read_tail = NULL;
+ socket->read_len -= len;
+ free(chunk->data);
+ free(chunk);
+
tls_emit(js, obj, "data", &data, 1);
}
+ tls_socket_maybe_emit_end(socket);
return js_mkundef();
}
static void tls_socket_schedule_read_drain(ant_tls_socket_t *socket) {
if (!socket || socket->read_drain_scheduled || socket->destroyed) return;
socket->read_drain_scheduled = true;
queue_microtask(socket->js, js_heavy_mkfun(socket->js, tls_socket_drain_read_queue, socket->obj));
}
static bool tls_value_bytes(
ant_t *js,
ant_value_t value,
const char **bytes_out,
size_t *len_out,
ant_value_t *error_out
) {
ant_value_t str_value = 0;
const uint8_t *buffer_bytes = NULL;
if (error_out) *error_out = js_mkundef();
if (bytes_out) *bytes_out = NULL;
if (len_out) *len_out = 0;
if (vtype(value) == T_UNDEF || vtype(value) == T_NULL) return true;
if (buffer_source_get_bytes(js, value, &buffer_bytes, len_out)) {
if (bytes_out) *bytes_out = (const char *)buffer_bytes;
return true;
}
str_value = js_tostring_val(js, value);
if (is_err(str_value)) {
if (error_out) *error_out = str_value;
return false;
}
if (!bytes_out || !len_out) return true;
*bytes_out = js_getstr(js, str_value, len_out);
if (!*bytes_out) {
if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS input");
return false;
}
return true;
}
-static ant_value_t tls_make_error(ant_t *js, tls_context *ctx, long code, const char *fallback) {
- const char *message = fallback;
- if (ctx && ctx->strerror) {
- const char *detail = ctx->strerror(code);
- if (detail && *detail) message = detail;
- }
- return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message ? message : "TLS error");
-}
-
-static void tls_socket_free_alpn(ant_tls_socket_t *socket) {
- if (!socket || !socket->alpn_protocols) return;
- for (int i = 0; i < socket->alpn_count; i++) free(socket->alpn_protocols[i]);
- free(socket->alpn_protocols);
- socket->alpn_protocols = NULL;
- socket->alpn_count = 0;
-}
-
-static void tls_socket_free(ant_tls_socket_t *socket) {
- if (!socket) return;
- tls_remove_active_socket(socket);
- if (is_object_type(socket->obj)) js_clear_native(socket->obj, TLS_SOCKET_NATIVE_TAG);
- tls_socket_free_read_queue(socket);
- tls_socket_free_alpn(socket);
- if (socket->ctx_wrap) tls_context_release(socket->ctx_wrap);
- if (socket->owns_ctx && socket->ctx && socket->ctx->free_ctx) socket->ctx->free_ctx(socket->ctx);
- free(socket->host);
- free(socket->servername);
- free(socket);
-}
-
static ant_value_t tls_stream_error(ant_tls_socket_t *socket, int status, const char *fallback) {
const char *message = fallback;
if (socket) {
const char *detail = tlsuv_stream_get_error(&socket->stream);
if (detail && *detail) message = detail;
else if (status < 0) message = uv_strerror(status);
}
return js_mkerr_typed(socket->js, JS_ERR_TYPE, "%s", message ? message : "TLS error");
}
-static void tls_socket_close_cb(uv_handle_t *handle) {
- tlsuv_stream_t *tls_stream = (tlsuv_stream_t *)handle;
- ant_tls_socket_t *socket = tls_stream ? (ant_tls_socket_t *)tls_stream->data : NULL;
- ant_t *js = socket ? socket->js : NULL;
- ant_value_t had_error = 0;
-
- if (!socket || !js) return;
-
- socket->destroyed = true;
- socket->connecting = false;
- socket->closing = false;
- tls_socket_sync_state(socket);
- had_error = js_bool(socket->had_error);
- tls_emit(js, socket->obj, "close", &had_error, 1);
- tls_socket_free(socket);
-}
-
-static void tls_socket_close(ant_tls_socket_t *socket) {
- if (!socket || socket->closing || socket->destroyed) return;
- socket->closing = true;
- tlsuv_stream_close(&socket->stream, tls_socket_close_cb);
-}
-
static bool tls_parse_write_args(
ant_t *js,
ant_value_t *args,
int nargs,
const uint8_t **bytes_out,
size_t *len_out,
ant_value_t *callback_out,
ant_value_t *error_out
) {
ant_value_t value = 0;
if (bytes_out) *bytes_out = NULL;
if (len_out) *len_out = 0;
if (callback_out) *callback_out = js_mkundef();
if (error_out) *error_out = js_mkundef();
if (nargs < 1 || vtype(args[0]) == T_UNDEF || vtype(args[0]) == T_NULL) return true;
value = args[0];
if (!buffer_source_get_bytes(js, value, bytes_out, len_out)) {
size_t slen = 0;
ant_value_t str_val = js_tostring_val(js, value);
const char *str = NULL;
if (is_err(str_val)) {
if (error_out) *error_out = str_val;
return false;
}
str = js_getstr(js, str_val, &slen);
if (!str) {
if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid socket write data");
return false;
}
if (bytes_out) *bytes_out = (const uint8_t *)str;
if (len_out) *len_out = slen;
}
if (callback_out) {
if (nargs > 1 && is_callable(args[1])) *callback_out = args[1];
else if (nargs > 2 && is_callable(args[2])) *callback_out = args[2];
}
return true;
}
static ant_value_t js_tls_context_close(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_context_wrap_t *wrap = tls_context_data(js_getthis(js));
if (!wrap) return js_getthis(js);
tls_context_free(wrap);
js_clear_native(wrap->obj, TLS_CONTEXT_NATIVE_TAG);
return js_getthis(js);
}
static void tls_init_context_proto(ant_t *js) {
if (g_tls_context_proto && g_tls_context_ctor) return;
if (!g_tls_context_proto) {
g_tls_context_proto = js_mkobj(js);
js_set(js, g_tls_context_proto, "close", js_mkfun(js_tls_context_close));
js_set_sym(js, g_tls_context_proto, get_toStringTag_sym(), js_mkstr(js, "SecureContext", 13));
}
}
static void tls_write_remove(ant_tls_socket_t *socket, tls_write_req_t *write) {
tls_write_req_t **it = NULL;
if (!socket || !write) return;
for (it = &socket->writes; *it; it = &(*it)->next) {
if (*it == write) {
*it = write->next;
write->next = NULL;
return;
}}
}
static void tls_socket_maybe_close_after_writes(ant_tls_socket_t *socket) {
if (socket && socket->ended && !socket->writes) tls_socket_close(socket);
}
static void tls_socket_write_cb(uv_write_t *req, int status) {
tls_write_req_t *write = (tls_write_req_t *)req;
ant_tls_socket_t *socket = write ? write->socket : NULL;
ant_t *js = socket ? socket->js : NULL;
if (!socket || !js) return;
tls_write_remove(socket, write);
if (status < 0) {
ant_value_t err = tls_stream_error(socket, status, "TLS write failed");
socket->had_error = true;
tls_emit(js, socket->obj, "error", &err, 1);
tls_socket_close(socket);
} else {
socket->bytes_written += write->len;
tls_socket_sync_state(socket);
if (is_callable(write->callback))
tls_call_value(js, write->callback, js_mkundef(), NULL, 0);
}
free(write->data);
free(write);
tls_socket_maybe_close_after_writes(socket);
}
static void tls_alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
(void)handle;
buf->base = malloc(suggested_size);
buf->len = suggested_size;
}
static void tls_socket_on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
tlsuv_stream_t *tls_stream = (tlsuv_stream_t *)stream;
ant_tls_socket_t *socket = tls_stream ? (ant_tls_socket_t *)tls_stream->data : NULL;
ant_t *js = socket ? socket->js : NULL;
if (!socket || !js) goto done;
if (nread > 0) {
ant_value_t chunk = 0;
socket->bytes_read += (uint64_t)nread;
tls_socket_sync_state(socket);
if (eventemitter_listener_count(js, socket->obj, "data") > 0) {
if (vtype(socket->encoding) == T_STR)
chunk = js_mkstr(js, buf->base, (size_t)nread);
else chunk = tls_make_buffer_chunk(js, buf->base, (size_t)nread);
if (is_err(chunk)) {
socket->had_error = true;
tls_emit(js, socket->obj, "error", &chunk, 1);
tls_socket_close(socket);
goto done;
}
tls_emit(js, socket->obj, "data", &chunk, 1);
} else {
if (!tls_socket_push_read(socket, buf->base, (size_t)nread, false)) {
ant_value_t err = js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
socket->had_error = true;
tls_emit(js, socket->obj, "error", &err, 1);
tls_socket_close(socket);
goto done;
}
tls_emit(js, socket->obj, "readable", NULL, 0);
}
} else if (nread == UV_EOF) {
socket->ended = true;
- tls_emit(js, socket->obj, "end", NULL, 0);
- tls_socket_close(socket);
+ tls_socket_maybe_emit_end(socket);
} else if (nread < 0) {
ant_value_t err = tls_stream_error(socket, (int)nread, "TLS read failed");
socket->had_error = true;
tls_emit(js, socket->obj, "error", &err, 1);
tls_socket_close(socket);
}
done:
free(buf ? buf->base : NULL);
}
static void tls_socket_on_connect(uv_connect_t *req, int status) {
ant_tls_socket_t *socket = req ? (ant_tls_socket_t *)req->data : NULL;
ant_t *js = socket ? socket->js : NULL;
const char *protocol = NULL;
if (!socket || !js) return;
socket->connecting = false;
if (status < 0) {
ant_value_t err = tls_stream_error(socket, status, "TLS connect failed");
socket->had_error = true;
tls_socket_sync_state(socket);
tls_emit(js, socket->obj, "error", &err, 1);
tls_socket_close(socket);
return;
}
protocol = tlsuv_stream_get_protocol(&socket->stream);
if (protocol && *protocol)
js_set(js, socket->obj, "alpnProtocol", js_mkstr(js, protocol, strlen(protocol)));
else js_set(js, socket->obj, "alpnProtocol", js_false);
tlsuv_stream_read_start(&socket->stream, tls_alloc_cb, tls_socket_on_read);
tls_socket_sync_state(socket);
tls_emit(js, socket->obj, "secureConnect", NULL, 0);
tls_emit(js, socket->obj, "connect", NULL, 0);
}
static ant_value_t js_tls_socket_read(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
size_t wanted = 0;
char *data = NULL;
size_t copied = 0;
ant_value_t out = 0;
if (!socket) return js->thrown_value;
if (!socket->read_head || socket->read_len == 0) return js_mknull();
if (nargs > 0 && vtype(args[0]) == T_NUM && js_getnum(args[0]) > 0)
wanted = (size_t)js_getnum(args[0]);
if (wanted == 0 || wanted > socket->read_len) wanted = socket->read_len;
data = malloc(wanted);
if (!data) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
while (copied < wanted && socket->read_head) {
tls_read_chunk_t *chunk = socket->read_head;
size_t available = chunk->len - chunk->off;
size_t take = wanted - copied;
if (take > available) take = available;
memcpy(data + copied, chunk->data + chunk->off, take);
copied += take;
chunk->off += take;
socket->read_len -= take;
if (chunk->off >= chunk->len) {
socket->read_head = chunk->next;
if (socket->read_tail == chunk) socket->read_tail = NULL;
free(chunk->data);
free(chunk);
}
}
if (vtype(socket->encoding) == T_STR)
out = js_mkstr(js, data, copied);
else out = tls_make_buffer_chunk(js, data, copied);
free(data);
+ tls_socket_maybe_emit_end(socket);
return out;
}
static ant_value_t js_tls_socket_unshift(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
const uint8_t *bytes = NULL;
size_t len = 0;
ant_value_t err = js_mkundef();
if (!socket) return js->thrown_value;
if (!tls_parse_write_args(js, args, nargs, &bytes, &len, NULL, &err)) return err;
if (len > 0) {
if (!tls_socket_push_read(socket, (const char *)bytes, len, true))
return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
tls_socket_schedule_read_drain(socket);
}
return js_getthis(js);
}
static ant_value_t js_tls_socket_write(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
const uint8_t *bytes = NULL;
size_t len = 0;
ant_value_t callback = js_mkundef();
ant_value_t err = js_mkundef();
tls_write_req_t *write = NULL;
uv_buf_t buf;
int rc = 0;
if (!socket) return js->thrown_value;
if (socket->destroyed || socket->closing) return js_false;
if (!tls_parse_write_args(js, args, nargs, &bytes, &len, &callback, &err)) return err;
if (len == 0) {
if (is_callable(callback)) tls_call_value(js, callback, js_mkundef(), NULL, 0);
return js_true;
}
write = calloc(1, sizeof(*write));
if (!write) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
write->data = malloc(len);
if (!write->data) {
free(write);
return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
}
memcpy(write->data, bytes, len);
write->socket = socket;
write->callback = callback;
write->len = len;
write->next = socket->writes;
socket->writes = write;
buf = uv_buf_init(write->data, (unsigned int)len);
rc = tlsuv_stream_write(&write->req, &socket->stream, &buf, tls_socket_write_cb);
if (rc != 0) {
tls_write_remove(socket, write);
free(write->data);
free(write);
return js_false;
}
return js_true;
}
static ant_value_t js_tls_socket_end(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
ant_value_t result = js_getthis(js);
if (!socket) return js->thrown_value;
if (nargs > 0 && vtype(args[0]) != T_UNDEF && vtype(args[0]) != T_NULL) {
result = js_tls_socket_write(js, args, nargs);
if (is_err(result)) return result;
}
socket->ended = true;
tls_socket_maybe_close_after_writes(socket);
return js_getthis(js);
}
static ant_value_t js_tls_socket_destroy(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
if (!socket) return js->thrown_value;
if (nargs > 0 && vtype(args[0]) != T_UNDEF && vtype(args[0]) != T_NULL) {
ant_value_t err = args[0];
socket->had_error = true;
tls_emit(js, socket->obj, "error", &err, 1);
}
tls_socket_close(socket);
return js_getthis(js);
}
static ant_value_t js_tls_socket_pause(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
if (!socket) return js->thrown_value;
tlsuv_stream_read_stop(&socket->stream);
return js_getthis(js);
}
static ant_value_t js_tls_socket_resume(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
if (!socket) return js->thrown_value;
if (!socket->destroyed) tlsuv_stream_read_start(&socket->stream, tls_alloc_cb, tls_socket_on_read);
return js_getthis(js);
}
static ant_value_t js_tls_socket_setEncoding(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
ant_value_t encoding = js_mkundef();
if (!socket) return js->thrown_value;
if (nargs > 0 && vtype(args[0]) != T_UNDEF) {
encoding = js_tostring_val(js, args[0]);
if (is_err(encoding)) return encoding;
}
socket->encoding = encoding;
return js_getthis(js);
}
static ant_value_t js_tls_socket_setNoDelay(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
bool enable = nargs == 0 || js_truthy(js, args[0]);
if (!socket) return js->thrown_value;
tlsuv_stream_nodelay(&socket->stream, enable ? 1 : 0);
return js_getthis(js);
}
static ant_value_t js_tls_socket_setKeepAlive(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
bool enable = nargs > 0 && js_truthy(js, args[0]);
unsigned int delay = nargs > 1 && vtype(args[1]) == T_NUM ? (unsigned int)(js_getnum(args[1]) / 1000.0) : 0;
if (!socket) return js->thrown_value;
tlsuv_stream_keepalive(&socket->stream, enable ? 1 : 0, delay);
return js_getthis(js);
}
static ant_value_t js_tls_socket_setTimeout(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
if (!socket) return js->thrown_value;
socket->timeout_ms = nargs > 0 && vtype(args[0]) == T_NUM && js_getnum(args[0]) > 0 ? (uint64_t)js_getnum(args[0]) : 0;
tls_socket_sync_state(socket);
if (nargs > 1 && is_callable(args[1]))
eventemitter_add_listener(js, socket->obj, "timeout", args[1], true);
return js_getthis(js);
}
static ant_value_t js_tls_socket_address(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
ant_value_t out = js_mkobj(js);
if (!socket) return js->thrown_value;
js_set(js, out, "address", js_mkundef());
js_set(js, out, "port", js_mkundef());
js_set(js, out, "family", js_mkundef());
return out;
}
static ant_value_t js_tls_socket_ref(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
if (!socket) return js->thrown_value;
if (
uv_handle_get_type((uv_handle_t *)&socket->stream.watcher) != UV_UNKNOWN_HANDLE &&
!uv_is_closing((uv_handle_t *)&socket->stream.watcher)
) uv_ref((uv_handle_t *)&socket->stream.watcher);
return js_getthis(js);
}
static ant_value_t js_tls_socket_unref(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
if (!socket) return js->thrown_value;
if (
uv_handle_get_type((uv_handle_t *)&socket->stream.watcher) != UV_UNKNOWN_HANDLE &&
!uv_is_closing((uv_handle_t *)&socket->stream.watcher)
) uv_unref((uv_handle_t *)&socket->stream.watcher);
return js_getthis(js);
}
static ant_value_t js_tls_socket_cork(ant_t *js, ant_value_t *args, int nargs) {
return js_getthis(js);
}
static ant_value_t js_tls_socket_uncork(ant_t *js, ant_value_t *args, int nargs) {
return js_getthis(js);
}
static ant_value_t js_tls_socket_getProtocol(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js));
const char *protocol = NULL;
if (!socket) return js->thrown_value;
protocol = tlsuv_stream_get_protocol(&socket->stream);
return protocol && *protocol ? js_mkstr(js, protocol, strlen(protocol)) : js_mkundef();
}
static ant_value_t js_tls_socket_getCipher(ant_t *js, ant_value_t *args, int nargs) {
return js_mkundef();
}
static ant_value_t js_tls_socket_getSession(ant_t *js, ant_value_t *args, int nargs) {
return js_mkundef();
}
static ant_value_t js_tls_socket_getPeerCertificate(ant_t *js, ant_value_t *args, int nargs) {
return js_mkobj(js);
}
static ant_value_t js_tls_socket_renegotiate(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t callback = js_mkundef();
if (nargs > 1 && is_callable(args[1])) callback = args[1];
else if (nargs > 0 && is_callable(args[0])) callback = args[0];
if (is_callable(callback)) {
ant_value_t cb_args[] = {js_mknull()};
tls_call_value(js, callback, js_getthis(js), cb_args, 1);
}
return js_true;
}
static ant_value_t js_tls_socket_ctor(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t existing = nargs > 0 ? args[0] : js_mkundef();
ant_value_t obj = 0;
ant_value_t proto = 0;
tls_init_socket_proto(js);
if (tls_socket_data(existing)) {
tls_define_default_socket_state(js, existing);
js_set_proto_wb(js, existing, g_tls_socket_proto);
return existing;
}
obj = js_mkobj(js);
proto = js_instance_proto_from_new_target(js, g_tls_socket_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
tls_define_default_socket_state(js, obj);
js_set(js, obj, "alpnProtocol", js_false);
return obj;
}
void tls_init_socket_proto(ant_t *js) {
ant_value_t events = 0;
ant_value_t ee_ctor = 0;
ant_value_t ee_proto = 0;
ant_value_t net = 0;
ant_value_t net_socket_ctor = 0;
ant_value_t net_socket_proto = 0;
if (g_tls_socket_proto && g_tls_socket_ctor) return;
if (!g_tls_socket_proto) {
net = net_library(js);
net_socket_ctor = js_get(js, net, "Socket");
net_socket_proto = js_get(js, net_socket_ctor, "prototype");
if (!is_object_type(net_socket_proto)) {
events = events_library(js);
ee_ctor = js_get(js, events, "EventEmitter");
ee_proto = js_get(js, ee_ctor, "prototype");
net_socket_proto = ee_proto;
}
g_tls_socket_proto = js_mkobj(js);
if (is_object_type(net_socket_proto)) js_set_proto_init(g_tls_socket_proto, net_socket_proto);
js_set(js, g_tls_socket_proto, "read", js_mkfun(js_tls_socket_read));
js_set(js, g_tls_socket_proto, "unshift", js_mkfun(js_tls_socket_unshift));
js_set(js, g_tls_socket_proto, "write", js_mkfun(js_tls_socket_write));
js_set(js, g_tls_socket_proto, "end", js_mkfun(js_tls_socket_end));
js_set(js, g_tls_socket_proto, "destroy", js_mkfun(js_tls_socket_destroy));
js_set(js, g_tls_socket_proto, "pause", js_mkfun(js_tls_socket_pause));
js_set(js, g_tls_socket_proto, "resume", js_mkfun(js_tls_socket_resume));
js_set(js, g_tls_socket_proto, "setEncoding", js_mkfun(js_tls_socket_setEncoding));
js_set(js, g_tls_socket_proto, "setNoDelay", js_mkfun(js_tls_socket_setNoDelay));
js_set(js, g_tls_socket_proto, "setKeepAlive", js_mkfun(js_tls_socket_setKeepAlive));
js_set(js, g_tls_socket_proto, "setTimeout", js_mkfun(js_tls_socket_setTimeout));
js_set(js, g_tls_socket_proto, "address", js_mkfun(js_tls_socket_address));
js_set(js, g_tls_socket_proto, "ref", js_mkfun(js_tls_socket_ref));
js_set(js, g_tls_socket_proto, "unref", js_mkfun(js_tls_socket_unref));
js_set(js, g_tls_socket_proto, "cork", js_mkfun(js_tls_socket_cork));
js_set(js, g_tls_socket_proto, "uncork", js_mkfun(js_tls_socket_uncork));
js_set(js, g_tls_socket_proto, "getProtocol", js_mkfun(js_tls_socket_getProtocol));
js_set(js, g_tls_socket_proto, "getCipher", js_mkfun(js_tls_socket_getCipher));
js_set(js, g_tls_socket_proto, "getSession", js_mkfun(js_tls_socket_getSession));
js_set(js, g_tls_socket_proto, "getPeerCertificate", js_mkfun(js_tls_socket_getPeerCertificate));
js_set(js, g_tls_socket_proto, "renegotiate", js_mkfun(js_tls_socket_renegotiate));
js_set_sym(js, g_tls_socket_proto, get_toStringTag_sym(), js_mkstr(js, "TLSSocket", 9));
}
if (!g_tls_socket_ctor)
g_tls_socket_ctor = js_make_ctor(js, js_tls_socket_ctor, g_tls_socket_proto, "TLSSocket", 9);
}
static ant_value_t js_tls_create_context(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t options = nargs > 0 ? args[0] : js_mkundef();
ant_value_t obj = 0;
ant_value_t error = js_mkundef();
ant_tls_context_wrap_t *wrap = NULL;
ant_value_t ca_v = js_mkundef();
ant_value_t key_v = js_mkundef();
ant_value_t cert_v = js_mkundef();
ant_value_t partial_v = js_mkundef();
const char *ca = NULL;
const char *key_data = NULL;
const char *cert_data = NULL;
size_t ca_len = 0;
size_t key_len = 0;
size_t cert_len = 0;
int rc = 0;
if (vtype(options) != T_UNDEF && vtype(options) != T_NULL && vtype(options) != T_OBJ)
return js_mkerr_typed(js, JS_ERR_TYPE, "TLS context options must be an object");
tls_init_context_proto(js);
if (vtype(options) == T_OBJ) {
ca_v = js_get(js, options, "ca");
key_v = js_get(js, options, "key");
cert_v = js_get(js, options, "cert");
partial_v = js_get(js, options, "allowPartialChain");
}
if (!tls_value_bytes(js, ca_v, &ca, &ca_len, &error)) return error;
if (!tls_value_bytes(js, key_v, &key_data, &key_len, &error)) return error;
if (!tls_value_bytes(js, cert_v, &cert_data, &cert_len, &error)) return error;
wrap = calloc(1, sizeof(*wrap));
if (!wrap) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
wrap->ctx = default_tls_context(ca, ca_len);
if (!wrap->ctx) {
free(wrap);
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to create TLS context");
}
if (wrap->ctx->allow_partial_chain && js_truthy(js, partial_v)) {
rc = wrap->ctx->allow_partial_chain(wrap->ctx, 1);
if (rc != 0) {
ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to enable partial chain verification");
tls_context_free(wrap);
free(wrap);
return err;
}}
if (key_data) {
if (!wrap->ctx->load_key) {
tls_context_free(wrap);
free(wrap);
return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support loading private keys");
}
rc = wrap->ctx->load_key(&wrap->key, key_data, key_len);
if (rc != 0) {
ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to load TLS private key");
tls_context_free(wrap);
free(wrap);
return err;
}
if (cert_data) {
if (!wrap->ctx->load_cert) {
tls_context_free(wrap);
free(wrap);
return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support loading certificates");
}
rc = wrap->ctx->load_cert(&wrap->cert, cert_data, cert_len);
if (rc != 0) {
ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to load TLS certificate");
tls_context_free(wrap);
free(wrap);
return err;
}}
if (!wrap->ctx->set_own_cert) {
tls_context_free(wrap);
free(wrap);
return js_mkerr_typed(js, JS_ERR_TYPE, "TLS engine does not support own certificate configuration");
}
rc = wrap->ctx->set_own_cert(wrap->ctx, wrap->key, wrap->cert);
if (rc != 0) {
ant_value_t err = tls_make_error(js, wrap->ctx, rc, "Failed to configure TLS certificate");
tls_context_free(wrap);
free(wrap);
return err;
}
} else if (cert_data) {
tls_context_free(wrap);
free(wrap);
return js_mkerr_typed(js, JS_ERR_TYPE, "TLS certificate requires a private key");
}
obj = js_mkobj(js);
js_set_proto_init(obj, g_tls_context_proto);
wrap->obj = obj;
js_set_native(obj, wrap, TLS_CONTEXT_NATIVE_TAG);
return obj;
}
static ant_value_t js_tls_is_context(ant_t *js, ant_value_t *args, int nargs) {
ant_tls_context_wrap_t *wrap = nargs > 0 ? tls_context_data(args[0]) : NULL;
return js_bool(wrap && !wrap->closed && wrap->ctx);
}
static ant_value_t js_tls_secure_context_ctor(ant_t *js, ant_value_t *args, int nargs) {
return js_tls_create_context(js, args, nargs);
}
static ant_value_t js_tls_set_config_path(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t str_value = js_mkundef();
const char *path = NULL;
int rc = 0;
if (nargs < 1 || vtype(args[0]) == T_UNDEF || vtype(args[0]) == T_NULL) {
rc = tlsuv_set_config_path(NULL);
if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc));
return js_mkundef();
}
str_value = js_tostring_val(js, args[0]);
if (is_err(str_value)) return str_value;
path = js_getstr(js, str_value, NULL);
if (!path) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid TLS config path");
rc = tlsuv_set_config_path(path);
if (rc != 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s", uv_strerror(rc));
return js_mkundef();
}
static bool tls_socket_set_alpn(ant_t *js, ant_tls_socket_t *socket, ant_value_t protocols) {
ant_offset_t len = 0;
if (!socket || vtype(protocols) != T_ARR) return true;
len = js_arr_len(js, protocols);
if (len <= 0) return true;
socket->alpn_protocols = calloc((size_t)len, sizeof(char *));
if (!socket->alpn_protocols) return false;
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t item = js_arr_get(js, protocols, i);
ant_value_t str_val = js_tostring_val(js, item);
const char *str = NULL;
if (is_err(str_val)) return false;
str = js_getstr(js, str_val, NULL);
if (!str) return false;
socket->alpn_protocols[socket->alpn_count] = strdup(str);
if (!socket->alpn_protocols[socket->alpn_count]) return false;
socket->alpn_count++;
}
return true;
}
static void tls_copy_connect_option(ant_t *js, ant_value_t dst, ant_value_t src, const char *key) {
ant_value_t value = js_get(js, src, key);
if (vtype(value) != T_UNDEF) js_set(js, dst, key, value);
}
static void tls_copy_connect_options(ant_t *js, ant_value_t dst, ant_value_t src) {
if (vtype(src) != T_OBJ) return;
tls_copy_connect_option(js, dst, src, "host");
tls_copy_connect_option(js, dst, src, "hostname");
tls_copy_connect_option(js, dst, src, "port");
tls_copy_connect_option(js, dst, src, "servername");
tls_copy_connect_option(js, dst, src, "secureContext");
tls_copy_connect_option(js, dst, src, "ALPNProtocols");
}
static ant_value_t tls_normalize_connect_options(
ant_t *js,
ant_value_t *args,
int nargs,
ant_value_t *callback_out
) {
int argc = nargs;
ant_value_t options = js_mkobj(js);
if (callback_out) *callback_out = js_mkundef();
if (argc > 0 && is_callable(args[argc - 1])) {
if (callback_out) *callback_out = args[argc - 1];
argc--;
}
if (argc == 1 && vtype(args[0]) == T_OBJ) return args[0];
if (argc > 0 && vtype(args[0]) == T_NUM) js_set(js, options, "port", args[0]);
if (argc > 1 && vtype(args[1]) == T_STR) js_set(js, options, "host", args[1]);
if (argc > 1 && vtype(args[1]) == T_OBJ) tls_copy_connect_options(js, options, args[1]);
if (argc > 2 && vtype(args[2]) == T_OBJ) tls_copy_connect_options(js, options, args[2]);
return options;
}
static ant_value_t js_tls_connect_options(ant_t *js, ant_value_t options, ant_value_t callback) {
ant_value_t obj = 0;
ant_tls_socket_t *socket = NULL;
ant_tls_context_wrap_t *ctx_wrap = NULL;
ant_value_t value = 0;
const char *host = "localhost";
const char *servername = NULL;
int port = 443;
int rc = 0;
if (vtype(options) != T_OBJ)
return js_mkerr_typed(js, JS_ERR_TYPE, "tls.connect requires an options object");
value = js_get(js, options, "host");
if (vtype(value) == T_STR) host = js_getstr(js, value, NULL);
value = js_get(js, options, "hostname");
if (vtype(value) == T_STR) host = js_getstr(js, value, NULL);
value = js_get(js, options, "port");
if (vtype(value) == T_NUM) port = (int)js_getnum(value);
value = js_get(js, options, "servername");
if (vtype(value) == T_STR) servername = js_getstr(js, value, NULL);
if (!servername || !*servername) servername = host;
tls_init_socket_proto(js);
socket = calloc(1, sizeof(*socket));
if (!socket) return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
socket->js = js;
socket->encoding = js_mkundef();
socket->secure_context = js_mkundef();
socket->host = strdup(host ? host : "localhost");
socket->servername = strdup(servername ? servername : (host ? host : "localhost"));
socket->port = port > 0 ? port : 443;
socket->connecting = true;
if (!socket->host || !socket->servername) {
tls_socket_free(socket);
return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
}
value = js_get(js, options, "secureContext");
ctx_wrap = tls_context_data(value);
if (ctx_wrap && !ctx_wrap->closed && ctx_wrap->ctx) {
socket->ctx = ctx_wrap->ctx;
socket->ctx_wrap = ctx_wrap;
socket->secure_context = value;
socket->owns_ctx = false;
tls_context_retain(ctx_wrap);
} else {
socket->ctx = default_tls_context(NULL, 0);
socket->owns_ctx = true;
if (!socket->ctx) {
tls_socket_free(socket);
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to create TLS context");
}
}
value = js_get(js, options, "ALPNProtocols");
if (!tls_socket_set_alpn(js, socket, value)) {
tls_socket_free(socket);
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to configure TLS ALPN protocols");
}
obj = js_mkobj(js);
js_set_proto_init(obj, g_tls_socket_proto);
socket->obj = obj;
js_set_native(obj, socket, TLS_SOCKET_NATIVE_TAG);
tls_define_default_socket_state(js, obj);
if (is_callable(callback)) eventemitter_add_listener(js, obj, "secureConnect", callback, true);
tlsuv_stream_init(uv_default_loop(), &socket->stream, socket->ctx);
socket->stream.data = socket;
socket->connect_req.data = socket;
if (socket->servername) tlsuv_stream_set_hostname(&socket->stream, socket->servername);
if (socket->alpn_count > 0)
tlsuv_stream_set_protocols(&socket->stream, socket->alpn_count, (const char **)socket->alpn_protocols);
js_set(js, obj, "localAddress", js_mkundef());
js_set(js, obj, "localPort", js_mkundef());
js_set(js, obj, "localFamily", js_mkundef());
js_set(js, obj, "alpnProtocol", js_false);
tls_socket_sync_state(socket);
tls_add_active_socket(socket);
rc = tlsuv_stream_connect(&socket->connect_req, &socket->stream, socket->host, socket->port, tls_socket_on_connect);
if (rc != 0) {
ant_value_t err = tls_stream_error(socket, rc, "TLS connect failed");
socket->connecting = false;
socket->had_error = true;
tls_socket_sync_state(socket);
tls_emit(js, socket->obj, "error", &err, 1);
tls_socket_close(socket);
}
return obj;
}
static ant_value_t js_tls_connect(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t callback = js_mkundef();
ant_value_t options = tls_normalize_connect_options(js, args, nargs, &callback);
return js_tls_connect_options(js, options, callback);
}
static ant_value_t js_tls_check_server_identity(ant_t *js, ant_value_t *args, int nargs) {
- return js_mkundef();
+ return js_mkerr_typed(js, JS_ERR_TYPE, "tls.checkServerIdentity is not implemented");
}
static ant_value_t js_tls_get_ciphers(ant_t *js, ant_value_t *args, int nargs) {
return js_mkarr(js);
}
static ant_value_t tls_build_library(ant_t *js) {
ant_value_t lib = js_mkobj(js);
ant_value_t version = js_mkstr(js, "unknown", 7);
ant_value_t connect = js_mkfun(js_tls_connect);
ant_value_t create_secure_context = js_mkfun(js_tls_create_context);
ant_value_t root_certificates = js_mkarr(js);
tls_context *ctx = default_tls_context(NULL, 0);
const char *version_str = NULL;
if (ctx && ctx->version) {
version_str = ctx->version();
if (version_str) version = js_mkstr(js, version_str, strlen(version_str));
}
if (ctx && ctx->free_ctx) ctx->free_ctx(ctx);
tls_init_context_proto(js);
tls_init_socket_proto(js);
if (!g_tls_context_ctor) g_tls_context_ctor = js_make_ctor(
js, js_tls_secure_context_ctor,
g_tls_context_proto, "SecureContext", 13
);
js_set(js, lib, "version", version);
js_set(js, lib, "TLSSocket", g_tls_socket_ctor);
js_set(js, lib, "SecureContext", g_tls_context_ctor);
js_set(js, lib, "createSecureContext", create_secure_context);
js_set(js, lib, "connect", connect);
js_set(js, lib, "createConnection", connect);
js_set(js, lib, "isSecureContext", js_mkfun(js_tls_is_context));
js_set(js, lib, "setConfigPath", js_mkfun(js_tls_set_config_path));
js_set(js, lib, "checkServerIdentity", js_mkfun(js_tls_check_server_identity));
js_set(js, lib, "getCiphers", js_mkfun(js_tls_get_ciphers));
builtin_object_freeze(js, &root_certificates, 1);
js_set(js, lib, "rootCertificates", root_certificates);
js_set(js, lib, "DEFAULT_ECDH_CURVE", js_mkstr(js, "auto", 4));
js_set(js, lib, "DEFAULT_MIN_VERSION", js_mkstr(js, "TLSv1.2", 7));
js_set(js, lib, "DEFAULT_MAX_VERSION", js_mkstr(js, "TLSv1.3", 7));
js_set(js, lib, "CLIENT_RENEG_LIMIT", js_mknum(3));
js_set(js, lib, "CLIENT_RENEG_WINDOW", js_mknum(600));
js_set(js, lib, "default", lib);
js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "tls", 3));
return lib;
}
ant_value_t tls_library(ant_t *js) {
return tls_build_library(js);
}
void gc_mark_tls(ant_t *js, gc_mark_fn mark) {
ant_tls_socket_t *socket = NULL;
if (g_tls_context_proto) mark(js, g_tls_context_proto);
if (g_tls_context_ctor) mark(js, g_tls_context_ctor);
if (g_tls_socket_proto) mark(js, g_tls_socket_proto);
if (g_tls_socket_ctor) mark(js, g_tls_socket_ctor);
for (socket = g_active_tls_sockets; socket; socket = socket->next_active) {
mark(js, socket->obj);
if (vtype(socket->encoding) != T_UNDEF) mark(js, socket->encoding);
if (vtype(socket->secure_context) != T_UNDEF) mark(js, socket->secure_context);
for (tls_write_req_t *write = socket->writes; write; write = write->next)
if (vtype(write->callback) != T_UNDEF) mark(js, write->callback);
}
}
diff --git a/tests/test_tls_secure_connect_event.cjs b/tests/test_tls_secure_connect_event.cjs
index db4bef6..cab4ac3 100644
--- a/tests/test_tls_secure_connect_event.cjs
+++ b/tests/test_tls_secure_connect_event.cjs
@@ -1,86 +1,89 @@
const net = require('node:net');
const tls = require('node:tls');
const assert = require('node:assert');
let sawError = false;
let sawSecureConnect = false;
const timeout = setTimeout(() => {
throw new Error('tls.connect did not fail the plain TCP peer');
}, 2000);
const context = tls.createSecureContext();
const constructedContext = new tls.SecureContext();
const detachedSocket = new tls.TLSSocket();
assert.strictEqual(tls.default, tls);
assert.strictEqual(tls.isSecureContext(context), true);
assert.strictEqual(tls.isSecureContext(constructedContext), true);
assert.strictEqual(tls.createContext, undefined);
assert.strictEqual(tls.isContext, undefined);
-assert.strictEqual(tls.checkServerIdentity('localhost', {}), undefined);
+assert.throws(
+ () => tls.checkServerIdentity('localhost', {}),
+ /tls\.checkServerIdentity is not implemented/
+);
assert.deepStrictEqual(tls.getCiphers(), []);
assert.deepStrictEqual(tls.rootCertificates, []);
assert.strictEqual(Object.isFrozen(tls.rootCertificates), true);
assert.strictEqual(tls.DEFAULT_ECDH_CURVE, 'auto');
assert.strictEqual(tls.DEFAULT_MIN_VERSION, 'TLSv1.2');
assert.strictEqual(tls.DEFAULT_MAX_VERSION, 'TLSv1.3');
assert.strictEqual(tls.CLIENT_RENEG_LIMIT, 3);
assert.strictEqual(tls.CLIENT_RENEG_WINDOW, 600);
assert(detachedSocket instanceof tls.TLSSocket);
assert(detachedSocket instanceof net.Socket);
assert.strictEqual(detachedSocket.encrypted, true);
assert.strictEqual(detachedSocket.renegotiate({}, (err) => assert.strictEqual(err, null)), true);
const server = net.createServer((socket) => {
socket.end();
});
server.listen(0, '127.0.0.1', () => {
const address = server.address();
const secureContext = tls.createSecureContext();
const socket = tls.connect({
port: address.port,
host: '127.0.0.1',
secureContext,
});
assert.strictEqual(tls.isSecureContext(secureContext), true);
assert.strictEqual(secureContext.close(), secureContext);
assert.strictEqual(tls.isSecureContext(secureContext), false);
assert(socket instanceof tls.TLSSocket);
assert(socket instanceof net.Socket);
assert.strictEqual(typeof socket.renegotiate, 'function');
assert.strictEqual(socket.ref(), socket);
assert.strictEqual(socket.unref(), socket);
assert.strictEqual(socket.cork(), socket);
assert.strictEqual(socket.uncork(), socket);
assert.strictEqual(socket.setEncoding('utf8'), socket);
const badEncoding = {
toString() {
throw new Error('encoding coercion failed');
},
};
let sawEncodingError = false;
try {
socket.setEncoding(badEncoding);
} catch (err) {
sawEncodingError = true;
assert.match(err.message, /encoding coercion failed/);
}
assert.strictEqual(sawEncodingError, true);
assert.strictEqual(socket.setEncoding(), socket);
socket.on('error', (err) => {
sawError = true;
if (sawSecureConnect) throw new Error('plain TCP peer emitted secureConnect');
socket.destroy();
server.close(() => {
clearTimeout(timeout);
console.log('tls:plain-peer-error:ok');
});
});
socket.once('secureConnect', () => {
sawSecureConnect = true;
if (!sawError) throw new Error('tls.connect unexpectedly accepted a plain TCP peer');
});
});
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Aug 1, 2:44 PM (3 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
603544
Default Alt Text
(83 KB)
Attached To
Mode
rANT Ant
Attached
Detach File
Event Timeline
Log In to Comment