Own DEFLATE decoder replaces zlib dependency
Zephyr bundles no zlib — the CONFIG_ZLIB select was a no-op and the string path returned errors on device. src/inflate.c: minimal raw DEFLATE (RFC 1951) decompressor, no allocation; one code path for host and Zephyr. Host round-trip: 42,000 vectors through the real compiled blobs, 0 mismatches.
This commit is contained in:
parent
404cd2a334
commit
65fde6d298
5 changed files with 347 additions and 69 deletions
|
|
@ -72,6 +72,7 @@ if(CONFIG_STENO_DICT_V4)
|
||||||
src/formatter.c
|
src/formatter.c
|
||||||
src/undo.c
|
src/undo.c
|
||||||
src/dict_v4.c
|
src/dict_v4.c
|
||||||
|
src/inflate.c
|
||||||
src/split_dict.c
|
src/split_dict.c
|
||||||
src/split_cache.c
|
src/split_cache.c
|
||||||
src/dict_embed.S
|
src/dict_embed.S
|
||||||
|
|
@ -81,6 +82,7 @@ if(CONFIG_STENO_DICT_V4)
|
||||||
# Peripheral: right half blob + GATT server
|
# Peripheral: right half blob + GATT server
|
||||||
target_sources(app PRIVATE
|
target_sources(app PRIVATE
|
||||||
src/dict_v4.c
|
src/dict_v4.c
|
||||||
|
src/inflate.c
|
||||||
src/split_dict.c
|
src/split_dict.c
|
||||||
src/split_cache.c
|
src/split_cache.c
|
||||||
src/dict_embed.S
|
src/dict_embed.S
|
||||||
|
|
|
||||||
3
Kconfig
3
Kconfig
|
|
@ -34,7 +34,6 @@ endchoice
|
||||||
config STENO_DICT_V4
|
config STENO_DICT_V4
|
||||||
bool "v4 union split-section dictionary format"
|
bool "v4 union split-section dictionary format"
|
||||||
default y
|
default y
|
||||||
select ZLIB
|
|
||||||
help
|
help
|
||||||
Compile the dictionary into the v4 union split-section format
|
Compile the dictionary into the v4 union split-section format
|
||||||
(docs/FORMAT_V4.md): two half blobs, left (central) holds the
|
(docs/FORMAT_V4.md): two half blobs, left (central) holds the
|
||||||
|
|
@ -45,10 +44,8 @@ config STENO_DICT_V4
|
||||||
config STENO_DICT_MPHF
|
config STENO_DICT_MPHF
|
||||||
bool
|
bool
|
||||||
default y if !STENO_DICT_V4
|
default y if !STENO_DICT_V4
|
||||||
select ZLIB
|
|
||||||
help
|
help
|
||||||
Legacy MPHF (minimal perfect hash) dictionary format.
|
Legacy MPHF (minimal perfect hash) dictionary format.
|
||||||
Selects ZLIB for block-compressed string table decompression.
|
|
||||||
|
|
||||||
config STENO_CUSTOM_KEYMAP
|
config STENO_CUSTOM_KEYMAP
|
||||||
bool "Custom steno keymap"
|
bool "Custom steno keymap"
|
||||||
|
|
|
||||||
|
|
@ -25,79 +25,18 @@
|
||||||
|
|
||||||
/* ─── Raw-deflate block inflate (wbits = -15) ─── */
|
/* ─── Raw-deflate block inflate (wbits = -15) ─── */
|
||||||
|
|
||||||
#ifndef __ZEPHYR__
|
#include "inflate.h"
|
||||||
|
|
||||||
/* Use zlib on host for native tests */
|
|
||||||
#ifdef HAS_ZLIB
|
|
||||||
#include <zlib.h>
|
|
||||||
static int block_inflate_raw(const uint8_t *src, size_t src_len,
|
static int block_inflate_raw(const uint8_t *src, size_t src_len,
|
||||||
uint8_t *dst, size_t dst_cap, size_t *dst_len)
|
uint8_t *dst, size_t dst_cap, size_t *dst_len)
|
||||||
{
|
{
|
||||||
z_stream strm;
|
int ret = steno_inflate(src, src_len, dst, dst_cap);
|
||||||
memset(&strm, 0, sizeof(strm));
|
if (ret < 0) {
|
||||||
if (inflateInit2(&strm, -15) != Z_OK) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
strm.next_in = (Bytef *)(uintptr_t)src;
|
*dst_len = (size_t)ret;
|
||||||
strm.avail_in = (uInt)src_len;
|
|
||||||
strm.next_out = dst;
|
|
||||||
strm.avail_out = (uInt)dst_cap;
|
|
||||||
int ret = inflate(&strm, Z_FINISH);
|
|
||||||
inflateEnd(&strm);
|
|
||||||
if (ret == Z_STREAM_END) {
|
|
||||||
*dst_len = dst_cap - strm.avail_out;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static int block_inflate_raw(const uint8_t *src, size_t src_len,
|
|
||||||
uint8_t *dst, size_t dst_cap, size_t *dst_len)
|
|
||||||
{
|
|
||||||
/* No zlib on host — string sections unavailable */
|
|
||||||
(void)src; (void)src_len; (void)dst; (void)dst_cap; (void)dst_len;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
#endif /* HAS_ZLIB */
|
|
||||||
|
|
||||||
#else /* __ZEPHYR__ */
|
|
||||||
|
|
||||||
#include <zephyr/sys/util.h>
|
|
||||||
|
|
||||||
#if __has_include(<zephyr/lib/zlib/zlib.h>)
|
|
||||||
#include <zephyr/lib/zlib/zlib.h>
|
|
||||||
#elif __has_include(<zlib.h>)
|
|
||||||
#include <zlib.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int block_inflate_raw(const uint8_t *src, size_t src_len,
|
|
||||||
uint8_t *dst, size_t dst_cap, size_t *dst_len)
|
|
||||||
{
|
|
||||||
#if defined(CONFIG_ZLIB)
|
|
||||||
z_stream strm;
|
|
||||||
memset(&strm, 0, sizeof(strm));
|
|
||||||
if (inflateInit2(&strm, -15) != Z_OK) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
strm.next_in = (Bytef *)(uintptr_t)src;
|
|
||||||
strm.avail_in = (uInt)src_len;
|
|
||||||
strm.next_out = dst;
|
|
||||||
strm.avail_out = (uInt)dst_cap;
|
|
||||||
int ret = inflate(&strm, Z_FINISH);
|
|
||||||
inflateEnd(&strm);
|
|
||||||
if (ret == Z_STREAM_END) {
|
|
||||||
*dst_len = dst_cap - strm.avail_out;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
#else
|
|
||||||
/* No zlib available — the v4 string path cannot run on this half */
|
|
||||||
(void)src; (void)src_len; (void)dst; (void)dst_cap; (void)dst_len;
|
|
||||||
return -1;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* __ZEPHYR__ */
|
|
||||||
|
|
||||||
/* ─── Byte helpers (blob fields may be unaligned) ─── */
|
/* ─── Byte helpers (blob fields may be unaligned) ─── */
|
||||||
|
|
||||||
|
|
|
||||||
316
src/inflate.c
Normal file
316
src/inflate.c
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 zmk-steno-engine contributors
|
||||||
|
* SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
||||||
|
*
|
||||||
|
* Minimal raw DEFLATE (RFC 1951) decompressor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "inflate.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define MAX_BITS 15
|
||||||
|
#define MAX_LCODES 288
|
||||||
|
#define MAX_DCODES 30
|
||||||
|
#define MAX_CODES (MAX_LCODES + MAX_DCODES)
|
||||||
|
|
||||||
|
struct bitstream {
|
||||||
|
const uint8_t *src;
|
||||||
|
size_t src_len;
|
||||||
|
size_t pos; /* byte position */
|
||||||
|
uint32_t bitbuf;
|
||||||
|
int bitcnt;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct huffman {
|
||||||
|
uint16_t count[MAX_BITS + 1]; /* codes per bit length */
|
||||||
|
uint16_t symbol[MAX_LCODES]; /* symbols in canonical order */
|
||||||
|
};
|
||||||
|
|
||||||
|
static int bits(struct bitstream *s, int need, uint32_t *out)
|
||||||
|
{
|
||||||
|
while (s->bitcnt < need) {
|
||||||
|
if (s->pos >= s->src_len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->bitbuf |= (uint32_t)s->src[s->pos++] << s->bitcnt;
|
||||||
|
s->bitcnt += 8;
|
||||||
|
}
|
||||||
|
*out = s->bitbuf & ((1u << need) - 1);
|
||||||
|
s->bitbuf >>= need;
|
||||||
|
s->bitcnt -= need;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int decode(struct bitstream *s, const struct huffman *h)
|
||||||
|
{
|
||||||
|
int code = 0, first = 0, index = 0;
|
||||||
|
|
||||||
|
for (int len = 1; len <= MAX_BITS; len++) {
|
||||||
|
uint32_t bit;
|
||||||
|
if (bits(s, 1, &bit) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
code |= (int)bit;
|
||||||
|
int count = h->count[len];
|
||||||
|
if (code - first < count) {
|
||||||
|
return h->symbol[index + (code - first)];
|
||||||
|
}
|
||||||
|
index += count;
|
||||||
|
first = (first + count) << 1;
|
||||||
|
code <<= 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int construct(struct huffman *h, const uint8_t *lengths, int n)
|
||||||
|
{
|
||||||
|
int offs[MAX_BITS + 1];
|
||||||
|
|
||||||
|
memset(h->count, 0, sizeof(h->count));
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
h->count[lengths[i]]++;
|
||||||
|
}
|
||||||
|
if (h->count[0] == n) {
|
||||||
|
return 0; /* no codes at all — legal, decode() will just fail */
|
||||||
|
}
|
||||||
|
|
||||||
|
int left = 1;
|
||||||
|
for (int len = 1; len <= MAX_BITS; len++) {
|
||||||
|
left <<= 1;
|
||||||
|
left -= h->count[len];
|
||||||
|
if (left < 0) {
|
||||||
|
return -1; /* over-subscribed */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offs[1] = 0;
|
||||||
|
for (int len = 1; len < MAX_BITS; len++) {
|
||||||
|
offs[len + 1] = offs[len] + h->count[len];
|
||||||
|
}
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (lengths[i] != 0) {
|
||||||
|
h->symbol[offs[lengths[i]]++] = (uint16_t)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int codes(struct bitstream *s,
|
||||||
|
const struct huffman *lencode, const struct huffman *distcode,
|
||||||
|
uint8_t *dst, size_t dst_cap, size_t *dst_len)
|
||||||
|
{
|
||||||
|
static const uint16_t lens_base[29] = {
|
||||||
|
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
|
||||||
|
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
|
||||||
|
};
|
||||||
|
static const uint8_t lens_extra[29] = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
|
||||||
|
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
|
||||||
|
};
|
||||||
|
static const uint16_t dist_base[30] = {
|
||||||
|
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
|
||||||
|
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
|
||||||
|
8193, 12289, 16385, 24577
|
||||||
|
};
|
||||||
|
static const uint8_t dist_extra[30] = {
|
||||||
|
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
|
||||||
|
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
|
||||||
|
};
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int sym = decode(s, lencode);
|
||||||
|
if (sym < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (sym < 256) {
|
||||||
|
if (*dst_len >= dst_cap) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
dst[(*dst_len)++] = (uint8_t)sym;
|
||||||
|
} else if (sym == 256) {
|
||||||
|
return 0; /* end of block */
|
||||||
|
} else {
|
||||||
|
sym -= 257;
|
||||||
|
if (sym >= 29) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint32_t extra;
|
||||||
|
if (bits(s, lens_extra[sym], &extra) < 0 && lens_extra[sym] > 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (lens_extra[sym] == 0) {
|
||||||
|
extra = 0;
|
||||||
|
}
|
||||||
|
uint32_t length = lens_base[sym] + extra;
|
||||||
|
|
||||||
|
int dsym = decode(s, distcode);
|
||||||
|
if (dsym < 0 || dsym >= 30) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint32_t dextra = 0;
|
||||||
|
if (dist_extra[dsym] > 0 && bits(s, dist_extra[dsym], &dextra) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint32_t dist = dist_base[dsym] + dextra;
|
||||||
|
|
||||||
|
if (dist > *dst_len) {
|
||||||
|
return -1; /* distance beyond output start */
|
||||||
|
}
|
||||||
|
if (*dst_len + length > dst_cap) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
uint8_t *out = dst + *dst_len;
|
||||||
|
const uint8_t *from = out - dist;
|
||||||
|
for (uint32_t i = 0; i < length; i++) {
|
||||||
|
out[i] = from[i];
|
||||||
|
}
|
||||||
|
*dst_len += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fixed_tables(struct huffman *lencode, struct huffman *distcode)
|
||||||
|
{
|
||||||
|
uint8_t lengths[MAX_LCODES];
|
||||||
|
|
||||||
|
for (int i = 0; i < 144; i++) lengths[i] = 8;
|
||||||
|
for (int i = 144; i < 256; i++) lengths[i] = 9;
|
||||||
|
for (int i = 256; i < 280; i++) lengths[i] = 7;
|
||||||
|
for (int i = 280; i < 288; i++) lengths[i] = 8;
|
||||||
|
if (construct(lencode, lengths, 288) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 30; i++) lengths[i] = 5;
|
||||||
|
return construct(distcode, lengths, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dynamic_tables(struct bitstream *s,
|
||||||
|
struct huffman *lencode, struct huffman *distcode)
|
||||||
|
{
|
||||||
|
static const uint8_t order[19] = {
|
||||||
|
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
|
||||||
|
};
|
||||||
|
uint8_t lengths[MAX_CODES];
|
||||||
|
uint32_t hlit, hdist, hclen;
|
||||||
|
|
||||||
|
if (bits(s, 5, &hlit) < 0 || bits(s, 5, &hdist) < 0 ||
|
||||||
|
bits(s, 4, &hclen) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
hlit += 257;
|
||||||
|
hdist += 1;
|
||||||
|
hclen += 4;
|
||||||
|
if (hlit > MAX_LCODES || hdist > MAX_DCODES) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(lengths, 0, 19);
|
||||||
|
for (uint32_t i = 0; i < hclen; i++) {
|
||||||
|
uint32_t v;
|
||||||
|
if (bits(s, 3, &v) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
lengths[order[i]] = (uint8_t)v;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct huffman clcode;
|
||||||
|
if (construct(&clcode, lengths, 19) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t idx = 0;
|
||||||
|
while (idx < hlit + hdist) {
|
||||||
|
int sym = decode(s, &clcode);
|
||||||
|
if (sym < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (sym < 16) {
|
||||||
|
lengths[idx++] = (uint8_t)sym;
|
||||||
|
} else {
|
||||||
|
uint8_t repeat_val = 0;
|
||||||
|
uint32_t repeat, v;
|
||||||
|
if (sym == 16) {
|
||||||
|
if (idx == 0 || bits(s, 2, &v) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
repeat_val = lengths[idx - 1];
|
||||||
|
repeat = 3 + v;
|
||||||
|
} else if (sym == 17) {
|
||||||
|
if (bits(s, 3, &v) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
repeat = 3 + v;
|
||||||
|
} else {
|
||||||
|
if (bits(s, 7, &v) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
repeat = 11 + v;
|
||||||
|
}
|
||||||
|
if (idx + repeat > hlit + hdist) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
while (repeat--) {
|
||||||
|
lengths[idx++] = repeat_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lengths[256] == 0) {
|
||||||
|
return -1; /* end-of-block code must exist */
|
||||||
|
}
|
||||||
|
if (construct(lencode, lengths, (int)hlit) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return construct(distcode, lengths + hlit, (int)hdist);
|
||||||
|
}
|
||||||
|
|
||||||
|
int steno_inflate(const uint8_t *src, size_t src_len,
|
||||||
|
uint8_t *dst, size_t dst_cap)
|
||||||
|
{
|
||||||
|
struct bitstream s = { .src = src, .src_len = src_len };
|
||||||
|
size_t dst_len = 0;
|
||||||
|
uint32_t last, type;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (bits(&s, 1, &last) < 0 || bits(&s, 2, &type) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == 0) {
|
||||||
|
/* stored block: discard remaining bits, read LEN/NLEN */
|
||||||
|
s.bitbuf = 0;
|
||||||
|
s.bitcnt = 0;
|
||||||
|
if (s.pos + 4 > s.src_len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint32_t len = s.src[s.pos] | ((uint32_t)s.src[s.pos + 1] << 8);
|
||||||
|
uint32_t nlen = s.src[s.pos + 2] | ((uint32_t)s.src[s.pos + 3] << 8);
|
||||||
|
s.pos += 4;
|
||||||
|
if ((len ^ 0xFFFF) != nlen || s.pos + len > s.src_len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (dst_len + len > dst_cap) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
memcpy(dst + dst_len, src + s.pos, len);
|
||||||
|
dst_len += len;
|
||||||
|
s.pos += len;
|
||||||
|
} else if (type == 1 || type == 2) {
|
||||||
|
struct huffman lencode, distcode;
|
||||||
|
int ret = (type == 1) ? fixed_tables(&lencode, &distcode)
|
||||||
|
: dynamic_tables(&s, &lencode, &distcode);
|
||||||
|
if (ret < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ret = codes(&s, &lencode, &distcode, dst, dst_cap, &dst_len);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} while (!last);
|
||||||
|
|
||||||
|
return (int)dst_len;
|
||||||
|
}
|
||||||
24
src/inflate.h
Normal file
24
src/inflate.h
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 zmk-steno-engine contributors
|
||||||
|
* SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
||||||
|
*
|
||||||
|
* Minimal raw DEFLATE (RFC 1951) decompressor. Decompress-only, no
|
||||||
|
* dynamic allocation. Used to inflate the dictionary string blocks
|
||||||
|
* (raw deflate streams, zlib wbits=-15).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STENO_INFLATE_H
|
||||||
|
#define STENO_INFLATE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inflate a raw DEFLATE stream.
|
||||||
|
* Returns the number of bytes written to dst (>= 0), or a negative
|
||||||
|
* value on malformed input / dst overflow.
|
||||||
|
*/
|
||||||
|
int steno_inflate(const uint8_t *src, size_t src_len,
|
||||||
|
uint8_t *dst, size_t dst_cap);
|
||||||
|
|
||||||
|
#endif /* STENO_INFLATE_H */
|
||||||
Loading…
Add table
Add a link
Reference in a new issue