Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split body_writer into its constituent parts #84

Merged
merged 1 commit into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 3 additions & 135 deletions src/body_writer.rs → src/body_writer/buffering_chunked.rs
Original file line number Diff line number Diff line change
@@ -1,115 +1,10 @@
use core::mem::size_of;

use embedded_io::{Error as _, ErrorType};
use embedded_io_async::Write;

const NEWLINE: &[u8; 2] = b"\r\n";
const EMPTY_CHUNK: &[u8; 5] = b"0\r\n\r\n";

pub struct FixedBodyWriter<C: Write>(C, usize);

impl<C> FixedBodyWriter<C>
where
C: Write,
{
pub fn new(conn: C) -> Self {
Self(conn, 0)
}

pub fn written(&self) -> usize {
self.1
}
}

impl<C> ErrorType for FixedBodyWriter<C>
where
C: Write,
{
type Error = C::Error;
}

impl<C> Write for FixedBodyWriter<C>
where
C: Write,
{
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
let written = self.0.write(buf).await?;
self.1 += written;
Ok(written)
}

async fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
self.0.write_all(buf).await?;
self.1 += buf.len();
Ok(())
}

async fn flush(&mut self) -> Result<(), Self::Error> {
self.0.flush().await
}
}

pub struct ChunkedBodyWriter<C: Write>(C);

impl<C> ChunkedBodyWriter<C>
where
C: Write,
{
pub fn new(conn: C) -> Self {
Self(conn)
}

/// Terminate the request body by writing an empty chunk
pub async fn terminate(&mut self) -> Result<(), C::Error> {
self.0.write_all(EMPTY_CHUNK).await
}
}

impl<C> ErrorType for ChunkedBodyWriter<C>
where
C: Write,
{
type Error = embedded_io::ErrorKind;
}

impl<C> Write for ChunkedBodyWriter<C>
where
C: Write,
{
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.write_all(buf).await.map_err(|e| e.kind())?;
Ok(buf.len())
}

async fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
let len = buf.len();

// Do not write an empty chunk as that will terminate the body
// Use `ChunkedBodyWriter.write_empty_chunk` instead if this is intended
if len == 0 {
return Ok(());
}
use super::chunked::write_chunked_header;

// Write chunk header
let mut header_buf = [0; 2 * size_of::<usize>() + 2];
let header_len = write_chunked_header(&mut header_buf, len);
self.0
.write_all(&header_buf[..header_len])
.await
.map_err(|e| e.kind())?;

// Write chunk
self.0.write_all(buf).await.map_err(|e| e.kind())?;

// Write newline footer
self.0.write_all(NEWLINE).await.map_err(|e| e.kind())?;
Ok(())
}

async fn flush(&mut self) -> Result<(), Self::Error> {
self.0.flush().await.map_err(|e| e.kind())
}
}
const EMPTY_CHUNK: &[u8; 5] = b"0\r\n\r\n";
const NEWLINE: &[u8; 2] = b"\r\n";

/// A body writer that buffers internally and emits chunks as expected by the
/// `Transfer-Encoding: chunked` header specification.
Expand Down Expand Up @@ -294,16 +189,6 @@ const fn get_max_chunk_header_size(buffer_size: usize) -> usize {
}
}

fn write_chunked_header(buf: &mut [u8], chunk_len: usize) -> usize {
let mut hex = [0; 2 * size_of::<usize>()];
hex::encode_to_slice(chunk_len.to_be_bytes(), &mut hex).unwrap();
let leading_zeros = hex.iter().position(|x| *x != b'0').unwrap_or(hex.len() - 1);
let hex_chars = hex.len() - leading_zeros;
buf[..hex_chars].copy_from_slice(&hex[leading_zeros..]);
buf[hex_chars..hex_chars + NEWLINE.len()].copy_from_slice(NEWLINE);
hex_chars + 2
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -332,23 +217,6 @@ mod tests {
assert_eq!(4, get_max_chunk_header_size(0x12 + 2 + 2));
}

#[test]
fn can_write_chunked_header() {
let mut buf = [0; 4];

let len = write_chunked_header(&mut buf, 0x00);
assert_eq!(b"0\r\n", &buf[..len]);

let len = write_chunked_header(&mut buf, 0x01);
assert_eq!(b"1\r\n", &buf[..len]);

let len = write_chunked_header(&mut buf, 0x0F);
assert_eq!(b"f\r\n", &buf[..len]);

let len = write_chunked_header(&mut buf, 0x10);
assert_eq!(b"10\r\n", &buf[..len]);
}

#[tokio::test]
async fn preserves_already_written_bytes_in_the_buffer_without_any_chunks() {
// Given
Expand Down
101 changes: 101 additions & 0 deletions src/body_writer/chunked.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use core::mem::size_of;

use embedded_io::{Error, ErrorType};
use embedded_io_async::Write;

pub struct ChunkedBodyWriter<C: Write>(C);

const EMPTY_CHUNK: &[u8; 5] = b"0\r\n\r\n";
const NEWLINE: &[u8; 2] = b"\r\n";

impl<C> ChunkedBodyWriter<C>
where
C: Write,
{
pub fn new(conn: C) -> Self {
Self(conn)
}

/// Terminate the request body by writing an empty chunk
pub async fn terminate(&mut self) -> Result<(), C::Error> {
self.0.write_all(EMPTY_CHUNK).await
}
}

impl<C> ErrorType for ChunkedBodyWriter<C>
where
C: Write,
{
type Error = embedded_io::ErrorKind;
}

impl<C> Write for ChunkedBodyWriter<C>
where
C: Write,
{
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.write_all(buf).await.map_err(|e| e.kind())?;
Ok(buf.len())
}

async fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
let len = buf.len();

// Do not write an empty chunk as that will terminate the body
// Use `ChunkedBodyWriter.write_empty_chunk` instead if this is intended
if len == 0 {
return Ok(());
}

// Write chunk header
let mut header_buf = [0; 2 * size_of::<usize>() + 2];
let header_len = write_chunked_header(&mut header_buf, len);
self.0
.write_all(&header_buf[..header_len])
.await
.map_err(|e| e.kind())?;

// Write chunk
self.0.write_all(buf).await.map_err(|e| e.kind())?;

// Write newline footer
self.0.write_all(NEWLINE).await.map_err(|e| e.kind())?;
Ok(())
}

async fn flush(&mut self) -> Result<(), Self::Error> {
self.0.flush().await.map_err(|e| e.kind())
}
}

pub(super) fn write_chunked_header(buf: &mut [u8], chunk_len: usize) -> usize {
let mut hex = [0; 2 * size_of::<usize>()];
hex::encode_to_slice(chunk_len.to_be_bytes(), &mut hex).unwrap();
let leading_zeros = hex.iter().position(|x| *x != b'0').unwrap_or(hex.len() - 1);
let hex_chars = hex.len() - leading_zeros;
buf[..hex_chars].copy_from_slice(&hex[leading_zeros..]);
buf[hex_chars..hex_chars + NEWLINE.len()].copy_from_slice(NEWLINE);
hex_chars + 2
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn can_write_chunked_header() {
let mut buf = [0; 4];

let len = write_chunked_header(&mut buf, 0x00);
assert_eq!(b"0\r\n", &buf[..len]);

let len = write_chunked_header(&mut buf, 0x01);
assert_eq!(b"1\r\n", &buf[..len]);

let len = write_chunked_header(&mut buf, 0x0F);
assert_eq!(b"f\r\n", &buf[..len]);

let len = write_chunked_header(&mut buf, 0x10);
assert_eq!(b"10\r\n", &buf[..len]);
}
}
45 changes: 45 additions & 0 deletions src/body_writer/fixed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use embedded_io::ErrorType;
use embedded_io_async::Write;

pub struct FixedBodyWriter<C: Write>(C, usize);

impl<C> FixedBodyWriter<C>
where
C: Write,
{
pub fn new(conn: C) -> Self {
Self(conn, 0)
}

pub fn written(&self) -> usize {
self.1
}
}

impl<C> ErrorType for FixedBodyWriter<C>
where
C: Write,
{
type Error = C::Error;
}

impl<C> Write for FixedBodyWriter<C>
where
C: Write,
{
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
let written = self.0.write(buf).await?;
self.1 += written;
Ok(written)
}

async fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
self.0.write_all(buf).await?;
self.1 += buf.len();
Ok(())
}

async fn flush(&mut self) -> Result<(), Self::Error> {
self.0.flush().await
}
}
7 changes: 7 additions & 0 deletions src/body_writer/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod buffering_chunked;
mod chunked;
mod fixed;

pub use buffering_chunked::BufferingChunkedBodyWriter;
pub use chunked::ChunkedBodyWriter;
pub use fixed::FixedBodyWriter;
4 changes: 1 addition & 3 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use crate::body_writer::BufferingChunkedBodyWriter;
use crate::body_writer::ChunkedBodyWriter;
use crate::body_writer::FixedBodyWriter;
/// Client using embedded-nal-async traits to establish connections and perform HTTP requests.
///
use crate::body_writer::{BufferingChunkedBodyWriter, ChunkedBodyWriter, FixedBodyWriter};
use crate::headers::ContentType;
use crate::request::*;
use crate::response::*;
Expand Down
Loading