fix base32 conversion/deconversion

This commit is contained in:
2025-07-11 19:38:49 -05:00
parent 6ff50cb521
commit 39152c1eb2
3 changed files with 93 additions and 33 deletions

View File

@@ -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'}
)

View File

@@ -410,4 +410,6 @@ function format_number(num, format) {
return null;
}
text.base32_to_blob = that.base32_to_blob
return text;

View File

@@ -1,7 +1,6 @@
#include "qjs_text.h"
#include "qjs_blob.h"
#include "blob.h"
#include "jsffi.h"
#include <string.h>
#include <stdlib.h>
@@ -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)