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