1use crate::error::{Error, MultipartError};
25
26#[cfg_attr(feature = "_docs", aquamarine::aquamarine)]
33#[cfg(feature = "alloc")]
46pub mod flow {}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct MultipartHeader {
51 pub total: u16,
53 pub offset: u16,
55 pub fragment: u16,
57}
58
59impl MultipartHeader {
60 pub const WIRE_LEN: usize = 6;
62
63 pub const fn encode(self) -> [u8; Self::WIRE_LEN] {
65 let t = self.total.to_le_bytes();
66 let o = self.offset.to_le_bytes();
67 let f = self.fragment.to_le_bytes();
68 [t[0], t[1], o[0], o[1], f[0], f[1]]
69 }
70
71 pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
73 if bytes.len() < Self::WIRE_LEN {
74 return Err(Error::Truncated {
75 have: bytes.len(),
76 need: Self::WIRE_LEN,
77 });
78 }
79 Ok(Self {
80 total: u16::from_le_bytes([bytes[0], bytes[1]]),
81 offset: u16::from_le_bytes([bytes[2], bytes[3]]),
82 fragment: u16::from_le_bytes([bytes[4], bytes[5]]),
83 })
84 }
85
86 pub const fn is_abort(self) -> bool {
88 self.fragment == 0 && self.offset >= self.total
89 }
90}
91
92#[cfg(feature = "alloc")]
93mod alloc_impls {
94 use super::*;
95 use alloc::vec::Vec;
96
97 #[derive(Debug, Clone)]
99 pub struct MultipartTx<'a> {
100 body: &'a [u8],
101 offset: u16,
102 fragment_size: u16,
103 }
104
105 impl<'a> MultipartTx<'a> {
106 pub fn new(body: &'a [u8], fragment_size: u16) -> Result<Self, Error> {
124 if fragment_size == 0 {
125 return Err(Error::Multipart(MultipartError::BadFragmentSize));
126 }
127 if body.len() > u16::MAX as usize {
128 return Err(Error::BufferOverflow {
129 need: body.len(),
130 have: u16::MAX as usize,
131 });
132 }
133 Ok(Self {
134 body,
135 offset: 0,
136 fragment_size,
137 })
138 }
139
140 pub fn total(&self) -> u16 {
142 self.body.len() as u16
143 }
144
145 pub fn remaining(&self) -> u16 {
147 self.total().saturating_sub(self.offset)
148 }
149 }
150
151 impl<'a> Iterator for MultipartTx<'a> {
152 type Item = (MultipartHeader, &'a [u8]);
153
154 fn next(&mut self) -> Option<Self::Item> {
155 if self.offset as usize >= self.body.len() {
156 return None;
157 }
158 let take =
159 (self.body.len() - self.offset as usize).min(self.fragment_size as usize) as u16;
160 let frag = &self.body[self.offset as usize..self.offset as usize + take as usize];
161 let header = MultipartHeader {
162 total: self.body.len() as u16,
163 offset: self.offset,
164 fragment: take,
165 };
166 self.offset += take;
167 Some((header, frag))
168 }
169 }
170
171 #[derive(Debug, Clone, Default)]
173 pub struct MultipartRx {
174 buf: Vec<u8>,
175 total: Option<u16>,
176 }
177
178 impl MultipartRx {
179 pub fn new() -> Self {
181 Self::default()
182 }
183
184 pub fn reset(&mut self) {
186 self.buf.clear();
187 self.total = None;
188 }
189
190 pub fn push(
194 &mut self,
195 header: MultipartHeader,
196 data: &[u8],
197 ) -> Result<Option<Vec<u8>>, Error> {
198 if header.is_abort() {
199 self.reset();
200 return Err(Error::Multipart(MultipartError::Aborted));
201 }
202
203 if header.fragment as usize != data.len() {
204 return Err(Error::Multipart(MultipartError::BadFragmentSize));
205 }
206
207 match self.total {
208 None => {
209 if header.offset != 0 {
210 return Err(Error::Multipart(MultipartError::UnexpectedFirstOffset(
211 header.offset,
212 )));
213 }
214 self.total = Some(header.total);
215 self.buf.clear();
216 self.buf.reserve(header.total as usize);
217 }
218 Some(t) if t != header.total => {
219 return Err(Error::Multipart(MultipartError::InconsistentTotal {
220 first: t,
221 now: header.total,
222 }));
223 }
224 _ => {
225 if header.offset as usize != self.buf.len() {
226 return Err(Error::Multipart(MultipartError::OutOfOrderOffset {
227 expected: self.buf.len() as u16,
228 got: header.offset,
229 }));
230 }
231 }
232 }
233
234 if (header.offset as usize) + data.len() > header.total as usize {
235 return Err(Error::Multipart(MultipartError::OverflowsTotal));
236 }
237
238 self.buf.extend_from_slice(data);
239
240 if self.buf.len() == header.total as usize {
241 let total = header.total;
242 let mut out = core::mem::take(&mut self.buf);
243 self.total = None;
244 out.truncate(total as usize);
245 Ok(Some(out))
246 } else {
247 Ok(None)
248 }
249 }
250
251 pub fn progress(&self) -> usize {
253 self.buf.len()
254 }
255 }
256
257 #[cfg(test)]
258 mod tests {
259 use super::*;
260
261 #[test]
262 fn split_then_assemble() {
263 let body: Vec<u8> = (0u16..1024).flat_map(|n| n.to_le_bytes()).collect();
264 let tx = MultipartTx::new(&body, 200).unwrap();
265 let mut rx = MultipartRx::new();
266 let mut out = None;
267 for (h, frag) in tx {
268 if let Some(v) = rx.push(h, frag).unwrap() {
269 out = Some(v);
270 }
271 }
272 assert_eq!(out.unwrap(), body);
273 }
274
275 #[test]
276 fn reject_out_of_order() {
277 let mut rx = MultipartRx::new();
278 let h0 = MultipartHeader {
279 total: 8,
280 offset: 0,
281 fragment: 4,
282 };
283 rx.push(h0, &[0u8; 4]).unwrap();
284 let bogus = MultipartHeader {
285 total: 8,
286 offset: 6,
287 fragment: 2,
288 };
289 assert!(rx.push(bogus, &[0u8; 2]).is_err());
290 }
291
292 #[test]
293 fn reject_inconsistent_total() {
294 let mut rx = MultipartRx::new();
295 let h0 = MultipartHeader {
296 total: 8,
297 offset: 0,
298 fragment: 4,
299 };
300 rx.push(h0, &[0u8; 4]).unwrap();
301 let bogus = MultipartHeader {
302 total: 12,
303 offset: 4,
304 fragment: 4,
305 };
306 assert!(rx.push(bogus, &[0u8; 4]).is_err());
307 }
308
309 #[test]
310 fn abort_signal() {
311 let mut rx = MultipartRx::new();
312 let h0 = MultipartHeader {
313 total: 8,
314 offset: 0,
315 fragment: 4,
316 };
317 rx.push(h0, &[0u8; 4]).unwrap();
318 let abort = MultipartHeader {
319 total: 8,
320 offset: 8,
321 fragment: 0,
322 };
323 assert!(matches!(
324 rx.push(abort, &[]),
325 Err(Error::Multipart(MultipartError::Aborted))
326 ));
327 }
328
329 #[test]
330 fn header_roundtrip() {
331 let h = MultipartHeader {
332 total: 0x1234,
333 offset: 0x0050,
334 fragment: 0x0010,
335 };
336 let bytes = h.encode();
337 assert_eq!(MultipartHeader::decode(&bytes).unwrap(), h);
338 }
339 }
340}
341
342#[cfg(feature = "alloc")]
343pub use alloc_impls::{MultipartRx, MultipartTx};