fix http; faster blob; list and help commands

This commit is contained in:
2025-06-01 09:34:15 -05:00
parent 06108df3d4
commit 3a40076958
19 changed files with 1809 additions and 2248 deletions

View File

@@ -1,7 +1,3 @@
# Cell configuration
# ENet service interval in seconds
ENETSERVICE = 0.05
# Reply timeout in seconds
REPLYTIMEOUT = 30
[dependencies]
extramath = "https://gitea.pockle.world/john/extramath@head"

View File

@@ -288,7 +288,7 @@ src += [
'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c',
'render.c','simplex.c','spline.c', 'transform.c','cell.c', 'wildmatch.c',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c',
'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c'
'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c'
]
# quirc src
src += [

View File

@@ -240,14 +240,10 @@ if (io.exists(configPath)) {
var config = toml.decode(configText)
// Override defaults with config values
if (config.ENETSERVICE !== undefined) {
if (config.ENETSERVICE !== undefined)
ENETSERVICE = config.ENETSERVICE
log.system(`Loaded ENETSERVICE from config: ${ENETSERVICE}`)
}
if (config.REPLYTIMEOUT !== undefined) {
if (config.REPLYTIMEOUT !== undefined)
REPLYTIMEOUT = config.REPLYTIMEOUT
log.system(`Loaded REPLYTIMEOUT from config: ${REPLYTIMEOUT}`)
}
} catch (err) {
log.error(`Failed to load config from ${configPath}: ${err}`)
}
@@ -756,13 +752,13 @@ if (!prog)
throw new Error(cell.args.program + " not found.");
// Mount the directory containing the program
var progDir = prog.substring(0, prog.lastIndexOf('/'))
var progDir = io.realdir(prog) + "/" + prog.substring(0, prog.lastIndexOf('/'))
if (progDir && progDir !== '.') {
io.mount(progDir, "")
}
var progContent = io.slurp(prog)
var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { ${progContent} })`
var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { var args = arg; ${progContent} })`
var val = js.eval(cell.args.program, prog_script)($_, cell.args.arg)
if (val)
throw new Error('Program must not return anything');

View File

@@ -4,8 +4,10 @@ var io = use('io')
var shop = use('shop')
if (args.length < 1) {
log.console("Usage: cell get <locator>")
log.console("Example: cell get git.world/jj/mod@v0.6.3")
log.console("Usage: cell get <locator> [alias]")
log.console("Examples:")
log.console(" cell get git.world/jj/mod@v0.6.3")
log.console(" cell get git.world/jj/mod (uses head/master)")
$_.stop()
return
}
@@ -13,22 +15,30 @@ if (args.length < 1) {
var locator = args[0]
var parsed = shop.parse_locator(locator)
// If no version specified, append @head
if (!parsed) {
log.error("Invalid locator format. Expected: host/owner/name@version")
if (locator.indexOf('@') === -1) {
locator = locator + '@head'
parsed = shop.parse_locator(locator)
}
}
if (!parsed) {
log.error("Invalid locator format. Expected: host/owner/name[@version]")
$_.stop()
return
}
// Initialize shop if needed
if (!io.exists('.cell/shop.toml')) {
log.console("No shop.toml found. Initializing...")
if (!io.exists('.cell/cell.toml')) {
log.console("No cell.toml found. Initializing...")
shop.init()
}
// Load current config
var config = shop.load_config()
if (!config) {
log.error("Failed to load shop.toml")
log.error("Failed to load cell.toml")
$_.stop()
return
}

44
scripts/help.ce Normal file
View File

@@ -0,0 +1,44 @@
// cell help - Display help information for cell commands
var io = use('io')
var command = args.length > 0 ? args[0] : null
// Display specific command help
if (command) {
var man_file = 'scripts/man/' + command + '.man'
if (io.exists(man_file)) {
var content = io.slurp(man_file)
log.console(content)
} else {
log.error("No help available for command: " + command)
log.console("Run 'cell help' to see available commands.")
}
$_.stop()
return
}
// Display general help
var cell_man = 'scripts/man/cell.man'
if (io.exists(cell_man)) {
var content = io.slurp(cell_man)
log.console(content)
} else {
// Fallback if man file doesn't exist
log.console("cell - The Cell module system for Prosperon")
log.console("")
log.console("Usage: cell <command> [arguments]")
log.console("")
log.console("Commands:")
log.console(" init Initialize a new Cell project")
log.console(" get Fetch and add a module dependency")
log.console(" update Update a dependency to a new version")
log.console(" vendor Copy all dependencies locally")
log.console(" build Compile all modules to bytecode")
log.console(" patch Create a patch for a module")
log.console(" help Show this help message")
log.console("")
log.console("Run 'cell help <command>' for more information on a command.")
}
$_.stop()

View File

@@ -11,13 +11,13 @@ var success = shop.init()
if (success) {
log.console("Created .cell directory structure:")
log.console(" .cell/")
log.console(" ├── shop.toml (manifest)")
log.console(" ├── cell.toml (manifest)")
log.console(" ├── lock.toml (checksums)")
log.console(" ├── modules/ (vendored source)")
log.console(" ├── build/ (compiled modules)")
log.console(" └── patches/ (patches)")
log.console("")
log.console("Edit .cell/shop.toml to configure your project.")
log.console("Edit .cell/cell.toml to configure your project.")
} else {
log.error("Failed to initialize .cell directory")
}

75
scripts/list.ce Normal file
View File

@@ -0,0 +1,75 @@
var shop = use('shop')
var io = use('io')
// Initialize shop if needed
if (!shop.init()) {
log.error("Failed to initialize .cell directory")
return
}
// Load configuration
var config = shop.load_config()
if (!config) {
log.console("No modules installed (no cell.toml found)")
return
}
// List dependencies
if (!config.dependencies || Object.keys(config.dependencies).length === 0) {
log.console("No modules installed")
return
}
log.console("Installed modules:")
log.console("")
// Display each dependency
for (var alias in config.dependencies) {
var locator = config.dependencies[alias]
var parsed = shop.parse_locator(locator)
if (parsed) {
log.console(" " + alias + " -> " + locator)
// Check if module directory exists
var module_dir = '.cell/modules/' + alias + '@' + parsed.version
if (io.exists(module_dir)) {
log.console(" ✓ Downloaded to " + module_dir)
} else {
log.console(" ✗ Not downloaded (run 'cell get " + locator + "')")
}
// Check if vendored
var vendor_dir = 'modules/' + alias + '@' + parsed.version
if (io.exists(vendor_dir)) {
log.console(" ✓ Vendored to " + vendor_dir)
}
// Check if compiled
var build_dir = '.cell/build/' + alias + '@' + parsed.version
if (io.exists(build_dir)) {
log.console(" ✓ Compiled to " + build_dir)
}
} else {
log.console(" " + alias + " -> " + locator + " (invalid locator)")
}
log.console("")
}
// Show patches if any
if (config.patches && Object.keys(config.patches).length > 0) {
log.console("Patches:")
for (var alias in config.patches) {
var patch_file = config.patches[alias]
log.console(" " + alias + " -> " + patch_file)
if (io.exists('.cell/' + patch_file)) {
log.console(" ✓ Patch file exists")
} else {
log.console(" ✗ Patch file missing")
}
}
}
$_.stop()

View File

@@ -6,18 +6,18 @@ var json = use('json')
var Shop = {}
// Load shop.toml configuration
// Load cell.toml configuration
Shop.load_config = function() {
var shop_path = '.cell/shop.toml'
var shop_path = '.cell/cell.toml'
if (!io.exists(shop_path)) {
return null
}
var content = io.slurp(shop_path)
return toml.parse(content)
return toml.decode(content)
}
// Save shop.toml configuration
// Save cell.toml configuration
Shop.save_config = function(config) {
// Simple TOML writer for our needs
var lines = []
@@ -74,7 +74,7 @@ Shop.save_config = function(config) {
lines.push(']')
}
io.slurpwrite('.cell/shop.toml', lines.join('\n'))
io.slurpwrite('.cell/cell.toml', lines.join('\n'))
}
// Initialize .cell directory structure
@@ -95,7 +95,7 @@ Shop.init = function() {
io.mkdir('.cell/patches')
}
if (!io.exists('.cell/shop.toml')) {
if (!io.exists('.cell/cell.toml')) {
var default_config = {
module: "my-game",
engine: "mist/prosperon@v0.9.3",
@@ -122,7 +122,7 @@ Shop.init = function() {
Shop.mount = function() {
var config = Shop.load_config()
if (!config) {
log.error("No shop.toml found")
log.error("No cell.toml found")
return false
}
@@ -224,7 +224,7 @@ Shop.parse_locator = function(locator) {
Shop.add_dependency = function(alias, locator) {
var config = Shop.load_config()
if (!config) {
log.error("No shop.toml found")
log.error("No cell.toml found")
return false
}

View File

@@ -12,29 +12,22 @@
// - stone (immutable): reading is allowed
//
// The blob is stored as an array of bits in memory, but for simplicity here,
// we store them in a dynamic byte array with a bit_length and capacity in bits.
// we store them in a dynamic byte array with a length and capacity in bits.
// -----------------------------------------------------------------------------
typedef struct blob {
// The actual buffer holding the bits (in multiples of 8 bits).
uint8_t *data;
// The total number of bits currently in use (the "length" of the blob).
size_t bit_length;
// The total capacity in bits that 'data' can currently hold without realloc.
size_t bit_capacity;
size_t length;
size_t capacity;
// 0 = antestone (mutable)
// 1 = stone (immutable)
int is_stone;
} blob;
// Initialize a new blob
void blob_init(blob *b);
// Create a new blob with specified bit capacity
blob *blob_new(size_t bit_capacity);
blob *blob_new(size_t capacity);
// Create a blob from another blob (copy bits from 'from' to 'to')
blob *blob_new_from_blob(const blob *src, size_t from, size_t to);
@@ -52,22 +45,16 @@ void blob_make_stone(blob *b);
int blob_write_bit(blob *b, int bit_val);
int blob_write_blob(blob *b, const blob *src);
int blob_write_dec64(blob *b, double d);
int blob_write_fit(blob *b, int64_t fit, int length);
int blob_write_kim(blob *b, int64_t value);
int blob_write_int64(blob *b, int64_t i);
int blob_write_pad(blob *b, int block_size);
int blob_write_text(blob *b, const char *text);
int blob_write_text_raw(blob *b, const char *text);
int blob_write_bytes(blob *b, void *data, size_t length);
// Read operations (only work on stone blobs)
int blob_read_bit(const blob *b, size_t pos, int *out_bit);
blob *blob_read_blob(const blob *b, size_t from, size_t to);
int blob_read_dec64(const blob *b, size_t from, double *out_value);
int blob_read_fit(const blob *b, size_t from, int length, int64_t *out_value);
int blob_read_kim(const blob *b, size_t from, int64_t *out_value, size_t *bits_read);
int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value);
int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read);
int blob_read_text_raw(const blob *b, size_t from, char **out_text, size_t *bits_read);
int blob_pad_check(const blob *b, size_t from, int block_size);
// Utility functions
@@ -77,88 +64,168 @@ int kim_length_for_fit(int64_t value);
// Helper to ensure capacity for writing
static int blob_ensure_capacity(blob *b, size_t new_bits) {
size_t need_bits = b->bit_length + new_bits;
if (need_bits <= b->bit_capacity) return 0;
size_t need_bits = b->length + new_bits;
if (need_bits <= b->capacity) return 0;
// Increase capacity (in multiples of bytes).
// We can pick a growth strategy. For demonstration, double it:
size_t new_capacity = b->bit_capacity == 0 ? 64 : b->bit_capacity * 2;
// Grow strategy: double until enough
size_t new_capacity = b->capacity == 0 ? 64 : b->capacity * 2;
while (new_capacity < need_bits) new_capacity *= 2;
// Round up new_capacity to a multiple of 8 bits
if (new_capacity % 8) {
new_capacity += 8 - (new_capacity % 8);
}
// Round up to multiple of 8 bits
if (new_capacity % 8) new_capacity += 8 - (new_capacity % 8);
size_t new_size_bytes = new_capacity / 8;
uint8_t *new_ptr = realloc(b->data, new_size_bytes);
if (!new_ptr) {
return -1; // out of memory
}
// zero-fill the new area (only beyond the old capacity)
size_t old_size_bytes = b->bit_capacity / 8;
if (new_size_bytes > old_size_bytes) {
if (!new_ptr) return -1;
// Zero-fill newly allocated area
size_t old_size_bytes = b->capacity / 8;
if (new_size_bytes > old_size_bytes)
memset(new_ptr + old_size_bytes, 0, new_size_bytes - old_size_bytes);
}
b->data = new_ptr;
b->bit_capacity = new_capacity;
b->capacity = new_capacity;
return 0;
}
void blob_init(blob *b) {
b->data = NULL;
b->bit_length = 0;
b->bit_capacity = 0;
b->is_stone = 0;
// A simple bit-level memcpy (may handle unaligned bits at both ends)
void bitcpy(unsigned char *dst, int dst_bit_offset,
unsigned char *src, int src_bit_offset, int bit_length)
{
int dst_byte_offset = dst_bit_offset / 8;
int src_byte_offset = src_bit_offset / 8;
int dst_bit = dst_bit_offset % 8;
int src_bit = src_bit_offset % 8;
int remaining_bits = bit_length;
if (dst_bit != 0 || src_bit != 0)
{
unsigned char mask = (1 << dst_bit) - 1;
unsigned char src_mask = (1 << (8 - src_bit)) - 1;
unsigned char src_byte = src[src_byte_offset] & (src_mask << src_bit);
dst[dst_byte_offset] = (dst[dst_byte_offset] & ~mask) |
(src_byte >> (src_bit - dst_bit));
remaining_bits -= (8 - dst_bit);
dst_byte_offset++;
src_byte_offset += (remaining_bits <= 0) ? 0 : 1;
}
int whole_bytes = remaining_bits / 8;
for (int i = 0; i < whole_bytes; i++)
dst[dst_byte_offset + i] = src[src_byte_offset + i];
remaining_bits %= 8;
dst_byte_offset += whole_bytes;
src_byte_offset += whole_bytes;
if (remaining_bits > 0)
{
unsigned char mask = (1 << remaining_bits) - 1;
unsigned char src_byte = src[src_byte_offset] & mask;
dst[dst_byte_offset] = (dst[dst_byte_offset] & ~mask) | src_byte;
}
}
blob *blob_new(size_t bit_capacity) {
blob *b = calloc(1, sizeof(blob));
if (!b) return NULL;
// Fast bit-copy for arbitrary bit ranges (inclusive) from src → dest
void copy_bits_fast(const void *src, void *dest,
int n, /* start bit in src (inclusive) */
int m, /* end bit in src (inclusive) */
int x) /* start bit in dest */
{
const uint8_t *s = (const uint8_t*)src;
uint8_t *d = (uint8_t*)dest;
blob_init(b);
int total_bits = m - n + 1;
if (total_bits <= 0) return;
if (bit_capacity > 0) {
// Round up to multiple of 8
if (bit_capacity % 8) {
bit_capacity += 8 - (bit_capacity % 8);
}
b->bit_capacity = bit_capacity;
size_t bytes = bit_capacity / 8;
b->data = calloc(bytes, 1);
if (!b->data) {
free(b);
return NULL;
int src_bit = n;
int dst_bit = x;
int src_byte = src_bit >> 3;
int dst_byte = dst_bit >> 3;
int src_off = src_bit & 7;
int dst_off = dst_bit & 7;
// 1) Leading partial byte to align dest
if (dst_off != 0) {
int chunk = 8 - dst_off;
if (chunk > total_bits) chunk = total_bits;
uint8_t dst_mask = (((1u << chunk) - 1u) << dst_off);
uint16_t wb = (uint16_t)s[src_byte] | ((uint16_t)s[src_byte + 1] << 8);
uint8_t bits = (uint8_t)((wb >> src_off) & ((1u << chunk) - 1u));
bits <<= dst_off;
d[dst_byte] = (d[dst_byte] & ~dst_mask) | (bits & dst_mask);
total_bits -= chunk;
src_bit += chunk;
dst_bit += chunk;
src_byte = src_bit >> 3;
dst_byte = dst_bit >> 3;
src_off = src_bit & 7;
dst_off = dst_bit & 7; // now zero
}
// 2) Copy full bytes
if (total_bits >= 8) {
if (src_off == 0) {
int num_bytes = total_bits >> 3;
memcpy(&d[dst_byte], &s[src_byte], (size_t)num_bytes);
total_bits -= num_bytes << 3;
src_byte += num_bytes;
dst_byte += num_bytes;
} else {
int num_bytes = total_bits >> 3;
for (int i = 0; i < num_bytes; i++) {
uint16_t wb = (uint16_t)s[src_byte + i] |
((uint16_t)s[src_byte + i + 1] << 8);
d[dst_byte + i] = (uint8_t)((wb >> src_off) & 0xFFu);
}
total_bits -= num_bytes << 3;
src_byte += num_bytes;
dst_byte += num_bytes;
}
}
// 3) Trailing bits (< 8)
if (total_bits > 0) {
uint8_t dst_mask = (1u << total_bits) - 1u;
uint16_t wb = (uint16_t)s[src_byte] | ((uint16_t)s[src_byte + 1] << 8);
uint8_t bits = (uint8_t)((wb >> src_off) & dst_mask);
d[dst_byte] = (d[dst_byte] & ~dst_mask) | (bits & dst_mask);
}
}
// 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) {
int n = (int)from;
int m = (int)(to - 1); // inclusive
copy_bits_fast(src, dest->data, n, m, (int)dest->length);
dest->length += (to - from);
return 0;
}
blob *blob_new(size_t capacity) {
if (capacity < 0) capacity = 0;
blob *b = calloc(1, sizeof(blob));
if (!b) return NULL;
if (blob_ensure_capacity(b, capacity) < 0) {
free(b);
return NULL;
}
return b;
}
blob *blob_new_from_blob(const blob *src, size_t from, size_t to) {
if (!src) return NULL;
if (to > src->bit_length) to = src->bit_length;
if (to > src->length) to = src->length;
if (from >= to) return blob_new(0);
size_t copy_len = to - from;
blob *b = blob_new(copy_len);
if (!b) return NULL;
b->bit_length = copy_len;
// Copy bits
for (size_t i = 0; i < copy_len; i++) {
size_t src_bit_index = from + i;
size_t src_byte = src_bit_index >> 3;
size_t src_off = src_bit_index & 7;
int bit_val = (src->data[src_byte] >> src_off) & 1;
size_t dst_byte = i >> 3;
size_t dst_off = i & 7;
if (bit_val) {
b->data[dst_byte] |= (1 << dst_off);
}
}
b->length = 0; // will be set by blob_copy_bits
blob_copy_bits(b, src->data, from, to);
return b;
}
@@ -166,182 +233,83 @@ blob *blob_new_with_fill(size_t length_bits, int logical_value) {
blob *b = blob_new(length_bits);
if (!b) return NULL;
b->bit_length = length_bits;
b->length = length_bits;
if (logical_value) {
// Fill with 1s
size_t bytes = b->bit_capacity / 8;
memset(b->data, 0xff, bytes);
size_t bytes = b->capacity / 8;
memset(b->data, 0xFF, bytes);
// Clear unused bits in last byte
size_t used_bits_in_last_byte = length_bits & 7;
if (used_bits_in_last_byte && bytes > 0) {
uint8_t mask = (1 << used_bits_in_last_byte) - 1;
size_t used_bits = length_bits & 7;
if (used_bits && bytes > 0) {
uint8_t mask = (1 << used_bits) - 1;
b->data[bytes - 1] &= mask;
}
}
return b;
}
void blob_destroy(blob *b) {
if (b) {
if (b && b->data) {
free(b->data);
b->data = NULL;
b->bit_length = 0;
b->bit_capacity = 0;
}
free(b);
if (!b) return;
if (b->data) {
free(b->data);
b->data = NULL;
b->length = 0;
b->capacity = 0;
}
free(b);
}
void blob_make_stone(blob *b) {
if (!b) return;
b->is_stone = 1;
// Optionally shrink the buffer to exactly bit_length in size
if (b->bit_capacity > b->bit_length) {
size_t size_in_bytes = (b->bit_length + 7) >> 3; // round up to full bytes
uint8_t *new_ptr = NULL;
if (size_in_bytes) {
new_ptr = realloc(b->data, size_in_bytes);
if (new_ptr) {
b->data = new_ptr;
}
// Optionally shrink buffer to exactly length bits
if (b->capacity > b->length) {
size_t size_bytes = (b->length + 7) >> 3;
if (size_bytes) {
uint8_t *new_ptr = realloc(b->data, size_bytes);
if (new_ptr) b->data = new_ptr;
} else {
// zero length
free(b->data);
b->data = NULL;
}
b->bit_capacity = b->bit_length; // capacity in bits now matches length
b->capacity = b->length;
}
}
int blob_write_bit(blob *b, int bit_val) {
if (!b || b->is_stone) return -1;
if (blob_ensure_capacity(b, 1) < 0) return -1;
if (blob_ensure_capacity(b, 1) < 0) {
return -1;
}
size_t bit_index = b->bit_length;
size_t bit_index = b->length;
size_t byte_index = bit_index >> 3;
size_t offset_in_byte = bit_index & 7;
// set or clear bit
if (bit_val)
b->data[byte_index] |= (1 << offset_in_byte);
else
b->data[byte_index] &= ~(1 << offset_in_byte);
b->bit_length++;
b->length++;
return 0;
}
int blob_write_blob(blob *b, const blob *src) {
if (!b || !src || b->is_stone) return -1;
// Append all bits from src blob
for (size_t i = 0; i < src->bit_length; i++) {
size_t byte_idx = i / 8;
size_t bit_idx = i % 8;
int bit = (src->data[byte_idx] >> bit_idx) & 1;
if (blob_write_bit(b, bit) < 0) {
return -1;
}
}
return 0;
return blob_copy_bits(b, src->data, 0, src->length);
}
int blob_write_dec64(blob *b, double d) {
if (!b || b->is_stone) return -1;
// Simple DEC64 encoding: store as IEEE 754 double (64 bits)
uint64_t bits;
memcpy(&bits, &d, sizeof(bits));
// Write 64 bits
for (int i = 0; i < 64; i++) {
if (blob_write_bit(b, (bits >> i) & 1) < 0) {
return -1;
}
}
if (blob_ensure_capacity(b, 64) < 0) return -1;
copy_bits_fast(&d, b->data, 0, 64 - 1, (int)b->length);
b->length += 64;
return 0;
}
int blob_write_fit(blob *b, int64_t fit, int length) {
int blob_write_int64(blob *b, int64_t i) {
if (!b || b->is_stone) return -1;
if (length < 0 || length > 64) return -1;
// Check if fit requires more bits than allowed
if (length < 64) {
int64_t max = (1LL << length) - 1;
if (fit < 0 || fit > max) {
return -1;
}
}
// Write the bits
for (int i = 0; i < length; i++) {
if (blob_write_bit(b, (fit >> i) & 1) < 0) {
return -1;
}
}
return 0;
}
// Calculate the kim length in bits for a fit (int64) value
int kim_length_for_fit(int64_t value) {
if (value >= -1 && value <= 127) return 8;
if (value >= -127 && value <= 16383) return 16;
if (value >= -16383 && value <= 2097151) return 24;
if (value >= -2097151 && value <= 268435455) return 32;
if (value >= -268435455 && value <= 34359738367LL) return 40;
if (value >= -34359738367LL && value <= 4398046511103LL) return 48;
if (value >= -4398046511103LL && value <= 562949953421311LL) return 56;
if (value >= -562949953421311LL && value <= 36028797018963967LL) return 64;
if (value >= -36028797018963967LL) return 72;
return 80; // Maximum kim length
}
int blob_write_kim(blob *b, int64_t value) {
if (!b || b->is_stone) return -1;
int bits = kim_length_for_fit(value);
int bytes = bits / 8;
uint8_t kim_bytes[10] = {0}; // Max 10 bytes for kim
if (value < 0) {
// Negative number: first byte is 0x80, then encode -value-1
kim_bytes[0] = 0x80;
value = -value - 1;
// Encode remaining bytes
for (int i = bytes - 1; i > 0; i--) {
kim_bytes[i] = value & 0x7F;
value >>= 7;
if (i > 1) kim_bytes[i] |= 0x80; // Set continuation bit
}
} else {
// Positive number
for (int i = bytes - 1; i >= 0; i--) {
kim_bytes[i] = value & 0x7F;
value >>= 7;
if (i > 0) kim_bytes[i] |= 0x80; // Set continuation bit
}
}
// Write the kim bytes as bits
for (int i = 0; i < bytes; i++) {
for (int j = 0; j < 8; j++) {
if (blob_write_bit(b, (kim_bytes[i] >> j) & 1) < 0)
return -1;
}
}
if (blob_ensure_capacity(b, 64) < 0) return -1;
copy_bits_fast(&i, b->data, 0, 64 - 1, (int)b->length);
b->length += 64;
return 0;
}
@@ -349,23 +317,15 @@ int blob_write_pad(blob *b, int block_size) {
if (!b || b->is_stone) return -1;
if (block_size <= 0) return -1;
// Write a 1 bit
if (blob_write_bit(b, 1) < 0) {
return -1;
}
if (blob_write_bit(b, 1) < 0) return -1;
// Calculate how many 0 bits to add
size_t current_len = b->bit_length;
size_t remainder = current_len % block_size;
if (remainder > 0) {
size_t zeros_needed = block_size - remainder;
for (size_t i = 0; i < zeros_needed; i++) {
if (blob_write_bit(b, 0) < 0) {
return -1;
}
}
size_t current = b->length;
size_t rem = current % block_size;
if (rem > 0) {
size_t zeros = block_size - rem;
for (size_t i = 0; i < zeros; i++)
if (blob_write_bit(b, 0) < 0) return -1;
}
return 0;
}
@@ -373,49 +333,23 @@ int blob_write_text(blob *b, const char *text) {
if (!b || !text || b->is_stone) return -1;
size_t len = strlen(text);
// Ensure capacity for 64-bit length + len*8 bits of text
if (blob_ensure_capacity(b, 64 + (len * 8)) < 0) return -1;
// Write kim-encoded length
if (blob_write_kim(b, len) < 0) {
return -1;
// 1) Write length prefix
if (blob_write_int64(b, (int64_t)len) < 0) return -1;
// 2) Write raw text bytes (len * 8 bits)
if (len > 0) {
copy_bits_fast(text, b->data, 0, (int)(len * 8 - 1), (int)b->length);
b->length += (len * 8);
}
// Write each character as kim-encoded UTF-32
// For simplicity, assuming ASCII
for (size_t i = 0; i < len; i++) {
if (blob_write_kim(b, (unsigned char)text[i]) < 0) {
return -1;
}
}
return 0;
}
int blob_write_text_raw(blob *b, const char *text) {
if (!b || !text || b->is_stone) return -1;
size_t len = strlen(text);
return blob_write_bytes(b, (void *)text, len);
}
int blob_write_bytes(blob *b, void *data, size_t length) {
if (!b || !data || b->is_stone) return -1;
// Write each byte as 8 bits
uint8_t *bytes = (uint8_t *)data;
for (size_t i = 0; i < length; i++) {
for (int j = 0; j < 8; j++) {
if (blob_write_bit(b, (bytes[i] >> j) & 1) < 0) {
return -1;
}
}
}
return 0;
}
int blob_read_bit(const blob *b, size_t pos, int *out_bit) {
if (!b || !b->is_stone || !out_bit) return -1;
if (pos >= b->bit_length) return -1;
if (pos >= b->length) return -1;
size_t byte_index = pos >> 3;
size_t offset_in_byte = pos & 7;
@@ -430,141 +364,46 @@ blob *blob_read_blob(const blob *b, size_t from, size_t to) {
int blob_read_dec64(const blob *b, size_t from, double *out_value) {
if (!b || !b->is_stone || !out_value) return -1;
if (from + 64 > b->bit_length) return -1;
// Read 64 bits
uint64_t bits = 0;
for (int i = 0; i < 64; i++) {
int bit;
blob_read_bit(b, from + i, &bit);
if (bit) bits |= (1ULL << i);
}
// Convert to double
memcpy(out_value, &bits, sizeof(*out_value));
if (from + 64 > b->length) return -1;
if (from < 0) return -1;
copy_bits_fast(b->data, out_value, (int)from, (int)(from + 64 - 1), 0);
return 0;
}
int blob_read_fit(const blob *b, size_t from, int length, int64_t *out_value) {
int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value) {
if (!b || !b->is_stone || !out_value) return -1;
if (length < 0 || length > 64) return -1;
if (from + length > b->bit_length) return -1;
// Read bits
int64_t value = 0;
for (int i = 0; i < length; i++) {
int bit;
blob_read_bit(b, from + i, &bit);
if (bit) value |= (1LL << i);
}
*out_value = value;
return 0;
}
int blob_read_kim(const blob *b, size_t from, int64_t *out_value, size_t *bits_read) {
if (!b || !b->is_stone || !out_value || !bits_read) return -1;
size_t pos = from;
uint8_t bytes[10];
int byte_count = 0;
// Read bytes until we find one without continuation bit
while (byte_count < 10) {
if (pos + 8 > b->bit_length) return -1;
uint8_t byte = 0;
for (int i = 0; i < 8; i++) {
int bit;
if (blob_read_bit(b, pos + i, &bit) < 0)
return -1;
if (bit) byte |= (1 << i);
}
bytes[byte_count++] = byte;
pos += 8;
if (!(byte & 0x80)) break; // No continuation bit
}
if (byte_count == 0) return -1;
// Decode the kim value
int64_t value = 0;
int is_negative = (bytes[0] == 0x80);
if (is_negative) {
// Skip the 0x80 byte and decode remaining
for (int i = 1; i < byte_count; i++) {
value = (value << 7) | (bytes[i] & 0x7F);
}
value = -value - 1;
} else {
for (int i = 0; i < byte_count; i++) {
value = (value << 7) | (bytes[i] & 0x7F);
}
}
*out_value = value;
*bits_read = byte_count * 8;
if (from + (size_t)length > b->length) return -1;
copy_bits_fast(b->data, out_value, (int)from, (int)(from + length - 1), 0);
return 0;
}
int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read) {
if (!b || !b->is_stone || !out_text || !bits_read) return -1;
if (from >= b->bit_length) return -1;
// Need at least 64 bits for length prefix
if (from + 64 > b->length) return -1;
// Read kim-encoded length
int64_t length;
size_t len_bits;
if (blob_read_kim(b, from, &length, &len_bits) < 0) {
return -1;
}
int64_t raw_len = 0;
// Read 64 bits of length
copy_bits_fast(b->data, &raw_len, (int)from, (int)(from + 64 - 1), 0);
if (raw_len < 0) return -1;
size_t len = (size_t)raw_len;
if (length < 0) return -1;
// Check that there are len bytes following
size_t after_len = from + 64;
size_t needed_end = after_len + (len * 8);
if (needed_end > b->length) return -1;
size_t pos = from + len_bits;
// Read characters
char *str = malloc(length + 1);
// Allocate (len + 1) bytes for C-string
char *str = malloc(len + 1);
if (!str) return -1;
for (int64_t i = 0; i < length; i++) {
int64_t ch;
size_t ch_bits;
if (blob_read_kim(b, pos, &ch, &ch_bits) < 0) {
free(str);
return -1;
}
// For simplicity, assuming ASCII
str[i] = (char)(ch & 0xFF);
pos += ch_bits;
}
str[length] = '\0';
// Copy exactly len*8 bits into str[0..len-1]
copy_bits_fast(b->data, str, (int)after_len, (int)(after_len + (len * 8) - 1), 0);
str[len] = '\0';
*out_text = str;
*bits_read = pos - from;
return 0;
}
int blob_read_text_raw(const blob *b, size_t from, char **out_text, size_t *bits_read) {
if (!b || !b->is_stone || !out_text || !bits_read) return -1;
if (from >= b->bit_length) return -1;
// Check that from is byte-aligned
if (from % 8 != 0) return -1;
size_t from_byte = from / 8;
size_t length_bytes = (b->bit_length - from) / 8;
char *str = malloc(length_bytes + 1);
if (!str) return -1;
memcpy(str, b->data + from_byte, length_bytes);
str[length_bytes] = '\0';
*out_text = str;
*bits_read = length_bytes * 8;
*bits_read = 64 + (len * 8);
return 0;
}
@@ -572,30 +411,15 @@ int blob_pad_check(const blob *b, size_t from, int block_size) {
if (!b || !b->is_stone) return 0;
if (block_size <= 0) return 0;
// Check if blob length is multiple of block_size
if (b->bit_length % block_size != 0) {
return 0;
}
if (b->length % block_size != 0) return 0;
int64_t diff = (int64_t)b->length - (int64_t)from;
if (diff <= 0 || diff > block_size) return 0;
// Check if difference between length and from is <= block_size
int64_t diff = b->bit_length - from;
if (diff <= 0 || diff > block_size) {
return 0;
}
// Check if bit at from is 1
int bit;
if (blob_read_bit(b, from, &bit) < 0 || bit != 1) {
return 0;
if (blob_read_bit(b, from, &bit) < 0 || bit != 1) return 0;
for (size_t i = from + 1; i < b->length; i++) {
if (blob_read_bit(b, i, &bit) < 0 || bit != 0) return 0;
}
// Check if remaining bits are 0
for (size_t i = from + 1; i < b->bit_length; i++) {
if (blob_read_bit(b, i, &bit) < 0 || bit != 0) {
return 0;
}
}
return 1;
}

View File

@@ -448,7 +448,7 @@ void set_actor_state(cell_rt *actor)
END:
if (actor->state == ACTOR_IDLE && !actor->ar && !has_upcoming) {
if (JS_IsUndefined(actor->unneeded))
actor->ar = SDL_AddTimerNS(SDL_SECONDS_TO_NS(5), actor_remove_cb, actor);
actor->ar = SDL_AddTimerNS(SDL_SECONDS_TO_NS(0.1), actor_remove_cb, actor);
else {
if (!isinf(actor->unneeded_secs))
actor->ar = SDL_AddTimerNS(SDL_SECONDS_TO_NS(actor->unneeded_secs), actor_remove_cb, actor);

685
source/picohttpparser.c Normal file
View File

@@ -0,0 +1,685 @@
/*
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
* Shigeo Mitsunari
*
* The software is licensed under either the MIT License (below) or the Perl
* license.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <assert.h>
#include <stddef.h>
#include <string.h>
#ifdef __SSE4_2__
#ifdef _MSC_VER
#include <nmmintrin.h>
#else
#include <x86intrin.h>
#endif
#endif
#include "picohttpparser.h"
#if __GNUC__ >= 3
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif
#ifdef _MSC_VER
#define ALIGNED(n) _declspec(align(n))
#else
#define ALIGNED(n) __attribute__((aligned(n)))
#endif
#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
#define CHECK_EOF() \
if (buf == buf_end) { \
*ret = -2; \
return NULL; \
}
#define EXPECT_CHAR_NO_CHECK(ch) \
if (*buf++ != ch) { \
*ret = -1; \
return NULL; \
}
#define EXPECT_CHAR(ch) \
CHECK_EOF(); \
EXPECT_CHAR_NO_CHECK(ch);
#define ADVANCE_TOKEN(tok, toklen) \
do { \
const char *tok_start = buf; \
static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \
int found2; \
buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \
if (!found2) { \
CHECK_EOF(); \
} \
while (1) { \
if (*buf == ' ') { \
break; \
} else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
if ((unsigned char)*buf < '\040' || *buf == '\177') { \
*ret = -1; \
return NULL; \
} \
} \
++buf; \
CHECK_EOF(); \
} \
tok = tok_start; \
toklen = buf - tok_start; \
} while (0)
static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
"\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
"\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found)
{
*found = 0;
#if __SSE4_2__
if (likely(buf_end - buf >= 16)) {
__m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
size_t left = (buf_end - buf) & ~15;
do {
__m128i b16 = _mm_loadu_si128((const __m128i *)buf);
int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
if (unlikely(r != 16)) {
buf += r;
*found = 1;
break;
}
buf += 16;
left -= 16;
} while (likely(left != 0));
}
#else
/* suppress unused parameter warning */
(void)buf_end;
(void)ranges;
(void)ranges_size;
#endif
return buf;
}
static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret)
{
const char *token_start = buf;
#ifdef __SSE4_2__
static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */
"\012\037" /* allow SP and up to but not including DEL */
"\177\177"; /* allow chars w. MSB set */
int found;
buf = findchar_fast(buf, buf_end, ranges1, 6, &found);
if (found)
goto FOUND_CTL;
#else
/* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */
while (likely(buf_end - buf >= 8)) {
#define DOIT() \
do { \
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
goto NonPrintable; \
++buf; \
} while (0)
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
#undef DOIT
continue;
NonPrintable:
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
goto FOUND_CTL;
}
++buf;
}
#endif
for (;; ++buf) {
CHECK_EOF();
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
goto FOUND_CTL;
}
}
}
FOUND_CTL:
if (likely(*buf == '\015')) {
++buf;
EXPECT_CHAR('\012');
*token_len = buf - 2 - token_start;
} else if (*buf == '\012') {
*token_len = buf - token_start;
++buf;
} else {
*ret = -1;
return NULL;
}
*token = token_start;
return buf;
}
static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret)
{
int ret_cnt = 0;
buf = last_len < 3 ? buf : buf + last_len - 3;
while (1) {
CHECK_EOF();
if (*buf == '\015') {
++buf;
CHECK_EOF();
EXPECT_CHAR('\012');
++ret_cnt;
} else if (*buf == '\012') {
++buf;
++ret_cnt;
} else {
++buf;
ret_cnt = 0;
}
if (ret_cnt == 2) {
return buf;
}
}
*ret = -2;
return NULL;
}
#define PARSE_INT(valp_, mul_) \
if (*buf < '0' || '9' < *buf) { \
buf++; \
*ret = -1; \
return NULL; \
} \
*(valp_) = (mul_) * (*buf++ - '0');
#define PARSE_INT_3(valp_) \
do { \
int res_ = 0; \
PARSE_INT(&res_, 100) \
*valp_ = res_; \
PARSE_INT(&res_, 10) \
*valp_ += res_; \
PARSE_INT(&res_, 1) \
*valp_ += res_; \
} while (0)
/* returned pointer is always within [buf, buf_end), or null */
static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char,
int *ret)
{
/* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128
* bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */
static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */
"\"\"" /* 0x22 */
"()" /* 0x28,0x29 */
",," /* 0x2c */
"//" /* 0x2f */
":@" /* 0x3a-0x40 */
"[]" /* 0x5b-0x5d */
"{\xff"; /* 0x7b-0xff */
const char *buf_start = buf;
int found;
buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found);
if (!found) {
CHECK_EOF();
}
while (1) {
if (*buf == next_char) {
break;
} else if (!token_char_map[(unsigned char)*buf]) {
*ret = -1;
return NULL;
}
++buf;
CHECK_EOF();
}
*token = buf_start;
*token_len = buf - buf_start;
return buf;
}
/* returned pointer is always within [buf, buf_end), or null */
static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret)
{
/* we want at least [HTTP/1.<two chars>] to try to parse */
if (buf_end - buf < 9) {
*ret = -2;
return NULL;
}
EXPECT_CHAR_NO_CHECK('H');
EXPECT_CHAR_NO_CHECK('T');
EXPECT_CHAR_NO_CHECK('T');
EXPECT_CHAR_NO_CHECK('P');
EXPECT_CHAR_NO_CHECK('/');
EXPECT_CHAR_NO_CHECK('1');
EXPECT_CHAR_NO_CHECK('.');
PARSE_INT(minor_version, 1);
return buf;
}
static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers,
size_t max_headers, int *ret)
{
for (;; ++*num_headers) {
CHECK_EOF();
if (*buf == '\015') {
++buf;
EXPECT_CHAR('\012');
break;
} else if (*buf == '\012') {
++buf;
break;
}
if (*num_headers == max_headers) {
*ret = -1;
return NULL;
}
if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
/* parsing name, but do not discard SP before colon, see
* http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */
if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) {
return NULL;
}
if (headers[*num_headers].name_len == 0) {
*ret = -1;
return NULL;
}
++buf;
for (;; ++buf) {
CHECK_EOF();
if (!(*buf == ' ' || *buf == '\t')) {
break;
}
}
} else {
headers[*num_headers].name = NULL;
headers[*num_headers].name_len = 0;
}
const char *value;
size_t value_len;
if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) {
return NULL;
}
/* remove trailing SPs and HTABs */
const char *value_end = value + value_len;
for (; value_end != value; --value_end) {
const char c = *(value_end - 1);
if (!(c == ' ' || c == '\t')) {
break;
}
}
headers[*num_headers].value = value;
headers[*num_headers].value_len = value_end - value;
}
return buf;
}
static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path,
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers,
size_t max_headers, int *ret)
{
/* skip first empty line (some clients add CRLF after POST content) */
CHECK_EOF();
if (*buf == '\015') {
++buf;
EXPECT_CHAR('\012');
} else if (*buf == '\012') {
++buf;
}
/* parse request line */
if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) {
return NULL;
}
do {
++buf;
CHECK_EOF();
} while (*buf == ' ');
ADVANCE_TOKEN(*path, *path_len);
do {
++buf;
CHECK_EOF();
} while (*buf == ' ');
if (*method_len == 0 || *path_len == 0) {
*ret = -1;
return NULL;
}
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
return NULL;
}
if (*buf == '\015') {
++buf;
EXPECT_CHAR('\012');
} else if (*buf == '\012') {
++buf;
} else {
*ret = -1;
return NULL;
}
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
}
int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path,
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len)
{
const char *buf = buf_start, *buf_end = buf_start + len;
size_t max_headers = *num_headers;
int r;
*method = NULL;
*method_len = 0;
*path = NULL;
*path_len = 0;
*minor_version = -1;
*num_headers = 0;
/* if last_len != 0, check if the request is complete (a fast countermeasure
againt slowloris */
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
return r;
}
if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers,
&r)) == NULL) {
return r;
}
return (int)(buf - buf_start);
}
static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg,
size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret)
{
/* parse "HTTP/1.x" */
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
return NULL;
}
/* skip space */
if (*buf != ' ') {
*ret = -1;
return NULL;
}
do {
++buf;
CHECK_EOF();
} while (*buf == ' ');
/* parse status code, we want at least [:digit:][:digit:][:digit:]<other char> to try to parse */
if (buf_end - buf < 4) {
*ret = -2;
return NULL;
}
PARSE_INT_3(status);
/* get message including preceding space */
if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
return NULL;
}
if (*msg_len == 0) {
/* ok */
} else if (**msg == ' ') {
/* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP
* before running past the end of the given buffer. */
do {
++*msg;
--*msg_len;
} while (**msg == ' ');
} else {
/* garbage found after status code */
*ret = -1;
return NULL;
}
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
}
int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
struct phr_header *headers, size_t *num_headers, size_t last_len)
{
const char *buf = buf_start, *buf_end = buf + len;
size_t max_headers = *num_headers;
int r;
*minor_version = -1;
*status = 0;
*msg = NULL;
*msg_len = 0;
*num_headers = 0;
/* if last_len != 0, check if the response is complete (a fast countermeasure
against slowloris */
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
return r;
}
if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) {
return r;
}
return (int)(buf - buf_start);
}
int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len)
{
const char *buf = buf_start, *buf_end = buf + len;
size_t max_headers = *num_headers;
int r;
*num_headers = 0;
/* if last_len != 0, check if the response is complete (a fast countermeasure
against slowloris */
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
return r;
}
if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) {
return r;
}
return (int)(buf - buf_start);
}
enum {
CHUNKED_IN_CHUNK_SIZE,
CHUNKED_IN_CHUNK_EXT,
CHUNKED_IN_CHUNK_DATA,
CHUNKED_IN_CHUNK_CRLF,
CHUNKED_IN_TRAILERS_LINE_HEAD,
CHUNKED_IN_TRAILERS_LINE_MIDDLE
};
static int decode_hex(int ch)
{
if ('0' <= ch && ch <= '9') {
return ch - '0';
} else if ('A' <= ch && ch <= 'F') {
return ch - 'A' + 0xa;
} else if ('a' <= ch && ch <= 'f') {
return ch - 'a' + 0xa;
} else {
return -1;
}
}
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz)
{
size_t dst = 0, src = 0, bufsz = *_bufsz;
ssize_t ret = -2; /* incomplete */
decoder->_total_read += bufsz;
while (1) {
switch (decoder->_state) {
case CHUNKED_IN_CHUNK_SIZE:
for (;; ++src) {
int v;
if (src == bufsz)
goto Exit;
if ((v = decode_hex(buf[src])) == -1) {
if (decoder->_hex_count == 0) {
ret = -1;
goto Exit;
}
/* the only characters that may appear after the chunk size are BWS, semicolon, or CRLF */
switch (buf[src]) {
case ' ':
case '\011':
case ';':
case '\012':
case '\015':
break;
default:
ret = -1;
goto Exit;
}
break;
}
if (decoder->_hex_count == sizeof(size_t) * 2) {
ret = -1;
goto Exit;
}
decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v;
++decoder->_hex_count;
}
decoder->_hex_count = 0;
decoder->_state = CHUNKED_IN_CHUNK_EXT;
/* fallthru */
case CHUNKED_IN_CHUNK_EXT:
/* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] == '\012')
break;
}
++src;
if (decoder->bytes_left_in_chunk == 0) {
if (decoder->consume_trailer) {
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
break;
} else {
goto Complete;
}
}
decoder->_state = CHUNKED_IN_CHUNK_DATA;
/* fallthru */
case CHUNKED_IN_CHUNK_DATA: {
size_t avail = bufsz - src;
if (avail < decoder->bytes_left_in_chunk) {
if (dst != src)
memmove(buf + dst, buf + src, avail);
src += avail;
dst += avail;
decoder->bytes_left_in_chunk -= avail;
goto Exit;
}
if (dst != src)
memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk);
src += decoder->bytes_left_in_chunk;
dst += decoder->bytes_left_in_chunk;
decoder->bytes_left_in_chunk = 0;
decoder->_state = CHUNKED_IN_CHUNK_CRLF;
}
/* fallthru */
case CHUNKED_IN_CHUNK_CRLF:
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] != '\015')
break;
}
if (buf[src] != '\012') {
ret = -1;
goto Exit;
}
++src;
decoder->_state = CHUNKED_IN_CHUNK_SIZE;
break;
case CHUNKED_IN_TRAILERS_LINE_HEAD:
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] != '\015')
break;
}
if (buf[src++] == '\012')
goto Complete;
decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
/* fallthru */
case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] == '\012')
break;
}
++src;
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
break;
default:
assert(!"decoder is corrupt");
}
}
Complete:
ret = bufsz - src;
Exit:
if (dst != src)
memmove(buf + dst, buf + src, bufsz - src);
*_bufsz = dst;
/* if incomplete but the overhead of the chunked encoding is >=100KB and >80%, signal an error */
if (ret == -2) {
decoder->_total_overhead += bufsz - dst;
if (decoder->_total_overhead >= 100 * 1024 && decoder->_total_read - decoder->_total_overhead < decoder->_total_read / 4)
ret = -1;
}
return ret;
}
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder)
{
return decoder->_state == CHUNKED_IN_CHUNK_DATA;
}
#undef CHECK_EOF
#undef EXPECT_CHAR
#undef ADVANCE_TOKEN

90
source/picohttpparser.h Normal file
View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
* Shigeo Mitsunari
*
* The software is licensed under either the MIT License (below) or the Perl
* license.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef picohttpparser_h
#define picohttpparser_h
#include <stdint.h>
#include <sys/types.h>
#ifdef _MSC_VER
#define ssize_t intptr_t
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* contains name and value of a header (name == NULL if is a continuing line
* of a multiline header */
struct phr_header {
const char *name;
size_t name_len;
const char *value;
size_t value_len;
};
/* returns number of bytes consumed if successful, -2 if request is partial,
* -1 if failed */
int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
/* ditto */
int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
struct phr_header *headers, size_t *num_headers, size_t last_len);
/* ditto */
int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
/* should be zero-filled before start */
struct phr_chunked_decoder {
size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
char consume_trailer; /* if trailing headers should be consumed */
char _hex_count;
char _state;
uint64_t _total_read;
uint64_t _total_overhead;
};
/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
* encoding headers. When the function returns without an error, bufsz is
* updated to the length of the decoded data available. Applications should
* repeatedly call the function while it returns -2 (incomplete) every time
* supplying newly arrived data. If the end of the chunked-encoded data is
* found, the function returns a non-negative number indicating the number of
* octets left undecoded, that starts from the offset returned by `*bufsz`.
* Returns -1 on error.
*/
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
/* returns if the chunked decoder is in middle of chunked data */
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -53,7 +53,7 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target,
// Random function provided - call it for each bit
bd = blob_new((size_t)length_bits);
if (bd) {
bd->bit_length = length_bits;
bd->length = length_bits;
for (size_t i = 0; i < length_bits; i++) {
JSValue randval = JS_Call(ctx, argv[1], JS_UNDEFINED, 0, NULL);
if (JS_IsException(randval)) {
@@ -85,7 +85,7 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target,
if (!src) {
return JS_ThrowTypeError(ctx, "Blob constructor: argument 1 not a blob");
}
int64_t from = 0, to = (int64_t)src->bit_length;
int64_t from = 0, to = (int64_t)src->length;
if (argc >= 2 && JS_IsNumber(argv[1])) {
JS_ToInt64(ctx, &from, argv[1]);
if (from < 0) from = 0;
@@ -93,7 +93,7 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target,
if (argc >= 3 && JS_IsNumber(argv[2])) {
JS_ToInt64(ctx, &to, argv[2]);
if (to < from) to = from;
if (to > (int64_t)src->bit_length) to = (int64_t)src->bit_length;
if (to > (int64_t)src->length) to = (int64_t)src->length;
}
bd = blob_new_from_blob(src, (size_t)from, (size_t)to);
}
@@ -162,84 +162,41 @@ static JSValue js_blob_write_blob(JSContext *ctx, JSValueConst this_val,
}
// blob.write_dec64(number)
static JSValue js_blob_write_dec64(JSContext *ctx, JSValueConst this_val,
static JSValue js_blob_write_number(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1) {
return JS_ThrowTypeError(ctx, "write_dec64(number) requires 1 argument");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
if (!bd)
return JS_ThrowTypeError(ctx, "write_dec64: not called on a blob");
}
// Get the number as a double and convert to DEC64
double d;
if (JS_ToFloat64(ctx, &d, argv[0]) < 0) {
if (JS_ToFloat64(ctx, &d, argv[0]) < 0)
return JS_EXCEPTION;
}
if (blob_write_dec64(bd, d) < 0) {
if (blob_write_dec64(bd, d) < 0)
return JS_ThrowTypeError(ctx, "write_dec64: cannot write to stone blob or OOM");
}
return JS_UNDEFINED;
}
// blob.write_fit(fit, length)
static JSValue js_blob_write_fit(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 2) {
return JS_ThrowTypeError(ctx, "write_fit(fit, length) requires 2 arguments");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "write_fit: not called on a blob");
}
int64_t fit;
int32_t length;
if (JS_ToInt64(ctx, &fit, argv[0]) < 0) return JS_EXCEPTION;
if (JS_ToInt32(ctx, &length, argv[1]) < 0) return JS_EXCEPTION;
if (blob_write_fit(bd, fit, length) < 0) {
return JS_ThrowTypeError(ctx, "write_fit: cannot write (stone blob, OOM, or value out of range)");
}
return JS_UNDEFINED;
}
// blob.write_kim(fit)
static JSValue js_blob_write_kim(JSContext *ctx, JSValueConst this_val,
static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1) {
if (argc < 1)
return JS_ThrowTypeError(ctx, "write_kim(fit) requires 1 argument");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
if (!bd)
return JS_ThrowTypeError(ctx, "write_kim: not called on a blob");
}
// Handle number or single character string
int64_t value;
if (JS_IsString(argv[0])) {
const char *str = JS_ToCString(ctx, argv[0]);
if (!str) return JS_EXCEPTION;
const char *str = JS_ToCString(ctx, argv[0]);
// Get first UTF-32 character
// For simplicity, assuming ASCII for now
if (strlen(str) == 0) {
JS_FreeCString(ctx, str);
return JS_ThrowTypeError(ctx, "write_kim: empty string");
}
value = (unsigned char)str[0];
JS_FreeCString(ctx, str);
} else {
if (JS_ToInt64(ctx, &value, argv[0]) < 0) return JS_EXCEPTION;
}
if (blob_write_kim(bd, value) < 0) {
if (blob_write_text(bd, str) < 0)
return JS_ThrowTypeError(ctx, "write_kim: cannot write to stone blob or OOM");
}
return JS_UNDEFINED;
}
@@ -247,68 +204,18 @@ static JSValue js_blob_write_kim(JSContext *ctx, JSValueConst this_val,
// blob.write_pad(block_size)
static JSValue js_blob_write_pad(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1) {
if (argc < 1)
return JS_ThrowTypeError(ctx, "write_pad(block_size) requires 1 argument");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
if (!bd)
return JS_ThrowTypeError(ctx, "write_pad: not called on a blob");
}
int32_t block_size;
if (JS_ToInt32(ctx, &block_size, argv[0]) < 0) return JS_EXCEPTION;
if (blob_write_pad(bd, block_size) < 0) {
if (blob_write_pad(bd, block_size) < 0)
return JS_ThrowTypeError(ctx, "write_pad: cannot write (stone blob, OOM, or invalid block size)");
}
return JS_UNDEFINED;
}
// blob.write_text(text)
static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1) {
return JS_ThrowTypeError(ctx, "write_text(text) requires 1 argument");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "write_text: not called on a blob");
}
const char *str = JS_ToCString(ctx, argv[0]);
if (!str) return JS_EXCEPTION;
int result = blob_write_text(bd, str);
JS_FreeCString(ctx, str);
if (result < 0) {
return JS_ThrowTypeError(ctx, "write_text: cannot write to stone blob or OOM");
}
return JS_UNDEFINED;
}
// blob.write_text_raw(text)
static JSValue js_blob_write_text_raw(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1) {
return JS_ThrowTypeError(ctx, "write_text_raw(text) requires 1 argument");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "write_text_raw: not called on a blob");
}
const char *str = JS_ToCString(ctx, argv[0]);
if (!str) return JS_EXCEPTION;
int result = blob_write_text_raw(bd, str);
JS_FreeCString(ctx, str);
if (result < 0) {
return JS_ThrowTypeError(ctx, "write_text_raw: cannot write to stone blob or OOM");
}
return JS_UNDEFINED;
}
@@ -350,7 +257,7 @@ static JSValue js_blob_read_blob(JSContext *ctx, JSValueConst this_val,
}
int64_t from = 0;
int64_t to = bd->bit_length;
int64_t to = bd->length;
if (argc >= 1) {
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
@@ -358,7 +265,7 @@ static JSValue js_blob_read_blob(JSContext *ctx, JSValueConst this_val,
}
if (argc >= 2) {
if (JS_ToInt64(ctx, &to, argv[1]) < 0) return JS_EXCEPTION;
if (to > (int64_t)bd->bit_length) to = bd->bit_length;
if (to > (int64_t)bd->length) to = bd->length;
}
blob *new_bd = blob_read_blob(bd, from, to);
@@ -370,7 +277,7 @@ static JSValue js_blob_read_blob(JSContext *ctx, JSValueConst this_val,
}
// blob.read_dec64(from)
static JSValue js_blob_read_dec64(JSContext *ctx, JSValueConst this_val,
static JSValue js_blob_read_number(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1) {
return JS_ThrowTypeError(ctx, "read_dec64(from) requires 1 argument");
@@ -384,10 +291,13 @@ static JSValue js_blob_read_dec64(JSContext *ctx, JSValueConst this_val,
return JS_ThrowTypeError(ctx, "read_dec64: blob must be stone");
}
int64_t from;
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
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");
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");
}
@@ -395,65 +305,6 @@ static JSValue js_blob_read_dec64(JSContext *ctx, JSValueConst this_val,
return JS_NewFloat64(ctx, d);
}
// blob.read_fit(from, length)
static JSValue js_blob_read_fit(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 2) {
return JS_ThrowTypeError(ctx, "read_fit(from, length) requires 2 arguments");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "read_fit: not called on a blob");
}
if (!bd->is_stone) {
return JS_ThrowTypeError(ctx, "read_fit: blob must be stone");
}
int64_t from;
int32_t length;
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
if (JS_ToInt32(ctx, &length, argv[1]) < 0) return JS_EXCEPTION;
int64_t value;
if (blob_read_fit(bd, from, length, &value) < 0) {
return JS_ThrowRangeError(ctx, "read_fit: out of range or invalid length");
}
return JS_NewInt64(ctx, value);
}
// blob.read_kim(from)
static JSValue js_blob_read_kim(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1) {
return JS_ThrowTypeError(ctx, "read_kim(from) requires 1 argument");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "read_kim: not called on a blob");
}
if (!bd->is_stone) {
return JS_ThrowTypeError(ctx, "read_kim: blob must be stone");
}
int64_t from;
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
int64_t value;
size_t bits_read;
if (blob_read_kim(bd, from, &value, &bits_read) < 0) {
return JS_ThrowRangeError(ctx, "read_kim: out of range or invalid kim encoding");
}
// Return an object with the value and the number of bits read
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "value", JS_NewInt64(ctx, value));
JS_SetPropertyStr(ctx, obj, "bits_read", JS_NewInt64(ctx, bits_read));
return obj;
}
// blob.read_text(from)
static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
@@ -478,44 +329,7 @@ static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val,
JSValue result = JS_NewString(ctx, text);
free(text);
// Return object with text and total bits read
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "text", result);
JS_SetPropertyStr(ctx, obj, "bits_read", JS_NewInt64(ctx, bits_read));
return obj;
}
// blob.read_text_raw(from)
static JSValue js_blob_read_text_raw(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "read_text_raw: not called on a blob");
}
if (!bd->is_stone) {
return JS_ThrowTypeError(ctx, "read_text_raw: blob must be stone");
}
int64_t from = 0;
if (argc >= 1) {
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
}
char *text;
size_t bits_read;
if (blob_read_text_raw(bd, from, &text, &bits_read) < 0) {
return JS_ThrowRangeError(ctx, "read_text_raw: out of range or not byte-aligned");
}
JSValue result = JS_NewString(ctx, text);
free(text);
// Return object with text and total bits read
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "text", result);
JS_SetPropertyStr(ctx, obj, "bits_read", JS_NewInt64(ctx, bits_read));
return obj;
return result;
}
// blob.pad?(from, block_size)
@@ -561,42 +375,7 @@ static JSValue js_blob_get_length(JSContext *ctx, JSValueConst this_val, int mag
if (!bd) {
return JS_ThrowTypeError(ctx, "length: not called on a blob");
}
return JS_NewInt64(ctx, bd->bit_length);
}
// Static kim_length function
static JSValue js_blob_kim_length(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1) {
return JS_ThrowTypeError(ctx, "kim_length(value) requires 1 argument");
}
if (JS_IsBool(argv[0])) {
return JS_NewInt32(ctx, 1);
}
if (JS_IsNumber(argv[0])) {
int64_t value;
if (JS_ToInt64(ctx, &value, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewInt32(ctx, kim_length_for_fit(value));
}
if (JS_IsString(argv[0])) {
const char *str = JS_ToCString(ctx, argv[0]);
if (!str) return JS_EXCEPTION;
size_t len = strlen(str);
// Length encoding + each character encoding
int total_bits = kim_length_for_fit(len);
for (size_t i = 0; i < len; i++) {
total_bits += kim_length_for_fit((unsigned char)str[i]);
}
JS_FreeCString(ctx, str);
return JS_NewInt32(ctx, total_bits);
}
return JS_NULL;
return JS_NewInt64(ctx, bd->length);
}
// -----------------------------------------------------------------------------
@@ -607,19 +386,15 @@ static const JSCFunctionListEntry js_blob_funcs[] = {
// Write methods
JS_CFUNC_DEF("write_bit", 1, js_blob_write_bit),
JS_CFUNC_DEF("write_blob", 1, js_blob_write_blob),
JS_CFUNC_DEF("write_dec64", 1, js_blob_write_dec64),
JS_CFUNC_DEF("write_fit", 2, js_blob_write_fit),
JS_CFUNC_DEF("write_kim", 1, js_blob_write_kim),
JS_CFUNC_DEF("write_number", 1, js_blob_write_number),
JS_CFUNC_DEF("write_text", 1, js_blob_write_text),
JS_CFUNC_DEF("write_pad", 1, js_blob_write_pad),
JS_CFUNC_DEF("write_text", 1, js_blob_write_text_raw),
// Read methods
JS_CFUNC_DEF("read_logical", 1, js_blob_read_logical),
JS_CFUNC_DEF("read_blob", 2, js_blob_read_blob),
JS_CFUNC_DEF("read_dec64", 1, js_blob_read_dec64),
JS_CFUNC_DEF("read_fit", 2, js_blob_read_fit),
JS_CFUNC_DEF("read_kim", 1, js_blob_read_kim),
JS_CFUNC_DEF("read_text", 1, js_blob_read_text_raw),
JS_CFUNC_DEF("read_number", 1, js_blob_read_number),
JS_CFUNC_DEF("read_text", 1, js_blob_read_text),
JS_CFUNC_DEF("pad?", 2, js_blob_pad_q),
// Other methods
@@ -645,10 +420,6 @@ JSValue js_blob_use(JSContext *js) {
JS_SetConstructor(js, ctor, proto);
JS_FreeValue(js, proto);
// Add static kim_length method to constructor
JS_SetPropertyStr(js, ctor, "kim_length",
JS_NewCFunction(js, js_blob_kim_length, "kim_length", 1));
return ctor;
}
@@ -656,7 +427,7 @@ JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t bytes)
{
blob *b = blob_new(bytes*8);
memcpy(b->data, data, bytes);
b->bit_length = bytes * 8; // Set the actual length in bits
b->length = bytes * 8; // Set the actual length in bits
blob_make_stone(b);
return blob2js(js, b);
@@ -668,7 +439,7 @@ void *js_get_blob_data(JSContext *js, size_t *size, JSValue v)
if (!b || !b->is_stone)
return NULL;
*size = (b->bit_length + 7) / 8; // Return actual byte size based on bit length
*size = (b->length + 7) / 8; // Return actual byte size based on bit length
return b->data;
}

View File

@@ -2,6 +2,7 @@
#define QJS_BLOB_H
#include "cell.h"
#include "blob.h"
JSValue js_blob_use(JSContext *ctx);

View File

@@ -9,23 +9,6 @@ JSC_CCALL(debug_local_vars, return js_debugger_local_variables(js, js2number(js,
JSC_CCALL(debug_fn_info, return js_debugger_fn_info(js, argv[0]))
JSC_CCALL(debug_backtrace_fns, return js_debugger_backtrace_fns(js,NULL))
#include "blob.h"
void dump_writer(blob *b, const char *buf, size_t len)
{
blob_write_bytes(b, buf, len);
}
JSC_CCALL(debug_dump,
JSPrintValueOptions opt = {0};
opt.show_hidden=1;
blob *write = blob_new(8*100);
JS_PrintValue(js, dump_writer, write, argv[0], &opt);
int bytes = write->bit_length/8;
ret = JS_NewStringLen(js, write->data, bytes);
blob_destroy(write);
)
static const JSCFunctionListEntry js_debug_funcs[] = {
MIST_FUNC_DEF(debug, stack_depth, 0),
MIST_FUNC_DEF(debug, build_backtrace, 0),
@@ -33,7 +16,6 @@ static const JSCFunctionListEntry js_debug_funcs[] = {
MIST_FUNC_DEF(debug, local_vars, 1),
MIST_FUNC_DEF(debug, fn_info, 1),
MIST_FUNC_DEF(debug, backtrace_fns,0),
MIST_FUNC_DEF(debug, dump, 1),
};
JSValue js_debug_use(JSContext *js) {

File diff suppressed because it is too large Load Diff

View File

@@ -1,612 +1,387 @@
// Blob test suite
// blob_test.ce
var Blob = use('blob');
var time = use('time')
var os = use('os');
// A small tolerance for floating comparisons if needed
var EPSILON = 1e-12;
log.console("=== Blob Module Test Suite ===\n");
// Track test results
var testResults = {
type: 'test_results',
test_name: 'blob',
passed: 0,
failed: 0,
total: 0,
failures: [],
duration: 0
};
var passed = 0;
var failed = 0;
var startTime;
function deepCompare(expected, actual, path) {
if (!path) path = '';
// Basic triple-equals check
if (expected === actual) {
return { passed: true, messages: [] };
}
// Compare booleans
if (typeof expected === 'boolean' && typeof actual === 'boolean') {
return {
passed: false,
messages: [
'Boolean mismatch at ' + path + ': expected ' + expected + ', got ' + actual
]
};
}
// Compare numbers with tolerance
if (typeof expected === 'number' && typeof actual === 'number') {
if (isNaN(expected) && isNaN(actual)) {
return { passed: true, messages: [] };
function test(name, fn) {
try {
fn();
passed++;
log.console("✓ " + name);
} catch (e) {
failed++;
log.console("✗ " + name + ": " + e);
}
var diff = Math.abs(expected - actual);
if (diff <= EPSILON) {
return { passed: true, messages: [] };
}
return {
passed: false,
messages: [
'Number mismatch at ' + path + ': expected ' + expected + ', got ' + actual,
'Difference of ' + diff + ' > EPSILON (' + EPSILON + ')'
]
};
}
// Compare arrays
if (Array.isArray(expected) && Array.isArray(actual)) {
if (expected.length !== actual.length) {
return {
passed: false,
messages: [
'Array length mismatch at ' + path + ': expected len=' + expected.length + ', got len=' + actual.length
]
};
}
var messages = [];
for (var i = 0; i < expected.length; i++) {
var r = deepCompare(expected[i], actual[i], path + '[' + i + ']');
if (!r.passed) messages.push.apply(messages, r.messages);
}
return {
passed: messages.length === 0,
messages: messages
};
}
// Compare objects
if (
typeof expected === 'object' &&
expected !== null &&
typeof actual === 'object' &&
actual !== null
) {
var expKeys = Object.keys(expected).sort();
var actKeys = Object.keys(actual).sort();
if (JSON.stringify(expKeys) !== JSON.stringify(actKeys)) {
return {
passed: false,
messages: [
'Object keys mismatch at ' + path + ': expected [' + expKeys + '], got [' + actKeys + ']'
]
};
}
var messages = [];
for (var k = 0; k < expKeys.length; k++) {
var key = expKeys[k];
var r = deepCompare(expected[key], actual[key], path ? path + '.' + key : key);
if (!r.passed) messages.push.apply(messages, r.messages);
}
return { passed: messages.length === 0, messages: messages };
}
// If none of the above, treat as a mismatch
return {
passed: false,
messages: [
'Mismatch at ' + path + ': expected ' + JSON.stringify(expected) + ', got ' + JSON.stringify(actual)
]
};
}
// Helper to run a single test
function runTest(testName, testFn) {
var passed = true;
var messages = [];
try {
var result = testFn();
if (typeof result === 'object' && result !== null) {
passed = result.passed;
messages = result.messages || [];
} else {
// If the testFn returned a boolean or no return, interpret it
passed = !!result;
function assert(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
}
} catch (e) {
passed = false;
messages.push('Exception thrown: ' + (e.stack || e.toString()));
}
// Update results
testResults.total++;
if (passed) {
testResults.passed++;
} else {
testResults.failed++;
testResults.failures.push({
name: testName,
error: messages.join('\n')
});
}
// Log individual result
log.console(testName + ' - ' + (passed ? 'Passed' : 'Failed'));
if (!passed && messages.length > 0) {
log.console(' ' + messages.join('\n '));
}
}
// Test suite
var tests = [
// 1) Ensure we can create a blank blob
{
name: "new Blob() should produce an empty antestone blob of length 0",
run: function() {
var b = new Blob();
var length = b.length;
var passed = (b instanceof Blob && length === 0);
var messages = [];
if (!(b instanceof Blob)) messages.push("Returned object is not recognized as a blob");
if (length !== 0) messages.push('Expected length 0, got ' + length);
return { passed: passed, messages: messages };
function assertEqual(actual, expected, message) {
if (actual !== expected) {
throw new Error(message || "Expected " + expected + ", got " + actual);
}
},
}
// 2) Make a blob with some capacity
{
name: "new Blob(16) should create a blob with capacity >=16 bits and length=0",
run: function() {
var b = new Blob(16);
var isBlob = b instanceof Blob;
var length = b.length;
var passed = isBlob && length === 0;
var messages = [];
if (!isBlob) messages.push("Not recognized as a blob");
if (length !== 0) messages.push('Expected length=0, got ' + length);
return { passed: passed, messages: messages };
}
},
// 3) Make a blob with (length, logical) - but can't read until stone
{
name: "new Blob(5, true) should create a blob of length=5 bits, all 1s - needs stone to read",
run: function() {
var b = new Blob(5, true);
var len = b.length;
if (len !== 5) {
return {
passed: false,
messages: ['Expected length=5, got ' + len]
};
}
// Try to read before stone - should return null
var bitVal = b.read_logical(0);
if (bitVal !== null) {
return {
passed: false,
messages: ['Expected null when reading antestone blob, got ' + bitVal]
};
}
// Stone it
stone(b);
// Now check bits
for (var i = 0; i < 5; i++) {
bitVal = b.read_logical(i);
if (bitVal !== true) {
return {
passed: false,
messages: ['Bit at index ' + i + ' expected true, got ' + bitVal]
};
}
}
return { passed: true, messages: [] };
}
},
// 4) Write bits to an empty blob, then stone and read
{
name: "write_bit() on an empty blob, then stone and read_logical() to verify bits",
run: function() {
var b = new Blob(); // starts length=0
// write bits: true, false, true
b.write_bit(true); // bit #0
b.write_bit(false); // bit #1
b.write_bit(true); // bit #2
var len = b.length;
if (len !== 3) {
return {
passed: false,
messages: ['Expected length=3, got ' + len]
};
}
// Must stone before reading
stone(b);
var bits = [
b.read_logical(0),
b.read_logical(1),
b.read_logical(2)
];
var compare = deepCompare([true, false, true], bits);
return compare;
}
},
// 5) Stone a blob, then attempt to write -> fail
{
name: "Stoning a blob should prevent further writes",
run: function() {
var b = new Blob(5, false);
// Stone it
stone(b);
// Try to write
var passed = true;
var messages = [];
try {
b.write_bit(true);
passed = false;
messages.push("Expected an error or refusal when writing to a stone blob, but none occurred");
} catch (e) {
// We expect an exception or some error scenario
}
return { passed: passed, messages: messages };
}
},
// 6) make(blob, from, to) - copying range from an existing blob
{
name: "new Blob(existing_blob, from, to) can copy partial range",
run: function() {
// Create a 10-bit blob: pattern T F T F T F T F T F
var original = new Blob();
for (var i = 0; i < 10; i++) {
original.write_bit(i % 2 === 0);
}
// Copy bits [2..7)
var copy = new Blob(original, 2, 7);
var len = copy.length;
if (len !== 5) {
return {
passed: false,
messages: ['Expected length=5, got ' + len]
};
}
// Stone the copy to read from it
stone(copy);
var bits = [];
for (var i = 0; i < len; i++) {
bits.push(copy.read_logical(i));
}
var compare = deepCompare([true, false, true, false, true], bits);
return compare;
}
},
// 7) Checking instanceof
{
name: "instanceof should correctly identify blob vs. non-blob",
run: function() {
var b = new Blob();
var isB = b instanceof Blob;
var isNum = 42 instanceof Blob;
var isObj = { length: 3 } instanceof Blob;
var passed = (isB === true && isNum === false && isObj === false);
var messages = [];
if (!passed) {
messages.push('Expected (b instanceof Blob)=true, (42 instanceof Blob)=false, ({} instanceof Blob)=false; got ' + isB + ', ' + isNum + ', ' + isObj);
}
return { passed: passed, messages: messages };
}
},
// 8) Test write_blob
{
name: "write_blob() should append one blob to another",
run: function() {
var b1 = new Blob();
b1.write_bit(true);
b1.write_bit(false);
var b2 = new Blob();
b2.write_bit(true);
b2.write_bit(true);
b1.write_blob(b2);
if (b1.length !== 4) {
return {
passed: false,
messages: ['Expected length=4 after write_blob, got ' + b1.length]
};
}
stone(b1);
var bits = [];
for (var i = 0; i < 4; i++) {
bits.push(b1.read_logical(i));
}
return deepCompare([true, false, true, true], bits);
}
},
// 9) Test write_fit and read_fit
{
name: "write_fit() and read_fit() should handle fixed-size bit fields",
run: function() {
var b = new Blob();
b.write_fit(5, 3); // Write value 5 in 3 bits (101)
b.write_fit(7, 4); // Write value 7 in 4 bits (0111)
if (b.length !== 7) {
return {
passed: false,
messages: ['Expected length=7, got ' + b.length]
};
}
stone(b);
var val1 = b.read_fit(0, 3);
var val2 = b.read_fit(3, 4);
if (val1 !== 5 || val2 !== 7) {
return {
passed: false,
messages: ['Expected read_fit to return 5 and 7, got ' + val1 + ' and ' + val2]
};
}
return { passed: true, messages: [] };
}
},
// 10) Test write_kim and read_kim - SKIPPED due to native error
{
name: "write_kim() and read_kim() should handle kim encoding",
run: function() {
// Skip this test as it's causing native errors
return { passed: true, messages: ['Test skipped due to native implementation issues'] };
}
},
// 11) Test write_text and read_text
{
name: "write_text() and read_text() should handle text encoding",
run: function() {
var b = new Blob();
b.write_text("Hello");
stone(b);
var result = b.read_text(0);
if (result.text !== "Hello") {
return {
passed: false,
messages: ['Expected text "Hello", got "' + result.text + '"']
};
}
return { passed: true, messages: [] };
}
},
// 12) Test write_dec64 and read_dec64
{
name: "write_dec64() and read_dec64() should handle decimal encoding",
run: function() {
var b = new Blob();
b.write_dec64(3.14159);
b.write_dec64(-42.5);
stone(b);
var val1 = b.read_dec64(0);
var val2 = b.read_dec64(64);
// Allow small floating point differences
var diff1 = Math.abs(val1 - 3.14159);
var diff2 = Math.abs(val2 - (-42.5));
if (diff1 > EPSILON || diff2 > EPSILON) {
return {
passed: false,
messages: ['Expected dec64 values 3.14159 and -42.5, got ' + val1 + ' and ' + val2]
};
}
return { passed: true, messages: [] };
}
},
// 13) Test write_pad and pad?
{
name: "write_pad() and pad?() should handle block padding",
run: function() {
var b = new Blob();
b.write_bit(true);
b.write_bit(false);
b.write_bit(true);
// Length is now 3
b.write_pad(8); // Pad to multiple of 8
if (b.length !== 8) {
return {
passed: false,
messages: ['Expected length=8 after padding, got ' + b.length]
};
}
stone(b);
// Check pad? function
var isPadded = b["pad?"](3, 8);
if (!isPadded) {
return {
passed: false,
messages: ['Expected pad?(3, 8) to return true']
};
}
// Verify padding pattern: original bits, then 1, then 0s
var bits = [];
for (var i = 0; i < 8; i++) {
bits.push(b.read_logical(i));
}
return deepCompare([true, false, true, true, false, false, false, false], bits);
}
},
// 14) Test Blob.kim_length static function
{
name: "Blob.kim_length() should calculate correct kim encoding lengths",
run: function() {
var len1 = Blob.kim_length(42); // Should be 8 bits
var len2 = Blob.kim_length(1000); // Should be 16 bits
var len3 = Blob.kim_length("Hello"); // 8 bits for length + 8*5 for chars = 48
if (len1 !== 8) {
return {
passed: false,
messages: ['Expected kim_length(42)=8, got ' + len1]
};
}
if (len2 !== 16) {
return {
passed: false,
messages: ['Expected kim_length(1000)=16, got ' + len2]
};
}
if (len3 !== 48) {
return {
passed: false,
messages: ['Expected kim_length("Hello")=48, got ' + len3]
};
}
return { passed: true, messages: [] };
}
},
// 15) Test write_bit with numeric 0 and 1
{
name: "write_bit() should accept 0, 1, true, false",
run: function() {
var b = new Blob();
b.write_bit(1);
b.write_bit(0);
b.write_bit(true);
b.write_bit(false);
stone(b);
var bits = [];
for (var i = 0; i < 4; i++) {
bits.push(b.read_logical(i));
}
return deepCompare([true, false, true, false], bits);
}
},
// 16) Test read_blob to create copies
{
name: "read_blob() should create partial copies of stone blobs",
run: function() {
var b = new Blob();
for (var i = 0; i < 10; i++) {
b.write_bit(i % 3 === 0); // Pattern: T,F,F,T,F,F,T,F,F,T
}
stone(b);
var copy = b.read_blob(3, 7); // Extract bits 3-6
stone(copy);
if (copy.length !== 4) {
return {
passed: false,
messages: ['Expected copy length=4, got ' + copy.length]
};
}
var bits = [];
for (var i = 0; i < 4; i++) {
bits.push(copy.read_logical(i));
}
// Bits 3-6 from original: T,F,F,T
return deepCompare([true, false, false, true], bits);
}
},
// 17) Test random blob creation
{
name: "new Blob(length, random_func) should create random blob",
run: function() {
// Simple random function that alternates
var counter = 0;
var randomFunc = function() { return counter++; };
var b = new Blob(8, randomFunc);
stone(b);
if (b.length !== 8) {
return {
passed: false,
messages: ['Expected length=8, got ' + b.length]
};
}
// Check pattern matches counter LSB: 0,1,0,1,0,1,0,1
var bits = [];
for (var i = 0; i < 8; i++) {
bits.push(b.read_logical(i));
}
return deepCompare([false, true, false, true, false, true, false, true], bits);
}
}
];
// Message receiver
$_.receiver(function(msg) {
if (msg.type === 'run_tests') {
log.console("HERE")
startTime = time.number();
// Run all tests
for (var i = 0; i < tests.length; i++) {
var test = tests[i];
runTest(test.name, test.run);
}
// Calculate duration
testResults.duration = time.number() - startTime;
// Send results back
send(msg, testResults);
}
// Test 1: Empty blob creation
test("Create empty blob", function() {
var b = new Blob();
assertEqual(b.length, 0, "Empty blob should have length 0");
});
// Test 2: Blob with capacity
test("Create blob with capacity", function() {
var b = new Blob(100);
assertEqual(b.length, 0, "New blob with capacity should still have length 0");
});
// Test 3: Write and read single bit
test("Write and read single bit", function() {
var b = new Blob();
b.write_bit(true);
b.write_bit(false);
b.write_bit(1);
b.write_bit(0);
assertEqual(b.length, 4, "Should have 4 bits after writing");
b.stone(); // Make it stone to read
assertEqual(b.read_logical(0), true, "First bit should be true");
assertEqual(b.read_logical(1), false, "Second bit should be false");
assertEqual(b.read_logical(2), true, "Third bit should be true (1)");
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() {
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");
});
// Test 5: Write and read numbers
test("Write and read numbers", function() {
var b = new Blob();
b.write_number(3.14159);
b.write_number(-42);
b.write_number(0);
b.write_number(1e20);
b.stone();
// Read back the numbers
assertEqual(b.read_number(0), 3.14159, "First number should match");
assertEqual(b.read_number(64), -42, "Second number should match");
assertEqual(b.read_number(128), 0, "Third number should match");
assertEqual(b.read_number(192), 1e20, "Fourth number should match");
});
// Test 6: Write and read text
test("Write and read text", function() {
var b = new Blob();
b.write_text("Hello");
b.write_text("World");
b.write_text("🎉"); // Unicode test
b.stone();
assertEqual(b.read_text(0), "Hello", "First text should match");
// Note: We need to know bit positions to read subsequent strings
});
// Test 7: Write and read blobs
test("Write and read blobs", function() {
var b1 = new Blob();
b1.write_bit(true);
b1.write_bit(false);
b1.write_bit(true);
var b2 = new Blob(10); // Give it some initial capacity
b2.write_blob(b1);
b2.write_bit(false);
assertEqual(b2.length, 4, "Combined blob should have 4 bits");
b2.stone();
assertEqual(b2.read_logical(0), true);
assertEqual(b2.read_logical(1), false);
assertEqual(b2.read_logical(2), true);
assertEqual(b2.read_logical(3), false);
});
// Test 8: Copy constructor
test("Blob copy constructor", function() {
var b1 = new Blob();
b1.write_bit(true);
b1.write_bit(false);
b1.write_bit(true);
b1.write_bit(true);
b1.stone();
var b2 = new Blob(b1);
b2.stone(); // Need to stone the copy before reading
assertEqual(b2.length, 4, "Copied blob should have same length");
assertEqual(b2.read_logical(0), true);
assertEqual(b2.read_logical(3), true);
});
// Test 9: Partial copy constructor
test("Blob partial copy constructor", function() {
var b1 = new Blob();
for (var i = 0; i < 10; i++) {
b1.write_bit(i % 2 === 0);
}
b1.stone();
var b2 = new Blob(b1, 2, 7); // Copy bits 2-6 (5 bits)
b2.stone(); // Need to stone the copy before reading
assertEqual(b2.length, 5, "Partial copy should have 5 bits");
assertEqual(b2.read_logical(0), true); // bit 2 of original
assertEqual(b2.read_logical(2), true); // bit 4 of original
});
// Test 10: Blob with fill
test("Create blob with fill", function() {
var b1 = new Blob(8, true); // 8 bits all set to 1
var b2 = new Blob(8, false); // 8 bits all set to 0
b1.stone();
b2.stone();
for (var i = 0; i < 8; i++) {
assertEqual(b1.read_logical(i), true, "Bit " + i + " should be true");
assertEqual(b2.read_logical(i), false, "Bit " + i + " should be false");
}
});
// Test 11: Blob with random function
test("Create blob with random function", function() {
var sequence = [true, false, true, true, false];
var index = 0;
var b = new Blob(5, function() {
return sequence[index++] ? 1 : 0;
});
b.stone();
for (var i = 0; i < 5; i++) {
assertEqual(b.read_logical(i), sequence[i], "Bit " + i + " should match sequence");
}
});
// Test 12: Write pad and pad check
test("Write pad and check padding", function() {
var b = new Blob();
b.write_bit(true);
b.write_bit(false);
b.write_bit(true);
b.write_pad(8); // Pad to 8-bit boundary
assertEqual(b.length, 8, "Should be padded to 8 bits");
b.stone();
assert(b['pad?'](3, 8), "Should detect valid padding at position 3");
assert(!b['pad?'](2, 8), "Should detect invalid padding at position 2");
});
// Test 13: read_blob method
test("Read blob from stone blob", function() {
var b1 = new Blob();
for (var i = 0; i < 16; i++) {
b1.write_bit(i % 3 === 0);
}
b1.stone();
var b2 = b1.read_blob(4, 12); // Read bits 4-11 (8 bits)
b2.stone(); // Need to stone the read blob before reading from it
assertEqual(b2.length, 8, "Read blob should have 8 bits");
// Check the pattern
assertEqual(b2.read_logical(2), true); // Original bit 6 (6 % 3 === 0)
assertEqual(b2.read_logical(5), true); // Original bit 9 (9 % 3 === 0)
});
// Test 14: Stone immutability
test("Stone blob is immutable", function() {
var b = new Blob();
b.write_bit(true);
b.stone();
var threw = false;
try {
b.write_bit(false); // Should fail on stone blob
} catch (e) {
threw = true;
}
assert(threw, "Writing to stone blob should throw error");
});
// Test 15: Multiple stone calls
test("Multiple stone calls are safe", function() {
var b = new Blob();
b.write_bit(true);
b.stone();
b.stone(); // Should be safe to call again
assertEqual(b.read_logical(0), true, "Blob data should remain intact");
});
// Test 16: Invalid constructor arguments
test("Invalid constructor arguments", function() {
var threw = false;
try {
var b = new Blob("invalid");
} catch (e) {
threw = true;
}
assert(threw, "Invalid constructor arguments should throw");
});
// Test 17: Write invalid bit values
test("Write bit validation", function() {
var b = new Blob();
b.write_bit(0);
b.write_bit(1);
var threw = false;
try {
b.write_bit(2); // Should only accept 0, 1, true, false
} catch (e) {
threw = true;
}
assert(threw, "write_bit with value 2 should throw");
});
// Test 18: Complex data round-trip
test("Complex data round-trip", function() {
var b = new Blob();
// Write mixed data
b.write_text("Test");
b.write_number(123.456);
b.write_bit(true);
b.write_bit(false);
b.write_number(-999.999);
var originalLength = b.length;
b.stone();
// Verify we can create a copy
var b2 = new Blob(b);
b2.stone(); // Need to stone the copy before reading
assertEqual(b2.length, originalLength, "Copy should have same length");
assertEqual(b2.read_text(0), "Test", "First text should match");
});
// Test 19: Zero capacity blob
test("Zero capacity blob", function() {
var b = new Blob(0);
assertEqual(b.length, 0, "Zero capacity blob should have length 0");
b.write_bit(true); // Should auto-expand
assertEqual(b.length, 1, "Should expand when writing");
});
// Test 20: Large blob handling
test("Large blob handling", function() {
var b = new Blob();
var testSize = 1000;
// Write many bits
for (var i = 0; i < testSize; i++) {
b.write_bit(i % 7 === 0);
}
assertEqual(b.length, testSize, "Should have " + testSize + " bits");
b.stone();
// Verify pattern
assertEqual(b.read_logical(0), true, "Bit 0 should be true");
assertEqual(b.read_logical(7), true, "Bit 7 should be true");
assertEqual(b.read_logical(14), true, "Bit 14 should be true");
assertEqual(b.read_logical(15), false, "Bit 15 should be false");
});
// Test 21: Non-stone blob read methods should throw
test("Non-stone blob read methods should throw", function() {
var b = new Blob();
b.write_bit(true);
b.write_number(42);
b.write_text("test");
// Try to read without stoning - should throw
var threw = false;
try {
b.read_number(0);
} catch (e) {
threw = true;
}
assert(threw, "read_number on non-stone blob should throw");
threw = false;
try {
b.read_text(0);
} catch (e) {
threw = true;
}
assert(threw, "read_text on non-stone blob should throw");
threw = false;
try {
b.read_blob(0, 10);
} catch (e) {
threw = true;
}
assert(threw, "read_blob on non-stone blob should throw");
threw = false;
try {
b['pad?'](0, 8);
} catch (e) {
threw = true;
}
assert(threw, "pad? on non-stone blob should throw");
});
// Test 22: Empty text write and read
test("Empty text write and read", function() {
var b = new Blob();
b.write_text("");
b.stone();
assertEqual(b.read_text(0), "", "Empty string should round-trip");
});
// Test 23: Blob error on invalid read positions
test("Invalid read positions", function() {
var b = new Blob();
b.write_number(42);
b.stone();
var threw = false;
try {
b.read_number(-10); // Negative position
} catch (e) {
threw = true;
}
assert(threw, "Negative position should throw");
threw = false;
try {
b.read_number(1000); // Beyond blob length
} catch (e) {
threw = true;
}
assert(threw, "Position beyond length should throw");
});
// Print summary
log.console("\n=== Test Summary ===");
log.console("Total tests: " + (passed + failed));
log.console("Passed: " + passed);
log.console("Failed: " + failed);
log.console("\nOverall: " + (failed === 0 ? "PASSED" : "FAILED"));
os.exit(failed === 0 ? 0 : 1);

View File

@@ -2,9 +2,6 @@
var http = use('http');
var os = use('os');
var getter = http.fetch('tortoise.png')
var file = getter.data() // invokes the data stream to wait for it
var got = false
var count = 0
http.fetch("https://dictionary.ink/find?word=theological", {
@@ -19,10 +16,6 @@ http.fetch("https://dictionary.ink/find?word=theological", {
}
})
while (!got) {
http.poll()
}
log.console(`got hit ${count} times`)
os.exit()

View File

@@ -19,7 +19,7 @@ baz = "qux@2.0"
[arrays]
items = ["one", "two", "three"]
`
var parsed = toml.parse(test_toml)
var parsed = toml.decode(test_toml)
log.console("Parsed module: " + parsed.module)
log.console("Dependencies: " + json.encode(parsed.dependencies))
log.console("✓ TOML parser working")