use crate::error::{Result, VncError};
use std::io::Read;
#[derive(Debug)]
pub enum ClientMessage {
SetPixelFormat {
format: [u8; 16],
},
SetEncodings {
encodings: Vec<i32>,
},
FramebufferUpdateRequest {
incremental: bool,
x: u16,
y: u16,
width: u16,
height: u16,
},
KeyEvent {
down: bool,
key: u32,
},
PointerEvent {
buttons: u8,
x: u16,
y: u16,
},
ClientCutText {
text: String,
},
}
impl ClientMessage {
pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
let mut msg_type = [0u8; 1];
reader.read_exact(&mut msg_type)?;
match msg_type[0] {
0 => {
let mut buf = [0u8; 19]; reader.read_exact(&mut buf)?;
let mut format = [0u8; 16];
format.copy_from_slice(&buf[3..19]);
Ok(ClientMessage::SetPixelFormat { format })
}
2 => {
let mut buf = [0u8; 3];
reader.read_exact(&mut buf)?;
let num = u16::from_be_bytes([buf[1], buf[2]]) as usize;
let mut encodings = Vec::with_capacity(num);
for _ in 0..num {
let mut enc = [0u8; 4];
reader.read_exact(&mut enc)?;
encodings.push(i32::from_be_bytes(enc));
}
Ok(ClientMessage::SetEncodings { encodings })
}
3 => {
let mut buf = [0u8; 9];
reader.read_exact(&mut buf)?;
Ok(ClientMessage::FramebufferUpdateRequest {
incremental: buf[0] != 0,
x: u16::from_be_bytes([buf[1], buf[2]]),
y: u16::from_be_bytes([buf[3], buf[4]]),
width: u16::from_be_bytes([buf[5], buf[6]]),
height: u16::from_be_bytes([buf[7], buf[8]]),
})
}
4 => {
let mut buf = [0u8; 7];
reader.read_exact(&mut buf)?;
Ok(ClientMessage::KeyEvent {
down: buf[0] != 0,
key: u32::from_be_bytes([buf[3], buf[4], buf[5], buf[6]]),
})
}
5 => {
let mut buf = [0u8; 5];
reader.read_exact(&mut buf)?;
Ok(ClientMessage::PointerEvent {
buttons: buf[0],
x: u16::from_be_bytes([buf[1], buf[2]]),
y: u16::from_be_bytes([buf[3], buf[4]]),
})
}
6 => {
let mut buf = [0u8; 7];
reader.read_exact(&mut buf)?;
let len = u32::from_be_bytes([buf[3], buf[4], buf[5], buf[6]]) as usize;
let mut text_buf = vec![0u8; len];
reader.read_exact(&mut text_buf)?;
let text = String::from_utf8_lossy(&text_buf).to_string();
Ok(ClientMessage::ClientCutText { text })
}
other => Err(VncError::Handshake(format!(
"Unknown message type: {}",
other
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_parse_key_event() {
let data: Vec<u8> = vec![
4, 1, 0, 0, 0x00, 0x00, 0xFF, 0x0D, ];
let mut cursor = Cursor::new(&data[1..]);
let mut buf = [0u8; 7];
cursor.read_exact(&mut buf).unwrap();
let down = buf[0] != 0;
let key = u32::from_be_bytes([buf[3], buf[4], buf[5], buf[6]]);
assert!(down);
assert_eq!(key, 0xFF0D);
}
#[test]
fn test_parse_pointer_event() {
let data: Vec<u8> = vec![
5, 1, 0, 100, 0, 200, ];
let mut cursor = Cursor::new(data);
let msg = ClientMessage::read_from(&mut cursor).unwrap();
match msg {
ClientMessage::PointerEvent { buttons, x, y } => {
assert_eq!(buttons, 1);
assert_eq!(x, 100);
assert_eq!(y, 200);
}
_ => panic!("Expected PointerEvent"),
}
}
#[test]
fn test_parse_framebuffer_update_request() {
let data: Vec<u8> = vec![
3, 1, 0, 0, 0, 0, 0x07, 0x80, 0x04, 0x38, ];
let mut cursor = Cursor::new(data);
let msg = ClientMessage::read_from(&mut cursor).unwrap();
match msg {
ClientMessage::FramebufferUpdateRequest {
incremental,
x,
y,
width,
height,
} => {
assert!(incremental);
assert_eq!(x, 0);
assert_eq!(y, 0);
assert_eq!(width, 1920);
assert_eq!(height, 1080);
}
_ => panic!("Expected FramebufferUpdateRequest"),
}
}
#[test]
fn test_parse_set_encodings() {
let data: Vec<u8> = vec![
2, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 0, ];
let mut cursor = Cursor::new(data);
let msg = ClientMessage::read_from(&mut cursor).unwrap();
match msg {
ClientMessage::SetEncodings { encodings } => {
assert_eq!(encodings, vec![5, 6, 0]);
}
_ => panic!("Expected SetEncodings"),
}
}
#[test]
fn test_parse_client_cut_text() {
let text = b"Hello VNC!";
let mut data: Vec<u8> = vec![
6, 0, 0, 0, ];
data.extend_from_slice(&(text.len() as u32).to_be_bytes());
data.extend_from_slice(text);
let mut cursor = Cursor::new(data);
let msg = ClientMessage::read_from(&mut cursor).unwrap();
match msg {
ClientMessage::ClientCutText { text: t } => {
assert_eq!(t, "Hello VNC!");
}
_ => panic!("Expected ClientCutText"),
}
}
}