use core::ffi::{c_int, c_void};
use core::marker::{PhantomData, PhantomPinned};
use core::pin::Pin;
use core::sync::atomic::{AtomicBool, Ordering};
use core::time::Duration;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
use std::sync::Arc;
use crate::error::{Error, ErrorKind, Result};
use crate::sys;
pub unsafe trait Io {
fn io_ptr(self: Pin<&mut Self>) -> *mut sys::chc_io;
fn set_read_timeout(self: Pin<&mut Self>, _timeout: Option<Duration>) -> Result<()> {
Err(Error::new(
ErrorKind::Usage,
"I/O backend does not support read timeouts",
))
}
}
#[derive(Clone, Debug, Default)]
pub struct CancelToken(Arc<AtomicBool>);
impl CancelToken {
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
self.0.store(true, Ordering::Relaxed);
}
pub fn is_cancelled(&self) -> bool {
self.0.load(Ordering::Relaxed)
}
}
unsafe extern "C" fn check_cancel_flag(ud: *mut c_void) -> bool {
unsafe { &*ud.cast::<AtomicBool>() }.load(Ordering::Relaxed)
}
pub struct PosixIo<'fd> {
state: sys::chc_posix_io,
io: sys::chc_io,
#[allow(dead_code)]
owned: Option<OwnedFd>,
#[allow(dead_code)]
cancel: Option<CancelToken>,
_fd: PhantomData<BorrowedFd<'fd>>,
_pin: PhantomPinned,
}
impl<'fd> PosixIo<'fd> {
pub fn new(fd: BorrowedFd<'fd>) -> Pin<Box<Self>> {
Self::build(fd.as_raw_fd(), None, None)
}
pub fn new_cancellable(fd: BorrowedFd<'fd>, cancel: CancelToken) -> Pin<Box<Self>> {
Self::build(fd.as_raw_fd(), None, Some(cancel))
}
fn build(fd: RawFd, owned: Option<OwnedFd>, cancel: Option<CancelToken>) -> Pin<Box<Self>> {
let mut boxed = Box::pin(Self {
state: sys::chc_posix_io {
fd,
check_cancel: None,
cancel_ud: core::ptr::null_mut(),
deadline_us: 0,
},
io: sys::chc_io {
ud: core::ptr::null_mut(),
read: None,
write: None,
check_cancel: None,
},
owned,
cancel,
_fd: PhantomData,
_pin: PhantomPinned,
});
unsafe {
let this = boxed.as_mut().get_unchecked_mut();
let (check, ud) = match &this.cancel {
Some(token) => (
Some(check_cancel_flag as unsafe extern "C" fn(*mut c_void) -> bool),
Arc::as_ptr(&token.0).cast_mut().cast::<c_void>(),
),
None => (None, core::ptr::null_mut()),
};
sys::chc_posix_io_init(&mut this.state, &mut this.io, fd, check, ud);
}
boxed
}
pub fn set_read_timeout(self: Pin<&mut Self>, timeout: Option<Duration>) {
let deadline_us = match timeout {
None => 0,
Some(d) => {
let now = unsafe { sys::chc_rs_monotonic_us() };
let add = i64::try_from(d.as_micros()).unwrap_or(i64::MAX);
now.saturating_add(add).max(1)
}
};
unsafe { sys::chc_posix_io_set_deadline(&mut self.get_unchecked_mut().state, deadline_us) };
}
}
impl PosixIo<'static> {
pub fn new_owned<F: Into<OwnedFd>>(fd: F) -> Pin<Box<Self>> {
let fd = fd.into();
let raw = fd.as_fd().as_raw_fd();
Self::build(raw, Some(fd), None)
}
pub fn new_owned_cancellable<F: Into<OwnedFd>>(fd: F, cancel: CancelToken) -> Pin<Box<Self>> {
let fd = fd.into();
let raw = fd.as_fd().as_raw_fd();
Self::build(raw, Some(fd), Some(cancel))
}
}
unsafe impl<'fd> Io for PosixIo<'fd> {
fn io_ptr(self: Pin<&mut Self>) -> *mut sys::chc_io {
unsafe { &mut self.get_unchecked_mut().io as *mut sys::chc_io }
}
fn set_read_timeout(self: Pin<&mut Self>, timeout: Option<Duration>) -> Result<()> {
Self::set_read_timeout(self, timeout);
Ok(())
}
}
unsafe impl<'fd> Send for PosixIo<'fd> {}
pub struct SliceIo<'a> {
io: sys::chc_io,
bytes: &'a [u8],
read_at: usize,
_pin: PhantomPinned,
}
impl<'a> SliceIo<'a> {
pub fn new(bytes: &'a [u8]) -> Pin<Box<Self>> {
let mut boxed = Box::pin(Self {
io: sys::chc_io {
ud: core::ptr::null_mut(),
read: Some(slice_read),
write: Some(slice_write),
check_cancel: None,
},
bytes,
read_at: 0,
_pin: PhantomPinned,
});
unsafe {
let this = boxed.as_mut().get_unchecked_mut();
this.io.ud = (this as *mut Self).cast();
}
boxed
}
pub fn remaining(self: Pin<&Self>) -> usize {
let this = self.get_ref();
this.bytes.len() - this.read_at
}
}
unsafe extern "C" fn slice_read(
ud: *mut c_void,
buf: *mut c_void,
len: usize,
out_n: *mut usize,
_err: *mut sys::chc_err,
) -> c_int {
let this = unsafe { &mut *ud.cast::<SliceIo<'_>>() };
let n = len.min(this.bytes.len() - this.read_at);
if n > 0 {
unsafe {
core::ptr::copy_nonoverlapping(
this.bytes[this.read_at..].as_ptr(),
buf.cast::<u8>(),
n,
);
}
this.read_at += n;
}
unsafe { *out_n = n };
sys::CHC_OK
}
unsafe extern "C" fn slice_write(
_ud: *mut c_void,
_buf: *const c_void,
_len: usize,
err: *mut sys::chc_err,
) -> c_int {
const MSG: &[u8] = b"slice transport is read-only";
if !err.is_null() {
let e = unsafe { &mut *err };
let n = MSG.len().min(e.msg.len() - 1);
for (slot, b) in e.msg.iter_mut().zip(&MSG[..n]) {
*slot = *b as core::ffi::c_char;
}
e.msg[n] = 0;
}
sys::CHC_ERR_IO
}
unsafe impl Send for SliceIo<'_> {}
unsafe impl Io for SliceIo<'_> {
fn io_ptr(self: Pin<&mut Self>) -> *mut sys::chc_io {
unsafe { &mut self.get_unchecked_mut().io as *mut sys::chc_io }
}
}
#[cfg(test)]
mod tests {
use super::slice_write;
use crate::sys;
#[test]
fn a_rejected_write_needs_no_error_slot() {
let rc = unsafe {
slice_write(
core::ptr::null_mut(),
core::ptr::null(),
0,
core::ptr::null_mut(),
)
};
assert_eq!(rc, sys::CHC_ERR_IO);
}
}