fix http; faster blob; list and help commands
This commit is contained in:
@@ -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"
|
||||
@@ -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 += [
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
44
scripts/help.ce
Normal 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()
|
||||
@@ -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
75
scripts/list.ce
Normal 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()
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
642
source/blob.h
642
source/blob.h
@@ -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,346 +64,293 @@ 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;
|
||||
|
||||
blob_init(b);
|
||||
|
||||
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;
|
||||
// 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;
|
||||
|
||||
int total_bits = m - n + 1;
|
||||
if (total_bits <= 0) return;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
size_t bit_index = b->bit_length;
|
||||
if (blob_ensure_capacity(b, 1) < 0) return -1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int blob_write_text(blob *b, const char *text) {
|
||||
if (!b || !text || b->is_stone) return -1;
|
||||
|
||||
size_t len = strlen(text);
|
||||
|
||||
// Write kim-encoded length
|
||||
if (blob_write_kim(b, len) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
// Ensure capacity for 64-bit length + len*8 bits of text
|
||||
if (blob_ensure_capacity(b, 64 + (len * 8)) < 0) return -1;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
*out_bit = (b->data[byte_index] & (1 << offset_in_byte)) ? 1 : 0;
|
||||
@@ -430,175 +364,65 @@ 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;
|
||||
|
||||
// Read kim-encoded length
|
||||
int64_t length;
|
||||
size_t len_bits;
|
||||
if (blob_read_kim(b, from, &length, &len_bits) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (length < 0) return -1;
|
||||
|
||||
size_t pos = from + len_bits;
|
||||
|
||||
// Read characters
|
||||
char *str = malloc(length + 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';
|
||||
|
||||
*out_text = str;
|
||||
*bits_read = pos - from;
|
||||
return 0;
|
||||
}
|
||||
// Need at least 64 bits for length prefix
|
||||
if (from + 64 > b->length) return -1;
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// Allocate (len + 1) bytes for C-string
|
||||
char *str = malloc(len + 1);
|
||||
if (!str) return -1;
|
||||
|
||||
memcpy(str, b->data + from_byte, length_bytes);
|
||||
str[length_bytes] = '\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 = length_bytes * 8;
|
||||
*bits_read = 64 + (len * 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#endif /* BLOB_IMPLEMENTATION */
|
||||
|
||||
#endif /* BLOB_H */
|
||||
#endif /* BLOB_H */
|
||||
|
||||
@@ -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
685
source/picohttpparser.c
Normal 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
90
source/picohttpparser.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
// 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) {
|
||||
const char *str = JS_ToCString(ctx, argv[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;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define QJS_BLOB_H
|
||||
|
||||
#include "cell.h"
|
||||
#include "blob.h"
|
||||
|
||||
JSValue js_blob_use(JSContext *ctx);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
1157
source/qjs_http.c
1157
source/qjs_http.c
File diff suppressed because it is too large
Load Diff
973
tests/blob.ce
973
tests/blob.ce
@@ -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')
|
||||
}
|
||||
|
||||
function assertEqual(actual, expected, message) {
|
||||
if (actual !== expected) {
|
||||
throw new Error(message || "Expected " + expected + ", got " + actual);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
// 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 };
|
||||
}
|
||||
},
|
||||
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
|
||||
// Calculate duration
|
||||
testResults.duration = time.number() - startTime;
|
||||
assertEqual(b.length, testSize, "Should have " + testSize + " bits");
|
||||
b.stone();
|
||||
|
||||
// Send results back
|
||||
send(msg, testResults);
|
||||
}
|
||||
});
|
||||
// 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);
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user