add LoadableFromPacket trait

two tests are failing
This commit is contained in:
Vinzenz Schroeter 2025-07-19 15:08:19 +02:00
parent e2d6529e06
commit 9ac5ff7186
21 changed files with 414 additions and 410 deletions

View file

@ -2,8 +2,8 @@ use crate::{
command_code::{CommandCode, InvalidCommandCodeError},
commands::errors::{TryFromPacketError, TryIntoPacketError},
compression::{compress, decompress},
Bitmap, CompressionCode, DataRef, Grid, Header, Origin, Packet, Pixels,
TypedCommand, TILE_SIZE,
Bitmap, CompressionCode, DataRef, Grid, Header, LoadableFromPacket, Origin,
Packet, Pixels, TypedCommand, TILE_SIZE,
};
/// Overwrites a rectangular region of pixels.
@ -49,10 +49,7 @@ impl TryFrom<&BitmapCommand> for Packet {
let command =
BitmapCommand::command_code_for_compression(value.compression);
let data_ref = value.bitmap.data_ref();
let payload = match compress(value.compression, data_ref) {
Ok(payload) => payload,
Err(_) => return Err(TryIntoPacketError::CompressionFailed),
};
let payload = compress(value.compression, data_ref)?;
Ok(Packet {
header: Header {
@ -75,50 +72,52 @@ impl TryFrom<BitmapCommand> for Packet {
}
}
impl TryFrom<Packet> for BitmapCommand {
type Error = TryFromPacketError;
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
let code = CommandCode::try_from(packet.header.command_code)?;
impl LoadableFromPacket for BitmapCommand {
fn load(
header: &Header,
payload: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
let code = CommandCode::try_from(header.command_code)?;
let compression = BitmapCommand::compression_for_command_code(code)
.ok_or(InvalidCommandCodeError(packet.header.command_code))?;
.ok_or(InvalidCommandCodeError(header.command_code))?;
let Packet {
header:
Header {
command_code: _,
a: tiles_x,
b: pixels_y,
c: tile_w,
d: pixel_h,
},
payload,
} = packet;
let Header {
command_code: _,
a: tiles_x,
b: pixels_y,
c: tile_w,
d: pixel_h,
} = *header;
let tile_w = tile_w as usize;
let pixel_h = pixel_h as usize;
let tiles_x = tiles_x as usize;
let pixels_y = pixels_y as usize;
let expected = tile_w as usize * pixel_h as usize;
let payload =
payload.ok_or(TryFromPacketError::UnexpectedPayloadSize {
actual: 0,
expected,
})?;
let expected_bytes = tile_w * pixel_h;
let (payload, read_payload_bytes) =
match decompress(compression, &payload, expected) {
Ok(payload) => payload,
Err(_) => return Err(TryFromPacketError::DecompressionFailed),
};
decompress(compression, &payload, expected_bytes)
.map_err(|_| TryFromPacketError::DecompressionFailed)?;
if expected_bytes < payload.len() {
return Err(TryFromPacketError::UnexpectedPayloadSize {
expected: expected_bytes,
actual: payload.len(),
});
}
let bitmap = Bitmap::load(
tile_w as usize * TILE_SIZE,
pixel_h as usize,
&payload,
tile_w * TILE_SIZE,
pixel_h,
&payload[..expected_bytes],
)?;
let origin =
Origin::new(tiles_x as usize * TILE_SIZE, pixels_y as usize);
let origin = Origin::new(tiles_x * TILE_SIZE, pixels_y);
Ok(Self {
bitmap,
origin,
compression,
})
Ok((
Self {
bitmap,
origin,
compression,
},
read_payload_bytes,
))
}
}
@ -203,11 +202,11 @@ mod tests {
#[test]
fn error_decompression_failed_win() {
for compression in CompressionCode::ALL {
for compression in CompressionCode::ALL.to_owned() {
let p: Packet = BitmapCommand {
origin: Origin::new(16, 8),
bitmap: Bitmap::new(8, 8).unwrap(),
compression: *compression,
compression: compression,
}
.try_into()
.unwrap();
@ -225,7 +224,7 @@ mod tests {
payload: Some(payload),
};
let result = TypedCommand::try_from(p);
if *compression != CompressionCode::Uncompressed {
if compression != CompressionCode::Uncompressed {
assert_eq!(
result,
Err(TryFromPacketError::DecompressionFailed)

View file

@ -1,7 +1,8 @@
#![allow(deprecated, reason = "this implements the deprecated functionality")]
use crate::{
command_code::CommandCode, commands::check_command_code_only,
commands::errors::TryFromPacketError, Packet, TypedCommand,
commands::errors::TryFromPacketError, Header, LoadableFromPacket, Packet,
TypedCommand,
};
use std::fmt::Debug;
@ -21,17 +22,13 @@ use std::fmt::Debug;
#[deprecated]
pub struct BitmapLegacyCommand;
impl TryFrom<Packet> for BitmapLegacyCommand {
type Error = TryFromPacketError;
fn try_from(value: Packet) -> Result<Self, Self::Error> {
if let Some(e) =
check_command_code_only(value, CommandCode::BitmapLegacy)
{
Err(e)
} else {
Ok(Self)
}
impl LoadableFromPacket for BitmapLegacyCommand {
fn load(
header: &Header,
_: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
check_command_code_only(header, CommandCode::BitmapLegacy)?;
Ok((Self, 0))
}
}

View file

@ -2,8 +2,8 @@ use crate::{
command_code::{CommandCode, InvalidCommandCodeError},
commands::errors::TryFromPacketError,
compression::{compress, decompress},
CompressionCode, DisplayBitVec, Header, Offset, Packet, TryIntoPacketError,
TypedCommand,
CompressionCode, DisplayBitVec, Header, LoadableFromPacket, Offset, Packet,
TryIntoPacketError, TypedCommand,
};
/// Binary operations for use with the [`BitVecCommand`] command.
@ -66,10 +66,7 @@ impl TryFrom<&BitVecCommand> for Packet {
let data_ref = value.bitvec.as_raw_slice();
let length = data_ref.len().try_into()?;
let payload = match compress(value.compression, data_ref) {
Ok(payload) => payload,
Err(_) => return Err(TryIntoPacketError::CompressionFailed),
};
let payload = compress(value.compression, data_ref)?;
Ok(Packet {
header: Header {
command_code: command_code.into(),
@ -83,22 +80,20 @@ impl TryFrom<&BitVecCommand> for Packet {
}
}
impl TryFrom<Packet> for BitVecCommand {
type Error = TryFromPacketError;
impl LoadableFromPacket for BitVecCommand {
fn load(
header: &Header,
payload: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
let Header {
command_code,
a: offset,
b: expected_len,
c: sub,
d: reserved,
..
} = *header;
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
let Packet {
header:
Header {
command_code,
a: offset,
b: expected_len,
c: sub,
d: reserved,
..
},
payload,
} = packet;
let expected_len = expected_len as usize;
let command_code = CommandCode::try_from(command_code)?;
let operation = match command_code {
@ -114,29 +109,26 @@ impl TryFrom<Packet> for BitVecCommand {
if reserved != 0 {
return Err(TryFromPacketError::ExtraneousHeaderValues);
}
let compression = CompressionCode::try_from(sub)?;
let payload =
payload.ok_or(TryFromPacketError::UnexpectedPayloadSize {
expected: expected_len,
actual: 0,
})?;
let (payload, read_payload_bytes) =
match decompress(compression, &payload, expected_len) {
Ok(payload) => payload,
Err(_) => return Err(TryFromPacketError::DecompressionFailed),
};
if payload.len() != expected_len {
decompress(compression, &payload, expected_len)
.map_err(|_| TryFromPacketError::DecompressionFailed)?;
if payload.len() < expected_len {
return Err(TryFromPacketError::UnexpectedPayloadSize {
expected: expected_len,
actual: payload.len(),
});
}
Ok(Self {
offset: offset as Offset,
bitvec: DisplayBitVec::from_vec(payload),
compression,
operation,
})
Ok((
Self {
offset: offset as Offset,
bitvec: DisplayBitVec::from_slice(&payload[..expected_len]),
compression,
operation,
},
read_payload_bytes,
))
}
}

View file

@ -1,7 +1,8 @@
use crate::{
command_code::CommandCode, commands::check_command_code,
commands::errors::TryFromPacketError, BrightnessGrid, ByteGrid, Header,
Origin, Packet, Tiles, TryIntoPacketError, TypedCommand,
LoadableFromPacket, Origin, Packet, Tiles, TryIntoPacketError,
TypedCommand,
};
/// Set the brightness of individual tiles in a rectangular area of the display.
@ -46,55 +47,46 @@ impl From<BrightnessGrid> for BrightnessGridCommand {
}
}
impl TryFrom<Packet> for BrightnessGridCommand {
type Error = TryFromPacketError;
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
let Packet {
header:
Header {
command_code,
a: x,
b: y,
c: width,
d: height,
},
payload,
} = packet;
impl LoadableFromPacket for BrightnessGridCommand {
fn load(
header: &Header,
payload: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
let Header {
command_code,
a: x,
b: y,
c: width,
d: height,
} = *header;
check_command_code(command_code, CommandCode::CharBrightness)?;
let expected_size = width as usize * height as usize;
let payload = match payload {
None => {
return Err(TryFromPacketError::UnexpectedPayloadSize {
actual: 0,
expected: expected_size,
})
}
Some(payload) if payload.len() != expected_size => {
return Err(TryFromPacketError::UnexpectedPayloadSize {
actual: payload.len(),
expected: expected_size,
})
}
Some(payload) => payload,
if payload.len() < expected_size {
return Err(TryFromPacketError::UnexpectedPayloadSize {
actual: payload.len(),
expected: expected_size,
});
};
let grid = ByteGrid::from_raw_parts_unchecked(
width as usize,
height as usize,
payload,
payload[..expected_size].to_vec(),
);
let grid = match BrightnessGrid::try_from(grid) {
Ok(grid) => grid,
Err(val) => return Err(TryFromPacketError::InvalidBrightness(val)),
};
Ok(Self {
grid,
origin: Origin::new(x as usize, y as usize),
})
Ok((
Self {
grid,
origin: Origin::new(x as usize, y as usize),
},
expected_size,
))
}
}

View file

@ -1,7 +1,7 @@
use crate::{
command_code::CommandCode, commands::check_command_code,
commands::errors::TryFromPacketError, CharGrid, Header, Origin, Packet,
Tiles, TryIntoPacketError, TypedCommand,
commands::errors::TryFromPacketError, CharGrid, Header, LoadableFromPacket,
Origin, Packet, Tiles, TryIntoPacketError, TypedCommand,
};
/// Show text on the screen.
@ -48,51 +48,44 @@ impl TryFrom<&CharGridCommand> for Packet {
}
}
impl TryFrom<Packet> for CharGridCommand {
type Error = TryFromPacketError;
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
let Packet {
header:
Header {
command_code,
a: origin_x,
b: origin_y,
c: width,
d: height,
},
payload,
} = packet;
impl LoadableFromPacket for CharGridCommand {
fn load(
header: &Header,
payload: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
let Header {
command_code,
a: origin_x,
b: origin_y,
c: width,
d: height,
} = *header;
check_command_code(command_code, CommandCode::Utf8Data)?;
let expected = width as usize * height as usize;
let payload = match payload {
None => {
return Err(TryFromPacketError::UnexpectedPayloadSize {
expected,
actual: 0,
})
}
Some(payload) if payload.len() != expected => {
return Err(TryFromPacketError::UnexpectedPayloadSize {
expected,
actual: payload.len(),
})
}
Some(payload) => {
String::from_utf8(payload.clone())?.chars().collect()
}
};
if payload.len() < expected {
return Err(TryFromPacketError::UnexpectedPayloadSize {
expected,
actual: payload.len(),
});
}
Ok(Self {
origin: Origin::new(origin_x as usize, origin_y as usize),
grid: CharGrid::from_raw_parts_unchecked(
width as usize,
height as usize,
payload,
),
})
// TODO this does not work as char != byte
let payload = String::from_utf8(payload[..expected].to_vec())?
.chars()
.collect();
Ok((
Self {
origin: Origin::new(origin_x as usize, origin_y as usize),
grid: CharGrid::from_raw_parts_unchecked(
width as usize,
height as usize,
payload,
),
},
expected,
))
}
}

View file

@ -1,7 +1,7 @@
use crate::{
command_code::CommandCode,
commands::{check_command_code_only, errors::TryFromPacketError},
Packet, TypedCommand,
Header, LoadableFromPacket, Packet, TypedCommand,
};
use std::fmt::Debug;
@ -17,15 +17,13 @@ use std::fmt::Debug;
/// ```
pub struct ClearCommand;
impl TryFrom<Packet> for ClearCommand {
type Error = TryFromPacketError;
fn try_from(value: Packet) -> Result<Self, Self::Error> {
if let Some(e) = check_command_code_only(value, CommandCode::Clear) {
Err(e)
} else {
Ok(Self)
}
impl LoadableFromPacket for ClearCommand {
fn load(
header: &Header,
_: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
check_command_code_only(header, CommandCode::Clear)?;
Ok((Self, 0))
}
}
@ -50,9 +48,10 @@ impl From<ClearCommand> for TypedCommand {
#[cfg(test)]
mod tests {
use super::*;
use crate::command_code::InvalidCommandCodeError;
use crate::commands::tests::TestImplementsCommand;
use crate::Header;
use crate::{
command_code::InvalidCommandCodeError,
commands::tests::TestImplementsCommand, Header,
};
impl TestImplementsCommand for ClearCommand {}

View file

@ -1,7 +1,8 @@
use crate::{
command_code::CommandCode, commands::check_command_code,
commands::errors::TryFromPacketError, Cp437Grid, Header, Origin, Packet,
Tiles, TryIntoPacketError, TypedCommand,
commands::errors::TryFromPacketError, Cp437Grid, Header,
LoadableFromPacket, Origin, Packet, Tiles, TryIntoPacketError,
TypedCommand,
};
/// Show text on the screen.
@ -61,49 +62,39 @@ impl TryFrom<&Cp437GridCommand> for Packet {
}
}
impl TryFrom<Packet> for Cp437GridCommand {
type Error = TryFromPacketError;
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
let Packet {
header:
Header {
command_code,
a: origin_x,
b: origin_y,
c: width,
d: height,
},
payload,
} = packet;
impl LoadableFromPacket for Cp437GridCommand {
fn load(
header: &Header,
payload: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
let Header {
command_code,
a: origin_x,
b: origin_y,
c: width,
d: height,
} = *header;
check_command_code(command_code, CommandCode::Cp437Data)?;
let expected = width as usize * height as usize;
let payload = match payload {
None => {
return Err(TryFromPacketError::UnexpectedPayloadSize {
expected,
actual: 0,
})
}
Some(payload) if payload.len() != expected => {
return Err(TryFromPacketError::UnexpectedPayloadSize {
expected,
actual: payload.len(),
})
}
Some(payload) => payload,
};
if payload.len() < expected {
return Err(TryFromPacketError::UnexpectedPayloadSize {
expected,
actual: payload.len(),
});
}
Ok(Self {
origin: Origin::new(origin_x as usize, origin_y as usize),
grid: Cp437Grid::from_raw_parts_unchecked(
width as usize,
height as usize,
payload,
),
})
Ok((
Self {
origin: Origin::new(origin_x as usize, origin_y as usize),
grid: Cp437Grid::from_raw_parts_unchecked(
width as usize,
height as usize,
Vec::from(&payload[..expected]),
),
},
expected,
))
}
}

View file

@ -1,3 +1,4 @@
use crate::compression::CompressionError;
use crate::{
command_code::InvalidCommandCodeError, InvalidCompressionCodeError,
LoadBitmapError,
@ -40,6 +41,8 @@ pub enum TryFromPacketError {
/// The bitmap contained in the payload could not be loaded
#[error(transparent)]
LoadBitmapFailed(#[from] LoadBitmapError),
#[error("The provided packet was too small to be loadable.")]
TooSmall,
}
/// An error that can occur when parsing a raw packet as a command
@ -47,7 +50,7 @@ pub enum TryFromPacketError {
pub enum TryIntoPacketError {
/// Compression of the payload failed.
#[error("The compression of the payload failed")]
CompressionFailed,
CompressionFailed(#[from] CompressionError),
/// Conversion (probably to u16) failed
#[error(transparent)]
ConversionError(#[from] TryFromIntError),

View file

@ -1,6 +1,7 @@
use crate::{
command_code::CommandCode, commands::check_command_code_only,
commands::errors::TryFromPacketError, Packet, TypedCommand,
commands::errors::TryFromPacketError, Header, LoadableFromPacket, Packet,
TypedCommand,
};
use std::fmt::Debug;
@ -18,15 +19,13 @@ use std::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FadeOutCommand;
impl TryFrom<Packet> for FadeOutCommand {
type Error = TryFromPacketError;
fn try_from(value: Packet) -> Result<Self, Self::Error> {
if let Some(e) = check_command_code_only(value, CommandCode::FadeOut) {
Err(e)
} else {
Ok(Self)
}
impl LoadableFromPacket for FadeOutCommand {
fn load(
header: &Header,
_: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
check_command_code_only(header, CommandCode::FadeOut)?;
Ok((Self, 0))
}
}
@ -100,12 +99,12 @@ mod tests {
payload: Some(vec![5, 7]),
};
let result = TypedCommand::try_from(p);
assert!(matches!(
assert_eq!(
result,
Err(TryFromPacketError::UnexpectedPayloadSize {
expected: 0,
actual: 2
})
));
);
}
}

View file

@ -1,7 +1,7 @@
use crate::{
command_code::CommandCode, commands::check_command_code,
commands::errors::TryFromPacketError, Brightness, Header, Packet,
TypedCommand,
commands::errors::TryFromPacketError, Brightness, Header,
LoadableFromPacket, Packet, TypedCommand,
};
/// Set the brightness of all tiles to the same value.
@ -41,21 +41,18 @@ impl From<&GlobalBrightnessCommand> for Packet {
}
}
impl TryFrom<Packet> for GlobalBrightnessCommand {
type Error = TryFromPacketError;
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
let Packet {
header:
Header {
command_code,
a,
b,
c,
d,
},
payload,
} = packet;
impl LoadableFromPacket for GlobalBrightnessCommand {
fn load(
header: &Header,
payload: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
let Header {
command_code,
a,
b,
c,
d,
} = *header;
check_command_code(command_code, CommandCode::Brightness)?;
@ -63,26 +60,16 @@ impl TryFrom<Packet> for GlobalBrightnessCommand {
return Err(TryFromPacketError::ExtraneousHeaderValues);
}
let brightness = match payload {
None => {
return Err(TryFromPacketError::UnexpectedPayloadSize {
expected: 1,
actual: 0,
})
}
Some(payload) if payload.len() == 1 => payload[0],
Some(payload) => {
return Err(TryFromPacketError::UnexpectedPayloadSize {
expected: 1,
actual: payload.len(),
});
}
};
let brightness = *payload.get(0).ok_or(
TryFromPacketError::UnexpectedPayloadSize {
expected: 1,
actual: 0,
},
)?;
match Brightness::try_from(brightness) {
Ok(brightness) => Ok(Self { brightness }),
Err(_) => Err(TryFromPacketError::InvalidBrightness(brightness)),
}
let brightness = Brightness::try_from(brightness)
.map_err(|_| TryFromPacketError::InvalidBrightness(brightness))?;
Ok((Self { brightness }, 1))
}
}
@ -158,24 +145,27 @@ mod tests {
}
#[test]
fn unexpected_payload_size_brightness() {
fn unexpected_payload_size() {
let mut packet = Packet {
header: Header {
command_code: CommandCode::Brightness.into(),
a: 0,
b: 0,
c: 0,
d: 0,
},
payload: None,
};
assert_eq!(
TypedCommand::try_from(Packet {
header: Header {
command_code: CommandCode::Brightness.into(),
a: 0,
b: 0,
c: 0,
d: 0,
},
payload: None
}),
TypedCommand::try_from(packet.clone()),
Err(TryFromPacketError::UnexpectedPayloadSize {
expected: 1,
actual: 0
})
);
packet.payload = Some(vec![0; 2]);
assert_eq!(
TypedCommand::try_from(Packet {
header: Header {

View file

@ -1,6 +1,7 @@
use crate::{
command_code::CommandCode, commands::check_command_code_only,
commands::errors::TryFromPacketError, Packet, TypedCommand,
commands::errors::TryFromPacketError, Header, LoadableFromPacket, Packet,
TypedCommand,
};
use std::fmt::Debug;
@ -18,16 +19,13 @@ use std::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HardResetCommand;
impl TryFrom<Packet> for HardResetCommand {
type Error = TryFromPacketError;
fn try_from(value: Packet) -> Result<Self, Self::Error> {
if let Some(e) = check_command_code_only(value, CommandCode::HardReset)
{
Err(e)
} else {
Ok(Self)
}
impl LoadableFromPacket for HardResetCommand {
fn load(
header: &Header,
_: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
check_command_code_only(header, CommandCode::HardReset)?;
Ok((Self, 0))
}
}
@ -52,8 +50,7 @@ impl From<HardResetCommand> for TypedCommand {
#[cfg(test)]
mod test {
use super::*;
use crate::commands::tests::TestImplementsCommand;
use crate::Header;
use crate::{commands::tests::TestImplementsCommand, Header};
impl TestImplementsCommand for HardResetCommand {}

View file

@ -77,41 +77,103 @@ pub use typed::*;
/// connection.send_command(command).unwrap();
/// ```
pub trait Command:
Debug + Clone + Eq + TryInto<Packet> + TryFrom<Packet> + Hash
Debug
+ Clone
+ Eq
+ TryInto<Packet>
+ LoadableFromPacket
+ TryFrom<Packet>
+ Hash
{
}
impl<T: Debug + Clone + Eq + TryInto<Packet> + TryFrom<Packet> + Hash> Command
for T
impl<
T: Debug
+ Clone
+ Eq
+ TryInto<Packet>
+ LoadableFromPacket
+ TryFrom<Packet>
+ Hash,
> Command for T
{
}
pub trait LoadableFromPacket: Sized {
/// Load the specified header and payload.
///
/// In case of success, returns a tuple of Self and the number of bytes read from payload.
fn load(
header: &Header,
payload: &[u8],
) -> Result<(Self, usize), TryFromPacketError>;
/// Load the specified bytes.
///
/// In case of success, returns a tuple of Self and the number of bytes read from the buffer.
fn from_bytes(buf: &[u8]) -> Result<(Self, usize), TryFromPacketError> {
let (header, bytes_header) =
Header::read_from(buf).ok_or(TryFromPacketError::TooSmall)?;
let (result, bytes_payload) =
Self::load(&header, &buf[bytes_header..])?;
Ok((result, bytes_header + bytes_payload))
}
}
// Because of the orphan rule, the following is not possible:
// impl<T: LoadableCommand> TryFrom<Packet> for T { .. }
//
// This macro is used instead to provide the implementation.
macro_rules! derive_try_from_packet {
($T:ty) => {
#[allow(unused, reason = "Macro is used with a deprecated type")]
impl TryFrom<Packet> for $T {
type Error = TryFromPacketError;
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
let payload = packet.payload.as_deref().unwrap_or(&[]);
let (result, read_bytes) = Self::load(&packet.header, payload)?;
//if payload.len() != read_bytes {
// return Err(TryFromPacketError::UnexpectedPayloadSize {
// actual: payload.len(),
// expected: read_bytes,
// });
//}
Ok(result)
}
}
};
}
derive_try_from_packet!(BitmapCommand);
derive_try_from_packet!(BitmapLegacyCommand);
derive_try_from_packet!(BitVecCommand);
derive_try_from_packet!(BrightnessGridCommand);
derive_try_from_packet!(CharGridCommand);
derive_try_from_packet!(ClearCommand);
derive_try_from_packet!(Cp437GridCommand);
derive_try_from_packet!(FadeOutCommand);
derive_try_from_packet!(GlobalBrightnessCommand);
derive_try_from_packet!(HardResetCommand);
derive_try_from_packet!(TypedCommand);
fn check_command_code_only(
packet: Packet,
header: &Header,
code: CommandCode,
) -> Option<TryFromPacketError> {
let Packet {
header:
Header {
command_code: _,
a,
b,
c,
d,
},
payload,
} = packet;
if packet.header.command_code != u16::from(code) {
Some(InvalidCommandCodeError(packet.header.command_code).into())
} else if let Some(payload) = payload {
Some(TryFromPacketError::UnexpectedPayloadSize {
expected: 0,
actual: payload.len(),
})
} else if a != 0 || b != 0 || c != 0 || d != 0 {
Some(TryFromPacketError::ExtraneousHeaderValues)
} else {
None
) -> Result<(), TryFromPacketError> {
let code = u16::from(code);
match header {
Header { command_code, .. } if *command_code != code => {
Err(InvalidCommandCodeError(*command_code).into())
}
Header {
command_code: _,
a: 0,
b: 0,
c: 0,
d: 0,
} => Ok(()),
_ => Err(TryFromPacketError::ExtraneousHeaderValues),
}
}
@ -138,19 +200,13 @@ mod tests {
pub(crate) fn round_trip(original: TypedCommand) {
let packet: Packet = original.clone().try_into().unwrap();
let copy: TypedCommand = match TypedCommand::try_from(packet) {
Ok(command) => command,
Err(err) => panic!("could not reload {original:?}: {err:?}"),
};
let copy: TypedCommand = TypedCommand::try_from(packet).unwrap();
assert_eq!(copy, original);
}
pub(crate) fn round_trip_ref(original: &TypedCommand) {
let packet: Packet = original.try_into().unwrap();
let copy: TypedCommand = match TypedCommand::try_from(packet) {
Ok(command) => command,
Err(err) => panic!("could not reload {original:?}: {err:?}"),
};
let copy: TypedCommand = TypedCommand::try_from(packet).unwrap();
assert_eq!(&copy, original);
}
}

View file

@ -1,8 +1,9 @@
use crate::{
command_code::CommandCode, commands::errors::TryFromPacketError,
BitVecCommand, BitmapCommand, BrightnessGridCommand, CharGridCommand,
ClearCommand, Cp437GridCommand, FadeOutCommand, GlobalBrightnessCommand,
HardResetCommand, Packet, TryIntoPacketError,
BitVecCommand, BitmapCommand, BitmapLegacyCommand, BrightnessGridCommand,
CharGridCommand, ClearCommand, Cp437GridCommand, FadeOutCommand,
GlobalBrightnessCommand, HardResetCommand, Header, LoadableFromPacket,
Packet, TryIntoPacketError,
};
/// This enum contains all commands provided by the library.
@ -26,63 +27,71 @@ pub enum TypedCommand {
BitmapLegacy(crate::BitmapLegacyCommand),
}
impl TryFrom<Packet> for TypedCommand {
type Error = TryFromPacketError;
impl LoadableFromPacket for TypedCommand {
fn load(
header: &Header,
payload: &[u8],
) -> Result<(Self, usize), TryFromPacketError> {
fn load_as_typed<T: LoadableFromPacket + Into<TypedCommand>>(
header: &Header,
payload: &[u8],
) -> Result<(TypedCommand, usize), TryFromPacketError> {
let (cmd, read) = T::load(header, payload)?;
Ok((cmd.into(), read))
}
/// Try to interpret the [Packet] as one containing a [`TypedCommand`]
fn try_from(packet: Packet) -> Result<Self, Self::Error> {
Ok(match CommandCode::try_from(packet.header.command_code)? {
match CommandCode::try_from(header.command_code)? {
CommandCode::Clear => {
TypedCommand::Clear(crate::ClearCommand::try_from(packet)?)
load_as_typed::<ClearCommand>(header, payload)
}
CommandCode::Brightness => {
load_as_typed::<GlobalBrightnessCommand>(header, payload)
}
CommandCode::HardReset => {
load_as_typed::<HardResetCommand>(header, payload)
}
CommandCode::Brightness => TypedCommand::Brightness(
crate::GlobalBrightnessCommand::try_from(packet)?,
),
CommandCode::HardReset => TypedCommand::HardReset(
crate::HardResetCommand::try_from(packet)?,
),
CommandCode::FadeOut => {
TypedCommand::FadeOut(crate::FadeOutCommand::try_from(packet)?)
load_as_typed::<FadeOutCommand>(header, payload)
}
CommandCode::Cp437Data => {
load_as_typed::<Cp437GridCommand>(header, payload)
}
CommandCode::CharBrightness => {
load_as_typed::<BrightnessGridCommand>(header, payload)
}
CommandCode::Utf8Data => {
load_as_typed::<CharGridCommand>(header, payload)
}
CommandCode::Cp437Data => TypedCommand::Cp437Grid(
crate::Cp437GridCommand::try_from(packet)?,
),
CommandCode::CharBrightness => TypedCommand::BrightnessGrid(
crate::BrightnessGridCommand::try_from(packet)?,
),
CommandCode::Utf8Data => TypedCommand::CharGrid(
crate::CharGridCommand::try_from(packet)?,
),
#[allow(deprecated)]
CommandCode::BitmapLegacy => TypedCommand::BitmapLegacy(
crate::BitmapLegacyCommand::try_from(packet)?,
),
CommandCode::BitmapLegacy => {
load_as_typed::<BitmapLegacyCommand>(header, payload)
}
CommandCode::BitmapLinear
| CommandCode::BitmapLinearOr
| CommandCode::BitmapLinearAnd
| CommandCode::BitmapLinearXor => {
TypedCommand::BitVec(crate::BitVecCommand::try_from(packet)?)
load_as_typed::<BitVecCommand>(header, payload)
}
CommandCode::BitmapLinearWinUncompressed => {
TypedCommand::Bitmap(crate::BitmapCommand::try_from(packet)?)
load_as_typed::<BitmapCommand>(header, payload)
}
#[cfg(feature = "compression_zlib")]
CommandCode::BitmapLinearWinZlib => {
TypedCommand::Bitmap(crate::BitmapCommand::try_from(packet)?)
load_as_typed::<BitmapCommand>(header, payload)
}
#[cfg(feature = "compression_bzip2")]
CommandCode::BitmapLinearWinBzip2 => {
TypedCommand::Bitmap(crate::BitmapCommand::try_from(packet)?)
load_as_typed::<BitmapCommand>(header, payload)
}
#[cfg(feature = "compression_lzma")]
CommandCode::BitmapLinearWinLzma => {
TypedCommand::Bitmap(crate::BitmapCommand::try_from(packet)?)
load_as_typed::<BitmapCommand>(header, payload)
}
#[cfg(feature = "compression_zstd")]
CommandCode::BitmapLinearWinZstd => {
TypedCommand::Bitmap(crate::BitmapCommand::try_from(packet)?)
load_as_typed::<BitmapCommand>(header, payload)
}
})
}
}
}
@ -90,19 +99,7 @@ impl TryFrom<TypedCommand> for Packet {
type Error = TryIntoPacketError;
fn try_from(value: TypedCommand) -> Result<Self, Self::Error> {
Ok(match value {
TypedCommand::Clear(c) => c.into(),
TypedCommand::CharGrid(c) => c.try_into()?,
TypedCommand::Cp437Grid(c) => c.try_into()?,
TypedCommand::Bitmap(c) => c.try_into()?,
TypedCommand::Brightness(c) => c.into(),
TypedCommand::BrightnessGrid(c) => c.try_into()?,
TypedCommand::BitVec(c) => c.try_into()?,
TypedCommand::HardReset(c) => c.into(),
TypedCommand::FadeOut(c) => c.into(),
#[allow(deprecated)]
TypedCommand::BitmapLegacy(c) => c.into(),
})
Packet::try_from(&value)
}
}

View file

@ -9,7 +9,7 @@ use bzip2::{
use log::error;
use std::io::Read;
pub struct Bzip2;
pub(crate) struct Bzip2;
impl CompressionAlgo for Bzip2 {
const CODE: CompressionCode = CompressionCode::Bzip2;

View file

@ -10,11 +10,19 @@ pub struct Lzma;
impl CompressionAlgo for Lzma {
const CODE: CompressionCode = CompressionCode::Lzma;
fn compress(payload: &[u8]) -> Result<Payload, CompressionError> {
lzma::compress(payload, 6).map_err(|e| {
error!("failed to compress data: {e}");
CompressionError::CompressionFailed
})
}
fn decompress(
payload: &[u8],
_: usize,
expected_size_hint: usize,
) -> Result<(Vec<u8>, usize), CompressionError> {
let mut output: Vec<u8> = Vec::new();
let mut output: Vec<u8> = Vec::with_capacity(expected_size_hint);
let mut reader = LzmaReader::new_decompressor(payload)
.map_err(|_| CompressionError::LibraryError)?;
let read = reader
@ -22,10 +30,4 @@ impl CompressionAlgo for Lzma {
.map_err(|_| CompressionError::CompressionFailed)?;
Ok((output, read))
}
fn compress(payload: &[u8]) -> Result<Payload, CompressionError> {
lzma::compress(payload, 6).map_err(|e| {
error!("failed to compress data: {e}");
CompressionError::CompressionFailed
})
}
}

View file

@ -3,7 +3,7 @@ use crate::{
CompressionCode,
};
pub struct Uncompressed;
pub(crate) struct Uncompressed;
impl CompressionAlgo for Uncompressed {
const CODE: CompressionCode = CompressionCode::Uncompressed;

View file

@ -5,7 +5,7 @@ use crate::{
use flate2::{FlushCompress, FlushDecompress, Status};
use log::error;
pub struct Zlib;
pub(crate) struct Zlib;
impl CompressionAlgo for Zlib {
const CODE: CompressionCode = CompressionCode::Zlib;

View file

@ -6,10 +6,11 @@ use log::error;
use std::io::{Read, Write};
use zstd::{Decoder, Encoder, DEFAULT_COMPRESSION_LEVEL};
pub struct Zstd;
pub(crate) struct Zstd;
impl CompressionAlgo for Zstd {
const CODE: CompressionCode = CompressionCode::Zstd;
fn compress(payload: &[u8]) -> Result<Payload, CompressionError> {
let buf = Vec::with_capacity(payload.len());
let mut encoder = Encoder::new(buf, DEFAULT_COMPRESSION_LEVEL)
@ -28,6 +29,7 @@ impl CompressionAlgo for Zstd {
CompressionError::CompressionFailed
})
}
fn decompress(
payload: &[u8],
expected_size_hint: usize,

View file

@ -38,6 +38,7 @@ pub trait Grid<T> {
/// Checks whether the specified signed position is in grid bounds
#[must_use]
#[inline]
fn is_in_bounds(&self, x: usize, y: usize) -> bool {
x < self.width() && y < self.height()
}

View file

@ -45,4 +45,12 @@ impl Header {
Some((header, read_size))
}
pub(crate) fn write_to(&self, slice: &mut [u8]) {
slice[0..=1].copy_from_slice(&u16::to_be_bytes(self.command_code));
slice[2..=3].copy_from_slice(&u16::to_be_bytes(self.a));
slice[4..=5].copy_from_slice(&u16::to_be_bytes(self.b));
slice[6..=7].copy_from_slice(&u16::to_be_bytes(self.c));
slice[8..=9].copy_from_slice(&u16::to_be_bytes(self.d));
}
}

View file

@ -74,23 +74,9 @@ impl Packet {
return None;
}
let Packet {
header:
Header {
command_code,
a,
b,
c,
d,
},
payload,
} = self;
let Packet { header, payload } = self;
slice[0..=1].copy_from_slice(&u16::to_be_bytes(*command_code));
slice[2..=3].copy_from_slice(&u16::to_be_bytes(*a));
slice[4..=5].copy_from_slice(&u16::to_be_bytes(*b));
slice[6..=7].copy_from_slice(&u16::to_be_bytes(*c));
slice[8..=9].copy_from_slice(&u16::to_be_bytes(*d));
header.write_to(slice);
if let Some(payload) = payload {
slice[10..][..payload.len()].copy_from_slice(payload);