use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures_core::Stream;
use minarrow::{Field, Vec64};
use tokio::io::{AsyncRead, ReadBuf};
use crate::models::codecs::lightstream::LightstreamCodec;
use crate::models::decoders::limits::DecodeLimits;
use crate::models::frames::lightstream_message::{FRAME_HEADER_SIZE, LightstreamMessage};
use crate::traits::stream_buffer::StreamBuffer;
const DEFAULT_CHUNK: usize = 64 * 1024;
pub struct LightstreamReader<B: StreamBuffer = Vec64<u8>> {
source: Box<dyn AsyncRead + Unpin + Send>,
codec: LightstreamCodec<B>,
header: [u8; FRAME_HEADER_SIZE],
header_filled: usize,
payload: Vec64<u8>,
payload_target: usize,
tag: u8,
chunk_size: usize,
eof: bool,
limits: DecodeLimits,
}
impl<B: StreamBuffer + Unpin> LightstreamReader<B> {
pub fn new(
source: impl AsyncRead + Unpin + Send + 'static,
limits: Option<DecodeLimits>,
) -> Self {
let limits = limits.unwrap_or_default();
Self {
source: Box::new(source),
codec: LightstreamCodec::new(Some(limits)),
header: [0u8; FRAME_HEADER_SIZE],
header_filled: 0,
payload: Vec64::with_capacity(0),
payload_target: 0,
tag: 0,
chunk_size: DEFAULT_CHUNK,
eof: false,
limits,
}
}
pub fn register_message(&mut self, name: impl Into<String>) -> u8 {
self.codec.register_message(name)
}
pub fn register_table(&mut self, name: impl Into<String>, schema: Vec<Field>) -> u8 {
self.codec.register_table(name, schema)
}
pub fn codec(&self) -> &LightstreamCodec<B> {
&self.codec
}
}
impl<B: StreamBuffer + Unpin> Stream for LightstreamReader<B> {
type Item = io::Result<LightstreamMessage>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
loop {
if this.payload_target == 0 {
if this.header_filled < FRAME_HEADER_SIZE {
if this.eof {
if this.header_filled == 0 {
return Poll::Ready(None);
}
return Poll::Ready(Some(Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"stream ended with incomplete TLV header",
))));
}
let remaining = &mut this.header[this.header_filled..];
let mut read_buf = ReadBuf::new(remaining);
match Pin::new(&mut *this.source).poll_read(cx, &mut read_buf) {
Poll::Ready(Ok(())) => {
let n = read_buf.filled().len();
if n == 0 {
this.eof = true;
continue;
}
this.header_filled += n;
continue;
}
Poll::Ready(Err(e)) => {
this.eof = true;
return Poll::Ready(Some(Err(e)));
}
Poll::Pending => return Poll::Pending,
}
}
this.tag = this.header[0];
let payload_len =
u32::from_le_bytes(this.header[1..5].try_into().unwrap()) as usize;
if let Err(e) =
this.limits
.check(payload_len, this.limits.max_frame_bytes, "TLV frame bytes")
{
this.eof = true;
return Poll::Ready(Some(Err(e)));
}
this.payload_target = payload_len;
this.payload.clear();
if this.payload.capacity() < payload_len {
this.payload.reserve(payload_len - this.payload.capacity());
}
if payload_len == 0 {
this.header_filled = 0;
this.payload_target = 0;
let frame_payload =
std::mem::replace(&mut this.payload, Vec64::with_capacity(0));
let msg = this.codec.decode_frame(this.tag, frame_payload)?;
return Poll::Ready(Some(Ok(msg)));
}
continue;
}
let filled = this.payload.len();
let remaining = this.payload_target - filled;
if remaining > 0 {
if this.eof {
return Poll::Ready(Some(Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"stream ended with incomplete TLV payload",
))));
}
let want = remaining
.max(this.chunk_size)
.min(this.payload.capacity() - filled);
if want == 0 {
this.payload.reserve(this.chunk_size);
}
let spare = this.payload.spare_capacity_mut();
let read_len = spare.len().min(remaining);
let mut read_buf = ReadBuf::uninit(&mut spare[..read_len]);
match Pin::new(&mut *this.source).poll_read(cx, &mut read_buf) {
Poll::Ready(Ok(())) => {
let n = read_buf.filled().len();
if n == 0 {
this.eof = true;
continue;
}
unsafe { this.payload.set_len(filled + n) };
continue;
}
Poll::Ready(Err(e)) => {
this.eof = true;
return Poll::Ready(Some(Err(e)));
}
Poll::Pending => return Poll::Pending,
}
}
let frame_payload = std::mem::replace(&mut this.payload, Vec64::with_capacity(0));
this.header_filled = 0;
this.payload_target = 0;
let msg = this.codec.decode_frame(this.tag, frame_payload)?;
return Poll::Ready(Some(Ok(msg)));
}
}
}
#[cfg(test)]
mod tests {
use futures_util::StreamExt;
use super::*;
#[tokio::test]
async fn oversized_frame_length_is_refused() {
let mut header = vec![1u8];
header.extend_from_slice(&u32::MAX.to_le_bytes());
let mut reader: LightstreamReader = LightstreamReader::new(
std::io::Cursor::new(header),
Some(DecodeLimits {
max_frame_bytes: 1024,
..DecodeLimits::default()
}),
);
let err = reader.next().await.unwrap().unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidData);
assert!(err.to_string().contains("TLV frame bytes"));
}
}