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>
236 lines
7.3 KiB
Rust
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)
|
|
}
|