Page MenuHomePhorge

No OneTemporary

Size
83 KB
Referenced Files
None
Subscribers
None
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

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)

Event Timeline