#![allow(clippy::len_zero)]
#![allow(clippy::manual_saturating_arithmetic)]
use futures_lite::AsyncBufRead;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use tracing::trace;
use crate::body::Body;
const CR: u8 = b'\r';
const LF: u8 = b'\n';
const CRLF_LEN: usize = 2;
#[derive(Debug)]
enum State {
Start,
EncodeChunks,
EndOfChunks,
ReceiveTrailers,
EncodeTrailers,
EndOfStream,
End,
}
#[derive(Debug)]
pub(crate) struct ChunkedEncoder {
bytes_written: usize,
state: State,
}
impl ChunkedEncoder {
pub(crate) fn new() -> Self {
Self {
state: State::Start,
bytes_written: 0,
}
}
pub(crate) fn encode(
&mut self,
body: &mut Body,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
self.bytes_written = 0;
match self.state {
State::Start => self.init(body, cx, buf),
State::EncodeChunks => self.encode_chunks(body, cx, buf),
State::EndOfChunks => self.encode_chunks_eos(body, cx, buf),
State::ReceiveTrailers => self.encode_trailers(body, cx, buf),
State::EncodeTrailers => self.encode_trailers(body, cx, buf),
State::EndOfStream => self.encode_eos(cx, buf),
State::End => Poll::Ready(Ok(0)),
}
}
fn set_state(&mut self, state: State) {
use State::*;
trace!("ChunkedEncoder state: {:?} -> {:?}", self.state, state);
#[cfg(debug_assertions)]
match self.state {
Start => assert!(matches!(state, EncodeChunks)),
EncodeChunks => assert!(matches!(state, EndOfChunks)),
EndOfChunks => assert!(matches!(state, ReceiveTrailers)),
ReceiveTrailers => assert!(matches!(state, EncodeTrailers | EndOfStream)),
EncodeTrailers => assert!(matches!(state, EndOfStream)),
EndOfStream => assert!(matches!(state, End)),
End => panic!("No state transitions allowed after the stream has ended"),
}
self.state = state;
}
fn init(
&mut self,
body: &mut Body,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
self.set_state(State::EncodeChunks);
self.encode_chunks(body, cx, buf)
}
fn encode_chunks(
&mut self,
mut body: &mut Body,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
let src = match Pin::new(&mut body).poll_fill_buf(cx) {
Poll::Ready(Ok(n)) => n,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => match self.bytes_written {
0 => return Poll::Pending,
n => return Poll::Ready(Ok(n)),
},
};
if src.len() == 0 {
self.set_state(State::EndOfChunks);
return self.encode_chunks_eos(body, cx, buf);
}
let buf_len = buf.len().checked_sub(self.bytes_written).unwrap_or(0);
let msg_len = src.len().min(buf_len);
let hex_len = ((msg_len + 1) as f64).log(16.0).ceil() as usize;
let framing_len = hex_len + CRLF_LEN * 2;
let buf_upper = buf_len.checked_sub(framing_len).unwrap_or(0);
let msg_len = msg_len.min(buf_upper);
let len_prefix = format!("{:X}", msg_len).into_bytes();
if buf.len() <= framing_len {
cx.waker().wake_by_ref();
return Poll::Ready(Ok(self.bytes_written));
}
let lower = self.bytes_written;
let upper = self.bytes_written + len_prefix.len();
buf[lower..upper].copy_from_slice(&len_prefix);
buf[upper] = CR;
buf[upper + 1] = LF;
self.bytes_written += len_prefix.len() + 2;
let lower = self.bytes_written;
let upper = self.bytes_written + msg_len;
buf[lower..upper].copy_from_slice(&src[0..msg_len]);
Pin::new(&mut body).consume(msg_len);
self.bytes_written += msg_len;
let idx = self.bytes_written;
buf[idx] = CR;
buf[idx + 1] = LF;
self.bytes_written += CRLF_LEN;
trace!("sending {} bytes", self.bytes_written);
Poll::Ready(Ok(self.bytes_written))
}
fn encode_chunks_eos(
&mut self,
body: &mut Body,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
if buf.len() < 3 {
cx.waker().wake_by_ref();
return Poll::Ready(Ok(self.bytes_written));
}
let idx = self.bytes_written;
buf[idx] = b'0';
buf[idx + 1] = CR;
buf[idx + 2] = LF;
self.bytes_written += 1 + CRLF_LEN;
self.set_state(State::ReceiveTrailers);
self.receive_trailers(body, cx, buf)
}
fn receive_trailers(
&mut self,
body: &mut Body,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
self.set_state(State::EncodeTrailers);
self.encode_trailers(body, cx, buf)
}
fn encode_trailers(
&mut self,
_body: &mut Body,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
self.set_state(State::EndOfStream);
self.encode_eos(cx, buf)
}
fn encode_eos(&mut self, _cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<io::Result<usize>> {
let idx = self.bytes_written;
buf[idx] = CR;
buf[idx + 1] = LF;
self.bytes_written += CRLF_LEN;
self.set_state(State::End);
Poll::Ready(Ok(self.bytes_written))
}
}