#ifndef BLOB_H #define BLOB_H #include #include #include #include #include // ----------------------------------------------------------------------------- // 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 length and capacity in bits. // ----------------------------------------------------------------------------- typedef struct blob blob; // Create a new blob with specified 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); // 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); // Accessors size_t blob_length(const blob *b); const uint8_t *blob_data(const blob *b); // 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_int64(blob *b, int64_t i); int blob_write_fit(blob *b, int64_t value, int len); int blob_write_pad(blob *b, int block_size); int blob_write_text(blob *b, const char *text); int blob_write_bytes(blob *b, const void *data, size_t len_bytes); // 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_int64(const blob *b, size_t from, int length, int64_t *out_value); int blob_read_fit(const blob *b, size_t from, int len, int64_t *out_value); 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 struct blob { uint8_t *data; // The total number of bits currently in use (the "length" of the blob). size_t length; size_t capacity; // 0 = antestone (mutable) // 1 = stone (immutable) int is_stone; }; size_t blob_length(const blob *b) { return b ? b->length : 0; } const uint8_t *blob_data(const blob *b) { return b ? b->data : NULL; } // Helper to ensure capacity for writing static int blob_ensure_capacity(blob *b, size_t new_bits) { size_t need_bits = b->length + new_bits; if (need_bits <= b->capacity) return 0; // 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 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; // 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->capacity = new_capacity; return 0; } // A simple bit-level memcpy (may handle unaligned bits at both ends) void bitcpy(unsigned char *dst, size_t dst_bit_offset, unsigned char *src, size_t src_bit_offset, size_t bit_length) { size_t dst_byte_offset = dst_bit_offset / 8; size_t src_byte_offset = src_bit_offset / 8; int dst_bit = dst_bit_offset % 8; int src_bit = src_bit_offset % 8; size_t 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; } size_t whole_bytes = remaining_bits / 8; for (size_t 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; } } static inline uint16_t load16_window(const uint8_t *s, size_t i, size_t last_byte) { uint16_t lo = s[i]; uint16_t hi = 0; if (i + 1 <= last_byte) hi = (uint16_t)s[i + 1] << 8; return lo | hi; } void copy_bits_fast(const void *src, void *dest, size_t n, size_t m, size_t x) { if (m < n) return; const uint8_t *s = (const uint8_t *)src; uint8_t *d = (uint8_t *)dest; size_t total_bits = m - n + 1; size_t src_bit = n; size_t dst_bit = x; size_t src_byte = src_bit >> 3; size_t dst_byte = dst_bit >> 3; int src_off = src_bit & 7; int dst_off = dst_bit & 7; size_t last_src_byte = m >> 3; /* Fast path: whole bytes, aligned */ if (src_off == 0 && dst_off == 0 && (total_bits & 7) == 0) { memcpy(d + dst_byte, s + src_byte, total_bits >> 3); return; } /* 1) Leading partial byte to align dest */ if (dst_off != 0) { size_t chunk = 8 - dst_off; if (chunk > total_bits) chunk = total_bits; uint8_t dst_mask = (uint8_t)(((1u << chunk) - 1u) << dst_off); uint16_t wb = load16_window(s, src_byte, last_src_byte); uint8_t bits = (uint8_t)((wb >> src_off) & ((1u << chunk) - 1u)); bits <<= dst_off; d[dst_byte] = (d[dst_byte] & (uint8_t)~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; } /* 2) Copy full bytes */ if (total_bits >= 8) { size_t num_bytes = total_bits >> 3; if (src_off == 0) { memcpy(d + dst_byte, s + src_byte, num_bytes); } else { for (size_t i = 0; i < num_bytes; i++) { uint16_t wb = load16_window(s, src_byte + i, last_src_byte); 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), dest is byte-aligned here */ if (total_bits > 0) { uint8_t dst_mask = (uint8_t)((1u << total_bits) - 1u); uint16_t wb = load16_window(s, src_byte, last_src_byte); uint8_t bits = (uint8_t)((wb >> src_off) & dst_mask); d[dst_byte] = (d[dst_byte] & (uint8_t)~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) { if (from >= to) return 0; size_t bits = to - from; if (blob_ensure_capacity(dest, bits) < 0) return -1; copy_bits_fast(src, dest->data, from, to - 1, dest->length); dest->length += bits; 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->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->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->length = length_bits; if (logical_value) { size_t bytes = b->capacity / 8; memset(b->data, 0xFF, bytes); 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) 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; // 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 { free(b->data); b->data = NULL; } 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->length; size_t byte_index = bit_index >> 3; size_t offset_in_byte = bit_index & 7; if (bit_val) b->data[byte_index] |= (1 << offset_in_byte); else b->data[byte_index] &= ~(1 << offset_in_byte); b->length++; return 0; } int blob_write_blob(blob *b, const blob *src) { if (!b || !src || b->is_stone) return -1; 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; if (blob_ensure_capacity(b, 64) < 0) return -1; copy_bits_fast(&d, b->data, 0, 64 - 1, b->length); b->length += 64; return 0; } int blob_write_int64(blob *b, int64_t i) { if (!b || b->is_stone) return -1; if (blob_ensure_capacity(b, 64) < 0) return -1; copy_bits_fast(&i, b->data, 0, 64 - 1, b->length); b->length += 64; return 0; } int blob_write_fit(blob *b, int64_t value, int len) { if (!b || b->is_stone) return -1; if (len < 1 || len > 64) return -1; // Check if value fits in len bits with sign if (len < 64) { int64_t max = (1LL << (len - 1)) - 1; int64_t min = -(1LL << (len - 1)); if (value < min || value > max) return -1; } if (blob_ensure_capacity(b, len) < 0) return -1; copy_bits_fast(&value, b->data, 0, len - 1, b->length); b->length += len; return 0; } int blob_write_pad(blob *b, int block_size) { if (!b || b->is_stone) return -1; if (block_size <= 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; } return 0; } int blob_write_text(blob *b, const char *text) { if (!b || !text || b->is_stone) return -1; size_t len = strlen(text); // Ensure capacity for 64-bit length + len*8 bits of text if (blob_ensure_capacity(b, 64 + (len * 8)) < 0) return -1; // 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, len * 8 - 1, b->length); b->length += (len * 8); } return 0; } int blob_write_bytes(blob *b, const void *data, size_t len_bytes) { if (!b || b->is_stone) return -1; if (!len_bytes) return 0; size_t bit_len = len_bytes << 3; size_t bit_off = b->length; size_t new_len = bit_off + bit_len; if (new_len < bit_off) return -1; if (blob_ensure_capacity(b, new_len) < 0) return -1; if ((bit_off & 7) == 0) { uint8_t *dst = (uint8_t *)b->data + (bit_off >> 3); memcpy(dst, data, len_bytes); } else { copy_bits_fast(data, b->data, 0, bit_len - 1, bit_off); } b->length = new_len; 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->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->length) return -1; if (from < 0) return -1; copy_bits_fast(b->data, out_value, from, from + 64 - 1, 0); return 0; } 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 + (size_t)length > b->length) return -1; copy_bits_fast(b->data, out_value, from, from + length - 1, 0); return 0; } int blob_read_fit(const blob *b, size_t from, int len, int64_t *out_value) { if (!b || !b->is_stone || !out_value) return -1; if (len < 1 || len > 64) return -1; if (from + (size_t)len > b->length) return -1; *out_value = 0; copy_bits_fast(b->data, out_value, from, from + len - 1, 0); // Sign extend if necessary (if the high bit is set and len < 64) if (len < 64 && (*out_value & (1LL << (len - 1)))) { // Set all bits above len to 1 for negative numbers int64_t mask = ~((1LL << len) - 1); *out_value |= mask; } 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; // Need at least 64 bits for length prefix if (from + 64 > b->length) return -1; int64_t raw_len = 0; // Read 64 bits of length copy_bits_fast(b->data, &raw_len, from, 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; // Copy exactly len*8 bits into str[0..len-1] copy_bits_fast(b->data, str, after_len, after_len + (len * 8) - 1, 0); str[len] = '\0'; *out_text = str; *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; 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; for (size_t i = from + 1; i < b->length; i++) { if (blob_read_bit(b, i, &bit) < 0 || bit != 0) return 0; } return 1; } #endif /* BLOB_IMPLEMENTATION */ #endif /* BLOB_H */