From 6a2c8a67177e265a54cfba9a1d6bee617e575120 Mon Sep 17 00:00:00 2001 From: Nicolas Menard Date: Fri, 6 Sep 2024 11:07:33 -0400 Subject: [PATCH 1/3] add coredump reader --- micro-rdk/src/common/generic.rs | 2 + micro-rdk/src/common/registry.rs | 1 + micro-rdk/src/esp32/coredump.rs | 196 +++++++++++++++++++++++++++++++ micro-rdk/src/esp32/mod.rs | 1 + 4 files changed, 200 insertions(+) create mode 100644 micro-rdk/src/esp32/coredump.rs diff --git a/micro-rdk/src/common/generic.rs b/micro-rdk/src/common/generic.rs index 14c3115c6..8dd6d8fb1 100644 --- a/micro-rdk/src/common/generic.rs +++ b/micro-rdk/src/common/generic.rs @@ -22,6 +22,8 @@ pub static COMPONENT_NAME: &str = "generic"; pub enum GenericError { #[error("Generic: method {0} unimplemented")] MethodUnimplemented(&'static str), + #[error("Generic other error: {0}")] + Other(Box), } #[cfg(feature = "builtin-components")] pub(crate) fn register_models(registry: &mut ComponentRegistry) { diff --git a/micro-rdk/src/common/registry.rs b/micro-rdk/src/common/registry.rs index 5a7e195a8..39c795ae4 100755 --- a/micro-rdk/src/common/registry.rs +++ b/micro-rdk/src/common/registry.rs @@ -161,6 +161,7 @@ impl Default for ComponentRegistry { crate::esp32::encoder::register_models(&mut r); crate::esp32::hcsr04::register_models(&mut r); crate::esp32::single_encoder::register_models(&mut r); + crate::esp32::coredump::register_models(&mut r); } } r diff --git a/micro-rdk/src/esp32/coredump.rs b/micro-rdk/src/esp32/coredump.rs new file mode 100644 index 000000000..7dba9667c --- /dev/null +++ b/micro-rdk/src/esp32/coredump.rs @@ -0,0 +1,196 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use base64::{engine::general_purpose::STANDARD, Engine as _}; +use bytes::BytesMut; +use esp_idf_svc::sys::{ + esp_partition_erase_range, esp_partition_find_first, esp_partition_read, + esp_partition_subtype_t_ESP_PARTITION_SUBTYPE_DATA_COREDUMP, esp_partition_t, + esp_partition_type_t_ESP_PARTITION_TYPE_DATA, +}; + +use crate::{ + common::{ + config::ConfigType, + generic::{DoCommand, GenericError}, + registry::{ComponentRegistry, Dependency}, + sensor::{GenericReadingsResult, Readings, Sensor, SensorError, SensorType}, + status::Status, + }, + google::protobuf::{self, value::Kind, Struct, Value}, +}; + +pub struct Coredump { + len: usize, + coredump_partition_ptr: &'static esp_partition_t, +} + +pub(crate) fn register_models(registry: &mut ComponentRegistry) { + if registry + .register_sensor("coredump", &Coredump::from_config) + .is_err() + { + log::error!("couldn't register coredump sensor"); + } +} + +/// Instantiate a coredump sensor if : any partition of type data and subtype coredump can be found, we will use the first found partition is more than one exists (similar to the behavior of the coredump code in esp-idf) +/// A coredump is consider available if the 4th byte of the version field is not 0xFF (flash erase value) +/// The coredump is implemented as a sensor and can be downloaded through the DoCommand interface ( since GetReading cannot return a stream of bytes object) +impl Coredump { + pub fn from_config(_: ConfigType, _: Vec) -> Result { + let mut len = 0; + // if a pointer is found it will be stored as a static reference since it will remain valid for the lifetime of the program + let partition_iterator = unsafe { + esp_partition_find_first( + esp_partition_type_t_ESP_PARTITION_TYPE_DATA, + esp_partition_subtype_t_ESP_PARTITION_SUBTYPE_DATA_COREDUMP, + std::ptr::null(), + ) + .as_ref() + .ok_or(SensorError::SensorGenericError( + "no coredump partition found", + ))? + }; + // the first two fields of the coredump are length (uint32_t) and version (uint32_t) + let mut first_bytes = [0xFF_u8; 8]; + esp_idf_svc::sys::esp!(unsafe { + esp_partition_read(partition_iterator, 0, first_bytes.as_mut_ptr() as *mut _, 8) + })?; + + if first_bytes[7] != 0xFF { + len = usize::from_le_bytes(first_bytes[0..4].try_into().unwrap()); // safe because we would have read 4 bytes + } + + Ok(Arc::new(Mutex::new(Coredump { + len, + coredump_partition_ptr: partition_iterator, + }))) + } +} + +impl Sensor for Coredump {} + +impl Readings for Coredump { + fn get_generic_readings( + &mut self, + ) -> Result { + let has_coredump = self.len > 0; + let res = GenericReadingsResult::from([( + "has_coredump".to_owned(), + Value { + kind: Some(Kind::BoolValue(has_coredump)), + }, + )]); + Ok(res) + } +} + +impl Status for Coredump { + fn get_status( + &self, + ) -> Result, crate::common::status::StatusError> { + Ok(Some(protobuf::Struct { + fields: HashMap::new(), + })) + } +} + +struct Null; + +const CORE_FRAGMENT_SIZE: usize = 3072; + +impl DoCommand for Coredump { + fn do_command( + &mut self, + command_struct: Option, + ) -> Result, crate::common::generic::GenericError> { + if let Some(cmd) = command_struct { + // Coredump would be chunked into 4096 bytes encoded in Base64 + // len(bases64) = 4*(n/3) therefore n = (4096/4)*3 rounded down + // which yield n = 3072 + if cmd.fields.get("sizes").is_some() { + return Ok(Some(Struct { + fields: HashMap::from([ + ( + "nb_chunk".to_owned(), + Value { + kind: Some(Kind::NumberValue( + (self.len / CORE_FRAGMENT_SIZE) as f64, + )), + }, + ), + ( + "len".to_owned(), + Value { + kind: Some(Kind::NumberValue(self.len as f64)), + }, + ), + ]), + })); + } + if cmd.fields.get("erase_coredump").is_some() { + esp_idf_svc::sys::esp!(unsafe { + esp_partition_erase_range( + self.coredump_partition_ptr, + 0_usize, + self.coredump_partition_ptr.size as usize, + ) + }) + .map_err(|e| GenericError::Other(e.into()))?; + self.len = 0; + return Ok(None); + } + if let Some(Kind::NumberValue(val)) = cmd + .fields + .get("get_nth_chunk") + .and_then(|v| v.kind.as_ref()) + { + let offset = ((val.floor() as usize) * CORE_FRAGMENT_SIZE).min(self.len); + offset + .lt(&self.len) + .then_some(Null) + .ok_or(GenericError::Other( + "chunk requested outside of bounds".into(), + ))?; + + let to_read: usize = CORE_FRAGMENT_SIZE.min(self.len - offset); + let mut buf = BytesMut::with_capacity(CORE_FRAGMENT_SIZE); + + esp_idf_svc::sys::esp!(unsafe { + esp_partition_read( + self.coredump_partition_ptr, + offset, + buf.as_mut_ptr() as *mut _, + to_read, + ) + }) + .map_err(|e| GenericError::Other(e.into()))?; + unsafe { + buf.set_len(to_read); + } + + let as_b64 = STANDARD.encode(buf); + return Ok(Some(Struct { + fields: HashMap::from([ + ( + "chunk".to_owned(), + Value { + kind: Some(Kind::NumberValue(val.floor())), + }, + ), + ( + "payload".to_owned(), + Value { + kind: Some(Kind::StringValue(as_b64)), + }, + ), + ]), + })); + } + } + Err(GenericError::Other("couldn't parse request".into())) + } +} diff --git a/micro-rdk/src/esp32/mod.rs b/micro-rdk/src/esp32/mod.rs index 89be82273..565679784 100644 --- a/micro-rdk/src/esp32/mod.rs +++ b/micro-rdk/src/esp32/mod.rs @@ -32,3 +32,4 @@ pub mod nvs_storage; pub mod provisioning { pub mod wifi_provisioning; } +pub mod coredump; From b33810744cb096b1745e90d0745e24836334efa6 Mon Sep 17 00:00:00 2001 From: Nicolas Menard Date: Tue, 10 Sep 2024 12:03:01 -0400 Subject: [PATCH 2/3] add positive check --- micro-rdk/src/esp32/coredump.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/micro-rdk/src/esp32/coredump.rs b/micro-rdk/src/esp32/coredump.rs index 7dba9667c..c96eebd64 100644 --- a/micro-rdk/src/esp32/coredump.rs +++ b/micro-rdk/src/esp32/coredump.rs @@ -148,7 +148,14 @@ impl DoCommand for Coredump { .get("get_nth_chunk") .and_then(|v| v.kind.as_ref()) { - let offset = ((val.floor() as usize) * CORE_FRAGMENT_SIZE).min(self.len); + val.is_sign_positive() + .then_some(Null) + .ok_or(GenericError::Other( + "chunk requested outside of bounds".into(), + ))?; + + let offset = (val.floor() as usize) * CORE_FRAGMENT_SIZE; + offset .lt(&self.len) .then_some(Null) From aa8c55ccc90ff376c153104106a1112f2bb34de1 Mon Sep 17 00:00:00 2001 From: Nicolas Menard Date: Tue, 10 Sep 2024 14:21:59 -0400 Subject: [PATCH 3/3] comments --- micro-rdk/src/esp32/coredump.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/micro-rdk/src/esp32/coredump.rs b/micro-rdk/src/esp32/coredump.rs index c96eebd64..a4c1e3c17 100644 --- a/micro-rdk/src/esp32/coredump.rs +++ b/micro-rdk/src/esp32/coredump.rs @@ -39,6 +39,10 @@ pub(crate) fn register_models(registry: &mut ComponentRegistry) { /// Instantiate a coredump sensor if : any partition of type data and subtype coredump can be found, we will use the first found partition is more than one exists (similar to the behavior of the coredump code in esp-idf) /// A coredump is consider available if the 4th byte of the version field is not 0xFF (flash erase value) /// The coredump is implemented as a sensor and can be downloaded through the DoCommand interface ( since GetReading cannot return a stream of bytes object) +/// +/// To download a coredump you can either call DoCommand passing {"get_nth_chunk":n} where n is the chunk number until it returns an error (no more data available) or +/// first call DoCommand passing {"sizes":null} to get the number of chunks. +/// To erase the coredump call DoCommand passing{"erase_coredump":null} impl Coredump { pub fn from_config(_: ConfigType, _: Vec) -> Result { let mut len = 0; @@ -100,6 +104,9 @@ impl Status for Coredump { struct Null; +// Coredump would be chunked into 4096 bytes encoded in Base64 +// len(bases64) = 4*(n/3) therefore n = (4096/4)*3 rounded down +// which yield n = 3072 const CORE_FRAGMENT_SIZE: usize = 3072; impl DoCommand for Coredump { @@ -108,9 +115,6 @@ impl DoCommand for Coredump { command_struct: Option, ) -> Result, crate::common::generic::GenericError> { if let Some(cmd) = command_struct { - // Coredump would be chunked into 4096 bytes encoded in Base64 - // len(bases64) = 4*(n/3) therefore n = (4096/4)*3 rounded down - // which yield n = 3072 if cmd.fields.get("sizes").is_some() { return Ok(Some(Struct { fields: HashMap::from([