From 39152c1eb26d54bae967189070a9d0394b89a7c2 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 11 Jul 2025 19:38:49 -0500 Subject: [PATCH] fix base32 conversion/deconversion --- prosperon/prosperon.ce | 1 - scripts/text.cm | 2 + source/qjs_text.c | 123 ++++++++++++++++++++++++++++++----------- 3 files changed, 93 insertions(+), 33 deletions(-) diff --git a/prosperon/prosperon.ce b/prosperon/prosperon.ce index 26efc632..0235d6ac 100644 --- a/prosperon/prosperon.ce +++ b/prosperon/prosperon.ce @@ -402,7 +402,6 @@ function create_batch(draw_cmds, done) { batch.push( {op:'set', prop:'drawColor', value:{r:1,g:1,b:1,a:1}}, -// {op:'debugText', data:{pos:{x:10,y:10}, text:`Fps: ${(1/frame_avg).toFixed(2)}`}}, {op:'imgui_render'}, {op:'present'} ) diff --git a/scripts/text.cm b/scripts/text.cm index 77a04a1d..d081b623 100644 --- a/scripts/text.cm +++ b/scripts/text.cm @@ -410,4 +410,6 @@ function format_number(num, format) { return null; } +text.base32_to_blob = that.base32_to_blob + return text; \ No newline at end of file diff --git a/source/qjs_text.c b/source/qjs_text.c index e1e317ba..57af724d 100644 --- a/source/qjs_text.c +++ b/source/qjs_text.c @@ -1,7 +1,6 @@ #include "qjs_text.h" #include "qjs_blob.h" #include "blob.h" -#include "jsffi.h" #include #include @@ -9,81 +8,141 @@ JSC_CCALL(text_blob_to_hex, size_t blob_len; void *blob_data = js_get_blob_data(js, &blob_len, argv[0]); if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob"); - uint8_t *bytes = (uint8_t *)blob_data; - - // Hex encoding: each byte becomes 2 hex characters size_t hex_len = blob_len * 2; char *hex_str = malloc(hex_len + 1); - + if (!hex_str) return JS_ThrowOutOfMemory(js); static const char hex_digits[] = "0123456789ABCDEF"; - - for (size_t i = 0; i < blob_len; i++) { + for (size_t i = 0; i < blob_len; ++i) { hex_str[i * 2] = hex_digits[(bytes[i] >> 4) & 0xF]; hex_str[i * 2 + 1] = hex_digits[bytes[i] & 0xF]; } - hex_str[hex_len] = '\0'; - ret = JS_NewString(js, hex_str); + JSValue val = JS_NewString(js, hex_str); free(hex_str); + return val; ) JSC_CCALL(text_blob_to_base32, size_t blob_len; void *blob_data = js_get_blob_data(js, &blob_len, argv[0]); if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob"); - uint8_t *bytes = (uint8_t *)blob_data; - static const char b32_digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - // Calculate output length: each 5 bytes becomes 8 base32 chars - size_t groups = (blob_len + 4) / 5; + // Calculate exact output length needed + size_t groups = (blob_len + 4) / 5; // Round up to next group of 5 size_t b32_len = groups * 8; char *b32_str = malloc(b32_len + 1); + if (!b32_str) return JS_ThrowOutOfMemory(js); size_t in_idx = 0; size_t out_idx = 0; while (in_idx < blob_len) { - // Process 5 bytes (40 bits) at a time to produce 8 base32 chars + // Read up to 5 bytes into a 40-bit buffer uint64_t buf = 0; - int bytes_read = 0; + int bytes_to_read = (blob_len - in_idx < 5) ? (blob_len - in_idx) : 5; - // Read up to 5 bytes into buffer - for (int i = 0; i < 5 && in_idx < blob_len; i++) { + for (int i = 0; i < bytes_to_read; ++i) { buf = (buf << 8) | bytes[in_idx++]; - bytes_read++; } - // Pad with zeros if we read fewer than 5 bytes - buf = buf << (8 * (5 - bytes_read)); + // Pad buffer to 40 bits if we read fewer than 5 bytes + buf <<= 8 * (5 - bytes_to_read); - // Extract 8 groups of 5 bits from the 40-bit buffer - for (int i = 7; i >= 0; i--) { - b32_str[out_idx + i] = b32_digits[buf & 0x1F]; - buf >>= 5; + // Extract 8 groups of 5 bits each + for (int i = 0; i < 8; ++i) { + b32_str[out_idx++] = b32_digits[(buf >> (35 - i * 5)) & 0x1F]; } - out_idx += 8; } - // Replace trailing chars with padding if needed - int padding = (5 - (blob_len % 5)) % 5; - if (padding > 0) { - static const int pad_chars[] = {0, 6, 4, 3, 1}; - for (int i = 0; i < pad_chars[padding]; i++) { + // Add padding if necessary + if (blob_len % 5 != 0) { + static const int pad_count[] = {0, 6, 4, 3, 1}; // padding for 0,1,2,3,4 bytes + int padding = pad_count[blob_len % 5]; + for (int i = 0; i < padding; ++i) { b32_str[b32_len - 1 - i] = '='; } } b32_str[b32_len] = '\0'; - ret = JS_NewString(js, b32_str); + JSValue val = JS_NewString(js, b32_str); free(b32_str); + return val; +) + +static int base32_char_to_val(char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a'; + if (c >= '2' && c <= '7') return c - '2' + 26; + return -1; +} + +JSC_CCALL(text_base32_to_blob, + const char *str = JS_ToCString(js, argv[0]); + if (!str) return JS_ThrowTypeError(js, "Expected string"); + size_t str_len = strlen(str); + if (str_len == 0) { + JS_FreeCString(js, str); + return JS_ThrowTypeError(js, "Empty base32 string"); + } + + // Remove padding to get effective length + size_t effective_len = str_len; + while (effective_len > 0 && str[effective_len - 1] == '=') { + effective_len--; + } + + // Calculate output length: each group of 8 base32 chars -> 5 bytes + size_t output_len = (effective_len * 5) / 8; + uint8_t *output = malloc(output_len); + if (!output) { + JS_FreeCString(js, str); + return JS_ThrowOutOfMemory(js); + } + + size_t in_idx = 0; + size_t out_idx = 0; + + // Process in groups of 8 characters (40 bits -> 5 bytes) + while (in_idx < effective_len) { + uint64_t buf = 0; + int chars_to_read = (effective_len - in_idx < 8) ? (effective_len - in_idx) : 8; + + // Read up to 8 base32 characters into buffer + for (int i = 0; i < chars_to_read; ++i) { + int val = base32_char_to_val(str[in_idx++]); + if (val < 0) { + free(output); + JS_FreeCString(js, str); + return JS_ThrowTypeError(js, "Invalid base32 character"); + } + buf = (buf << 5) | val; + } + + // Calculate how many bytes we can extract + int bytes_to_extract = (chars_to_read * 5) / 8; + + // Shift buffer to align the most significant bits + buf <<= (40 - chars_to_read * 5); + + // Extract bytes from most significant to least significant + for (int i = 0; i < bytes_to_extract && out_idx < output_len; ++i) { + output[out_idx++] = (buf >> (32 - i * 8)) & 0xFF; + } + } + + JSValue val = js_new_blob_stoned_copy(js, output, output_len); + free(output); + JS_FreeCString(js, str); + return val; ) static const JSCFunctionListEntry js_text_funcs[] = { MIST_FUNC_DEF(text, blob_to_hex, 1), MIST_FUNC_DEF(text, blob_to_base32, 1), + MIST_FUNC_DEF(text, base32_to_blob, 1), }; JSValue js_text_use(JSContext *js) @@ -91,4 +150,4 @@ JSValue js_text_use(JSContext *js) JSValue mod = JS_NewObject(js); JS_SetPropertyFunctionList(js, mod, js_text_funcs, countof(js_text_funcs)); return mod; -} +} \ No newline at end of file