DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
ssh_zlib.h
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 ssh_zlib.h
6 * @brief SSH server-to-client compression: a context-takeover DEFLATE stream (no heap).
7 *
8 * SSH `zlib` / `zlib@openssh.com` (RFC 4253 sec 6.2) compress the packet payload with a single zlib
9 * stream per direction that is kept alive for the whole session - a persistent sliding window carried
10 * across packets ("context takeover"), sync-flushed at each packet boundary. This is the OPPOSITE of
11 * the WebSocket permessage-deflate codec (presentation/deflate), which is stateless per message
12 * (`no_context_takeover`); the two cannot share an instance.
13 *
14 * Only the SERVER->CLIENT direction is implemented here. It emits a zlib stream (RFC 1950 2-byte
15 * header once, then RFC 1951 fixed-Huffman blocks) with a Z_SYNC_FLUSH boundary after every packet:
16 * the block is byte-aligned and the empty stored block `00 00 ff ff` is KEPT on the wire (unlike
17 * permessage-deflate, which strips it). A standard zlib `inflate()` - as in OpenSSH - decodes it.
18 * The client->server direction stays `none` (OpenSSH compresses outbound with Z_PARTIAL_FLUSH, whose
19 * blocks straddle packet byte-boundaries and would need a resumable inflate state machine for a
20 * direction - keystrokes / uploads to the device - that barely benefits).
21 *
22 * All state and buffers are caller-supplied; the codec allocates nothing. See ssh_zlib.cpp for the
23 * work-buffer / hash-table sizing helpers (SSH_ZLIB_* macros).
24 *
25 * @author Douglas Quigg (dstroy0)
26 * @date 2026
27 */
28
29#ifndef DETERMINISTICESPASYNCWEBSERVER_SSH_ZLIB_H
30#define DETERMINISTICESPASYNCWEBSERVER_SSH_ZLIB_H
31
32#include "ServerConfig.h"
33#include <stddef.h>
34#include <stdint.h>
35
36#if DETWS_ENABLE_SSH_ZLIB
37
38/** @brief Hash-table buckets for the LZ77 3-byte match search (2^bits). */
39#define SSH_ZLIB_HASH_BITS 13
40#define SSH_ZLIB_HASH_SIZE (1 << SSH_ZLIB_HASH_BITS)
41
42/** @brief Work buffer capacity the compressor needs: window history + one input payload. */
43#define SSH_ZLIB_WORK_SIZE ((size_t)DETWS_SSH_ZLIB_WINDOW + (size_t)DETWS_SSH_ZLIB_MAX_IN)
44
45/**
46 * @brief Streaming server-to-client DEFLATE compressor (one per SSH connection).
47 *
48 * The window (history) lives at the front of @ref work; @ref hist bytes are valid. Hash chains
49 * (@ref head / @ref prev) are rebuilt over the history each packet, so a slid buffer needs no chain
50 * fix-up. All pointers are caller-owned; ssh_deflate_init() wires them and seeds the fixed tables.
51 */
52struct SshDeflate
53{
54 uint8_t *work; ///< history+input work buffer, capacity SSH_ZLIB_WORK_SIZE.
55 uint16_t *head; ///< hash bucket heads, SSH_ZLIB_HASH_SIZE entries.
56 uint16_t *prev; ///< hash chain (absolute-position indexed), SSH_ZLIB_WORK_SIZE entries.
57 uint16_t *ll_code; ///< fixed literal/length Huffman codes (bit-reversed), 288 entries.
58 uint8_t *ll_len; ///< their bit lengths, 288 entries.
59 uint16_t *d_code; ///< fixed distance Huffman codes (bit-reversed), 30 entries.
60 uint8_t *d_len; ///< their bit lengths, 30 entries.
61 size_t hist; ///< bytes of history currently at the front of @ref work.
62 bool header_sent; ///< true once the leading 2-byte zlib header has been emitted.
63};
64
65/**
66 * @brief Bind caller memory to a compressor and reset it to stream start.
67 *
68 * @param z the compressor to initialize.
69 * @param work work buffer, >= SSH_ZLIB_WORK_SIZE bytes.
70 * @param head hash heads, SSH_ZLIB_HASH_SIZE uint16 entries.
71 * @param prev hash chain, SSH_ZLIB_WORK_SIZE uint16 entries.
72 * @param ll_code,ll_len,d_code,d_len fixed-Huffman tables (288/288/30/30 entries); seeded here.
73 */
74void ssh_deflate_init(SshDeflate *z, uint8_t *work, uint16_t *head, uint16_t *prev, uint16_t *ll_code, uint8_t *ll_len,
75 uint16_t *d_code, uint8_t *d_len);
76
77/**
78 * @brief Compress one packet payload, continuing the session's zlib stream.
79 *
80 * Emits the 2-byte zlib header on the first call, then a fixed-Huffman block for @p src followed by a
81 * Z_SYNC_FLUSH boundary (`00 00 ff ff`, kept on the wire). Back-references may reach into the
82 * persistent window (prior packets), then the window slides to keep the last DETWS_SSH_ZLIB_WINDOW
83 * bytes for the next call.
84 *
85 * @param z the compressor.
86 * @param src,src_len uncompressed payload (src_len <= DETWS_SSH_ZLIB_MAX_IN).
87 * @param dst,dst_cap output buffer for the on-wire compressed payload.
88 * @param out_len set to the compressed length on success.
89 * @return 0 on success, -1 on bad input length or output overflow.
90 */
91int ssh_deflate_packet(SshDeflate *z, const uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_cap,
92 size_t *out_len);
93
94/**
95 * @brief Worst-case compressed size for @p src_len input (header + block overhead + sync marker).
96 *
97 * Callers size @p dst with this. Fixed-Huffman can expand incompressible data slightly; the bound
98 * covers the 2-byte header, per-byte worst case, end-of-block, and the 4-byte sync marker.
99 */
100static inline size_t ssh_deflate_bound(size_t src_len)
101{
102 return 2 + src_len + (src_len >> 3) + 32;
103}
104
105#endif // DETWS_ENABLE_SSH_ZLIB
106#endif // DETERMINISTICESPASYNCWEBSERVER_SSH_ZLIB_H
User-facing configuration for DeterministicESPAsyncWebServer.