560 lines
20 KiB
C
560 lines
20 KiB
C
/*
|
||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||
* Copyright (C) 2026 Cristian Cezar Moisés
|
||
* Commercial licensing: sac@securityops.co
|
||
*
|
||
* VaptVupt — Canonical Huffman Codec Implementation
|
||
*
|
||
* Performance targets (x86-64, gcc -O2):
|
||
* Encode: ≥ 150 MB/s (bottleneck: bit packing, 1 symbol per ~4 cycles)
|
||
* Decode: ≥ 800 MB/s (bottleneck: table lookup + refill, 1 symbol per ~5 cycles)
|
||
*
|
||
* If decode falls short of 800 MB/s, the cause is likely the refill frequency.
|
||
* Fix: unroll the decode loop 4× and refill once per 4 symbols (amortize refill).
|
||
*
|
||
* Algorithm:
|
||
* 1. Count symbol frequencies
|
||
* 2. Build Huffman tree (two-queue merge, O(n) after sort)
|
||
* 3. Extract code lengths, limit to 15 bits
|
||
* 4. Assign canonical codes (sorted by length then symbol)
|
||
* 5. Encode: LSB-first bitstream with 64-bit accumulator
|
||
* 6. Decode: 12-bit lookup table (16 KB, L1-resident)
|
||
*
|
||
* Header format (on-disk):
|
||
* [1B max_symbol] — highest symbol index with nonzero code length (0-255)
|
||
* [(max_symbol+2)/2 bytes] — code lengths packed as nibble pairs:
|
||
* byte[i] = (lengths[2*i] << 4) | lengths[2*i+1]
|
||
* Total header: 1 + ceil((max_symbol+1)/2) bytes (1-129 bytes)
|
||
*/
|
||
|
||
#include "vv_huffman.h"
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* BITSTREAM WRITER (LSB-first, 64-bit accumulator)
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
typedef struct {
|
||
uint64_t bits;
|
||
int nbits;
|
||
uint8_t *dst;
|
||
size_t pos;
|
||
size_t cap;
|
||
} bw_t;
|
||
|
||
static inline void bw_init(bw_t *w, uint8_t *dst, size_t cap) {
|
||
w->bits = 0; w->nbits = 0; w->dst = dst; w->pos = 0; w->cap = cap;
|
||
}
|
||
|
||
/* Add up to 16 bits. Flushes full bytes automatically. */
|
||
static inline void bw_add(bw_t *w, uint32_t val, int n) {
|
||
w->bits |= (uint64_t)(val & ((1u << n) - 1)) << w->nbits;
|
||
w->nbits += n;
|
||
/* Flush complete bytes */
|
||
while (w->nbits >= 8 && w->pos < w->cap) {
|
||
w->dst[w->pos++] = (uint8_t)(w->bits);
|
||
w->bits >>= 8;
|
||
w->nbits -= 8;
|
||
}
|
||
}
|
||
|
||
static inline size_t bw_flush(bw_t *w) {
|
||
while (w->nbits > 0 && w->pos < w->cap) {
|
||
w->dst[w->pos++] = (uint8_t)(w->bits);
|
||
w->bits >>= 8;
|
||
w->nbits -= 8;
|
||
}
|
||
return w->pos;
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* BITSTREAM READER (LSB-first, 64-bit accumulator)
|
||
*
|
||
* PERFORMANCE-CRITICAL: this is the decode hot path.
|
||
* The refill reads 8 bytes at a time when possible.
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
typedef struct {
|
||
uint64_t bits;
|
||
int nbits;
|
||
const uint8_t *src;
|
||
size_t pos;
|
||
size_t len;
|
||
} br_t;
|
||
|
||
static inline void br_init(br_t *r, const uint8_t *src, size_t len) {
|
||
r->bits = 0; r->nbits = 0; r->src = src; r->pos = 0; r->len = len;
|
||
}
|
||
|
||
/* Refill: load bytes until accumulator is full (≥56 bits) */
|
||
static inline void br_refill(br_t *r) {
|
||
while (r->nbits <= 56 && r->pos < r->len) {
|
||
r->bits |= (uint64_t)r->src[r->pos++] << r->nbits;
|
||
r->nbits += 8;
|
||
}
|
||
}
|
||
|
||
static inline uint32_t br_peek(const br_t *r, int n) {
|
||
return (uint32_t)(r->bits & ((1ULL << n) - 1));
|
||
}
|
||
|
||
static inline void br_consume(br_t *r, int n) {
|
||
r->bits >>= n;
|
||
r->nbits -= n;
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* REVERSE BITS (for LSB-first canonical code storage)
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
static inline uint16_t reverse_bits(uint16_t code, int len) {
|
||
uint16_t rev = 0;
|
||
for (int i = 0; i < len; i++) {
|
||
rev = (uint16_t)((rev << 1) | (code & 1));
|
||
code >>= 1;
|
||
}
|
||
return rev;
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* BUILD HUFFMAN CODE LENGTHS FROM FREQUENCIES
|
||
*
|
||
* Two-queue merge algorithm (O(n) after sorting):
|
||
* 1. Sort non-zero symbols by frequency (ascending)
|
||
* 2. Merge two cheapest nodes repeatedly using two queues
|
||
* (leaf queue + internal node queue)
|
||
* 3. Extract depths via parent pointers
|
||
* 4. Limit max depth to VVH_MAX_CODE_LEN (15)
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
static void build_code_lengths(const uint32_t freq[VVH_SYMBOLS],
|
||
uint8_t lengths[VVH_SYMBOLS]) {
|
||
/* Collect non-zero symbols, sort by frequency */
|
||
int sym_idx[VVH_SYMBOLS];
|
||
uint32_t sym_freq[VVH_SYMBOLS];
|
||
int n = 0;
|
||
|
||
memset(lengths, 0, VVH_SYMBOLS);
|
||
for (int i = 0; i < VVH_SYMBOLS; i++) {
|
||
if (freq[i] > 0) {
|
||
sym_idx[n] = i;
|
||
sym_freq[n] = freq[i];
|
||
n++;
|
||
}
|
||
}
|
||
|
||
if (n == 0) return;
|
||
if (n == 1) { lengths[sym_idx[0]] = 1; return; }
|
||
if (n == 2) { lengths[sym_idx[0]] = 1; lengths[sym_idx[1]] = 1; return; }
|
||
|
||
/* Insertion sort by frequency ascending (n ≤ 256, fast enough) */
|
||
for (int i = 1; i < n; i++) {
|
||
uint32_t tf = sym_freq[i];
|
||
int ts = sym_idx[i];
|
||
int j = i - 1;
|
||
while (j >= 0 && sym_freq[j] > tf) {
|
||
sym_freq[j + 1] = sym_freq[j];
|
||
sym_idx[j + 1] = sym_idx[j];
|
||
j--;
|
||
}
|
||
sym_freq[j + 1] = tf;
|
||
sym_idx[j + 1] = ts;
|
||
}
|
||
|
||
/* Heap-allocate tree workspace: 2n-1 nodes (n >= 3, so total >= 5) */
|
||
size_t total = 2u * (unsigned)n - 1u;
|
||
uint32_t *nf = (uint32_t *)calloc(total, sizeof(uint32_t));
|
||
int16_t *par = (int16_t *)malloc(total * sizeof(int16_t));
|
||
if (!nf || !par) { free(nf); free(par); return; }
|
||
|
||
/* Initialize leaf nodes */
|
||
for (int i = 0; i < n; i++) {
|
||
nf[i] = sym_freq[i];
|
||
par[i] = -1;
|
||
}
|
||
for (size_t i = (size_t)n; i < total; i++) {
|
||
nf[i] = 0;
|
||
par[i] = -1;
|
||
}
|
||
|
||
/* Two-queue merge */
|
||
int lq = 0; /* Leaf queue read pointer */
|
||
int iq = n; /* Internal queue read pointer */
|
||
int next = n; /* Next internal node to create */
|
||
|
||
for (int m = 0; m < n - 1; m++) {
|
||
uint32_t cost = 0;
|
||
for (int pick = 0; pick < 2; pick++) {
|
||
int use_leaf = (lq < n) && (iq >= next || nf[lq] <= nf[iq]);
|
||
if (use_leaf) {
|
||
cost += nf[lq];
|
||
par[lq] = (int16_t)next;
|
||
lq++;
|
||
} else {
|
||
cost += nf[iq];
|
||
par[iq] = (int16_t)next;
|
||
iq++;
|
||
}
|
||
}
|
||
nf[next] = cost;
|
||
par[next] = -1;
|
||
next++;
|
||
}
|
||
|
||
/* Compute depths */
|
||
uint8_t *dep = (uint8_t *)calloc(total, 1);
|
||
if (!dep) { free(nf); free(par); return; }
|
||
dep[total - 1] = 0;
|
||
for (int i = (int)total - 2; i >= 0; i--)
|
||
dep[i] = dep[par[i]] + 1;
|
||
|
||
/* Extract leaf depths */
|
||
for (int i = 0; i < n; i++)
|
||
lengths[sym_idx[i]] = dep[i];
|
||
|
||
free(nf); free(par); free(dep);
|
||
|
||
/* ─── Depth limiting to VVH_MAX_CODE_LEN ─── */
|
||
int max_d = 0;
|
||
for (int i = 0; i < VVH_SYMBOLS; i++)
|
||
if (lengths[i] > max_d) max_d = lengths[i];
|
||
if (max_d <= VVH_MAX_CODE_LEN) return;
|
||
|
||
/* Count symbols per depth */
|
||
int bl_count[32];
|
||
memset(bl_count, 0, sizeof(bl_count));
|
||
for (int i = 0; i < VVH_SYMBOLS; i++)
|
||
if (lengths[i] > 0) bl_count[lengths[i]]++;
|
||
|
||
/* Cap depths > 15 to 15 */
|
||
for (int d = VVH_MAX_CODE_LEN + 1; d < 32; d++) {
|
||
bl_count[VVH_MAX_CODE_LEN] += bl_count[d];
|
||
bl_count[d] = 0;
|
||
}
|
||
|
||
/* Fix Kraft inequality: sum(bl_count[d] * 2^(15-d)) must ≤ 2^15 */
|
||
for (;;) {
|
||
uint32_t kraft = 0;
|
||
for (int d = 1; d <= VVH_MAX_CODE_LEN; d++)
|
||
kraft += (uint32_t)bl_count[d] << (VVH_MAX_CODE_LEN - d);
|
||
if (kraft <= (1u << VVH_MAX_CODE_LEN)) break;
|
||
/* Move one symbol from shallowest level deeper */
|
||
for (int d = VVH_MAX_CODE_LEN - 1; d >= 1; d--) {
|
||
if (bl_count[d] > 0) {
|
||
bl_count[d]--;
|
||
bl_count[d + 1]++;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Reassign lengths: sort non-zero symbols by (current_length asc, symbol asc)
|
||
* then assign from the bl_count distribution shortest-first */
|
||
typedef struct { uint8_t len; uint8_t sym; } ls_t;
|
||
ls_t sorted[VVH_SYMBOLS];
|
||
int ns = 0;
|
||
for (int i = 0; i < VVH_SYMBOLS; i++)
|
||
if (lengths[i] > 0) {
|
||
sorted[ns].len = lengths[i] > VVH_MAX_CODE_LEN
|
||
? VVH_MAX_CODE_LEN : lengths[i];
|
||
sorted[ns].sym = (uint8_t)i;
|
||
ns++;
|
||
}
|
||
/* Sort by len ascending, then sym ascending */
|
||
for (int i = 1; i < ns; i++) {
|
||
ls_t tmp = sorted[i];
|
||
int j = i - 1;
|
||
while (j >= 0 && (sorted[j].len > tmp.len ||
|
||
(sorted[j].len == tmp.len && sorted[j].sym > tmp.sym))) {
|
||
sorted[j + 1] = sorted[j]; j--;
|
||
}
|
||
sorted[j + 1] = tmp;
|
||
}
|
||
/* Assign from distribution */
|
||
int si = 0;
|
||
for (int d = 1; d <= VVH_MAX_CODE_LEN && si < ns; d++)
|
||
for (int c = 0; c < bl_count[d] && si < ns; c++)
|
||
lengths[sorted[si++].sym] = (uint8_t)d;
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* CANONICAL CODE ASSIGNMENT
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
static void assign_canonical_codes(const uint8_t lengths[VVH_SYMBOLS],
|
||
uint16_t codes[VVH_SYMBOLS]) {
|
||
/* Count symbols at each length */
|
||
int bl_count[VVH_MAX_CODE_LEN + 1];
|
||
memset(bl_count, 0, sizeof(bl_count));
|
||
for (int i = 0; i < VVH_SYMBOLS; i++)
|
||
if (lengths[i] > 0 && lengths[i] <= VVH_MAX_CODE_LEN)
|
||
bl_count[lengths[i]]++;
|
||
|
||
/* Compute first code for each length (MSB-first canonical) */
|
||
uint16_t next_code[VVH_MAX_CODE_LEN + 1];
|
||
uint16_t code = 0;
|
||
next_code[0] = 0;
|
||
for (int bits = 1; bits <= VVH_MAX_CODE_LEN; bits++) {
|
||
code = (uint16_t)((code + bl_count[bits - 1]) << 1);
|
||
next_code[bits] = code;
|
||
}
|
||
|
||
/* Assign codes in symbol order (canonical: sorted by length then symbol) */
|
||
for (int i = 0; i < VVH_SYMBOLS; i++) {
|
||
if (lengths[i] > 0)
|
||
codes[i] = next_code[lengths[i]]++;
|
||
else
|
||
codes[i] = 0;
|
||
}
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* BUILD ENCODER TABLE
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
static void build_enc_table(const uint32_t freq[VVH_SYMBOLS],
|
||
vvh_enc_table_t *enc) {
|
||
build_code_lengths(freq, enc->lengths);
|
||
|
||
uint16_t canonical[VVH_SYMBOLS];
|
||
assign_canonical_codes(enc->lengths, canonical);
|
||
|
||
/* Store bit-reversed codes for LSB-first writing */
|
||
for (int i = 0; i < VVH_SYMBOLS; i++) {
|
||
if (enc->lengths[i] > 0)
|
||
enc->codes[i] = reverse_bits(canonical[i], enc->lengths[i]);
|
||
else
|
||
enc->codes[i] = 0;
|
||
}
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* BUILD DECODER TABLE
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
static void build_dec_table(const uint8_t lengths[VVH_SYMBOLS],
|
||
vvh_dec_table_t *dec) {
|
||
uint16_t canonical[VVH_SYMBOLS];
|
||
assign_canonical_codes(lengths, canonical);
|
||
|
||
memset(dec->table, 0, sizeof(dec->table));
|
||
dec->slow_count = 0;
|
||
|
||
for (int sym = 0; sym < VVH_SYMBOLS; sym++) {
|
||
int len = lengths[sym];
|
||
if (len == 0) continue;
|
||
|
||
uint16_t rev = reverse_bits(canonical[sym], len);
|
||
|
||
if (len <= VVH_DECODE_BITS) {
|
||
/* Fast path: fill all entries where low `len` bits match `rev` */
|
||
int fill = 1 << (VVH_DECODE_BITS - len);
|
||
for (int j = 0; j < fill; j++) {
|
||
int idx = (int)rev | (j << len);
|
||
dec->table[idx] = (uint32_t)sym | ((uint32_t)len << 8);
|
||
}
|
||
} else {
|
||
/* Slow path: store for linear scan */
|
||
int si = dec->slow_count++;
|
||
dec->slow_code[si] = rev;
|
||
dec->slow_len[si] = (uint8_t)len;
|
||
dec->slow_sym[si] = (uint8_t)sym;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* WRITE HEADER (code lengths as packed nibbles)
|
||
*
|
||
* Format: [1B max_sym] [(max_sym+2)/2 bytes packed nibble pairs]
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
static size_t write_header(const uint8_t lengths[VVH_SYMBOLS],
|
||
uint8_t *dst, size_t cap) {
|
||
/* Find max symbol with nonzero length */
|
||
int max_sym = 0;
|
||
for (int i = VVH_SYMBOLS - 1; i >= 0; i--) {
|
||
if (lengths[i] > 0) { max_sym = i; break; }
|
||
}
|
||
|
||
size_t hdr_size = 1 + ((size_t)max_sym + 2) / 2;
|
||
if (hdr_size > cap) return 0;
|
||
|
||
dst[0] = (uint8_t)max_sym;
|
||
|
||
/* Pack nibble pairs */
|
||
for (int i = 0; i <= max_sym; i += 2) {
|
||
uint8_t hi = lengths[i];
|
||
uint8_t lo = (i + 1 <= max_sym) ? lengths[i + 1] : 0;
|
||
dst[1 + i / 2] = (uint8_t)((hi << 4) | (lo & 0x0F));
|
||
}
|
||
|
||
return hdr_size;
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* READ HEADER
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
static size_t read_header(const uint8_t *src, size_t src_len,
|
||
uint8_t lengths[VVH_SYMBOLS]) {
|
||
memset(lengths, 0, VVH_SYMBOLS);
|
||
if (src_len < 1) return 0;
|
||
|
||
int max_sym = src[0];
|
||
size_t hdr_size = 1 + ((size_t)max_sym + 2) / 2;
|
||
if (hdr_size > src_len) return 0;
|
||
|
||
for (int i = 0; i <= max_sym; i += 2) {
|
||
uint8_t packed = src[1 + i / 2];
|
||
lengths[i] = packed >> 4;
|
||
if (i + 1 <= max_sym)
|
||
lengths[i + 1] = packed & 0x0F;
|
||
}
|
||
|
||
return hdr_size;
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* ENCODE
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
vvh_error_t vvh_encode(const uint8_t *src, size_t src_len,
|
||
uint8_t *dst, size_t dst_cap, size_t *dst_len) {
|
||
if (src_len == 0) {
|
||
*dst_len = 0;
|
||
return VVH_OK;
|
||
}
|
||
|
||
/* Count frequencies */
|
||
uint32_t freq[VVH_SYMBOLS];
|
||
memset(freq, 0, sizeof(freq));
|
||
for (size_t i = 0; i < src_len; i++)
|
||
freq[src[i]]++;
|
||
|
||
/* Build encode table */
|
||
vvh_enc_table_t enc;
|
||
build_enc_table(freq, &enc);
|
||
|
||
/* Check: any symbols with length 0 that appear in input? (shouldn't happen) */
|
||
/* Write header */
|
||
size_t hdr_sz = write_header(enc.lengths, dst, dst_cap);
|
||
if (hdr_sz == 0) return VVH_ERR_OVERFLOW;
|
||
|
||
/* Encode bitstream */
|
||
bw_t w;
|
||
bw_init(&w, dst + hdr_sz, dst_cap - hdr_sz);
|
||
|
||
for (size_t i = 0; i < src_len; i++) {
|
||
uint8_t sym = src[i];
|
||
bw_add(&w, enc.codes[sym], enc.lengths[sym]);
|
||
}
|
||
|
||
size_t bs_sz = bw_flush(&w);
|
||
size_t total = hdr_sz + bs_sz;
|
||
|
||
/* Incompressible guard: if not smaller, signal failure */
|
||
if (total >= src_len) {
|
||
return VVH_ERR_OVERFLOW;
|
||
}
|
||
|
||
*dst_len = total;
|
||
return VVH_OK;
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════════════════════
|
||
* DECODE
|
||
*
|
||
* PERFORMANCE-CRITICAL: the inner loop decodes one symbol per
|
||
* iteration using a 12-bit table lookup + refill.
|
||
*
|
||
* Hot path (codes ≤ 12 bits, ~99% of symbols):
|
||
* 1. Peek 12 bits from accumulator
|
||
* 2. Table lookup → (symbol, length)
|
||
* 3. Consume `length` bits
|
||
* 4. Refill accumulator if needed
|
||
* 5. Write symbol to output
|
||
*
|
||
* Cold path (codes 13-15 bits, <1% of symbols):
|
||
* Linear scan of slow_code/slow_len/slow_sym arrays.
|
||
* ═══════════════════════════════════════════════════════════════ */
|
||
|
||
vvh_error_t vvh_decode(const uint8_t *src, size_t src_len,
|
||
uint8_t *dst, size_t dst_cap,
|
||
size_t num_literals, size_t *src_consumed) {
|
||
if (num_literals == 0) {
|
||
*src_consumed = 0;
|
||
return VVH_OK;
|
||
}
|
||
if (num_literals > dst_cap) return VVH_ERR_OVERFLOW;
|
||
|
||
/* Read header */
|
||
uint8_t lengths[VVH_SYMBOLS];
|
||
size_t hdr_sz = read_header(src, src_len, lengths);
|
||
if (hdr_sz == 0) return VVH_ERR_CORRUPT;
|
||
|
||
/* Check for valid tree: at least one nonzero length */
|
||
int has_sym = 0;
|
||
for (int i = 0; i < VVH_SYMBOLS; i++)
|
||
if (lengths[i] > 0) { has_sym = 1; break; }
|
||
if (!has_sym) return VVH_ERR_CORRUPT;
|
||
|
||
/* Build decode table (heap-allocated: 16 KB) */
|
||
vvh_dec_table_t *dec = (vvh_dec_table_t *)malloc(sizeof(vvh_dec_table_t));
|
||
if (!dec) return VVH_ERR_NOMEM;
|
||
build_dec_table(lengths, dec);
|
||
|
||
/* Initialize bitstream reader */
|
||
br_t r;
|
||
br_init(&r, src + hdr_sz, src_len - hdr_sz);
|
||
br_refill(&r);
|
||
|
||
/* ─── Decode loop ─── */
|
||
for (size_t i = 0; i < num_literals; i++) {
|
||
/* Refill if accumulator is getting low */
|
||
if (r.nbits < VVH_MAX_CODE_LEN)
|
||
br_refill(&r);
|
||
|
||
uint32_t peek = br_peek(&r, VVH_DECODE_BITS);
|
||
uint32_t entry = dec->table[peek];
|
||
int sym = (int)(entry & 0xFF);
|
||
int len = (int)((entry >> 8) & 0xF);
|
||
|
||
if (VV_LIKELY(len > 0)) {
|
||
/* Fast path: code ≤ 12 bits */
|
||
br_consume(&r, len);
|
||
dst[i] = (uint8_t)sym;
|
||
} else {
|
||
/* Slow path: code > 12 bits */
|
||
int found = 0;
|
||
for (int s = 0; s < dec->slow_count; s++) {
|
||
int slen = dec->slow_len[s];
|
||
uint32_t mask = (1u << slen) - 1;
|
||
if ((br_peek(&r, slen) & mask) == dec->slow_code[s]) {
|
||
br_consume(&r, slen);
|
||
dst[i] = dec->slow_sym[s];
|
||
found = 1;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) {
|
||
free(dec);
|
||
return VVH_ERR_CORRUPT;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Calculate bytes consumed from src */
|
||
*src_consumed = hdr_sz + r.pos;
|
||
/* Account for bits still in accumulator that we didn't fully consume */
|
||
if (r.nbits >= 8) {
|
||
/* We over-read by (nbits/8) bytes */
|
||
size_t over = (size_t)(r.nbits / 8);
|
||
if (*src_consumed >= over)
|
||
*src_consumed -= over;
|
||
}
|
||
|
||
free(dec);
|
||
return VVH_OK;
|
||
}
|