use std::error::Error; use std::io; use std::pin::{Pin, pin}; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::task::{Context, Poll, Wake}; use futures::{AsyncRead, AsyncReadExt, AsyncWrite}; use itertools::{Chunk, Itertools}; use crate::Encode; pub async fn encode_enum<'a, W: AsyncWrite + ?Sized>( mut write: Pin<&'a mut W>, id: u8, f: impl AsyncFnOnce(Pin<&'a mut W>) -> io::Result<()>, ) -> io::Result<()> { id.encode(write.as_mut()).await?; f(write).await } pub fn print_bytes(b: &[u8]) -> String { (b.iter().map(|b| format!("{b:02x}"))) .chunks(4) .into_iter() .map(|mut c: Chunk<_>| c.join(" ")) .join(" ") } pub async fn read_exact( mut read: Pin<&mut R>, bytes: &'static [u8], ) -> io::Result<()> { let mut data = vec![0u8; bytes.len()]; read.read_exact(&mut data).await?; if data == bytes { Ok(()) } else { let msg = format!("Wrong bytes!\nExpected: {}\nFound: {}", print_bytes(bytes), print_bytes(&data)); Err(io::Error::new(io::ErrorKind::InvalidData, msg)) } } pub fn enc_vec(enc: &impl Encode) -> Vec { let mut vec = Vec::new(); enc.encode_vec(&mut vec); vec } /// Raises a bool flag when called struct FlagWaker(AtomicBool); impl Wake for FlagWaker { fn wake(self: Arc) { self.0.store(true, Ordering::Relaxed) } } pub fn spin_on(fut: F) -> F::Output { let flag = AtomicBool::new(false); let flag_waker = Arc::new(FlagWaker(flag)); let mut future = pin!(fut); loop { let waker = flag_waker.clone().into(); let mut ctx = Context::from_waker(&waker); match future.as_mut().poll(&mut ctx) { // ideally the future should return synchronously Poll::Ready(res) => break res, // poorly written futures may yield and immediately wake Poll::Pending if flag_waker.0.load(Ordering::Relaxed) => (), // there is no external event to wait for, this has to be a deadlock Poll::Pending => panic!("Future inside spin_on cannot block"), }; } } pub fn decode_err() -> io::Error { io::Error::new(io::ErrorKind::InvalidData, "Unexpected zero") } pub fn decode_err_for(e: impl Error) -> io::Error { io::Error::new(io::ErrorKind::InvalidData, e.to_string()) }