Some checks failed
Build and Deploy / build-linux (push) Failing after 1m29s
Build and Deploy / build-macos (push) Failing after 7s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
558 lines
14 KiB
C
558 lines
14 KiB
C
#ifndef BLOB_H
|
|
#define BLOB_H
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// A simple blob structure that can be in two states:
|
|
// - antestone (mutable): writing is allowed
|
|
// - 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.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
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;
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
|
|
// Create a blob with length bits, filled with logical value or random function
|
|
blob *blob_new_with_fill(size_t length_bits, int logical_value);
|
|
|
|
// Destroy blob and free memory
|
|
void blob_destroy(blob *b);
|
|
|
|
// Turn a blob into stone (immutable)
|
|
void blob_make_stone(blob *b);
|
|
|
|
// Write operations (only work on antestone blobs)
|
|
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_pad(blob *b, int block_size);
|
|
int blob_write_text(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_text(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
|
|
int kim_length_for_fit(int64_t value);
|
|
|
|
#ifdef BLOB_IMPLEMENTATION
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
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);
|
|
}
|
|
|
|
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) {
|
|
memset(new_ptr + old_size_bytes, 0, new_size_bytes - old_size_bytes);
|
|
}
|
|
b->data = new_ptr;
|
|
b->bit_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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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 (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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (logical_value) {
|
|
// Fill with 1s
|
|
size_t bytes = b->bit_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;
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
} else {
|
|
// zero length
|
|
free(b->data);
|
|
b->data = NULL;
|
|
}
|
|
b->bit_capacity = b->bit_length; // capacity in bits now matches 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;
|
|
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++;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int blob_write_fit(blob *b, int64_t fit, int length) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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_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;
|
|
|
|
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;
|
|
return 0;
|
|
}
|
|
|
|
blob *blob_read_blob(const blob *b, size_t from, size_t to) {
|
|
if (!b || !b->is_stone) return NULL;
|
|
return blob_new_from_blob(b, from, 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));
|
|
return 0;
|
|
}
|
|
|
|
int blob_read_fit(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;
|
|
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;
|
|
}
|
|
|
|
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
|
|
int bit;
|
|
if (blob_read_bit(b, from, &bit) < 0 || bit != 1) {
|
|
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 */ |