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>
This commit is contained in:
235
crates/ericrfb/src/msg.rs
Normal file
235
crates/ericrfb/src/msg.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user