1use serde::{Deserialize, Serialize};
44
45#[cfg(feature = "client")]
46pub mod client;
47
48#[cfg(feature = "client")]
49pub use client::Mount;
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54pub struct Cell {
55 pub symbol: String,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub fg: Option<RgbOrIndex>,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub bg: Option<RgbOrIndex>,
63 #[serde(default, skip_serializing_if = "is_zero_u16")]
65 pub modifiers: u16,
66}
67
68fn is_zero_u16(v: &u16) -> bool {
69 *v == 0
70}
71
72#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
74#[serde(untagged)]
75pub enum RgbOrIndex {
76 Rgb([u8; 3]),
78 Index(u8),
81}
82
83pub mod modifier {
86 pub const BOLD: u16 = 1 << 0;
87 pub const DIM: u16 = 1 << 1;
88 pub const ITALIC: u16 = 1 << 2;
89 pub const UNDERLINED: u16 = 1 << 3;
90 pub const SLOW_BLINK: u16 = 1 << 4;
91 pub const RAPID_BLINK: u16 = 1 << 5;
92 pub const REVERSED: u16 = 1 << 6;
93 pub const HIDDEN: u16 = 1 << 7;
94 pub const CROSSED_OUT: u16 = 1 << 8;
95}
96
97#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
99pub struct Geometry {
100 pub cols: u16,
101 pub rows: u16,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
107#[serde(tag = "kind", rename_all = "snake_case")]
108pub enum InputEvent {
109 Key { spec: String },
111 Click { col: u16, row: u16, button: String },
113 Scroll { col: u16, row: u16, dy: i16 },
115 Hover { col: u16, row: u16 },
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121#[serde(tag = "kind", rename_all = "snake_case")]
122pub enum HostMessage {
123 Hello { geometry: Geometry, theme: String },
126 Resize { geometry: Geometry },
128 Input { event: InputEvent },
130 Goodbye,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136#[serde(tag = "kind", rename_all = "snake_case")]
137pub enum SiblingMessage {
138 Frame { cells: Vec<Vec<Cell>> },
143 Bye,
145}
146
147pub fn read_message<R, T>(r: &mut R) -> std::io::Result<Option<T>>
153where
154 R: std::io::Read,
155 T: serde::de::DeserializeOwned,
156{
157 let mut len_buf = [0u8; 4];
158 match r.read_exact(&mut len_buf) {
159 Ok(()) => {}
160 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
161 Err(e) => return Err(e),
162 }
163 let len = u32::from_le_bytes(len_buf) as usize;
164 if len > 16 * 1024 * 1024 {
165 return Err(std::io::Error::new(
166 std::io::ErrorKind::InvalidData,
167 format!("bridge message too large: {len} bytes"),
168 ));
169 }
170 let mut body = vec![0u8; len];
171 r.read_exact(&mut body)?;
172 let parsed: T = serde_json::from_slice(&body).map_err(|e| {
173 std::io::Error::new(
174 std::io::ErrorKind::InvalidData,
175 format!("bridge JSON parse: {e}"),
176 )
177 })?;
178 Ok(Some(parsed))
179}
180
181pub fn write_message<W, T>(w: &mut W, msg: &T) -> std::io::Result<()>
183where
184 W: std::io::Write,
185 T: Serialize,
186{
187 let body = serde_json::to_vec(msg).map_err(|e| {
188 std::io::Error::new(
189 std::io::ErrorKind::InvalidData,
190 format!("bridge JSON serialize: {e}"),
191 )
192 })?;
193 let len = body.len() as u32;
194 w.write_all(&len.to_le_bytes())?;
195 w.write_all(&body)?;
196 Ok(())
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn frame_roundtrip() {
205 let frame = SiblingMessage::Frame {
206 cells: vec![vec![Cell {
207 symbol: "x".to_string(),
208 fg: Some(RgbOrIndex::Rgb([255, 0, 0])),
209 bg: None,
210 modifiers: modifier::BOLD,
211 }]],
212 };
213 let mut buf = Vec::new();
214 write_message(&mut buf, &frame).unwrap();
215 let mut cursor = std::io::Cursor::new(&buf);
216 let back: SiblingMessage = read_message(&mut cursor).unwrap().unwrap();
217 match back {
218 SiblingMessage::Frame { cells } => {
219 assert_eq!(cells.len(), 1);
220 assert_eq!(cells[0][0].symbol, "x");
221 assert_eq!(cells[0][0].fg, Some(RgbOrIndex::Rgb([255, 0, 0])));
222 assert_eq!(cells[0][0].modifiers, modifier::BOLD);
223 }
224 _ => panic!("wrong variant"),
225 }
226 }
227
228 #[test]
229 fn host_hello_roundtrip() {
230 let hello = HostMessage::Hello {
231 geometry: Geometry { cols: 80, rows: 24 },
232 theme: "cyberdream".to_string(),
233 };
234 let mut buf = Vec::new();
235 write_message(&mut buf, &hello).unwrap();
236 let mut cursor = std::io::Cursor::new(&buf);
237 let back: HostMessage = read_message(&mut cursor).unwrap().unwrap();
238 match back {
239 HostMessage::Hello { geometry, theme } => {
240 assert_eq!(geometry.cols, 80);
241 assert_eq!(geometry.rows, 24);
242 assert_eq!(theme, "cyberdream");
243 }
244 _ => panic!("wrong variant"),
245 }
246 }
247
248 #[test]
249 fn eof_returns_none() {
250 let mut empty = std::io::Cursor::new(Vec::<u8>::new());
251 let res: Option<HostMessage> = read_message(&mut empty).unwrap();
252 assert!(res.is_none());
253 }
254
255 #[test]
256 fn rejects_oversize_length() {
257 let mut buf = (100u32 * 1024 * 1024).to_le_bytes().to_vec();
259 buf.extend_from_slice(b"junk");
260 let mut cursor = std::io::Cursor::new(&buf);
261 let res: std::io::Result<Option<HostMessage>> = read_message(&mut cursor);
262 assert!(res.is_err());
263 }
264}