use crate::router::I2pRouter;
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::task::JoinHandle;
const RECEIVE_POLL_SECS: i32 = 10;
const MAX_RECEIVE_CHUNK: usize = 64 * 1024;
struct StreamHandle {
ptr: *mut i2pd_sys::I2pdStream,
closed: AtomicBool,
}
unsafe impl Send for StreamHandle {}
unsafe impl Sync for StreamHandle {}
impl StreamHandle {
fn close(&self) {
if self.closed.swap(true, Ordering::AcqRel) {
return; }
unsafe { i2pd_sys::i2pd_stream_close(self.ptr) }
}
fn is_open(&self) -> bool {
unsafe { i2pd_sys::i2pd_stream_is_open(self.ptr) != 0 }
}
}
impl Drop for StreamHandle {
fn drop(&mut self) {
unsafe { i2pd_sys::i2pd_destroy_stream(self.ptr) }
}
}
impl std::fmt::Debug for StreamHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StreamHandle").finish_non_exhaustive()
}
}
type ReadOutcome = (Arc<StreamHandle>, Vec<u8>, io::Result<usize>);
#[derive(Debug)]
enum ReadState {
Idle {
buf: Vec<u8>,
pos: usize,
},
Reading(JoinHandle<ReadOutcome>),
}
#[derive(Debug)]
pub struct I2pStream {
handle: Arc<StreamHandle>,
read: ReadState,
saw_open: bool,
_router: I2pRouter,
}
impl I2pStream {
pub(crate) unsafe fn from_raw(router: I2pRouter, ptr: *mut i2pd_sys::I2pdStream) -> Self {
Self {
handle: Arc::new(StreamHandle {
ptr,
closed: AtomicBool::new(false),
}),
read: ReadState::Idle {
buf: Vec::with_capacity(8192),
pos: 0,
},
saw_open: false,
_router: router,
}
}
#[must_use]
pub fn is_open(&self) -> bool {
self.handle.is_open()
}
}
fn drain_leftover(buf: &mut Vec<u8>, pos: &mut usize, out: &mut ReadBuf<'_>) -> bool {
if *pos >= buf.len() {
return false;
}
let n = (buf.len() - *pos).min(out.remaining());
out.put_slice(&buf[*pos..*pos + n]);
*pos += n;
if *pos == buf.len() {
buf.clear();
*pos = 0;
}
true
}
impl AsyncRead for I2pStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
out: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let this = self.get_mut();
if out.remaining() == 0 {
return Poll::Ready(Ok(()));
}
loop {
match &mut this.read {
ReadState::Idle { buf, pos } => {
if drain_leftover(buf, pos, out) {
return Poll::Ready(Ok(()));
}
let mut scratch = std::mem::take(buf);
*pos = 0;
let want = out.remaining().min(MAX_RECEIVE_CHUNK);
if scratch.len() < want {
scratch.resize(want, 0);
}
let handle = this.handle.clone();
this.read = ReadState::Reading(tokio::task::spawn_blocking(move || {
let result = receive_until_data_or_close(&handle, &mut scratch[..want]);
(handle, scratch, result)
}));
}
ReadState::Reading(join) => {
let (handle, mut buf, result) = match Pin::new(join).poll(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Ok(outcome)) => outcome,
Poll::Ready(Err(_)) => {
this.read = ReadState::Idle {
buf: Vec::new(),
pos: 0,
};
return Poll::Ready(Err(io::Error::other(
"i2p receive worker panicked",
)));
}
};
drop(handle); let n = match result {
Ok(n) => n,
Err(e) => {
this.read = ReadState::Idle {
buf: Vec::new(),
pos: 0,
};
return Poll::Ready(Err(e));
}
};
if n == 0 {
this.read = ReadState::Idle {
buf: Vec::new(),
pos: 0,
};
return Poll::Ready(Ok(()));
}
buf.truncate(n);
this.read = ReadState::Idle { buf, pos: 0 };
}
}
}
}
}
fn receive_until_data_or_close(handle: &StreamHandle, buf: &mut [u8]) -> io::Result<usize> {
loop {
let n = unsafe {
i2pd_sys::i2pd_stream_receive(
handle.ptr,
buf.as_mut_ptr(),
buf.len(),
RECEIVE_POLL_SECS,
)
};
if n < 0 {
return Err(io::Error::other("i2p stream receive failed"));
}
if n > 0 {
return Ok(usize::try_from(n).unwrap_or(0).min(buf.len()));
}
if !handle.is_open() {
return Ok(0); }
}
}
impl AsyncWrite for I2pStream {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let this = self.get_mut();
if this.handle.is_open() {
this.saw_open = true;
} else if this.saw_open {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"i2p stream is closed",
)));
}
let n = unsafe { i2pd_sys::i2pd_stream_send(this.handle.ptr, buf.as_ptr(), buf.len()) };
if n < 0 {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"i2p stream send failed",
)));
}
let n = usize::try_from(n).unwrap_or(0).min(buf.len());
if n == 0 && !buf.is_empty() {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"i2p stream accepted no bytes",
)));
}
Poll::Ready(Ok(n))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.handle.close();
Poll::Ready(Ok(()))
}
}
impl Drop for I2pStream {
fn drop(&mut self) {
self.handle.close();
}
}
#[cfg(test)]
mod tests {
use super::drain_leftover;
use tokio::io::ReadBuf;
#[test]
fn leftover_larger_than_destination_is_retained() {
let mut leftover = vec![7u8; 50];
let mut pos = 0usize;
let mut dst = [0u8; 10];
let mut out = ReadBuf::new(&mut dst);
assert!(drain_leftover(&mut leftover, &mut pos, &mut out));
assert_eq!(out.filled().len(), 10);
assert_eq!(out.filled(), &[7u8; 10]);
assert_eq!(pos, 10);
assert_eq!(
leftover.len(),
50,
"buffer must be retained until fully drained"
);
let mut dst2 = [0u8; 100];
let mut out2 = ReadBuf::new(&mut dst2);
assert!(drain_leftover(&mut leftover, &mut pos, &mut out2));
assert_eq!(out2.filled().len(), 40);
assert_eq!(pos, 0);
assert!(
leftover.is_empty(),
"fully-drained buffer must reset to empty"
);
}
#[test]
fn no_leftover_reports_nothing_to_drain() {
let mut buf = Vec::new();
let mut pos = 0usize;
let mut dst = [0u8; 10];
let mut out = ReadBuf::new(&mut dst);
assert!(!drain_leftover(&mut buf, &mut pos, &mut out));
assert_eq!(out.filled().len(), 0);
}
}