ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
deflate.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file deflate.cpp
6 * @brief Bounded RFC 1951 DEFLATE compressor - implementation.
7 *
8 * Fixed-Huffman (BTYPE=01) encoding with greedy LZ77 matching over a bounded
9 * window. Hash chains (head[]/prev[]) locate candidate matches; the chain depth
10 * and the window are both capped so the per-byte cost is bounded - deterministic
11 * rather than maximal compression, which suits the small messages this serves.
12 * The static code tables are generated from the RFC 1951 sec 3.2.6 code lengths
13 * (the same lengths inflate's fixed() builds) so the two stay in lock-step.
14 *
15 * Bits are packed LSB-first into the byte stream; Huffman codes are stored
16 * bit-reversed so writing them LSB-first puts them on the wire MSB-first as
17 * RFC 1951 sec 3.1.1 requires. All state is the caller's scratch plus the stack.
18 */
19
20#include "deflate.h"
21
22#if PC_ENABLE_WS_DEFLATE
23
24#include <string.h>
25
27
28namespace
29{
30#define PC_MIN_MATCH 3 // shortest LZ77 back-reference
31#define PC_MAX_MATCH 258 // longest (RFC 1951 length code 285)
32#define PC_HASH_BITS 10 // hash table size = 1<<PC_HASH_BITS buckets
33#define PC_HASH_SIZE (1 << PC_HASH_BITS)
34#define PC_HASH_MASK (PC_HASH_SIZE - 1)
35#define PC_WINDOW 512 // max back-reference distance (>= WS_FRAME_SIZE)
36#define PC_WIN_MASK (PC_WINDOW - 1)
37#define PC_MAX_CHAIN 64 // bounded hash-chain walk per position
38#define PC_NONE 0xFFFF // empty hash slot / chain terminator
39
40// Length code base values and extra bits (RFC 1951 sec 3.2.5), codes 257..285.
41const short LEN_BASE[29] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27,
42 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
43const short LEN_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};
44
45// Distance code base values and extra bits, codes 0..29.
46const short DIST_BASE[30] = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129,
47 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
48const short DIST_EXTRA[30] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6,
49 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
50
51// All working memory deflate_raw() needs, laid over the caller's scratch.
52struct Tables
53{
54 uint16_t head[PC_HASH_SIZE]; // most-recent position for each 3-byte hash
55 uint16_t prev[PC_WINDOW]; // previous position with the same hash (chain)
56 uint16_t ll_code[288]; // fixed lit/length Huffman codes (bit-reversed)
57 uint8_t ll_len[288]; // their lengths in bits
58 uint16_t d_code[30]; // fixed distance Huffman codes (bit-reversed)
59 uint8_t d_len[30]; // their lengths in bits (all 5)
60};
61static_assert(sizeof(Tables) <= DEFLATE_SCRATCH_SIZE, "bump DEFLATE_SCRATCH_SIZE");
62
63// Reverse the low @p len bits of @p code (Huffman codes go on the wire MSB-first).
64uint16_t reverse_bits(uint16_t code, int len)
65{
66 uint16_t r = 0;
67 for (int k = 0; k < len; k++)
68 {
69 r = (uint16_t)((r << 1) | (code & 1));
70 code >>= 1;
71 }
72 return r;
73}
74
75// Build the fixed Huffman code/length tables (RFC 1951 sec 3.2.6) into @p t,
76// storing each code bit-reversed so it can be emitted LSB-first.
77void build_fixed(Tables *t)
78{
79 int sym = 0;
80 for (; sym < 144; sym++)
81 {
82 t->ll_len[sym] = 8;
83 }
84 for (; sym < 256; sym++)
85 {
86 t->ll_len[sym] = 9;
87 }
88 for (; sym < 280; sym++)
89 {
90 t->ll_len[sym] = 7;
91 }
92 for (; sym < 288; sym++)
93 {
94 t->ll_len[sym] = 8;
95 }
96 for (sym = 0; sym < 30; sym++)
97 {
98 t->d_len[sym] = 5;
99 }
100
101 // Canonical code assignment (RFC 1951 sec 3.2.2) for the lit/length alphabet.
102 uint16_t bl_count[16];
103 memset(bl_count, 0, sizeof(bl_count));
104 for (sym = 0; sym < 288; sym++)
105 {
106 bl_count[t->ll_len[sym]]++;
107 }
108 uint16_t next_code[16];
109 next_code[0] = 0;
110 uint16_t code = 0;
111 for (int bits = 1; bits <= 15; bits++)
112 {
113 code = (uint16_t)((code + bl_count[bits - 1]) << 1);
114 next_code[bits] = code;
115 }
116 for (sym = 0; sym < 288; sym++)
117 {
118 int len = t->ll_len[sym];
119 t->ll_code[sym] = reverse_bits(next_code[len]++, len);
120 }
121
122 // Distance alphabet: 30 codes all of length 5 -> codes 0..29 in order.
123 for (sym = 0; sym < 30; sym++)
124 {
125 t->d_code[sym] = reverse_bits((uint16_t)sym, 5);
126 }
127}
128
129// 3-byte rolling hash into a PC_HASH_SIZE bucket.
130inline int hash3(const uint8_t *p)
131{
132 return (int)(((uint32_t)p[0] << 8 ^ (uint32_t)p[1] << 4 ^ (uint32_t)p[2]) & PC_HASH_MASK);
133}
134
135// Emit one literal byte via the fixed lit/length code.
136void emit_literal(pc_bit_writer *w, const Tables *t, uint8_t b)
137{
138 pc_bitw_put(w, t->ll_code[b], t->ll_len[b]);
139}
140
141// Emit a (length, distance) back-reference via the fixed code tables.
142void emit_match(pc_bit_writer *w, const Tables *t, int len, int dist)
143{
144 int li = 0;
145 while (li < 28 && len >= LEN_BASE[li + 1])
146 {
147 li++;
148 }
149 int lsym = 257 + li;
150 pc_bitw_put(w, t->ll_code[lsym], t->ll_len[lsym]);
151 if (LEN_EXTRA[li])
152 {
153 pc_bitw_put(w, (uint32_t)(len - LEN_BASE[li]), LEN_EXTRA[li]);
154 }
155
156 int di = 0;
157 // di == 29 (loop exit via the left operand) is unreachable: dist is always <=
158 // PC_WINDOW (512) here - the chain walk in deflate_raw() discards any candidate
159 // farther away - and DIST_BASE[18] is 513, so di never advances past 17.
160 while (di < 29 && dist >= DIST_BASE[di + 1]) // GCOVR_EXCL_BR_LINE di<29 never goes false, see above
161 {
162 di++;
163 }
164 pc_bitw_put(w, t->d_code[di], t->d_len[di]);
165 if (DIST_EXTRA[di])
166 {
167 pc_bitw_put(w, (uint32_t)(dist - DIST_BASE[di]), DIST_EXTRA[di]);
168 }
169}
170} // namespace
171
172DeflateResult deflate_raw(const uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_cap, size_t *out_len,
173 void *scratch, size_t scratch_len)
174{
175 if (scratch_len < DEFLATE_SCRATCH_SIZE)
176 {
177 return DeflateResult::DEFLATE_ERR_SCRATCH;
178 }
179
180 Tables *t = (Tables *)scratch;
181 build_fixed(t);
182 for (int i = 0; i < PC_HASH_SIZE; i++)
183 {
184 t->head[i] = PC_NONE;
185 }
186
188 w.out = dst;
189 w.cap = dst_cap;
190 w.cnt = 0;
191 w.acc = 0;
192 w.nbits = 0;
193 w.overflow = false;
194
195 // One fixed-Huffman block, not final (permessage-deflate streams never set
196 // BFINAL): BFINAL=0 (1 bit), BTYPE=01 (2 bits, value 1).
197 pc_bitw_put(&w, 0, 1);
198 pc_bitw_put(&w, 1, 2);
199
200 size_t i = 0;
201 while (i < src_len)
202 {
203 int best_len = 0;
204 int best_dist = 0;
205
206 // Only positions with PC_MIN_MATCH lookahead bytes can start a match.
207 if (i + PC_MIN_MATCH <= src_len)
208 {
209 int h = hash3(src + i);
210 uint16_t cand = t->head[h];
211 int chain = PC_MAX_CHAIN;
212 size_t max_len = src_len - i;
213 if (max_len > (size_t)PC_MAX_MATCH)
214 {
215 max_len = PC_MAX_MATCH;
216 }
217 while (cand != PC_NONE && chain > 0)
218 {
219 chain--; // bound the hash-chain walk; decrement here, not in the && (no side effect in the condition)
220 size_t dist = i - cand;
221 if (dist > (size_t)PC_WINDOW)
222 {
223 break; // chain is newest-first; everything past here is farther
224 }
225 size_t l = 0;
226 while (l < max_len && src[cand + l] == src[i + l])
227 {
228 l++;
229 }
230 if ((int)l > best_len)
231 {
232 best_len = (int)l;
233 best_dist = (int)dist;
234 if (l >= max_len)
235 {
236 break; // can't beat the lookahead limit
237 }
238 }
239 cand = t->prev[cand & PC_WIN_MASK];
240 }
241 }
242
243 size_t advance;
244 if (best_len >= PC_MIN_MATCH)
245 {
246 emit_match(&w, t, best_len, best_dist);
247 advance = (size_t)best_len;
248 }
249 else
250 {
251 emit_literal(&w, t, src[i]);
252 advance = 1;
253 }
254
255 // Step over the consumed bytes, inserting each into the hash chains so
256 // later positions can reference them (only where PC_MIN_MATCH bytes remain).
257 // The byte comparison above always validates a candidate before use, so a
258 // stale insert can only cost ratio, never correctness.
259 size_t end = i + advance;
260 while (i < end)
261 {
262 if (i + PC_MIN_MATCH <= src_len)
263 {
264 int h = hash3(src + i);
265 t->prev[i & PC_WIN_MASK] = t->head[h];
266 t->head[h] = (uint16_t)i;
267 }
268 i++;
269 }
270 }
271
272 // End-of-block, then a sync flush: byte-align via an empty stored block and
273 // drop its 0x00 0x00 0xff 0xff tail (RFC 7692 sec 7.2.1), leaving a ready
274 // permessage-deflate payload.
275 pc_bitw_put(&w, t->ll_code[256], t->ll_len[256]); // end-of-block symbol
276 pc_bitw_put(&w, 0, 1); // BFINAL=0 (empty stored block)
277 pc_bitw_put(&w, 0, 2); // BTYPE=00 (stored)
278 pc_bitw_align(&w);
279 static const uint8_t marker[4] = {0x00, 0x00, 0xff, 0xff};
280 for (int k = 0; k < 4; k++)
281 {
282 if (w.cnt >= w.cap)
283 {
284 w.overflow = true;
285 break;
286 }
287 w.out[w.cnt++] = marker[k];
288 }
289
290 if (w.overflow)
291 {
292 return DeflateResult::DEFLATE_ERR_OVERFLOW;
293 }
294 *out_len = w.cnt - 4; // strip the marker for the on-wire payload
295 return DeflateResult::DEFLATE_OK;
296}
297
298#endif // PC_ENABLE_WS_DEFLATE
LSB-first bit writer over a caller-owned byte buffer - one source of truth.
void pc_bitw_align(pc_bit_writer *w)
Flush any partial byte, padding the high bits with zero (byte alignment).
Definition bitio.h:57
void pc_bitw_put(pc_bit_writer *w, uint32_t bits, int n)
Append the low n bits of bits, LSB-first, spilling any completed bytes to the output.
Definition bitio.h:39
Bounded RFC 1951 DEFLATE compressor (DEFLATE) - no heap.
LSB-first bit writer over the caller's output buffer; overflow latches once cap is exceeded.
Definition bitio.h:29
int nbits
bits currently buffered (< 8 between calls)
Definition bitio.h:34
size_t cnt
bytes written so far
Definition bitio.h:32
uint32_t acc
bit accumulator (LSB-first)
Definition bitio.h:33
size_t cap
Definition bitio.h:31
bool overflow
Definition bitio.h:35
uint8_t * out
Definition bitio.h:30