use crate::router::I2pRouter;
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::task::JoinHandle;
const RECEIVE_POLL_SECS: i32 = 10;
struct StreamHandle(*mut i2pd_sys::I2pdStream);
unsafe impl Send for StreamHandle {}
unsafe impl Sync for StreamHandle {}
impl StreamHandle {
fn close(&self) {
unsafe { i2pd_sys::i2pd_stream_close(self.0) }
}
}
impl Drop for StreamHandle {
fn drop(&mut self) {
unsafe { i2pd_sys::i2pd_destroy_stream(self.0) }
}
}
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,
_router: I2pRouter,
}
impl I2pStream {
pub(crate) unsafe fn from_raw(router: I2pRouter, ptr: *mut i2pd_sys::I2pdStream) -> Self {
Self {
handle: Arc::new(StreamHandle(ptr)),
read: ReadState::Idle {
buf: Vec::with_capacity(8192),
pos: 0,
},
_router: router,
}
}
#[must_use]
pub fn is_open(&self) -> bool {
unsafe { i2pd_sys::i2pd_stream_is_open(self.handle.0) != 0 }
}
}
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();
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;
scratch.clear();
let want = out.remaining().max(1);
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);
(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.0, 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));
}
let still_open = unsafe { i2pd_sys::i2pd_stream_is_open(handle.0) != 0 };
if !still_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();
let n = unsafe { i2pd_sys::i2pd_stream_send(this.handle.0, buf.as_ptr(), buf.len()) };
if n < 0 {
Poll::Ready(Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"i2p stream send failed",
)))
} else {
Poll::Ready(Ok(usize::try_from(n).unwrap_or(0)))
}
}
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_does_not_overflow_or_panic() {
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);
}
#[test]
fn leftover_exactly_matching_destination_drains_fully_in_one_call() {
let mut buf = vec![1u8, 2, 3];
let mut pos = 0usize;
let mut dst = [0u8; 3];
let mut out = ReadBuf::new(&mut dst);
assert!(drain_leftover(&mut buf, &mut pos, &mut out));
assert_eq!(out.filled(), &[1, 2, 3]);
assert_eq!(pos, 0);
assert!(buf.is_empty());
}
}