diff options
Diffstat (limited to 'src/server/protocol.rs')
| -rw-r--r-- | src/server/protocol.rs | 64 |
1 files changed, 59 insertions, 5 deletions
diff --git a/src/server/protocol.rs b/src/server/protocol.rs index 74e2d7f3..15aa3784 100644 --- a/src/server/protocol.rs +++ b/src/server/protocol.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; // --------------------------------------------------------------------------- /// Current protocol version. Bumped when wire format changes incompatibly. -pub const PROTOCOL_VERSION: u32 = 3; +pub const PROTOCOL_VERSION: u32 = 4; /// Maximum allowed frame payload size (2 MB). Frames larger than this are /// rejected to prevent denial-of-service via oversized length prefixes. @@ -26,6 +26,15 @@ const LENGTH_PREFIX_BYTES: usize = 4; // Client → Server messages // --------------------------------------------------------------------------- +/// Render payload encoding negotiated during client handshake. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum RenderEncoding { + /// Send full semantic FrameData values. This is the local/default mode. + SemanticFrame, + /// Send already-diffed terminal ANSI byte streams. + TerminalAnsi, +} + /// Messages sent from the client to the server over the client protocol socket. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ClientMessage { @@ -37,6 +46,8 @@ pub enum ClientMessage { cols: u16, /// Terminal height in rows. rows: u16, + /// Render encoding requested by the client. + requested_encoding: RenderEncoding, }, /// Raw input bytes read from the client's stdin. @@ -200,6 +211,21 @@ impl FrameData { } } +/// Terminal ANSI bytes encoded by the server for network-efficient clients. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TerminalFrame { + /// Monotonic per-client frame sequence. + pub seq: u64, + /// Frame width in columns. + pub width: u16, + /// Frame height in rows. + pub height: u16, + /// Whether bytes contain a full redraw rather than an incremental diff. + pub full: bool, + /// Terminal escape bytes ready to write directly to stdout. + pub bytes: Vec<u8>, +} + /// Notification kind forwarded from server to client. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum NotifyKind { @@ -216,14 +242,19 @@ pub enum ServerMessage { Welcome { /// Protocol version the server speaks. version: u32, + /// Render encoding selected by the server for this connection. + encoding: RenderEncoding, /// If present, the handshake failed and this describes why. /// The client should exit with a clear error message. error: Option<String>, }, - /// A rendered frame to be displayed by the client. + /// A rendered frame to be displayed by a semantic-frame client. Frame(FrameData), + /// Terminal bytes to write directly for a terminal-ANSI client. + Terminal(TerminalFrame), + /// Server is shutting down. Clients should exit gracefully. ServerShutdown { /// Optional reason for the shutdown. @@ -464,10 +495,9 @@ fn read_exact_or_eof<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<(), Fram /// Result of checking a client's protocol version against the server's. #[derive(Debug, Clone, PartialEq, Eq)] pub enum VersionCheck { - /// Versions are compatible. The server should reply `Welcome { version, error: None }`. + /// Versions are compatible. The server should reply with a successful Welcome. Compatible, - /// Versions are incompatible. The server should reply - /// `Welcome { version, error: Some(reason) }` and close the connection. + /// Versions are incompatible. The server should reply with a Welcome error and close. Incompatible(String), } @@ -516,6 +546,7 @@ mod tests { version: PROTOCOL_VERSION, cols: 80, rows: 24, + requested_encoding: RenderEncoding::SemanticFrame, }; let encoded = bincode::serde::encode_to_vec(&msg, bincode::config::standard()).unwrap(); let (decoded, _): (ClientMessage, _) = @@ -558,6 +589,7 @@ mod tests { fn server_welcome_roundtrip() { let msg = ServerMessage::Welcome { version: PROTOCOL_VERSION, + encoding: RenderEncoding::SemanticFrame, error: None, }; let encoded = bincode::serde::encode_to_vec(&msg, bincode::config::standard()).unwrap(); @@ -570,6 +602,7 @@ mod tests { fn server_welcome_with_error_roundtrip() { let msg = ServerMessage::Welcome { version: PROTOCOL_VERSION, + encoding: RenderEncoding::SemanticFrame, error: Some("incompatible version".to_owned()), }; let encoded = bincode::serde::encode_to_vec(&msg, bincode::config::standard()).unwrap(); @@ -690,6 +723,21 @@ mod tests { } #[test] + fn server_terminal_frame_roundtrip() { + let msg = ServerMessage::Terminal(TerminalFrame { + seq: 7, + width: 120, + height: 40, + full: false, + bytes: b"\x1b[1;1Hhello".to_vec(), + }); + let encoded = bincode::serde::encode_to_vec(&msg, bincode::config::standard()).unwrap(); + let (decoded, _): (ServerMessage, _) = + bincode::serde::decode_from_slice(&encoded, bincode::config::standard()).unwrap(); + assert_eq!(msg, decoded); + } + + #[test] fn server_reload_sound_config_roundtrip() { let msg = ServerMessage::ReloadSoundConfig; let encoded = bincode::serde::encode_to_vec(&msg, bincode::config::standard()).unwrap(); @@ -706,6 +754,7 @@ mod tests { version: PROTOCOL_VERSION, cols: 80, rows: 24, + requested_encoding: RenderEncoding::SemanticFrame, }; let mut buf = Vec::new(); write_message(&mut buf, &msg).unwrap(); @@ -773,6 +822,7 @@ mod tests { version: PROTOCOL_VERSION, cols: (80 + (i % 40) as u16), rows: (24 + (i % 20) as u16), + requested_encoding: RenderEncoding::SemanticFrame, }, 1 => ClientMessage::Input { data: vec![(i % 256) as u8; (i as usize % 50) + 1], @@ -929,10 +979,12 @@ mod tests { let response = match check { VersionCheck::Compatible => ServerMessage::Welcome { version: PROTOCOL_VERSION, + encoding: RenderEncoding::SemanticFrame, error: None, }, VersionCheck::Incompatible(reason) => ServerMessage::Welcome { version: PROTOCOL_VERSION, + encoding: RenderEncoding::SemanticFrame, error: Some(reason), }, }; @@ -1194,6 +1246,7 @@ mod tests { version: PROTOCOL_VERSION, cols: 80, rows: 24, + requested_encoding: RenderEncoding::SemanticFrame, }; let mut buf = Vec::new(); write_message(&mut buf, &msg).unwrap(); @@ -1224,6 +1277,7 @@ mod tests { version: PROTOCOL_VERSION, cols: 200, rows: 60, + requested_encoding: RenderEncoding::SemanticFrame, }, ClientMessage::Input { data: b"hello world".to_vec(), |
