use std::future::Future;
use std::time::Duration;
use a7n_proto::{FrameKind, decode_frame};
use prost::Message;
use quinn::{Chunk, ReadError, RecvStream};
use tokio::time::timeout;
use crate::link::LinkError;
pub(crate) trait FrameStream: Send {
fn read_chunk(
&mut self,
max: usize,
) -> impl Future<Output = Result<Option<Chunk>, ReadError>> + Send;
}
impl FrameStream for RecvStream {
async fn read_chunk(&mut self, max: usize) -> Result<Option<Chunk>, ReadError> {
self.read_chunk(max, true).await
}
}
pub(crate) enum ChunkBound {
Fixed(usize),
RemainingFrame,
}
pub(crate) async fn read<M: Message + Default>(
stream: &mut impl FrameStream,
buf: &mut Vec<u8>,
kind: FrameKind,
bound: ChunkBound,
chunk_timeout: Option<Duration>,
) -> Result<Option<M>, LinkError> {
loop {
if let Some((message, used)) = decode_frame::<M>(buf, kind)? {
buf.drain(..used);
return Ok(Some(message));
}
let max = match bound {
ChunkBound::Fixed(max) => max,
ChunkBound::RemainingFrame => kind.max_frame().saturating_sub(buf.len()).max(1),
};
let read = stream.read_chunk(max);
let chunk = match chunk_timeout.filter(|_| !buf.is_empty()) {
Some(duration) => timeout(duration, read)
.await
.map_err(|_| LinkError::Timeout)??,
None => read.await?,
};
match chunk {
Some(chunk) => buf.extend_from_slice(&chunk.bytes),
None if buf.is_empty() => return Ok(None),
None => return Err(LinkError::StreamClosed),
}
}
}
#[cfg(test)]
pub(crate) mod tests;