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}