From 5e0ba37bc74bce622e1ed7c54ab4ff9759367615 Mon Sep 17 00:00:00 2001 From: Tobias Maier Date: Tue, 18 Feb 2025 20:47:31 +0000 Subject: [PATCH] added method to upload filesystem images --- src/database.rs | 10 ++---- src/device_telemetry_api.rs | 20 +++++++---- src/firmware_api.rs | 67 ++++++++++++++++++++++++++----------- src/main.rs | 8 ++--- src/schemas.rs | 10 ++++++ src/util.rs | 6 ++-- 6 files changed, 81 insertions(+), 40 deletions(-) diff --git a/src/database.rs b/src/database.rs index 4ff2f40..e56ff50 100644 --- a/src/database.rs +++ b/src/database.rs @@ -227,13 +227,11 @@ mod tests { let db = Database::init_from_pool(pool); let device_id = MacAddress::from([0x1A, 0x2B, 0x3C, 0x4D, 0x5E, 0x6F]); - db.add_device(&device_id) - .await - .unwrap(); - let msg = ValueMessageFromDevice{ + db.add_device(&device_id).await.unwrap(); + let msg = ValueMessageFromDevice { active_errors: 0, value: 112.0, - value_id: 1 + value_id: 1, }; db.add_value(&msg, &device_id).await.unwrap(); @@ -242,6 +240,4 @@ mod tests { assert!((values[0].value - msg.value).abs() < 1e-5); assert_eq!(values[0].value_id, msg.value_id); } - - } diff --git a/src/device_telemetry_api.rs b/src/device_telemetry_api.rs index 8eb460a..8d97647 100644 --- a/src/device_telemetry_api.rs +++ b/src/device_telemetry_api.rs @@ -1,9 +1,11 @@ - use actix_web::{get, post, put, web, HttpResponse, Responder}; use log::{error, info}; use sqlx::types::mac_address::MacAddress; -use crate::{schemas::{AppState, DeviceMetadata, TelemetryMessageFromDevice, ValueMessageFromDevice}, util::parse_mac_address}; +use crate::{ + schemas::{AppState, DeviceMetadata, TelemetryMessageFromDevice, ValueMessageFromDevice}, + util::parse_mac_address, +}; #[post("/telemetry/{device_id}")] async fn receive_telemetry( @@ -113,8 +115,12 @@ async fn get_devices(data: web::Data) -> impl Responder { } #[put("/device/{device_id}/display_name")] -async fn set_display_name(data: web::Data, device_id: web::Path, device_data: web::Json) -> impl Responder { - info!("PUT - device/{device_id} - Display Name", ); +async fn set_display_name( + data: web::Data, + device_id: web::Path, + device_data: web::Json, +) -> impl Responder { + info!("PUT - device/{device_id} - Display Name",); let display_name = device_data.0.display_name; let db = &data.db; let Ok(mac_converted) = parse_mac_address(&device_id) else { @@ -122,10 +128,10 @@ async fn set_display_name(data: web::Data, device_id: web::Path, - path: web::Path<(String, String, String)>, + path: web::Path<(String, String, String, String)>, body: web::Bytes, ) -> impl Responder { - let (device, config, version) = path.into_inner(); + let (service, device, config, version) = path.into_inner(); let version = version.replace('.', "-"); let firmware_root_path = &data.firmwares_path; + let Ok(service) = Services::from_str(&service) else { + return HttpResponse::NotFound().finish(); + }; - let firmware_folder = firmware_root_path.join(&device); + let firmware_folder = match service { + Services::Firmware => firmware_root_path.join(&device), + Services::Filesystem => firmware_root_path.join(format!("{}_filesystem", &device)) + }; let firmware_path = firmware_folder - .join(format!("firmware_{config}_{version}")) + .join(format!("{service}_{}_{version}", config.to_lowercase())) .with_extension("bin"); if firmware_path.is_file() { - warn!("Firmware with product: {device}, config: {config} and version: {version} at path {firmware_path:?} already exists, cant upload"); + warn!("{service} with product: {device}, config: {config} and version: {version} at path {firmware_path:?} already exists, cant upload"); return HttpResponse::Conflict().body(format!("{firmware_path:?}")); } - info!("Uploading firmware with product: {device}, config: {config} and version: {version} to {firmware_path:?}"); + info!("Uploading {service} with product: {device}, config: {config} and version: {version} to {firmware_path:?}"); fs::create_dir_all(&firmware_folder).unwrap(); let x = tokio::fs::write(&firmware_path, &body).await; debug!("{x:?}"); - HttpResponse::Ok().body(format!( - "Firmware version {version} uploaded successfully" - )) + HttpResponse::Ok().body(format!("Firmware version {version} uploaded successfully")) } -#[get("/firmware/{device}")] -async fn get_firmware_json(data: web::Data, path: web::Path) -> impl Responder { - let device = path.into_inner(); - let fw_path = &data.firmwares_path.join(device); +#[get("/{service}/{device}")] +async fn get_firmware_json( + data: web::Data, + path: web::Path<(String, String)>, +) -> impl Responder { + let (service, device) = path.into_inner(); + let Ok(firmware_or_filesystem) = Services::from_str(&service) else { + return HttpResponse::NotFound().finish(); + }; + + let fw_path = match firmware_or_filesystem { + Services::Firmware => &data.firmwares_path.join(device), + Services::Filesystem => &data.firmwares_path.join(format!("{device}_filesystem")) + }; + if fw_path.is_dir() { match get_files(fw_path, &data.hostname) { Ok(cfg) => HttpResponse::Ok().json(OTAConfigurationList { @@ -56,16 +75,26 @@ async fn get_firmware_json(data: web::Data, path: web::Path) - } } -#[get("/firmware/{product}/{config}/{version}.bin")] +#[get("/{service}/{product}/{config}/{version}.bin")] async fn serve_firmware( - path: web::Path<(String, String, String)>, + path: web::Path<(String, String, String, String)>, data: web::Data, ) -> impl Responder { - let (product, config, version) = path.into_inner(); + let (service, product, config, version) = path.into_inner(); let version = version.replace(['.', '_'], "-"); + let Ok(service) = Services::from_str(&service) else { + return HttpResponse::NotFound().finish(); + }; let fw_root_path = &data.firmwares_path; - let file_path = PathBuf::from(format!("{product}/firmware_{config}_{version}.bin")); + + + let file_path = match service { + Services::Firmware => PathBuf::from(format!("{product}/firmware_{config}_{version}.bin")), + Services::Filesystem => PathBuf::from(format!("{product}/filesystem_{config}_{version}.bin")), + }; + let file_path = fw_root_path.join(&file_path); + info!("Requested firmware for product: {product}, config: {config} and version: {version}, expected to be stored at {file_path:?}"); diff --git a/src/main.rs b/src/main.rs index f85e78a..58b4807 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,11 +7,10 @@ use log::{error, info}; use schemas::AppState; mod database; +mod device_telemetry_api; +mod firmware_api; mod schemas; mod util; -mod firmware_api; -mod device_telemetry_api; - #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -43,7 +42,7 @@ async fn main() -> std::io::Result<()> { firmwares_path: PathBuf::from(firmware_path.clone()), hostname: external_url.clone(), })) - .app_data(web::PayloadConfig::new(256 * 1024 * 1024)) //256MB + .app_data(web::PayloadConfig::new(1 * 1024 * 1024 * 1024)) //1GB .service(device_telemetry_api::receive_telemetry) .service(device_telemetry_api::get_telemetry) .service(device_telemetry_api::receive_value) @@ -57,4 +56,3 @@ async fn main() -> std::io::Result<()> { .run() .await } - diff --git a/src/schemas.rs b/src/schemas.rs index b5ed81a..ee95776 100644 --- a/src/schemas.rs +++ b/src/schemas.rs @@ -87,12 +87,15 @@ pub struct OTAConfiguration { #[strum(serialize_all = "snake_case")] pub enum BoardType { Waterlevel, + WaterlevelFilesystem, } #[derive(serde::Serialize, EnumString, PartialEq, Debug, Display)] +#[strum(ascii_case_insensitive, serialize_all = "snake_case")] pub enum BoardConfig { INA226, INA233, + Generic, } pub struct AppState { @@ -104,4 +107,11 @@ pub struct AppState { #[derive(Serialize, Deserialize, Debug)] pub struct DeviceMetadata { pub display_name: String, +} + +#[derive(EnumString, PartialEq, Debug, Display, Serialize)] +#[strum(ascii_case_insensitive, serialize_all = "snake_case")] +pub enum Services { + Firmware, + Filesystem, } \ No newline at end of file diff --git a/src/util.rs b/src/util.rs index 119b3fc..dc60aa4 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,6 @@ use std::{fs, path::PathBuf}; -use log::info; +use log::{debug, info}; use std::str::FromStr; use thiserror::Error; @@ -69,11 +69,13 @@ pub fn get_files( .strip_suffix(".bin") .ok_or(GetFilesError::Extension)? .replace('-', "."); + // TODO this is kinda messy let board_config = BoardConfig::from_str(split_name[1])?; + let service = split_name[0]; let board_type = BoardType::from_str(&product_name).unwrap(); let version_replaced = version.replace('.', "_"); let fw_url = - format!("{hostname}/firmware/{board_type}/{board_config}/{version_replaced}.bin"); + format!("{hostname}/{service}/{board_type}/{board_config}/{version_replaced}.bin"); let cfg = OTAConfiguration { version: version.to_string(), url: fw_url,