Added an OS based random number generator (RNG). (0.37.11)
This commit is contained in:
parent
1eacf9411c
commit
87f540797b
12 changed files with 216 additions and 21 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(fun VERSION 0.37.10 LANGUAGES C)
|
project(fun VERSION 0.37.11 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
0
examples/random_demo.fun
Normal file → Executable file
0
examples/random_demo.fun
Normal file → Executable file
43
examples/random_number_example.fun
Executable file
43
examples/random_number_example.fun
Executable file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2025-12-12
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Example: Using OS-based random_number(len) builtin
|
||||||
|
* This generates cryptographically strong random bytes and returns them
|
||||||
|
* hex-encoded as a string of length 2*len (since each byte -> two hex chars).
|
||||||
|
*/
|
||||||
|
|
||||||
|
print("--- random_number(len) demo ---")
|
||||||
|
|
||||||
|
len_bytes = 16
|
||||||
|
hexstr = random_number(len_bytes)
|
||||||
|
print("Requested bytes: " + to_string(len_bytes))
|
||||||
|
print("Hex string: " + hexstr)
|
||||||
|
print("Hex length (should be 2*bytes = 32): " + to_string(len(hexstr)))
|
||||||
|
|
||||||
|
// Zero length returns empty string
|
||||||
|
empty = random_number(0)
|
||||||
|
print("Empty (0 bytes) -> length: " + to_string(len(empty)) + " value: " + to_string(empty))
|
||||||
|
|
||||||
|
// You can request longer values as needed, e.g., 32 bytes -> 64 hex chars
|
||||||
|
hex64 = random_number(32)
|
||||||
|
print("32 bytes -> " + to_string(len(hex64)) + " hex chars")
|
||||||
|
|
||||||
|
/* Possible output:
|
||||||
|
--- random_number(len) demo ---
|
||||||
|
Requested bytes: 16
|
||||||
|
Hex string: d5f12eb93b71e78cc803aa802e76e3b4
|
||||||
|
Hex length (should be 2*bytes = 32): 32
|
||||||
|
Empty (0 bytes) -> length: 0 value:
|
||||||
|
32 bytes -> 64 hex chars
|
||||||
|
*/
|
||||||
36
make
36
make
|
|
@ -34,6 +34,23 @@ if [ "$target" = "all" ]; then
|
||||||
-DFUN_USE_MUSL=OFF \
|
-DFUN_USE_MUSL=OFF \
|
||||||
-DFUN_DEBUG=OFF \
|
-DFUN_DEBUG=OFF \
|
||||||
&& cmake --build build --target fun
|
&& cmake --build build --target fun
|
||||||
|
elif [ "$target" = "all_debug" ]; then
|
||||||
|
rm -rf build \
|
||||||
|
&& cmake -S . -B build \
|
||||||
|
-DFUN_WITH_PCSC=ON \
|
||||||
|
-DFUN_WITH_REPL=ON \
|
||||||
|
-DFUN_WITH_LIBSQL=ON \
|
||||||
|
-DFUN_WITH_SQLITE=ON \
|
||||||
|
-DFUN_WITH_CURL=ON \
|
||||||
|
-DFUN_WITH_PCRE2=ON \
|
||||||
|
-DFUN_WITH_XML2=ON \
|
||||||
|
-DFUN_WITH_JSON=ON \
|
||||||
|
-DFUN_WITH_TCLTK=ON \
|
||||||
|
-DFUN_WITH_INI=ON \
|
||||||
|
-DFUN_LINK_STATIC=OFF \
|
||||||
|
-DFUN_USE_MUSL=OFF \
|
||||||
|
-DFUN_DEBUG=ON \
|
||||||
|
&& cmake --build build --target fun
|
||||||
elif [ "$target" = "alpine" ]; then
|
elif [ "$target" = "alpine" ]; then
|
||||||
rm -rf build \
|
rm -rf build \
|
||||||
&& cmake -S . -B build \
|
&& cmake -S . -B build \
|
||||||
|
|
@ -67,23 +84,6 @@ elif [ "$target" = "debug" ]; then
|
||||||
-DFUN_USE_MUSL=OFF \
|
-DFUN_USE_MUSL=OFF \
|
||||||
-DFUN_DEBUG=ON \
|
-DFUN_DEBUG=ON \
|
||||||
&& cmake --build build --target fun
|
&& cmake --build build --target fun
|
||||||
elif [ "$target" = "debug_all" ]; then
|
|
||||||
rm -rf build \
|
|
||||||
&& cmake -S . -B build \
|
|
||||||
-DFUN_WITH_PCSC=ON \
|
|
||||||
-DFUN_WITH_REPL=ON \
|
|
||||||
-DFUN_WITH_LIBSQL=ON \
|
|
||||||
-DFUN_WITH_SQLITE=ON \
|
|
||||||
-DFUN_WITH_CURL=ON \
|
|
||||||
-DFUN_WITH_PCRE2=ON \
|
|
||||||
-DFUN_WITH_XML2=ON \
|
|
||||||
-DFUN_WITH_JSON=ON \
|
|
||||||
-DFUN_WITH_TCLTK=ON \
|
|
||||||
-DFUN_WITH_INI=ON \
|
|
||||||
-DFUN_LINK_STATIC=OFF \
|
|
||||||
-DFUN_USE_MUSL=OFF \
|
|
||||||
-DFUN_DEBUG=ON \
|
|
||||||
&& cmake --build build --target fun
|
|
||||||
elif [ "$target" = "freebsd" ]; then
|
elif [ "$target" = "freebsd" ]; then
|
||||||
rm -rf build \
|
rm -rf build \
|
||||||
&& cmake -S . -B build \
|
&& cmake -S . -B build \
|
||||||
|
|
@ -155,9 +155,9 @@ else
|
||||||
echo "Build target $target not found... aborting!";
|
echo "Build target $target not found... aborting!";
|
||||||
echo "Available targets:";
|
echo "Available targets:";
|
||||||
echo " - all";
|
echo " - all";
|
||||||
|
echo " - all_debug";
|
||||||
echo " - alpine";
|
echo " - alpine";
|
||||||
echo " - debug";
|
echo " - debug";
|
||||||
echo " - debug_all";
|
|
||||||
echo " - freebsd";
|
echo " - freebsd";
|
||||||
echo " - minimal";
|
echo " - minimal";
|
||||||
echo " - musl";
|
echo " - musl";
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ static const char *opcode_name(OpCode op) {
|
||||||
case OP_THREAD_SPAWN: return "THREAD_SPAWN";
|
case OP_THREAD_SPAWN: return "THREAD_SPAWN";
|
||||||
case OP_THREAD_JOIN: return "THREAD_JOIN";
|
case OP_THREAD_JOIN: return "THREAD_JOIN";
|
||||||
case OP_SLEEP_MS: return "SLEEP_MS";
|
case OP_SLEEP_MS: return "SLEEP_MS";
|
||||||
|
case OP_RANDOM_NUMBER: return "RANDOM_NUMBER";
|
||||||
case OP_BAND: return "BAND";
|
case OP_BAND: return "BAND";
|
||||||
case OP_BOR: return "BOR";
|
case OP_BOR: return "BOR";
|
||||||
case OP_BXOR: return "BXOR";
|
case OP_BXOR: return "BXOR";
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ typedef enum {
|
||||||
OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0)
|
OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0)
|
||||||
OP_THREAD_JOIN, // pops thread id; waits; pushes result value (or Nil)
|
OP_THREAD_JOIN, // pops thread id; waits; pushes result value (or Nil)
|
||||||
OP_SLEEP_MS, // pops milliseconds; sleeps; pushes Nil (for statement POP safety)
|
OP_SLEEP_MS, // pops milliseconds; sleeps; pushes Nil (for statement POP safety)
|
||||||
|
OP_RANDOM_NUMBER, // pops length; pushes hex string of that length from OS RNG (hex-encoded)
|
||||||
|
|
||||||
// Bitwise (32-bit) and shifts/rotates
|
// Bitwise (32-bit) and shifts/rotates
|
||||||
OP_BAND, // pops b, a; pushes (uint32_t)(a & b)
|
OP_BAND, // pops b, a; pushes (uint32_t)(a & b)
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "bytecode.h"
|
#include "bytecode.h"
|
||||||
#include "value.h"
|
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#ifdef FUN_WITH_REPL
|
#ifdef FUN_WITH_REPL
|
||||||
#include "repl.h"
|
#include "repl.h"
|
||||||
|
|
|
||||||
10
src/parser.c
10
src/parser.c
|
|
@ -1649,6 +1649,16 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strcmp(name, "random_number") == 0) {
|
||||||
|
(*pos)++; /* '(' */
|
||||||
|
/* expects exactly 1 arg: length */
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "random_number expects 1 arg (length)"); free(name); return 0; }
|
||||||
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after random_number arg"); free(name); return 0; }
|
||||||
|
bytecode_add_instruction(bc, OP_RANDOM_NUMBER, 0);
|
||||||
|
free(name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* threading */
|
/* threading */
|
||||||
if (strcmp(name, "thread_spawn") == 0) {
|
if (strcmp(name, "thread_spawn") == 0) {
|
||||||
(*pos)++; /* '(' */
|
(*pos)++; /* '(' */
|
||||||
|
|
|
||||||
1
src/vm.c
1
src/vm.c
|
|
@ -666,6 +666,7 @@ void vm_run(VM *vm, Bytecode *entry) {
|
||||||
#include "vm/os/time_now_ms.c"
|
#include "vm/os/time_now_ms.c"
|
||||||
#include "vm/os/clock_mono_ms.c"
|
#include "vm/os/clock_mono_ms.c"
|
||||||
#include "vm/os/date_format.c"
|
#include "vm/os/date_format.c"
|
||||||
|
#include "vm/os/random_number.c"
|
||||||
|
|
||||||
/* Socket ops */
|
/* Socket ops */
|
||||||
#include "vm/os/socket_tcp_listen.c"
|
#include "vm/os/socket_tcp_listen.c"
|
||||||
|
|
|
||||||
1
src/vm.h
1
src/vm.h
|
|
@ -37,6 +37,7 @@ static const char *opcode_names[] = {
|
||||||
"READ_FILE","WRITE_FILE","ENV","INPUT_LINE","PROC_RUN","PROC_SYSTEM",
|
"READ_FILE","WRITE_FILE","ENV","INPUT_LINE","PROC_RUN","PROC_SYSTEM",
|
||||||
"TIME_NOW_MS","CLOCK_MONO_MS","DATE_FORMAT",
|
"TIME_NOW_MS","CLOCK_MONO_MS","DATE_FORMAT",
|
||||||
"THREAD_SPAWN","THREAD_JOIN","SLEEP_MS",
|
"THREAD_SPAWN","THREAD_JOIN","SLEEP_MS",
|
||||||
|
"RANDOM_NUMBER",
|
||||||
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR",
|
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR",
|
||||||
"JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE",
|
"JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE",
|
||||||
"CURL_GET","CURL_POST","CURL_DOWNLOAD",
|
"CURL_GET","CURL_POST","CURL_DOWNLOAD",
|
||||||
|
|
|
||||||
139
src/vm/os/random_number.c
Normal file
139
src/vm/os/random_number.c
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
/**
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2025-12-12
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Generate OS-based random bytes and return them hex-encoded.
|
||||||
|
* Opcode: OP_RANDOM_NUMBER
|
||||||
|
* Stack: pops len (number of raw bytes), pushes hex string of length 2*len
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Platform-specific headers guarded per OS to avoid leaking problematic macros */
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#include <windows.h>
|
||||||
|
#include <bcrypt.h>
|
||||||
|
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||||
|
#include <stdlib.h>
|
||||||
|
#elif defined(__unix__)
|
||||||
|
#if __has_include(<sys/random.h>)
|
||||||
|
#include <sys/random.h>
|
||||||
|
#endif
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case OP_RANDOM_NUMBER: {
|
||||||
|
/* pop requested raw byte length */
|
||||||
|
Value lv = pop_value(vm);
|
||||||
|
if (lv.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: random_number(len) expects integer length\n");
|
||||||
|
free_value(lv);
|
||||||
|
/* For safety, push empty string so callers expecting a value won't underflow */
|
||||||
|
push_value(vm, make_string(""));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int64_t len = lv.i;
|
||||||
|
if (len < 0) {
|
||||||
|
fprintf(stderr, "random_number error: negative length (%" PRId64 ")\n", len);
|
||||||
|
free_value(lv);
|
||||||
|
push_value(vm, make_string(""));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (len == 0) {
|
||||||
|
free_value(lv);
|
||||||
|
push_value(vm, make_string(""));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cap to prevent excessive allocations (max 1 MiB raw -> 2 MiB hex) */
|
||||||
|
const int64_t MAX_RAW = (1LL << 20);
|
||||||
|
if (len > MAX_RAW) {
|
||||||
|
fprintf(stderr, "random_number error: requested length too large (%" PRId64 ", max %" PRId64 ")\n", len, MAX_RAW);
|
||||||
|
free_value(lv);
|
||||||
|
push_value(vm, make_string(""));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char *raw = (unsigned char*)malloc((size_t)len);
|
||||||
|
char *hex = (char*)malloc((size_t)len * 2 + 1);
|
||||||
|
if (!raw || !hex) {
|
||||||
|
if (raw) free(raw);
|
||||||
|
if (hex) free(hex);
|
||||||
|
free_value(lv);
|
||||||
|
fprintf(stderr, "Out of memory in random_number\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ok = 0;
|
||||||
|
|
||||||
|
/* --- Fill raw with cryptographically secure random bytes from the OS --- */
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
{
|
||||||
|
/* Windows: use BCryptGenRandom */
|
||||||
|
NTSTATUS st = BCryptGenRandom(NULL, raw, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||||
|
ok = (st == 0);
|
||||||
|
}
|
||||||
|
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||||
|
{
|
||||||
|
arc4random_buf(raw, (size_t)len);
|
||||||
|
ok = 1;
|
||||||
|
}
|
||||||
|
#elif defined(__unix__)
|
||||||
|
{
|
||||||
|
/* Prefer getrandom if available, otherwise /dev/urandom */
|
||||||
|
#if __has_include(<sys/random.h>)
|
||||||
|
ssize_t n = getrandom(raw, (size_t)len, 0);
|
||||||
|
ok = (n == (ssize_t)len);
|
||||||
|
#else
|
||||||
|
ok = 0;
|
||||||
|
#endif
|
||||||
|
if (!ok) {
|
||||||
|
int fd = open("/dev/urandom", O_RDONLY);
|
||||||
|
if (fd >= 0) {
|
||||||
|
size_t off = 0; ssize_t n;
|
||||||
|
while (off < (size_t)len && (n = read(fd, raw + off, (size_t)len - off)) > 0) off += (size_t)n;
|
||||||
|
close(fd);
|
||||||
|
ok = (off == (size_t)len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ok = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
free(raw);
|
||||||
|
free(hex);
|
||||||
|
free_value(lv);
|
||||||
|
fprintf(stderr, "random_number error: OS RNG unavailable or failed\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hex encode */
|
||||||
|
static const char hexdig[] = "0123456789abcdef";
|
||||||
|
for (int64_t i = 0; i < len; ++i) {
|
||||||
|
unsigned char b = raw[i];
|
||||||
|
hex[2*i] = hexdig[(b >> 4) & 0xF];
|
||||||
|
hex[2*i+1] = hexdig[b & 0xF];
|
||||||
|
}
|
||||||
|
hex[len * 2] = '\0';
|
||||||
|
|
||||||
|
Value s = make_string(hex);
|
||||||
|
free(raw);
|
||||||
|
free(hex);
|
||||||
|
free_value(lv);
|
||||||
|
push_value(vm, s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue