diff --git a/Cargo.lock b/Cargo.lock index ddf028c2..141f9b07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2754,6 +2754,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "snap", "socket2", "stun_codec", "test-log", @@ -4156,6 +4157,12 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + [[package]] name = "socket2" version = "0.5.8" diff --git a/Cargo.toml b/Cargo.toml index 0f140a9d..7ad21688 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,6 +113,7 @@ secrecy = { version = "~0.8.0", features = ["serde"] } serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" sha2 = "0.10.8" +snap = "1.1.1" socket2 = "0.5.8" stun_codec = { version = "0.3.0" , git = "https://github.com/viamrobotics/stun_codec"} syn = "2.0.90" diff --git a/micro-rdk/Cargo.toml b/micro-rdk/Cargo.toml index c4cb24e0..689286fa 100644 --- a/micro-rdk/Cargo.toml +++ b/micro-rdk/Cargo.toml @@ -89,6 +89,7 @@ stun_codec.workspace = true thiserror.workspace = true trackable.workspace = true uuid = { workspace = true, features = ["v4"]} +snap.workspace = true [build-dependencies] embuild.workspace = true diff --git a/micro-rdk/src/esp32/nvs_storage.rs b/micro-rdk/src/esp32/nvs_storage.rs index 46f4b612..c9641bc4 100644 --- a/micro-rdk/src/esp32/nvs_storage.rs +++ b/micro-rdk/src/esp32/nvs_storage.rs @@ -2,7 +2,14 @@ use bytes::Bytes; use hyper::{http::uri::InvalidUri, Uri}; use prost::{DecodeError, Message}; -use std::{cell::RefCell, ffi::CString, num::NonZeroI32, rc::Rc}; +use snap::{read::FrameDecoder, write::FrameEncoder}; +use std::{ + cell::RefCell, + ffi::CString, + io::{Read, Write}, + num::NonZeroI32, + rc::Rc, +}; use thiserror::Error; use crate::{ @@ -34,6 +41,8 @@ pub enum NVSStorageError { NVSValueDecodeError(#[from] DecodeError), #[error(transparent)] NVSUriParseError(#[from] InvalidUri), + #[error(transparent)] + NVSRobotConfigCompressionError(#[from] std::io::Error), } #[derive(Clone)] @@ -246,13 +255,44 @@ impl RobotConfigurationStorage for NVSStorage { } fn store_robot_configuration(&self, cfg: &RobotConfig) -> Result<(), Self::Error> { - self.set_blob(NVS_ROBOT_CONFIG_KEY, cfg.encode_to_vec().into())?; + // Normally, we would just rely on blob comparison in + // `NVSStorage::set_blob` to dedup the write, but if snappy + // compression of the robot config isn't entirely + // deterministic, then that protection would be defeated. So, + // check the preimages here instead, and if they are + // equivalent, skip writing. + if self.has_robot_configuration() { + if let Ok(cached_cfg) = self.get_robot_configuration() { + if cached_cfg == *cfg { + return Ok(()); + } + } + } + + let encoded_cfg = cfg.encode_to_vec(); + let mut compressor = FrameEncoder::new(Vec::new()); + compressor + .write_all(&encoded_cfg[..]) + .map_err(NVSStorageError::NVSRobotConfigCompressionError)?; + let compressed = compressor + .into_inner() + .map_err(|e| NVSStorageError::NVSRobotConfigCompressionError(e.into_error()))?; + log::info!( + "robot configuration compressed for NVS storage: {} bytes before, {} bytes after", + encoded_cfg.len(), + compressed.len() + ); + self.set_blob(NVS_ROBOT_CONFIG_KEY, compressed.into())?; Ok(()) } fn get_robot_configuration(&self) -> Result { - let robot_config = self.get_blob(NVS_ROBOT_CONFIG_KEY)?; - RobotConfig::decode(&robot_config[..]).map_err(NVSStorageError::NVSValueDecodeError) + let mut decompressed_robot_config = vec![]; + FrameDecoder::new(&self.get_blob(NVS_ROBOT_CONFIG_KEY)?[..]) + .read_to_end(&mut decompressed_robot_config) + .map_err(NVSStorageError::NVSRobotConfigCompressionError)?; + RobotConfig::decode(&decompressed_robot_config[..]) + .map_err(NVSStorageError::NVSValueDecodeError) } fn reset_robot_configuration(&self) -> Result<(), Self::Error> {