use std::io::Read;
use std::time::Instant;
use nostd_multipart::{content_disposition, Encoder, Event, Parser, Part};
const FILE_LEN: u64 = 50 * 1024 * 1024;
const BOUNDARY: &[u8] = b"----nostd-multipart-example-6f2a";
const BUF: usize = 1024;
struct Gen(u32);
impl Gen {
fn next_byte(&mut self) -> u8 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
self.0 = x;
(x >> 24) as u8
}
}
struct UploadSource {
enc: Encoder<'static>,
stage: u8,
header: Vec<u8>,
header_pos: usize,
gen: Gen,
remaining: u64,
}
const PART: Part<'static> = Part {
name: b"firmware",
filename: Some(b"app.bin"),
content_type: Some(b"application/octet-stream"),
};
impl UploadSource {
fn new() -> Self {
let mut enc = Encoder::new(BOUNDARY).unwrap();
let mut header = vec![0u8; 256];
let (n, done) = enc.write_part_header(&PART, &mut header).unwrap();
assert!(done);
header.truncate(n);
UploadSource {
enc,
stage: 0,
header,
header_pos: 0,
gen: Gen(0x1234_5678),
remaining: FILE_LEN,
}
}
}
impl Read for UploadSource {
fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
match self.stage {
0 => {
let n = (self.header.len() - self.header_pos).min(out.len());
out[..n].copy_from_slice(&self.header[self.header_pos..self.header_pos + n]);
self.header_pos += n;
if self.header_pos == self.header.len() {
self.stage = 1;
}
Ok(n)
}
1 => {
let n = (self.remaining.min(out.len() as u64)) as usize;
for b in &mut out[..n] {
*b = self.gen.next_byte();
}
self.remaining -= n as u64;
if self.remaining == 0 {
self.stage = 2;
}
Ok(n)
}
2 => {
let (n, done) = self.enc.write_close(out);
if done {
self.stage = 3;
}
Ok(n)
}
_ => Ok(0),
}
}
}
fn main() {
let mut source = UploadSource::new();
let expected_len = Encoder::new(BOUNDARY)
.unwrap()
.content_length(&[(PART, FILE_LEN)]);
let mut parser: Parser<'_, 256, 8> = Parser::new(BOUNDARY).unwrap();
let mut buf = [0u8; BUF];
let (mut start, mut end, mut eof) = (0usize, 0usize, false);
assert!(BUF >= parser.min_buffer());
let mut check = Gen(0x1234_5678);
let mut file_bytes: u64 = 0;
let mut body_bytes: u64 = 0;
let mut is_firmware = false;
let mut parse_calls: u64 = 0;
let t0 = Instant::now();
loop {
parse_calls += 1;
let (consumed, ev) = parser.parse(&buf[start..end], eof).expect("malformed body");
start += consumed;
body_bytes += consumed as u64;
match ev {
Some(Event::Header { name, value })
if name.eq_ignore_ascii_case(b"content-disposition") =>
{
let cd = content_disposition(value).expect("bad Content-Disposition");
is_firmware = cd.name().is_some_and(|n| n == "firmware");
let mut fname = [0u8; 64];
let fname = cd
.filename()
.and_then(|f| f.decode_into(&mut fname))
.unwrap_or(b"");
println!(
"part name={:?} filename={:?}",
cd.name().map(|n| n.raw()),
std::str::from_utf8(fname)
);
}
Some(Event::Data(d)) if is_firmware => {
for &b in d {
assert_eq!(b, check.next_byte(), "data mismatch at byte {file_bytes}");
file_bytes += 1;
}
}
Some(Event::Finished) => break,
_ => {}
}
if consumed == 0 && ev.is_none() {
buf.copy_within(start..end, 0);
end -= start;
start = 0;
let n = source.read(&mut buf[end..]).unwrap();
if n == 0 {
eof = true;
}
end += n;
}
}
let secs = t0.elapsed().as_secs_f64();
println!("file bytes : {file_bytes} (expected {FILE_LEN})");
println!("body bytes : {body_bytes} (Content-Length {expected_len})");
println!("parse calls : {parse_calls}");
println!("buffer : {BUF} bytes");
println!(
"parser : {} bytes (size_of)",
core::mem::size_of::<Parser<'_, 256, 8>>()
);
println!(
"throughput : {:.0} MiB/s",
body_bytes as f64 / 1048576.0 / secs
);
assert_eq!(file_bytes, FILE_LEN);
assert_eq!(body_bytes, expected_len);
}