Files
bux-lang/rt/runtime.c
T

1761 lines
52 KiB
C

/* Bux Runtime - Minimal C runtime for Bux programs */
/* This is linked with every Bux program compiled via the C backend */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <pthread.h>
#include <ucontext.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
/* Command-line argument storage */
int g_argc = 0;
char** g_argv = NULL;
int bux_argc(void) { return g_argc; }
char* bux_argv(int index) {
if (index < 0 || index >= g_argc) return "";
return g_argv[index];
}
/* Memory allocation */
void* bux_alloc(size_t size) {
void* ptr = calloc(1, size);
if (ptr == NULL) {
fprintf(stderr, "bux runtime: out of memory (alloc %zu bytes)\n", size);
abort();
}
return ptr;
}
void* bux_realloc(void* ptr, size_t size) {
void* new_ptr = realloc(ptr, size);
if (new_ptr == NULL && size > 0) {
fprintf(stderr, "bux runtime: out of memory (realloc %zu bytes)\n", size);
abort();
}
return new_ptr;
}
void bux_free(void* ptr) {
free(ptr);
}
/* Escape a string for C output (newlines -> \n, quotes -> \", etc.) */
/* Returns escaped content WITHOUT surrounding quotes */
char* bux_escape_c_string(const char* s, int len) {
if (s == NULL || len <= 0) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
/* Worst case: every char needs escaping (e.g., all newlines -> 2 chars) */
char* buf = (char*)bux_alloc(len * 2 + 1);
int j = 0;
for (int i = 0; i < len; i++) {
char c = s[i];
switch (c) {
case '\n': buf[j++] = '\\'; buf[j++] = 'n'; break;
case '\r': buf[j++] = '\\'; buf[j++] = 'r'; break;
case '\t': buf[j++] = '\\'; buf[j++] = 't'; break;
case '\\': buf[j++] = '\\'; buf[j++] = '\\'; break;
case '"': buf[j++] = '\\'; buf[j++] = '"'; break;
default: buf[j++] = c; break;
}
}
buf[j] = '\0';
return buf;
}
int bux_run_nim(const char* nim_file, const char* out_bin) {
char cmd[4096];
snprintf(cmd, sizeof(cmd), "nim c -o:%s -d:release --gc:orc %s 2>&1", out_bin, nim_file);
return system(cmd);
}
int bux_system(const char* cmd) {
if (cmd == NULL) return -1;
return system(cmd);
}
int bux_strlen_c(const char* s) {
if (s == NULL) return 0;
const char* p = s;
while (*p) p++;
return (int)(p - s);
}
/* I/O */
void bux_print(const char* s) {
if (s != NULL) {
fputs(s, stdout);
}
}
void bux_println(const char* s) {
if (s != NULL) {
puts(s);
} else {
puts("");
}
}
void bux_print_int(int64_t n) {
printf("%lld", (long long)n);
}
void bux_print_float(double f) {
printf("%g", f);
}
void bux_print_bool(bool b) {
printf("%s", b ? "true" : "false");
}
void bux_print_char(char c) {
putchar(c);
}
/* Panic */
void bux_panic(const char* msg) {
fprintf(stderr, "bux panic: %s\n", msg ? msg : "unknown error");
abort();
}
/* Division by zero check */
int64_t bux_div_i64(int64_t a, int64_t b) {
if (b == 0) {
bux_panic("division by zero");
}
return a / b;
}
int64_t bux_mod_i64(int64_t a, int64_t b) {
if (b == 0) {
bux_panic("modulo by zero");
}
return a % b;
}
/* String operations */
typedef struct {
const char* data;
size_t len;
} BuxString;
BuxString bux_string_from_cstr(const char* s) {
BuxString result;
result.data = s;
result.len = s ? strlen(s) : 0;
return result;
}
BuxString bux_string_concat(BuxString a, BuxString b) {
BuxString result;
result.len = a.len + b.len;
char* buf = (char*)bux_alloc(result.len + 1);
if (a.data && a.len > 0) memcpy(buf, a.data, a.len);
if (b.data && b.len > 0) memcpy(buf + a.len, b.data, b.len);
buf[result.len] = '\0';
result.data = buf;
return result;
}
/* Slice operations */
typedef struct {
void* data;
size_t len;
size_t cap;
} BuxSlice;
BuxSlice bux_slice_new(size_t elem_size, size_t len) {
BuxSlice result;
result.len = len;
result.cap = len;
result.data = bux_alloc(elem_size * len);
return result;
}
void bux_bounds_check(size_t index, size_t len) {
if (index >= len) {
fprintf(stderr, "bux panic: index out of bounds (index %zu, len %zu)\n", index, len);
abort();
}
}
/* String wrappers with Bux-compatible signatures */
unsigned int bux_strlen(const char* s) {
return (unsigned int)strlen(s);
}
int bux_strcmp(const char* a, const char* b) {
if (a == b) return 0;
if (!a) return -1;
if (!b) return 1;
return strcmp(a, b);
}
int bux_strncmp(const char* a, const char* b, unsigned int n) {
if (a == b) return 0;
if (!a) return -1;
if (!b) return 1;
return strncmp(a, b, (size_t)n);
}
char* bux_strcpy(char* dest, const char* src) {
if (!dest || !src) return dest;
return strcpy(dest, src);
}
char* bux_strcat(char* dest, const char* src) {
return strcat(dest, src);
}
char* bux_strncpy(char* dest, const char* src, unsigned int n) {
return strncpy(dest, src, (size_t)n);
}
double bux_str_to_float(const char* s) {
if (!s) return 0.0;
return strtod(s, NULL);
}
/* String find: returns pointer to first occurrence of needle in haystack, or NULL */
const char* bux_strstr(const char* haystack, const char* needle) {
if (!haystack || !needle) return NULL;
return strstr(haystack, needle);
}
/* String offset: byte offset of pos within base (both must point into same string) */
unsigned int bux_str_offset(const char* pos, const char* base) {
if (!pos || !base) return 0;
return (unsigned int)(pos - base);
}
/* String contains: returns 1 if haystack contains needle, 0 otherwise */
int bux_str_contains(const char* haystack, const char* needle) {
return bux_strstr(haystack, needle) != NULL;
}
/* String is null: returns 1 if string is NULL */
int bux_str_is_null(const char* s) {
return s == NULL;
}
/* String slice: extract substring from start, length len */
char* bux_str_slice(const char* s, unsigned int start, unsigned int len) {
if (!s) return NULL;
unsigned int s_len = (unsigned int)strlen(s);
if (start >= s_len) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
unsigned int avail = s_len - start;
if (len > avail) len = avail;
char* result = (char*)bux_alloc(len + 1);
memcpy(result, s + start, len);
result[len] = '\0';
return result;
}
/* String trim left: remove leading whitespace */
char* bux_str_trim_left(const char* s) {
if (!s) return NULL;
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
unsigned int len = (unsigned int)strlen(s);
char* result = (char*)bux_alloc(len + 1);
memcpy(result, s, len + 1);
return result;
}
/* String trim right: remove trailing whitespace */
char* bux_str_trim_right(const char* s) {
if (!s) return NULL;
unsigned int len = (unsigned int)strlen(s);
while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\t' || s[len-1] == '\n' || s[len-1] == '\r')) {
len--;
}
char* result = (char*)bux_alloc(len + 1);
memcpy(result, s, len);
result[len] = '\0';
return result;
}
/* String trim: remove both leading and trailing whitespace */
char* bux_str_trim(const char* s) {
char* left = bux_str_trim_left(s);
char* result = bux_str_trim_right(left);
bux_free(left);
return result;
}
/* Int to string conversion */
char* bux_int_to_str(int64_t n) {
char* result = (char*)bux_alloc(32);
snprintf(result, 32, "%lld", (long long)n);
return result;
}
/* String to int conversion */
int64_t bux_str_to_int(const char* s) {
if (!s) return 0;
return (int64_t)atoll(s);
}
/* String builder */
typedef struct {
char* buf;
unsigned int len;
unsigned int cap;
} BuxStringBuilder;
BuxStringBuilder* bux_sb_new(unsigned int initial_cap) {
BuxStringBuilder* sb = (BuxStringBuilder*)bux_alloc(sizeof(BuxStringBuilder));
sb->cap = initial_cap > 0 ? initial_cap : 64;
sb->buf = (char*)bux_alloc(sb->cap);
sb->buf[0] = '\0';
sb->len = 0;
return sb;
}
void bux_sb_append(BuxStringBuilder* sb, const char* s) {
if (!sb || !s) return;
unsigned int s_len = (unsigned int)strlen(s);
unsigned int new_len = sb->len + s_len;
if (new_len + 1 > sb->cap) {
while (sb->cap < new_len + 1) sb->cap *= 2;
sb->buf = (char*)bux_realloc(sb->buf, sb->cap);
}
memcpy(sb->buf + sb->len, s, s_len);
sb->len = new_len;
sb->buf[sb->len] = '\0';
}
void bux_sb_append_int(BuxStringBuilder* sb, int64_t n) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%lld", (long long)n);
bux_sb_append(sb, tmp);
}
void bux_sb_append_float(BuxStringBuilder* sb, double f) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%g", f);
bux_sb_append(sb, tmp);
}
void bux_sb_append_char(BuxStringBuilder* sb, char c) {
if (!sb) return;
if (sb->len + 2 > sb->cap) {
sb->cap *= 2;
sb->buf = (char*)bux_realloc(sb->buf, sb->cap);
}
sb->buf[sb->len++] = c;
sb->buf[sb->len] = '\0';
}
const char* bux_sb_build(BuxStringBuilder* sb) {
if (!sb) return "";
const char* buf = sb->buf;
sb->buf = NULL; // detach — ownership transferred to caller
return buf;
}
void bux_sb_free(BuxStringBuilder* sb) {
if (!sb) return;
bux_free(sb->buf); // safe: NULL if bux_sb_build already detached
bux_free(sb);
}
/* String split: count parts separated by delimiter */
unsigned int bux_str_split_count(const char* s, const char* delim) {
if (!s || !delim || !*delim) return 1;
unsigned int count = 1;
size_t delim_len = strlen(delim);
const char* p = s;
while ((p = strstr(p, delim)) != NULL) {
count++;
p += delim_len;
}
return count;
}
/* String split: get the n-th part (0-indexed) */
char* bux_str_split_part(const char* s, const char* delim, unsigned int index) {
if (!s || !delim || !*delim) {
if (index == 0) {
unsigned int len = s ? (unsigned int)strlen(s) : 0;
char* result = (char*)bux_alloc(len + 1);
if (s) memcpy(result, s, len);
result[len] = '\0';
return result;
}
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
size_t delim_len = strlen(delim);
const char* start = s;
const char* end;
unsigned int current = 0;
while (current < index) {
end = strstr(start, delim);
if (!end) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
start = end + delim_len;
current++;
}
end = strstr(start, delim);
size_t part_len;
if (end) {
part_len = (size_t)(end - start);
} else {
part_len = strlen(start);
}
char* result = (char*)bux_alloc(part_len + 1);
memcpy(result, start, part_len);
result[part_len] = '\0';
return result;
}
/* String join: join two strings with separator */
char* bux_str_join2(const char* a, const char* b, const char* sep) {
if (!a && !b) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
unsigned int len_a = a ? (unsigned int)strlen(a) : 0;
unsigned int len_b = b ? (unsigned int)strlen(b) : 0;
unsigned int len_sep = sep ? (unsigned int)strlen(sep) : 0;
unsigned int total = len_a + len_sep + len_b;
char* result = (char*)bux_alloc(total + 1);
if (a) memcpy(result, a, len_a);
if (sep && len_a > 0 && len_b > 0) memcpy(result + len_a, sep, len_sep);
if (b) memcpy(result + len_a + (len_a > 0 && len_b > 0 ? len_sep : 0), b, len_b);
result[total] = '\0';
return result;
}
/* Simple string format: replace {0}, {1}, ... with string arguments.
Returns formatted string. Supports up to 8 arguments. */
char* bux_float_to_string(double f) {
char* buf = (char*)bux_alloc(64);
snprintf(buf, 64, "%g", f);
return buf;
}
char* bux_str_format(const char* pattern,
const char* a0, const char* a1, const char* a2, const char* a3,
const char* a4, const char* a5, const char* a6, const char* a7) {
if (!pattern) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
const char* args[8] = { a0, a1, a2, a3, a4, a5, a6, a7 };
/* Calculate result size */
size_t total = 0;
const char* p = pattern;
while (*p) {
if (*p == '{' && p[1] >= '0' && p[1] <= '7' && p[2] == '}') {
int idx = p[1] - '0';
if (args[idx]) total += strlen(args[idx]);
p += 3;
} else {
total++;
p++;
}
}
char* result = (char*)bux_alloc(total + 1);
char* w = result;
p = pattern;
while (*p) {
if (*p == '{' && p[1] >= '0' && p[1] <= '7' && p[2] == '}') {
int idx = p[1] - '0';
if (args[idx]) {
size_t len = strlen(args[idx]);
memcpy(w, args[idx], len);
w += len;
}
p += 3;
} else {
*w++ = *p++;
}
}
*w = '\0';
return result;
}
/* File I/O — read entire file into string */
char* bux_read_file(const char* path) {
if (!path) return NULL;
FILE* f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
char* buf = (char*)bux_alloc((size_t)size + 1);
if (!buf) { fclose(f); return NULL; }
size_t read = fread(buf, 1, (size_t)size, f);
fclose(f);
buf[read] = '\0';
return buf;
}
/* File I/O — write string to file */
int bux_write_file(const char* path, const char* content) {
if (!path || !content) return 0;
FILE* f = fopen(path, "wb");
if (!f) return 0;
size_t len = strlen(content);
size_t written = fwrite(content, 1, len, f);
fclose(f);
return written > 0 ? 1 : 0;
}
/* File I/O — check if file exists */
int bux_file_exists(const char* path) {
if (!path) return 0;
FILE* f = fopen(path, "rb");
if (f) {
fclose(f);
return 1;
}
return 0;
}
/* Path operations */
char* bux_path_join(const char* a, const char* b) {
if (!a && !b) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
if (!a) {
unsigned int len = (unsigned int)strlen(b);
char* result = (char*)bux_alloc(len + 1);
memcpy(result, b, len + 1);
return result;
}
if (!b) {
unsigned int len = (unsigned int)strlen(a);
char* result = (char*)bux_alloc(len + 1);
memcpy(result, a, len + 1);
return result;
}
unsigned int len_a = (unsigned int)strlen(a);
unsigned int len_b = (unsigned int)strlen(b);
int need_sep = (len_a > 0 && a[len_a-1] != '/') ? 1 : 0;
unsigned int total = len_a + (need_sep ? 1 : 0) + len_b;
char* result = (char*)bux_alloc(total + 1);
memcpy(result, a, len_a);
if (need_sep) result[len_a] = '/';
memcpy(result + len_a + (need_sep ? 1 : 0), b, len_b);
result[total] = '\0';
return result;
}
char* bux_path_parent(const char* path) {
if (!path) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
int len = (int)strlen(path);
while (len > 0 && path[len-1] == '/') len--;
while (len > 0 && path[len-1] != '/') len--;
while (len > 0 && path[len-1] == '/') len--;
if (len == 0) {
char* dot = (char*)bux_alloc(2);
dot[0] = '.';
dot[1] = '\0';
return dot;
}
char* result = (char*)bux_alloc((unsigned int)len + 1);
memcpy(result, path, (unsigned int)len);
result[len] = '\0';
return result;
}
char* bux_path_ext(const char* path) {
if (!path) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
const char* dot = strrchr(path, '.');
if (!dot) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
const char* slash = strrchr(path, '/');
if (slash && slash > dot) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
unsigned int len = (unsigned int)strlen(dot);
char* result = (char*)bux_alloc(len + 1);
memcpy(result, dot, len + 1);
return result;
}
/* Math functions — wrap C math.h */
#include <math.h>
double bux_sqrt(double x) { return sqrt(x); }
double bux_pow(double x, double y) { return pow(x, y); }
int64_t bux_abs_i64(int64_t x) { return x < 0 ? -x : x; }
double bux_abs_f64(double x) { return x < 0 ? -x : x; }
int64_t bux_min_i64(int64_t a, int64_t b) { return a < b ? a : b; }
int64_t bux_max_i64(int64_t a, int64_t b) { return a > b ? a : b; }
double bux_min_f64(double a, double b) { return a < b ? a : b; }
double bux_max_f64(double a, double b) { return a > b ? a : b; }
/* Hash function (djb2) over raw bytes — for generic key types */
unsigned int bux_hash_bytes(const void* ptr, size_t size) {
if (!ptr) return 0;
unsigned int hash = 5381;
const unsigned char* bytes = (const unsigned char*)ptr;
for (size_t i = 0; i < size; i++) {
hash = ((hash << 5) + hash) + bytes[i]; /* hash * 33 + byte */
}
return hash;
}
/* Byte equality check — for generic key comparison */
int bux_mem_eq(const void* a, const void* b, size_t size) {
if (a == b) return 1;
if (!a || !b) return 0;
return memcmp(a, b, size) == 0;
}
/* Hash function (djb2) for string keys */
unsigned int bux_hash_string(const char* s) {
unsigned int hash = 5381;
int c;
while ((c = *s++)) {
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
}
return hash;
}
/* ---------------------------------------------------------------------------
* Directory listing and build tools (for self-hosting compiler)
* --------------------------------------------------------------------------- */
#include <dirent.h>
#include <sys/stat.h>
/* bux_list_dir: returns array of file paths in dir (recursively) matching ext suffix.
* Result is malloc'd array of malloc'd strings. Caller must free.
* Sets *out_count to number of files found. */
static int bux_count_files_recursive(const char* dir, const char* ext, size_t ext_len) {
DIR* d = opendir(dir);
if (!d) return 0;
int count = 0;
struct dirent* entry;
while ((entry = readdir(d)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
size_t path_len = strlen(dir) + 1 + strlen(entry->d_name) + 1;
char* path = (char*)malloc(path_len);
if (!path) continue;
snprintf(path, path_len, "%s/%s", dir, entry->d_name);
struct stat st;
if (stat(path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
count += bux_count_files_recursive(path, ext, ext_len);
} else {
size_t name_len = strlen(entry->d_name);
if (name_len > ext_len && strcmp(entry->d_name + name_len - ext_len, ext) == 0) {
count++;
}
}
}
free(path);
}
closedir(d);
return count;
}
static int bux_collect_files_recursive(const char* dir, const char* ext, size_t ext_len, char** result, int idx) {
DIR* d = opendir(dir);
if (!d) return idx;
struct dirent* entry;
while ((entry = readdir(d)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
size_t path_len = strlen(dir) + 1 + strlen(entry->d_name) + 1;
char* path = (char*)malloc(path_len);
if (!path) continue;
snprintf(path, path_len, "%s/%s", dir, entry->d_name);
struct stat st;
if (stat(path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
idx = bux_collect_files_recursive(path, ext, ext_len, result, idx);
free(path);
} else {
size_t name_len = strlen(entry->d_name);
if (name_len > ext_len && strcmp(entry->d_name + name_len - ext_len, ext) == 0) {
result[idx++] = path;
} else {
free(path);
}
}
} else {
free(path);
}
}
closedir(d);
return idx;
}
char** bux_list_dir(const char* dir, const char* ext, int* out_count) {
size_t ext_len = strlen(ext);
int count = bux_count_files_recursive(dir, ext, ext_len);
if (count == 0) { *out_count = 0; return NULL; }
char** result = (char**)malloc(count * sizeof(char*));
if (!result) { *out_count = 0; return NULL; }
int idx = bux_collect_files_recursive(dir, ext, ext_len, result, 0);
*out_count = idx;
return result;
}
/* bux_mkdir_if_needed: create directory if it doesn't exist */
int bux_mkdir_if_needed(const char* path) {
struct stat st;
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) return 0;
return mkdir(path, 0755);
}
/* bux_run_cc: invoke C compiler to compile c_file into out_bin,
* linking runtime.c and io.c. Returns exit code. */
int bux_run_cc(const char* c_file, const char* out_bin,
const char* runtime_c, const char* io_c,
const char* math_lib) {
char cmd[4096];
snprintf(cmd, sizeof(cmd),
"cc %s %s %s -o %s %s 2>&1",
c_file,
runtime_c ? runtime_c : "",
io_c ? io_c : "",
out_bin,
math_lib ? "-lm" : "");
return system(cmd);
}
/* bux_dir_exists: check if directory exists */
int bux_dir_exists(const char* path) {
struct stat st;
return (stat(path, &st) == 0 && S_ISDIR(st.st_mode));
}
/* ============================================================================
* Concurrency primitives (Phase 8.3)
* ============================================================================ */
typedef struct {
pthread_t thread;
} BuxTask;
typedef struct {
uint8_t* buffer;
size_t capacity;
size_t elem_size;
size_t head;
size_t tail;
size_t count;
pthread_mutex_t mutex;
pthread_cond_t not_empty;
pthread_cond_t not_full;
int closed;
} BuxChannel;
/* Task / thread spawning */
void* bux_task_spawn(void* (*func)(void*), void* arg) {
BuxTask* task = (BuxTask*)malloc(sizeof(BuxTask));
if (!task) {
fprintf(stderr, "bux runtime: out of memory (task spawn)\n");
abort();
}
int rc = pthread_create(&task->thread, NULL, func, arg);
if (rc != 0) {
fprintf(stderr, "bux runtime: pthread_create failed (%d)\n", rc);
free(task);
return NULL;
}
return task;
}
void bux_task_join(void* handle) {
if (!handle) return;
BuxTask* task = (BuxTask*)handle;
pthread_join(task->thread, NULL);
free(task);
}
void bux_task_sleep(int64_t ms) {
if (ms <= 0) return;
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
}
/* Channel implementation */
void* bux_channel_new(int64_t capacity, int64_t elem_size) {
if (capacity <= 0) capacity = 1;
if (elem_size <= 0) elem_size = 1;
BuxChannel* ch = (BuxChannel*)malloc(sizeof(BuxChannel));
if (!ch) {
fprintf(stderr, "bux runtime: out of memory (channel new)\n");
abort();
}
ch->buffer = (uint8_t*)malloc((size_t)capacity * (size_t)elem_size);
if (!ch->buffer) {
fprintf(stderr, "bux runtime: out of memory (channel buffer)\n");
free(ch);
abort();
}
ch->capacity = (size_t)capacity;
ch->elem_size = (size_t)elem_size;
ch->head = 0;
ch->tail = 0;
ch->count = 0;
ch->closed = 0;
pthread_mutex_init(&ch->mutex, NULL);
pthread_cond_init(&ch->not_empty, NULL);
pthread_cond_init(&ch->not_full, NULL);
return ch;
}
void bux_channel_send(void* handle, void* elem) {
if (!handle || !elem) return;
BuxChannel* ch = (BuxChannel*)handle;
pthread_mutex_lock(&ch->mutex);
while (ch->count >= ch->capacity && !ch->closed) {
pthread_cond_wait(&ch->not_full, &ch->mutex);
}
if (ch->closed) {
pthread_mutex_unlock(&ch->mutex);
return;
}
uint8_t* dst = ch->buffer + ch->tail * ch->elem_size;
memcpy(dst, elem, ch->elem_size);
ch->tail = (ch->tail + 1) % ch->capacity;
ch->count++;
pthread_cond_signal(&ch->not_empty);
pthread_mutex_unlock(&ch->mutex);
}
int bux_channel_recv(void* handle, void* out) {
if (!handle || !out) return 0;
BuxChannel* ch = (BuxChannel*)handle;
pthread_mutex_lock(&ch->mutex);
while (ch->count == 0 && !ch->closed) {
pthread_cond_wait(&ch->not_empty, &ch->mutex);
}
if (ch->count == 0) {
pthread_mutex_unlock(&ch->mutex);
return 0; /* channel empty and closed */
}
uint8_t* src = ch->buffer + ch->head * ch->elem_size;
memcpy(out, src, ch->elem_size);
ch->head = (ch->head + 1) % ch->capacity;
ch->count--;
pthread_cond_signal(&ch->not_full);
pthread_mutex_unlock(&ch->mutex);
return 1;
}
void bux_channel_close(void* handle) {
if (!handle) return;
BuxChannel* ch = (BuxChannel*)handle;
pthread_mutex_lock(&ch->mutex);
ch->closed = 1;
pthread_cond_broadcast(&ch->not_empty);
pthread_cond_broadcast(&ch->not_full);
pthread_mutex_unlock(&ch->mutex);
}
void bux_channel_free(void* handle) {
if (!handle) return;
BuxChannel* ch = (BuxChannel*)handle;
pthread_mutex_destroy(&ch->mutex);
pthread_cond_destroy(&ch->not_empty);
pthread_cond_destroy(&ch->not_full);
free(ch->buffer);
free(ch);
}
/* ============================================================================
* Synchronization primitives (Mutex, RwLock)
* ============================================================================ */
typedef struct bux_mutex {
pthread_mutex_t mtx;
} bux_mutex_t;
typedef struct bux_rwlock {
pthread_rwlock_t rwl;
} bux_rwlock_t;
void* bux_mutex_new(void) {
bux_mutex_t* m = (bux_mutex_t*)malloc(sizeof(bux_mutex_t));
if (!m) return NULL;
pthread_mutex_init(&m->mtx, NULL);
return m;
}
void bux_mutex_lock(void* handle) {
if (!handle) return;
bux_mutex_t* m = (bux_mutex_t*)handle;
pthread_mutex_lock(&m->mtx);
}
void bux_mutex_unlock(void* handle) {
if (!handle) return;
bux_mutex_t* m = (bux_mutex_t*)handle;
pthread_mutex_unlock(&m->mtx);
}
void bux_mutex_free(void* handle) {
if (!handle) return;
bux_mutex_t* m = (bux_mutex_t*)handle;
pthread_mutex_destroy(&m->mtx);
free(m);
}
void* bux_rwlock_new(void) {
bux_rwlock_t* rw = (bux_rwlock_t*)malloc(sizeof(bux_rwlock_t));
if (!rw) return NULL;
pthread_rwlock_init(&rw->rwl, NULL);
return rw;
}
void bux_rwlock_rdlock(void* handle) {
if (!handle) return;
bux_rwlock_t* rw = (bux_rwlock_t*)handle;
pthread_rwlock_rdlock(&rw->rwl);
}
void bux_rwlock_wrlock(void* handle) {
if (!handle) return;
bux_rwlock_t* rw = (bux_rwlock_t*)handle;
pthread_rwlock_wrlock(&rw->rwl);
}
void bux_rwlock_unlock(void* handle) {
if (!handle) return;
bux_rwlock_t* rw = (bux_rwlock_t*)handle;
pthread_rwlock_unlock(&rw->rwl);
}
void bux_rwlock_free(void* handle) {
if (!handle) return;
bux_rwlock_t* rw = (bux_rwlock_t*)handle;
pthread_rwlock_destroy(&rw->rwl);
free(rw);
}
/* ============================================================================
* Stackful Coroutines + Async Scheduler (Phase 8.3 true async)
* ============================================================================ */
#define BUX_CORO_STACK_SIZE (64 * 1024)
typedef struct bux_async_task {
ucontext_t ctx;
ucontext_t* caller_ctx;
uint8_t* stack;
int state; /* 0 = ready, 1 = running, 2 = done */
void (*entry)(void);
void* result; /* pointer to heap-allocated result */
int64_t sleep_until_ms;
struct bux_async_task* next;
} bux_async_task_t;
static bux_async_task_t* bux_ready_head = NULL;
static bux_async_task_t* bux_ready_tail = NULL;
static bux_async_task_t* bux_current_task = NULL;
static ucontext_t bux_scheduler_ctx;
static int bux_scheduler_running = 0;
static void bux_enqueue_ready(bux_async_task_t* task) {
task->next = NULL;
if (bux_ready_tail) {
bux_ready_tail->next = task;
} else {
bux_ready_head = task;
}
bux_ready_tail = task;
}
static bux_async_task_t* bux_dequeue_ready(void) {
bux_async_task_t* task = bux_ready_head;
if (task) {
bux_ready_head = task->next;
if (!bux_ready_head) bux_ready_tail = NULL;
}
return task;
}
static void bux_remove_from_ready(bux_async_task_t* target) {
bux_async_task_t* prev = NULL;
bux_async_task_t* curr = bux_ready_head;
while (curr) {
if (curr == target) {
if (prev) {
prev->next = curr->next;
} else {
bux_ready_head = curr->next;
}
if (bux_ready_tail == curr) {
bux_ready_tail = prev;
}
return;
}
prev = curr;
curr = curr->next;
}
}
static void bux_coro_trampoline(void) {
bux_async_task_t* self = bux_current_task;
if (self != NULL && self->entry != NULL) {
self->entry();
}
if (self != NULL) {
self->state = 2; /* done */
swapcontext(&self->ctx, &bux_scheduler_ctx);
}
}
void* bux_async_spawn(void (*func)(void)) {
bux_async_task_t* task = (bux_async_task_t*)malloc(sizeof(bux_async_task_t));
if (!task) {
fprintf(stderr, "bux runtime: out of memory (async spawn)\n");
abort();
}
task->stack = (uint8_t*)malloc(BUX_CORO_STACK_SIZE);
if (!task->stack) {
fprintf(stderr, "bux runtime: out of memory (coro stack)\n");
free(task);
abort();
}
task->state = 0;
task->next = NULL;
task->caller_ctx = NULL;
task->entry = func;
task->sleep_until_ms = 0;
getcontext(&task->ctx);
task->ctx.uc_stack.ss_sp = task->stack;
task->ctx.uc_stack.ss_size = BUX_CORO_STACK_SIZE;
task->ctx.uc_link = &bux_scheduler_ctx;
makecontext(&task->ctx, bux_coro_trampoline, 0);
bux_enqueue_ready(task);
return task;
}
void bux_async_yield(void) {
if (bux_current_task != NULL) {
bux_async_task_t* task = bux_current_task;
bux_current_task = NULL;
bux_enqueue_ready(task);
swapcontext(&task->ctx, &bux_scheduler_ctx);
}
}
void bux_async_run(void);
void* bux_async_await(void* handle) {
if (!handle) return NULL;
bux_async_task_t* target = (bux_async_task_t*)handle;
while (target->state != 2) {
if (bux_current_task != NULL) {
bux_async_yield();
} else {
if (!bux_scheduler_running) {
bux_async_run();
}
}
}
void* result = target->result;
bux_remove_from_ready(target);
free(target->stack);
free(target);
return result;
}
static int64_t bux_now_ms(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
void bux_async_run(void) {
if (bux_scheduler_running) return;
bux_scheduler_running = 1;
getcontext(&bux_scheduler_ctx);
while (bux_ready_head != NULL) {
bux_async_task_t* task = bux_dequeue_ready();
if (!task) break;
if (task->state == 2) {
/* Leave completed tasks in queue for await to clean up */
bux_enqueue_ready(task);
continue;
}
/* Check if task is still sleeping */
if (task->sleep_until_ms > 0) {
int64_t now = bux_now_ms();
if (now < task->sleep_until_ms) {
/* Re-queue and check next task */
bux_enqueue_ready(task);
/* If all tasks are sleeping, sleep the thread */
if (bux_ready_head == task && bux_ready_tail == task) {
int64_t delay = task->sleep_until_ms - now;
struct timespec ts;
ts.tv_sec = delay / 1000;
ts.tv_nsec = (delay % 1000) * 1000000;
nanosleep(&ts, NULL);
}
continue;
}
task->sleep_until_ms = 0;
}
task->state = 1;
bux_current_task = task;
swapcontext(&bux_scheduler_ctx, &task->ctx);
bux_current_task = NULL;
}
bux_scheduler_running = 0;
}
void bux_async_sleep(int64_t ms) {
if (ms > 0 && bux_current_task != NULL) {
bux_current_task->sleep_until_ms = bux_now_ms() + ms;
bux_async_yield();
}
}
void bux_async_return(void* value, size_t size) {
if (bux_current_task != NULL && value != NULL && size > 0) {
void* copy = malloc(size);
if (copy) memcpy(copy, value, size);
bux_current_task->result = copy;
}
}
void* bux_async_result(void* handle) {
if (!handle) return NULL;
bux_async_task_t* task = (bux_async_task_t*)handle;
return task->result;
}
/* ============================================================================
* OS primitives
* ============================================================================ */
#include <unistd.h>
const char* bux_getenv(const char* name) {
if (!name) return "";
const char* val = getenv(name);
return val ? val : "";
}
int bux_setenv(const char* name, const char* value) {
if (!name || !value) return -1;
return setenv(name, value, 1);
}
const char* bux_getcwd(void) {
static char buf[4096];
if (getcwd(buf, sizeof(buf))) {
return buf;
}
return "";
}
int bux_chdir(const char* path) {
if (!path) return -1;
return chdir(path);
}
/* ============================================================================
* Time primitives
* ============================================================================ */
int64_t bux_time_ms(void) {
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
return 0;
}
int64_t bux_time_us(void) {
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
return (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
return 0;
}
void bux_sleep_ms(int64_t ms) {
if (ms <= 0) return;
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
}
/* ============================================================================
* Process primitives
* ============================================================================ */
#include <stdio.h>
int bux_process_run(const char* cmd) {
if (!cmd) return -1;
return system(cmd);
}
char* bux_process_output(const char* cmd) {
if (!cmd) return NULL;
FILE* pipe = popen(cmd, "r");
if (!pipe) return NULL;
size_t cap = 1024;
size_t len = 0;
char* buf = (char*)malloc(cap);
if (!buf) { pclose(pipe); return NULL; }
int c;
while ((c = fgetc(pipe)) != EOF) {
if (len + 1 >= cap) {
cap *= 2;
char* new_buf = (char*)realloc(buf, cap);
if (!new_buf) { free(buf); pclose(pipe); return NULL; }
buf = new_buf;
}
buf[len++] = (char)c;
}
buf[len] = '\0';
pclose(pipe);
return buf;
}
/* ============================================================================
* Network / Socket primitives
* ============================================================================ */
int bux_socket_create(void) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
return fd;
}
int bux_socket_reuse(int fd) {
int opt = 1;
return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
int bux_socket_bind(int fd, const char* addr, int port) {
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons((uint16_t)port);
if (addr == NULL || addr[0] == '\0') {
sa.sin_addr.s_addr = INADDR_ANY;
} else {
if (inet_pton(AF_INET, addr, &sa.sin_addr) <= 0) {
return -1;
}
}
return bind(fd, (struct sockaddr*)&sa, sizeof(sa));
}
int bux_socket_listen(int fd, int backlog) {
return listen(fd, backlog);
}
int bux_socket_accept(int fd) {
struct sockaddr_in sa;
socklen_t len = sizeof(sa);
return accept(fd, (struct sockaddr*)&sa, &len);
}
int bux_socket_connect(int fd, const char* addr, int port) {
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons((uint16_t)port);
if (inet_pton(AF_INET, addr, &sa.sin_addr) <= 0) {
return -1;
}
return connect(fd, (struct sockaddr*)&sa, sizeof(sa));
}
int bux_socket_send(int fd, const char* data, int len) {
if (!data || len <= 0) return 0;
return (int)send(fd, data, (size_t)len, 0);
}
BuxString bux_socket_recv(int fd, int max_len) {
BuxString result;
if (max_len <= 0) {
result.data = "";
result.len = 0;
return result;
}
char* buf = (char*)bux_alloc((size_t)max_len + 1);
ssize_t n = recv(fd, buf, (size_t)max_len, 0);
if (n <= 0) {
result.data = "";
result.len = 0;
return result;
}
buf[n] = '\0';
result.data = buf;
result.len = (size_t)n;
return result;
}
int bux_socket_close(int fd) {
return close(fd);
}
const char* bux_socket_error(void) {
return strerror(errno);
}
/* ============================================================================
* Test / Assert primitives
* ============================================================================ */
void bux_exit(int code) {
exit(code);
}
void bux_assert(int cond, const char* file, int line, const char* expr) {
if (!cond) {
fprintf(stderr, "ASSERT FAILED: %s at %s:%d\n", expr, file, line);
exit(1);
}
}
/* ============================================================================
* Cryptography primitives (OpenSSL)
* ============================================================================ */
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
void bux_sha256(const char* data, int len, unsigned char* out) {
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
EVP_DigestUpdate(ctx, data, (size_t)len);
EVP_DigestFinal_ex(ctx, out, NULL);
EVP_MD_CTX_free(ctx);
}
void bux_hmac_sha256(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) {
unsigned int outlen = 32;
HMAC(EVP_sha256(), key, keylen, (const unsigned char*)msg, (size_t)msglen, out, &outlen);
}
int bux_random_bytes(unsigned char* buf, int len) {
return RAND_bytes(buf, len);
}
char* bux_base64_encode(const unsigned char* in, int inlen) {
int outlen = 4 * ((inlen + 2) / 3);
char* out = (char*)bux_alloc(outlen + 1);
int elen = EVP_EncodeBlock((unsigned char*)out, in, inlen);
out[elen] = '\0';
return out;
}
char* bux_base64_decode(const char* in, int inlen, int* outlen) {
int maxlen = 3 * inlen / 4;
char* out = (char*)bux_alloc(maxlen + 1);
*outlen = EVP_DecodeBlock((unsigned char*)out, (const unsigned char*)in, inlen);
if (*outlen < 0) {
*outlen = 0;
out[0] = '\0';
}
return out;
}
char* bux_bytes_to_hex(const unsigned char* data, int len) {
char* out = (char*)bux_alloc((size_t)len * 2 + 1);
const char* hex = "0123456789abcdef";
for (int i = 0; i < len; i++) {
out[i * 2] = hex[(data[i] >> 4) & 0x0F];
out[i * 2 + 1] = hex[data[i] & 0x0F];
}
out[len * 2] = '\0';
return out;
}
/* ============================================================================
* Extended cryptography primitives (OpenSSL)
* ============================================================================ */
static void bux_digest(const EVP_MD* md, const char* data, int len, unsigned char* out) {
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, md, NULL);
EVP_DigestUpdate(ctx, data, (size_t)len);
EVP_DigestFinal_ex(ctx, out, NULL);
EVP_MD_CTX_free(ctx);
}
void bux_sha1(const char* data, int len, unsigned char* out) {
bux_digest(EVP_sha1(), data, len, out);
}
void bux_sha384(const char* data, int len, unsigned char* out) {
bux_digest(EVP_sha384(), data, len, out);
}
void bux_sha512(const char* data, int len, unsigned char* out) {
bux_digest(EVP_sha512(), data, len, out);
}
/* --- HMAC --- */
void bux_hmac_sha384(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) {
unsigned int outlen = 48;
HMAC(EVP_sha384(), key, keylen, (const unsigned char*)msg, (size_t)msglen, out, &outlen);
}
void bux_hmac_sha512(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) {
unsigned int outlen = 64;
HMAC(EVP_sha512(), key, keylen, (const unsigned char*)msg, (size_t)msglen, out, &outlen);
}
/* --- Base64URL --- */
static const char b64url_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
char* bux_base64url_encode(const unsigned char* in, int inlen) {
int outlen = 4 * ((inlen + 2) / 3);
char* out = (char*)bux_alloc(outlen + 1);
int j = 0;
for (int i = 0; i < inlen; i += 3) {
int a = in[i];
int b = (i + 1 < inlen) ? in[i + 1] : 0;
int c = (i + 2 < inlen) ? in[i + 2] : 0;
out[j++] = b64url_table[(a >> 2) & 0x3F];
out[j++] = b64url_table[((a << 4) | (b >> 4)) & 0x3F];
if (i + 1 < inlen)
out[j++] = b64url_table[((b << 2) | (c >> 6)) & 0x3F];
if (i + 2 < inlen)
out[j++] = b64url_table[c & 0x3F];
}
out[j] = '\0';
return out;
}
static int b64url_char_val(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '-') return 62;
if (c == '_') return 63;
return -1;
}
char* bux_base64url_decode(const char* in, int inlen, int* outlen) {
int maxlen = 3 * inlen / 4 + 1;
char* out = (char*)bux_alloc(maxlen);
int j = 0;
int buf = 0, bits = 0;
for (int i = 0; i < inlen; i++) {
int val = b64url_char_val(in[i]);
if (val < 0) continue;
buf = (buf << 6) | val;
bits += 6;
if (bits >= 8) {
bits -= 8;
out[j++] = (buf >> bits) & 0xFF;
}
}
out[j] = '\0';
*outlen = j;
return out;
}
/* --- AES-256-CBC --- */
static int aes_cbc_ctx_encrypt(const unsigned char* in, int inlen,
unsigned char* out, const unsigned char* key,
const unsigned char* iv, int encrypt) {
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv, encrypt);
int outlen = 0, tmplen = 0;
EVP_CipherUpdate(ctx, out, &tmplen, in, inlen);
outlen = tmplen;
EVP_CipherFinal_ex(ctx, out + outlen, &tmplen);
outlen += tmplen;
EVP_CIPHER_CTX_free(ctx);
return outlen;
}
char* bux_aes_256_cbc_encrypt(const char* plain, int plainlen,
const char* key, const char* iv, int* outlen) {
int maxlen = plainlen + EVP_MAX_BLOCK_LENGTH;
char* out = (char*)bux_alloc(maxlen + 1);
*outlen = aes_cbc_ctx_encrypt((const unsigned char*)plain, plainlen,
(unsigned char*)out,
(const unsigned char*)key,
(const unsigned char*)iv, 1);
out[*outlen] = '\0';
return out;
}
char* bux_aes_256_cbc_decrypt(const char* cipher, int cipherlen,
const char* key, const char* iv, int* outlen) {
char* out = (char*)bux_alloc(cipherlen + 1);
*outlen = aes_cbc_ctx_encrypt((const unsigned char*)cipher, cipherlen,
(unsigned char*)out,
(const unsigned char*)key,
(const unsigned char*)iv, 0);
out[*outlen] = '\0';
return out;
}
/* --- AES-256-GCM --- */
char* bux_aes_256_gcm_encrypt(const char* plain, int plainlen,
const char* key, const char* iv,
unsigned char* tag, int* outlen) {
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (const unsigned char*)key,
(const unsigned char*)iv);
int maxlen = plainlen + EVP_MAX_BLOCK_LENGTH;
char* out = (char*)bux_alloc(maxlen);
int tmplen = 0;
EVP_EncryptUpdate(ctx, (unsigned char*)out, &tmplen,
(const unsigned char*)plain, plainlen);
*outlen = tmplen;
EVP_EncryptFinal_ex(ctx, (unsigned char*)out + *outlen, &tmplen);
*outlen += tmplen;
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
EVP_CIPHER_CTX_free(ctx);
return out;
}
char* bux_aes_256_gcm_decrypt(const char* cipher, int cipherlen,
const char* key, const char* iv,
const char* tag, int* outlen) {
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (const unsigned char*)key,
(const unsigned char*)iv);
char* out = (char*)bux_alloc(cipherlen);
int tmplen = 0;
EVP_DecryptUpdate(ctx, (unsigned char*)out, &tmplen,
(const unsigned char*)cipher, cipherlen);
*outlen = tmplen;
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void*)tag);
int ret = EVP_DecryptFinal_ex(ctx, (unsigned char*)out + *outlen, &tmplen);
EVP_CIPHER_CTX_free(ctx);
if (ret <= 0) { *outlen = 0; out[0] = '\0'; return out; }
*outlen += tmplen;
out[*outlen] = '\0';
return out;
}
/* --- RSA PKCS#1 v1.5 sign / verify --- */
static EVP_PKEY* bux_load_private_key(const char* pem, int len) {
BIO* bio = BIO_new_mem_buf(pem, len);
if (!bio) return NULL;
EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
BIO_free(bio);
return pkey;
}
static EVP_PKEY* bux_load_public_key(const char* pem, int len) {
BIO* bio = BIO_new_mem_buf(pem, len);
if (!bio) return NULL;
EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
BIO_free(bio);
return pkey;
}
static char* bux_rsa_sign_evp(const EVP_MD* md, const char* pem_key, int keylen,
const char* data, int datalen, int* siglen) {
EVP_PKEY* pkey = bux_load_private_key(pem_key, keylen);
if (!pkey) { *siglen = 0; return (char*)bux_alloc(1); }
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_SignInit_ex(ctx, md, NULL);
EVP_SignUpdate(ctx, data, (size_t)datalen);
unsigned int slen = (unsigned int)EVP_PKEY_size(pkey);
unsigned char* sig = (unsigned char*)bux_alloc(slen + 1);
EVP_SignFinal(ctx, sig, &slen, pkey);
*siglen = (int)slen;
sig[*siglen] = '\0';
EVP_MD_CTX_free(ctx);
EVP_PKEY_free(pkey);
return (char*)sig;
}
static int bux_rsa_verify_evp(const EVP_MD* md, const char* pem_key, int keylen,
const char* data, int datalen,
const char* sig, int siglen) {
EVP_PKEY* pkey = bux_load_public_key(pem_key, keylen);
if (!pkey) return 0;
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_VerifyInit_ex(ctx, md, NULL);
EVP_VerifyUpdate(ctx, data, (size_t)datalen);
int ret = EVP_VerifyFinal(ctx, (const unsigned char*)sig, (size_t)siglen, pkey);
EVP_MD_CTX_free(ctx);
EVP_PKEY_free(pkey);
return ret;
}
char* bux_rsa_sign_sha256(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
return bux_rsa_sign_evp(EVP_sha256(), pem, keylen, data, datalen, siglen);
}
char* bux_rsa_sign_sha384(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
return bux_rsa_sign_evp(EVP_sha384(), pem, keylen, data, datalen, siglen);
}
char* bux_rsa_sign_sha512(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
return bux_rsa_sign_evp(EVP_sha512(), pem, keylen, data, datalen, siglen);
}
int bux_rsa_verify_sha256(const char* pem, int keylen, const char* data, int datalen,
const char* sig, int siglen) {
return bux_rsa_verify_evp(EVP_sha256(), pem, keylen, data, datalen, sig, siglen);
}
int bux_rsa_verify_sha384(const char* pem, int keylen, const char* data, int datalen,
const char* sig, int siglen) {
return bux_rsa_verify_evp(EVP_sha384(), pem, keylen, data, datalen, sig, siglen);
}
int bux_rsa_verify_sha512(const char* pem, int keylen, const char* data, int datalen,
const char* sig, int siglen) {
return bux_rsa_verify_evp(EVP_sha512(), pem, keylen, data, datalen, sig, siglen);
}
/* --- ECDSA P-256 / P-384 --- */
static char* bux_ecdsa_sign_evp(const EVP_MD* md, const char* pem, int keylen,
const char* data, int datalen, int* siglen) {
EVP_PKEY* pkey = bux_load_private_key(pem, keylen);
if (!pkey) { *siglen = 0; return (char*)bux_alloc(1); }
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_SignInit_ex(ctx, md, NULL);
EVP_SignUpdate(ctx, data, (size_t)datalen);
unsigned int slen = (unsigned int)EVP_PKEY_size(pkey);
unsigned char* sig = (unsigned char*)bux_alloc(slen + 1);
EVP_SignFinal(ctx, sig, &slen, pkey);
*siglen = (int)slen;
sig[*siglen] = '\0';
EVP_MD_CTX_free(ctx);
EVP_PKEY_free(pkey);
return (char*)sig;
}
static int bux_ecdsa_verify_evp(const EVP_MD* md, const char* pem, int keylen,
const char* data, int datalen,
const char* sig, int siglen) {
EVP_PKEY* pkey = bux_load_public_key(pem, keylen);
if (!pkey) return 0;
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_VerifyInit_ex(ctx, md, NULL);
EVP_VerifyUpdate(ctx, data, (size_t)datalen);
int ret = EVP_VerifyFinal(ctx, (const unsigned char*)sig, (size_t)siglen, pkey);
EVP_MD_CTX_free(ctx);
EVP_PKEY_free(pkey);
return ret;
}
char* bux_ecdsa_sign_p256(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
return bux_ecdsa_sign_evp(EVP_sha256(), pem, keylen, data, datalen, siglen);
}
char* bux_ecdsa_sign_p384(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
return bux_ecdsa_sign_evp(EVP_sha384(), pem, keylen, data, datalen, siglen);
}
int bux_ecdsa_verify_p256(const char* pem, int keylen, const char* data, int datalen,
const char* sig, int siglen) {
return bux_ecdsa_verify_evp(EVP_sha256(), pem, keylen, data, datalen, sig, siglen);
}
int bux_ecdsa_verify_p384(const char* pem, int keylen, const char* data, int datalen,
const char* sig, int siglen) {
return bux_ecdsa_verify_evp(EVP_sha384(), pem, keylen, data, datalen, sig, siglen);
}
/* --- Ed25519 (OpenSSL 1.1.1+) --- */
int bux_ed25519_keypair(unsigned char* pub, unsigned char* priv) {
EVP_PKEY* pkey = NULL;
EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
if (!pctx) return 0;
if (EVP_PKEY_keygen_init(pctx) <= 0 ||
EVP_PKEY_keygen(pctx, &pkey) <= 0) {
EVP_PKEY_CTX_free(pctx);
return 0;
}
EVP_PKEY_CTX_free(pctx);
size_t pub_len = 32, priv_len = 32;
EVP_PKEY_get_raw_public_key(pkey, pub, &pub_len);
EVP_PKEY_get_raw_private_key(pkey, priv, &priv_len);
EVP_PKEY_free(pkey);
return 1;
}
int bux_ed25519_sign(const char* priv, const char* data, int datalen, unsigned char* sig) {
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
(const unsigned char*)priv, 32);
if (!pkey) return 0;
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
int ret = 0;
if (EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) > 0) {
size_t siglen = 64;
EVP_DigestSign(ctx, sig, &siglen, (const unsigned char*)data, (size_t)datalen);
ret = 1;
}
EVP_MD_CTX_free(ctx);
EVP_PKEY_free(pkey);
return ret;
}
int bux_ed25519_verify(const char* pub, const char* sig, const char* data, int datalen) {
EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
(const unsigned char*)pub, 32);
if (!pkey) return 0;
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
int ret = 0;
if (EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey) > 0) {
ret = EVP_DigestVerify(ctx, (const unsigned char*)sig, 64,
(const unsigned char*)data, (size_t)datalen);
ret = (ret == 1) ? 1 : 0;
}
EVP_MD_CTX_free(ctx);
EVP_PKEY_free(pkey);
return ret;
}