use bytes::{Buf, BufMut, Bytes, BytesMut};
use log::{debug, trace, warn};
use pingora_error::{
Error,
ErrorType::{self, *},
OrErr, Result,
};
use std::fmt::Debug;
use std::future::Future;
use std::pin::{pin, Pin};
use std::task::{ready, Context, Poll};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use crate::utils::BufRef;
const BODY_BUFFER_SIZE: usize = 1024 * 64;
const PARTIAL_CHUNK_HEAD_LIMIT: usize = 1024 * 8;
const TRAILER_SIZE_LIMIT: usize = 1024 * 64;
const LAST_CHUNK: &[u8; 5] = &[b'0', CR, LF, CR, LF];
const CR: u8 = b'\r';
const LF: u8 = b'\n';
const CRLF: &[u8; 2] = &[CR, LF];
const TRAILERS_END: &[u8; 4] = &[CR, LF, CR, LF];
pub const INVALID_CHUNK: ErrorType = ErrorType::new("InvalidChunk");
pub const INVALID_TRAILER_END: ErrorType = ErrorType::new("InvalidTrailerEnd");
pub const PREMATURE_BODY_END: ErrorType = ErrorType::new("PrematureBodyEnd");
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParseState {
ToStart,
Complete(usize),
Partial(usize, usize),
Chunked(usize, usize, usize, usize),
ChunkedFinal(usize, usize, usize, u8),
Done(usize),
UntilClose(usize),
}
type PS = ParseState;
impl ParseState {
pub fn finish(&self, additional_bytes: usize) -> Self {
match self {
PS::Partial(read, to_read) => PS::Complete(read + to_read),
PS::Chunked(read, _, _, _) => PS::Complete(read + additional_bytes),
PS::ChunkedFinal(read, _, _, _) => PS::Complete(read + additional_bytes),
PS::UntilClose(read) => PS::Complete(read + additional_bytes),
_ => self.clone(),
}
}
pub fn done(&self, additional_bytes: usize) -> Self {
match self {
PS::Partial(read, _) => PS::Done(read + additional_bytes),
PS::Chunked(read, _, _, _) => PS::Done(read + additional_bytes),
PS::ChunkedFinal(read, _, _, _) => PS::Done(read + additional_bytes),
PS::UntilClose(read) => PS::Done(read + additional_bytes),
_ => self.clone(),
}
}
pub fn read_final_chunk(&self, remaining_buf_size: usize) -> Self {
match self {
PS::Chunked(read, _, _, _) => {
PS::ChunkedFinal(*read, 0, remaining_buf_size, 2)
}
PS::ChunkedFinal(..) => panic!("already read final chunk"),
_ => self.clone(),
}
}
pub fn partial_chunk(&self, bytes_read: usize, bytes_to_read: usize) -> Self {
match self {
PS::Chunked(read, _, _, _) => PS::Chunked(read + bytes_read, 0, 0, bytes_to_read),
PS::ChunkedFinal(..) => panic!("chunked transactions not applicable after final chunk"),
_ => self.clone(),
}
}
pub fn multi_chunk(&self, bytes_read: usize, buf_start_index: usize) -> Self {
match self {
PS::Chunked(read, _, buf_end, _) => {
PS::Chunked(read + bytes_read, buf_start_index, *buf_end, 0)
}
PS::ChunkedFinal(..) => panic!("chunked transactions not applicable after final chunk"),
_ => self.clone(),
}
}
pub fn partial_chunk_head(&self, head_end: usize, head_size: usize) -> Self {
match self {
PS::Chunked(read, _, _, _) => PS::Chunked(*read, 0, head_end, head_size),
PS::ChunkedFinal(..) => panic!("chunked transactions not applicable after final chunk"),
_ => self.clone(),
}
}
pub fn new_buf(&self, buf_end: usize) -> Self {
match self {
PS::Chunked(read, _, _, _) => PS::Chunked(*read, 0, buf_end, 0),
PS::ChunkedFinal(..) => panic!("chunked transactions not applicable after final chunk"),
_ => self.clone(),
}
}
}
pub struct BodyReader {
pub body_state: ParseState,
pub body_buf: Option<BytesMut>,
pub body_buf_size: usize,
rewind_buf_len: usize,
upstream: bool,
body_buf_overread: Option<BytesMut>,
}
impl BodyReader {
pub fn new(upstream: bool) -> Self {
BodyReader {
body_state: PS::ToStart,
body_buf: None,
body_buf_size: BODY_BUFFER_SIZE,
rewind_buf_len: 0,
upstream,
body_buf_overread: None,
}
}
pub fn need_init(&self) -> bool {
matches!(self.body_state, PS::ToStart)
}
pub fn reinit(&mut self) {
self.body_state = PS::ToStart;
}
fn prepare_buf(&mut self, buf_to_rewind: &[u8]) {
let mut body_buf = BytesMut::with_capacity(self.body_buf_size.max(buf_to_rewind.len()));
if !buf_to_rewind.is_empty() {
self.rewind_buf_len = buf_to_rewind.len();
body_buf.put_slice(buf_to_rewind);
}
self.body_buf = Some(body_buf);
}
fn prepare_owned_buf(&mut self, buf_to_rewind: BytesMut) {
self.rewind_buf_len = buf_to_rewind.len();
self.body_buf = Some(buf_to_rewind);
}
fn prepare_buf_for_io(&mut self, read_size: usize) -> &mut BytesMut {
let body_buf = self.body_buf.get_or_insert_default();
body_buf.clear();
body_buf.reserve(read_size);
body_buf
}
fn reserve_buf_for_io(&mut self) {
let body_buf = self.body_buf.get_or_insert_default();
if body_buf.len() < self.body_buf_size {
body_buf.reserve(self.body_buf_size - body_buf.len());
}
}
pub fn init_chunked(&mut self, buf_to_rewind: &[u8]) {
self.body_state = PS::Chunked(0, 0, 0, 0);
self.prepare_buf(buf_to_rewind);
}
pub fn init_chunked_owned(&mut self, buf_to_rewind: BytesMut) {
self.body_state = PS::Chunked(0, 0, 0, 0);
self.prepare_owned_buf(buf_to_rewind);
}
pub fn init_content_length(&mut self, cl: usize, buf_to_rewind: &[u8]) {
match cl {
0 => {
self.body_state = PS::Complete(0);
if !buf_to_rewind.is_empty() {
let mut overread = BytesMut::with_capacity(buf_to_rewind.len());
overread.put_slice(buf_to_rewind);
self.body_buf_overread = Some(overread);
}
}
_ => {
self.prepare_buf(buf_to_rewind);
self.body_state = PS::Partial(0, cl);
}
}
}
pub fn init_content_length_owned(&mut self, cl: usize, buf_to_rewind: BytesMut) {
match cl {
0 => {
self.body_state = PS::Complete(0);
self.body_buf_overread = (!buf_to_rewind.is_empty()).then_some(buf_to_rewind);
}
_ => {
self.prepare_owned_buf(buf_to_rewind);
self.body_state = PS::Partial(0, cl);
}
}
}
pub fn init_close_delimited(&mut self, buf_to_rewind: &[u8]) {
self.prepare_buf(buf_to_rewind);
self.body_state = PS::UntilClose(0);
}
pub fn init_close_delimited_owned(&mut self, buf_to_rewind: BytesMut) {
self.prepare_owned_buf(buf_to_rewind);
self.body_state = PS::UntilClose(0);
}
pub fn convert_to_close_delimited(&mut self) {
if matches!(self.body_state, PS::UntilClose(_)) {
return;
}
if self.rewind_buf_len == 0 {
let extra = self.body_buf_overread.take().unwrap_or_default();
self.prepare_owned_buf(extra);
} self.body_state = PS::UntilClose(0);
}
pub fn get_body(&self, buf_ref: &BufRef) -> &[u8] {
buf_ref.get(self.body_buf.as_ref().unwrap())
}
#[allow(dead_code)]
pub fn get_body_overread(&self) -> Option<&[u8]> {
self.body_buf_overread.as_deref()
}
pub fn has_bytes_overread(&self) -> bool {
self.get_body_overread().is_some_and(|b| !b.is_empty())
}
pub fn take_body_overread(&mut self) -> Option<BytesMut> {
self.body_buf_overread.take()
}
pub fn push_body_overread(&mut self, bytes: &[u8]) {
if bytes.is_empty() {
return;
}
match self.body_buf_overread.as_mut() {
Some(buf) => buf.extend_from_slice(bytes),
None => {
let mut buf = BytesMut::with_capacity(bytes.len());
buf.extend_from_slice(bytes);
self.body_buf_overread = Some(buf);
}
}
}
pub fn body_done(&self) -> bool {
matches!(self.body_state, PS::Complete(_) | PS::Done(_))
}
pub fn body_complete(&self) -> bool {
matches!(self.body_state, PS::Complete(_))
}
pub fn body_empty(&self) -> bool {
self.body_state == PS::Complete(0)
}
fn finish_body_buf(&mut self, end_of_body: usize, total_read: usize) {
let body_buf_mut = self.body_buf.as_mut().expect("must have read body buf");
body_buf_mut.truncate(total_read);
let overread_bytes = body_buf_mut.split_off(end_of_body);
self.body_buf_overread = (!overread_bytes.is_empty()).then_some(overread_bytes);
}
pub async fn read_body<S>(&mut self, stream: &mut S) -> Result<Option<BufRef>>
where
S: AsyncRead + Unpin + Send,
{
match self.body_state {
PS::Complete(_) => Ok(None),
PS::Done(_) => Ok(None),
PS::Partial(_, _) => self.do_read_body(stream).await,
PS::Chunked(..) => self.do_read_chunked_body(stream).await,
PS::ChunkedFinal(..) => self.do_read_chunked_body_final(stream).await,
PS::UntilClose(_) => self.do_read_body_until_closed(stream).await,
PS::ToStart => panic!("need to init BodyReader first"),
}
}
pub async fn do_read_body<S>(&mut self, stream: &mut S) -> Result<Option<BufRef>>
where
S: AsyncRead + Unpin + Send,
{
let mut n = self.rewind_buf_len;
self.rewind_buf_len = 0;
let read_size = match self.body_state {
PS::Partial(_, remaining) if !self.upstream => remaining.min(self.body_buf_size),
_ => self.body_buf_size,
};
if n == 0 {
let body_buf = self.prepare_buf_for_io(read_size);
n = stream
.take(read_size as u64)
.read_buf(body_buf)
.await
.or_err(ReadError, "when reading body")?;
}
match self.body_state {
PS::Partial(read, to_read) => {
debug!(
"BodyReader body_state: {:?}, read data from IO: {n}",
self.body_state
);
if n == 0 {
self.body_state = PS::Done(read);
Error::e_explain(ConnectionClosed, format!(
"Peer prematurely closed connection with {} bytes of body remaining to read",
to_read
))
} else if n >= to_read {
if n > to_read {
let extra = n - to_read;
if self.upstream {
warn!("Peer sent more data than expected: extra {extra} bytes");
} else {
debug!("Peer sent {extra} bytes past the end of the request body");
}
}
self.body_state = PS::Complete(read + to_read);
self.finish_body_buf(to_read, n);
Ok(Some(BufRef::new(0, to_read)))
} else {
self.body_state = PS::Partial(read + n, to_read - n);
Ok(Some(BufRef::new(0, n)))
}
}
_ => panic!("wrong body state: {:?}", self.body_state),
}
}
pub async fn do_read_body_until_closed<S>(&mut self, stream: &mut S) -> Result<Option<BufRef>>
where
S: AsyncRead + Unpin + Send,
{
let mut n = self.rewind_buf_len;
self.rewind_buf_len = 0; if n == 0 {
let read_size = self.body_buf_size;
let body_buf = self.prepare_buf_for_io(read_size);
n = stream
.take(read_size as u64)
.read_buf(body_buf)
.await
.or_err(ReadError, "when reading body")?;
}
match self.body_state {
PS::UntilClose(read) => {
if n == 0 {
self.body_state = PS::Complete(read);
Ok(None)
} else {
self.body_state = PS::UntilClose(read + n);
Ok(Some(BufRef::new(0, n)))
}
}
_ => panic!("wrong body state: {:?}", self.body_state),
}
}
pub async fn do_read_chunked_body<S>(&mut self, stream: &mut S) -> Result<Option<BufRef>>
where
S: AsyncRead + Unpin + Send,
{
match self.body_state {
PS::Chunked(
total_read,
existing_buf_start,
mut existing_buf_end,
mut expecting_from_io,
) => {
if existing_buf_start == 0 {
if existing_buf_end == 0 {
existing_buf_end = self.rewind_buf_len;
self.rewind_buf_len = 0; if existing_buf_end == 0 {
let read_size = self.body_buf_size;
let body_buf = self.prepare_buf_for_io(read_size);
existing_buf_end = stream
.take(read_size as u64)
.read_buf(body_buf)
.await
.or_err(ReadError, "when reading body")?;
}
} else {
self.reserve_buf_for_io();
let body_buf = self
.body_buf
.as_mut()
.expect("body buf exists once a partial chunk head was buffered");
let partial_head_start = existing_buf_end - expecting_from_io;
if partial_head_start != 0 {
body_buf.copy_within(partial_head_start..existing_buf_end, 0);
}
body_buf.truncate(expecting_from_io);
self.body_state = self
.body_state
.partial_chunk_head(expecting_from_io, expecting_from_io);
let new_bytes = stream
.take((self.body_buf_size - expecting_from_io) as u64)
.read_buf(body_buf)
.await
.or_err(ReadError, "when reading body")?;
if new_bytes == 0 {
self.body_state = self.body_state.done(0);
return Error::e_explain(
ConnectionClosed,
format!(
"Connection prematurely closed without the termination chunk \
(partial chunk head), read {total_read} bytes"
),
);
}
existing_buf_end = expecting_from_io + new_bytes;
expecting_from_io = 0;
}
self.body_state = self.body_state.new_buf(existing_buf_end);
}
if existing_buf_end == 0 {
self.body_state = self.body_state.done(0);
Error::e_explain(
ConnectionClosed,
format!(
"Connection prematurely closed without the termination chunk, \
read {total_read} bytes"
),
)
} else {
if expecting_from_io > 0 {
let body_buf = self.body_buf.as_ref().unwrap();
trace!(
"partial chunk payload, expecting_from_io: {}, \
existing_buf_end {}, buf: {:?}",
expecting_from_io,
existing_buf_end,
self.body_buf.as_ref().unwrap()[..existing_buf_end].escape_ascii()
);
if expecting_from_io >= existing_buf_end + 2 {
self.body_state = self.body_state.partial_chunk(
existing_buf_end,
expecting_from_io - existing_buf_end,
);
return Ok(Some(BufRef::new(0, existing_buf_end)));
}
let payload_size = expecting_from_io.saturating_sub(2);
let need_lf_only = expecting_from_io == 1; if expecting_from_io > existing_buf_end {
if payload_size < existing_buf_end {
Self::validate_crlf(
&mut self.body_state,
&body_buf[payload_size..existing_buf_end],
need_lf_only,
false,
)?;
}
} else {
assert!(Self::validate_crlf(
&mut self.body_state,
&body_buf[payload_size..expecting_from_io],
need_lf_only,
false,
)?);
}
if expecting_from_io >= existing_buf_end {
self.body_state = self
.body_state
.partial_chunk(payload_size, expecting_from_io - existing_buf_end);
return Ok(Some(BufRef::new(0, payload_size)));
}
self.body_state =
self.body_state.multi_chunk(payload_size, expecting_from_io);
return Ok(Some(BufRef::new(0, payload_size)));
}
let (buf_res, last_chunk_size_end) =
self.parse_chunked_buf(existing_buf_start, existing_buf_end)?;
if buf_res.is_some() {
if let Some(idx) = last_chunk_size_end {
let mut body_buf = self
.body_buf
.take()
.expect("body buf exists once a chunk was parsed out of it");
trace!(
"last chunk size end buf {:?}",
body_buf[..existing_buf_end].escape_ascii(),
);
body_buf.truncate(existing_buf_end);
self.body_buf = Some(body_buf.split_off(idx));
}
}
Ok(buf_res)
}
}
_ => panic!("wrong body state: {:?}", self.body_state),
}
}
fn parse_chunked_buf(
&mut self,
buf_index_start: usize,
buf_index_end: usize,
) -> Result<(Option<BufRef>, Option<usize>)> {
let buf = &self.body_buf.as_ref().unwrap()[buf_index_start..buf_index_end];
let chunk_status = httparse::parse_chunk_size(buf);
match chunk_status {
Ok(status) => {
match status {
httparse::Status::Complete((payload_index, chunk_size)) => {
trace!(
"Got size {chunk_size}, payload_index: {payload_index}, chunk: {:?}",
String::from_utf8_lossy(buf).escape_default(),
);
let chunk_size = match usize::try_from(chunk_size) {
Ok(chunk_size) => chunk_size,
Err(_) => {
self.body_state = self.body_state.done(0);
return Error::e_explain(
INVALID_CHUNK,
"chunk size overflows usize",
);
}
};
if chunk_size == 0 {
let Some(chunk_end_index) = payload_index.checked_add(2) else {
self.body_state = self.body_state.done(0);
return Error::e_explain(
INVALID_CHUNK,
"chunk terminator overflows buffer index",
);
};
return if chunk_end_index <= buf.len()
&& buf[payload_index..chunk_end_index] == CRLF[..]
{
self.body_state = self.body_state.finish(0);
self.finish_body_buf(
buf_index_start + chunk_end_index,
buf_index_end,
);
Ok((None, Some(buf_index_start + payload_index)))
} else {
self.body_state = self.body_state.read_final_chunk(
buf_index_end - (buf_index_start + payload_index),
);
Ok((
Some(BufRef::new(0, 0)),
Some(buf_index_start + payload_index),
))
};
}
let Some(data_end_index) = payload_index.checked_add(chunk_size) else {
self.body_state = self.body_state.done(0);
return Error::e_explain(
INVALID_CHUNK,
"chunk size overflows buffer index",
);
};
let Some(chunk_end_index) = data_end_index.checked_add(2) else {
self.body_state = self.body_state.done(0);
return Error::e_explain(
INVALID_CHUNK,
"chunk terminator overflows buffer index",
);
};
if chunk_end_index >= buf.len() {
let actual_size = if data_end_index > buf.len() {
buf.len() - payload_index
} else {
chunk_size
};
let crlf_start = chunk_end_index.saturating_sub(2);
if crlf_start < buf.len() {
Self::validate_crlf(
&mut self.body_state,
&buf[crlf_start..],
false,
false,
)?;
}
self.body_state = self
.body_state
.partial_chunk(actual_size, chunk_end_index - buf.len());
return Ok((
Some(BufRef::new(buf_index_start + payload_index, actual_size)),
None,
));
}
assert!(Self::validate_crlf(
&mut self.body_state,
&buf[data_end_index..chunk_end_index],
false,
false,
)?);
self.body_state = self
.body_state
.multi_chunk(chunk_size, buf_index_start + chunk_end_index);
Ok((
Some(BufRef::new(buf_index_start + payload_index, chunk_size)),
None,
))
}
httparse::Status::Partial => {
if buf.len() > PARTIAL_CHUNK_HEAD_LIMIT {
self.body_state = self.body_state.done(0);
Error::e_explain(INVALID_CHUNK, "Chunk ext over limit")
} else {
self.body_state =
self.body_state.partial_chunk_head(buf_index_end, buf.len());
Ok((Some(BufRef::new(0, 0)), None))
}
}
}
}
Err(e) => {
let context = format!("Invalid chunked encoding: {e:?}");
debug!(
"{context}, {:?}",
String::from_utf8_lossy(buf).escape_default()
);
self.body_state = self.body_state.done(0);
Error::e_explain(INVALID_CHUNK, context)
}
}
}
pub async fn do_read_chunked_body_final<S>(&mut self, stream: &mut S) -> Result<Option<BufRef>>
where
S: AsyncRead + Unpin + Send,
{
match self.body_state {
PS::ChunkedFinal(read, trailers_read, existing_buf_end, end_read) => {
let n = if existing_buf_end != 0 {
self.body_state = PS::ChunkedFinal(read, trailers_read, 0, end_read);
existing_buf_end
} else {
let read_size = self.body_buf_size;
let body_buf = self.prepare_buf_for_io(read_size);
stream
.take(read_size as u64)
.read_buf(body_buf)
.await
.or_err(ReadError, "when reading trailers end")?
};
if n == 0 {
self.body_state = PS::Done(read);
return Error::e_explain(
ConnectionClosed,
format!(
"Connection prematurely closed without the termination chunk, \
read {read} bytes, {trailers_read} trailer bytes"
),
);
}
let buf = &self
.body_buf
.as_deref()
.expect("body buf is initialized before reading trailers")[..n];
let mut start = 0;
while start < n {
let (trailers_read, end_read) = match self.body_state {
PS::ChunkedFinal(_, new_trailers_read, _, new_end_read) => {
(new_trailers_read, new_end_read)
}
_ => unreachable!(),
};
let mut buf = &buf[start..n];
trace!(
"Parsing chunk end for buf {:?}",
String::from_utf8_lossy(buf).escape_default(),
);
if end_read == 0 {
let (trailers_read, no_crlf) =
if let Some(p) = buf.iter().position(|b| *b == CR || *b == LF) {
buf = &buf[p..];
start += p;
(trailers_read + p, false)
} else {
(trailers_read + (n - start), true)
};
if trailers_read > TRAILER_SIZE_LIMIT {
self.body_state = self.body_state.done(0);
return Error::e_explain(
INVALID_TRAILER_END,
"Trailer size over limit",
);
}
self.body_state = PS::ChunkedFinal(read, trailers_read, 0, 0);
if no_crlf {
break;
}
}
match Self::parse_trailers_end(&mut self.body_state, buf)? {
TrailersEndParseState::NotEnd(next_parse_index) => {
trace!(
"Parsing chunk end for buf {:?}, resume at {next_parse_index}",
String::from_utf8_lossy(buf).escape_default(),
);
start += next_parse_index;
}
TrailersEndParseState::Complete(end_idx) => {
trace!(
"Parsing chunk end for buf {:?}, finished at {end_idx}",
String::from_utf8_lossy(buf).escape_default(),
);
self.finish_body_buf(start + end_idx, n);
return Ok(None);
}
}
}
}
_ => panic!("wrong body state: {:?}", self.body_state),
}
Ok(Some(BufRef(0, 0)))
}
fn parse_trailers_end(
body_state: &mut ParseState,
buf: &[u8],
) -> Result<TrailersEndParseState> {
assert!(!buf.is_empty(), "parse_trailers_end given empty buffer");
match body_state.clone() {
PS::ChunkedFinal(read, trailers_read, _, end_read) => {
let end_read = end_read as usize;
assert!(end_read < TRAILERS_END.len());
let to_read = std::cmp::min(buf.len(), TRAILERS_END.len() - end_read);
let buf = &buf[..to_read];
if end_read % 2 == 0 && buf[0] != CR && buf[0] != LF {
trace!(
"parse trailers end {:?}, not CRLF sequence",
String::from_utf8_lossy(buf).escape_default(),
);
*body_state = PS::ChunkedFinal(read, trailers_read + end_read, 0, 0);
return Ok(TrailersEndParseState::NotEnd(0));
}
let next_parse_index = match end_read {
0 | 2 => {
if Self::validate_crlf(body_state, buf, false, true)? {
2
} else {
1
}
}
1 | 3 => {
assert!(Self::validate_crlf(body_state, buf, true, true)?);
1
}
_ => unreachable!(),
};
let next_end_read = end_read + next_parse_index;
let finished = next_end_read == TRAILERS_END.len();
if finished {
trace!(
"parse trailers end {:?}, complete {next_end_read}",
String::from_utf8_lossy(buf).escape_default(),
);
*body_state = PS::Complete(read);
Ok(TrailersEndParseState::Complete(next_parse_index))
} else {
trace!(
"parse trailers end {:?}, resume at {next_parse_index}",
String::from_utf8_lossy(buf).escape_default(),
);
*body_state =
PS::ChunkedFinal(read, trailers_read, 0, next_end_read.try_into().unwrap());
Ok(TrailersEndParseState::NotEnd(next_parse_index))
}
}
_ => panic!("wrong body state: {:?}", body_state),
}
}
fn validate_crlf(
body_state: &mut ParseState,
buf: &[u8],
need_lf_only: bool,
for_trailer_end: bool,
) -> Result<bool> {
let etype = if for_trailer_end {
INVALID_TRAILER_END
} else {
INVALID_CHUNK
};
if need_lf_only {
if buf.is_empty() {
Ok(false)
} else {
let b = &buf[..1];
if b == b"\n" {
Ok(true)
} else {
*body_state = body_state.done(0);
Error::e_explain(
etype,
format!(
"Invalid chunked encoding: {} was not LF",
String::from_utf8_lossy(b).escape_default(),
),
)
}
}
} else {
match buf.len() {
0 => Ok(false),
1 => {
let b = &buf[..1];
if b == b"\r" {
Ok(false)
} else {
*body_state = body_state.done(0);
Error::e_explain(
etype,
format!(
"Invalid chunked encoding: {} was not CR",
String::from_utf8_lossy(b).escape_default(),
),
)
}
}
_ => {
let b = &buf[..2];
if b == b"\r\n" {
Ok(true)
} else {
*body_state = body_state.done(0);
Error::e_explain(
etype,
format!(
"Invalid chunked encoding: {} was not CRLF",
String::from_utf8_lossy(b).escape_default(),
),
)
}
}
}
}
}
}
pub enum TrailersEndParseState {
NotEnd(usize), Complete(usize), }
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BodyMode {
ToSelect,
ContentLength(usize, usize), ChunkedEncoding(usize), UntilClose(usize), Complete(usize), }
type BM = BodyMode;
impl BodyMode {
fn expect_content_length(&self) -> (usize, usize) {
match self {
BodyMode::ContentLength(total, written) => (*total, *written),
_ => panic!("wrong body mode: expected ContentLength, got {:?}", self),
}
}
fn expect_chunked(&self) -> usize {
match self {
BodyMode::ChunkedEncoding(written) => *written,
_ => panic!("wrong body mode: expected ChunkedEncoding, got {:?}", self),
}
}
fn expect_until_close(&self) -> usize {
match self {
BodyMode::UntilClose(written) => *written,
_ => panic!("wrong body mode: expected UntilClose, got {:?}", self),
}
}
}
type ChunkedBuf = bytes::buf::Chain<bytes::buf::Chain<Bytes, Bytes>, &'static [u8]>;
enum WriteBuf<C = ChunkedBuf> {
Simple(Bytes),
Chained(C),
}
impl<C: Buf> Buf for WriteBuf<C> {
fn remaining(&self) -> usize {
match self {
WriteBuf::Simple(b) => b.remaining(),
WriteBuf::Chained(c) => c.remaining(),
}
}
fn chunk(&self) -> &[u8] {
match self {
WriteBuf::Simple(b) => b.chunk(),
WriteBuf::Chained(c) => c.chunk(),
}
}
fn advance(&mut self, cnt: usize) {
match self {
WriteBuf::Simple(b) => b.advance(cnt),
WriteBuf::Chained(c) => c.advance(cnt),
}
}
fn chunks_vectored<'a>(&'a self, dst: &mut [std::io::IoSlice<'a>]) -> usize {
match self {
WriteBuf::Simple(b) => b.chunks_vectored(dst),
WriteBuf::Chained(c) => c.chunks_vectored(dst),
}
}
}
enum WriteState {
Idle,
Writing(usize, WriteBuf<ChunkedBuf>),
Flushing(usize),
Done(usize),
TimedOut,
}
impl std::fmt::Debug for WriteState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WriteState::Idle => write!(f, "Idle"),
WriteState::Writing(size, _buf) => {
write!(f, "Writing(size: {})", size)
}
WriteState::Flushing(size) => write!(f, "Flushing(size: {})", size),
WriteState::Done(size) => write!(f, "Done(size: {})", size),
WriteState::TimedOut => write!(f, "TimedOut"),
}
}
}
enum FinishWriteState {
NotStarted,
Idle,
WritingLastChunk(WriteBuf),
Flushing,
Done,
}
impl std::fmt::Debug for FinishWriteState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FinishWriteState::NotStarted => write!(f, "NotStarted"),
FinishWriteState::Idle => write!(f, "Idle"),
FinishWriteState::WritingLastChunk(_) => write!(f, "WritingLastChunk"),
FinishWriteState::Flushing => write!(f, "Flushing"),
FinishWriteState::Done => write!(f, "Done"),
}
}
}
struct SendBodyState {
pending_bytes: Option<Bytes>,
write_state: WriteState,
timeout_duration: Option<std::time::Duration>,
timeout_fut: Option<std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + Sync>>>,
}
impl std::fmt::Debug for SendBodyState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SendBodyState")
.field("pending_bytes", &self.pending_bytes)
.field("write_state", &self.write_state)
.field("timeout_duration", &self.timeout_duration)
.field(
"timeout_fut",
&self.timeout_fut.as_ref().map(|_| "Some(Future)"),
)
.finish()
}
}
impl SendBodyState {
fn new() -> Self {
SendBodyState {
pending_bytes: None,
write_state: WriteState::Idle,
timeout_duration: None,
timeout_fut: None,
}
}
}
pub struct BodyWriter {
pub body_mode: BodyMode,
send_body_state: Box<SendBodyState>,
send_finish_state: FinishWriteState,
}
impl Default for BodyWriter {
fn default() -> Self {
Self::new()
}
}
impl BodyWriter {
pub fn new() -> Self {
BodyWriter {
body_mode: BM::ToSelect,
send_body_state: Box::new(SendBodyState::new()),
send_finish_state: FinishWriteState::NotStarted,
}
}
pub fn init_chunked(&mut self) {
self.body_mode = BM::ChunkedEncoding(0);
}
pub fn init_close_delimited(&mut self) {
self.body_mode = BM::UntilClose(0);
}
pub fn init_content_length(&mut self, cl: usize) {
self.body_mode = BM::ContentLength(cl, 0);
}
pub fn convert_to_close_delimited(&mut self) {
if matches!(self.body_mode, BodyMode::UntilClose(_)) {
return;
}
self.body_mode = BM::UntilClose(0);
}
pub async fn write_body<S>(&mut self, stream: &mut S, buf: &[u8]) -> Result<Option<usize>>
where
S: AsyncWrite + Unpin + Send,
{
trace!("Writing Body, size: {}", buf.len());
match self.body_mode {
BM::Complete(_) => Ok(None),
BM::ContentLength(_, _) => self.do_write_body(stream, buf).await,
BM::ChunkedEncoding(_) => self.do_write_chunked_body(stream, buf).await,
BM::UntilClose(_) => self.do_write_until_close_body(stream, buf).await,
BM::ToSelect => Ok(None), }
}
pub fn finished(&self) -> bool {
match self.body_mode {
BM::Complete(_) => true,
BM::ContentLength(total, written) => written >= total,
_ => false,
}
}
pub fn is_close_delimited(&self) -> bool {
matches!(self.body_mode, BM::UntilClose(_))
}
async fn do_write_body<S>(&mut self, stream: &mut S, buf: &[u8]) -> Result<Option<usize>>
where
S: AsyncWrite + Unpin + Send,
{
match self.body_mode {
BM::ContentLength(total, written) => {
if written >= total {
return Ok(None);
}
let mut to_write = total - written;
if to_write < buf.len() {
warn!("Trying to write data over content-length: {total}");
} else {
to_write = buf.len();
}
let res = stream.write_all(&buf[..to_write]).await;
match res {
Ok(()) => {
self.body_mode = BM::ContentLength(total, written + to_write);
if self.finished() {
stream.flush().await.or_err(WriteError, "flushing body")?;
}
Ok(Some(to_write))
}
Err(e) => Error::e_because(WriteError, "while writing body", e),
}
}
_ => panic!("wrong body mode: {:?}", self.body_mode),
}
}
async fn do_write_chunked_body<S>(
&mut self,
stream: &mut S,
buf: &[u8],
) -> Result<Option<usize>>
where
S: AsyncWrite + Unpin + Send,
{
match self.body_mode {
BM::ChunkedEncoding(written) => {
let chunk_size = buf.len();
let chuck_size_buf = format!("{:X}\r\n", chunk_size);
let mut output_buf = Bytes::from(chuck_size_buf).chain(buf).chain(&b"\r\n"[..]);
stream
.write_all_buf(&mut output_buf)
.await
.or_err(WriteError, "while writing body")?;
stream.flush().await.or_err(WriteError, "flushing body")?;
self.body_mode = BM::ChunkedEncoding(written + chunk_size);
Ok(Some(chunk_size))
}
_ => panic!("wrong body mode: {:?}", self.body_mode),
}
}
async fn do_write_until_close_body<S>(
&mut self,
stream: &mut S,
buf: &[u8],
) -> Result<Option<usize>>
where
S: AsyncWrite + Unpin + Send,
{
match self.body_mode {
BM::UntilClose(written) => {
let res = stream.write_all(buf).await;
match res {
Ok(()) => {
self.body_mode = BM::UntilClose(written + buf.len());
stream.flush().await.or_err(WriteError, "flushing body")?;
Ok(Some(buf.len()))
}
Err(e) => Error::e_because(WriteError, "while writing body", e),
}
}
_ => panic!("wrong body mode: {:?}", self.body_mode),
}
}
pub async fn finish<S>(&mut self, stream: &mut S) -> Result<Option<usize>>
where
S: AsyncWrite + Unpin + Send,
{
match self.body_mode {
BM::Complete(_) => Ok(None),
BM::ContentLength(_, _) => self.do_finish_body(stream),
BM::ChunkedEncoding(_) => self.do_finish_chunked_body(stream).await,
BM::UntilClose(_) => self.do_finish_until_close_body(stream),
BM::ToSelect => Ok(None),
}
}
fn do_finish_body<S>(&mut self, _stream: S) -> Result<Option<usize>> {
match self.body_mode {
BM::ContentLength(total, written) => {
self.body_mode = BM::Complete(written);
if written < total {
return Error::e_explain(
PREMATURE_BODY_END,
format!("Content-length: {total} bytes written: {written}"),
);
}
Ok(Some(written))
}
_ => panic!("wrong body mode: {:?}", self.body_mode),
}
}
async fn do_finish_chunked_body<S>(&mut self, stream: &mut S) -> Result<Option<usize>>
where
S: AsyncWrite + Unpin + Send,
{
match self.body_mode {
BM::ChunkedEncoding(written) => {
let res = stream.write_all(&LAST_CHUNK[..]).await;
self.body_mode = BM::Complete(written);
match res {
Ok(()) => Ok(Some(written)),
Err(e) => Error::e_because(WriteError, "while writing body", e),
}
}
_ => panic!("wrong body mode: {:?}", self.body_mode),
}
}
fn do_finish_until_close_body<S>(&mut self, _stream: &mut S) -> Result<Option<usize>> {
match self.body_mode {
BM::UntilClose(written) => {
self.body_mode = BM::Complete(written);
Ok(Some(written))
}
_ => panic!("wrong body mode: {:?}", self.body_mode),
}
}
#[cfg(test)]
pub fn has_pending_body_task(&self) -> bool {
self.send_body_state.pending_bytes.is_some()
|| !matches!(
self.send_body_state.write_state,
WriteState::Idle | WriteState::Done(_) | WriteState::TimedOut
)
}
pub fn send_body_task(&mut self, bytes: Bytes, timeout: Option<std::time::Duration>) {
assert!(
matches!(
self.send_body_state.write_state,
WriteState::Idle | WriteState::Done(_)
),
"send_body_task called while previous task is still in progress: {:?}",
self.send_body_state.write_state
);
self.send_body_state.pending_bytes = Some(bytes);
self.send_body_state.write_state = WriteState::Idle;
self.send_body_state.timeout_duration = timeout;
self.send_body_state.timeout_fut = None;
}
pub async fn write_current_body_task<S>(&mut self, stream: &mut S) -> Result<Option<usize>>
where
S: AsyncWrite + Unpin + Send,
{
std::future::poll_fn(|cx| self.poll_write_current_body_task(cx, Pin::new(stream))).await
}
fn poll_write_current_body_task<S>(
&mut self,
cx: &mut Context<'_>,
stream: Pin<&mut S>,
) -> Poll<Result<Option<usize>>>
where
S: AsyncWrite + Unpin + Send,
{
if matches!(self.send_body_state.write_state, WriteState::TimedOut) {
return Poll::Ready(Error::e_explain(
WriteTimedout,
"write task previously timed out",
));
}
let result = match self.body_mode {
BM::Complete(_) => Poll::Ready(Ok(None)),
BM::ContentLength(_, _) => self.poll_write_content_length_body_task(cx, stream),
BM::ChunkedEncoding(_) => self.poll_write_chunked_body_task(cx, stream),
BM::UntilClose(_) => self.poll_write_until_close_body_task(cx, stream),
BM::ToSelect => Poll::Ready(Ok(None)),
};
if result.is_ready() {
return result;
}
if let Some(duration) = self.send_body_state.timeout_duration {
let timeout = self.send_body_state.timeout_fut.get_or_insert_with(|| {
Box::pin(pingora_timeout::sleep(duration))
as std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + Sync>>
});
if timeout.as_mut().poll(cx).is_ready() {
self.send_body_state.write_state = WriteState::TimedOut;
self.send_body_state.timeout_fut = None;
return Poll::Ready(Error::e_explain(
WriteTimedout,
"writing body task timed out",
));
}
}
Poll::Pending
}
#[cfg(test)]
pub fn has_pending_finish_task(&self) -> bool {
!matches!(
self.send_finish_state,
FinishWriteState::NotStarted | FinishWriteState::Done
)
}
pub fn send_finish_task(&mut self) {
self.send_finish_state = FinishWriteState::Idle;
}
pub async fn write_current_finish_task<S>(&mut self, stream: &mut S) -> Result<Option<usize>>
where
S: AsyncWrite + Unpin + Send,
{
std::future::poll_fn(|cx| self.poll_write_current_finish_task(cx, Pin::new(stream))).await
}
fn poll_write_current_finish_task<S>(
&mut self,
cx: &mut Context<'_>,
stream: Pin<&mut S>,
) -> Poll<Result<Option<usize>>>
where
S: AsyncWrite + Unpin + Send,
{
if matches!(
self.send_finish_state,
FinishWriteState::NotStarted | FinishWriteState::Done
) {
return Poll::Ready(Ok(None));
}
match self.body_mode {
BM::Complete(_) => Poll::Ready(Ok(None)),
BM::ContentLength(_, _) => self.poll_finish_content_length_task(cx, stream),
BM::ChunkedEncoding(_) => self.poll_finish_chunked_task(cx, stream),
BM::UntilClose(_) => self.poll_finish_until_close_task(cx, stream),
BM::ToSelect => Poll::Ready(Ok(None)),
}
}
fn poll_finish_content_length_task<S>(
&mut self,
_cx: &mut Context<'_>,
_stream: Pin<&mut S>,
) -> Poll<Result<Option<usize>>>
where
S: AsyncWrite + Unpin + Send,
{
let written = match self.body_mode {
BM::ContentLength(total, w) => {
if w < total {
self.send_finish_state = FinishWriteState::Done;
return Poll::Ready(Error::e_explain(
PREMATURE_BODY_END,
format!("Content-length: {total} bytes written: {w}"),
));
}
w
}
_ => panic!("wrong body mode: {:?}", self.body_mode),
};
self.body_mode = BM::Complete(written);
self.send_finish_state = FinishWriteState::Done;
Poll::Ready(Ok(Some(written)))
}
fn poll_finish_chunked_task<S>(
&mut self,
cx: &mut Context<'_>,
mut stream: Pin<&mut S>,
) -> Poll<Result<Option<usize>>>
where
S: AsyncWrite + Unpin + Send,
{
let written = match self.body_mode {
BM::ChunkedEncoding(w) => w,
_ => panic!("wrong body mode: {:?}", self.body_mode),
};
loop {
match &mut self.send_finish_state {
FinishWriteState::Idle => {
let buf = WriteBuf::Simple(Bytes::from_static(&LAST_CHUNK[..]));
self.send_finish_state = FinishWriteState::WritingLastChunk(buf);
}
FinishWriteState::WritingLastChunk(buf) => {
ready!(pin!(stream.as_mut().get_mut().write_all_buf(buf)).poll(cx))
.map_err(|e| Error::because(WriteError, "while writing last chunk", e))?;
self.send_finish_state = FinishWriteState::Flushing;
}
FinishWriteState::Flushing => {
ready!(stream.as_mut().poll_flush(cx))
.map_err(|e| Error::because(WriteError, "flushing after last chunk", e))?;
self.body_mode = BM::Complete(written);
self.send_finish_state = FinishWriteState::Done;
return Poll::Ready(Ok(Some(written)));
}
FinishWriteState::Done => {
unreachable!(
"Done state should have been handled in poll_write_current_finish_task"
)
}
FinishWriteState::NotStarted => {
unreachable!("NotStarted state should have been handled in poll_write_current_finish_task")
}
}
}
}
fn poll_finish_until_close_task<S>(
&mut self,
_cx: &mut Context<'_>,
_stream: Pin<&mut S>,
) -> Poll<Result<Option<usize>>>
where
S: AsyncWrite + Unpin + Send,
{
let written = match self.body_mode {
BM::UntilClose(w) => w,
_ => panic!("wrong body mode: {:?}", self.body_mode),
};
self.body_mode = BM::Complete(written);
self.send_finish_state = FinishWriteState::Done;
Poll::Ready(Ok(Some(written)))
}
fn poll_write_content_length_body_task<S>(
&mut self,
cx: &mut Context<'_>,
mut stream: Pin<&mut S>,
) -> Poll<Result<Option<usize>>>
where
S: AsyncWrite + Unpin + Send,
{
if matches!(self.send_body_state.write_state, WriteState::Idle) {
if let Some(mut bytes) = self.send_body_state.pending_bytes.take() {
let (total, written) = self.body_mode.expect_content_length();
if written >= total {
self.send_body_state.write_state = WriteState::Done(0);
return Poll::Ready(Ok(None));
}
let original_size = bytes.len();
let remaining = total - written;
if original_size > remaining {
warn!(
"Trying to write {} bytes over content-length: {}, truncating to {}",
original_size, total, remaining
);
bytes.truncate(remaining);
}
let bytes_to_write = bytes.len();
self.send_body_state.write_state =
WriteState::Writing(bytes_to_write, WriteBuf::Simple(bytes));
} else {
self.send_body_state.write_state = WriteState::Done(0);
return Poll::Ready(Ok(None));
}
}
if let WriteState::Writing(size, ref mut buf) = &mut self.send_body_state.write_state {
let bytes_written = *size;
match ready!(pin!(stream.as_mut().get_mut().write_all_buf(buf)).poll(cx)) {
Ok(()) => {
let (total, written) = self.body_mode.expect_content_length();
self.body_mode = BM::ContentLength(total, written + bytes_written);
if written + bytes_written >= total {
self.send_body_state.write_state = WriteState::Flushing(bytes_written);
} else {
self.send_body_state.write_state = WriteState::Done(bytes_written);
}
}
Err(e) => {
return Poll::Ready(Error::e_because(WriteError, "while writing body", e))
}
}
}
if let WriteState::Flushing(size) = self.send_body_state.write_state {
let bytes_written = size;
match ready!(stream.poll_flush(cx)) {
Ok(()) => {
self.send_body_state.write_state = WriteState::Done(bytes_written);
}
Err(e) => return Poll::Ready(Error::e_because(WriteError, "flushing body", e)),
}
}
match self.send_body_state.write_state {
WriteState::Done(size) => {
self.send_body_state.timeout_fut = None;
Poll::Ready(Ok(Some(size)))
}
WriteState::TimedOut => Poll::Ready(Error::e_explain(
WriteTimedout,
"write task previously timed out",
)),
WriteState::Writing(..) | WriteState::Flushing(..) => {
unreachable!("Writing/Flushing states should have been handled above or returned Pending via ready!")
}
WriteState::Idle => {
unreachable!("Idle state should have been handled in setup")
}
}
}
fn poll_write_chunked_body_task<S>(
&mut self,
cx: &mut Context<'_>,
mut stream: Pin<&mut S>,
) -> Poll<Result<Option<usize>>>
where
S: AsyncWrite + Unpin + Send,
{
if matches!(self.send_body_state.write_state, WriteState::Idle) {
if let Some(bytes) = self.send_body_state.pending_bytes.take() {
let application_bytes_size = bytes.len();
let chunk_size_header = format!("{:X}\r\n", application_bytes_size);
let output_buf = Bytes::from(chunk_size_header)
.chain(bytes)
.chain(&b"\r\n"[..]);
self.send_body_state.write_state =
WriteState::Writing(application_bytes_size, WriteBuf::Chained(output_buf));
} else {
self.send_body_state.write_state = WriteState::Done(0);
return Poll::Ready(Ok(None));
}
}
if let WriteState::Writing(size, ref mut buf) = &mut self.send_body_state.write_state {
let bytes_written = *size;
match ready!(pin!(stream.as_mut().get_mut().write_all_buf(buf)).poll(cx)) {
Ok(()) => {
let written = self.body_mode.expect_chunked();
self.body_mode = BM::ChunkedEncoding(written + bytes_written);
self.send_body_state.write_state = WriteState::Flushing(bytes_written);
}
Err(e) => {
return Poll::Ready(Error::e_because(WriteError, "while writing body", e))
}
}
}
if let WriteState::Flushing(size) = self.send_body_state.write_state {
let bytes_written = size;
match ready!(stream.poll_flush(cx)) {
Ok(()) => {
self.send_body_state.write_state = WriteState::Done(bytes_written);
}
Err(e) => return Poll::Ready(Error::e_because(WriteError, "flushing body", e)),
}
}
match self.send_body_state.write_state {
WriteState::Done(size) => {
self.send_body_state.timeout_fut = None;
Poll::Ready(Ok(Some(size)))
}
WriteState::TimedOut => Poll::Ready(Error::e_explain(
WriteTimedout,
"write task previously timed out",
)),
WriteState::Writing(..) | WriteState::Flushing(..) => {
unreachable!("Writing/Flushing states should have been handled above or returned Pending via ready!")
}
WriteState::Idle => {
unreachable!("Idle state should have been handled in setup")
}
}
}
fn poll_write_until_close_body_task<S>(
&mut self,
cx: &mut Context<'_>,
mut stream: Pin<&mut S>,
) -> Poll<Result<Option<usize>>>
where
S: AsyncWrite + Unpin + Send,
{
if matches!(self.send_body_state.write_state, WriteState::Idle) {
if let Some(bytes) = self.send_body_state.pending_bytes.take() {
let original_size = bytes.len();
self.send_body_state.write_state =
WriteState::Writing(original_size, WriteBuf::Simple(bytes));
} else {
self.send_body_state.write_state = WriteState::Done(0);
return Poll::Ready(Ok(None));
}
}
if let WriteState::Writing(size, ref mut buf) = &mut self.send_body_state.write_state {
let bytes_written = *size;
match ready!(pin!(stream.as_mut().get_mut().write_all_buf(buf)).poll(cx)) {
Ok(()) => {
let written = self.body_mode.expect_until_close();
self.body_mode = BM::UntilClose(written + bytes_written);
self.send_body_state.write_state = WriteState::Flushing(bytes_written);
}
Err(e) => {
return Poll::Ready(Error::e_because(WriteError, "while writing body", e))
}
}
}
if let WriteState::Flushing(size) = self.send_body_state.write_state {
let bytes_written = size;
match ready!(stream.poll_flush(cx)) {
Ok(()) => {
self.send_body_state.write_state = WriteState::Done(bytes_written);
}
Err(e) => return Poll::Ready(Error::e_because(WriteError, "flushing body", e)),
}
}
match self.send_body_state.write_state {
WriteState::Done(size) => {
self.send_body_state.timeout_fut = None;
Poll::Ready(Ok(Some(size)))
}
WriteState::TimedOut => Poll::Ready(Error::e_explain(
WriteTimedout,
"write task previously timed out",
)),
WriteState::Writing(..) | WriteState::Flushing(..) => {
unreachable!("Writing/Flushing states should have been handled above or returned Pending via ready!")
}
WriteState::Idle => {
unreachable!("Idle state should have been handled in setup")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio_test::io::Builder;
fn init_log() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test]
fn content_length_zero_owned_rewind_becomes_overread_without_copying() {
let rewind = BytesMut::from(&b"next request"[..]);
let rewind_ptr = rewind.as_ptr();
let mut body_reader = BodyReader::new(false);
body_reader.init_content_length_owned(0, rewind);
let overread = body_reader.take_body_overread().unwrap();
assert_eq!(overread.as_ptr(), rewind_ptr);
assert_eq!(overread.as_ref(), b"next request");
}
#[tokio::test]
async fn partial_owned_body_allocates_only_remaining_content_length() {
let mut mock_io = Builder::new().read(b"bc").build();
let mut body_reader = BodyReader::new(false);
body_reader.init_content_length_owned(3, BytesMut::from(&b"a"[..]));
let first = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(body_reader.get_body(&first), b"a");
let second = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(body_reader.get_body(&second), b"bc");
assert!(body_reader.body_buf.as_ref().unwrap().capacity() < BODY_BUFFER_SIZE);
}
#[tokio::test]
async fn read_with_body_content_length() {
init_log();
let input = b"abc";
let mut mock_io = Builder::new().read(&input[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_content_length(3, b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 3));
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(input, body_reader.get_body(&res));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_content_length_2() {
init_log();
let input1 = b"a";
let input2 = b"bc";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_content_length(3, b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 1));
assert_eq!(body_reader.body_state, ParseState::Partial(1, 2));
assert_eq!(input1, body_reader.get_body(&res));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 2));
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(input2, body_reader.get_body(&res));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_content_length_less() {
init_log();
let input1 = b"a";
let input2 = b""; let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_content_length(3, b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 1));
assert_eq!(body_reader.body_state, ParseState::Partial(1, 2));
assert_eq!(input1, body_reader.get_body(&res));
let res = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(&ConnectionClosed, res.etype());
assert_eq!(body_reader.body_state, ParseState::Done(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_content_length_more() {
init_log();
let input1 = b"a";
let input2 = b"bcd";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_content_length(3, b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 1));
assert_eq!(body_reader.body_state, ParseState::Partial(1, 2));
assert_eq!(input1, body_reader.get_body(&res));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 2));
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(&input2[0..2], body_reader.get_body(&res));
body_reader.init_content_length(1, b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(&input2[2..], body_reader.get_body(&res));
}
#[tokio::test]
async fn read_with_body_content_length_overread() {
init_log();
let input1 = b"a";
let input2 = b"bcd";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(true);
body_reader.init_content_length(3, b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 1));
assert_eq!(body_reader.body_state, ParseState::Partial(1, 2));
assert_eq!(input1, body_reader.get_body(&res));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 2));
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(&input2[0..2], body_reader.get_body(&res));
assert_eq!(body_reader.get_body_overread(), Some(&b"d"[..]));
}
#[tokio::test]
async fn read_with_body_content_length_rewind() {
init_log();
let rewind = b"ab";
let input = b"c";
let mut mock_io = Builder::new().read(&input[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_content_length(3, rewind);
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 2));
assert_eq!(body_reader.body_state, ParseState::Partial(2, 1));
assert_eq!(rewind, body_reader.get_body(&res));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 1));
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(input, body_reader.get_body(&res));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_http10() {
init_log();
let input1 = b"a";
let input2 = b""; let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_close_delimited(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 1));
assert_eq!(body_reader.body_state, ParseState::UntilClose(1));
assert_eq!(input1, body_reader.get_body(&res));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_http10_rewind() {
init_log();
let rewind = b"ab";
let input1 = b"c";
let input2 = b""; let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_close_delimited(rewind);
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 2));
assert_eq!(body_reader.body_state, ParseState::UntilClose(2));
assert_eq!(rewind, body_reader.get_body(&res));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 1));
assert_eq!(body_reader.body_state, ParseState::UntilClose(3));
assert_eq!(input1, body_reader.get_body(&res));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_zero_chunk() {
init_log();
let input = b"0\r\n\r\n";
let mut mock_io = Builder::new().read(&input[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(0));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_overflowing_chunk_size() {
init_log();
let input = b"FFFFFFFFFFFFFFFE\r\nAAAA\r\n0\r\n\r\n";
let mut mock_io = Builder::new().read(&input[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let err = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*err.etype(), INVALID_CHUNK);
assert_eq!(body_reader.body_state, ParseState::Done(0));
}
#[tokio::test]
async fn read_with_body_max_chunk_size() {
init_log();
let input = b"FFFFFFFFFFFFFFFF\r\nAAAA\r\n0\r\n\r\n";
let mut mock_io = Builder::new().read(&input[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let err = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*err.etype(), INVALID_CHUNK);
assert_eq!(body_reader.body_state, ParseState::Done(0));
}
#[tokio::test]
async fn read_with_body_zero_chunk_malformed() {
init_log();
let input = b"0\r\nr\n";
let mut mock_io = Builder::new().read(&input[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(0, 0, 2, 2));
let e = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*e.etype(), INVALID_TRAILER_END);
assert_eq!(body_reader.body_state, ParseState::Done(0));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_zero_chunk_split() {
init_log();
let input1 = b"0\r\n";
let input2 = b"\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(0, 0, 0, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(0));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_zero_chunk_split_head() {
init_log();
let input1 = b"0\r";
let input2 = b"\n";
let input3 = b"\r\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 2, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(0, 0, 0, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(0));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_zero_chunk_split_head_2() {
init_log();
let input1 = b"0";
let input2 = b"\r\n";
let input3 = b"\r\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 1, 1));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(0, 0, 0, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(0));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_zero_chunk_split_head_3() {
init_log();
let input1 = b"0\r";
let input2 = b"\n";
let input3 = b"\r";
let input4 = b"\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.read(&input4[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 2, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(0, 0, 0, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(0, 0, 0, 3));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(0));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_chunk_ext() {
init_log();
let input = b"0;aaaa\r\n\r\n";
let mut mock_io = Builder::new().read(&input[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(0));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_chunk_ext_oversize() {
init_log();
let chunk_size = b"0;";
let ext1 = [b'a'; 1024 * 5];
let ext2 = [b'a'; 1024 * 3];
let mut mock_io = Builder::new()
.read(&chunk_size[..])
.read(&ext1[..])
.read(&ext2[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, Some(BufRef::new(0, 0)));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, Some(BufRef::new(0, 0)));
let res = body_reader.read_body(&mut mock_io).await;
assert!(res.is_err());
assert_eq!(body_reader.body_state, ParseState::Done(0));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_1_chunk() {
init_log();
let input1 = b"1\r\na\r\n";
let input2 = b"0\r\n\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 0, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_1_chunk_malformed() {
init_log();
let input1 = b"1\r\na\rn";
let mut mock_io = Builder::new().read(&input1[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let e = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*e.etype(), INVALID_CHUNK);
assert_eq!(body_reader.body_state, ParseState::Done(0));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_1_chunk_partial_end() {
init_log();
let input1 = b"1\r\na\r";
let input2 = b"\n0\r\n\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 0, 1));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 1, 6, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_1_chunk_partial_end_1() {
init_log();
let input1 = b"3\r\n";
let input2 = b"abc\r";
let input3 = b"\n0\r\n\r\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 0));
assert_eq!(b"", body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 0, 5));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 3));
assert_eq!(&input2[0..3], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(3, 0, 0, 1));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_1_chunk_partial_end_2() {
init_log();
let input1 = b"3\r\n";
let input2 = b"abc";
let input3 = b"\r\n0\r\n\r\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 0));
assert_eq!(b"", body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 0, 5));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 3));
assert_eq!(&input2[0..3], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(3, 0, 0, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_1_chunk_incomplete() {
init_log();
let input1 = b"1\r\na\r\n";
let mut mock_io = Builder::new().read(&input1[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 0, 0));
let res = body_reader.read_body(&mut mock_io).await;
assert!(res.is_err());
assert_eq!(body_reader.body_state, ParseState::Done(1));
}
#[tokio::test]
async fn read_with_body_1_chunk_partial_end_malformed() {
init_log();
let input1 = b"1\r\na\r";
let input2 = b"n0\r\n\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 0, 1));
let e = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*e.etype(), INVALID_CHUNK);
assert_eq!(body_reader.body_state, ParseState::Done(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_1_chunk_rewind() {
init_log();
let rewind = b"1\r\nx\r\n";
let input1 = b"1\r\na\r\n";
let input2 = b"0\r\n\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(rewind);
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&rewind[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 0, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(2, 0, 0, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(2));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_multi_chunk() {
init_log();
let input1 = b"1\r\na\r\n2\r\nbc\r\n";
let input2 = b"0\r\n\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 13, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(9, 2));
assert_eq!(&input1[9..11], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(3, 0, 0, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_multi_chunk_malformed() {
init_log();
let input1 = b"1\r\na\r\n2\r\nbcr\n";
let mut mock_io = Builder::new().read(&input1[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 13, 0));
let e = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*e.etype(), INVALID_CHUNK);
assert_eq!(body_reader.body_state, ParseState::Done(1));
assert_eq!(body_reader.get_body_overread(), None);
let input1 = b"1\r\nar\n2\r\nbc\rn";
let mut mock_io = Builder::new().read(&input1[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let e = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*e.etype(), INVALID_CHUNK);
assert_eq!(body_reader.body_state, ParseState::Done(0));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_partial_chunk() {
init_log();
let input1 = b"3\r\na";
let input2 = b"bc\r\n0\r\n\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 0, 4));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 2));
assert_eq!(&input2[0..2], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(3, 4, 9, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_partial_chunk_end() {
init_log();
let input1 = b"3\r\nabc";
let input2 = b"\r\n0\r\n\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 3));
assert_eq!(&input1[3..6], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(3, 0, 0, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(&input2[0..0], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(3, 2, 7, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_partial_head_chunk() {
init_log();
let input1 = b"1\r";
let input2 = b"\na\r\n0\r\n\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 2, 2));
let _res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
}
#[tokio::test(start_paused = true)]
async fn partial_chunk_head_read_resumes_after_cancellation() {
init_log();
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(100))
.read(b"\na\r\n0\r\n\r\n")
.build();
let mut body_reader = BodyReader::new(false);
body_reader.body_state = ParseState::Chunked(0, 0, 8, 2);
body_reader.body_buf = Some(BytesMut::from(&b"unused1\r"[..]));
tokio::select! {
result = body_reader.read_body(&mut mock_io) => {
panic!("body read unexpectedly completed before cancellation: {result:?}");
}
_ = tokio::time::sleep(std::time::Duration::from_millis(10)) => {}
}
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 2, 2));
assert_eq!(body_reader.body_buf.as_deref().unwrap(), b"1\r");
body_reader.read_body(&mut mock_io).await.unwrap();
}
#[tokio::test]
async fn read_with_body_partial_head_chunk_incomplete() {
init_log();
let input1 = b"1\r";
let mut mock_io = Builder::new().read(&input1[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 2, 2));
let res = body_reader.read_body(&mut mock_io).await;
assert!(res.is_err());
assert_eq!(body_reader.body_state, ParseState::Done(0));
}
#[tokio::test]
async fn read_with_body_partial_head_terminal_crlf() {
init_log();
let input1 = b"1\r";
let input2 = b"\na\r\n0\r\n\r";
let input3 = b"\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 2, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1)); assert_eq!(&input2[1..2], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 10, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0)); assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 1, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 0, 3));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_partial_head_terminal_crlf_2() {
init_log();
let input1 = b"1\r";
let input2 = b"\na\r\n0\r";
let input3 = b"\n\r\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 2, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1)); assert_eq!(&input2[1..2], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 8, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0)); assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 8, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_partial_head_terminal_crlf_3() {
init_log();
let input1 = b"1\r\na\r\n0";
let input2 = b"\r";
let input3 = b"\n";
let input4 = b"\r";
let input5 = b"\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.read(&input4[..])
.read(&input5[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 7, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 7, 1));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 2, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 0, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 0, 3));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_partial_head_terminal_crlf_malformed() {
init_log();
let input1 = b"1\r";
let input2 = b"\na\r\n0\r\nr";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 2, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1)); assert_eq!(&input2[1..2], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 10, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 1, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 3, 0, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(&ConnectionClosed, res.etype());
assert_eq!(body_reader.body_state, ParseState::Done(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_partial_head_terminal_crlf_overread() {
init_log();
let input1 = b"1\r";
let input2 = b"\na\r\n0\r\n\r";
let input3 = b"\nabcd";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(0, 0, 2, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1)); assert_eq!(&input2[1..2], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 10, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0)); assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 1, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 0, 3));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(body_reader.get_body_overread(), Some(&b"abcd"[..]));
}
#[tokio::test]
async fn read_with_body_multi_chunk_overread() {
init_log();
let input1 = b"1\r\na\r\n2\r\nbc\r\n";
let input2 = b"0\r\n\r\nabc";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 13, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(9, 2));
assert_eq!(&input1[9..11], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(3, 0, 0, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(body_reader.get_body_overread(), Some(&b"abc"[..]));
}
#[tokio::test]
async fn read_with_body_trailers() {
init_log();
let input1 = b"1\r\na\r\n2\r\nbc\r\n";
let input2 = b"0\r\nabc: hi";
let input3 = b"\r\ndef: bye\r";
let input4 = b"\nghi: more\r\n";
let input5 = b"\r\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.read(&input4[..])
.read(&input5[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 13, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(9, 2));
assert_eq!(&input1[9..11], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(3, 0, 0, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(3, 0, 7, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(
body_reader.body_state,
ParseState::ChunkedFinal(3, 9, 0, 0)
);
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(
body_reader.body_state,
ParseState::ChunkedFinal(3, 19, 0, 1)
);
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(
body_reader.body_state,
ParseState::ChunkedFinal(3, 30, 0, 2)
);
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(3));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_trailers_2() {
init_log();
let input1 = b"1\r\na\r\n0\r";
let input2 = b"\nabc: hi\r\n\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 8, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 8, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(
body_reader.body_state,
ParseState::ChunkedFinal(1, 0, 11, 2)
);
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_trailers_3() {
init_log();
let input1 = b"1\r\na\r\n0\r";
let input2 = b"\nabc: hi";
let input3 = b"\r\n\r\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 8, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 8, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 7, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(
body_reader.body_state,
ParseState::ChunkedFinal(1, 9, 0, 0)
);
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_trailers_4() {
init_log();
let input1 = b"1\r\na\r\n0\r";
let input2 = b"\nabc: hi\r\n\r";
let input3 = b"\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 8, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 8, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(
body_reader.body_state,
ParseState::ChunkedFinal(1, 0, 10, 2)
);
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(
body_reader.body_state,
ParseState::ChunkedFinal(1, 9, 0, 3)
);
let res = body_reader.read_body(&mut mock_io).await.unwrap();
assert_eq!(res, None);
assert_eq!(body_reader.body_state, ParseState::Complete(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_trailers_malformed() {
init_log();
let input1 = b"1\r\na\r\n0\r";
let input2 = b"\nabc: hi\rn";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 8, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 8, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 9, 2));
let e = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*e.etype(), INVALID_TRAILER_END);
assert_eq!(body_reader.body_state, ParseState::Done(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_trailers_malformed_2() {
init_log();
let input1 = b"1\r\na\r\n0\r";
let input2 = b"\nabc: hi\r\n";
let mut mock_io = Builder::new().read(&input1[..]).read(&input2[..]).build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 8, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 0, 8, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 9, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 9, 0, 2));
let e = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*e.etype(), ConnectionClosed);
assert_eq!(body_reader.body_state, ParseState::Done(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_trailers_malformed_3() {
init_log();
let input1 = b"1\r\na\r\n0\r\n";
let input2 = b"abc: hi\r\n";
let input3 = b"r\n";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&input3[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 9, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 0, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 9, 0, 2));
let e = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*e.etype(), INVALID_TRAILER_END);
assert_eq!(body_reader.body_state, ParseState::Done(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn read_with_body_trailers_overflow() {
init_log();
let input1 = b"1\r\na\r\n0\r\n";
let input2 = b"abc: ";
let trailer1 = [b'a'; 1024 * 60];
let trailer2 = [b'a'; 1024 * 5];
let input3 = b"defghi: ";
let mut mock_io = Builder::new()
.read(&input1[..])
.read(&input2[..])
.read(&trailer1[..])
.read(&CRLF[..])
.read(&input3[..])
.read(&trailer2[..])
.build();
let mut body_reader = BodyReader::new(false);
body_reader.init_chunked(b"");
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(3, 1));
assert_eq!(&input1[3..4], body_reader.get_body(&res));
assert_eq!(body_reader.body_state, ParseState::Chunked(1, 6, 9, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 0, 0, 2));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(body_reader.body_state, ParseState::ChunkedFinal(1, 7, 0, 0));
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(
body_reader.body_state,
ParseState::ChunkedFinal(1, 1024 * 60 + 7, 0, 0)
);
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(
body_reader.body_state,
ParseState::ChunkedFinal(1, 1024 * 60 + 7, 0, 2)
);
let res = body_reader.read_body(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, BufRef::new(0, 0));
assert_eq!(
body_reader.body_state,
ParseState::ChunkedFinal(1, 1024 * 60 + 17, 0, 0)
);
let e = body_reader.read_body(&mut mock_io).await.unwrap_err();
assert_eq!(*e.etype(), INVALID_TRAILER_END);
assert_eq!(body_reader.body_state, ParseState::Done(1));
assert_eq!(body_reader.get_body_overread(), None);
}
#[tokio::test]
async fn write_body_cl() {
init_log();
let output = b"a";
let mut mock_io = Builder::new().write(&output[..]).build();
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(1);
assert_eq!(body_writer.body_mode, BodyMode::ContentLength(1, 0));
let res = body_writer
.write_body(&mut mock_io, &output[..])
.await
.unwrap()
.unwrap();
assert_eq!(res, 1);
assert_eq!(body_writer.body_mode, BodyMode::ContentLength(1, 1));
let res = body_writer
.write_body(&mut mock_io, &output[..])
.await
.unwrap();
assert_eq!(res, None);
assert_eq!(body_writer.body_mode, BodyMode::ContentLength(1, 1));
let res = body_writer.finish(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, 1);
assert_eq!(body_writer.body_mode, BodyMode::Complete(1));
}
#[tokio::test]
async fn write_body_chunked() {
init_log();
let data = b"abcdefghij";
let output = b"A\r\nabcdefghij\r\n";
let mut mock_io = Builder::new()
.write(&output[..])
.write(&output[..])
.write(&LAST_CHUNK[..])
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_chunked();
assert_eq!(body_writer.body_mode, BodyMode::ChunkedEncoding(0));
let res = body_writer
.write_body(&mut mock_io, &data[..])
.await
.unwrap()
.unwrap();
assert_eq!(res, data.len());
assert_eq!(body_writer.body_mode, BodyMode::ChunkedEncoding(data.len()));
let res = body_writer
.write_body(&mut mock_io, &data[..])
.await
.unwrap()
.unwrap();
assert_eq!(res, data.len());
assert_eq!(
body_writer.body_mode,
BodyMode::ChunkedEncoding(data.len() * 2)
);
let res = body_writer.finish(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, data.len() * 2);
assert_eq!(body_writer.body_mode, BodyMode::Complete(data.len() * 2));
}
#[tokio::test]
async fn write_body_until_close() {
init_log();
let data = b"a";
let mut mock_io = Builder::new().write(&data[..]).write(&data[..]).build();
let mut body_writer = BodyWriter::new();
body_writer.init_close_delimited();
assert_eq!(body_writer.body_mode, BodyMode::UntilClose(0));
let res = body_writer
.write_body(&mut mock_io, &data[..])
.await
.unwrap()
.unwrap();
assert_eq!(res, 1);
assert_eq!(body_writer.body_mode, BodyMode::UntilClose(1));
let res = body_writer
.write_body(&mut mock_io, &data[..])
.await
.unwrap()
.unwrap();
assert_eq!(res, 1);
assert_eq!(body_writer.body_mode, BodyMode::UntilClose(2));
let res = body_writer.finish(&mut mock_io).await.unwrap().unwrap();
assert_eq!(res, 2);
assert_eq!(body_writer.body_mode, BodyMode::Complete(2));
}
}
#[cfg(test)]
mod test_body_task_api {
use super::*;
use crate::protocols::http::v1::test_util::FlushTrackingMock;
use tokio_test::io::Builder;
fn init_log() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[tokio::test]
async fn test_has_pending_body_task() {
init_log();
let data = b"test data";
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data.len());
assert!(!body_writer.has_pending_body_task());
body_writer.send_body_task(Bytes::from_static(data), None);
assert!(body_writer.has_pending_body_task());
}
#[tokio::test(start_paused = true)]
async fn cancel_safe_content_length_write() {
init_log();
let data = b"Hello, World!";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(100))
.write(data)
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data.len());
body_writer.send_body_task(Bytes::from_static(data), None);
let mut cancel_count = 0;
let mut total_bytes_written = 0;
loop {
if !body_writer.has_pending_body_task() {
break;
}
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(10)) => {
cancel_count += 1;
}
result = body_writer.write_current_body_task(&mut mock_io) => {
assert!(result.is_ok(), "Write should succeed");
if let Ok(Some(n)) = result {
total_bytes_written += n;
}
}
}
}
assert!(
cancel_count > 0,
"At least one cancellation should have occurred"
);
assert_eq!(
total_bytes_written,
data.len(),
"Should have written all application bytes"
);
assert_eq!(
body_writer.body_mode,
BodyMode::ContentLength(data.len(), data.len())
);
let mut mock_io_finish = Builder::new().build();
loop {
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(5)) => {
}
result = body_writer.finish(&mut mock_io_finish) => {
assert!(result.is_ok());
break;
}
}
}
assert_eq!(body_writer.body_mode, BodyMode::Complete(data.len()));
}
#[tokio::test(start_paused = true)]
async fn cancel_safe_chunked_write() {
init_log();
let data = b"abcdefghij";
let expected_output = b"A\r\nabcdefghij\r\n";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(100))
.write(expected_output)
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_chunked();
body_writer.send_body_task(Bytes::from_static(data), None);
let mut cancel_count = 0;
let mut total_bytes_written = 0;
loop {
if !body_writer.has_pending_body_task() {
break;
}
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(10)) => {
cancel_count += 1;
}
result = body_writer.write_current_body_task(&mut mock_io) => {
assert!(result.is_ok());
if let Ok(Some(n)) = result {
total_bytes_written += n;
}
}
}
}
assert!(cancel_count > 0, "Should have cancelled at least once");
assert_eq!(
total_bytes_written,
data.len(),
"Should have written all application bytes"
);
assert_eq!(body_writer.body_mode, BodyMode::ChunkedEncoding(data.len()));
let mut mock_io_finish = Builder::new()
.wait(std::time::Duration::from_millis(50))
.write(&LAST_CHUNK[..]) .build();
loop {
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(5)) => {}
result = body_writer.finish(&mut mock_io_finish) => {
assert!(result.is_ok());
break;
}
}
}
assert_eq!(body_writer.body_mode, BodyMode::Complete(data.len()));
}
#[tokio::test(start_paused = true)]
async fn cancel_safe_until_close_write() {
init_log();
let data = b"test data";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(100))
.write(data)
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_close_delimited();
body_writer.send_body_task(Bytes::from_static(data), None);
let mut cancel_count = 0;
let mut total_bytes_written = 0;
loop {
if !body_writer.has_pending_body_task() {
break;
}
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(10)) => {
cancel_count += 1;
}
result = body_writer.write_current_body_task(&mut mock_io) => {
assert!(result.is_ok());
if let Ok(Some(n)) = result {
total_bytes_written += n;
}
}
}
}
assert!(cancel_count > 0);
assert_eq!(
total_bytes_written,
data.len(),
"Should have written all application bytes"
);
let mut mock_io_finish = Builder::new().build();
loop {
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(5)) => {}
result = body_writer.finish(&mut mock_io_finish) => {
assert!(result.is_ok());
break;
}
}
}
}
#[tokio::test(start_paused = true)]
async fn cancel_safe_multiple_cancellations() {
init_log();
let data = b"Long test data that requires multiple writes";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(50))
.write(&data[..15])
.wait(std::time::Duration::from_millis(50))
.write(&data[15..30])
.wait(std::time::Duration::from_millis(50))
.write(&data[30..])
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data.len());
body_writer.send_body_task(Bytes::from_static(data), None);
let mut cancel_count = 0;
let mut total_bytes_written = 0;
loop {
if !body_writer.has_pending_body_task() {
break;
}
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(5)) => {
cancel_count += 1;
}
result = body_writer.write_current_body_task(&mut mock_io) => {
assert!(result.is_ok());
if let Ok(Some(n)) = result {
total_bytes_written += n;
}
}
}
}
assert!(cancel_count >= 2, "Should have multiple cancellations");
assert_eq!(
total_bytes_written,
data.len(),
"Should have written all application bytes"
);
let mut mock_io_finish = Builder::new().build();
loop {
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(5)) => {}
result = body_writer.finish(&mut mock_io_finish) => {
assert!(result.is_ok());
break;
}
}
}
}
#[tokio::test(start_paused = true)]
async fn cancel_safe_partial_writes() {
init_log();
let data = b"12345678901234567890";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(50))
.write(&data[..7])
.wait(std::time::Duration::from_millis(50))
.write(&data[7..14])
.wait(std::time::Duration::from_millis(50))
.write(&data[14..])
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data.len());
body_writer.send_body_task(Bytes::from_static(data), None);
let mut cancel_count = 0;
let mut total_bytes_written = 0;
loop {
if !body_writer.has_pending_body_task() {
break;
}
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(10)) => {
cancel_count += 1;
}
result = body_writer.write_current_body_task(&mut mock_io) => {
assert!(result.is_ok());
if let Ok(Some(n)) = result {
total_bytes_written += n;
}
}
}
}
assert!(cancel_count > 0);
assert_eq!(
total_bytes_written,
data.len(),
"Should have written all application bytes"
);
let mut mock_io_finish = Builder::new()
.wait(std::time::Duration::from_millis(30))
.build();
loop {
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(5)) => {}
result = body_writer.finish(&mut mock_io_finish) => {
assert!(result.is_ok(), "Finish should succeed after cancel-safe writes");
break;
}
}
}
}
#[tokio::test]
async fn test_task_write_timeout() {
init_log();
let data = b"test data";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_secs(1000))
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data.len());
body_writer.send_body_task(
Bytes::from_static(data),
Some(std::time::Duration::from_millis(50)),
);
let result = body_writer.write_current_body_task(&mut mock_io).await;
assert!(result.is_err(), "Write should timeout");
if let Err(e) = result {
assert_eq!(e.etype(), &WriteTimedout);
}
}
#[tokio::test]
async fn test_task_timeout_persists_across_cancellations() {
init_log();
let data = b"test data";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(200))
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data.len());
body_writer.send_body_task(
Bytes::from_static(data),
Some(std::time::Duration::from_millis(100)),
);
let mut attempts = 0;
let mut timedout = false;
loop {
if !body_writer.has_pending_body_task() {
break;
}
attempts += 1;
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(10)) => {
continue;
}
result = body_writer.write_current_body_task(&mut mock_io) => {
match result {
Ok(_) => {
break;
}
Err(e) if e.etype() == &WriteTimedout => {
timedout = true;
break;
}
Err(e) => {
panic!("Unexpected error: {:?}", e);
}
}
}
}
}
assert!(timedout, "Timeout should have fired despite cancellations");
assert!(
attempts >= 5,
"Should have had multiple attempts before timeout"
);
}
#[tokio::test]
async fn test_task_write_succeeds_within_timeout() {
init_log();
let data = b"Hello, World!";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(20))
.write(data)
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data.len());
body_writer.send_body_task(
Bytes::from_static(data),
Some(std::time::Duration::from_millis(500)),
);
let result = body_writer.write_current_body_task(&mut mock_io).await;
assert!(result.is_ok(), "Write should succeed: {:?}", result);
assert_eq!(result.unwrap(), Some(data.len()));
}
#[tokio::test]
async fn test_task_write_no_timeout() {
init_log();
let data = b"test data";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(100))
.write(data)
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data.len());
body_writer.send_body_task(Bytes::from_static(data), None);
let result = body_writer.write_current_body_task(&mut mock_io).await;
assert!(result.is_ok(), "Write should succeed without timeout");
assert_eq!(result.unwrap(), Some(data.len()));
}
#[tokio::test]
async fn test_task_chunked_write_timeout() {
init_log();
let data = b"chunked data";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_secs(1000))
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_chunked();
body_writer.send_body_task(
Bytes::from_static(data),
Some(std::time::Duration::from_millis(50)),
);
let result = body_writer.write_current_body_task(&mut mock_io).await;
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.etype(), &WriteTimedout);
}
}
#[tokio::test]
async fn test_task_timeout_reset_on_new_task() {
init_log();
let data1 = b"first";
let data2 = b"second";
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data1.len() + data2.len());
body_writer.send_body_task(
Bytes::from_static(data1),
Some(std::time::Duration::from_millis(50)),
);
tokio::time::sleep(std::time::Duration::from_millis(30)).await;
body_writer.send_body_task(
Bytes::from_static(data2),
Some(std::time::Duration::from_millis(500)),
);
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(100))
.write(data2)
.build();
let result = body_writer.write_current_body_task(&mut mock_io).await;
assert!(
result.is_ok(),
"Second task should succeed with new timeout"
);
}
#[tokio::test]
async fn test_task_timeout_with_partial_writes() {
init_log();
let data1 = b"first";
let data2 = b"second";
let data3 = b"third";
let mut mock_io = Builder::new()
.wait(std::time::Duration::from_millis(10))
.write(data1)
.wait(std::time::Duration::from_millis(40))
.write(data2)
.wait(std::time::Duration::from_secs(1000))
.build();
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data1.len() + data2.len() + data3.len());
let mut total_written = 0;
body_writer.send_body_task(
Bytes::from_static(data1),
Some(std::time::Duration::from_millis(100)),
);
let result = body_writer.write_current_body_task(&mut mock_io).await;
assert!(result.is_ok());
total_written += result.unwrap().unwrap();
body_writer.send_body_task(
Bytes::from_static(data2),
Some(std::time::Duration::from_millis(100)),
);
let result = body_writer.write_current_body_task(&mut mock_io).await;
assert!(result.is_ok());
total_written += result.unwrap().unwrap();
body_writer.send_body_task(
Bytes::from_static(data3),
Some(std::time::Duration::from_millis(50)),
);
let result = body_writer.write_current_body_task(&mut mock_io).await;
assert!(result.is_err());
assert_eq!(result.unwrap_err().etype(), &WriteTimedout);
assert_eq!(total_written, data1.len() + data2.len());
assert!(
total_written < data1.len() + data2.len() + data3.len(),
"Should not have written all data"
);
}
#[tokio::test(start_paused = true)]
async fn cancel_safe_finish_task_chunked() {
init_log();
let data = Bytes::from("hello");
let expected_chunk = b"5\r\nhello\r\n";
let mock_io = Builder::new().write(expected_chunk).build();
let (mut flush_mock, flush_count) = FlushTrackingMock::new(mock_io);
let mut body_writer = BodyWriter::new();
body_writer.init_chunked();
body_writer.send_body_task(data, None);
body_writer
.write_current_body_task(&mut flush_mock)
.await
.unwrap();
assert_eq!(
FlushTrackingMock::flush_count(&flush_count),
1,
"Chunked body data write should flush once"
);
body_writer.send_finish_task();
assert!(body_writer.has_pending_finish_task());
let mock_io_finish = Builder::new()
.wait(std::time::Duration::from_millis(100))
.write(b"0\r\n\r\n")
.build();
let (mut flush_mock_finish, flush_count_finish) = FlushTrackingMock::new(mock_io_finish);
let mut cancel_count = 0;
loop {
if !body_writer.has_pending_finish_task() {
break;
}
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(10)) => {
cancel_count += 1;
}
result = body_writer.write_current_finish_task(&mut flush_mock_finish) => {
assert!(result.is_ok());
break;
}
}
}
assert!(cancel_count > 0, "Should have cancelled at least once");
assert!(matches!(body_writer.body_mode, BodyMode::Complete(_)));
assert_eq!(
FlushTrackingMock::flush_count(&flush_count_finish),
1,
"Chunked finish should flush exactly once"
);
}
#[tokio::test]
async fn finish_task_content_length() {
init_log();
let data = b"hello";
let mock_io = Builder::new().write(data).build();
let (mut flush_mock, flush_count) = FlushTrackingMock::new(mock_io);
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(data.len());
body_writer.send_body_task(Bytes::from_static(data), None);
body_writer
.write_current_body_task(&mut flush_mock)
.await
.unwrap();
assert_eq!(
FlushTrackingMock::flush_count(&flush_count),
1,
"Content-length body write should flush once (all bytes written)"
);
body_writer.send_finish_task();
let mock_io_finish = Builder::new().build();
let (mut flush_mock_finish, flush_count_finish) = FlushTrackingMock::new(mock_io_finish);
let result = body_writer
.write_current_finish_task(&mut flush_mock_finish)
.await;
assert!(result.is_ok());
assert!(matches!(body_writer.body_mode, BodyMode::Complete(_)));
assert_eq!(
FlushTrackingMock::flush_count(&flush_count_finish),
0,
"Content-length finish should not flush (no I/O needed)"
);
}
#[tokio::test]
async fn content_length_body_mode_tracks_across_chunks() {
init_log();
let chunk1 = b"Hello";
let chunk2 = b", World!";
let total_len = chunk1.len() + chunk2.len();
let mut mock_io = Builder::new().write(chunk1).write(chunk2).build();
let mut body_writer = BodyWriter::new();
body_writer.init_content_length(total_len);
body_writer.send_body_task(Bytes::from_static(chunk1), None);
let result = body_writer
.write_current_body_task(&mut mock_io)
.await
.unwrap();
assert_eq!(result, Some(chunk1.len()));
assert!(
!body_writer.finished(),
"Should not be finished after first chunk"
);
assert!(
matches!(body_writer.body_mode, BodyMode::ContentLength(total, written)
if total == total_len && written == chunk1.len()),
"body_mode should reflect bytes written so far, got: {:?}",
body_writer.body_mode
);
body_writer.send_body_task(Bytes::from_static(chunk2), None);
let result = body_writer
.write_current_body_task(&mut mock_io)
.await
.unwrap();
assert_eq!(result, Some(chunk2.len()));
assert!(
body_writer.finished(),
"Should be finished after all bytes written"
);
body_writer.send_finish_task();
let mut mock_io_finish = Builder::new().build();
let result = body_writer
.write_current_finish_task(&mut mock_io_finish)
.await;
assert!(
result.is_ok(),
"finish_task should succeed when all content-length bytes written"
);
assert!(matches!(body_writer.body_mode, BodyMode::Complete(_)));
}
}
#[cfg(test)]
mod test_poll_body_writer {
use super::*;
use bytes::Bytes;
use futures::task::noop_waker;
use std::collections::VecDeque;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::AsyncWrite;
struct MockWriter {
written: Vec<u8>,
next: VecDeque<Poll<io::Result<usize>>>,
vectored: bool,
}
impl MockWriter {
fn new(vectored: bool, results: Vec<Poll<io::Result<usize>>>) -> Self {
Self {
written: Vec::new(),
next: results.into(),
vectored,
}
}
}
impl AsyncWrite for MockWriter {
fn poll_write(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let r = self
.next
.pop_front()
.expect("MockWriter::poll_write: out of scripted results");
if let Poll::Ready(Ok(n)) = &r {
self.written.extend_from_slice(&buf[..*n]);
}
r
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
let r = self
.next
.pop_front()
.expect("MockWriter::poll_write_vectored: out of scripted results");
if let Poll::Ready(Ok(n)) = &r {
let mut remaining = *n;
for slice in bufs {
if remaining == 0 {
break;
}
let take = slice.len().min(remaining);
self.written.extend_from_slice(&slice[..take]);
remaining -= take;
}
}
r
}
fn is_write_vectored(&self) -> bool {
self.vectored
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
fn drive<R>(mut poll_fn: impl FnMut(&mut Context<'_>) -> Poll<R>) -> R {
const MAX_POLLS: usize = 1024;
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
for _ in 0..MAX_POLLS {
if let Poll::Ready(v) = poll_fn(&mut cx) {
return v;
}
}
panic!("drive(): poll closure did not complete after {MAX_POLLS} iterations");
}
#[test]
fn content_length_write_completes() {
let data = b"hello";
let mut mock = MockWriter::new(false, vec![Poll::Ready(Ok(5))]);
let mut bw = BodyWriter::new();
bw.init_content_length(data.len());
bw.send_body_task(Bytes::from_static(data), None);
let res = drive(|cx| bw.poll_write_current_body_task(cx, Pin::new(&mut mock)));
assert!(matches!(res, Ok(Some(5))));
assert_eq!(mock.written, data);
assert!(matches!(bw.body_mode, BodyMode::ContentLength(5, 5)));
}
#[test]
fn content_length_write_resumes_after_pending_and_short_write() {
let data = b"hello";
let mut mock = MockWriter::new(
false,
vec![Poll::Pending, Poll::Ready(Ok(2)), Poll::Ready(Ok(3))],
);
let mut bw = BodyWriter::new();
bw.init_content_length(data.len());
bw.send_body_task(Bytes::from_static(data), None);
let res = drive(|cx| bw.poll_write_current_body_task(cx, Pin::new(&mut mock)));
assert!(matches!(res, Ok(Some(5))));
assert_eq!(mock.written, data);
}
#[test]
fn chunked_write_uses_vectored_path() {
let payload = b"hello";
let mut mock = MockWriter::new(true, vec![Poll::Ready(Ok(10))]);
let mut bw = BodyWriter::new();
bw.init_chunked();
bw.send_body_task(Bytes::from_static(payload), None);
let res = drive(|cx| bw.poll_write_current_body_task(cx, Pin::new(&mut mock)));
assert!(matches!(res, Ok(Some(5)))); assert_eq!(mock.written, b"5\r\nhello\r\n");
assert!(mock.next.is_empty());
}
#[test]
fn chunked_write_resumes_on_short_write() {
let payload = b"hello";
let mut mock = MockWriter::new(
true,
vec![
Poll::Ready(Ok(2)), Poll::Ready(Ok(8)), ],
);
let mut bw = BodyWriter::new();
bw.init_chunked();
bw.send_body_task(Bytes::from_static(payload), None);
let res = drive(|cx| bw.poll_write_current_body_task(cx, Pin::new(&mut mock)));
assert!(matches!(res, Ok(Some(5))));
assert_eq!(mock.written, b"5\r\nhello\r\n");
assert!(mock.next.is_empty());
}
#[test]
fn until_close_write_completes() {
let data = b"abcdef";
let mut mock = MockWriter::new(false, vec![Poll::Ready(Ok(6))]);
let mut bw = BodyWriter::new();
bw.init_close_delimited();
bw.send_body_task(Bytes::from_static(data), None);
let res = drive(|cx| bw.poll_write_current_body_task(cx, Pin::new(&mut mock)));
assert!(matches!(res, Ok(Some(6))));
assert_eq!(mock.written, data);
}
#[test]
fn finish_chunked_writes_terminator() {
let mut mock = MockWriter::new(true, vec![Poll::Ready(Ok(5))]);
let mut bw = BodyWriter::new();
bw.init_chunked();
bw.body_mode = BodyMode::ChunkedEncoding(5);
bw.send_finish_task();
let res = drive(|cx| bw.poll_write_current_finish_task(cx, Pin::new(&mut mock)));
assert!(res.is_ok());
assert_eq!(mock.written, b"0\r\n\r\n");
assert!(matches!(bw.body_mode, BodyMode::Complete(5)));
}
#[test]
fn chunked_write_non_vectored_writer() {
let payload = b"hello";
let mut mock = MockWriter::new(
false,
vec![
Poll::Ready(Ok(3)), Poll::Ready(Ok(5)), Poll::Ready(Ok(2)), ],
);
let mut bw = BodyWriter::new();
bw.init_chunked();
bw.send_body_task(Bytes::from_static(payload), None);
let res = drive(|cx| bw.poll_write_current_body_task(cx, Pin::new(&mut mock)));
assert!(matches!(res, Ok(Some(5))));
assert_eq!(mock.written, b"5\r\nhello\r\n");
assert!(mock.next.is_empty());
}
#[test]
fn chunked_write_resumes_after_pending() {
let payload = b"hello";
let mut mock = MockWriter::new(
true,
vec![
Poll::Pending,
Poll::Ready(Ok(10)), ],
);
let mut bw = BodyWriter::new();
bw.init_chunked();
bw.send_body_task(Bytes::from_static(payload), None);
let res = drive(|cx| bw.poll_write_current_body_task(cx, Pin::new(&mut mock)));
assert!(matches!(res, Ok(Some(5))));
assert_eq!(mock.written, b"5\r\nhello\r\n");
assert!(mock.next.is_empty());
}
#[test]
fn chunked_write_two_frames_in_sequence() {
let mut mock = MockWriter::new(
true,
vec![
Poll::Ready(Ok(10)), Poll::Ready(Ok(10)), ],
);
let mut bw = BodyWriter::new();
bw.init_chunked();
bw.send_body_task(Bytes::from_static(b"hello"), None);
let res1 = drive(|cx| bw.poll_write_current_body_task(cx, Pin::new(&mut mock)));
assert!(matches!(res1, Ok(Some(5))));
bw.send_body_task(Bytes::from_static(b"world"), None);
let res2 = drive(|cx| bw.poll_write_current_body_task(cx, Pin::new(&mut mock)));
assert!(matches!(res2, Ok(Some(5))));
assert_eq!(mock.written, b"5\r\nhello\r\n5\r\nworld\r\n");
assert!(mock.next.is_empty());
}
#[test]
fn content_length_write_uses_vectored_path() {
let data = b"hello";
let mut mock = MockWriter::new(true, vec![Poll::Ready(Ok(5))]);
let mut bw = BodyWriter::new();
bw.init_content_length(data.len());
bw.send_body_task(Bytes::from_static(data), None);
let res = drive(|cx| bw.poll_write_current_body_task(cx, Pin::new(&mut mock)));
assert!(matches!(res, Ok(Some(5))));
assert_eq!(mock.written, data);
assert!(mock.next.is_empty());
}
}