use std::io;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use crate::stats::EndpointStats;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Side {
Client,
Upstream,
}
#[derive(Debug)]
pub struct ConnLog {
id: u64,
target: String,
}
impl ConnLog {
pub fn new(id: u64, target: impl Into<String>) -> Self {
Self {
id,
target: target.into(),
}
}
pub fn event(&self, body: impl AsRef<str>) {
eprintln!(
"[{}] [CONN #{}] {}",
chrono::Local::now().format("%H:%M:%S%.3f"),
self.id,
body.as_ref()
);
}
pub fn tx(&self, n: usize) {
self.event(format!("TX -> {} bytes {}", n, self.target));
}
pub fn rx(&self, n: usize) {
self.event(format!("RX <- {} bytes {}", n, self.target));
}
}
#[derive(Debug)]
pub struct Counting<S> {
inner: S,
stats: Arc<EndpointStats>,
side: Side,
log: Option<Arc<ConnLog>>,
}
impl<S> Counting<S> {
pub fn new(inner: S, stats: Arc<EndpointStats>, side: Side, log: Option<Arc<ConnLog>>) -> Self {
Self {
inner,
stats,
side,
log,
}
}
pub fn inner(&self) -> &S {
&self.inner
}
pub fn into_inner(self) -> S {
self.inner
}
#[inline]
fn on_read(&self, n: usize) {
if n == 0 {
return;
}
match self.side {
Side::Client => {
self.stats.add_egress(n as u64);
if let Some(log) = &self.log {
log.tx(n);
}
}
Side::Upstream => {
self.stats.add_ingress(n as u64);
if let Some(log) = &self.log {
log.rx(n);
}
}
}
}
#[inline]
fn on_write(&self, n: usize) {
if n == 0 {
return;
}
match self.side {
Side::Client => {
self.stats.add_ingress(n as u64);
if let Some(log) = &self.log {
log.rx(n);
}
}
Side::Upstream => {
self.stats.add_egress(n as u64);
if let Some(log) = &self.log {
log.tx(n);
}
}
}
}
}
impl<S: AsyncRead + Unpin> AsyncRead for Counting<S> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let before = buf.filled().len();
let poll = Pin::new(&mut self.inner).poll_read(cx, buf);
if let Poll::Ready(Ok(())) = &poll {
let read = buf.filled().len() - before;
self.on_read(read);
}
poll
}
}
impl<S: AsyncWrite + Unpin> AsyncWrite for Counting<S> {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let poll = Pin::new(&mut self.inner).poll_write(cx, buf);
if let Poll::Ready(Ok(n)) = &poll {
self.on_write(*n);
}
poll
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
let poll = Pin::new(&mut self.inner).poll_write_vectored(cx, bufs);
if let Poll::Ready(Ok(n)) = &poll {
self.on_write(*n);
}
poll
}
fn is_write_vectored(&self) -> bool {
self.inner.is_write_vectored()
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_flush(cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_shutdown(cx)
}
}
#[derive(Debug)]
pub struct Rewind<S> {
prefix: Option<Vec<u8>>,
offset: usize,
inner: S,
}
impl<S> Rewind<S> {
pub fn new(prefix: Vec<u8>, inner: S) -> Self {
Self {
prefix: (!prefix.is_empty()).then_some(prefix),
offset: 0,
inner,
}
}
}
impl<S: AsyncRead + Unpin> AsyncRead for Rewind<S> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let Self {
prefix,
offset,
inner,
} = &mut *self;
if let Some(bytes) = prefix {
let remaining = &bytes[*offset..];
let n = remaining.len().min(buf.remaining());
buf.put_slice(&remaining[..n]);
*offset += n;
if *offset == bytes.len() {
*prefix = None;
}
return Poll::Ready(Ok(()));
}
Pin::new(inner).poll_read(cx, buf)
}
}
impl<S: AsyncWrite + Unpin> AsyncWrite for Rewind<S> {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.inner).poll_write(cx, buf)
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.inner).poll_write_vectored(cx, bufs)
}
fn is_write_vectored(&self) -> bool {
self.inner.is_write_vectored()
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_flush(cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_shutdown(cx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::test]
async fn counting_client_side_maps_read_to_egress() {
let stats = Arc::new(EndpointStats::default());
let mut stream = Counting::new(
std::io::Cursor::new(b"hello".to_vec()),
Arc::clone(&stats),
Side::Client,
None,
);
let mut out = Vec::new();
stream.read_to_end(&mut out).await.unwrap();
assert_eq!(stats.egress(), 5);
assert_eq!(stats.ingress(), 0);
}
#[tokio::test]
async fn counting_upstream_side_maps_write_to_egress() {
let stats = Arc::new(EndpointStats::default());
let mut stream = Counting::new(Vec::new(), Arc::clone(&stats), Side::Upstream, None);
stream.write_all(b"abcd").await.unwrap();
assert_eq!(stats.egress(), 4);
assert_eq!(stats.ingress(), 0);
}
#[tokio::test]
async fn rewind_replays_prefix_then_stream() {
let mut stream = Rewind::new(b"head".to_vec(), std::io::Cursor::new(b"tail".to_vec()));
let mut out = Vec::new();
stream.read_to_end(&mut out).await.unwrap();
assert_eq!(out, b"headtail");
}
}