diff --git a/Cargo.toml b/Cargo.toml index 63ed006..2b94780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,8 @@ base64 = { version = "0.22", default-features = false, features = ["std"] } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_urlencoded = { version = "0.7", default-features = false } lazy_static = { version = "1.5", default-features = false } +http = "1.2.0" +mime = "0.3.17" [build-dependencies] embuild = "0.33" \ No newline at end of file diff --git a/src/ota/mod.rs b/src/ota/mod.rs index 70b786d..3abd427 100644 --- a/src/ota/mod.rs +++ b/src/ota/mod.rs @@ -1 +1,105 @@ // TODO +use core::mem::size_of; + +use embedded_svc::http::Headers; +use http::header::ACCEPT; +use http::Uri; +use embedded_svc::ota::FirmwareInfo; +use embedded_svc::http::{client::Client, Method}; +use esp_idf_svc::http::client::{Configuration, EspHttpConnection}; +use esp_idf_svc::ota::EspOta; +use esp_idf_svc::sys::{EspError, ESP_ERR_IMAGE_INVALID, ESP_ERR_INVALID_RESPONSE}; + +/// Macro to quickly create EspError from an ESP_ERR_ constant. +#[macro_export] +macro_rules! esp_err { + ($x:ident) => { + EspError::from_infallible::<$x>() + }; +} + +// Config to use once https is enabled +// +//use esp_idf_svc::sys::esp_crt_bundle_attach; +// +//... +//Configuration { +// buffer_size: Some(1024 * 4), +// use_global_ca_store: true, +// crt_bundle_attach: Some(esp_crt_bundle_attach), +// ..Default::default() +//} + + +const FIRMWARE_DOWNLOAD_CHUNK_SIZE: usize = 1024 * 20; +// Not expect firmware bigger than 2MB +const FIRMWARE_MAX_SIZE: usize = 1024 * 1024 * 2; +const FIRMWARE_MIN_SIZE: usize = size_of::() + 1024; + +pub fn download_and_update_firmware(url: Uri) -> Result<(), EspError> { + let mut client = Client::wrap(EspHttpConnection::new(&Configuration { + buffer_size: Some(1024 * 4), + ..Default::default() + })?); + let headers = [(ACCEPT.as_str(), mime::APPLICATION_OCTET_STREAM.as_ref())]; + let surl = url.to_string(); + let request = client + .request(Method::Get, &surl, &headers) + .map_err(|e| e.0)?; + let mut response = request.submit().map_err(|e| e.0)?; + if response.status() != 200 { + log::info!("Bad HTTP response: {}", response.status()); + return Err(esp_err!(ESP_ERR_INVALID_RESPONSE)); + } + let file_size = response.content_len().unwrap_or(0) as usize; + if file_size <= FIRMWARE_MIN_SIZE { + log::info!( + "File size is {file_size}, too small to be a firmware! No need to proceed further." + ); + return Err(esp_err!(ESP_ERR_IMAGE_INVALID)); + } + if file_size > FIRMWARE_MAX_SIZE { + log::info!("File is too big ({file_size} bytes)."); + return Err(esp_err!(ESP_ERR_IMAGE_INVALID)); + } + let mut ota = EspOta::new()?; + let mut work = ota.initiate_update()?; + let mut buff = vec![0; FIRMWARE_DOWNLOAD_CHUNK_SIZE]; + let mut total_read_len: usize = 0; + let mut got_info = false; + let dl_result = loop { + let n = response.read(&mut buff).unwrap_or_default(); + total_read_len += n; + if !got_info { + match get_firmware_info(&buff[..n]) { + Ok(info) => log::info!("Firmware to be downloaded: {info:?}"), + Err(e) => { + log::error!("Failed to get firmware info from downloaded bytes!"); + break Err(e); + } + }; + got_info = true; + } + if n > 0 { + if let Err(e) = work.write(&buff[..n]) { + log::error!("Failed to write to OTA. {e}"); + break Err(e); + } + } + if total_read_len >= file_size { + break Ok(()); + } + }; + if dl_result.is_err() { + return work.abort(); + } + if total_read_len < file_size { + log::error!("Supposed to download {file_size} bytes, but we could only get {total_read_len}. May be network error?"); + return work.abort(); + } + work.complete() +} + +fn get_firmware_info(buff: &[u8]) -> Result<(), EspError>{ + todo!() +} \ No newline at end of file