Files
One-KVM/src/vnc/rfb.rs
2026-07-14 22:59:33 +08:00

1507 lines
51 KiB
Rust

use std::net::SocketAddr;
use bytes::{Bytes, BytesMut};
use des::cipher::{BlockEncrypt, KeyInit};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::sync::broadcast;
use crate::config::{VncConfig, VncEncoding};
use crate::error::{AppError, Result};
use crate::hid::{
CanonicalKey, KeyEventType, KeyboardEvent, KeyboardModifiers, MouseButton, MouseEvent,
MouseEventType,
};
const ENCODING_TIGHT: i32 = 7;
const ENCODING_H264: i32 = 50;
const ENCODING_DESKTOP_SIZE: i32 = -223;
const MAX_ENCODING_COUNT: usize = 1024;
const MAX_CLIPBOARD_SIZE: usize = 1024 * 1024;
const SECURITY_TYPE_VNC_AUTH: u8 = 2;
#[derive(Clone, Debug)]
pub enum RfbFrame {
Jpeg {
data: Bytes,
width: u16,
height: u16,
sequence: u64,
},
H264 {
data: Bytes,
width: u16,
height: u16,
key: bool,
sequence: u64,
},
}
impl RfbFrame {
pub fn sequence(&self) -> u64 {
match self {
Self::Jpeg { sequence, .. } | Self::H264 { sequence, .. } => *sequence,
}
}
pub fn size(&self) -> (u16, u16) {
match self {
Self::Jpeg { width, height, .. } | Self::H264 { width, height, .. } => {
(*width, *height)
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum RfbVersion {
V3_3,
V3_7,
V3_8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RfbPixelFormat {
pub bits_per_pixel: u8,
pub depth: u8,
pub big_endian: bool,
pub true_colour: bool,
pub red_max: u16,
pub green_max: u16,
pub blue_max: u16,
pub red_shift: u8,
pub green_shift: u8,
pub blue_shift: u8,
}
impl Default for RfbPixelFormat {
fn default() -> Self {
Self {
bits_per_pixel: 32,
depth: 24,
big_endian: false,
true_colour: true,
red_max: 255,
green_max: 255,
blue_max: 255,
red_shift: 16,
green_shift: 8,
blue_shift: 0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FramebufferUpdateRequest {
pub incremental: bool,
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FrameSendOutcome {
NotSent,
FrameSent,
DesktopSizeSent,
}
#[derive(Debug)]
pub enum RfbInputEvent {
Key(RfbKeyEvent),
Pointer(RfbPointerEvent),
SetPixelFormat(RfbPixelFormat),
SetEncodings {
encoding_enabled: bool,
resumed: bool,
},
FramebufferUpdateRequest(FramebufferUpdateRequest),
UnsupportedClientCutText,
Disconnected,
}
#[derive(Debug)]
pub struct RfbKeyEvent {
pub down: bool,
pub keysym: u32,
}
#[derive(Debug)]
pub struct RfbPointerEvent {
pub x: u16,
pub y: u16,
pub button_mask: u8,
pub previous_button_mask: u8,
}
#[derive(Default, Clone, Copy)]
struct ClientEncodings {
has_tight: bool,
has_jpeg_quality: bool,
has_h264: bool,
has_resize: bool,
}
#[derive(Default)]
struct KeyboardState {
modifiers: KeyboardModifiers,
}
pub struct RfbClient {
stream: TcpStream,
peer: SocketAddr,
config: VncConfig,
encodings: ClientEncodings,
pixel_format: RfbPixelFormat,
width: u16,
height: u16,
last_buttons: u8,
keyboard: KeyboardState,
input_buffer: BytesMut,
pending_request: Option<bool>,
last_sent_sequence: Option<u64>,
h264_waiting_keyframe: bool,
shared: bool,
shutdown_tx: broadcast::Sender<()>,
}
impl RfbClient {
pub fn new(stream: TcpStream, peer: SocketAddr, config: VncConfig) -> Self {
let (shutdown_tx, _) = broadcast::channel(1);
Self {
stream,
peer,
config,
encodings: ClientEncodings::default(),
pixel_format: RfbPixelFormat::default(),
width: 800,
height: 600,
last_buttons: 0,
keyboard: KeyboardState::default(),
input_buffer: BytesMut::with_capacity(1024),
pending_request: None,
last_sent_sequence: None,
h264_waiting_keyframe: true,
shared: false,
shutdown_tx,
}
}
pub fn set_size(&mut self, width: u16, height: u16) {
self.width = width.max(1);
self.height = height.max(1);
}
pub fn shutdown_receiver(&self) -> broadcast::Receiver<()> {
self.shutdown_tx.subscribe()
}
pub fn framebuffer_size(&self) -> (u16, u16) {
(self.width, self.height)
}
pub fn has_pending_request(&self) -> bool {
self.pending_request.is_some()
}
pub fn has_complete_buffered_input(&self) -> Result<bool> {
Ok(input_message_len(&self.input_buffer)?
.is_some_and(|length| self.input_buffer.len() >= length))
}
pub fn shared(&self) -> bool {
self.shared
}
pub async fn handshake(&mut self) -> Result<()> {
self.stream.write_all(b"RFB 003.008\n").await?;
let mut version = [0u8; 12];
self.stream.read_exact(&mut version).await?;
let version = parse_rfb_version(&version)?;
match version {
RfbVersion::V3_3 => {
self.stream
.write_all(&(SECURITY_TYPE_VNC_AUTH as u32).to_be_bytes())
.await?;
}
RfbVersion::V3_7 | RfbVersion::V3_8 => {
self.stream.write_all(&[1, SECURITY_TYPE_VNC_AUTH]).await?;
let sec_type = read_u8(&mut self.stream).await?;
if sec_type != SECURITY_TYPE_VNC_AUTH {
return Err(AppError::BadRequest("VNCAuth is required".to_string()));
}
}
}
self.handle_vnc_auth(version).await?;
self.shared = read_u8(&mut self.stream).await? != 0;
self.write_server_init().await?;
self.read_until_set_encodings().await?;
self.validate_encoding_policy()?;
tracing::info!(
"VNC client {} negotiated encoding {:?}",
self.peer,
self.config.encoding
);
Ok(())
}
async fn handle_vnc_auth(&mut self, version: RfbVersion) -> Result<()> {
let challenge: [u8; 16] = rand::random();
self.stream.write_all(&challenge).await?;
let mut response = [0u8; 16];
self.stream.read_exact(&mut response).await?;
let password = self.config.password.as_deref().unwrap_or("");
let expected = encrypt_vnc_challenge(&challenge, password)?;
let ok = response == expected;
self.stream
.write_all(&(if ok { 0u32 } else { 1u32 }).to_be_bytes())
.await?;
if !ok {
if version == RfbVersion::V3_8 {
let reason = b"Invalid VNC password";
self.stream
.write_all(&(reason.len() as u32).to_be_bytes())
.await?;
self.stream.write_all(reason).await?;
}
self.stream.flush().await?;
return Err(AppError::BadRequest("Invalid VNC password".to_string()));
}
Ok(())
}
async fn write_server_init(&mut self) -> Result<()> {
self.stream.write_all(&self.width.to_be_bytes()).await?;
self.stream.write_all(&self.height.to_be_bytes()).await?;
self.stream
.write_all(&pixel_format_bytes(self.pixel_format))
.await?;
let name = b"One-KVM VNC";
self.stream
.write_all(&(name.len() as u32).to_be_bytes())
.await?;
self.stream.write_all(name).await?;
self.stream.flush().await?;
Ok(())
}
async fn read_until_set_encodings(&mut self) -> Result<()> {
loop {
match self.read_input_event().await? {
RfbInputEvent::SetEncodings { .. } => return Ok(()),
RfbInputEvent::Disconnected => {
return Err(AppError::BadRequest(
"VNC client disconnected during negotiation".to_string(),
))
}
_ => {}
}
}
}
fn validate_encoding_policy(&self) -> Result<()> {
match self.config.encoding {
VncEncoding::TightJpeg => {
if !self.configured_encoding_enabled() {
return Err(AppError::BadRequest(
"VNC client must support Tight JPEG encoding".to_string(),
));
}
}
VncEncoding::H264 => {
if !self.configured_encoding_enabled() {
return Err(AppError::BadRequest(
"VNC client must support Open H.264 encoding".to_string(),
));
}
}
}
Ok(())
}
pub async fn read_input_event(&mut self) -> Result<RfbInputEvent> {
loop {
if let Some(event) = self.decode_buffered_input()? {
return Ok(event);
}
// read_buf is cancellation-safe and keeps completed reads in input_buffer.
let read = self.stream.read_buf(&mut self.input_buffer).await?;
if read == 0 {
if self.input_buffer.is_empty() {
return Ok(RfbInputEvent::Disconnected);
}
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"VNC client disconnected during an input message",
)
.into());
}
}
}
fn decode_buffered_input(&mut self) -> Result<Option<RfbInputEvent>> {
let Some(message_len) = input_message_len(&self.input_buffer)? else {
return Ok(None);
};
if self.input_buffer.len() < message_len {
return Ok(None);
}
let message = self.input_buffer.split_to(message_len);
let event = match message[0] {
0 => {
let format = parse_pixel_format(&message[4..20]);
self.pixel_format = format;
RfbInputEvent::SetPixelFormat(format)
}
2 => {
let was_enabled = self.configured_encoding_enabled();
self.encodings = parse_encodings(&message[4..]);
let encoding_enabled = self.configured_encoding_enabled();
let resumed = !was_enabled && encoding_enabled;
if resumed && self.config.encoding == VncEncoding::H264 {
self.h264_waiting_keyframe = true;
}
RfbInputEvent::SetEncodings {
encoding_enabled,
resumed,
}
}
3 => {
let request = FramebufferUpdateRequest {
incremental: message[1] != 0,
x: u16::from_be_bytes([message[2], message[3]]),
y: u16::from_be_bytes([message[4], message[5]]),
width: u16::from_be_bytes([message[6], message[7]]),
height: u16::from_be_bytes([message[8], message[9]]),
};
self.pending_request =
Some(self.pending_request.map_or(request.incremental, |pending| {
pending && request.incremental
}));
if !request.incremental && self.config.encoding == VncEncoding::H264 {
self.h264_waiting_keyframe = true;
}
RfbInputEvent::FramebufferUpdateRequest(request)
}
4 => RfbInputEvent::Key(RfbKeyEvent {
down: message[1] != 0,
keysym: u32::from_be_bytes([message[4], message[5], message[6], message[7]]),
}),
5 => {
let button_mask = message[1];
let previous_button_mask = self.last_buttons;
self.last_buttons = button_mask;
RfbInputEvent::Pointer(RfbPointerEvent {
x: u16::from_be_bytes([message[2], message[3]]),
y: u16::from_be_bytes([message[4], message[5]]),
button_mask,
previous_button_mask,
})
}
6 => RfbInputEvent::UnsupportedClientCutText,
msg_type => {
return Err(AppError::BadRequest(format!(
"Unsupported RFB message {}",
msg_type
)))
}
};
Ok(Some(event))
}
fn configured_encoding_enabled(&self) -> bool {
match self.config.encoding {
VncEncoding::TightJpeg => {
self.encodings.has_tight
&& self.encodings.has_jpeg_quality
&& matches!(self.pixel_format.bits_per_pixel, 16 | 32)
&& self.pixel_format.true_colour
}
VncEncoding::H264 => self.encodings.has_h264,
}
}
pub fn key_event_to_hid(&mut self, event: RfbKeyEvent) -> Option<KeyboardEvent> {
let (key, shifted) = match keysym_to_key(event.keysym) {
Some(mapping) => mapping,
None => {
tracing::debug!(
"Ignoring unsupported VNC keysym 0x{:08x} from {}",
event.keysym,
self.peer
);
return None;
}
};
if key.is_modifier() {
update_modifier(&mut self.keyboard.modifiers, key, event.down);
}
let mut modifiers = self.keyboard.modifiers;
if shifted && !modifiers.left_shift && !modifiers.right_shift && event.down {
modifiers.left_shift = true;
}
Some(KeyboardEvent {
event_type: if event.down {
KeyEventType::Down
} else {
KeyEventType::Up
},
key,
modifiers,
})
}
pub async fn send_frame(&mut self, frame: &RfbFrame) -> Result<FrameSendOutcome> {
let Some(incremental) = self.pending_request else {
return Ok(FrameSendOutcome::NotSent);
};
if !self.configured_encoding_enabled() {
return Ok(FrameSendOutcome::NotSent);
}
let (width, height) = frame.size();
if width != self.width || height != self.height {
if !self.encodings.has_resize {
return Err(AppError::BadRequest(
"VNC client does not support DesktopSize resize; reconnect required"
.to_string(),
));
}
self.write_frame_header(width, height, ENCODING_DESKTOP_SIZE)
.await?;
self.stream.flush().await?;
self.width = width.max(1);
self.height = height.max(1);
self.pending_request = None;
self.last_sent_sequence = None;
self.h264_waiting_keyframe = true;
return Ok(FrameSendOutcome::DesktopSizeSent);
}
let sequence = frame.sequence();
if incremental && self.last_sent_sequence.is_some_and(|last| sequence <= last) {
return Ok(FrameSendOutcome::NotSent);
}
match frame {
RfbFrame::Jpeg {
data,
width,
height,
..
} => {
if self.config.encoding != VncEncoding::TightJpeg {
return Ok(FrameSendOutcome::NotSent);
}
self.write_frame_header(*width, *height, ENCODING_TIGHT)
.await?;
write_tight_jpeg_payload(&mut self.stream, &data).await?;
}
RfbFrame::H264 {
data,
width,
height,
key,
..
} => {
if self.config.encoding != VncEncoding::H264 || (self.h264_waiting_keyframe && !key)
{
return Ok(FrameSendOutcome::NotSent);
}
self.write_frame_header(*width, *height, ENCODING_H264)
.await?;
self.stream
.write_all(&(data.len() as u32).to_be_bytes())
.await?;
self.stream
.write_all(&(self.h264_waiting_keyframe as u32).to_be_bytes())
.await?;
self.stream.write_all(&data).await?;
self.h264_waiting_keyframe = false;
}
}
self.stream.flush().await?;
self.pending_request = None;
self.last_sent_sequence = Some(sequence);
Ok(FrameSendOutcome::FrameSent)
}
async fn write_frame_header(&mut self, width: u16, height: u16, encoding: i32) -> Result<()> {
self.stream.write_all(&[0, 0]).await?;
self.stream.write_all(&1u16.to_be_bytes()).await?;
self.stream.write_all(&0u16.to_be_bytes()).await?;
self.stream.write_all(&0u16.to_be_bytes()).await?;
self.stream.write_all(&width.to_be_bytes()).await?;
self.stream.write_all(&height.to_be_bytes()).await?;
self.stream.write_all(&encoding.to_be_bytes()).await?;
Ok(())
}
}
fn input_message_len(buffer: &[u8]) -> Result<Option<usize>> {
let Some(&msg_type) = buffer.first() else {
return Ok(None);
};
let len = match msg_type {
0 => 20,
2 => {
if buffer.len() < 4 {
return Ok(None);
}
let count = u16::from_be_bytes([buffer[2], buffer[3]]) as usize;
if count > MAX_ENCODING_COUNT {
return Err(AppError::BadRequest(
"Invalid VNC encoding list".to_string(),
));
}
4 + count * 4
}
3 => 10,
4 => 8,
5 => 6,
6 => {
if buffer.len() < 8 {
return Ok(None);
}
let payload_len =
u32::from_be_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]) as usize;
if payload_len > MAX_CLIPBOARD_SIZE {
return Err(AppError::BadRequest(
"VNC clipboard message is too large".to_string(),
));
}
8 + payload_len
}
_ => {
return Err(AppError::BadRequest(format!(
"Unsupported RFB message {}",
msg_type
)))
}
};
Ok(Some(len))
}
fn parse_rfb_version(version: &[u8; 12]) -> Result<RfbVersion> {
if &version[..4] != b"RFB "
|| version[7] != b'.'
|| version[11] != b'\n'
|| !version[4..7].iter().all(u8::is_ascii_digit)
|| !version[8..11].iter().all(u8::is_ascii_digit)
{
return Err(AppError::BadRequest("Invalid RFB version".to_string()));
}
let major = (version[4] - b'0') as u16 * 100
+ (version[5] - b'0') as u16 * 10
+ (version[6] - b'0') as u16;
if major != 3 {
return Err(AppError::BadRequest(
"Unsupported RFB major version".to_string(),
));
}
let minor = (version[8] - b'0') as u16 * 100
+ (version[9] - b'0') as u16 * 10
+ (version[10] - b'0') as u16;
Ok(match minor {
7 => RfbVersion::V3_7,
8 => RfbVersion::V3_8,
_ => RfbVersion::V3_3,
})
}
fn pixel_format_bytes(format: RfbPixelFormat) -> [u8; 16] {
let mut bytes = [0u8; 16];
bytes[0] = format.bits_per_pixel;
bytes[1] = format.depth;
bytes[2] = u8::from(format.big_endian);
bytes[3] = u8::from(format.true_colour);
bytes[4..6].copy_from_slice(&format.red_max.to_be_bytes());
bytes[6..8].copy_from_slice(&format.green_max.to_be_bytes());
bytes[8..10].copy_from_slice(&format.blue_max.to_be_bytes());
bytes[10] = format.red_shift;
bytes[11] = format.green_shift;
bytes[12] = format.blue_shift;
bytes
}
fn parse_pixel_format(bytes: &[u8]) -> RfbPixelFormat {
RfbPixelFormat {
bits_per_pixel: bytes[0],
depth: bytes[1],
big_endian: bytes[2] != 0,
true_colour: bytes[3] != 0,
red_max: u16::from_be_bytes([bytes[4], bytes[5]]),
green_max: u16::from_be_bytes([bytes[6], bytes[7]]),
blue_max: u16::from_be_bytes([bytes[8], bytes[9]]),
red_shift: bytes[10],
green_shift: bytes[11],
blue_shift: bytes[12],
}
}
fn parse_encodings(bytes: &[u8]) -> ClientEncodings {
let mut encodings = ClientEncodings::default();
for encoding in bytes.chunks_exact(4) {
match i32::from_be_bytes([encoding[0], encoding[1], encoding[2], encoding[3]]) {
ENCODING_TIGHT => encodings.has_tight = true,
ENCODING_H264 => encodings.has_h264 = true,
ENCODING_DESKTOP_SIZE => encodings.has_resize = true,
-32..=-23 => encodings.has_jpeg_quality = true,
_ => {}
}
}
encodings
}
async fn write_tight_jpeg_payload(stream: &mut TcpStream, data: &[u8]) -> Result<()> {
if data.len() > 0x3f_ffff {
return Err(AppError::BadRequest(
"JPEG frame too large for Tight encoding".to_string(),
));
}
stream.write_all(&[0b1001_1111]).await?;
write_compact_len(stream, data.len()).await?;
stream.write_all(data).await?;
Ok(())
}
async fn write_compact_len(stream: &mut TcpStream, len: usize) -> Result<()> {
if len <= 127 {
stream.write_all(&[(len & 0x7f) as u8]).await?;
} else if len <= 16_383 {
stream
.write_all(&[((len & 0x7f) as u8) | 0x80, ((len >> 7) & 0x7f) as u8])
.await?;
} else {
stream
.write_all(&[
((len & 0x7f) as u8) | 0x80,
(((len >> 7) & 0x7f) as u8) | 0x80,
((len >> 14) & 0xff) as u8,
])
.await?;
}
Ok(())
}
fn encrypt_vnc_challenge(challenge: &[u8; 16], password: &str) -> Result<[u8; 16]> {
let mut key = [0u8; 8];
for (dst, src) in key.iter_mut().zip(password.as_bytes().iter().take(8)) {
*dst = reverse_bits(*src);
}
let cipher = des::Des::new_from_slice(&key)
.map_err(|_| AppError::BadRequest("Invalid VNC DES key".to_string()))?;
let mut out = *challenge;
for chunk in out.chunks_exact_mut(8) {
cipher.encrypt_block(chunk.into());
}
Ok(out)
}
fn reverse_bits(byte: u8) -> u8 {
byte.reverse_bits()
}
async fn read_u8(stream: &mut TcpStream) -> Result<u8> {
let mut buf = [0u8; 1];
stream.read_exact(&mut buf).await?;
Ok(buf[0])
}
fn keysym_to_key(keysym: u32) -> Option<(CanonicalKey, bool)> {
let plain = |key| Some((key, false));
let shifted = |key| Some((key, true));
match keysym {
0xff08 => plain(CanonicalKey::Backspace),
0xff09 => plain(CanonicalKey::Tab),
0xff0d => plain(CanonicalKey::Enter),
0xff13 => plain(CanonicalKey::Pause),
0xff14 => plain(CanonicalKey::ScrollLock),
0xff15 => plain(CanonicalKey::PrintScreen),
0xff1b => plain(CanonicalKey::Escape),
0xff50 => plain(CanonicalKey::Home),
0xff51 => plain(CanonicalKey::ArrowLeft),
0xff52 => plain(CanonicalKey::ArrowUp),
0xff53 => plain(CanonicalKey::ArrowRight),
0xff54 => plain(CanonicalKey::ArrowDown),
0xff55 => plain(CanonicalKey::PageUp),
0xff56 => plain(CanonicalKey::PageDown),
0xff57 => plain(CanonicalKey::End),
0xff61 => plain(CanonicalKey::PrintScreen),
0xff63 => plain(CanonicalKey::Insert),
0xff67 => plain(CanonicalKey::ContextMenu),
0xff6b => plain(CanonicalKey::Pause),
0xff7f => plain(CanonicalKey::NumLock),
0xff80 => plain(CanonicalKey::Space),
0xff89 => plain(CanonicalKey::Tab),
0xff8d => plain(CanonicalKey::NumpadEnter),
0xff95 => plain(CanonicalKey::Numpad7),
0xff96 => plain(CanonicalKey::Numpad4),
0xff97 => plain(CanonicalKey::Numpad8),
0xff98 => plain(CanonicalKey::Numpad6),
0xff99 => plain(CanonicalKey::Numpad2),
0xff9a => plain(CanonicalKey::Numpad9),
0xff9b => plain(CanonicalKey::Numpad3),
0xff9c => plain(CanonicalKey::Numpad1),
0xff9d => plain(CanonicalKey::Numpad5),
0xff9e => plain(CanonicalKey::Numpad0),
0xff9f => plain(CanonicalKey::NumpadDecimal),
0xffaa => plain(CanonicalKey::NumpadMultiply),
0xffab => plain(CanonicalKey::NumpadAdd),
0xffad => plain(CanonicalKey::NumpadSubtract),
0xffae => plain(CanonicalKey::NumpadDecimal),
0xffaf => plain(CanonicalKey::NumpadDivide),
0xffb0 => plain(CanonicalKey::Numpad0),
0xffb1 => plain(CanonicalKey::Numpad1),
0xffb2 => plain(CanonicalKey::Numpad2),
0xffb3 => plain(CanonicalKey::Numpad3),
0xffb4 => plain(CanonicalKey::Numpad4),
0xffb5 => plain(CanonicalKey::Numpad5),
0xffb6 => plain(CanonicalKey::Numpad6),
0xffb7 => plain(CanonicalKey::Numpad7),
0xffb8 => plain(CanonicalKey::Numpad8),
0xffb9 => plain(CanonicalKey::Numpad9),
0xffbd => plain(CanonicalKey::Equal),
0xffbe..=0xffc9 => {
CanonicalKey::from_hid_usage((keysym - 0xffbe + 0x3a) as u8).map(|key| (key, false))
}
0xffca..=0xffd5 => {
CanonicalKey::from_hid_usage((keysym - 0xffca + 0x68) as u8).map(|key| (key, false))
}
0xffe1 => plain(CanonicalKey::ShiftLeft),
0xffe2 => plain(CanonicalKey::ShiftRight),
0xffe3 => plain(CanonicalKey::ControlLeft),
0xffe4 => plain(CanonicalKey::ControlRight),
0xffe5 | 0xffe6 => plain(CanonicalKey::CapsLock),
0xffe7 | 0xffeb => plain(CanonicalKey::MetaLeft),
0xffe8 | 0xffec => plain(CanonicalKey::MetaRight),
0xffe9 => plain(CanonicalKey::AltLeft),
0xffea => plain(CanonicalKey::AltRight),
0xffff => plain(CanonicalKey::Delete),
0x20 => plain(CanonicalKey::Space),
0x61..=0x7a => {
CanonicalKey::from_hid_usage((keysym - 0x61 + 0x04) as u8).map(|key| (key, false))
}
0x41..=0x5a => {
CanonicalKey::from_hid_usage((keysym - 0x41 + 0x04) as u8).map(|key| (key, true))
}
0x31..=0x39 => {
CanonicalKey::from_hid_usage((keysym - 0x31 + 0x1e) as u8).map(|key| (key, false))
}
0x30 => plain(CanonicalKey::Digit0),
0x21 => shifted(CanonicalKey::Digit1),
0x40 => shifted(CanonicalKey::Digit2),
0x23 => shifted(CanonicalKey::Digit3),
0x24 => shifted(CanonicalKey::Digit4),
0x25 => shifted(CanonicalKey::Digit5),
0x5e => shifted(CanonicalKey::Digit6),
0x26 => shifted(CanonicalKey::Digit7),
0x2a => shifted(CanonicalKey::Digit8),
0x28 => shifted(CanonicalKey::Digit9),
0x29 => shifted(CanonicalKey::Digit0),
0x5f => shifted(CanonicalKey::Minus),
0x2b => shifted(CanonicalKey::Equal),
0x7b => shifted(CanonicalKey::BracketLeft),
0x7d => shifted(CanonicalKey::BracketRight),
0x7c => shifted(CanonicalKey::Backslash),
0x3a => shifted(CanonicalKey::Semicolon),
0x22 => shifted(CanonicalKey::Quote),
0x7e => shifted(CanonicalKey::Backquote),
0x3c => shifted(CanonicalKey::Comma),
0x3e => shifted(CanonicalKey::Period),
0x3f => shifted(CanonicalKey::Slash),
0x2d => plain(CanonicalKey::Minus),
0x3d => plain(CanonicalKey::Equal),
0x5b => plain(CanonicalKey::BracketLeft),
0x5d => plain(CanonicalKey::BracketRight),
0x5c => plain(CanonicalKey::Backslash),
0x3b => plain(CanonicalKey::Semicolon),
0x27 => plain(CanonicalKey::Quote),
0x60 => plain(CanonicalKey::Backquote),
0x2c => plain(CanonicalKey::Comma),
0x2e => plain(CanonicalKey::Period),
0x2f => plain(CanonicalKey::Slash),
_ => None,
}
}
fn update_modifier(modifiers: &mut KeyboardModifiers, key: CanonicalKey, down: bool) {
match key {
CanonicalKey::ControlLeft => modifiers.left_ctrl = down,
CanonicalKey::ShiftLeft => modifiers.left_shift = down,
CanonicalKey::AltLeft => modifiers.left_alt = down,
CanonicalKey::MetaLeft => modifiers.left_meta = down,
CanonicalKey::ControlRight => modifiers.right_ctrl = down,
CanonicalKey::ShiftRight => modifiers.right_shift = down,
CanonicalKey::AltRight => modifiers.right_alt = down,
CanonicalKey::MetaRight => modifiers.right_meta = down,
_ => {}
}
}
pub fn pointer_event_to_hid(event: RfbPointerEvent, width: u16, height: u16) -> Vec<MouseEvent> {
let mut out = Vec::new();
let max_x = width.saturating_sub(1);
let max_y = height.saturating_sub(1);
let abs_x = if max_x == 0 {
0
} else {
((event.x.min(max_x) as u64 * 32767) / max_x as u64) as i32
};
let abs_y = if max_y == 0 {
0
} else {
((event.y.min(max_y) as u64 * 32767) / max_y as u64) as i32
};
out.push(MouseEvent {
event_type: MouseEventType::MoveAbs,
x: abs_x,
y: abs_y,
button: None,
scroll: 0,
});
if event.button_mask & 0x08 != 0 {
out.push(MouseEvent::scroll(1));
}
if event.button_mask & 0x10 != 0 {
out.push(MouseEvent::scroll(-1));
}
for (bit, button) in [
(0x01, MouseButton::Left),
(0x02, MouseButton::Middle),
(0x04, MouseButton::Right),
] {
if (event.button_mask ^ event.previous_button_mask) & bit == 0 {
continue;
}
if event.button_mask & bit != 0 {
out.push(MouseEvent::button_down(button));
} else {
out.push(MouseEvent::button_up(button));
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use std::future::{poll_fn, Future};
use std::task::Poll;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
async fn tcp_pair() -> (RfbClient, TcpStream) {
tcp_pair_with_config(VncConfig::default()).await
}
async fn tcp_pair_with_config(config: VncConfig) -> (RfbClient, TcpStream) {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("failed to bind test listener");
let addr = listener.local_addr().expect("missing listener address");
let (accepted, connected) = tokio::join!(listener.accept(), TcpStream::connect(addr));
let (server, peer) = accepted.expect("failed to accept test connection");
let client = connected.expect("failed to connect test client");
(RfbClient::new(server, peer, config), client)
}
fn set_encodings_message(encodings: &[i32]) -> Vec<u8> {
let mut message = vec![2, 0];
message.extend_from_slice(&(encodings.len() as u16).to_be_bytes());
for encoding in encodings {
message.extend_from_slice(&encoding.to_be_bytes());
}
message
}
fn update_request(incremental: bool) -> [u8; 10] {
[3, u8::from(incremental), 0, 0, 0, 0, 0x03, 0x20, 0x02, 0x58]
}
async fn complete_client_handshake(client: &mut TcpStream, version: &[u8; 12]) {
let mut server_version = [0u8; 12];
client
.read_exact(&mut server_version)
.await
.expect("missing server version");
assert_eq!(&server_version, b"RFB 003.008\n");
client
.write_all(version)
.await
.expect("failed to send client version");
match parse_rfb_version(version).expect("test version should parse") {
RfbVersion::V3_3 => {
let mut security = [0u8; 4];
client
.read_exact(&mut security)
.await
.expect("missing 3.3 security type");
assert_eq!(u32::from_be_bytes(security), SECURITY_TYPE_VNC_AUTH as u32);
}
RfbVersion::V3_7 | RfbVersion::V3_8 => {
let mut security = [0u8; 2];
client
.read_exact(&mut security)
.await
.expect("missing security list");
assert_eq!(security, [1, SECURITY_TYPE_VNC_AUTH]);
client
.write_all(&[SECURITY_TYPE_VNC_AUTH])
.await
.expect("failed to choose security type");
}
}
let mut challenge = [0u8; 16];
client
.read_exact(&mut challenge)
.await
.expect("missing VNC challenge");
client
.write_all(
&encrypt_vnc_challenge(&challenge, "secret").expect("challenge encryption failed"),
)
.await
.expect("failed to send challenge response");
let mut security_result = [0u8; 4];
client
.read_exact(&mut security_result)
.await
.expect("missing security result");
assert_eq!(security_result, 0u32.to_be_bytes());
client
.write_all(&[1])
.await
.expect("failed to send ClientInit");
let mut server_init = [0u8; 24];
client
.read_exact(&mut server_init)
.await
.expect("missing ServerInit");
let name_len = u32::from_be_bytes([
server_init[20],
server_init[21],
server_init[22],
server_init[23],
]) as usize;
let mut name = vec![0u8; name_len];
client
.read_exact(&mut name)
.await
.expect("missing desktop name");
assert_eq!(&name, b"One-KVM VNC");
let mut messages = set_encodings_message(&[ENCODING_TIGHT, -23, ENCODING_DESKTOP_SIZE]);
messages.extend_from_slice(&update_request(true));
client
.write_all(&messages)
.await
.expect("failed to send initial messages");
}
#[test]
fn version_parser_supports_and_downgrades_rfb_3x() {
assert_eq!(
parse_rfb_version(b"RFB 003.003\n").unwrap(),
RfbVersion::V3_3
);
assert_eq!(
parse_rfb_version(b"RFB 003.007\n").unwrap(),
RfbVersion::V3_7
);
assert_eq!(
parse_rfb_version(b"RFB 003.008\n").unwrap(),
RfbVersion::V3_8
);
assert_eq!(
parse_rfb_version(b"RFB 003.889\n").unwrap(),
RfbVersion::V3_3
);
assert!(parse_rfb_version(b"RFB 004.008\n").is_err());
assert!(parse_rfb_version(b"RFB 003.08x\n").is_err());
assert!(parse_rfb_version(b"RFB 003-008\n").is_err());
}
#[tokio::test]
async fn handshake_supports_all_versions_and_preserves_coalesced_request() {
for version in [
b"RFB 003.003\n",
b"RFB 003.007\n",
b"RFB 003.008\n",
b"RFB 003.889\n",
] {
let config = VncConfig {
password: Some("secret".to_string()),
..VncConfig::default()
};
let (mut server, mut client) = tcp_pair_with_config(config).await;
let (server_result, ()) = tokio::join!(server.handshake(), async {
complete_client_handshake(&mut client, version).await;
});
server_result.expect("handshake should succeed");
assert!(server.shared());
assert!(matches!(
server
.read_input_event()
.await
.expect("coalesced request was lost"),
RfbInputEvent::FramebufferUpdateRequest(_)
));
}
}
#[tokio::test]
async fn auth_failure_reason_is_only_sent_for_rfb_3_8() {
for (version, expected_tail) in [
(RfbVersion::V3_3, Vec::new()),
(RfbVersion::V3_7, Vec::new()),
(
RfbVersion::V3_8,
[
&("Invalid VNC password".len() as u32).to_be_bytes()[..],
b"Invalid VNC password",
]
.concat(),
),
] {
let config = VncConfig {
password: Some("secret".to_string()),
..VncConfig::default()
};
let (mut server, mut client) = tcp_pair_with_config(config).await;
let task = tokio::spawn(async move { server.handle_vnc_auth(version).await });
let mut challenge = [0u8; 16];
client.read_exact(&mut challenge).await.unwrap();
let mut wrong = encrypt_vnc_challenge(&challenge, "secret").unwrap();
wrong[0] ^= 0xff;
client.write_all(&wrong).await.unwrap();
assert!(task.await.unwrap().is_err());
let mut result = Vec::new();
client.read_to_end(&mut result).await.unwrap();
assert_eq!(&result[..4], &1u32.to_be_bytes());
assert_eq!(&result[4..], expected_tail);
}
}
#[tokio::test]
async fn input_read_resumes_after_cancellation_mid_message() {
let (mut server, mut client) = tcp_pair().await;
client
.write_all(&[3])
.await
.expect("failed to write message type");
server
.stream
.readable()
.await
.expect("server stream did not become readable");
let mut read = Box::pin(server.read_input_event());
let state = poll_fn(|cx| Poll::Ready(read.as_mut().poll(cx))).await;
assert!(state.is_pending());
drop(read);
assert_eq!(&server.input_buffer[..], &[3]);
client
.write_all(&[
1, 0, 0, 0, 0, 0x05, 0x00, 0x02, 0xd0, // update request body
5, 1, 0, 10, 0, 20, // pointer event
5, 0, 0, 11, 0, 21, // pointer release
])
.await
.expect("failed to write remaining messages");
assert!(matches!(
server.read_input_event().await.expect("update read failed"),
RfbInputEvent::FramebufferUpdateRequest(FramebufferUpdateRequest {
incremental: true,
..
})
));
match server
.read_input_event()
.await
.expect("pointer read failed")
{
RfbInputEvent::Pointer(pointer) => {
assert_eq!(pointer.button_mask, 1);
assert_eq!(pointer.previous_button_mask, 0);
assert_eq!(pointer.x, 10);
assert_eq!(pointer.y, 20);
}
_ => panic!("expected pointer event"),
}
match server
.read_input_event()
.await
.expect("pointer release read failed")
{
RfbInputEvent::Pointer(pointer) => {
assert_eq!(pointer.button_mask, 0);
assert_eq!(pointer.previous_button_mask, 1);
assert_eq!(pointer.x, 11);
assert_eq!(pointer.y, 21);
}
_ => panic!("expected pointer release event"),
}
}
#[tokio::test]
async fn coalesced_input_messages_keep_their_boundaries() {
let (mut server, mut client) = tcp_pair().await;
client
.write_all(&[
4, 1, 0, 0, 0, 0, 0, 0x61, // key down: a
6, 0, 0, 0, 0, 0, 0, 3, b'a', b'b', b'c', // clipboard
])
.await
.expect("failed to write coalesced messages");
match server.read_input_event().await.expect("key read failed") {
RfbInputEvent::Key(key) => {
assert!(key.down);
assert_eq!(key.keysym, 0x61);
}
_ => panic!("expected key event"),
}
assert!(matches!(
server
.read_input_event()
.await
.expect("clipboard read failed"),
RfbInputEvent::UnsupportedClientCutText
));
}
#[test]
fn input_message_limits_are_enforced_before_allocation() {
let encodings = input_message_len(&[2, 0, 0x04, 0x01]);
assert!(matches!(encodings, Err(AppError::BadRequest(_))));
let clipboard = input_message_len(&[6, 0, 0, 0, 0, 0x10, 0, 1]);
assert!(matches!(clipboard, Err(AppError::BadRequest(_))));
}
#[tokio::test]
async fn eof_distinguishes_disconnect_from_truncated_message() {
let (mut clean_server, clean_client) = tcp_pair().await;
drop(clean_client);
assert!(matches!(
clean_server
.read_input_event()
.await
.expect("clean disconnect failed"),
RfbInputEvent::Disconnected
));
let (mut partial_server, mut partial_client) = tcp_pair().await;
partial_client
.write_all(&[4, 1])
.await
.expect("failed to write partial message");
drop(partial_client);
match partial_server.read_input_event().await {
Err(AppError::Io(err)) => {
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof)
}
_ => panic!("expected truncated message error"),
}
}
#[tokio::test]
async fn set_encodings_and_pixel_format_control_tight_availability() {
let (mut server, _client) = tcp_pair().await;
server
.input_buffer
.extend_from_slice(&set_encodings_message(&[ENCODING_TIGHT, -23]));
assert!(matches!(
server.decode_buffered_input().unwrap(),
Some(RfbInputEvent::SetEncodings {
encoding_enabled: true,
resumed: true,
})
));
let mut indexed_format = vec![0, 0, 0, 0];
let mut format = RfbPixelFormat::default();
format.true_colour = false;
indexed_format.extend_from_slice(&pixel_format_bytes(format));
server.input_buffer.extend_from_slice(&indexed_format);
assert!(matches!(
server.decode_buffered_input().unwrap(),
Some(RfbInputEvent::SetPixelFormat(RfbPixelFormat {
true_colour: false,
..
}))
));
assert!(!server.configured_encoding_enabled());
let mut true_colour_16 = vec![0, 0, 0, 0];
format.bits_per_pixel = 16;
format.depth = 16;
format.true_colour = true;
true_colour_16.extend_from_slice(&pixel_format_bytes(format));
server.input_buffer.extend_from_slice(&true_colour_16);
server.decode_buffered_input().unwrap();
assert!(server.configured_encoding_enabled());
server
.input_buffer
.extend_from_slice(&set_encodings_message(&[]));
assert!(matches!(
server.decode_buffered_input().unwrap(),
Some(RfbInputEvent::SetEncodings {
encoding_enabled: false,
resumed: false,
})
));
server
.input_buffer
.extend_from_slice(&set_encodings_message(&[ENCODING_TIGHT, -32]));
assert!(matches!(
server.decode_buffered_input().unwrap(),
Some(RfbInputEvent::SetEncodings {
encoding_enabled: true,
resumed: true,
})
));
}
#[tokio::test]
async fn keyboard_tracks_modifiers_and_synthesizes_shift() {
let (mut server, _client) = tcp_pair().await;
let ctrl = server
.key_event_to_hid(RfbKeyEvent {
down: true,
keysym: 0xffe3,
})
.unwrap();
assert_eq!(ctrl.key, CanonicalKey::ControlLeft);
let alt = server
.key_event_to_hid(RfbKeyEvent {
down: true,
keysym: 0xffea,
})
.unwrap();
assert!(alt.modifiers.left_ctrl);
assert!(alt.modifiers.right_alt);
let delete = server
.key_event_to_hid(RfbKeyEvent {
down: true,
keysym: 0xffff,
})
.unwrap();
assert_eq!(delete.key, CanonicalKey::Delete);
assert!(delete.modifiers.left_ctrl);
assert!(delete.modifiers.right_alt);
let uppercase = server
.key_event_to_hid(RfbKeyEvent {
down: true,
keysym: b'A' as u32,
})
.unwrap();
assert!(uppercase.modifiers.left_shift);
let uppercase_up = server
.key_event_to_hid(RfbKeyEvent {
down: false,
keysym: b'A' as u32,
})
.unwrap();
assert!(!uppercase_up.modifiers.left_shift);
assert!(uppercase_up.modifiers.left_ctrl);
assert!(uppercase_up.modifiers.right_alt);
assert!(server
.key_event_to_hid(RfbKeyEvent {
down: true,
keysym: 0x0101_f642,
})
.is_none());
let plain = server
.key_event_to_hid(RfbKeyEvent {
down: true,
keysym: b'a' as u32,
})
.unwrap();
assert!(!plain.modifiers.left_shift);
assert!(plain.modifiers.left_ctrl);
assert!(plain.modifiers.right_alt);
}
#[test]
fn shifted_symbols_and_extended_keys_map_to_canonical_keys() {
for (keysym, expected) in [
(b'!' as u32, CanonicalKey::Digit1),
(b'@' as u32, CanonicalKey::Digit2),
(b'#' as u32, CanonicalKey::Digit3),
(b'$' as u32, CanonicalKey::Digit4),
(b'%' as u32, CanonicalKey::Digit5),
(b'^' as u32, CanonicalKey::Digit6),
(b'&' as u32, CanonicalKey::Digit7),
(b'*' as u32, CanonicalKey::Digit8),
(b'(' as u32, CanonicalKey::Digit9),
(b')' as u32, CanonicalKey::Digit0),
(b'_' as u32, CanonicalKey::Minus),
(b'+' as u32, CanonicalKey::Equal),
(b'{' as u32, CanonicalKey::BracketLeft),
(b'}' as u32, CanonicalKey::BracketRight),
(b'|' as u32, CanonicalKey::Backslash),
(b':' as u32, CanonicalKey::Semicolon),
(b'"' as u32, CanonicalKey::Quote),
(b'~' as u32, CanonicalKey::Backquote),
(b'<' as u32, CanonicalKey::Comma),
(b'>' as u32, CanonicalKey::Period),
(b'?' as u32, CanonicalKey::Slash),
] {
assert_eq!(keysym_to_key(keysym), Some((expected, true)));
}
assert_eq!(keysym_to_key(0xffe2).unwrap().0, CanonicalKey::ShiftRight);
assert_eq!(keysym_to_key(0xff61).unwrap().0, CanonicalKey::PrintScreen);
assert_eq!(keysym_to_key(0xff67).unwrap().0, CanonicalKey::ContextMenu);
assert_eq!(keysym_to_key(0xffd5).unwrap().0, CanonicalKey::F24);
assert_eq!(keysym_to_key(0xffb0).unwrap().0, CanonicalKey::Numpad0);
assert_eq!(keysym_to_key(0xffb9).unwrap().0, CanonicalKey::Numpad9);
assert_eq!(keysym_to_key(0xffaf).unwrap().0, CanonicalKey::NumpadDivide);
assert_eq!(keysym_to_key(0xffe5).unwrap().0, CanonicalKey::CapsLock);
assert_eq!(keysym_to_key(0xff7f).unwrap().0, CanonicalKey::NumLock);
}
#[test]
fn pointer_coordinates_use_endpoint_mapping_and_clamp() {
let event = |x, y| RfbPointerEvent {
x,
y,
button_mask: 0,
previous_button_mask: 0,
};
let top_left = pointer_event_to_hid(event(0, 0), 1920, 1080);
assert_eq!((top_left[0].x, top_left[0].y), (0, 0));
let bottom_right = pointer_event_to_hid(event(1919, 1079), 1920, 1080);
assert_eq!((bottom_right[0].x, bottom_right[0].y), (32767, 32767));
let clamped = pointer_event_to_hid(event(u16::MAX, u16::MAX), 1280, 720);
assert_eq!((clamped[0].x, clamped[0].y), (32767, 32767));
let resized = pointer_event_to_hid(event(1279, 719), 1280, 720);
assert_eq!((resized[0].x, resized[0].y), (32767, 32767));
}
#[tokio::test]
async fn framebuffer_requests_merge_and_jpeg_replay_obeys_sequence() {
let (mut server, mut client) = tcp_pair().await;
server.encodings = ClientEncodings {
has_tight: true,
has_jpeg_quality: true,
has_resize: true,
..ClientEncodings::default()
};
let frame = RfbFrame::Jpeg {
data: Bytes::from_static(b"jpeg"),
width: 800,
height: 600,
sequence: 7,
};
assert_eq!(
server.send_frame(&frame).await.unwrap(),
FrameSendOutcome::NotSent
);
assert!(
tokio::time::timeout(Duration::from_millis(20), client.read_u8())
.await
.is_err()
);
server.input_buffer.extend_from_slice(&update_request(true));
server
.input_buffer
.extend_from_slice(&update_request(false));
server.decode_buffered_input().unwrap();
server.decode_buffered_input().unwrap();
assert_eq!(server.pending_request, Some(false));
assert_eq!(
server.send_frame(&frame).await.unwrap(),
FrameSendOutcome::FrameSent
);
assert!(!server.has_pending_request());
server.pending_request = Some(true);
assert_eq!(
server.send_frame(&frame).await.unwrap(),
FrameSendOutcome::NotSent
);
assert!(server.has_pending_request());
server.pending_request = Some(false);
assert_eq!(
server.send_frame(&frame).await.unwrap(),
FrameSendOutcome::FrameSent
);
}
#[tokio::test]
async fn desktop_size_consumes_request_and_h264_waits_for_keyframe() {
let config = VncConfig {
encoding: VncEncoding::H264,
..VncConfig::default()
};
let (mut server, _client) = tcp_pair_with_config(config).await;
server.encodings = ClientEncodings {
has_h264: true,
has_resize: true,
..ClientEncodings::default()
};
server.pending_request = Some(true);
let non_key = RfbFrame::H264 {
data: Bytes::from_static(b"p"),
width: 1024,
height: 768,
key: false,
sequence: 10,
};
assert_eq!(
server.send_frame(&non_key).await.unwrap(),
FrameSendOutcome::DesktopSizeSent
);
assert!(!server.has_pending_request());
assert_eq!(server.last_sent_sequence, None);
assert_eq!(server.framebuffer_size(), (1024, 768));
server.pending_request = Some(true);
assert_eq!(
server.send_frame(&non_key).await.unwrap(),
FrameSendOutcome::NotSent
);
assert!(server.has_pending_request());
let key = RfbFrame::H264 {
data: Bytes::from_static(b"i"),
width: 1024,
height: 768,
key: true,
sequence: 11,
};
assert_eq!(
server.send_frame(&key).await.unwrap(),
FrameSendOutcome::FrameSent
);
assert!(!server.has_pending_request());
assert_eq!(server.last_sent_sequence, Some(11));
}
}