use binrw::{
meta::{ReadEndian, WriteEndian},
BinRead, BinWrite,
};
mod cipher;
pub use cipher::{CipherCore, OpeningCipher, SealingCipher};
mod mac;
pub use mac::Mac;
pub const PACKET_MAX_SIZE: usize = u16::MAX as usize;
pub const PACKET_MIN_SIZE: usize = 16;
#[derive(Debug, Clone)]
pub struct Packet {
pub payload: Vec<u8>,
}
impl Packet {
pub fn to<T: for<'a> BinRead<Args<'a> = ()> + ReadEndian>(&self) -> Result<T, binrw::Error> {
T::read(&mut std::io::Cursor::new(&self.payload))
}
#[cfg(feature = "futures")]
#[cfg_attr(docsrs, doc(cfg(feature = "futures")))]
pub async fn from_async_reader<R, C>(
reader: &mut R,
cipher: &mut C,
seq: u32,
) -> Result<Self, C::Err>
where
R: futures::io::AsyncRead + Unpin,
C: OpeningCipher,
{
use futures::io::AsyncReadExt;
let mut buf = vec![0; cipher.block_size()];
reader.read_exact(&mut buf[..]).await?;
if !cipher.mac().etm() {
cipher.decrypt(&mut buf[..])?;
}
let len = u32::from_be_bytes(
buf[..4]
.try_into()
.expect("The buffer of size 4 is not of size 4"),
);
if len as usize > PACKET_MAX_SIZE {
return Err(binrw::Error::Custom {
pos: 0x0,
err: Box::new(format!("Packet size too large, {len} > {PACKET_MAX_SIZE}")),
})?;
}
buf.resize(std::mem::size_of_val(&len) + len as usize, 0);
reader.read_exact(&mut buf[cipher.block_size()..]).await?;
let mut mac = vec![0; cipher.mac().size()];
reader.read_exact(&mut mac[..]).await?;
if cipher.mac().etm() {
cipher.open(&buf, mac, seq)?;
cipher.decrypt(&mut buf[4..])?;
} else {
cipher.decrypt(&mut buf[cipher.block_size()..])?;
cipher.open(&buf, mac, seq)?;
}
let (padlen, mut decrypted) =
buf[4..].split_first().ok_or_else(|| binrw::Error::Custom {
pos: 0x4,
err: Box::new(format!("Packet size too small ({len})")),
})?;
if *padlen as usize > len as usize - 1 {
return Err(binrw::Error::Custom {
pos: 0x4,
err: Box::new(format!("Padding size too large, {padlen} > {} - 1", len)),
})?;
}
let mut payload = vec![0; len as usize - *padlen as usize - std::mem::size_of_val(padlen)];
std::io::Read::read_exact(&mut decrypted, &mut payload[..])?;
let payload = cipher.decompress(payload)?;
Ok(Self { payload })
}
#[cfg(feature = "futures")]
#[cfg_attr(docsrs, doc(cfg(feature = "futures")))]
pub async fn to_async_writer<W, C>(
&self,
writer: &mut W,
cipher: &mut C,
seq: u32,
) -> Result<(), C::Err>
where
W: futures::io::AsyncWrite + Unpin,
C: SealingCipher,
{
use futures::AsyncWriteExt;
let compressed = cipher.compress(&self.payload)?;
let padding = cipher.padding(compressed.len());
let buf = cipher.pad(compressed, padding)?;
let mut buf = [(buf.len() as u32).to_be_bytes().to_vec(), buf].concat();
let (buf, mac) = if cipher.mac().etm() {
cipher.encrypt(&mut buf[4..])?;
let mac = cipher.seal(&buf, seq)?;
(buf, mac)
} else {
let mac = cipher.seal(&buf, seq)?;
cipher.encrypt(&mut buf[..])?;
(buf, mac)
};
writer.write_all(&buf).await?;
writer.write_all(&mac).await?;
Ok(())
}
}
pub trait ToPacket: for<'a> BinWrite<Args<'a> = ()> + WriteEndian {
fn to_packet(&self) -> Result<Packet, binrw::Error> {
let mut buffer = std::io::Cursor::new(Vec::new());
self.write(&mut buffer)?;
Ok(Packet {
payload: buffer.into_inner(),
})
}
}
impl<T: for<'a> BinWrite<Args<'a> = ()> + WriteEndian> ToPacket for T {}