io_http/rfc9112/
chunk_stream.rs1use core::mem;
8
9use alloc::{
10 string::{String, ToString},
11 vec::Vec,
12};
13
14use log::{debug, trace};
15use memchr::{memchr, memmem};
16use thiserror::Error;
17
18use crate::{coroutine::*, rfc9110::chars::CRLF};
19
20#[derive(Debug, Error)]
22pub enum Http11ChunksReadStreamError {
23 #[error("HTTP/1.1 read chunks failed: invalid chunk size `{0}`")]
25 InvalidChunkSize(String),
26}
27
28#[derive(Debug)]
31pub enum Http11ChunksReadStreamYield {
32 WantsRead,
35 Frame {
37 body: Vec<u8>,
39 },
40}
41
42#[derive(Debug, Default)]
43enum State {
44 #[default]
45 ChunkSize,
46 ChunkData(usize),
47}
48
49#[derive(Debug, Default)]
52pub struct Http11ChunksReadStream {
53 state: State,
54 wants_read: bool,
55 done: bool,
56 buf: Vec<u8>,
57}
58
59impl HttpCoroutine for Http11ChunksReadStream {
60 type Yield = Http11ChunksReadStreamYield;
61 type Return = Result<Vec<u8>, Http11ChunksReadStreamError>;
62
63 fn resume(&mut self, arg: Option<&[u8]>) -> HttpCoroutineState<Self::Yield, Self::Return> {
64 if let Some(data) = arg {
65 self.buf.extend_from_slice(data);
66 }
67
68 loop {
69 if self.wants_read {
70 self.wants_read = false;
71 return HttpCoroutineState::Yielded(Http11ChunksReadStreamYield::WantsRead);
72 }
73
74 if self.done {
75 let remaining = mem::take(&mut self.buf);
76 return HttpCoroutineState::Complete(Ok(remaining));
77 }
78
79 match self.state {
80 State::ChunkSize => {
81 let Some(crlf) = memmem::find(&self.buf, &CRLF) else {
82 self.wants_read = true;
83 continue;
84 };
85
86 let ext = match memchr(b';', &self.buf[..crlf]) {
87 None => crlf,
88 Some(ext) => {
89 let exts = String::from_utf8_lossy(self.buf[ext..crlf].trim_ascii());
90 trace!("ignore extension(s) `{exts}`");
91 ext
92 }
93 };
94
95 let chunk_size = String::from_utf8_lossy(self.buf[..ext].trim_ascii());
96
97 let Ok(n) = usize::from_str_radix(&chunk_size, 16) else {
98 let chunk_size = chunk_size.to_string();
99 let err = Http11ChunksReadStreamError::InvalidChunkSize(chunk_size);
100 return HttpCoroutineState::Complete(Err(err));
101 };
102
103 self.buf.drain(..crlf + CRLF.len());
104 debug!("reading chunk of {n} bytes");
105 self.state = State::ChunkData(n);
106 }
107 State::ChunkData(size) if self.buf.len() < size + CRLF.len() => {
108 trace!("received incomplete chunk data {}/{size}", self.buf.len());
109 self.wants_read = true;
110 continue;
111 }
112 State::ChunkData(0) => {
113 self.buf.drain(..CRLF.len());
114 self.state = State::ChunkSize;
115 self.done = true;
116 }
117 State::ChunkData(size) => {
118 let body: Vec<u8> = self.buf.drain(..size).collect();
119 self.buf.drain(..CRLF.len());
120 self.state = State::ChunkSize;
121 return HttpCoroutineState::Yielded(Http11ChunksReadStreamYield::Frame {
122 body,
123 });
124 }
125 }
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use alloc::vec;
133
134 use crate::{coroutine::*, rfc9112::chunk_stream::*};
135
136 #[test]
137 fn single_chunk() {
138 let frames = collect_all(b"5\r\nhello\r\n0\r\n\r\n");
139 assert_eq!(frames, vec![b"hello".to_vec()]);
140 }
141
142 #[test]
143 fn two_chunks_yielded_separately() {
144 let frames = collect_all(b"5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n");
145 assert_eq!(frames, vec![b"hello".to_vec(), b" world".to_vec()]);
146 }
147
148 #[test]
149 fn empty_body() {
150 let frames = collect_all(b"0\r\n\r\n");
151 assert!(frames.is_empty());
152 }
153
154 #[test]
155 fn ignored_extension() {
156 let frames = collect_all(b"5;ext\r\nHello\r\n0\r\n\r\n");
157 assert_eq!(frames, vec![b"Hello".to_vec()]);
158 }
159
160 #[test]
161 fn invalid_chunk_size() {
162 let mut coroutine = Http11ChunksReadStream::default();
163 let err = expect_complete_err(&mut coroutine, Some(b":\r\n0\r\n\r\n"));
164 let Http11ChunksReadStreamError::InvalidChunkSize(s) = err;
165 assert_eq!(s, ":");
166 }
167
168 #[test]
169 fn incomplete_chunk_size_then_resume() {
170 let mut coroutine = Http11ChunksReadStream::default();
171 expect_wants_read(&mut coroutine, Some(b"5\r"));
172 let body = expect_frame(&mut coroutine, Some(b"\nHello\r\n0\r\n\r\n"));
173 assert_eq!(body, b"Hello");
174 let remaining = expect_complete_ok(&mut coroutine, None);
175 assert!(remaining.is_empty());
176 }
177
178 #[test]
179 fn remaining_bytes_after_terminator() {
180 let mut coroutine = Http11ChunksReadStream::default();
181 let body = expect_frame(&mut coroutine, Some(b"5\r\nhello\r\n0\r\n\r\nNEXT"));
182 assert_eq!(body, b"hello");
183 let remaining = expect_complete_ok(&mut coroutine, None);
184 assert_eq!(remaining, b"NEXT");
185 }
186
187 fn collect_all(encoded: &[u8]) -> Vec<Vec<u8>> {
188 let mut coroutine = Http11ChunksReadStream::default();
189 let mut arg: Option<&[u8]> = Some(encoded);
190 let mut frames = Vec::new();
191
192 loop {
193 match coroutine.resume(arg.take()) {
194 HttpCoroutineState::Yielded(Http11ChunksReadStreamYield::Frame { body }) => {
195 frames.push(body);
196 }
197 HttpCoroutineState::Complete(Ok(remaining)) => {
198 assert!(remaining.is_empty(), "unexpected remaining bytes");
199 return frames;
200 }
201 state => panic!("expected Frame or Complete, got {state:?}"),
202 }
203 }
204 }
205
206 fn expect_wants_read(cor: &mut Http11ChunksReadStream, arg: Option<&[u8]>) {
207 match cor.resume(arg) {
208 HttpCoroutineState::Yielded(Http11ChunksReadStreamYield::WantsRead) => {}
209 state => panic!("expected WantsRead, got {state:?}"),
210 }
211 }
212
213 fn expect_frame(cor: &mut Http11ChunksReadStream, arg: Option<&[u8]>) -> Vec<u8> {
214 match cor.resume(arg) {
215 HttpCoroutineState::Yielded(Http11ChunksReadStreamYield::Frame { body }) => body,
216 state => panic!("expected Frame, got {state:?}"),
217 }
218 }
219
220 fn expect_complete_ok(cor: &mut Http11ChunksReadStream, arg: Option<&[u8]>) -> Vec<u8> {
221 match cor.resume(arg) {
222 HttpCoroutineState::Complete(Ok(remaining)) => remaining,
223 state => panic!("expected Complete(Ok), got {state:?}"),
224 }
225 }
226
227 fn expect_complete_err(
228 cor: &mut Http11ChunksReadStream,
229 arg: Option<&[u8]>,
230 ) -> Http11ChunksReadStreamError {
231 match cor.resume(arg) {
232 HttpCoroutineState::Complete(Err(err)) => err,
233 state => panic!("expected Complete(Err), got {state:?}"),
234 }
235 }
236}