From efe93b7206dd11c1462e8a94c7dceb9e91ceed1b Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sat, 29 Nov 2025 14:22:19 -0600 Subject: [PATCH] move random number gen --- scripts/crypto.c | 115 +++----------- scripts/engine.cm | 390 +++++++++++++++++++++++++--------------------- scripts/fd.c | 16 ++ scripts/os.c | 114 ++++++++++++++ source/jsffi.c | 153 +----------------- 5 files changed, 363 insertions(+), 425 deletions(-) diff --git a/scripts/crypto.c b/scripts/crypto.c index 09d78834..c19f4ed6 100644 --- a/scripts/crypto.c +++ b/scripts/crypto.c @@ -5,70 +5,6 @@ #include "monocypher.h" -#include -#include - -#if defined(_WIN32) -// ------- Windows: use BCryptGenRandom ------- -#include -#include - -int randombytes(void *buf, size_t n) { - NTSTATUS status = BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)n, BCRYPT_USE_SYSTEM_PREFERRED_RNG); - return (status == 0) ? 0 : -1; -} - -#elif defined(__linux__) -// ------- Linux: try getrandom, fall back to /dev/urandom ------- -#include -#include -#include -#include - -// If we have a new enough libc and kernel, getrandom is available. -// Otherwise, we’ll do a /dev/urandom fallback. -#include - -static int randombytes_fallback(void *buf, size_t n) { - int fd = open("/dev/urandom", O_RDONLY); - if (fd < 0) return -1; - ssize_t r = read(fd, buf, n); - close(fd); - return (r == (ssize_t)n) ? 0 : -1; -} - -int randombytes(void *buf, size_t n) { -#ifdef SYS_getrandom - // Try getrandom(2) if available - ssize_t ret = syscall(SYS_getrandom, buf, n, 0); - if (ret < 0) { - // If getrandom is not supported or fails, fall back - if (errno == ENOSYS) { - return randombytes_fallback(buf, n); - } - return -1; - } - return (ret == (ssize_t)n) ? 0 : -1; -#else - // getrandom not available, just fallback - return randombytes_fallback(buf, n); -#endif -} - -#else -// ------- Other Unix: read from /dev/urandom ------- -#include -#include - -int randombytes(void *buf, size_t n) { - int fd = open("/dev/urandom", O_RDONLY); - if (fd < 0) return -1; - ssize_t r = read(fd, buf, n); - close(fd); - return (r == (ssize_t)n) ? 0 : -1; -} -#endif - static inline void to_hex(const uint8_t *in, size_t in_len, char *out) { static const char hexchars[] = "0123456789abcdef"; @@ -134,7 +70,27 @@ JSValue js_crypto_keypair(JSContext *js, JSValue self, int argc, JSValue *argv) uint8_t public[32]; uint8_t private[32]; - randombytes(private,32); + JSValue global = JS_GetGlobalObject(js); + JSValue os = JS_GetPropertyStr(js, global, "os"); + JSValue random_blob = JS_GetPropertyStr(js, os, "random_blob"); + JSValue size_val = JS_NewInt32(js, 32); + JSValue blob = JS_Call(js, random_blob, os, 1, &size_val); + size_t len; + uint8_t *data = js_get_blob_data(js, &len, blob); + if (!data || len != 32) { + JS_FreeValue(js, blob); + JS_FreeValue(js, size_val); + JS_FreeValue(js, random_blob); + JS_FreeValue(js, os); + JS_FreeValue(js, global); + return JS_ThrowInternalError(js, "failed to get random bytes"); + } + memcpy(private, data, 32); + JS_FreeValue(js, blob); + JS_FreeValue(js, size_val); + JS_FreeValue(js, random_blob); + JS_FreeValue(js, os); + JS_FreeValue(js, global); private[0] &= 248; private[31] &= 127; @@ -181,33 +137,6 @@ JSValue js_crypto_shared(JSContext *js, JSValue self, int argc, JSValue *argv) return crypto2js(js, shared); } -JSValue js_crypto_random(JSContext *js, JSValue self, int argc, JSValue *argv) -{ - // 1) Pull 64 bits of cryptographically secure randomness - uint64_t r; - if (randombytes(&r, sizeof(r)) != 0) { - // If something fails (extremely rare), throw an error - return JS_ThrowInternalError(js, "crypto.random: unable to get random bytes"); - } - - // 2) Convert r to a double in the range [0,1). - // We divide by (UINT64_MAX + 1.0) to ensure we never produce exactly 1.0. - double val = (double)r / ((double)UINT64_MAX + 1.0); - - // 3) Return that as a JavaScript number - return JS_NewFloat64(js, val); -} - -JSValue js_crypto_random_fit(JSContext *js, JSValue self, int argc, JSValue *argv) -{ - int32_t r; - if (randombytes(&r, sizeof(r)) != 0) { - return JS_ThrowInternalError(js, "crypto.random: unable to get random bytes"); - } - - return JS_NewInt32(js, r); -} - JSValue js_crypto_hash(JSContext *js, JSValue self, int argc, JSValue *argv) { if (argc < 1) @@ -245,8 +174,6 @@ JSValue js_crypto_hash(JSContext *js, JSValue self, int argc, JSValue *argv) static const JSCFunctionListEntry js_crypto_funcs[] = { JS_CFUNC_DEF("keypair", 0, js_crypto_keypair), JS_CFUNC_DEF("shared", 1, js_crypto_shared), - JS_CFUNC_DEF("random", 0, js_crypto_random), - JS_CFUNC_DEF("random_fit", 0, js_crypto_random_fit), JS_CFUNC_DEF("hash", 2, js_crypto_hash), }; diff --git a/scripts/engine.cm b/scripts/engine.cm index 6bac787d..1978c267 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -7,6 +7,7 @@ var SYSYM = '__SYSTEM__' // Get hidden modules from cell.hidden before stripping it var hidden = cell.hidden +var os = hidden.os; var use_dyn = hidden.use_dyn var dylib_ext = hidden.dylib_ext var load_internal = hidden.load_internal @@ -54,8 +55,6 @@ function get_import_dl(name) { return null } -// tries to load a C symbol from either a dynamic library or within the executable -// an import 'debug/profile' can either be in a dynamic library debug.so or within the executable function get_c_symbol(name) { var dl = get_import_dl(name) var symname = `js_${name.replace('/', '_')}_use` @@ -96,7 +95,7 @@ globalThis.log = {} log.console = function(msg) { var caller = caller_data(1) - console_mod.print(console_rec(caller.line, caller.file, msg)) + os.print(console_rec(caller.line, caller.file, msg)) } log.error = function(msg = new Error()) @@ -106,7 +105,7 @@ log.error = function(msg = new Error()) if (msg instanceof Error) msg = msg.name + ": " + msg.message + "\n" + msg.stack - console_mod.print(console_rec(caller.line,caller.file,msg)) + os.print(console_rec(caller.line,caller.file,msg)) } log.system = function(msg) { @@ -121,15 +120,6 @@ if (!fd.stat('.cell').isDirectory) { os.exit(1); } -function is_file(path) { - try { - var st = fd.stat(path) - return !!(st && st.isFile) - } catch { - return false - } -} - function write_file(path, blob) { var fd_handle = fd.open(path, 'w') fd.write(fd_handle, blob) @@ -216,8 +206,13 @@ function get_package_from_path(path) { // This will be set after shop.load_config() is called var config = null +// Unified path resolution function // Unified path resolution function function resolve_path(requested, pkg_context, ext) { + + + if (requested.endsWith(ext)) ext = '' + var dependencies = (config && config.dependencies) ? config.dependencies : {} // Helper to check file existence and return result object @@ -232,14 +227,14 @@ function resolve_path(requested, pkg_context, ext) { return null } catch (e) { return null } } - if (is_file(path)) { + if (fd.is_file(path)) { // log.console("check: found file: " + path) return { path: path, package_name: pkg, isCore: false } } return null } - // Step 1: current package + // Step 1: current package (Local) if (pkg_context) { var pkg_path = '.cell/modules/' + pkg_context + '/' + requested + ext var res = check(pkg_path, pkg_context, false) @@ -253,6 +248,7 @@ function resolve_path(requested, pkg_context, ext) { if (res) return res } } else { + // Top-level local var project_path = requested + ext var res = check(project_path, null, false) if (res) return res @@ -275,6 +271,12 @@ function resolve_path(requested, pkg_context, ext) { var res = check(dep_path, pkg_alias, false) if (res) return res } + + // Also check if it's just a module in .cell/modules even if not in dependencies (implicit) + var implicit_path = '.cell/modules/' + requested + ext + var res = check(implicit_path, pkg_alias, false) + if (res) return res + } else { // Check replace directives for simple names if (config && config.replace && config.replace[requested]) { @@ -285,14 +287,23 @@ function resolve_path(requested, pkg_context, ext) { } // Check dependencies for simple names for (var alias in dependencies) { - var dep_simple = '.cell/modules/' + alias + '/' + requested + ext - var res = check(dep_simple, alias, false) - if (res) return res + if (alias == requested) { + var dep_simple = '.cell/modules/' + alias + '/' + requested + ext + var res = check(dep_simple, alias, false) + if (res) return res + } } + + // Implicit check + var implicit_path = '.cell/modules/' + requested + '/' + requested + ext + var res = check(implicit_path, requested, false) + if (res) return res } // Step 3: core - return check(requested + ext, null, true) + var core_res = check(requested + ext, null, true) + if (core_res) log.console(`resolve_path: found core ${requested}`) + return core_res } function get_compiled_path(resolved) { @@ -315,197 +326,215 @@ function get_compiled_path(resolved) { } } -globalThis.use = function use(file, ...args) { - /* Package-aware module resolution: - 1. Check local package (cwd, ie '.') - 2. Check declared dependencies (from cell.toml [dependencies]) - 3. Check core_qop (standard library) +var open_dl = {} - There's also the possibility of native C code; - there may be, in a package, a .so/.dll/.dylib - that can be loaded. If that exists, as well as a .cm file, the - .so/.dll/.dylib is loaded and the .cm file is ran with the - loaded module as this. - - for embedded modules, it's the same, but in the cell runtime, so no .so/.dll/.dylib - is loaded. - */ - var requested = file +function get_c_symbol(requested, pkg_context) { + // Construct symbol name: js_x_y_z_use + var symname = `js_${requested.replace(/\//g, '_')}_use` - // Check embedded modules first (these are always available) - var embed_mod = use_embed(requested) - if (!embed_mod) embed_mod = load_internal(requested) - - // Check for dynamic library - var dyn_mod = null - var dyn_path = resolve_path(requested, current_package, dylib_ext) - if (dyn_path) { - try { - if (!dyn_path.isCore) { - dyn_mod = use_dyn(dyn_path.path) - log.console("use: loaded dynamic library " + dyn_path.path) + // 1. Check Local + if (!pkg_context) { + // Check local dylib: .cell/local/local.dylib + var local_dl_path = '.cell/local/local' + dylib_ext + if (fd.is_file(local_dl_path)) { + var dl = open_dl['local'] + if (!dl) { + try { + dl = os.dylib_open(local_dl_path) + open_dl['local'] = dl + } catch(e) {} + } + if (dl) { + try { + var sym = os.dylib_symbol(dl, symname) + if (sym) return sym + } catch(e) {} } - } catch (e) { - log.console(`use: failed to load dynamic library ${dyn_path.path}: ${e}`) } } - - // If we have a dynamic module, use it as the embedded module (context) - if (dyn_mod) { - embed_mod = dyn_mod + + // 2. Check Modules + // Determine package name from requested path + var pkg_name = null + if (pkg_context) pkg_name = pkg_context + else if (requested.includes('/')) pkg_name = requested.split('/')[0] + else pkg_name = requested + + if (pkg_name) { + var mod_dl_path = `.cell/modules/${pkg_name}/${pkg_name}${dylib_ext}` + if (fd.is_file(mod_dl_path)) { + var dl = open_dl[pkg_name] + if (!dl) { + try { + dl = os.dylib_open(mod_dl_path) + open_dl[pkg_name] = dl + } catch(e) {} + } + if (dl) { + try { + var sym = os.dylib_symbol(dl, symname) + if (sym) return sym + } catch(e) {} + } + } } - // Resolve the module path with package awareness - var resolved = resolve_path(requested, current_package, MOD_EXT) + return null +} + +globalThis.use = function use(file, ...args) { + var requested = file + log.console(`use: ${file}`) - // Generate cache key based on resolution + // 1. Check Local + // Check for C symbol locally + var c_mod = null + if (!current_package) { + c_mod = get_c_symbol(requested, null) + } + + var resolved = null + + // Check for local file + if (!current_package) { + resolved = resolve_path(requested, null, MOD_EXT) + // If we found a core module but we were looking for local, ignore it for now + if (resolved && resolved.isCore) resolved = null + } + + // If we found something locally (C or Script), stop looking elsewhere + if (c_mod || resolved) { + // Proceed with local + } else { + // 2. Check Modules + // Check for C symbol in modules + // We need to guess the package name if not in a package context + var pkg_guess = requested.split('/')[0] + c_mod = get_c_symbol(requested, pkg_guess) + + if (!c_mod) { + // Check for module file + resolved = resolve_path(requested, null, MOD_EXT) // resolve_path handles module lookup if we pass null context but path has / + if (resolved && resolved.isCore) resolved = null + } + } + + // 3. Check Core + if (!c_mod && !resolved) { + var embed_mod = use_embed(requested) + if (embed_mod) c_mod = embed_mod + + var res = resolve_path(requested, null, MOD_EXT) + if (res && res.isCore) resolved = res + } + + // If still nothing + if (!c_mod && !resolved) { + // Try load_internal as last resort for core C + c_mod = load_internal(`js_${requested}_use`) + } + + if (!c_mod && !resolved) + throw new Error(`Module ${file} could not be found (package context: ${current_package || 'none'})`) + + // Generate cache key var cache_key = resolved ? (resolved.isCore ? 'core:' + resolved.path : resolved.path) - : (dyn_path ? dyn_path.path : requested) + : (c_mod ? 'c:' + requested : requested) if (use_cache[cache_key]) return use_cache[cache_key] - log.console(`cache miss: ${cache_key}`) + // log.console(`cache miss: ${cache_key}`) - if (!resolved && !embed_mod) - throw new Error(`Module ${file} could not be found (package context: ${current_package || 'none'})`) - - // If only embedded/dynamic module exists, return it - if (!resolved && embed_mod) { - use_cache[cache_key] = embed_mod - return embed_mod + // If we have a C module, use it as context + var context = {} + if (c_mod) { + context = c_mod + log.console("use: using c_mod as context") } - var path = resolved.path - var isCore = resolved.isCore - var module_package = resolved.package_name + // If we have a script, run it + var ret = c_mod - // If core module, load it - if (isCore) { - var ret = null - try { - // Try to load compiled version first - var compiledPath = get_compiled_path(resolved) - var useCompiled = false + if (resolved) { + log.console("use: running script " + resolved.path) + var path = resolved.path + var isCore = resolved.isCore + var module_package = resolved.package_name - // Always compile from source - never use precompiled for core modules - // if (is_file(compiledPath)) { + // Check for circular dependencies + if (path && loadingStack.includes(path)) { + let cycleIndex = loadingStack.indexOf(path) + let cyclePath = loadingStack.slice(cycleIndex).concat(path) + throw new Error(`Circular dependency: ${cyclePath.join(" -> ")}`) + } + + inProgress[path] = true + loadingStack.push(path) + + var prev_package = current_package + current_package = module_package + + var compiledPath = get_compiled_path(resolved) + mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/'))) + + var useCompiled = false + var srcStat = fd.stat(path) + var compiledStat = fd.stat(compiledPath) + + // Always compile from source - never use precompiled for regular modules + // if (srcStat && srcStat.isFile && compiledStat && compiledStat.isFile && compiledStat.mtime > srcStat.mtime) { // useCompiled = true // } var fn + var mod_name = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.')) + if (useCompiled) { var compiledBlob = fd.slurp(compiledPath) fn = js.compile_unblob(compiledBlob) fn = js.eval_compile(fn) - log.console("use: using compiled core version " + compiledPath) + log.console("use: using compiled version " + compiledPath) } else { - var script = utf8.decode(core_qop.read(path)) - var mod_script = `(function setup_${requested.replace(/[^a-zA-Z0-9_]/g, '_')}_module(arg, $_){${script};})` - fn = js.compile(path, mod_script) - - // Save compiled version - mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/'))) - var compiled = js.compile_blob(fn) - write_file(compiledPath, compiled) - - fn = js.eval_compile(fn) + if (isCore) { + var script = utf8.decode(core_qop.read(path)) + var mod_script = `(function setup_${requested.replace(/[^a-zA-Z0-9_]/g, '_')}_module(arg, $_){${script};})` + fn = js.compile(path, mod_script) + + // Save compiled version + mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/'))) + var compiled = js.compile_blob(fn) + write_file(compiledPath, compiled) + + fn = js.eval_compile(fn) + } else { + var script = utf8.decode(fd.slurp(path)) + var mod_script = `(function setup_${mod_name}_module(arg, $_){${script};})` + fn = js.compile(path, mod_script) + + // Save compiled version to .cell directory + var compiled = js.compile_blob(fn) + write_file(compiledPath, compiled) + + fn = js.eval_compile(fn) + } } - var context = embed_mod ? embed_mod : {} ret = fn.call(context, args, $_) - } catch (e) { - // Script component doesn't exist or failed, fall back to embedded module - // log.console("use: core module " + path + " failed to load script (using embedded if avail)") - } - - if (!ret && embed_mod) { - ret = embed_mod - } else if (!ret) { - throw new Error(`Use must be used with a module, but ${path} doesn't return a value`) - } - - use_cache[cache_key] = ret - return ret + log.console("use: script returned " + (typeof ret)) + + current_package = prev_package + loadingStack.pop() + delete inProgress[path] } - // Check for circular dependencies using the resolved path - if (path && loadingStack.includes(path)) { - let cycleIndex = loadingStack.indexOf(path) - let cyclePath = loadingStack.slice(cycleIndex).concat(path) - - throw new Error( - `Circular dependency detected while loading "${file}".\n` + - `Module chain: ${loadingStack.join(" -> ")}\n` + - `Cycle specifically: ${cyclePath.join(" -> ")}` - ) + if (!ret && c_mod) { + log.console("use: script returned nothing, using c_mod") + ret = c_mod } + else if (!ret) throw new Error(`Use must be used with a module, but ${file} doesn't return a value`) - log.console("use: loading file " + path + " (package: " + (module_package || 'local') + ")") - inProgress[path] = true - loadingStack.push(path) - - // Save and set package context for nested use() calls - var prev_package = current_package - current_package = module_package - - // Determine the compiled file path in .cell directory - var compiledPath = get_compiled_path(resolved) - - mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/'))) - - // Check if compiled version exists and is newer than source - var useCompiled = false - var srcStat = fd.stat(path) - var compiledStat = fd.stat(compiledPath) - - // Always compile from source - never use precompiled for regular modules - // if (srcStat && srcStat.isFile && compiledStat && compiledStat.isFile && compiledStat.mtime > srcStat.mtime) { - // useCompiled = true - // } - - var fn - var mod_name = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.')) - - if (useCompiled) { - var compiledBlob = fd.slurp(compiledPath) - fn = js.compile_unblob(compiledBlob) - fn = js.eval_compile(fn) - log.console("use: using compiled version " + compiledPath) - } else { - var script = utf8.decode(fd.slurp(path)) - var mod_script = `(function setup_${mod_name}_module(arg, $_){${script};})` - fn = js.compile(path, mod_script) - - // Save compiled version to .cell directory - var compiled = js.compile_blob(fn) - write_file(compiledPath, compiled) - - fn = js.eval_compile(fn) - } - - // Create context - if embedded module exists, use it as 'this' - var context = embed_mod ? embed_mod : {} - - // Call the script - pass embedded module as 'this' if it exists - var ret = fn.call(context, args, $_) - - // Restore previous package context - current_package = prev_package - - // If script doesn't return anything, check if we have embedded module - if (!ret && embed_mod) { - ret = embed_mod - } else if (!ret) { - throw new Error(`Use must be used with a module, but ${path} doesn't return a value`) - } - - loadingStack.pop() - delete inProgress[path] - - // Cache the result use_cache[cache_key] = ret - return ret } @@ -614,7 +643,7 @@ stone.p = function(object) function guid(bits = 256) { - var guid = new blob(bits, hidden.randi) + var guid = new blob(bits, os.random) stone(guid) return text(guid,'h') } @@ -629,10 +658,13 @@ function create_actor(desc = {id:guid()}) { var $_ = create_actor() -$_.random = hidden.rand +$_.random = function() { + var n = os.random() + return n > 100000 +} $_.random[cell.DOC] = "returns a number between 0 and 1. There is a 50% chance that the result is less than 0.5." -$_.random_fit = hidden.randi +$_.random_fit = os.random $_.clock = function(fn) { actor_mod.clock(_ => { @@ -1113,7 +1145,7 @@ var useCompiled = false // Always compile from source - never use precompiled for main program // if (resolved_prog.isCore) { // // For core, we check if we have a compiled version, else we compile it -// if (is_file(compiledPath)) { +// if (fd.is_file(compiledPath)) { // useCompiled = true // } // } else { diff --git a/scripts/fd.c b/scripts/fd.c index 89213a82..d745ccf0 100644 --- a/scripts/fd.c +++ b/scripts/fd.c @@ -411,6 +411,20 @@ JSC_SCALL(fd_readdir, #endif ) +JSC_SCALL(fd_is_file, + struct stat st; + if (stat(str, &st) != 0) + return JS_NewBool(js, false); + return JS_NewBool(js, S_ISREG(st.st_mode)); +) + +JSC_SCALL(fd_is_dir, + struct stat st; + if (stat(str, &st) != 0) + return JS_NewBool(js, false); + return JS_NewBool(js, S_ISDIR(st.st_mode)); +) + static const JSCFunctionListEntry js_fd_funcs[] = { MIST_FUNC_DEF(fd, open, 2), MIST_FUNC_DEF(fd, write, 2), @@ -428,6 +442,8 @@ static const JSCFunctionListEntry js_fd_funcs[] = { MIST_FUNC_DEF(fd, stat, 1), MIST_FUNC_DEF(fd, fstat, 1), MIST_FUNC_DEF(fd, readdir, 1), + MIST_FUNC_DEF(fd, is_file, 1), + MIST_FUNC_DEF(fd, is_dir, 1), }; JSValue js_fd_use(JSContext *js) { diff --git a/scripts/os.c b/scripts/os.c index a95c291e..653becd0 100644 --- a/scripts/os.c +++ b/scripts/os.c @@ -13,6 +13,7 @@ #ifdef _WIN32 #include +#include #else #include #include @@ -20,9 +21,14 @@ #ifdef __linux__ #include #include +#include +#include +#include #endif #endif +#include + static JSClassID js_dylib_class_id; static void js_dylib_finalizer(JSRuntime *rt, JSValue val) { @@ -58,6 +64,8 @@ JSC_CCALL(os_power_state, case SDL_POWERSTATE_NO_BATTERY: statestr = "no battery"; break; case SDL_POWERSTATE_CHARGING: statestr = "charging"; break; case SDL_POWERSTATE_CHARGED: statestr = "charged"; break; + case SDL_POWERSTATE_ERROR: statestr = "error"; break; + case SDL_POWERSTATE_UNKNOWN: statestr = "unknown"; break; } ret = JS_NewObject(js); JS_SetPropertyStr(js,ret,"state",JS_NewString(js,statestr)); @@ -383,6 +391,109 @@ static JSValue js_os_dylib_symbol(JSContext *js, JSValue self, int argc, JSValue return symbol(js); } +JSC_CCALL(os_print, + size_t len; + const char *str = JS_ToCStringLen(js, &len, argv[0]); + printf("%.*s", (int)len, str); + JS_FreeCString(js, str); +) + +static JSValue js_os_load_internal(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + void *handle; +#ifdef _WIN32 + handle = GetModuleHandle(NULL); +#else + handle = dlopen(NULL, RTLD_LAZY); +#endif + if (argc < 1) { + return JS_ThrowTypeError(js, "load_internal requires a symbol name"); + } + + const char *symbol_name = JS_ToCString(js, argv[0]); + if (!symbol_name) { + return JS_ThrowTypeError(js, "symbol name must be a string"); + } + + JSValue (*symbol)(JSContext *js); +#if defined(_WIN32) + symbol = GetProcAddress((HMODULE)handle, symbol_name); +#else + symbol = dlsym(handle, symbol_name); +#endif + + JS_FreeCString(js, symbol_name); + + if (!symbol) { + const char *error_msg = "Symbol not found"; +#ifndef _WIN32 + const char *dl_error = dlerror(); + if (dl_error) { + error_msg = dl_error; + } +#endif + return JS_ThrowReferenceError(js, "Failed to get symbol: %s", error_msg); + } + + // Return the symbol as a pointer value + return symbol(js); +} + +#if defined(_WIN32) +// ------- Windows: use BCryptGenRandom ------- +int randombytes(void *buf, size_t n) { + NTSTATUS status = BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)n, BCRYPT_USE_SYSTEM_PREFERRED_RNG); + return (status == 0) ? 0 : -1; +} + +#elif defined(__linux__) +// ------- Linux: try getrandom, fall back to /dev/urandom ------- +static int randombytes_fallback(void *buf, size_t n) { + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) return -1; + ssize_t r = read(fd, buf, n); + close(fd); + return (r == (ssize_t)n) ? 0 : -1; +} + +int randombytes(void *buf, size_t n) { +#ifdef SYS_getrandom + // Try getrandom(2) if available + ssize_t ret = syscall(SYS_getrandom, buf, n, 0); + if (ret < 0) { + // If getrandom is not supported or fails, fall back + if (errno == ENOSYS) { + return randombytes_fallback(buf, n); + } + return -1; + } + return (ret == (ssize_t)n) ? 0 : -1; +#else + // getrandom not available, just fallback + return randombytes_fallback(buf, n); +#endif +} + +#else +// ------- Other Unix: read from /dev/urandom ------- +int randombytes(void *buf, size_t n) { + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) return -1; + ssize_t r = read(fd, buf, n); + close(fd); + return (r == (ssize_t)n) ? 0 : -1; +} +#endif + +JSC_CCALL(os_random, + double random_double; + uint8_t *buf = (uint8_t *)&random_double; + if (randombytes(buf, sizeof(double)) != 0) { + return JS_ThrowInternalError(js, "failed to generate random bytes"); + } + return JS_NewFloat64(js, random_double); +) + static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, platform, 0), MIST_FUNC_DEF(os, arch, 0), @@ -400,6 +511,9 @@ static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, exit, 0), MIST_FUNC_DEF(os, dylib_open, 1), MIST_FUNC_DEF(os, dylib_symbol, 2), + MIST_FUNC_DEF(os, load_internal, 1), + MIST_FUNC_DEF(os, print, 1), + MIST_FUNC_DEF(os, random, 0), }; JSValue js_os_use(JSContext *js) { diff --git a/source/jsffi.c b/source/jsffi.c index a243abc7..9c985c36 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -157,33 +157,6 @@ static uint32_t xorshift32(){ return rng_state = x; } -JSC_CCALL(os_guid, - uint8_t data[16]; - for(int i = 0; i < 4; i++){ - uint32_t v = xorshift32(); - memcpy(&data[i*4], &v, 4); - } - - static const char hex[] = "0123456789abcdef"; - char buf[32]; - for(int i = 0; i < 16; i++){ - uint8_t b = data[i]; - buf[i*2 ] = hex[b >> 4]; - buf[i*2 + 1] = hex[b & 0x0f]; - } - - return JS_NewStringLen(js, buf, 32); -) - -JSC_SCALL(console_print, printf("%s", str); ) - -static const JSCFunctionListEntry js_console_funcs[] = { - MIST_FUNC_DEF(console,print,1), -}; - - -JSC_SCALL(os_system, ret = number2js(js,system(str)); ) - JSC_CCALL(os_rand, MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand; return number2js(js, genRand(mrand)); @@ -199,124 +172,6 @@ JSC_CCALL(os_srand, m_seedRand(mrand, js2number(js,argv[0])); ) -static const JSCFunctionListEntry js_util_funcs[] = { - MIST_FUNC_DEF(os, guid, 0), -}; - -static void *get_main_module_handle() { -#if defined(_WIN32) - return GetModuleHandle(NULL); -#else - return dlopen(NULL, RTLD_LAZY); -#endif -} - -static void *get_symbol(void *handle, const char *name) { -#if defined(_WIN32) - return (void*)GetProcAddress((HMODULE)handle, name); -#else - return dlsym(handle, name); -#endif -} - -JSC_CCALL(os_use_dyn, - const char *path = JS_ToCString(js, argv[0]); - if (!path) return JS_ThrowTypeError(js, "path must be a string"); - - // 1. Load the library -#if defined(_WIN32) - HMODULE handle = LoadLibraryA(path); -#else - void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); -#endif - - if (!handle) { - JS_FreeCString(js, path); -#if defined(_WIN32) - return JS_ThrowReferenceError(js, "Could not load library"); -#else - return JS_ThrowReferenceError(js, "Could not load library: %s", dlerror()); -#endif - } - - // 2. Construct symbol name: js__use - // Extract basename from path - const char *base = strrchr(path, '/'); -#if defined(_WIN32) - const char *base2 = strrchr(path, '\\'); - if (base2 > base) base = base2; -#endif - - if (base) base++; // Skip the slash - else base = path; // No slash, use whole path - - char name_buf[256]; - const char *dot = strrchr(base, '.'); - size_t len = dot ? (size_t)(dot - base) : strlen(base); - if (len > 100) len = 100; // Safety cap - - char clean_base[128]; - memcpy(clean_base, base, len); - clean_base[len] = '\0'; - - snprintf(name_buf, sizeof(name_buf), "js_%s_use", clean_base); - - // 3. Get the symbol - typedef JSValue (*init_fn)(JSContext*); - init_fn fn = (init_fn)get_symbol(handle, name_buf); - - JS_FreeCString(js, path); - - if (!fn) { - // Try without stripping extension? No, standard is usually without. - // Maybe try "js_main_use"? No. - // Let's stick to the plan. -#if defined(_WIN32) - FreeLibrary(handle); -#else - dlclose(handle); -#endif - return JS_ThrowReferenceError(js, "Could not find entry point %s in library", name_buf); - } - - // 4. Call the function - return fn(js); -) - -JSC_SCALL(os_load_internal, - void *handle = get_main_module_handle(); - if (!handle) { - return JS_ThrowReferenceError(js, "Could not get main module handle"); - } - - JSValue (*js_use)(JSContext*) = get_symbol(handle, str); - - if (!js_use) { - // Try without "js_" prefix or other variations if needed, but standard is js__use - return JS_NULL; - } - - ret = js_use(js); -) - -JSValue js_util_use(JSContext *js) { - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js,mod,js_util_funcs,countof(js_util_funcs)); - return mod; -} - - -JSValue js_console_use(JSContext *js) { - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js,mod,js_console_funcs,countof(js_console_funcs)); - return mod; -} - -JSC_CCALL(os_value_id, - JS_SetPropertyStr(js, argv[0], "id", number2js(js, (uintptr_t)JS_VALUE_GET_PTR(argv[0]))); - return argv[0]; -) - void ffi_load(JSContext *js) { cell_rt *rt = JS_GetContextOpaque(js); @@ -336,13 +191,7 @@ void ffi_load(JSContext *js) JSValue hidden_fn = JS_NewObject(js); - // Add functions that should only be accessible to engine.js - JS_SetPropertyStr(js, hidden_fn, "load_internal", JS_NewCFunction(js, js_os_load_internal, "load_internal", 1)); - JS_SetPropertyStr(js, hidden_fn, "rand", JS_NewCFunction(js, js_os_rand, "rand", 0)); - JS_SetPropertyStr(js, hidden_fn, "randi", JS_NewCFunction(js, js_os_randi, "randi", 0)); - JS_SetPropertyStr(js, hidden_fn, "srand", JS_NewCFunction(js, js_os_srand, "srand", 1)); - JS_SetPropertyStr(js, hidden_fn, "use_dyn", JS_NewCFunction(js, js_os_use_dyn, "use_dyn", 1)); - + #if defined(_WIN32) JS_SetPropertyStr(js, hidden_fn, "dylib_ext", JS_NewString(js, ".dll")); #elif defined(__APPLE__)