1use crate::error::{Error, MultipartError};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct MultipartHeader {
27 pub total: u16,
29 pub offset: u16,
31 pub fragment: u16,
33}
34
35impl MultipartHeader {
36 pub const WIRE_LEN: usize = 6;
38
39 pub const fn encode(self) -> [u8; Self::WIRE_LEN] {
41 let t = self.total.to_le_bytes();
42 let o = self.offset.to_le_bytes();
43 let f = self.fragment.to_le_bytes();
44 [t[0], t[1], o[0], o[1], f[0], f[1]]
45 }
46
47 pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
49 if bytes.len() < Self::WIRE_LEN {
50 return Err(Error::Truncated {
51 have: bytes.len(),
52 need: Self::WIRE_LEN,
53 });
54 }
55 Ok(Self {
56 total: u16::from_le_bytes([bytes[0], bytes[1]]),
57 offset: u16::from_le_bytes([bytes[2], bytes[3]]),
58 fragment: u16::from_le_bytes([bytes[4], bytes[5]]),
59 })
60 }
61
62 pub const fn is_abort(self) -> bool {
64 self.fragment == 0 && self.offset >= self.total
65 }
66}
67
68#[cfg(feature = "alloc")]
69mod alloc_impls {
70 use super::*;
71 use alloc::vec::Vec;
72
73 #[derive(Debug, Clone)]
75 pub struct MultipartTx<'a> {
76 body: &'a [u8],
77 offset: u16,
78 fragment_size: u16,
79 }
80
81 impl<'a> MultipartTx<'a> {
82 pub fn new(body: &'a [u8], fragment_size: u16) -> Result<Self, Error> {
86 if fragment_size == 0 {
87 return Err(Error::Multipart(MultipartError::BadFragmentSize));
88 }
89 if body.len() > u16::MAX as usize {
90 return Err(Error::BufferOverflow {
91 need: body.len(),
92 have: u16::MAX as usize,
93 });
94 }
95 Ok(Self {
96 body,
97 offset: 0,
98 fragment_size,
99 })
100 }
101
102 pub fn total(&self) -> u16 {
104 self.body.len() as u16
105 }
106
107 pub fn remaining(&self) -> u16 {
109 self.total().saturating_sub(self.offset)
110 }
111 }
112
113 impl<'a> Iterator for MultipartTx<'a> {
114 type Item = (MultipartHeader, &'a [u8]);
115
116 fn next(&mut self) -> Option<Self::Item> {
117 if self.offset as usize >= self.body.len() {
118 return None;
119 }
120 let take =
121 (self.body.len() - self.offset as usize).min(self.fragment_size as usize) as u16;
122 let frag = &self.body[self.offset as usize..self.offset as usize + take as usize];
123 let header = MultipartHeader {
124 total: self.body.len() as u16,
125 offset: self.offset,
126 fragment: take,
127 };
128 self.offset += take;
129 Some((header, frag))
130 }
131 }
132
133 #[derive(Debug, Clone, Default)]
135 pub struct MultipartRx {
136 buf: Vec<u8>,
137 total: Option<u16>,
138 }
139
140 impl MultipartRx {
141 pub fn new() -> Self {
143 Self::default()
144 }
145
146 pub fn reset(&mut self) {
148 self.buf.clear();
149 self.total = None;
150 }
151
152 pub fn push(
156 &mut self,
157 header: MultipartHeader,
158 data: &[u8],
159 ) -> Result<Option<Vec<u8>>, Error> {
160 if header.is_abort() {
161 self.reset();
162 return Err(Error::Multipart(MultipartError::Aborted));
163 }
164
165 if header.fragment as usize != data.len() {
166 return Err(Error::Multipart(MultipartError::BadFragmentSize));
167 }
168
169 match self.total {
170 None => {
171 if header.offset != 0 {
172 return Err(Error::Multipart(MultipartError::UnexpectedFirstOffset(
173 header.offset,
174 )));
175 }
176 self.total = Some(header.total);
177 self.buf.clear();
178 self.buf.reserve(header.total as usize);
179 }
180 Some(t) if t != header.total => {
181 return Err(Error::Multipart(MultipartError::InconsistentTotal {
182 first: t,
183 now: header.total,
184 }));
185 }
186 _ => {
187 if header.offset as usize != self.buf.len() {
188 return Err(Error::Multipart(MultipartError::OutOfOrderOffset {
189 expected: self.buf.len() as u16,
190 got: header.offset,
191 }));
192 }
193 }
194 }
195
196 if (header.offset as usize) + data.len() > header.total as usize {
197 return Err(Error::Multipart(MultipartError::OverflowsTotal));
198 }
199
200 self.buf.extend_from_slice(data);
201
202 if self.buf.len() == header.total as usize {
203 let total = header.total;
204 let mut out = core::mem::take(&mut self.buf);
205 self.total = None;
206 out.truncate(total as usize);
207 Ok(Some(out))
208 } else {
209 Ok(None)
210 }
211 }
212
213 pub fn progress(&self) -> usize {
215 self.buf.len()
216 }
217 }
218
219 #[cfg(test)]
220 mod tests {
221 use super::*;
222
223 #[test]
224 fn split_then_assemble() {
225 let body: Vec<u8> = (0u16..1024).flat_map(|n| n.to_le_bytes()).collect();
226 let tx = MultipartTx::new(&body, 200).unwrap();
227 let mut rx = MultipartRx::new();
228 let mut out = None;
229 for (h, frag) in tx {
230 if let Some(v) = rx.push(h, frag).unwrap() {
231 out = Some(v);
232 }
233 }
234 assert_eq!(out.unwrap(), body);
235 }
236
237 #[test]
238 fn reject_out_of_order() {
239 let mut rx = MultipartRx::new();
240 let h0 = MultipartHeader {
241 total: 8,
242 offset: 0,
243 fragment: 4,
244 };
245 rx.push(h0, &[0u8; 4]).unwrap();
246 let bogus = MultipartHeader {
247 total: 8,
248 offset: 6,
249 fragment: 2,
250 };
251 assert!(rx.push(bogus, &[0u8; 2]).is_err());
252 }
253
254 #[test]
255 fn reject_inconsistent_total() {
256 let mut rx = MultipartRx::new();
257 let h0 = MultipartHeader {
258 total: 8,
259 offset: 0,
260 fragment: 4,
261 };
262 rx.push(h0, &[0u8; 4]).unwrap();
263 let bogus = MultipartHeader {
264 total: 12,
265 offset: 4,
266 fragment: 4,
267 };
268 assert!(rx.push(bogus, &[0u8; 4]).is_err());
269 }
270
271 #[test]
272 fn abort_signal() {
273 let mut rx = MultipartRx::new();
274 let h0 = MultipartHeader {
275 total: 8,
276 offset: 0,
277 fragment: 4,
278 };
279 rx.push(h0, &[0u8; 4]).unwrap();
280 let abort = MultipartHeader {
281 total: 8,
282 offset: 8,
283 fragment: 0,
284 };
285 assert!(matches!(
286 rx.push(abort, &[]),
287 Err(Error::Multipart(MultipartError::Aborted))
288 ));
289 }
290
291 #[test]
292 fn header_roundtrip() {
293 let h = MultipartHeader {
294 total: 0x1234,
295 offset: 0x0050,
296 fragment: 0x0010,
297 };
298 let bytes = h.encode();
299 assert_eq!(MultipartHeader::decode(&bytes).unwrap(), h);
300 }
301 }
302}
303
304#[cfg(feature = "alloc")]
305pub use alloc_impls::{MultipartRx, MultipartTx};