Skip to content

Why erase bounds check differs from read/write? #4094

@microidea

Description

@microidea

I've encountered a storage error: OutOfBounds. I think this might be because the bounds check in the ease implementation differs from the read/write operations. My NVS partition is 16KB, but I have to use only 12KB to avoid the OutOfBounds error when calling the erase_all_nvs function.

Here's my code:

use core::str;

extern crate alloc;
use alloc::{string::String, vec, vec::Vec};
use embassy_embedded_hal::adapter::BlockingAsync;
use esp_bootloader_esp_idf::partitions::{
    read_partition_table, DataPartitionSubType, Error as PartitionError, PartitionType,
    PARTITION_TABLE_MAX_LEN,
};
use esp_storage::FlashStorage;
use sequential_storage::cache::NoCache;
use sequential_storage::map::{fetch_item, store_item};

type KvError = sequential_storage::Error<PartitionError>;

#[inline]
fn nvs_range(len: u32) -> core::ops::Range<u32> {
    let sector_size = esp_storage::FlashStorage::SECTOR_SIZE as u32;
    0..(len & !(sector_size - 1))
}

macro_rules! with_nvs {
    ($af:ident, $range:ident, $cache:ident, $body:block) => {{
        let mut flash = FlashStorage::new();
        let mut pt_buf = [0u8; PARTITION_TABLE_MAX_LEN];
        let pt = read_partition_table(&mut flash, &mut pt_buf)
            .map_err(|e| KvError::Storage { value: e })?;
        let nvs = pt
            .find_partition(PartitionType::Data(DataPartitionSubType::Nvs))
            .map_err(|e| KvError::Storage { value: e })?
            .ok_or(PartitionError::Invalid)
            .map_err(|e| KvError::Storage { value: e })?;

        let mut region = nvs.as_embedded_storage(&mut flash);
        let mut $af = BlockingAsync::new(&mut region);
        let mut $cache = NoCache::new();
        let $range = nvs_range(nvs.len());

        $body
    }};
}

pub async fn erase_all_nvs() -> Result<(), KvError> {
    with_nvs!(async_flash, range, _cache, {
        sequential_storage::erase_all(&mut async_flash, range.clone()).await
    })
}

pub async fn write_kv(key: u16, value: &[u8]) -> Result<(), KvError> {
    with_nvs!(async_flash, range, cache, {
        // Buffer must be large enough to serialize key + value
        let mut data_buffer: Vec<u8> = vec![0; 2 + value.len()];
        store_item(
            &mut async_flash,
            range,
            &mut cache,
            &mut data_buffer,
            &key,
            &value,
        )
        .await
    })
}

/// Read raw bytes from NVS for the given 16-bit key.
pub async fn read_kv(key: u16) -> Result<Option<Vec<u8>>, KvError> {
    with_nvs!(async_flash, range, cache, {
        let mut buf: Vec<u8> = vec![0; 256];
        loop {
            match fetch_item::<u16, &'_ [u8], _>(
                &mut async_flash,
                range.clone(),
                &mut cache,
                &mut buf,
                &key,
            )
            .await
            {
                Ok(Some(s)) => return Ok(Some(s.to_vec())),
                Ok(None) => return Ok(None),
                Err(KvError::BufferTooSmall(needed)) => {
                    let new_len = core::cmp::max(buf.len() * 2, needed);
                    buf.resize(new_len, 0);
                }
                Err(e) => return Err(e),
            }
        }
    })
}

fn range(&self) -> core::ops::Range<u32> {
self.raw.offset()..self.raw.offset() + self.raw.len()
}
fn in_range(&self, start: u32, len: usize) -> bool {
self.range().contains(&start) && (start + len as u32 <= self.range().end)
}

ease function use range() which is end-exclusive

fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
let address_from = from + self.raw.offset();
let address_to = to + self.raw.offset();
if self.raw.is_read_only() {
return Err(Error::WriteProtected);
}
if !self.range().contains(&address_from) {
return Err(Error::OutOfBounds);
}
if !self.range().contains(&address_to) {
return Err(Error::OutOfBounds);
}
self.flash
.erase(address_from, address_to)
.map_err(|_e| Error::StorageError)
}

but read/write function use in_range() which is end-inclusive

fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
let address = offset + self.raw.offset();
if !self.in_range(address, bytes.len()) {
return Err(Error::OutOfBounds);
}
self.flash
.read(address, bytes)
.map_err(|_e| Error::StorageError)
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions