morpheus_network/transfer/
chunked.rsuse crate::error::{NetworkError, Result};
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecoderState {
ReadingSize,
ReadingData,
ExpectingCR,
ExpectingLF,
Done,
}
#[derive(Debug)]
pub struct ChunkedDecoder {
state: DecoderState,
size_buffer: Vec<u8>,
current_chunk_size: usize,
chunk_bytes_read: usize,
output: Vec<u8>,
}
impl ChunkedDecoder {
pub fn new() -> Self {
Self {
state: DecoderState::ReadingSize,
size_buffer: Vec::new(),
current_chunk_size: 0,
chunk_bytes_read: 0,
output: Vec::new(),
}
}
pub fn state(&self) -> DecoderState {
self.state
}
pub fn is_done(&self) -> bool {
self.state == DecoderState::Done
}
pub fn output(&self) -> &[u8] {
&self.output
}
pub fn take_output(self) -> Vec<u8> {
self.output
}
pub fn decode(data: &[u8]) -> Result<Vec<u8>> {
let mut decoder = ChunkedDecoder::new();
decoder.feed(data)?;
if !decoder.is_done() {
return Err(NetworkError::InvalidResponse);
}
Ok(decoder.take_output())
}
pub fn feed(&mut self, data: &[u8]) -> Result<usize> {
let mut consumed = 0;
while consumed < data.len() && self.state != DecoderState::Done {
let byte = data[consumed];
consumed += 1;
match self.state {
DecoderState::ReadingSize => {
if byte == b'\n'
&& !self.size_buffer.is_empty()
&& self.size_buffer.last() == Some(&b'\r')
{
self.size_buffer.pop(); self.parse_chunk_size()?;
} else {
self.size_buffer.push(byte);
}
}
DecoderState::ReadingData => {
self.output.push(byte);
self.chunk_bytes_read += 1;
if self.chunk_bytes_read == self.current_chunk_size {
self.state = DecoderState::ExpectingCR;
}
}
DecoderState::ExpectingCR => {
if byte == b'\r' {
self.state = DecoderState::ExpectingLF;
} else {
return Err(NetworkError::InvalidResponse);
}
}
DecoderState::ExpectingLF => {
if byte == b'\n' {
self.state = DecoderState::ReadingSize;
} else {
return Err(NetworkError::InvalidResponse);
}
}
DecoderState::Done => break,
}
}
Ok(consumed)
}
fn parse_chunk_size(&mut self) -> Result<()> {
let size_str =
core::str::from_utf8(&self.size_buffer).map_err(|_| NetworkError::InvalidResponse)?;
let size_part = size_str.split(';').next().unwrap_or("").trim();
self.current_chunk_size =
usize::from_str_radix(size_part, 16).map_err(|_| NetworkError::InvalidResponse)?;
self.size_buffer.clear();
self.chunk_bytes_read = 0;
if self.current_chunk_size == 0 {
self.state = DecoderState::Done;
} else {
self.state = DecoderState::ReadingData;
}
Ok(())
}
pub fn reset(&mut self) {
self.state = DecoderState::ReadingSize;
self.size_buffer.clear();
self.current_chunk_size = 0;
self.chunk_bytes_read = 0;
self.output.clear();
}
}
impl Default for ChunkedDecoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[test]
fn test_decode_single_chunk() {
let data = b"5\r\nHello\r\n0\r\n\r\n";
let result = ChunkedDecoder::decode(data).unwrap();
assert_eq!(result, b"Hello");
}
#[test]
fn test_decode_multiple_chunks() {
let data = b"5\r\nHello\r\n6\r\n World\r\n0\r\n\r\n";
let result = ChunkedDecoder::decode(data).unwrap();
assert_eq!(result, b"Hello World");
}
#[test]
fn test_decode_empty_body() {
let data = b"0\r\n\r\n";
let result = ChunkedDecoder::decode(data).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_decode_hex_lowercase() {
let data = b"a\r\n0123456789\r\n0\r\n\r\n";
let result = ChunkedDecoder::decode(data).unwrap();
assert_eq!(result.len(), 10);
}
#[test]
fn test_decode_hex_uppercase() {
let data = b"A\r\n0123456789\r\n0\r\n\r\n";
let result = ChunkedDecoder::decode(data).unwrap();
assert_eq!(result.len(), 10);
}
#[test]
fn test_decode_large_chunk_size() {
let data = b"10\r\n0123456789ABCDEF\r\n0\r\n\r\n";
let result = ChunkedDecoder::decode(data).unwrap();
assert_eq!(result, b"0123456789ABCDEF");
}
#[test]
fn test_decode_with_chunk_extension() {
let data = b"5;name=value\r\nHello\r\n0\r\n\r\n";
let result = ChunkedDecoder::decode(data).unwrap();
assert_eq!(result, b"Hello");
}
#[test]
fn test_incremental_feed() {
let mut decoder = ChunkedDecoder::new();
decoder.feed(b"5\r\nHel").unwrap();
assert!(!decoder.is_done());
decoder.feed(b"lo\r\n0\r\n\r\n").unwrap();
assert!(decoder.is_done());
assert_eq!(decoder.output(), b"Hello");
}
#[test]
fn test_incremental_byte_by_byte() {
let mut decoder = ChunkedDecoder::new();
let data = b"3\r\nABC\r\n0\r\n\r\n";
for &byte in data.iter() {
decoder.feed(&[byte]).unwrap();
}
assert!(decoder.is_done());
assert_eq!(decoder.output(), b"ABC");
}
#[test]
fn test_initial_state() {
let decoder = ChunkedDecoder::new();
assert_eq!(decoder.state(), DecoderState::ReadingSize);
assert!(!decoder.is_done());
}
#[test]
fn test_done_state() {
let data = b"0\r\n\r\n";
let mut decoder = ChunkedDecoder::new();
decoder.feed(data).unwrap();
assert_eq!(decoder.state(), DecoderState::Done);
assert!(decoder.is_done());
}
#[test]
fn test_reset() {
let mut decoder = ChunkedDecoder::new();
decoder.feed(b"5\r\nHello\r\n0\r\n\r\n").unwrap();
assert!(decoder.is_done());
assert!(!decoder.output().is_empty());
decoder.reset();
assert!(!decoder.is_done());
assert!(decoder.output().is_empty());
assert_eq!(decoder.state(), DecoderState::ReadingSize);
}
#[test]
fn test_take_output() {
let data = b"5\r\nHello\r\n0\r\n\r\n";
let mut decoder = ChunkedDecoder::new();
decoder.feed(data).unwrap();
let output = decoder.take_output();
assert_eq!(output, b"Hello");
}
#[test]
fn test_decode_incomplete() {
let data = b"5\r\nHel"; let result = ChunkedDecoder::decode(data);
assert!(result.is_err());
}
#[test]
fn test_invalid_hex() {
let data = b"XYZ\r\ndata\r\n0\r\n\r\n";
let result = ChunkedDecoder::decode(data);
assert!(result.is_err());
}
#[test]
fn test_typical_response() {
let chunk1 = b"<!DOCTYPE html>\n<html><body>";
let chunk2 = b"</body></html>";
let mut data = Vec::new();
data.extend_from_slice(format!("{:x}\r\n", chunk1.len()).as_bytes());
data.extend_from_slice(chunk1);
data.extend_from_slice(b"\r\n");
data.extend_from_slice(format!("{:x}\r\n", chunk2.len()).as_bytes());
data.extend_from_slice(chunk2);
data.extend_from_slice(b"\r\n0\r\n\r\n");
let result = ChunkedDecoder::decode(&data).unwrap();
let html = core::str::from_utf8(&result).unwrap();
assert!(html.starts_with("<!DOCTYPE html>"));
assert!(html.ends_with("</body></html>"));
}
#[test]
fn test_binary_data() {
let chunk_data = [0x00, 0xFF, 0x7F, 0x80, 0x01];
let mut data = Vec::new();
data.extend_from_slice(b"5\r\n");
data.extend_from_slice(&chunk_data);
data.extend_from_slice(b"\r\n0\r\n\r\n");
let result = ChunkedDecoder::decode(&data).unwrap();
assert_eq!(result, chunk_data);
}
}