for reading, add async keyword to your function, replace std::io::Read with futures_util::io::AsyncReadExt, and add .await to read() methods, something like:
pub async fn read(&mut self) -> Result<(u8, Vec<u8>)>
where
S: AsyncReadExt + Unpin
{
let mut len_buf = [0; 4];
self.0.read_exact(&mut len_buf).await?;
let len = u32::from_be_bytes(len_buf) as usize;
let mut ptype_buf = [0; 1];
self.0.read_exact(&mut ptype_buf).await?;
let ptype = ptype_buf[0];
let mut buffer = vec![0; len];
self.0.read_exact(&mut buffer).await?;
Ok((ptype, buffer))
}
for writing, it's similar, but with AsyncWriteExt.
the long answer:
it depends what async runtime you are using. for example, if you are using tokio only, you can use tokio::io::AsyncReadExt and tokio::io::AsyncWriteExt. if you are using async_std, you can use async_std::io::ReadExt and async_std::io::WriteExt etc.
the futures_io in theory should be supported by different async ecosystems, but you might need a compatibility layer, e.g. for tokio, you can use tokio_util with the "compat" feature.
the Unpin bound is necessary due to how async and Future work in rust. if you know how futures work, technically you can use futures::future::poll_fn() and the AsyncRead/AsyncWrite trait directly (no Ext, no Unpin), but you need to manage the pinning yourself, so I would not recommend it if you are new to async rust.