use std::io;
use std::pin::Pin;
use std::sync::atomic::{AtomicU8, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use crate::phases::PhaseClock;
const IDLE: u8 = 0;
const WRITING: u8 = 1;
const ABORTED: u8 = 2;
#[derive(Debug, Clone, Default)]
pub struct SentTracker(Arc<Tracker>);
#[derive(Debug, Default)]
struct Tracker {
state: AtomicU8,
bytes: AtomicU64,
}
impl SentTracker {
pub fn new() -> Self {
Self::default()
}
pub fn begin_write(&self) -> bool {
match self
.0
.state
.compare_exchange(IDLE, WRITING, Ordering::SeqCst, Ordering::SeqCst)
{
Ok(_) => true,
Err(s) => s == WRITING,
}
}
pub fn try_abort(&self) -> bool {
match self
.0
.state
.compare_exchange(IDLE, ABORTED, Ordering::SeqCst, Ordering::SeqCst)
{
Ok(_) => true,
Err(s) => s == ABORTED,
}
}
pub fn is_aborted(&self) -> bool {
self.0.state.load(Ordering::SeqCst) == ABORTED
}
pub fn may_have_sent(&self) -> bool {
self.0.state.load(Ordering::SeqCst) == WRITING
}
pub fn bytes(&self) -> u64 {
self.0.bytes.load(Ordering::SeqCst)
}
fn add(&self, n: usize) {
self.0.bytes.fetch_add(n as u64, Ordering::SeqCst);
}
}
#[derive(Debug, Clone, Default)]
pub struct IoHooks {
pub sent: SentTracker,
pub clock: Option<PhaseClock>,
}
pub type SharedHooks = Arc<Mutex<IoHooks>>;
pub struct TrackedIo<S> {
inner: S,
hooks: SharedHooks,
}
fn hooks_of(h: &SharedHooks) -> IoHooks {
h.lock().unwrap_or_else(|e| e.into_inner()).clone()
}
impl<S> TrackedIo<S> {
pub fn new(inner: S, sent: SentTracker) -> Self {
TrackedIo {
inner,
hooks: Arc::new(Mutex::new(IoHooks { sent, clock: None })),
}
}
pub fn shared(inner: S, hooks: SharedHooks) -> Self {
TrackedIo { inner, hooks }
}
pub fn with_clock(self, clock: Option<PhaseClock>) -> Self {
self.hooks.lock().unwrap_or_else(|e| e.into_inner()).clock = clock;
self
}
pub fn tracker(&self) -> SentTracker {
hooks_of(&self.hooks).sent
}
}
fn aborted() -> io::Error {
io::Error::new(
io::ErrorKind::ConnectionAborted,
"the attempt stopped before its request was sent",
)
}
impl<S: AsyncRead + Unpin> AsyncRead for TrackedIo<S> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let before = buf.filled().len();
let r = Pin::new(&mut self.inner).poll_read(cx, buf);
if let Poll::Ready(Ok(())) = &r
&& buf.filled().len() > before
&& let Some(c) = hooks_of(&self.hooks).clock
{
c.response_byte();
}
r
}
}
impl<S: AsyncWrite + Unpin> AsyncWrite for TrackedIo<S> {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
let sent = hooks_of(&self.hooks).sent;
if !sent.begin_write() {
return Poll::Ready(Err(aborted()));
}
let r = Pin::new(&mut self.inner).poll_write(cx, buf);
if let Poll::Ready(Ok(n)) = &r {
sent.add(*n);
}
r
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
if bufs.iter().all(|b| b.is_empty()) {
return Poll::Ready(Ok(0));
}
let sent = hooks_of(&self.hooks).sent;
if !sent.begin_write() {
return Poll::Ready(Err(aborted()));
}
let r = Pin::new(&mut self.inner).poll_write_vectored(cx, bufs);
if let Poll::Ready(Ok(n)) = &r {
sent.add(*n);
}
r
}
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<()>> {
let r = Pin::new(&mut self.inner).poll_flush(cx);
if let Poll::Ready(Ok(())) = &r
&& let Some(c) = hooks_of(&self.hooks).clock
{
c.flushed();
}
r
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_shutdown(cx)
}
}
pub fn digest_bytes(bytes: &[u8]) -> String {
use sha2::{Digest, Sha256};
let d = Sha256::digest(bytes);
let mut s = String::with_capacity(71);
s.push_str("sha256:");
for b in d {
s.push_str(&format!("{b:02x}"));
}
s
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::io::AsyncWriteExt;
#[test]
fn an_abort_before_any_write_forbids_every_write() {
let t = SentTracker::new();
assert!(!t.may_have_sent());
assert!(t.try_abort());
assert!(t.try_abort(), "idempotent");
assert!(t.is_aborted());
assert!(!t.begin_write());
assert!(!t.may_have_sent());
}
#[test]
fn a_write_that_began_cannot_be_unsent() {
let t = SentTracker::new();
assert!(t.begin_write());
assert!(t.begin_write());
assert!(!t.try_abort());
assert!(t.may_have_sent());
}
#[tokio::test(flavor = "current_thread")]
async fn tracked_writes_are_counted_and_refused_after_abort() {
let t = SentTracker::new();
let mut io = TrackedIo::new(Vec::<u8>::new(), t.clone());
io.write_all(b"hello").await.unwrap();
assert_eq!(t.bytes(), 5);
assert!(t.may_have_sent());
let t2 = SentTracker::new();
assert!(t2.try_abort());
let mut io = TrackedIo::new(Vec::<u8>::new(), t2.clone());
assert!(io.write_all(b"x").await.is_err());
assert_eq!(t2.bytes(), 0);
}
#[test]
fn digests_are_tagged_hex() {
let d = digest_bytes(b"");
assert_eq!(
d,
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
}
}