rfb_encodings/lib.rs
1// Copyright 2025 Dustin McAfee
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! RFB (Remote Framebuffer) protocol encoding implementations.
16//!
17//! This crate provides encoding implementations for the VNC/RFB protocol,
18//! including all standard encodings: Raw, RRE, `CoRRE`, Hextile, Tight, `TightPng`,
19//! Zlib, `ZlibHex`, ZRLE, and ZYWRLE.
20
21#![deny(missing_docs)]
22#![warn(clippy::pedantic)]
23
24use bytes::{Buf, BufMut, BytesMut};
25use std::io;
26
27// Encoding modules
28pub mod common;
29pub mod corre;
30pub mod hextile;
31pub mod jpeg;
32pub mod raw;
33pub mod rre;
34pub mod tight;
35pub mod tightpng;
36pub mod translate;
37pub mod zlib;
38pub mod zlibhex;
39pub mod zrle;
40pub mod zywrle;
41
42// Encoding type constants (from RFC 6143)
43
44/// Encoding type: Raw pixel data.
45pub const ENCODING_RAW: i32 = 0;
46
47/// Encoding type: Copy Rectangle.
48pub const ENCODING_COPYRECT: i32 = 1;
49
50/// Encoding type: Rise-and-Run-length Encoding.
51pub const ENCODING_RRE: i32 = 2;
52
53/// Encoding type: Compact RRE.
54pub const ENCODING_CORRE: i32 = 4;
55
56/// Encoding type: Hextile.
57pub const ENCODING_HEXTILE: i32 = 5;
58
59/// Encoding type: Zlib compressed.
60pub const ENCODING_ZLIB: i32 = 6;
61
62/// Encoding type: Tight.
63pub const ENCODING_TIGHT: i32 = 7;
64
65/// Encoding type: `ZlibHex`.
66pub const ENCODING_ZLIBHEX: i32 = 8;
67
68/// Encoding type: Zlib compressed TRLE.
69pub const ENCODING_ZRLE: i32 = 16;
70
71/// Encoding type: ZYWRLE (Zlib+Wavelet+Run-Length Encoding).
72pub const ENCODING_ZYWRLE: i32 = 17;
73
74/// Encoding type: `TightPng`.
75pub const ENCODING_TIGHTPNG: i32 = -260;
76
77// Re-export common types
78pub use common::*;
79pub use corre::CorRreEncoding;
80pub use hextile::HextileEncoding;
81pub use raw::RawEncoding;
82pub use rre::RreEncoding;
83pub use tight::TightEncoding;
84pub use tightpng::TightPngEncoding;
85pub use zlib::encode_zlib_persistent;
86pub use zlibhex::encode_zlibhex_persistent;
87pub use zrle::encode_zrle_persistent;
88pub use zywrle::zywrle_analyze;
89
90// Hextile subencoding flags
91
92/// Hextile: Raw pixel data for this tile.
93pub const HEXTILE_RAW: u8 = 1 << 0;
94
95/// Hextile: Background color is specified.
96pub const HEXTILE_BACKGROUND_SPECIFIED: u8 = 1 << 1;
97
98/// Hextile: Foreground color is specified.
99pub const HEXTILE_FOREGROUND_SPECIFIED: u8 = 1 << 2;
100
101/// Hextile: Tile contains subrectangles.
102pub const HEXTILE_ANY_SUBRECTS: u8 = 1 << 3;
103
104/// Hextile: Subrectangles are colored (not monochrome).
105pub const HEXTILE_SUBRECTS_COLOURED: u8 = 1 << 4;
106
107// Tight subencoding types
108
109/// Tight/TightPng: PNG compression subencoding.
110pub const TIGHT_PNG: u8 = 0x0A;
111
112/// Represents the pixel format used in RFB protocol.
113///
114/// This struct defines how pixel data is interpreted, including color depth,
115/// endianness, and RGB component details.
116#[derive(Debug, Clone)]
117pub struct PixelFormat {
118    /// Number of bits per pixel.
119    pub bits_per_pixel: u8,
120    /// Depth of the pixel in bits.
121    pub depth: u8,
122    /// Flag indicating if the pixel data is big-endian (1) or little-endian (0).
123    pub big_endian_flag: u8,
124    /// Flag indicating if the pixel format is true-colour (1) or colormapped (0).
125    pub true_colour_flag: u8,
126    /// Maximum red color value.
127    pub red_max: u16,
128    /// Maximum green color value.
129    pub green_max: u16,
130    /// Maximum blue color value.
131    pub blue_max: u16,
132    /// Number of shifts to apply to get the red color component.
133    pub red_shift: u8,
134    /// Number of shifts to apply to get the green color component.
135    pub green_shift: u8,
136    /// Number of shifts to apply to get the blue color component.
137    pub blue_shift: u8,
138}
139
140impl PixelFormat {
141    /// Creates a standard 32-bit RGBA pixel format.
142    ///
143    /// # Returns
144    ///
145    /// A `PixelFormat` instance configured for 32-bit RGBA.
146    #[must_use]
147    pub fn rgba32() -> Self {
148        Self {
149            bits_per_pixel: 32,
150            depth: 24,
151            big_endian_flag: 0,
152            true_colour_flag: 1,
153            red_max: 255,
154            green_max: 255,
155            blue_max: 255,
156            red_shift: 0,
157            green_shift: 8,
158            blue_shift: 16,
159        }
160    }
161
162    /// Checks if this `PixelFormat` is compatible with the standard 32-bit RGBA format.
163    ///
164    /// # Returns
165    ///
166    /// `true` if the pixel format matches 32-bit RGBA, `false` otherwise.
167    #[must_use]
168    pub fn is_compatible_with_rgba32(&self) -> bool {
169        self.bits_per_pixel == 32
170            && self.depth == 24
171            && self.big_endian_flag == 0
172            && self.true_colour_flag == 1
173            && self.red_max == 255
174            && self.green_max == 255
175            && self.blue_max == 255
176            && self.red_shift == 0
177            && self.green_shift == 8
178            && self.blue_shift == 16
179    }
180
181    /// Validates that this pixel format is supported.
182    ///
183    /// Checks that the format uses valid bits-per-pixel values and is either
184    /// true-color or a supported color-mapped format.
185    ///
186    /// # Returns
187    ///
188    /// `true` if the format is valid and supported, `false` otherwise.
189    #[must_use]
190    pub fn is_valid(&self) -> bool {
191        // Check bits per pixel is valid
192        if self.bits_per_pixel != 8
193            && self.bits_per_pixel != 16
194            && self.bits_per_pixel != 24
195            && self.bits_per_pixel != 32
196        {
197            return false;
198        }
199
200        // Check depth is reasonable
201        if self.depth == 0 || self.depth > 32 {
202            return false;
203        }
204
205        // For non-truecolor (color-mapped), only 8bpp is supported
206        if self.true_colour_flag == 0 && self.bits_per_pixel != 8 {
207            return false;
208        }
209
210        // For truecolor, validate color component ranges
211        if self.true_colour_flag != 0 {
212            // Check that max values fit in the bit depth
213            #[allow(clippy::cast_possible_truncation)]
214            // leading_zeros() returns max 32, result always fits in u8
215            let bits_needed = |max: u16| -> u8 {
216                if max == 0 {
217                    0
218                } else {
219                    (16 - max.leading_zeros()) as u8
220                }
221            };
222
223            let red_bits = bits_needed(self.red_max);
224            let green_bits = bits_needed(self.green_max);
225            let blue_bits = bits_needed(self.blue_max);
226
227            // Total bits should not exceed depth
228            if red_bits + green_bits + blue_bits > self.depth {
229                return false;
230            }
231
232            // Shifts should not cause overlap or exceed bit depth
233            if self.red_shift >= 32 || self.green_shift >= 32 || self.blue_shift >= 32 {
234                return false;
235            }
236        }
237
238        true
239    }
240
241    /// Creates a 16-bit RGB565 pixel format.
242    ///
243    /// RGB565 uses 5 bits for red, 6 bits for green, and 5 bits for blue.
244    /// This is a common format for embedded displays and bandwidth-constrained clients.
245    ///
246    /// # Returns
247    ///
248    /// A `PixelFormat` instance configured for 16-bit RGB565.
249    #[must_use]
250    pub fn rgb565() -> Self {
251        Self {
252            bits_per_pixel: 16,
253            depth: 16,
254            big_endian_flag: 0,
255            true_colour_flag: 1,
256            red_max: 31,   // 5 bits
257            green_max: 63, // 6 bits
258            blue_max: 31,  // 5 bits
259            red_shift: 11,
260            green_shift: 5,
261            blue_shift: 0,
262        }
263    }
264
265    /// Creates a 16-bit RGB555 pixel format.
266    ///
267    /// RGB555 uses 5 bits for each of red, green, and blue, with 1 unused bit.
268    ///
269    /// # Returns
270    ///
271    /// A `PixelFormat` instance configured for 16-bit RGB555.
272    #[must_use]
273    pub fn rgb555() -> Self {
274        Self {
275            bits_per_pixel: 16,
276            depth: 15,
277            big_endian_flag: 0,
278            true_colour_flag: 1,
279            red_max: 31,   // 5 bits
280            green_max: 31, // 5 bits
281            blue_max: 31,  // 5 bits
282            red_shift: 10,
283            green_shift: 5,
284            blue_shift: 0,
285        }
286    }
287
288    /// Creates an 8-bit BGR233 pixel format.
289    ///
290    /// BGR233 uses 2 bits for blue, 3 bits for green, and 3 bits for red.
291    /// This format is used for very low bandwidth connections and legacy clients.
292    ///
293    /// # Returns
294    ///
295    /// A `PixelFormat` instance configured for 8-bit BGR233.
296    #[must_use]
297    pub fn bgr233() -> Self {
298        Self {
299            bits_per_pixel: 8,
300            depth: 8,
301            big_endian_flag: 0,
302            true_colour_flag: 1,
303            red_max: 7,   // 3 bits
304            green_max: 7, // 3 bits
305            blue_max: 3,  // 2 bits
306            red_shift: 0,
307            green_shift: 3,
308            blue_shift: 6,
309        }
310    }
311
312    /// Writes the pixel format data into a `BytesMut` buffer.
313    ///
314    /// This function serializes the `PixelFormat` into the RFB protocol format.
315    ///
316    /// # Arguments
317    ///
318    /// * `buf` - A mutable reference to the `BytesMut` buffer to write into.
319    pub fn write_to(&self, buf: &mut BytesMut) {
320        buf.put_u8(self.bits_per_pixel);
321        buf.put_u8(self.depth);
322        buf.put_u8(self.big_endian_flag);
323        buf.put_u8(self.true_colour_flag);
324        buf.put_u16(self.red_max);
325        buf.put_u16(self.green_max);
326        buf.put_u16(self.blue_max);
327        buf.put_u8(self.red_shift);
328        buf.put_u8(self.green_shift);
329        buf.put_u8(self.blue_shift);
330        buf.put_bytes(0, 3); // padding
331    }
332
333    /// Reads and deserializes a `PixelFormat` from a `BytesMut` buffer.
334    ///
335    /// This function extracts pixel format information from the RFB protocol stream.
336    ///
337    /// # Arguments
338    ///
339    /// * `buf` - A mutable reference to the `BytesMut` buffer to read from.
340    ///
341    /// # Returns
342    ///
343    /// `Ok(Self)` containing the parsed `PixelFormat`.
344    ///
345    /// # Errors
346    ///
347    /// Returns `Err(io::Error)` if there are not enough bytes in the buffer
348    /// to read a complete `PixelFormat`.
349    pub fn from_bytes(buf: &mut BytesMut) -> io::Result<Self> {
350        if buf.len() < 16 {
351            return Err(io::Error::new(
352                io::ErrorKind::UnexpectedEof,
353                "Not enough bytes for PixelFormat",
354            ));
355        }
356
357        let pf = Self {
358            bits_per_pixel: buf.get_u8(),
359            depth: buf.get_u8(),
360            big_endian_flag: buf.get_u8(),
361            true_colour_flag: buf.get_u8(),
362            red_max: buf.get_u16(),
363            green_max: buf.get_u16(),
364            blue_max: buf.get_u16(),
365            red_shift: buf.get_u8(),
366            green_shift: buf.get_u8(),
367            blue_shift: buf.get_u8(),
368        };
369        buf.advance(3);
370        Ok(pf)
371    }
372}
373
374/// Trait defining the interface for RFB encoding implementations.
375pub trait Encoding {
376    /// Encodes raw pixel data into RFB-compatible byte stream.
377    ///
378    /// # Arguments
379    ///
380    /// * `data` - Raw pixel data (RGBA format: 4 bytes per pixel)
381    /// * `width` - Width of the framebuffer
382    /// * `height` - Height of the framebuffer
383    /// * `quality` - Quality level for lossy encodings (0-100)
384    /// * `compression` - Compression level (0-9)
385    ///
386    /// # Returns
387    ///
388    /// Encoded data as `BytesMut`
389    fn encode(
390        &self,
391        data: &[u8],
392        width: u16,
393        height: u16,
394        quality: u8,
395        compression: u8,
396    ) -> BytesMut;
397}
398
399/// Creates an encoder instance for the specified encoding type.
400///
401/// # Arguments
402///
403/// * `encoding_type` - The RFB encoding type constant
404///
405/// # Returns
406///
407/// `Some(Box<dyn Encoding>)` if the encoding is supported, `None` otherwise
408#[must_use]
409pub fn get_encoder(encoding_type: i32) -> Option<Box<dyn Encoding>> {
410    match encoding_type {
411        ENCODING_RAW => Some(Box::new(RawEncoding)),
412        ENCODING_RRE => Some(Box::new(RreEncoding)),
413        ENCODING_CORRE => Some(Box::new(CorRreEncoding)),
414        ENCODING_HEXTILE => Some(Box::new(HextileEncoding)),
415        ENCODING_TIGHT => Some(Box::new(TightEncoding)),
416        ENCODING_TIGHTPNG => Some(Box::new(TightPngEncoding)),
417        _ => None,
418    }
419}