use byteorder::{LittleEndian, WriteBytesExt as _};
const ZGFX_SEGMENTED_SINGLE: u8 = 0xE0;
const ZGFX_SEGMENTED_MULTIPART: u8 = 0xE1;
const ZGFX_PACKET_COMPR_TYPE_RDP8: u8 = 0x04;
const ZGFX_PACKET_COMPRESSED: u8 = 0x02;
pub(crate) const ZGFX_SEGMENTED_MAXSIZE: usize = 65535;
pub fn wrap_uncompressed(data: &[u8]) -> Vec<u8> {
if data.len() <= ZGFX_SEGMENTED_MAXSIZE {
wrap_single_segment(data, false)
} else {
wrap_multipart_segments(data, false)
}
}
pub fn wrap_compressed(compressed_data: &[u8]) -> Vec<u8> {
assert!(
compressed_data.len() <= ZGFX_SEGMENTED_MAXSIZE,
"compressed data ({} bytes) exceeds single-segment limit ({}); \
the compressor must emit pre-segmented output for larger payloads",
compressed_data.len(),
ZGFX_SEGMENTED_MAXSIZE,
);
wrap_single_segment(compressed_data, true)
}
fn wrap_single_segment(data: &[u8], compressed: bool) -> Vec<u8> {
let mut output = Vec::with_capacity(data.len() + 2);
output.push(ZGFX_SEGMENTED_SINGLE);
let flags = if compressed {
ZGFX_PACKET_COMPR_TYPE_RDP8 | (ZGFX_PACKET_COMPRESSED << 4)
} else {
ZGFX_PACKET_COMPR_TYPE_RDP8
};
output.push(flags);
output.extend_from_slice(data);
output
}
fn wrap_multipart_segments(data: &[u8], compressed: bool) -> Vec<u8> {
let segment_count = data.len().div_ceil(ZGFX_SEGMENTED_MAXSIZE);
let mut output = Vec::with_capacity(data.len() + 7 + segment_count * 5);
output.push(ZGFX_SEGMENTED_MULTIPART);
output
.write_u16::<LittleEndian>(u16::try_from(segment_count).expect("segment count exceeds u16"))
.expect("write to Vec cannot fail");
output
.write_u32::<LittleEndian>(u32::try_from(data.len()).expect("data exceeds u32"))
.expect("write to Vec cannot fail");
for segment in data.chunks(ZGFX_SEGMENTED_MAXSIZE) {
output
.write_u32::<LittleEndian>(u32::try_from(segment.len() + 1).expect("segment size exceeds u32"))
.expect("write to Vec cannot fail");
let flags = if compressed {
ZGFX_PACKET_COMPR_TYPE_RDP8 | (ZGFX_PACKET_COMPRESSED << 4)
} else {
ZGFX_PACKET_COMPR_TYPE_RDP8
};
output.push(flags);
output.extend_from_slice(segment);
}
output
}
#[cfg(test)]
#[expect(clippy::as_conversions, reason = "test assertions use as for clarity")]
mod tests {
use super::*;
#[test]
fn test_wrap_small_data() {
let data = b"Hello, ZGFX!";
let wrapped = wrap_uncompressed(data);
assert_eq!(wrapped.len(), data.len() + 2);
assert_eq!(wrapped[0], 0xE0); assert_eq!(wrapped[1], 0x04); assert_eq!(&wrapped[2..], data);
}
#[test]
fn test_wrap_empty_data() {
let data = b"";
let wrapped = wrap_uncompressed(data);
assert_eq!(wrapped.len(), 2);
assert_eq!(wrapped[0], 0xE0);
assert_eq!(wrapped[1], 0x04);
}
#[test]
fn test_wrap_max_single_segment() {
let data = vec![0xAB; 65535]; let wrapped = wrap_uncompressed(&data);
assert_eq!(wrapped[0], 0xE0); assert_eq!(wrapped.len(), 65535 + 2);
}
#[test]
fn test_wrap_large_data() {
let data = vec![0xCD; 100000]; let wrapped = wrap_uncompressed(&data);
assert_eq!(wrapped[0], 0xE1);
let segment_count = u16::from_le_bytes([wrapped[1], wrapped[2]]) as usize;
assert_eq!(segment_count, 2);
let uncompressed_size = u32::from_le_bytes([wrapped[3], wrapped[4], wrapped[5], wrapped[6]]) as usize;
assert_eq!(uncompressed_size, 100000);
let seg1_size = u32::from_le_bytes([wrapped[7], wrapped[8], wrapped[9], wrapped[10]]) as usize;
assert_eq!(seg1_size, 65536); assert_eq!(wrapped[11], 0x04);
let seg2_offset = 7 + 4 + seg1_size;
let seg2_size = u32::from_le_bytes([
wrapped[seg2_offset],
wrapped[seg2_offset + 1],
wrapped[seg2_offset + 2],
wrapped[seg2_offset + 3],
]) as usize;
assert_eq!(seg2_size, 100000 - 65535 + 1); assert_eq!(wrapped[seg2_offset + 4], 0x04); }
#[test]
fn test_round_trip_with_decompressor() {
use super::super::Decompressor;
let data = b"Test data for ZGFX round-trip verification";
let wrapped = wrap_uncompressed(data);
let mut decompressor = Decompressor::new();
let mut output = Vec::new();
decompressor.decompress(&wrapped, &mut output).unwrap();
assert_eq!(&output, data);
}
#[test]
fn test_round_trip_large_data() {
use super::super::Decompressor;
let data = vec![0x42; 150000];
let wrapped = wrap_uncompressed(&data);
let mut decompressor = Decompressor::new();
let mut output = Vec::new();
decompressor.decompress(&wrapped, &mut output).unwrap();
assert_eq!(output, data);
}
#[test]
fn test_wrap_compressed_single_segment() {
let fake_compressed = vec![0xFF; 128];
let wrapped = wrap_compressed(&fake_compressed);
assert_eq!(wrapped[0], 0xE0); assert_eq!(wrapped[1], 0x24); assert_eq!(&wrapped[2..], &*fake_compressed);
}
#[test]
#[should_panic(expected = "exceeds single-segment limit")]
fn test_wrap_compressed_rejects_oversized() {
let too_large = vec![0xFF; ZGFX_SEGMENTED_MAXSIZE + 1];
wrap_compressed(&too_large);
}
#[test]
fn test_wrap_typical_egfx_pdu() {
let egfx_caps_confirm = vec![0x13, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00]; let wrapped = wrap_uncompressed(&egfx_caps_confirm);
assert_eq!(wrapped[0], 0xE0); assert_eq!(wrapped[1], 0x04); assert_eq!(wrapped.len(), egfx_caps_confirm.len() + 2);
}
#[test]
fn test_wrap_typical_h264_frame() {
let h264_frame = vec![0x00; 85000];
let wrapped = wrap_uncompressed(&h264_frame);
assert_eq!(wrapped[0], 0xE1);
let segment_count = u16::from_le_bytes([wrapped[1], wrapped[2]]);
assert_eq!(segment_count, 2);
}
#[test]
fn test_wrap_compressed_data() {
use crate::zgfx::Compressor;
let mut compressor = Compressor::new();
let data = b"Test data with some patterns for compression";
let compressed = compressor.compress(data).unwrap();
let wrapped = wrap_compressed(&compressed);
assert_eq!(wrapped[0], 0xE0); assert_eq!(wrapped[1], 0x24);
use crate::zgfx::Decompressor;
let mut decompressor = Decompressor::new();
let mut output = Vec::new();
decompressor.decompress(&wrapped, &mut output).unwrap();
assert_eq!(&output, data);
}
#[test]
fn test_compress_and_wrap_full_pipeline() {
use crate::zgfx::{Compressor, Decompressor};
let mut compressor = Compressor::new();
let data = b"This is test data that will be compressed using ZGFX algorithm and then wrapped";
let compressed_data = compressor.compress(data).unwrap();
let wrapped = wrap_compressed(&compressed_data);
let mut decompressor = Decompressor::new();
let mut output = Vec::new();
decompressor.decompress(&wrapped, &mut output).unwrap();
assert_eq!(&output, data);
}
}