Add VNC ZRLE decoding

Implements RFC 6143 SS7.7.6: a ZRLE rectangle is a 4-byte compressed
length followed by that many zlib-compressed bytes, decompressing to
64x64 tiles each using one of five subencodings (Raw, Solid, packed
palette, Plain RLE, Palette RLE). The zlib stream persists for the
whole connection rather than being reset per-rectangle or per-update,
so VncSessionBackend now owns a lazily-initialized, persistent
z_stream torn down only in resetProtocolState() on a fresh
connect/reconnect.

Since the entire rectangle's compressed data decompresses into memory
in one shot, tile parsing is a plain synchronous loop rather than
needing its own RfbState values -- only the compressed-length and
compressed-data reads are actual protocol states. Tile decoding (the
five subencodings, including the continuation-byte run-length
encoding shared by two of them) lives in vnc_pixel_codecs.h/.cpp
alongside the Hextile decoder, unit-tested with 6 new tests covering
each subencoding plus a persistence test that splits one continuous
deflate stream across two separate FramebufferUpdate messages -- it
only decodes correctly if the connection's inflate stream is retained
between them.

Adds a top-level find_package(ZLIB REQUIRED) + ZLIB::ZLIB link
(previously only pulled in transitively via vendored FreeRDP's own
smartcard-emulation feature, which happened to have it enabled but
shouldn't be relied on for that).

Live-verified against the TightVNC test server that nothing regressed
(connect, cursor, clipboard); that server consistently sends Raw for
actual framebuffer content regardless of announced encodings, so
Hextile/ZRLE's live decode path isn't independently confirmed against
a real server -- the unit tests are the primary evidence here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 21:02:26 -06:00
co-authored by Claude Sonnet 5
parent e49fa0cf26
commit bb022edcf2
8 changed files with 772 additions and 19 deletions
+16 -5
View File
@@ -10,6 +10,7 @@
#include <QRgb>
class QTcpSocket;
struct z_stream_s;
// Implements RFB (RFC 6143) directly against QTcpSocket -- there is no
// permissively licensed VNC client library to vendor the way FreeRDP was
@@ -22,11 +23,10 @@ class QTcpSocket;
//
// Scope (see plan / issue #3 for the full rationale): standard VNC
// Authentication (security type 2) and no-auth (type 1) only -- not
// Apple's Screen Sharing scheme (type 30). Raw + CopyRect + Hextile
// encodings (ZRLE/Tight compression not yet implemented). No dynamic
// resize. Clipboard sync (Latin-1 only, per RFB's ServerCutText/
// ClientCutText) and remote cursor shape sync (the Cursor pseudo-encoding)
// are supported.
// Apple's Screen Sharing scheme (type 30). Raw + CopyRect + Hextile + ZRLE
// encodings (Tight compression not yet implemented). No dynamic resize.
// Clipboard sync (Latin-1 only, per RFB's ServerCutText/ClientCutText) and
// remote cursor shape sync (the Cursor pseudo-encoding) are supported.
class VncSessionBackend : public SessionBackend
{
Q_OBJECT
@@ -90,6 +90,8 @@ private:
WaitingHextileTileMeta,
WaitingHextileSubrectData,
WaitingHextileRawTileData,
WaitingZrleCompressedLength,
WaitingZrleCompressedData,
WaitingSetColourMapHeader,
WaitingSetColourMapData,
WaitingServerCutTextHeader,
@@ -139,6 +141,15 @@ private:
int m_hextileSubrectsRemaining;
bool m_hextileSubrectsColoured;
// ZRLE's zlib stream (RFC 6143 SS7.7.6) persists for the whole
// connection, not per-rectangle or per-update -- lazily initialized on
// the first ZRLE rectangle, torn down and reset on every fresh
// connect/reconnect via resetProtocolState(). z_stream_s is only
// forward-declared here so <zlib.h> doesn't leak into every includer of
// this header; the full type is only needed in the .cpp.
z_stream_s* m_zrleInflateStream;
bool m_zrleInflateInitialized;
void setState(SessionState state, const QString& message);
void resetProtocolState();
void processReceiveBuffer();