Files
blekin/crates/ericrfb/src/msg.rs
rob thijssen 1bd43fc1f9
All checks were successful
CI / fmt (push) Successful in 30s
CI / check (push) Successful in 1m1s
CI / clippy (push) Successful in 1m4s
feat: phase 2 — handshake, message writers, and server message dispatch
handshake.rs:
- Config, PixelFormat, ServerInit, Session types
- connect() walks all 11 handshake steps per aw.g() line 226
- Auth error mapping from aw.a(int) line 350 (7 error codes)

msg.rs — client-to-server writers:
- SetEncodings (type 2), FramebufferUpdateRequest (type 3)
- KeyEvent (type 4), PointerEvent (type 5, 8 bytes)
- PingResponse (type 149), BandwidthMarker (type 151)

msg.rs — server message dispatch:
- ServerMsg enum covering all 15 message types
- Readers for ping, bandwidth probe, ack, debug string,
  RFB command, server cut text, server name update,
  layout/locale, RDP event, FB update header

examples/handshake.rs: connects and prints session info.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 14:11:31 +03:00

236 lines
7.3 KiB
Rust

use std::io::Write;
use crate::proto::{self, read_exact, read_i32_be, read_modified_utf8, read_u8, read_u16_be};
// ---------------------------------------------------------------------------
// Client-to-server message writers
// ---------------------------------------------------------------------------
/// Msg type 2: SetEncodings — aw.a(int[], int), line 597.
/// Encoding IDs are i32 (negative values are pseudo-encodings).
pub fn write_set_encodings(w: &mut impl Write, encodings: &[i32]) -> proto::Result<()> {
let count = encodings.len() as u16;
let mut buf = vec![0u8; 4 + 4 * encodings.len()];
buf[0] = 2; // msg type
// buf[1] = 0 (pad)
buf[2] = (count >> 8) as u8;
buf[3] = count as u8;
for (i, &enc) in encodings.iter().enumerate() {
let bytes = enc.to_be_bytes();
buf[4 + i * 4..4 + i * 4 + 4].copy_from_slice(&bytes);
}
w.write_all(&buf)?;
w.flush()?;
Ok(())
}
/// Msg type 3: FramebufferUpdateRequest — aw.a(...), line 562.
pub fn write_fb_update_request(
w: &mut impl Write,
x: u16,
y: u16,
width: u16,
height: u16,
incremental: bool,
) -> proto::Result<()> {
let buf: [u8; 10] = [
3, // msg type
if incremental { 1 } else { 0 },
(x >> 8) as u8,
x as u8,
(y >> 8) as u8,
y as u8,
(width >> 8) as u8,
width as u8,
(height >> 8) as u8,
height as u8,
];
w.write_all(&buf)?;
w.flush()?;
Ok(())
}
/// Msg type 4: KeyEvent — aw.a(byte), line 655.
/// Single scancode byte.
pub fn write_key_event(w: &mut impl Write, scancode: u8) -> proto::Result<()> {
w.write_all(&[4, scancode])?;
w.flush()?;
Ok(())
}
/// Msg type 5: PointerEvent — aw.a(boolean, int, int, int, int), line 612.
/// 8 bytes: [5, mask, x_u16, y_u16, extra_u16].
pub fn write_pointer_event(
w: &mut impl Write,
x: u16,
y: u16,
button_mask: u8,
) -> proto::Result<()> {
let buf: [u8; 8] = [
5, // msg type (absolute mode)
button_mask,
(x >> 8) as u8,
x as u8,
(y >> 8) as u8,
y as u8,
0,
0, // extra_u16 = 0 in absolute mode
];
w.write_all(&buf)?;
w.flush()?;
Ok(())
}
/// Msg type 149: PingResponse — aw.if(int), line 636.
/// 8 bytes: [149(-107 signed), 0, 0, 0, n_i32].
pub fn write_ping_response(w: &mut impl Write, payload: i32) -> proto::Result<()> {
let mut buf = [0u8; 8];
buf[0] = 149u8; // -107 as u8
// buf[1..4] = 0 (pad)
buf[4..8].copy_from_slice(&payload.to_be_bytes());
w.write_all(&buf)?;
w.flush()?;
Ok(())
}
/// Msg type 151: bandwidth measurement bookend — aw.for(byte), line 649.
/// 2 bytes: [151, phase]. Phase 1 = start, 2 = done.
pub fn write_bandwidth_marker(w: &mut impl Write, phase: u8) -> proto::Result<()> {
w.write_all(&[151u8, phase])?;
w.flush()?;
Ok(())
}
// ---------------------------------------------------------------------------
// Server-to-client message types
// ---------------------------------------------------------------------------
/// Server message type tag, read as the first byte of each server message.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ServerMsg {
FramebufferUpdate, // 0
SetColourMapEntries, // 1
Bell, // 2
ServerCutText, // 3
ServerNameUpdate, // 7
PixelFormatChange, // 8
LayoutLocale, // 9
DesktopResize, // 16
Ack, // 17
ModeChange, // 128
DebugString, // 131
RfbCommand, // 132
Ping, // 148
BandwidthProbe, // 150
RdpEvent, // 161
Unknown(u8),
}
impl From<u8> for ServerMsg {
fn from(b: u8) -> Self {
match b {
0 => Self::FramebufferUpdate,
1 => Self::SetColourMapEntries,
2 => Self::Bell,
3 => Self::ServerCutText,
7 => Self::ServerNameUpdate,
8 => Self::PixelFormatChange,
9 => Self::LayoutLocale,
16 => Self::DesktopResize,
17 => Self::Ack,
128 => Self::ModeChange,
131 => Self::DebugString,
132 => Self::RfbCommand,
148 => Self::Ping,
150 => Self::BandwidthProbe,
161 => Self::RdpEvent,
other => Self::Unknown(other),
}
}
}
// ---------------------------------------------------------------------------
// Server message readers (for dispatch loop)
// ---------------------------------------------------------------------------
/// Read ping payload: 3 pad bytes + i32 — aw.b(), line 629.
pub fn read_ping(r: &mut impl std::io::Read) -> proto::Result<i32> {
let _pad = read_exact(r, 3)?;
read_i32_be(r)
}
/// Read and discard bandwidth probe: 1 pad + u16 len + data — aw.do(), line 642.
pub fn read_bandwidth_probe(r: &mut impl std::io::Read) -> proto::Result<()> {
let _pad = read_u8(r)?;
let len = read_u16_be(r)? as usize;
let _data = read_exact(r, len)?;
Ok(())
}
/// Read 2-byte ack (no-op) — aw.for(), line 553.
pub fn read_ack(r: &mut impl std::io::Read) -> proto::Result<()> {
let _b1 = read_u8(r)?;
let _b2 = read_u8(r)?;
Ok(())
}
/// Read debug string — aw.d(), line 498.
/// 3 pad bytes + i32 length + string bytes.
pub fn read_debug_string(r: &mut impl std::io::Read) -> proto::Result<String> {
let _pad = read_exact(r, 3)?;
let len = read_i32_be(r)? as usize;
let data = read_exact(r, len)?;
Ok(String::from_utf8_lossy(&data).into_owned())
}
/// Read RFB command — aw.long(), line 507.
/// 1 pad + u16 key_len + u16 val_len + key bytes + val bytes.
pub fn read_rfb_command(r: &mut impl std::io::Read) -> proto::Result<(String, String)> {
let _pad = read_u8(r)?;
let key_len = read_u16_be(r)? as usize;
let val_len = read_u16_be(r)? as usize;
let key_bytes = read_exact(r, key_len)?;
let val_bytes = read_exact(r, val_len)?;
Ok((
String::from_utf8_lossy(&key_bytes).into_owned(),
String::from_utf8_lossy(&val_bytes).into_owned(),
))
}
/// Read server cut text — aw.goto(), line 464 (reads i32 error code, reused
/// for ServerCutText which reads the text via standard RFB: 3 pad + u32 len + text).
pub fn read_server_cut_text(r: &mut impl std::io::Read) -> proto::Result<String> {
let _pad = read_exact(r, 3)?;
let len = read_i32_be(r)? as usize;
let data = read_exact(r, len)?;
Ok(String::from_utf8_lossy(&data).into_owned())
}
/// Read server name update — aw.l(), line 413.
/// 1 pad + modified-UTF-8 string.
pub fn read_server_name_update(r: &mut impl std::io::Read) -> proto::Result<String> {
let _pad = read_u8(r)?;
read_modified_utf8(r)
}
/// Read layout/locale string — aw.else(), line 529.
/// 1 pad + u16 len + string bytes.
pub fn read_layout_locale(r: &mut impl std::io::Read) -> proto::Result<String> {
let _pad = read_u8(r)?;
let len = read_u16_be(r)? as usize;
let data = read_exact(r, len)?;
Ok(String::from_utf8_lossy(&data).into_owned())
}
/// Read RDP event type byte — aw.case(), line 558.
pub fn read_rdp_event(r: &mut impl std::io::Read) -> proto::Result<i8> {
proto::read_i8(r)
}
/// Read framebuffer update header — aw.null(), line 459.
/// 1 pad byte + u16 num_rects.
pub fn read_fb_update_header(r: &mut impl std::io::Read) -> proto::Result<u16> {
let _pad = read_u8(r)?;
read_u16_be(r)
}