use crate::{command_code::CommandCode, Grid, Origin, Tiles};
use log::trace;
use std::{mem::size_of, num::TryFromIntError};
#[derive(Copy, Clone, Debug, PartialEq, Default, Hash)]
#[repr(C)]
pub struct Header {
pub command_code: u16,
pub a: u16,
pub b: u16,
pub c: u16,
pub d: u16,
}
pub const HEADER_SIZE: usize = size_of::<Header>();
pub type Payload = Vec<u8>;
#[derive(Clone, Debug, PartialEq, Hash)]
pub struct Packet {
pub header: Header,
pub payload: Option<Payload>,
}
impl From<&Packet> for Vec<u8> {
fn from(value: &Packet) -> Self {
let mut vec = vec![0u8; value.size()];
value.serialize_to(vec.as_mut_slice());
vec
}
}
impl From<Packet> for Vec<u8> {
fn from(value: Packet) -> Self {
(&value).into()
}
}
#[derive(Debug, thiserror::Error, Eq, PartialEq)]
#[error("The provided slice is smaller than the header size, so it cannot be read as a packet.")]
pub struct SliceSmallerThanHeader;
impl TryFrom<&[u8]> for Packet {
type Error = SliceSmallerThanHeader;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() < HEADER_SIZE {
return Err(SliceSmallerThanHeader);
}
let header = {
let command_code = Self::u16_from_be_slice(&value[0..=1]);
let a = Self::u16_from_be_slice(&value[2..=3]);
let b = Self::u16_from_be_slice(&value[4..=5]);
let c = Self::u16_from_be_slice(&value[6..=7]);
let d = Self::u16_from_be_slice(&value[8..=9]);
Header {
command_code,
a,
b,
c,
d,
}
};
let payload = if value.len() <= HEADER_SIZE {
None
} else {
Some(value[HEADER_SIZE..].to_vec())
};
trace!("loaded packet {header:?}");
Ok(Packet { header, payload })
}
}
impl TryFrom<Vec<u8>> for Packet {
type Error = SliceSmallerThanHeader;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Self::try_from(value.as_slice())
}
}
impl Packet {
pub fn serialize_to(&self, slice: &mut [u8]) -> Option<usize> {
let size = self.size();
if slice.len() < size {
return None;
}
let Packet {
header:
Header {
command_code,
a,
b,
c,
d,
},
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));
if let Some(payload) = payload {
slice[10..][..payload.len()].copy_from_slice(payload);
}
Some(size)
}
#[must_use]
pub fn size(&self) -> usize {
HEADER_SIZE + self.payload.as_ref().map_or(0, Vec::len)
}
fn u16_from_be_slice(slice: &[u8]) -> u16 {
let mut bytes = [0u8; 2];
bytes[0] = slice[0];
bytes[1] = slice[1];
u16::from_be_bytes(bytes)
}
pub(crate) fn origin_grid_to_packet<T>(
origin: Origin<Tiles>,
grid: impl Grid<T> + Into<Payload>,
command_code: CommandCode,
) -> Result<Packet, TryFromIntError> {
Ok(Packet {
header: Self::origin_grid_header(origin, &grid, command_code)?,
payload: Some(grid.into()),
})
}
pub(crate) fn origin_grid_as_packet<T, G: Grid<T>>(
origin: Origin<Tiles>,
grid: &G,
command_code: CommandCode,
) -> Result<Packet, TryFromIntError>
where
for<'a> &'a G: Into<Payload>,
{
Ok(Packet {
header: Self::origin_grid_header(origin, grid, command_code)?,
payload: Some(grid.into()),
})
}
fn origin_grid_header<T>(
origin: Origin<Tiles>,
grid: &impl Grid<T>,
command_code: CommandCode,
) -> Result<Header, TryFromIntError> {
Ok(Header {
command_code: command_code.into(),
a: origin.x.try_into()?,
b: origin.y.try_into()?,
c: grid.width().try_into()?,
d: grid.height().try_into()?,
})
}
pub(crate) fn command_code_only(c: CommandCode) -> Self {
Self {
header: Header {
command_code: c.into(),
..Default::default()
},
payload: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
let p = Packet {
header: Header {
command_code: 0,
a: 1,
b: 2,
c: 3,
d: 4,
},
payload: Some(vec![42u8; 23]),
};
let data: Vec<u8> = p.into();
let p = Packet::try_from(data).unwrap();
assert_eq!(
p,
Packet {
header: Header {
command_code: 0,
a: 1,
b: 2,
c: 3,
d: 4
},
payload: Some(vec![42u8; 23])
}
);
}
#[test]
fn too_small() {
let data = vec![0u8; 4];
assert_eq!(
Packet::try_from(data.as_slice()),
Err(SliceSmallerThanHeader)
);
}
}