leyline-http 0.1.5

HTTP client that impersonates Chrome, Firefox, and Safari on the wire: TLS (JA3/JA4), HTTP/2, and HTTP/3 fingerprints
Documentation
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::sync::oneshot;

use crate::h2::error::H2Error;
use crate::h2::frame::Frame;

use super::*;

enum Event {
    Written(Result<(), H2Error>),
    Read(Result<Option<Frame>, H2Error>),
    Command(Option<DriverCommand>),
    BodyChunk(BodyChunkIn),
    Ping(oneshot::Sender<()>),
    Sweep,
    FlushStalled,
}

impl<T: AsyncRead + AsyncWrite + Unpin + Send + 'static> Driver<T> {
    pub(super) async fn wait_event(
        &mut self,
        sweep_tick: &mut tokio::time::Interval,
        flush_tick: &mut tokio::time::Interval,
    ) -> Result<Option<bool>, H2Error> {
        let event = self.next_event(sweep_tick, flush_tick).await;
        self.on_event(event).await
    }

    async fn next_event(
        &mut self,
        sweep_tick: &mut tokio::time::Interval,
        flush_tick: &mut tokio::time::Interval,
    ) -> Event {
        let writing = self.writer.has_output();
        let reading = !self.writer.saturated();
        let accepting = self.accepting_commands();
        let taking_body = !self.writer.congested();
        let stalled = self.has_stalled();
        tokio::select! {
            biased;
            _ = sweep_tick.tick() => Event::Sweep,
            written = self.writer.write_some(), if writing => Event::Written(written),
            frame = self.reader.next(), if reading => Event::Read(frame),
            cmd = self.command_rx.recv(), if accepting => Event::Command(cmd),
            Some(chunk) = self.body_chunk_rx.recv(), if taking_body => Event::BodyChunk(chunk),
            Some(ack_tx) = self.ping_rx.recv() => Event::Ping(ack_tx),
            _ = flush_tick.tick(), if stalled => Event::FlushStalled,
        }
    }

    async fn on_event(&mut self, event: Event) -> Result<Option<bool>, H2Error> {
        match event {
            Event::Written(written) => self.output.wrote(written)?,
            Event::Read(frame) => {
                return Ok(self.on_read(frame).await?.is_continue().then_some(false));
            }
            Event::Command(cmd) => self.on_command_received(cmd).await?,
            Event::BodyChunk(chunk) => self.on_body_chunk(chunk).await?,
            Event::Ping(ack_tx) => self.on_command(DriverCommand::Ping { ack_tx }).await?,
            Event::Sweep => return Ok(Some(true)),
            Event::FlushStalled => self.flush_stalled().await?,
        }
        Ok(Some(false))
    }
}