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