Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ default = ["text", "binary"]
all = ["text", "binary"]
text = ["dep:atoi_radix10"]
binary = []
palette = []

[dev-dependencies]
tempfile = "*"
Expand Down
12 changes: 11 additions & 1 deletion src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ use std::fmt::Display;

use rand::{distr::StandardUniform, prelude::Distribution};

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub enum Color {
RGB24(u8, u8, u8),
RGBA32(u8, u8, u8, u8),
W8(u8),
}

impl Color {
pub fn to_bytes(&self) -> [u8; 4] {
match self {
Color::RGB24(r, g, b) => [*r, *g, *b, 0xff],
Color::RGBA32(r, g, b, a) => [*r, *g, *b, *a],
Color::W8(w) => [*w, *w, *w, 0xff],
}
}
}

impl Display for Color {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand Down
47 changes: 40 additions & 7 deletions src/flutclient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ use std::{

use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter};

#[cfg(feature = "binary")]
use crate::protocols::BinaryParser;
#[cfg(feature = "palette")]
use crate::protocols::PaletteParser;
#[cfg(feature = "text")]
use crate::protocols::TextParser;
use crate::{
get_pixel,
grid::{self, Flut},
increment_counter,
protocols::{BinaryParser, IOProtocol, Parser, Responder, TextParser},
protocols::{IOProtocol, Parser, Responder},
set_pixel_rgba, Canvas, Color, Command, Coordinate, Protocol, ProtocolStatus, Response,
};

Expand Down Expand Up @@ -73,6 +79,7 @@ macro_rules! build_parser_type_enum {
build_parser_type_enum! {
TextParser: TextParser: "text",
BinaryParser: BinaryParser: "binary",
PaletteParser: PaletteParser: "palette",
}

pub struct FlutClient<R, W>
Expand Down Expand Up @@ -148,23 +155,45 @@ where
match_parser!(parser: self.parser => parser.change_canvas(canvas))
}

fn change_protocol(&mut self, protocol: &Protocol) {
#[cfg(feature = "palette")]
fn change_color(&mut self, index: u8, color: Color) -> () {
match &mut self.parser {
ParserTypes::PaletteParser(ref mut parser) => parser.set_color(index, color),
_ => {}
};
}

async fn change_protocol(&mut self, protocol: &Protocol) -> io::Result<()> {
match protocol {
#[cfg(feature = "text")]
Protocol::Text => self.parser = ParserTypes::TextParser(TextParser::default()),
#[cfg(not(feature = "text"))]
Protocol::Text => {
self.writer.write(b"feature \"text\" is not enabled.");
self.writer.flush();
self.writer
.write_all(b"feature \"text\" is not enabled.")
.await?;
self.writer.flush().await?;
}
#[cfg(feature = "binary")]
Protocol::Binary => self.parser = ParserTypes::BinaryParser(BinaryParser::default()),
#[cfg(not(feature = "binary"))]
Protocol::Binary => {
self.writer.write(b"feature \"binary\" is not enabled.");
self.writer.flush();
self.writer
.write_all(b"feature \"binary\" is not enabled.")
.await?;
self.writer.flush().await?;
}
#[cfg(feature = "palette")]
Protocol::Palette => self.parser = ParserTypes::PaletteParser(PaletteParser::default()),
#[cfg(not(feature = "palette"))]
Protocol::Palette => {
self.writer
.write_all(b"feature \"binary\" is not enabled.")
.await?;
self.writer.flush().await?;
}
}
Ok(())
}

pub fn new(reader: R, writer: W, grids: Arc<[grid::Flut<u32>]>) -> Self {
Expand Down Expand Up @@ -193,9 +222,13 @@ where
break 'outer;
}
Ok(Command::ChangeProtocol(protocol)) => {
self.change_protocol(&protocol);
self.change_protocol(&protocol).await?;
break 'outer;
}
#[cfg(feature = "palette")]
Ok(Command::ChangeColor(index, color)) => {
self.change_color(index, color);
}
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
tracing::error!("Process socket got error: {err:?}");
increment_counter(self.counter);
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub enum ProtocolStatus {
pub enum Protocol {
Text,
Binary,
Palette,
}

#[derive(Debug, PartialEq)]
Expand All @@ -75,6 +76,8 @@ pub enum Command {
SetPixel(Canvas, Coordinate, Coordinate, Color),
ChangeCanvas(Canvas),
ChangeProtocol(Protocol),
#[cfg(feature = "palette")]
ChangeColor(u8, Color),
}

#[derive(Debug, PartialEq)]
Expand Down
2 changes: 2 additions & 0 deletions src/protocols.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod binary_protocol;
mod palette_protocol;
mod text_protocol;

use std::io;

pub use binary_protocol::BinaryParser;
pub use palette_protocol::PaletteParser;
pub use text_protocol::TextParser;
use tokio::io::AsyncWriteExt;

Expand Down
181 changes: 181 additions & 0 deletions src/protocols/palette_protocol.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use std::io::{self, Error, ErrorKind};

use image::EncodableLayout;
use rand::random;
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_PALETTE_BIN: u8 = 33;
#[cfg(feature = "palette")]
const SET_PALETTE_COLOR: u8 = 34;

#[derive(Clone)]
pub struct PaletteParser {
colors: [Color; 256],
}

impl PaletteParser {
pub fn set_color(&mut self, index: u8, color: Color) {
self.colors[index as usize] = color;
}
}

impl Default for PaletteParser {
fn default() -> Self {
PaletteParser {
colors: [0; 256].map(|_| random()),
}
}
}

impl<R: AsyncBufRead + AsyncBufReadExt + std::marker::Unpin> Parser<R> for PaletteParser {
async fn parse(&self, reader: &mut R) -> io::Result<Command> {
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_PALETTE_BIN => {
let canvas = reader.read_u8().await?;
let horizontal = reader.read_u16().await?;
let vertical = reader.read_u16().await?;
let color = reader.read_u8().await?;
Ok(Command::SetPixel(canvas, horizontal, vertical, unsafe {
self.colors.get_unchecked(color as usize).clone()
}))
}
#[cfg(feature = "palette")]
SET_PALETTE_COLOR => {
let index = reader.read_u8().await?;
let r = reader.read_u8().await?;
let g = reader.read_u8().await?;
let b = reader.read_u8().await?;
Ok(Command::ChangeColor(index, Color::RGB24(r, g, b)))
}
_ => {
tracing::error!("received illegal command: {command}");
Err(Error::from(ErrorKind::InvalidInput))
}
},
Err(err) => {
tracing::error!("{err}");
Err(err)
}
}
}
}

impl IOProtocol for PaletteParser {
fn change_canvas(&mut self, _canvas: Canvas) -> io::Result<()> {
Err(Error::from(ErrorKind::Unsupported))
}
}

impl<W: AsyncWriteExt + std::marker::Unpin> Responder<W> for PaletteParser {
async fn unparse(&self, response: Response, writer: &mut W) -> io::Result<()> {
match response {
Response::Help => {
writer
.write_all(
self.colors
.iter()
.map(|c| c.to_bytes())
.collect::<Vec<_>>()
.concat()
.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_palette_px_set_parse() {
let parser = PaletteParser::default();
let reader = tokio_test::io::Builder::new()
.read(&[SET_PX_PALETTE_BIN, 0x01, 0x69, 0x42, 0x42, 0x69, 0x82])
.build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await.unwrap();
assert_eq!(
thingy,
Command::SetPixel(1, 0x6942, 0x4269, parser.colors[0x82].clone())
);
}

#[cfg(feature = "palette")]
#[tokio::test]
async fn test_palette_set_palette_color_parse() {
let parser = PaletteParser::default();
let reader = tokio_test::io::Builder::new()
.read(&[SET_PALETTE_COLOR, 0x04, 0xfa, 0xbd, 0x2f])
.build();
let mut bufreader = BufReader::new(reader);
let thingy = parser.parse(&mut bufreader).await.unwrap();
assert_eq!(
thingy,
Command::ChangeColor(0x04, Color::RGB24(0xfa, 0xbd, 0x2f))
);
}

#[tokio::test]
async fn test_palette_set_color() {
let mut parser = PaletteParser::default();
parser.set_color(0x04, Color::RGB24(0xfa, 0xbd, 0x2f));
parser.set_color(0x13, Color::RGB24(0x23, 0x45, 0x67));
parser.set_color(0x69, Color::RGB24(0xb0, 0x07, 0x1e));
assert_eq!(parser.colors[0x04], Color::RGB24(0xfa, 0xbd, 0x2f));
assert_eq!(parser.colors[0x13], Color::RGB24(0x23, 0x45, 0x67));
assert_eq!(parser.colors[0x69], Color::RGB24(0xb0, 0x07, 0x1e));
}
}
1 change: 1 addition & 0 deletions src/protocols/text_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ impl TextParser {
match protocol {
"binary" => Ok(Command::ChangeProtocol(Protocol::Binary)),
"text" => Ok(Command::ChangeProtocol(Protocol::Text)),
"palette" => Ok(Command::ChangeProtocol(Protocol::Palette)),
_ => Err(Error::from(ErrorKind::InvalidInput)),
}
}
Expand Down