From 2fdf74f6ee61c0c8fee088f35cb286f8a86ca91f Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 2 Jun 2025 13:23:05 -0500 Subject: [PATCH] fix blob; throw on non stone reads; update blob test --- scripts/engine.cm | 2 ++ source/blob.h | 10 ++++++---- source/qjs_blob.c | 41 ++++++++++++++++++++++------------------- source/qjs_crypto.c | 11 +++++++++++ tests/blob.ce | 30 ++++++++++++++++++++++++++---- 5 files changed, 67 insertions(+), 27 deletions(-) diff --git a/scripts/engine.cm b/scripts/engine.cm index 76ddea78..f6629792 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -333,6 +333,8 @@ var $_ = create_actor() $_.random = crypto.random $_.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 = crypto.random_fit + $_.clock = function(fn) { return os.now() } $_.clock[cell.DOC] = "takes a function input value that will eventually be called with the current time in number form." diff --git a/source/blob.h b/source/blob.h index 17cb90f2..3aaefd3b 100644 --- a/source/blob.h +++ b/source/blob.h @@ -197,10 +197,12 @@ void copy_bits_fast(const void *src, void *dest, // Copy bits [from..to) from src buffer into the blob at its current length // (i.e. 'to' is exclusive). Advance blob->length by (to - from). Return 0. static int blob_copy_bits(blob *dest, const void *src, size_t from, size_t to) { + size_t bits = to - from; + if (blob_ensure_capacity(dest, bits) < 0) return -1; int n = (int)from; - int m = (int)(to - 1); // inclusive + int m = (int)(to - 1); copy_bits_fast(src, dest->data, n, m, (int)dest->length); - dest->length += (to - from); + dest->length += bits; return 0; } @@ -237,7 +239,6 @@ blob *blob_new_with_fill(size_t length_bits, int logical_value) { if (logical_value) { size_t bytes = b->capacity / 8; memset(b->data, 0xFF, bytes); - // Clear unused bits in last byte size_t used_bits = length_bits & 7; if (used_bits && bytes > 0) { uint8_t mask = (1 << used_bits) - 1; @@ -247,6 +248,7 @@ blob *blob_new_with_fill(size_t length_bits, int logical_value) { return b; } + void blob_destroy(blob *b) { if (!b) return; if (b->data) { @@ -261,7 +263,7 @@ void blob_destroy(blob *b) { void blob_make_stone(blob *b) { if (!b) return; b->is_stone = 1; - // Optionally shrink buffer to exactly length bits + // shrink buffer to exactly length bits if (b->capacity > b->length) { size_t size_bytes = (b->length + 7) >> 3; if (size_bytes) { diff --git a/source/qjs_blob.c b/source/qjs_blob.c index 657606a1..0f5bd645 100644 --- a/source/qjs_blob.c +++ b/source/qjs_blob.c @@ -50,28 +50,32 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target, int is_one = JS_ToBool(ctx, argv[1]); bd = blob_new_with_fill((size_t)length_bits, is_one); } else if (JS_IsFunction(ctx, argv[1])) { - // Random function provided - call it for each bit + /* Random function provided – call it for each bit */ + size_t bytes = (length_bits + 7) / 8; bd = blob_new((size_t)length_bits); if (bd) { bd->length = length_bits; + /* Ensure the backing storage starts out zeroed */ + memset(bd->data, 0, bytes); for (size_t i = 0; i < length_bits; i++) { JSValue randval = JS_Call(ctx, argv[1], JS_UNDEFINED, 0, NULL); if (JS_IsException(randval)) { blob_destroy(bd); return JS_EXCEPTION; } - int64_t fitval; - JS_ToInt64(ctx, &fitval, randval); + + int32_t fitval; + JS_ToInt32(ctx, &fitval, randval); JS_FreeValue(ctx, randval); - - // Set bit based on random value + + /* Compute which byte and which bit within that byte to set/clear */ size_t byte_idx = i / 8; - size_t bit_idx = i % 8; - if (fitval & 1) { - bd->data[byte_idx] |= (1 << bit_idx); - } else { - bd->data[byte_idx] &= ~(1 << bit_idx); - } + size_t bit_idx = i % 8; + + if (fitval & 1) + bd->data[byte_idx] |= (uint8_t)(1 << bit_idx); + else + bd->data[byte_idx] &= (uint8_t)~(1 << bit_idx); } } } else { @@ -232,14 +236,14 @@ static JSValue js_blob_read_logical(JSContext *ctx, JSValueConst this_val, } int64_t pos; if (JS_ToInt64(ctx, &pos, argv[0]) < 0) { - return JS_EXCEPTION; + return JS_ThrowInternalError(ctx, "must provide a positive bit"); } if (pos < 0) { - return JS_NULL; // out of range + return JS_ThrowRangeError(ctx, "read_logical: position must be non-negative"); } int bit_val; if (blob_read_bit(bd, (size_t)pos, &bit_val) < 0) { - return JS_NULL; // not stone or out of range + return JS_ThrowTypeError(ctx, "read_logical: blob must be stone"); } return JS_NewBool(ctx, bit_val); } @@ -284,22 +288,21 @@ static JSValue js_blob_read_number(JSContext *ctx, JSValueConst this_val, } blob *bd = js2blob(ctx, this_val); if (!bd) { - return JS_ThrowTypeError(ctx, "read_dec64: not called on a blob"); + return JS_ThrowTypeError(ctx, "read_number: not called on a blob"); } if (!bd->is_stone) { - return JS_ThrowTypeError(ctx, "read_dec64: blob must be stone"); + return JS_ThrowTypeError(ctx, "read_number: blob must be stone"); } double from; if (JS_ToFloat64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; - if (from < 0) return JS_ThrowRangeError(ctx, "read_dec64: out of range"); + if (from < 0) return JS_ThrowRangeError(ctx, "read_number: position must be non-negative"); double d; - printf("reading blob from %g\n", from); if (blob_read_dec64(bd, from, &d) < 0) { - return JS_ThrowRangeError(ctx, "read_dec64: out of range"); + return JS_ThrowRangeError(ctx, "read_number: out of range"); } return JS_NewFloat64(ctx, d); diff --git a/source/qjs_crypto.c b/source/qjs_crypto.c index 881e6c82..07176ccc 100644 --- a/source/qjs_crypto.c +++ b/source/qjs_crypto.c @@ -199,6 +199,16 @@ JSValue js_crypto_random(JSContext *js, JSValue self, int argc, JSValue *argv) 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) @@ -237,6 +247,7 @@ 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/tests/blob.ce b/tests/blob.ce index a259b9cd..08dadd22 100644 --- a/tests/blob.ce +++ b/tests/blob.ce @@ -58,13 +58,27 @@ test("Write and read single bit", function() { assertEqual(b.read_logical(3), false, "Fourth bit should be false (0)"); }); -// Test 4: Out of range read returns null -test("Out of range read returns null", function() { +// Test 4: Out of range read throws error +test("Out of range read throws error", function() { var b = new Blob(); b.write_bit(true); b.stone(); - assertEqual(b.read_logical(100), null, "Out of range read should return null"); - assertEqual(b.read_logical(-1), null, "Negative index read should return null"); + + var threw = false; + try { + b.read_logical(100); // Out of range + } catch (e) { + threw = true; + } + assert(threw, "Out of range read should throw"); + + threw = false; + try { + b.read_logical(-1); // Negative index + } catch (e) { + threw = true; + } + assert(threw, "Negative index read should throw"); }); // Test 5: Write and read numbers @@ -314,6 +328,14 @@ test("Non-stone blob read methods should throw", function() { // Try to read without stoning - should throw var threw = false; + try { + b.read_logical(0); + } catch (e) { + threw = true; + } + assert(threw, "read_logical on non-stone blob should throw"); + + threw = false; try { b.read_number(0); } catch (e) {