use std::io::{self, Error, ErrorKind}; use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}; use crate::{Canvas, Color, Command, Response}; use super::{IOProtocol, Parser, Responder}; const SIZE_BIN: u8 = 115; const PROTOCOLS_BIN: u8 = 116; const HELP_BIN: u8 = 104; const GET_PX_BIN: u8 = 32; const SET_PX_RGB_BIN: u8 = 128; const SET_PX_RGBA_BIN: u8 = 129; const SET_PX_W_BIN: u8 = 130; #[derive(Clone, Default)] pub struct BinaryParser {} impl Parser for BinaryParser { async fn parse(&self, reader: &mut R) -> io::Result { let fst = reader.read_u8().await; match fst { Ok(command) => match command { HELP_BIN => Ok(Command::Help), PROTOCOLS_BIN => Ok(Command::Protocols), SIZE_BIN => { let canvas = reader.read_u8().await?; Ok(Command::Size(canvas)) } GET_PX_BIN => { let canvas = reader.read_u8().await?; let horizontal = reader.read_u16().await?; let vertical = reader.read_u16().await?; Ok(Command::GetPixel(canvas, horizontal, vertical)) } SET_PX_W_BIN => { let canvas = reader.read_u8().await?; let horizontal = reader.read_u16().await?; let vertical = reader.read_u16().await?; let white = reader.read_u8().await?; Ok(Command::SetPixel( canvas, horizontal, vertical, Color::W8(white), )) } SET_PX_RGB_BIN => { let canvas = reader.read_u8().await?; let horizontal = reader.read_u16().await?; let vertical = reader.read_u16().await?; let red = reader.read_u8().await?; let green = reader.read_u8().await?; let blue = reader.read_u8().await?; Ok(Command::SetPixel( canvas, horizontal, vertical, Color::RGB24(red, green, blue), )) } SET_PX_RGBA_BIN => { let canvas = reader.read_u8().await?; let horizontal = reader.read_u16().await?; let vertical = reader.read_u16().await?; let red = reader.read_u8().await?; let green = reader.read_u8().await?; let blue = reader.read_u8().await?; let alpha = reader.read_u8().await?; Ok(Command::SetPixel( canvas, horizontal, vertical, Color::RGBA32(red, green, blue, alpha), )) } _ => { tracing::error!("received illegal command: {command}"); Err(Error::from(ErrorKind::InvalidInput)) } }, Err(err) => { tracing::error!("{err}"); Err(err) } } } } impl IOProtocol for BinaryParser { fn change_canvas(&mut self, _canvas: Canvas) -> io::Result<()> { Err(Error::from(ErrorKind::Unsupported)) } } impl Responder for BinaryParser { async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()> { match response { Response::Help => { let help_text = format!( " You found the binary protocol help text you can get this by sending ({HELP_BIN:02X}) to the server To get the size of a canvas, send ({SIZE_BIN:02X}) (u8 canvas) to the server To set a pixel using RGB, use ({SET_PX_RGB_BIN:02X}) (u8 canvas) (x as u16_le) (y as u16_le) (u8 r) (u8 g) (u8 b) ", ); writer.write_all(help_text.as_bytes()).await } Response::Protocols(protos) => { for protocol in protos { match protocol { crate::ProtocolStatus::Enabled(proto) => { writer .write_all(format!("Enabled: {}\n", proto).as_bytes()) .await?; } crate::ProtocolStatus::Disabled(proto) => { writer .write_all(format!("Disabled: {}\n", proto).as_bytes()) .await?; } } } Ok(()) } Response::Size(x, y) => { writer.write_u16(x).await?; writer.write_u16(y).await } Response::GetPixel(_, _, c) => { writer.write_u8(c[0]).await?; writer.write_u8(c[1]).await?; writer.write_u8(c[2]).await } } } } #[cfg(test)] #[allow(clippy::needless_return)] mod tests { use super::*; use tokio::io::BufReader; #[tokio::test] async fn test_bin_help_parse() { let parser = BinaryParser::default(); let reader = tokio_test::io::Builder::new().read(&[HELP_BIN]).build(); let mut bufreader = BufReader::new(reader); let thingy = parser.parse(&mut bufreader).await; assert_eq!(thingy.unwrap(), Command::Help); } #[tokio::test] async fn test_bin_size_parse() { let parser = BinaryParser::default(); let reader = tokio_test::io::Builder::new().read(&[SIZE_BIN, 3]).build(); let mut bufreader = BufReader::new(reader); let thingy = parser.parse(&mut bufreader).await; assert_eq!(thingy.unwrap(), Command::Size(3)); } #[tokio::test] async fn test_bin_px_set_w_parse() { let parser = BinaryParser::default(); let reader = tokio_test::io::Builder::new() .read(&[SET_PX_W_BIN, 0x01, 0x69, 0x42, 0x42, 0x69, 0x82]) .build(); let mut bufreader = BufReader::new(reader); let thingy = parser.parse(&mut bufreader).await; assert_eq!( thingy.unwrap(), Command::SetPixel(1, 0x6942, 0x4269, Color::W8(0x82)) ); } #[tokio::test] async fn test_bin_px_set_rgb_parse() { let parser = BinaryParser::default(); let reader = tokio_test::io::Builder::new() .read(&[ SET_PX_RGB_BIN, 0x01, 0x42, 0x69, 0x69, 0x42, 0x82, 0x00, 0xff, ]) .build(); let mut bufreader = BufReader::new(reader); let thingy = parser.parse(&mut bufreader).await; assert_eq!( thingy.unwrap(), Command::SetPixel(1, 0x4269, 0x6942, Color::RGB24(0x82, 0x00, 0xff)) ); } #[tokio::test] async fn test_bin_px_set_rgba_parse() { let parser = BinaryParser::default(); let reader = tokio_test::io::Builder::new() .read(&[ SET_PX_RGBA_BIN, 0x01, 0x42, 0x69, 0x69, 0x42, 0x82, 0x00, 0xff, 0xa0, ]) .build(); let mut bufreader = BufReader::new(reader); let thingy = parser.parse(&mut bufreader).await; assert_eq!( thingy.unwrap(), Command::SetPixel(1, 0x4269, 0x6942, Color::RGBA32(0x82, 0x00, 0xff, 0xa0)) ); } #[tokio::test] async fn test_bin_px_get_parse() { let parser = BinaryParser::default(); let reader = tokio_test::io::Builder::new() .read(&[GET_PX_BIN, 0x03, 0x69, 0x42, 0x42, 0x69]) .build(); let mut bufreader = BufReader::new(reader); let thingy = parser.parse(&mut bufreader).await; assert_eq!(thingy.unwrap(), Command::GetPixel(3, 0x6942, 0x4269)); } #[tokio::test] async fn test_bin_parse_multiple() { let parser = BinaryParser::default(); let reader = tokio_test::io::Builder::new() .read(&[ SET_PX_RGB_BIN, 0x01, 0x69, 0x42, 0x42, 0x69, 0x82, 0x00, 0xff, ]) .read(&[ SET_PX_RGBA_BIN, 0x01, 0x69, 0x42, 0x42, 0x70, 0x82, 0x00, 0xff, 0xa0, ]) .build(); let mut bufreader = BufReader::new(reader); let thingy = parser.parse(&mut bufreader).await; let thingy2 = parser.parse(&mut bufreader).await; assert_eq!( thingy.unwrap(), Command::SetPixel(1, 0x6942, 0x4269, Color::RGB24(0x82, 0x00, 0xff)) ); assert_eq!( thingy2.unwrap(), Command::SetPixel(1, 0x6942, 0x4270, Color::RGBA32(0x82, 0x00, 0xff, 0xa0)) ); } }