fix blob; throw on non stone reads; update blob test

This commit is contained in:
2025-06-02 13:23:05 -05:00
parent e689679aac
commit 2fdf74f6ee
5 changed files with 67 additions and 27 deletions

View File

@@ -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."

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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),
};

View File

@@ -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) {