/ firmware / src / services / http / upload.rs
upload.rs
 1  use core::fmt::Write;
 2  
 3  use alloc::string::String as AllocString;
 4  use embassy_time::Duration;
 5  use heapless::String as HeaplessString;
 6  use picoserve::response::{IntoResponse, StatusCode};
 7  
 8  use crate::filesystems::sd::is_supported_flat_file_name;
 9  
10  const CHUNK_SIZE: usize = 4096;
11  
12  pub struct FileUploadService;
13  
14  impl picoserve::routing::RequestHandlerService<(), (AllocString,)> for FileUploadService {
15      async fn call_request_handler_service<
16          R: picoserve::io::Read,
17          W: picoserve::response::ResponseWriter<Error = R::Error>,
18      >(
19          &self,
20          _state: &(),
21          (file_name,): (AllocString,),
22          mut request: picoserve::request::Request<'_, R>,
23          response_writer: W,
24      ) -> Result<picoserve::ResponseSent, W::Error> {
25          if !is_supported_flat_file_name(&file_name) {
26              return (StatusCode::BAD_REQUEST, "invalid path\n")
27                  .write_to(request.body_connection.finalize().await?, response_writer)
28                  .await;
29          }
30  
31          use picoserve::io::Read;
32  
33          let content_length = request.body_connection.body().content_length();
34          let timeout_secs = (content_length / 10_000).max(60) as u64;
35          let timeout = Duration::from_secs(timeout_secs);
36  
37          let mut reader = request
38              .body_connection
39              .body()
40              .reader()
41              .with_different_timeout(timeout);
42  
43          let mut chunk = [0u8; CHUNK_SIZE];
44          let mut offset = 0u32;
45  
46          loop {
47              let mut filled = 0;
48              while filled < CHUNK_SIZE {
49                  let bytes_read = reader.read(&mut chunk[filled..]).await?;
50                  if bytes_read == 0 {
51                      break;
52                  }
53                  filled += bytes_read;
54              }
55  
56              if filled == 0 {
57                  break;
58              }
59  
60              if let Err(error_message) =
61                  crate::filesystems::sd::write_file_chunk(&file_name, offset, &chunk[..filled])
62              {
63                  let mut error_response = HeaplessString::<128>::new();
64                  let _ = write!(
65                      error_response,
66                      "write failed at byte {}: {}\n",
67                      offset,
68                      error_message
69                  );
70                  return (StatusCode::INTERNAL_SERVER_ERROR, error_response)
71                      .write_to(request.body_connection.finalize().await?, response_writer)
72                      .await;
73              }
74              offset += filled as u32;
75          }
76  
77          let mut success_response = HeaplessString::<64>::new();
78          let _ = write!(success_response, "ok {} bytes\n", offset);
79          (StatusCode::OK, success_response)
80              .write_to(request.body_connection.finalize().await?, response_writer)
81              .await
82      }
83  }