use std::fmt::{Debug, Display}; use bytemuck::{Pod, Zeroable}; use serde::Serialize; use smallvec::SmallVec; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::tcp::{ReadHalf, WriteHalf}, }; pub const REJECT_OOP: &[u8; 6] = b"\x04\x04oop\x00"; pub const REJECT_TIMEOUT: &[u8; 10] = b"\x04\x08timeout\x00"; pub const REJECT_UNKNOWN_CLIENT: &[u8; 17] = b"\x04\x0funknown client\x00"; #[derive(Debug)] pub enum Error { TooLittleData { expected: u8, got: u8, }, Unexpected { expected: PacketKind, got: PacketKind, }, Io(std::io::Error), Client(Option), } impl std::error::Error for Error {} impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::TooLittleData { expected, got } => write!( f, "received too little data: expected {expected} bytes but received only {got}" ), Error::Unexpected { expected, got } => write!( f, "received an unexpected packet: expected {expected:?} but received {got:?}", ), Error::Io(inner) => Display::fmt(inner, f), Error::Client(Some(inner)) => write!(f, "client reported an error: {inner}"), Error::Client(None) => write!(f, "client reported a malformed error",), } } } impl From for Error { fn from(value: std::io::Error) -> Self { Self::Io(value) } } type Result = std::result::Result; #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PacketKind { Ping = 0x00, DynIpUpdate = 0x01, DynIpUpdateResponse = 0x02, End = 0x03, Reject = 0x04, RemConnect = 0x81, RemConfirm = 0x82, RemCall = 0x83, RemAck = 0x84, Unknown(u8), Error = 0xff, } #[allow(clippy::enum_glob_use)] impl PacketKind { #[must_use] fn from_u8(raw: u8) -> Self { use PacketKind::*; match raw { 0x00 => Ping, 0x01 => DynIpUpdate, 0x02 => DynIpUpdateResponse, 0x03 => End, 0x04 => Reject, 0x81 => RemConnect, 0x82 => RemConfirm, 0x83 => RemCall, 0x84 => RemAck, 0xff => Error, kind => Unknown(kind), } } #[must_use] pub fn raw(&self) -> u8 { use PacketKind::*; match self { Ping => 0, DynIpUpdate => 0x01, DynIpUpdateResponse => 0x02, End => 0x03, Reject => 0x04, RemConnect => 0x81, RemConfirm => 0x82, RemCall => 0x83, RemAck => 0x84, Error => 0xff, Unknown(value) => *value, } } } #[derive(Serialize, Default, Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct Header { pub kind: u8, pub length: u8, } #[derive(Serialize, Default, Clone)] pub struct Packet { pub header: Header, pub data: SmallVec<[u8; 8]>, } impl Packet { #[must_use] pub fn data(&self) -> &[u8] { &self.data[..self.header.length as usize] } #[must_use] pub fn as_string(&self) -> Option<&str> { let data = self.data(); let nul = data.iter().enumerate().find(|(_i, c)| **c == 0); let data = if let Some((i, _)) = nul { &data[..i] } else { data }; std::str::from_utf8(data).ok() } } impl Debug for Packet { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut debugger = f.debug_struct("Packet"); debugger.field("kind", &PacketKind::from_u8(self.header.kind)); match self.as_string() { Some(string) if string.chars().all(|c| !c.is_control()) => { debugger.field("data", &string); } _ => { debugger.field("data", &self.data()); } } debugger.finish() } } #[derive(Default, Debug, Clone, Copy)] #[repr(C)] pub struct RemConnect { pub number: u32, pub pin: u16, } impl Packet { #[allow(clippy::missing_errors_doc)] pub async fn peek_packet_kind(stream: &mut ReadHalf<'_>) -> std::io::Result { Self::peek_packet_kind_raw(stream) .await .map(PacketKind::from_u8) } #[allow(clippy::missing_errors_doc)] pub async fn peek_packet_kind_raw(stream: &mut ReadHalf<'_>) -> std::io::Result { let mut kind = 0; let n = stream.peek(std::slice::from_mut(&mut kind)).await?; if n == 1 { Ok(kind) } else { Err(std::io::ErrorKind::UnexpectedEof.into()) } } #[allow(clippy::missing_errors_doc)] pub async fn recv_into_cancelation_safe(&mut self, stream: &mut ReadHalf<'_>) -> Result<()> { // Makes sure all data is available before reading let header_bytes = bytemuck::bytes_of_mut(&mut self.header); stream.peek(header_bytes).await?; self.data.resize(self.header.length as usize + 2, 0); stream.peek(&mut self.data).await?; // All data is available. Read the data self.recv_into(stream).await } #[allow(clippy::missing_errors_doc)] pub async fn recv_into(&mut self, stream: &mut ReadHalf<'_>) -> Result<()> { let header_bytes = bytemuck::bytes_of_mut(&mut self.header); stream.read_exact(header_bytes).await?; self.data.resize(self.header.length as usize, 0); stream.read_exact(&mut self.data).await?; if self.header.kind == PacketKind::Error.raw() { return Err(Error::Client(self.as_string().map(ToOwned::to_owned))); } Ok(()) } #[allow(clippy::missing_errors_doc)] pub async fn send(&self, stream: &mut WriteHalf<'_>) -> std::io::Result<()> { stream.write_all(bytemuck::bytes_of(&self.header)).await?; stream.write_all(self.data()).await?; Ok(()) } #[must_use] pub fn kind(&self) -> PacketKind { PacketKind::from_u8(self.header.kind) } /// # Errors /// the packet must be a `RemConnect` packet and must contain at least 6 bytes of data #[allow(clippy::missing_panics_doc)] pub fn as_rem_connect(&self) -> Result { if self.kind() != PacketKind::RemConnect { return Err(Error::Unexpected { expected: PacketKind::RemConnect, got: self.kind(), }); } if self.data.len() < 6 { #[allow(clippy::cast_possible_truncation)] return Err(Error::TooLittleData { expected: 6, got: self.data.len() as u8, }); } Ok(RemConnect { number: u32::from_le_bytes(self.data[..4].try_into().unwrap()), pin: u16::from_le_bytes(self.data[4..6].try_into().unwrap()), }) } }