runtime: add dharma-required functions to el_runtime.c and runtime/*.el
Add the following functions that dharma registry calls but were missing from the El runtime: el_runtime.c (consumed by the old build system via released SDK): - list_len, list_get — aliases for el_list_len/el_list_get (handlers.el) - json_array_push — append pre-encoded element to JSON array string - now_millis, unix_timestamp_ms, time_now_ms — ms-since-epoch aliases - log_info, log_warn — structured stderr log helpers - config — reads config from environment (alias for getenv) - http_patch — HTTP PATCH with Content-Type: application/json - http_post_engram — HTTP POST with optional X-API-Key header - http_get_engram — HTTP GET with optional X-API-Key header - str_to_bytes — encode string as JSON byte array [72,101,...] - bytes_to_str — decode JSON byte array back to string - hash_sha256 — SHA-256 hex digest using built-in sha256 impl runtime/*.el (consumed by the new build system): - http.el: http_patch, http_post_engram, http_get_engram - time.el: now_millis, unix_timestamp_ms, time_now_ms - env.el: config, log_info, log_warn, list_len, list_get - json.el: json_array_push, bytes_to_str - string.el: str_to_bytes, hash_sha256 (via __sha256_hex seed) el_seed.h / el_seed.c: - __sha256_hex primitive with self-contained SHA-256 implementation
This commit is contained in:
@@ -10605,3 +10605,322 @@ void __channel_close(el_val_t ch_v) {
|
||||
pthread_mutex_unlock(&ch->mu);
|
||||
}
|
||||
|
||||
/* ── DHARMA runtime additions ────────────────────────────────────────────────
|
||||
*
|
||||
* Functions required by the dharma registry service. Added here so the
|
||||
* released el_runtime.c includes them without requiring dharma to bundle
|
||||
* its own stubs.
|
||||
*
|
||||
* Functions added:
|
||||
* list_len — alias for el_list_len (used in handlers.el)
|
||||
* list_get — alias for el_list_get (used in handlers.el)
|
||||
* json_array_push — append a pre-encoded JSON element to a JSON array string
|
||||
* now_millis — milliseconds since Unix epoch (alias for time_now)
|
||||
* unix_timestamp_ms — same as now_millis (alias)
|
||||
* time_now_ms — same as now_millis (alias)
|
||||
* log_info — stderr structured log at INFO level
|
||||
* log_warn — stderr structured log at WARN level
|
||||
* config — reads a config value from the environment
|
||||
* http_patch — HTTP PATCH with JSON Content-Type
|
||||
* http_post_engram — HTTP POST with optional X-API-Key header
|
||||
* http_get_engram — HTTP GET with optional X-API-Key header
|
||||
* str_to_bytes — encode a string as a JSON array of byte values
|
||||
* bytes_to_str — decode a JSON array of byte values back to a string
|
||||
* hash_sha256 — SHA-256 hex digest of a string
|
||||
*/
|
||||
|
||||
/* list_len — return the number of elements in a list. */
|
||||
el_val_t list_len(el_val_t list) {
|
||||
return el_list_len(list);
|
||||
}
|
||||
|
||||
/* list_get — return the element at index i in a list. */
|
||||
el_val_t list_get(el_val_t list, el_val_t index) {
|
||||
return el_list_get(list, index);
|
||||
}
|
||||
|
||||
/* json_array_push — append element (a pre-encoded JSON fragment, e.g. "\"foo\""
|
||||
* or "42") to the JSON array string arr. Returns a new JSON array string.
|
||||
* Example: json_array_push("[]", "\"alice\"") -> "[\"alice\"]"
|
||||
* json_array_push("[\"alice\"]", "\"bob\"") -> "[\"alice\",\"bob\"]" */
|
||||
el_val_t json_array_push(el_val_t arr_v, el_val_t elem_v) {
|
||||
const char* arr = EL_CSTR(arr_v);
|
||||
const char* elem = EL_CSTR(elem_v);
|
||||
if (!arr || !*arr) arr = "[]";
|
||||
if (!elem || !*elem) elem = "null";
|
||||
|
||||
/* Trim whitespace, find the closing ']'. */
|
||||
const char* p = arr;
|
||||
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
|
||||
if (*p != '[') {
|
||||
/* Not an array — return a single-element array. */
|
||||
size_t n = strlen(elem) + 4;
|
||||
char* out = el_strbuf(n);
|
||||
snprintf(out, n, "[%s]", elem);
|
||||
return el_wrap_str(out);
|
||||
}
|
||||
size_t arr_len = strlen(arr);
|
||||
size_t elem_len = strlen(elem);
|
||||
|
||||
/* Walk from the end to find the matching ']'. */
|
||||
const char* end = arr + arr_len - 1;
|
||||
while (end > p && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) end--;
|
||||
if (*end != ']') {
|
||||
/* Malformed — wrap elem in a new array. */
|
||||
size_t n = elem_len + 4;
|
||||
char* out = el_strbuf(n);
|
||||
snprintf(out, n, "[%s]", elem);
|
||||
return el_wrap_str(out);
|
||||
}
|
||||
|
||||
/* Content between '[' and ']'. */
|
||||
const char* inner_start = p + 1;
|
||||
const char* inner_end = end; /* points AT ']' */
|
||||
/* Check if the array is empty (only whitespace between brackets). */
|
||||
const char* q = inner_start;
|
||||
while (q < inner_end && (*q == ' ' || *q == '\t' || *q == '\n' || *q == '\r')) q++;
|
||||
int empty = (q == inner_end);
|
||||
|
||||
/* Build: prefix + (comma if non-empty) + elem + "]" */
|
||||
size_t prefix_len = (size_t)(inner_end - arr); /* up to but not including ']' */
|
||||
size_t sep_len = empty ? 0 : 1; /* "," if non-empty */
|
||||
size_t out_len = prefix_len + sep_len + elem_len + 2; /* +"]" + NUL */
|
||||
char* out = el_strbuf(out_len);
|
||||
memcpy(out, arr, prefix_len);
|
||||
if (!empty) out[prefix_len] = ',';
|
||||
memcpy(out + prefix_len + sep_len, elem, elem_len);
|
||||
out[prefix_len + sep_len + elem_len] = ']';
|
||||
out[prefix_len + sep_len + elem_len + 1] = '\0';
|
||||
return el_wrap_str(out);
|
||||
}
|
||||
|
||||
/* now_millis — milliseconds since Unix epoch. */
|
||||
el_val_t now_millis(void) {
|
||||
return time_now();
|
||||
}
|
||||
|
||||
/* unix_timestamp_ms — same as now_millis. */
|
||||
el_val_t unix_timestamp_ms(void) {
|
||||
return time_now();
|
||||
}
|
||||
|
||||
/* time_now_ms — same as now_millis. */
|
||||
el_val_t time_now_ms(void) {
|
||||
return time_now();
|
||||
}
|
||||
|
||||
/* log_info — write a structured [INFO] line to stderr. */
|
||||
void log_info(el_val_t msg_v) {
|
||||
const char* msg = EL_CSTR(msg_v);
|
||||
fprintf(stderr, "[INFO] %s\n", msg ? msg : "");
|
||||
}
|
||||
|
||||
/* log_warn — write a structured [WARN] line to stderr. */
|
||||
void log_warn(el_val_t msg_v) {
|
||||
const char* msg = EL_CSTR(msg_v);
|
||||
fprintf(stderr, "[WARN] %s\n", msg ? msg : "");
|
||||
}
|
||||
|
||||
/* config — read a configuration value from the environment.
|
||||
* Returns "" if the variable is not set (same as __env_get). */
|
||||
el_val_t config(el_val_t key_v) {
|
||||
const char* key = EL_CSTR(key_v);
|
||||
if (!key || !*key) return EL_STR("");
|
||||
const char* val = getenv(key);
|
||||
if (!val) return EL_STR("");
|
||||
return el_wrap_str(el_strdup(val));
|
||||
}
|
||||
|
||||
/* http_patch — HTTP PATCH request with Content-Type: application/json.
|
||||
* Returns the response body (same error convention as http_post_json). */
|
||||
el_val_t http_patch(el_val_t url_v, el_val_t body_v) {
|
||||
const char* url = EL_CSTR(url_v);
|
||||
const char* body = EL_CSTR(body_v);
|
||||
if (!url || !*url) return http_error_json("empty url");
|
||||
CURL* c = curl_easy_init();
|
||||
if (!c) return http_error_json("curl_easy_init failed");
|
||||
HttpBuf rb; httpbuf_init(&rb);
|
||||
char errbuf[CURL_ERROR_SIZE]; errbuf[0] = '\0';
|
||||
struct curl_slist* h = NULL;
|
||||
h = curl_slist_append(h, "Content-Type: application/json");
|
||||
curl_easy_setopt(c, CURLOPT_URL, url);
|
||||
curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "PATCH");
|
||||
curl_easy_setopt(c, CURLOPT_POSTFIELDS, body ? body : "");
|
||||
curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)(body ? strlen(body) : 0));
|
||||
curl_easy_setopt(c, CURLOPT_HTTPHEADER, h);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, http_write_cb);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEDATA, &rb);
|
||||
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, el_http_timeout_ms());
|
||||
curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_ERRORBUFFER, errbuf);
|
||||
curl_easy_setopt(c, CURLOPT_USERAGENT, "el-runtime/1.0");
|
||||
CURLcode rc = curl_easy_perform(c);
|
||||
curl_slist_free_all(h);
|
||||
curl_easy_cleanup(c);
|
||||
if (rc != CURLE_OK) {
|
||||
free(rb.data);
|
||||
const char* m = errbuf[0] ? errbuf : curl_easy_strerror(rc);
|
||||
return http_error_json(m);
|
||||
}
|
||||
return el_wrap_str(rb.data);
|
||||
}
|
||||
|
||||
/* http_post_engram — HTTP POST with optional X-API-Key header.
|
||||
* If key is "" no authentication header is sent. */
|
||||
el_val_t http_post_engram(el_val_t url_v, el_val_t key_v, el_val_t body_v) {
|
||||
const char* url = EL_CSTR(url_v);
|
||||
const char* key = EL_CSTR(key_v);
|
||||
const char* body = EL_CSTR(body_v);
|
||||
if (!url || !*url) return http_error_json("empty url");
|
||||
CURL* c = curl_easy_init();
|
||||
if (!c) return http_error_json("curl_easy_init failed");
|
||||
HttpBuf rb; httpbuf_init(&rb);
|
||||
char errbuf[CURL_ERROR_SIZE]; errbuf[0] = '\0';
|
||||
struct curl_slist* h = NULL;
|
||||
h = curl_slist_append(h, "Content-Type: application/json");
|
||||
if (key && *key) {
|
||||
size_t n = strlen(key) + 32;
|
||||
char* hdr = malloc(n);
|
||||
snprintf(hdr, n, "X-API-Key: %s", key);
|
||||
h = curl_slist_append(h, hdr);
|
||||
free(hdr);
|
||||
}
|
||||
curl_easy_setopt(c, CURLOPT_URL, url);
|
||||
curl_easy_setopt(c, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_POSTFIELDS, body ? body : "");
|
||||
curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)(body ? strlen(body) : 0));
|
||||
curl_easy_setopt(c, CURLOPT_HTTPHEADER, h);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, http_write_cb);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEDATA, &rb);
|
||||
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, el_http_timeout_ms());
|
||||
curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_ERRORBUFFER, errbuf);
|
||||
curl_easy_setopt(c, CURLOPT_USERAGENT, "el-runtime/1.0");
|
||||
CURLcode rc = curl_easy_perform(c);
|
||||
curl_slist_free_all(h);
|
||||
curl_easy_cleanup(c);
|
||||
if (rc != CURLE_OK) {
|
||||
free(rb.data);
|
||||
const char* m = errbuf[0] ? errbuf : curl_easy_strerror(rc);
|
||||
return http_error_json(m);
|
||||
}
|
||||
return el_wrap_str(rb.data);
|
||||
}
|
||||
|
||||
/* http_get_engram — HTTP GET with optional X-API-Key header. */
|
||||
el_val_t http_get_engram(el_val_t url_v, el_val_t key_v) {
|
||||
const char* url = EL_CSTR(url_v);
|
||||
const char* key = EL_CSTR(key_v);
|
||||
if (!url || !*url) return http_error_json("empty url");
|
||||
CURL* c = curl_easy_init();
|
||||
if (!c) return http_error_json("curl_easy_init failed");
|
||||
HttpBuf rb; httpbuf_init(&rb);
|
||||
char errbuf[CURL_ERROR_SIZE]; errbuf[0] = '\0';
|
||||
struct curl_slist* h = NULL;
|
||||
if (key && *key) {
|
||||
size_t n = strlen(key) + 32;
|
||||
char* hdr = malloc(n);
|
||||
snprintf(hdr, n, "X-API-Key: %s", key);
|
||||
h = curl_slist_append(h, hdr);
|
||||
free(hdr);
|
||||
}
|
||||
curl_easy_setopt(c, CURLOPT_URL, url);
|
||||
curl_easy_setopt(c, CURLOPT_HTTPGET, 1L);
|
||||
if (h) curl_easy_setopt(c, CURLOPT_HTTPHEADER, h);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, http_write_cb);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEDATA, &rb);
|
||||
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, el_http_timeout_ms());
|
||||
curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_ERRORBUFFER, errbuf);
|
||||
curl_easy_setopt(c, CURLOPT_USERAGENT, "el-runtime/1.0");
|
||||
CURLcode rc = curl_easy_perform(c);
|
||||
if (h) curl_slist_free_all(h);
|
||||
curl_easy_cleanup(c);
|
||||
if (rc != CURLE_OK) {
|
||||
free(rb.data);
|
||||
const char* m = errbuf[0] ? errbuf : curl_easy_strerror(rc);
|
||||
return http_error_json(m);
|
||||
}
|
||||
return el_wrap_str(rb.data);
|
||||
}
|
||||
|
||||
/* str_to_bytes — encode a string as a JSON array of unsigned byte values.
|
||||
* "hello" -> "[104,101,108,108,111]"
|
||||
* Used by db.el to store binary content in Engram JSON nodes. */
|
||||
el_val_t str_to_bytes(el_val_t sv) {
|
||||
const char* s = EL_CSTR(sv);
|
||||
if (!s || !*s) return el_wrap_str(el_strdup("[]"));
|
||||
size_t n = strlen(s);
|
||||
/* Worst case: each byte is 3 digits + comma = 4 chars, plus "[]" + NUL. */
|
||||
char* out = el_strbuf(n * 4 + 3);
|
||||
size_t pos = 0;
|
||||
out[pos++] = '[';
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
unsigned char b = (unsigned char)s[i];
|
||||
if (i > 0) out[pos++] = ',';
|
||||
/* Write decimal representation of b. */
|
||||
if (b >= 100) {
|
||||
out[pos++] = (char)('0' + b / 100);
|
||||
out[pos++] = (char)('0' + (b / 10) % 10);
|
||||
out[pos++] = (char)('0' + b % 10);
|
||||
} else if (b >= 10) {
|
||||
out[pos++] = (char)('0' + b / 10);
|
||||
out[pos++] = (char)('0' + b % 10);
|
||||
} else {
|
||||
out[pos++] = (char)('0' + b);
|
||||
}
|
||||
}
|
||||
out[pos++] = ']';
|
||||
out[pos] = '\0';
|
||||
return el_wrap_str(out);
|
||||
}
|
||||
|
||||
/* bytes_to_str — decode a JSON array of integer byte values back to a string.
|
||||
* "[104,101,108,108,111]" -> "hello"
|
||||
* Inverse of str_to_bytes. */
|
||||
el_val_t bytes_to_str(el_val_t arr_v) {
|
||||
const char* s = EL_CSTR(arr_v);
|
||||
if (!s) return el_wrap_str(el_strdup(""));
|
||||
/* Skip whitespace, expect '['. */
|
||||
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
|
||||
if (*s != '[') return el_wrap_str(el_strdup(""));
|
||||
s++;
|
||||
|
||||
/* Count elements to size the output buffer. */
|
||||
int64_t n = (int64_t)json_array_len(arr_v);
|
||||
if (n <= 0) return el_wrap_str(el_strdup(""));
|
||||
|
||||
char* out = el_strbuf((size_t)n + 1);
|
||||
size_t pos = 0;
|
||||
|
||||
/* Walk the array, parse each integer, store as a byte. */
|
||||
while (*s) {
|
||||
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
|
||||
if (*s == ']' || *s == '\0') break;
|
||||
/* Parse decimal integer. */
|
||||
char* end_ptr;
|
||||
long v = strtol(s, &end_ptr, 10);
|
||||
if (end_ptr == s) break; /* parse failure */
|
||||
s = end_ptr;
|
||||
if (v >= 0 && v <= 255) out[pos++] = (char)(unsigned char)v;
|
||||
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
|
||||
if (*s == ',') { s++; continue; }
|
||||
if (*s == ']' || *s == '\0') break;
|
||||
}
|
||||
out[pos] = '\0';
|
||||
return el_wrap_str(out);
|
||||
}
|
||||
|
||||
/* hash_sha256 — return the SHA-256 hex digest of a string.
|
||||
* Uses the built-in el_sha256_oneshot implementation (no OpenSSL required). */
|
||||
el_val_t hash_sha256(el_val_t sv) {
|
||||
const char* s = EL_CSTR(sv);
|
||||
if (!s) s = "";
|
||||
unsigned char digest[32];
|
||||
el_sha256_oneshot((const unsigned char*)s, strlen(s), digest);
|
||||
return el_hex_encode(digest, 32);
|
||||
}
|
||||
|
||||
|
||||
@@ -1097,3 +1097,109 @@ el_val_t __engram_list_layers_json(void) { return engram_list_
|
||||
el_val_t __engram_compile_layered_json(el_val_t intent, el_val_t depth) {
|
||||
return engram_compile_layered_json(intent, depth);
|
||||
}
|
||||
|
||||
/* ── Cryptographic hashing ────────────────────────────────────────────────── */
|
||||
/*
|
||||
* SHA-256 — self-contained implementation (no OpenSSL dependency).
|
||||
* Based on Brad Conte's public-domain reference implementation.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[64];
|
||||
uint32_t datalen;
|
||||
uint64_t bitlen;
|
||||
uint32_t state[8];
|
||||
} _seed_sha256_ctx;
|
||||
|
||||
static const uint32_t _seed_sha256_k[64] = {
|
||||
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
|
||||
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
|
||||
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
|
||||
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
|
||||
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
|
||||
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
|
||||
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
|
||||
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
|
||||
};
|
||||
|
||||
#define _SEED_ROTR32(x,n) (((x)>>(n))|((x)<<(32-(n))))
|
||||
#define _SEED_CH(x,y,z) (((x)&(y))^(~(x)&(z)))
|
||||
#define _SEED_MAJ(x,y,z) (((x)&(y))^((x)&(z))^((y)&(z)))
|
||||
#define _SEED_EP0(x) (_SEED_ROTR32(x,2)^_SEED_ROTR32(x,13)^_SEED_ROTR32(x,22))
|
||||
#define _SEED_EP1(x) (_SEED_ROTR32(x,6)^_SEED_ROTR32(x,11)^_SEED_ROTR32(x,25))
|
||||
#define _SEED_SIG0(x) (_SEED_ROTR32(x,7)^_SEED_ROTR32(x,18)^((x)>>3))
|
||||
#define _SEED_SIG1(x) (_SEED_ROTR32(x,17)^_SEED_ROTR32(x,19)^((x)>>10))
|
||||
|
||||
static void _seed_sha256_transform(_seed_sha256_ctx* ctx, const uint8_t* data) {
|
||||
uint32_t a,b,c,d,e,f,g,h,t1,t2,m[64];
|
||||
for (int i=0,j=0; i<16; i++,j+=4)
|
||||
m[i]=(uint32_t)(data[j]<<24)|(data[j+1]<<16)|(data[j+2]<<8)|data[j+3];
|
||||
for (int i=16; i<64; i++)
|
||||
m[i]=_SEED_SIG1(m[i-2])+m[i-7]+_SEED_SIG0(m[i-15])+m[i-16];
|
||||
a=ctx->state[0]; b=ctx->state[1]; c=ctx->state[2]; d=ctx->state[3];
|
||||
e=ctx->state[4]; f=ctx->state[5]; g=ctx->state[6]; h=ctx->state[7];
|
||||
for (int i=0; i<64; i++) {
|
||||
t1=h+_SEED_EP1(e)+_SEED_CH(e,f,g)+_seed_sha256_k[i]+m[i];
|
||||
t2=_SEED_EP0(a)+_SEED_MAJ(a,b,c);
|
||||
h=g; g=f; f=e; e=d+t1; d=c; c=b; b=a; a=t1+t2;
|
||||
}
|
||||
ctx->state[0]+=a; ctx->state[1]+=b; ctx->state[2]+=c; ctx->state[3]+=d;
|
||||
ctx->state[4]+=e; ctx->state[5]+=f; ctx->state[6]+=g; ctx->state[7]+=h;
|
||||
}
|
||||
|
||||
static void _seed_sha256_init(_seed_sha256_ctx* ctx) {
|
||||
ctx->datalen=0; ctx->bitlen=0;
|
||||
ctx->state[0]=0x6a09e667; ctx->state[1]=0xbb67ae85;
|
||||
ctx->state[2]=0x3c6ef372; ctx->state[3]=0xa54ff53a;
|
||||
ctx->state[4]=0x510e527f; ctx->state[5]=0x9b05688c;
|
||||
ctx->state[6]=0x1f83d9ab; ctx->state[7]=0x5be0cd19;
|
||||
}
|
||||
|
||||
static void _seed_sha256_update(_seed_sha256_ctx* ctx, const uint8_t* data, size_t len) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
ctx->data[ctx->datalen++] = data[i];
|
||||
if (ctx->datalen==64) { _seed_sha256_transform(ctx,ctx->data); ctx->bitlen+=512; ctx->datalen=0; }
|
||||
}
|
||||
}
|
||||
|
||||
static void _seed_sha256_final(_seed_sha256_ctx* ctx, uint8_t hash[32]) {
|
||||
uint32_t i=ctx->datalen;
|
||||
ctx->data[i++]=0x80;
|
||||
if (ctx->datalen<56) { while(i<56) ctx->data[i++]=0; }
|
||||
else { while(i<64) ctx->data[i++]=0; _seed_sha256_transform(ctx,ctx->data); memset(ctx->data,0,56); }
|
||||
ctx->bitlen+=ctx->datalen*8;
|
||||
ctx->data[63]=(uint8_t)(ctx->bitlen); ctx->data[62]=(uint8_t)(ctx->bitlen>>8);
|
||||
ctx->data[61]=(uint8_t)(ctx->bitlen>>16); ctx->data[60]=(uint8_t)(ctx->bitlen>>24);
|
||||
ctx->data[59]=(uint8_t)(ctx->bitlen>>32); ctx->data[58]=(uint8_t)(ctx->bitlen>>40);
|
||||
ctx->data[57]=(uint8_t)(ctx->bitlen>>48); ctx->data[56]=(uint8_t)(ctx->bitlen>>56);
|
||||
_seed_sha256_transform(ctx,ctx->data);
|
||||
for (i=0; i<4; i++) {
|
||||
hash[i] =(uint8_t)(ctx->state[0]>>(24-i*8));
|
||||
hash[i+4] =(uint8_t)(ctx->state[1]>>(24-i*8));
|
||||
hash[i+8] =(uint8_t)(ctx->state[2]>>(24-i*8));
|
||||
hash[i+12] =(uint8_t)(ctx->state[3]>>(24-i*8));
|
||||
hash[i+16] =(uint8_t)(ctx->state[4]>>(24-i*8));
|
||||
hash[i+20] =(uint8_t)(ctx->state[5]>>(24-i*8));
|
||||
hash[i+24] =(uint8_t)(ctx->state[6]>>(24-i*8));
|
||||
hash[i+28] =(uint8_t)(ctx->state[7]>>(24-i*8));
|
||||
}
|
||||
}
|
||||
|
||||
el_val_t __sha256_hex(el_val_t sv) {
|
||||
const char* s = EL_CSTR(sv);
|
||||
if (!s) s = "";
|
||||
_seed_sha256_ctx ctx;
|
||||
_seed_sha256_init(&ctx);
|
||||
_seed_sha256_update(&ctx, (const uint8_t*)s, strlen(s));
|
||||
uint8_t digest[32];
|
||||
_seed_sha256_final(&ctx, digest);
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
char* out = malloc(65);
|
||||
if (!out) return EL_STR("");
|
||||
for (int i=0; i<32; i++) {
|
||||
out[i*2] = hex[(digest[i]>>4)&0xf];
|
||||
out[i*2+1] = hex[digest[i]&0xf];
|
||||
}
|
||||
out[64] = '\0';
|
||||
return EL_STR(out);
|
||||
}
|
||||
|
||||
@@ -233,6 +233,12 @@ el_val_t __engram_stats_json(void);
|
||||
el_val_t __engram_list_layers_json(void);
|
||||
el_val_t __engram_compile_layered_json(el_val_t intent, el_val_t depth);
|
||||
|
||||
/* ── Cryptographic hashing ────────────────────────────────────────────────── */
|
||||
|
||||
/* __sha256_hex — return the SHA-256 hex digest of a string.
|
||||
* The returned string is 64 hex characters (lowercase). */
|
||||
el_val_t __sha256_hex(el_val_t s);
|
||||
|
||||
/* ── args init (called from main) ────────────────────────────────────────── */
|
||||
/* Store argc/argv for __args_json. Call once at the start of main(). */
|
||||
void el_seed_init_args(int argc, char** argv);
|
||||
|
||||
@@ -63,6 +63,34 @@ fn state_keys() -> String {
|
||||
return __state_keys()
|
||||
}
|
||||
|
||||
// ── DHARMA runtime helpers ─────────────────────────────────────────────────
|
||||
|
||||
// config — read a configuration value from the environment.
|
||||
// Returns "" if the variable is not set. Alias for env().
|
||||
fn config(key: String) -> String {
|
||||
return __env_get(key)
|
||||
}
|
||||
|
||||
// log_info — write an [INFO] log line to stdout.
|
||||
fn log_info(msg: String) {
|
||||
__println("[INFO] " + msg)
|
||||
}
|
||||
|
||||
// log_warn — write a [WARN] log line to stdout.
|
||||
fn log_warn(msg: String) {
|
||||
__println("[WARN] " + msg)
|
||||
}
|
||||
|
||||
// list_len — return the number of elements in a list. Alias for el_list_len.
|
||||
fn list_len(lst: [String]) -> Int {
|
||||
return el_list_len(lst)
|
||||
}
|
||||
|
||||
// list_get — return the element at index i in a list. Alias for el_list_get.
|
||||
fn list_get(lst: [String], i: Int) -> String {
|
||||
return el_list_get(lst, i)
|
||||
}
|
||||
|
||||
// ── UUID generation ────────────────────────────────────────────────────────
|
||||
|
||||
// uuid_new — generate a new random UUID v4.
|
||||
|
||||
@@ -170,6 +170,35 @@ fn http_response(status: Int, headers_json: String, body: String) -> String {
|
||||
return __http_response(status, headers_json, body)
|
||||
}
|
||||
|
||||
// ── HTTP client — PATCH ───────────────────────────────────────────────────────
|
||||
|
||||
// http_patch performs an HTTP PATCH request with Content-Type: application/json.
|
||||
fn http_patch(url: String, body: String) -> String {
|
||||
return __http_do("PATCH", url, body, "{\"Content-Type\":\"application/json\"}", el_http_timeout_ms())
|
||||
}
|
||||
|
||||
// ── HTTP client — Engram variants (optional API key) ──────────────────────────
|
||||
//
|
||||
// These are used by dharma's db.el to talk to Engram nodes.
|
||||
// The key parameter is the X-API-Key header value; pass "" for no auth.
|
||||
|
||||
// http_post_engram performs an HTTP POST with Content-Type: application/json
|
||||
// and an optional X-API-Key header. If key is "" no auth header is added.
|
||||
fn http_post_engram(url: String, key: String, body: String) -> String {
|
||||
if str_eq(key, "") {
|
||||
return __http_do("POST", url, body, "{\"Content-Type\":\"application/json\"}", el_http_timeout_ms())
|
||||
}
|
||||
return __http_do("POST", url, body, "{\"Content-Type\":\"application/json\",\"X-API-Key\":\"" + key + "\"}", el_http_timeout_ms())
|
||||
}
|
||||
|
||||
// http_get_engram performs an HTTP GET with an optional X-API-Key header.
|
||||
fn http_get_engram(url: String, key: String) -> String {
|
||||
if str_eq(key, "") {
|
||||
return __http_do("GET", url, "", "{}", el_http_timeout_ms())
|
||||
}
|
||||
return __http_do("GET", url, "", "{\"X-API-Key\":\"" + key + "\"}", el_http_timeout_ms())
|
||||
}
|
||||
|
||||
// ── SSE — Server-Sent Events streaming ───────────────────────────────────────
|
||||
//
|
||||
// Usage pattern for an SSE handler:
|
||||
|
||||
@@ -157,6 +157,24 @@ fn json_build_array(items: [String]) -> String {
|
||||
return result + "]"
|
||||
}
|
||||
|
||||
// json_array_push — append a pre-encoded JSON element to a JSON array string.
|
||||
// elem must be a valid JSON fragment (e.g. "\"foo\"" or "42").
|
||||
// Returns a new JSON array string with elem appended.
|
||||
// Example: json_array_push("[]", "\"alice\"") -> "[\"alice\"]"
|
||||
fn json_array_push(arr: String, elem: String) -> String {
|
||||
let n: Int = json_array_len(arr)
|
||||
if n == 0 {
|
||||
return "[" + elem + "]"
|
||||
}
|
||||
// arr ends with ']'; insert before it
|
||||
let inner_end: Int = str_last_index_of(arr, "]")
|
||||
if inner_end < 0 {
|
||||
return "[" + elem + "]"
|
||||
}
|
||||
let prefix: String = str_slice(arr, 0, inner_end)
|
||||
return prefix + "," + elem + "]"
|
||||
}
|
||||
|
||||
// json_escape_string — escape a raw string so it can be safely embedded as a
|
||||
// JSON string value.
|
||||
//
|
||||
@@ -171,3 +189,25 @@ fn json_escape_string(s: String) -> String {
|
||||
let s5: String = str_replace(s4, "\t", "\\t")
|
||||
return s5
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DHARMA byte decoding
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// bytes_to_str — decode a JSON array of integer byte values back to a string.
|
||||
// "[104,105]" -> "hi"
|
||||
// Inverse of str_to_bytes (defined in string.el). Defined here because it
|
||||
// depends on json_array_len and json_array_get_string which live in this file.
|
||||
fn bytes_to_str(arr: String) -> String {
|
||||
let n: Int = json_array_len(arr)
|
||||
if n == 0 { return "" }
|
||||
let out: String = __str_alloc(n)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let elem: String = json_array_get_string(arr, i)
|
||||
let b: Int = __str_to_int(elem)
|
||||
out = __str_set_char(out, i, b)
|
||||
i = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -876,3 +876,32 @@ fn str_join(parts: [String], sep: String) -> String {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ── DHARMA byte encoding (str_to_bytes) ──────────────────────────────────────
|
||||
//
|
||||
// str_to_bytes — encode a string as a JSON array of unsigned byte values.
|
||||
// "hi" -> "[104,105]"
|
||||
// Used by db.el to store content in Engram JSON nodes as a byte array.
|
||||
// Note: bytes_to_str (the inverse) is defined in json.el because it depends
|
||||
// on json_array_get_string which is defined there.
|
||||
fn str_to_bytes(s: String) -> String {
|
||||
let n: Int = __str_len(s)
|
||||
if n == 0 { return "[]" }
|
||||
let result: String = "["
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let b: Int = __str_char_at(s, i)
|
||||
if i > 0 { result = __str_concat_raw(result, ",") }
|
||||
result = __str_concat_raw(result, __int_to_str(b))
|
||||
i = i + 1
|
||||
}
|
||||
return __str_concat_raw(result, "]")
|
||||
}
|
||||
|
||||
// ── Cryptographic hashing ─────────────────────────────────────────────────────
|
||||
|
||||
// hash_sha256 — return the SHA-256 hex digest of a string.
|
||||
// Delegates to the __sha256_hex seed primitive.
|
||||
fn hash_sha256(s: String) -> String {
|
||||
return __sha256_hex(s)
|
||||
}
|
||||
|
||||
@@ -400,3 +400,26 @@ fn uuid_new() -> String {
|
||||
fn uuid_v4() -> String {
|
||||
return __uuid_v4()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DHARMA-compatible aliases — millisecond-precision timestamps.
|
||||
//
|
||||
// now_millis, unix_timestamp_ms, and time_now_ms all return the same value:
|
||||
// milliseconds since the Unix epoch. They exist because different parts of
|
||||
// the dharma codebase use different names for the same concept.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// now_millis — milliseconds since Unix epoch. Alias for time_now().
|
||||
fn now_millis() -> Int {
|
||||
return __time_now_ns() / 1000000
|
||||
}
|
||||
|
||||
// unix_timestamp_ms — same as now_millis.
|
||||
fn unix_timestamp_ms() -> Int {
|
||||
return __time_now_ns() / 1000000
|
||||
}
|
||||
|
||||
// time_now_ms — same as now_millis.
|
||||
fn time_now_ms() -> Int {
|
||||
return __time_now_ns() / 1000000
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user