use core::future::poll_fn;
use core::mem::MaybeUninit;
use core::ops::{Bound, RangeBounds};
use core::pin::Pin;
use core::task::{Context, Poll};
use std::io;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use crate::bytes::{Bytes, BytesMut};
pub type BufResult<T, B> = (io::Result<T>, B);
pub unsafe trait IoBuf: Send + 'static {
fn as_init(&self) -> &[u8];
#[inline]
fn buf_len(&self) -> usize {
self.as_init().len()
}
#[inline]
fn buf_ptr(&self) -> *const u8 {
self.as_init().as_ptr()
}
fn slice(self, range: impl RangeBounds<usize>) -> Slice<Self>
where
Self: Sized,
{
#[expect(
clippy::expect_used,
reason = "a valid buffer range bound + 1 cannot overflow usize"
)]
let begin = match range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n.checked_add(1).expect("slice range out of bounds"),
Bound::Unbounded => 0,
};
#[expect(
clippy::expect_used,
reason = "a valid buffer range bound + 1 cannot overflow usize"
)]
let end = match range.end_bound() {
Bound::Included(&n) => n.checked_add(1).expect("slice range out of bounds"),
Bound::Excluded(&n) => n,
Bound::Unbounded => self.buf_len(),
};
assert!(
begin <= end && end <= self.buf_len(),
"slice range out of bounds (must be within the initialized region)"
);
Slice {
buf: self,
begin,
end,
}
}
}
pub trait SetLen {
unsafe fn set_len(&mut self, len: usize);
}
pub unsafe trait IoBufMut: IoBuf + SetLen {
fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>];
#[inline]
fn buf_capacity(&mut self) -> usize {
self.as_uninit().len()
}
#[inline]
fn buf_mut_ptr(&mut self) -> *mut u8 {
self.as_uninit().as_mut_ptr().cast()
}
#[inline]
fn spare_mut(&mut self) -> &mut [MaybeUninit<u8>] {
let len = self.buf_len();
&mut self.as_uninit()[len..]
}
#[inline]
fn as_mut_slice(&mut self) -> &mut [u8] {
let len = self.buf_len();
unsafe { self.as_uninit()[..len].assume_init_mut() }
}
fn uninit(self) -> Uninit<Self>
where
Self: Sized,
{
let begin = self.buf_len();
Uninit { buf: self, begin }
}
}
#[derive(Debug)]
pub struct Slice<B> {
buf: B,
begin: usize,
end: usize,
}
impl<B> Slice<B> {
#[inline]
pub fn into_inner(self) -> B {
self.buf
}
}
unsafe impl<B: IoBuf> IoBuf for Slice<B> {
#[inline]
fn as_init(&self) -> &[u8] {
&self.buf.as_init()[self.begin..self.end]
}
}
#[derive(Debug)]
pub struct Uninit<B> {
buf: B,
begin: usize,
}
impl<B> Uninit<B> {
#[inline]
pub fn into_inner(self) -> B {
self.buf
}
}
unsafe impl<B: IoBuf> IoBuf for Uninit<B> {
#[inline]
fn as_init(&self) -> &[u8] {
&[]
}
}
impl<B: SetLen + IoBuf> SetLen for Uninit<B> {
#[inline]
unsafe fn set_len(&mut self, len: usize) {
unsafe { self.buf.set_len(self.begin + len) };
}
}
unsafe impl<B: IoBufMut> IoBufMut for Uninit<B> {
#[inline]
fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>] {
let begin = self.begin;
&mut self.buf.as_uninit()[begin..]
}
}
#[derive(Debug)]
pub enum BufSlot<B> {
Ready(B),
InFlight,
Parked(B),
}
impl<B> BufSlot<B> {
#[inline]
pub fn new(buf: B) -> Self {
Self::Ready(buf)
}
#[inline]
pub fn ready(&self) -> Option<&B> {
match self {
Self::Ready(b) => Some(b),
_ => None,
}
}
#[inline]
pub fn ready_mut(&mut self) -> Option<&mut B> {
match self {
Self::Ready(b) => Some(b),
_ => None,
}
}
#[inline]
pub fn take_ready(self) -> Option<B> {
match self {
Self::Ready(b) => Some(b),
_ => None,
}
}
#[inline]
pub fn reclaim(self) -> Option<B> {
match self {
Self::Ready(b) | Self::Parked(b) => Some(b),
Self::InFlight => None,
}
}
#[inline]
pub fn is_ready(&self) -> bool {
matches!(self, Self::Ready(_))
}
#[inline]
pub fn take(&mut self) -> Option<B> {
match core::mem::replace(self, Self::InFlight) {
Self::Ready(b) | Self::Parked(b) => Some(b),
Self::InFlight => None,
}
}
#[inline]
pub fn fill(&mut self, buf: B) {
*self = Self::Ready(buf);
}
#[inline]
pub fn park(&mut self, buf: B) {
*self = Self::Parked(buf);
}
}
pub trait AsyncReadOwned {
fn poll_read_owned<B: IoBufMut>(
&mut self,
cx: &mut Context<'_>,
slot: &mut BufSlot<B>,
) -> Poll<io::Result<usize>>;
fn read_owned<B: IoBufMut>(
&mut self,
buf: B,
) -> impl Future<Output = BufResult<usize, B>> + Send
where
Self: Send,
{
async move {
let mut slot = BufSlot::new(buf);
let res = poll_fn(|cx| self.poll_read_owned(cx, &mut slot)).await;
#[expect(
clippy::expect_used,
reason = "poll_fn resolved the op, so the slot holds the buffer"
)]
let buf = slot
.reclaim()
.expect("the op resolved (Ok or Err), leaving the buffer in the slot");
(res, buf)
}
}
}
pub trait AsyncWriteOwned {
fn poll_write_owned<B: IoBuf>(
&mut self,
cx: &mut Context<'_>,
slot: &mut BufSlot<B>,
) -> Poll<io::Result<usize>>;
fn poll_flush_owned(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
fn poll_shutdown_owned(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
fn is_write_vectored_owned(&self) -> bool {
false
}
fn write_owned<B: IoBuf>(&mut self, buf: B) -> impl Future<Output = BufResult<usize, B>> + Send
where
Self: Send,
{
async move {
let mut slot = BufSlot::new(buf);
let res = poll_fn(|cx| self.poll_write_owned(cx, &mut slot)).await;
#[expect(
clippy::expect_used,
reason = "poll_fn resolved the op, so the slot holds the buffer"
)]
let buf = slot
.reclaim()
.expect("the op resolved (Ok or Err), leaving the buffer in the slot");
(res, buf)
}
}
fn flush_owned(&mut self) -> impl Future<Output = io::Result<()>> + Send
where
Self: Send,
{
async move { poll_fn(|cx| self.poll_flush_owned(cx)).await }
}
fn shutdown_owned(&mut self) -> impl Future<Output = io::Result<()>> + Send
where
Self: Send,
{
async move { poll_fn(|cx| self.poll_shutdown_owned(cx)).await }
}
}
impl<T: AsyncRead + Unpin + Send + ?Sized> AsyncReadOwned for T {
#[inline]
fn poll_read_owned<B: IoBufMut>(
&mut self,
cx: &mut Context<'_>,
slot: &mut BufSlot<B>,
) -> Poll<io::Result<usize>> {
#[expect(
clippy::expect_used,
reason = "readiness never moves the buffer to a leaf, so the slot is filled here"
)]
let mut buf = slot
.take()
.expect("poll_read_owned: slot empty (readiness never moves the buffer to a leaf)");
let (res, n) = {
let mut rb = ReadBuf::uninit(buf.as_uninit());
let res = Pin::new(&mut *self).poll_read(cx, &mut rb);
let n = rb.filled().len();
(res, n)
};
unsafe { buf.set_len(n) };
match res {
Poll::Ready(Ok(())) => {
slot.fill(buf);
Poll::Ready(Ok(n))
}
Poll::Ready(Err(e)) => {
slot.fill(buf);
Poll::Ready(Err(e))
}
Poll::Pending => {
slot.park(buf);
Poll::Pending
}
}
}
}
impl<T: AsyncWrite + Unpin + Send + ?Sized> AsyncWriteOwned for T {
#[inline]
fn poll_write_owned<B: IoBuf>(
&mut self,
cx: &mut Context<'_>,
slot: &mut BufSlot<B>,
) -> Poll<io::Result<usize>> {
#[expect(
clippy::expect_used,
reason = "readiness never moves the buffer to a leaf, so the slot is filled here"
)]
let buf = slot.take().expect("poll_write_owned: slot empty");
match Pin::new(&mut *self).poll_write(cx, buf.as_init()) {
Poll::Ready(res) => {
slot.fill(buf);
Poll::Ready(res)
}
Poll::Pending => {
slot.park(buf);
Poll::Pending
}
}
}
#[inline]
fn poll_flush_owned(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut *self).poll_flush(cx)
}
#[inline]
fn poll_shutdown_owned(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut *self).poll_shutdown(cx)
}
#[inline]
fn is_write_vectored_owned(&self) -> bool {
Self::is_write_vectored(self)
}
}
unsafe impl IoBuf for Vec<u8> {
#[inline]
fn as_init(&self) -> &[u8] {
self
}
}
impl SetLen for Vec<u8> {
#[inline]
unsafe fn set_len(&mut self, len: usize) {
unsafe { Self::set_len(self, len) };
}
}
unsafe impl IoBufMut for Vec<u8> {
#[inline]
fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>] {
let cap = self.capacity();
let ptr = self.as_mut_ptr().cast::<MaybeUninit<u8>>();
unsafe { std::slice::from_raw_parts_mut(ptr, cap) }
}
}
unsafe impl IoBuf for BytesMut {
#[inline]
fn as_init(&self) -> &[u8] {
self
}
}
impl SetLen for BytesMut {
#[inline]
unsafe fn set_len(&mut self, len: usize) {
unsafe { Self::set_len(self, len) };
}
}
unsafe impl IoBufMut for BytesMut {
#[inline]
fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>] {
let cap = self.capacity();
let ptr = self.as_mut_ptr().cast::<MaybeUninit<u8>>();
unsafe { std::slice::from_raw_parts_mut(ptr, cap) }
}
}
unsafe impl IoBuf for Bytes {
#[inline]
fn as_init(&self) -> &[u8] {
self
}
}
pub struct ArrayBuf<const N: usize> {
buf: [MaybeUninit<u8>; N],
init: usize,
}
impl<const N: usize> ArrayBuf<N> {
#[inline]
pub const fn new() -> Self {
Self {
buf: [MaybeUninit::uninit(); N],
init: 0,
}
}
#[inline]
pub fn try_into_array(self) -> Result<[u8; N], Self> {
if self.init != N {
return Err(self);
}
Ok(self.buf.map(|b| unsafe { b.assume_init() }))
}
}
impl<const N: usize> Default for ArrayBuf<N> {
#[inline]
fn default() -> Self {
Self::new()
}
}
unsafe impl<const N: usize> IoBuf for ArrayBuf<N> {
#[inline]
fn as_init(&self) -> &[u8] {
unsafe { self.buf[..self.init].assume_init_ref() }
}
}
impl<const N: usize> SetLen for ArrayBuf<N> {
#[inline]
unsafe fn set_len(&mut self, len: usize) {
debug_assert!(len <= N);
self.init = len;
}
}
unsafe impl<const N: usize> IoBufMut for ArrayBuf<N> {
#[inline]
fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>] {
&mut self.buf
}
}
pub trait SplitIo: Sized {
type ReadHalf: AsyncReadOwned + Send;
type WriteHalf: AsyncWriteOwned + Send;
fn split_io(self) -> (Self::ReadHalf, Self::WriteHalf);
}
impl<T: AsyncRead + AsyncWrite + Send> SplitIo for T {
type ReadHalf = tokio::io::ReadHalf<T>;
type WriteHalf = tokio::io::WriteHalf<T>;
#[inline]
fn split_io(self) -> (Self::ReadHalf, Self::WriteHalf) {
tokio::io::split(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn io_buf_slice_rejects_overflowing_bounds() {
let start = std::panic::catch_unwind(|| {
b"abc"
.to_vec()
.slice((Bound::Excluded(usize::MAX), Bound::Unbounded))
});
start.unwrap_err();
let end = std::panic::catch_unwind(|| {
b"abc"
.to_vec()
.slice((Bound::Unbounded, Bound::Included(usize::MAX)))
});
end.unwrap_err();
}
#[test]
fn array_buf_try_into_array_requires_full_init() {
let mut partial = ArrayBuf::<4>::new();
unsafe { partial.set_len(2) };
let partial = partial.try_into_array().unwrap_err();
assert_eq!(partial.buf_len(), 2);
let mut full = ArrayBuf::<4>::new();
full.as_uninit()[..4].copy_from_slice(&[
MaybeUninit::new(b't'),
MaybeUninit::new(b'e'),
MaybeUninit::new(b's'),
MaybeUninit::new(b't'),
]);
unsafe { full.set_len(4) };
let Ok(full) = full.try_into_array() else {
panic!("fully initialized ArrayBuf should convert to an array")
};
assert_eq!(full, *b"test");
}
#[tokio::test]
async fn owned_roundtrip_vec() {
let (mut a, mut b) = tokio::io::duplex(64);
let (r, wbuf) = a.write_owned(b"hello".to_vec()).await;
assert_eq!(r.unwrap(), 5);
assert_eq!(wbuf, b"hello".to_vec());
let (r, rbuf) = b.read_owned(Vec::with_capacity(16)).await;
assert_eq!(r.unwrap(), 5);
assert_eq!(&rbuf[..], b"hello");
assert_eq!(rbuf.len(), 5);
}
#[tokio::test]
async fn owned_read_overwrites_from_zero() {
let (mut a, mut b) = tokio::io::duplex(64);
let (w, _) = a.write_owned(b"world".to_vec()).await;
assert_eq!(w.unwrap(), 5);
let mut buf = Vec::with_capacity(16);
buf.extend_from_slice(b"pre-"); let (r, buf) = b.read_owned(buf).await;
assert_eq!(r.unwrap(), 5);
assert_eq!(&buf[..], b"world");
}
#[tokio::test]
async fn owned_read_into_uninit_appends() {
let (mut a, mut b) = tokio::io::duplex(64);
let (w, _) = a.write_owned(b"world".to_vec()).await;
assert_eq!(w.unwrap(), 5);
let mut buf = Vec::with_capacity(16);
buf.extend_from_slice(b"pre-"); let (r, view) = b.read_owned(buf.uninit()).await;
assert_eq!(r.unwrap(), 5);
let buf = view.into_inner();
assert_eq!(&buf[..], b"pre-world");
}
#[tokio::test]
async fn owned_roundtrip_bytes_mut_and_bytes() {
let (mut a, mut b) = tokio::io::duplex(64);
let (w, _) = a.write_owned(Bytes::from_static(b"xyz")).await;
assert_eq!(w.unwrap(), 3);
let (r, buf) = b.read_owned(BytesMut::with_capacity(8)).await;
assert_eq!(r.unwrap(), 3);
assert_eq!(&buf[..], b"xyz");
}
#[tokio::test]
async fn poll_read_owned_reads_then_signals_eof() {
let (mut a, mut b) = tokio::io::duplex(64);
let (w, _) = a.write_owned(b"chunk".to_vec()).await;
w.unwrap();
let mut slot = BufSlot::new(BytesMut::with_capacity(16));
let n = core::future::poll_fn(|cx| b.poll_read_owned(cx, &mut slot))
.await
.unwrap();
assert_eq!(&slot.ready_mut().unwrap()[..n], b"chunk");
drop(a);
let mut slot = BufSlot::new(BytesMut::with_capacity(16));
let eof = core::future::poll_fn(|cx| b.poll_read_owned(cx, &mut slot))
.await
.unwrap();
assert_eq!(eof, 0);
}
#[tokio::test]
async fn split_io_halves_are_owned_and_concurrent() {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let (a, mut b) = tokio::io::duplex(64);
let (mut a_r, mut a_w) = a.split_io();
let (res, _) = a_w.write_owned(b"split".to_vec()).await;
res.unwrap();
let mut got = [0u8; 5];
b.read_exact(&mut got).await.unwrap();
assert_eq!(&got, b"split");
b.write_all(b"back!").await.unwrap();
let (res, buf) = a_r.read_owned(Vec::with_capacity(8)).await;
assert_eq!(res.unwrap(), 5);
assert_eq!(&buf[..], b"back!");
}
}