Files
blekin/crates/ericrfb/src/session.rs
rob thijssen acf99f849b
All checks were successful
CI / check (push) Successful in 1m22s
Publish / frontend (push) Successful in 43s
CI / fmt (push) Successful in 54s
CI / clippy (push) Successful in 1m40s
Publish / backend (push) Successful in 2m27s
feat: phase 9 — encoding 9 (IIP) with tile-versioned delta cache
codec/iip.rs:
- TileCache: (fb_width/16 × fb_height/16) tiles, 8 versions × 256 bytes
  each, matching t.java's (8, 16*16) allocation
- TileEntry: versioned read/write at byte offsets within tile data
- decode_iip() handles all 4 modes from the control byte:
  - Mode 0/12 (cache-read): tile control bytes select which cached
    version of each 16x16 tile to display, no new pixel data on wire
  - Mode 4 (write-only): Tight-decoded pixel data written to cache
  - Mode 8 (update+read): conditionally writes new data to cache
    (bit 7 of control byte = 0 means update), then reads from cache
- Tile control bytes compressed via zlib (varint length) when >= 12
- Sub-types 1-4/8 map to bit-depths 1/2/4/4/8 bpp
- Cache resized on framebuffer resize (ModeChange msg 128)

Wired into session dispatch for encoding 9. Not advertised in default
encoding list — only active if explicitly requested.

codec/tight.rs: made get_or_init() pub for IIP's zlib access.

39 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 09:39:49 +03:00

260 lines
9.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::io::{BufReader, BufWriter};
use std::net::TcpStream;
use crate::codec::{hextile, iip, raw_tile, tight};
use crate::framebuffer::Framebuffer;
use crate::handshake::{self, Config, ServerInit};
use crate::msg::{self, ServerMsg};
use crate::proto::{self, RectHeader, read_exact, read_u8};
#[derive(Debug, thiserror::Error)]
pub enum SessionError {
#[error("handshake: {0}")]
Handshake(#[from] handshake::HandshakeError),
#[error("protocol: {0}")]
Proto(#[from] proto::ProtoError),
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("unsupported encoding: {0}")]
UnsupportedEncoding(i32),
#[error("unsupported message type: {0}")]
UnsupportedMessage(u8),
}
/// Events emitted by the session pump to consumers.
#[derive(Debug)]
pub enum Event {
/// A region of the framebuffer was updated.
FramebufferDirty,
/// The framebuffer was resized.
Resize { width: u16, height: u16 },
/// Bell from server.
Bell,
/// Server sent a debug string.
Debug(String),
/// Server sent an RFB command (key, value).
RfbCommand(String, String),
/// Server name updated.
NameUpdate(String),
}
/// Active protocol session with framebuffer.
pub struct ActiveSession {
pub framebuffer: Framebuffer,
pub server_name: String,
pub reader: BufReader<TcpStream>,
pub writer: BufWriter<TcpStream>,
pub server_init: ServerInit,
zlib: tight::ZlibStreams,
tile_cache: iip::TileCache,
}
impl ActiveSession {
/// Connect, handshake, send SetEncodings + initial FBUpdateRequest.
pub fn connect(cfg: &Config, encodings: &[i32]) -> Result<Self, SessionError> {
let raw = handshake::connect(cfg)?;
let w = raw.server_init.width;
let h = raw.server_init.height;
let mut session = Self {
framebuffer: Framebuffer::new(w, h),
server_name: raw.server_name,
reader: raw.reader,
writer: raw.writer,
server_init: raw.server_init,
zlib: tight::ZlibStreams::new(),
tile_cache: iip::TileCache::new(w, h),
};
// Tell server to send 8bpp RGB332 pixels
msg::write_set_pixel_format_rgb332(&mut session.writer)?;
// Send SetEncodings
msg::write_set_encodings(&mut session.writer, encodings)?;
// Request full non-incremental framebuffer update
msg::write_fb_update_request(
&mut session.writer,
0,
0,
session.framebuffer.width,
session.framebuffer.height,
false,
)?;
Ok(session)
}
/// Request an incremental framebuffer update.
pub fn request_update(&mut self) -> Result<(), SessionError> {
msg::write_fb_update_request(
&mut self.writer,
0,
0,
self.framebuffer.width,
self.framebuffer.height,
true,
)?;
Ok(())
}
/// Process one server message. Returns an event if meaningful to the consumer.
pub fn process_one(&mut self) -> Result<Option<Event>, SessionError> {
let msg_type = read_u8(&mut self.reader)?;
let msg = ServerMsg::from(msg_type);
match msg {
ServerMsg::FramebufferUpdate => {
self.handle_fb_update()?;
Ok(Some(Event::FramebufferDirty))
}
ServerMsg::Bell => Ok(Some(Event::Bell)),
ServerMsg::Ping => {
let payload = msg::read_ping(&mut self.reader)?;
msg::write_ping_response(&mut self.writer, payload)?;
Ok(None)
}
ServerMsg::BandwidthProbe => {
msg::write_bandwidth_marker(&mut self.writer, 1)?;
msg::read_bandwidth_probe(&mut self.reader)?;
msg::write_bandwidth_marker(&mut self.writer, 2)?;
Ok(None)
}
ServerMsg::Ack => {
msg::read_ack(&mut self.reader)?;
Ok(None)
}
ServerMsg::DebugString => {
let s = msg::read_debug_string(&mut self.reader)?;
Ok(Some(Event::Debug(s)))
}
ServerMsg::RfbCommand => {
let (k, v) = msg::read_rfb_command(&mut self.reader)?;
Ok(Some(Event::RfbCommand(k, v)))
}
ServerMsg::ServerCutText => {
let _text = msg::read_server_cut_text(&mut self.reader)?;
Ok(None)
}
ServerMsg::ServerNameUpdate => {
let name = msg::read_server_name_update(&mut self.reader)?;
self.server_name = name.clone();
Ok(Some(Event::NameUpdate(name)))
}
ServerMsg::LayoutLocale => {
let _locale = msg::read_layout_locale(&mut self.reader)?;
Ok(None)
}
ServerMsg::DesktopResize => {
// Reads same struct as handshake pixel-format (aw.i, line 519)
let _flag = read_u8(&mut self.reader)?;
let _depth = proto::read_u16_be(&mut self.reader)?;
let label_len = proto::read_u16_be(&mut self.reader)? as usize;
let _label = read_exact(&mut self.reader, label_len)?;
Ok(None)
}
ServerMsg::ModeChange => {
// Re-read ServerInit (aw.k, line 435) — framebuffer dimensions may change
let si = handshake::read_server_init_from(&mut self.reader)?;
let old_w = self.framebuffer.width;
let old_h = self.framebuffer.height;
self.server_init = si;
if self.server_init.width != old_w || self.server_init.height != old_h {
self.framebuffer
.resize(self.server_init.width, self.server_init.height);
self.tile_cache
.resize(self.server_init.width, self.server_init.height);
return Ok(Some(Event::Resize {
width: self.server_init.width,
height: self.server_init.height,
}));
}
Ok(None)
}
ServerMsg::RdpEvent => {
let _event_type = msg::read_rdp_event(&mut self.reader)?;
Ok(None)
}
ServerMsg::PixelFormatChange => {
// aw.e(), line 537: 1 pad + 4×u8 + 8×u16 = 21 bytes
let _data = read_exact(&mut self.reader, 21)?;
Ok(None)
}
ServerMsg::SetColourMapEntries => Err(SessionError::UnsupportedMessage(msg_type)),
ServerMsg::Unknown(t) => Err(SessionError::UnsupportedMessage(t)),
}
}
fn handle_fb_update(&mut self) -> Result<(), SessionError> {
let num_rects = msg::read_fb_update_header(&mut self.reader)?;
for _ in 0..num_rects {
let hdr = RectHeader::read_from(&mut self.reader)?;
match hdr.encoding {
0 => {
// Raw: read w*h bytes
let size = hdr.w as usize * hdr.h as usize;
let data = read_exact(&mut self.reader, size)?;
self.framebuffer
.apply_raw(hdr.x, hdr.y, hdr.w, hdr.h, &data);
}
1 => {
// CopyRect: read src_x, src_y (u16 each)
let src_x = proto::read_u16_be(&mut self.reader)?;
let src_y = proto::read_u16_be(&mut self.reader)?;
self.framebuffer
.copy_rect(src_x, src_y, hdr.x, hdr.y, hdr.w, hdr.h);
}
5 => {
hextile::decode_hextile(
&mut self.reader,
&mut self.framebuffer,
hdr.x,
hdr.y,
hdr.w,
hdr.h,
)?;
}
7 => {
// Tight
tight::decode_tight(
&mut self.reader,
&mut self.framebuffer,
&mut self.zlib,
hdr.x,
hdr.y,
hdr.w,
hdr.h,
)?;
}
9 => {
iip::decode_iip(
&mut self.reader,
&mut self.framebuffer,
&mut self.tile_cache,
&mut self.zlib,
hdr.x,
hdr.y,
hdr.w,
hdr.h,
)?;
}
10 => {
raw_tile::decode_raw_tile(
&mut self.reader,
&mut self.framebuffer,
hdr.x,
hdr.y,
hdr.w,
hdr.h,
)?;
}
other => {
return Err(SessionError::UnsupportedEncoding(other));
}
}
}
Ok(())
}
}