esp-nvs 0.1.3

ESP-IDF compatible, bare metal, non-volatile storage (NVS) library
Documentation
use esp_nvs::error::{Error, ItemType};
use esp_nvs::{EntryStatistics, Key, NvsStatistics, PageStatistics};

mod common;

#[test]
fn from_generated_partition() {
    let mut flash = common::Flash::new_from_file("tests/assets/test_nvs_data.bin");

    let mut nvs = esp_nvs::Nvs::new(0, flash.len(), &mut flash).unwrap();

    assert_eq!(
        nvs.get::<u8>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_u8")
        )
        .unwrap(),
        100
    );
    assert_eq!(
        nvs.get::<i8>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_i8")
        )
        .unwrap(),
        -100
    );
    assert_eq!(
        nvs.get::<u16>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_u16")
        )
        .unwrap(),
        65000
    );
    assert_eq!(
        nvs.get::<i16>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_i16")
        )
        .unwrap(),
        -32000
    );
    assert_eq!(
        nvs.get::<u32>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_u32")
        )
        .unwrap(),
        4294960000
    );
    assert_eq!(
        nvs.get::<i32>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_i32")
        )
        .unwrap(),
        -2147480000
    );

    // 64 bit values cant be generated by the python nvs generator so they're skipped here
    // the write test still reads them though

    assert_eq!(
        nvs.get::<String>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_s_short")
        )
        .unwrap(),
        "short string"
    );
    assert_eq!(
        nvs.get::<String>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_s_long")
        )
        .unwrap(),
        "long string spanning multiple entries whereas each entry is 32 bytes in total"
    );

    assert_eq!(
        nvs.get::<Vec<u8>>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_b_short")
        )
        .unwrap(),
        vec![
            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD,
            0xFF, 0x00, 0xAA,
        ]
    );
    assert_eq!(
        nvs.get::<Vec<u8>>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_b_long")
        )
        .unwrap(),
        std::fs::read("tests/assets/multi_page_blob.bin").unwrap()
    );

    assert_eq!(
        nvs.get::<u8>(
            &Key::from_str("namespace_two"),
            &Key::from_str("example_u8")
        )
        .unwrap(),
        123
    );
    assert_eq!(
        nvs.get::<u8>(
            &Key::from_str("namespace_two"),
            &Key::from_str("only_in_two")
        )
        .unwrap(),
        1
    );
    assert!(
        nvs.get::<bool>(
            &Key::from_str("namespace_two"),
            &Key::from_str("only_in_two")
        )
        .unwrap()
    );

    // Item type mismatch is reported correctly -> only_in_two is an u8
    assert_eq!(
        nvs.get::<u32>(
            &Key::from_str("namespace_one"),
            &Key::from_str("example_u16")
        ),
        Err(Error::ItemTypeMismatch(ItemType::U16))
    );
}

#[test]
fn corrupt_page() {
    let mut flash = common::Flash::new_from_file("tests/assets/test_nvs_data.bin");

    // we manually corrupt header of the first page
    // -> crc invalid
    flash.buf[4] = 123;

    let mut nvs = esp_nvs::Nvs::new(0, flash.len(), &mut flash).unwrap();
    let result = nvs.get::<u8>(
        &Key::from_str("namespace_one"),
        &Key::from_str("example_u8"),
    );
    assert_eq!(result, Err(Error::NamespaceNotFound));

    assert_eq!(
        nvs.statistics().unwrap(),
        NvsStatistics {
            pages: PageStatistics {
                empty: 1,
                active: 0,
                full: 2,
                erasing: 0,
                corrupted: 1,
            },
            entries_per_page: vec![
                EntryStatistics {
                    empty: 0,
                    written: 0,
                    erased: 0,
                    illegal: 126,
                },
                EntryStatistics {
                    empty: 0,
                    written: 0,
                    erased: 126,
                    illegal: 0,
                },
                EntryStatistics {
                    empty: 105,
                    written: 3,
                    erased: 18,
                    illegal: 0,
                },
                EntryStatistics {
                    empty: 126,
                    written: 0,
                    erased: 0,
                    illegal: 0,
                },
            ],
            entries_overall: EntryStatistics {
                empty: 231,
                written: 3,
                erased: 144,
                illegal: 126,
            },
        }
    );
}

#[test]
fn corrupt_entry() {
    let mut flash = common::Flash::new_from_file("tests/assets/test_nvs_data.bin");

    // we manually corrupt the example_u8 entry
    // -> crc invalid
    flash.buf[0x60] = 123;

    let mut nvs = esp_nvs::Nvs::new(0, flash.len(), &mut flash).unwrap();
    let result = nvs.get::<u8>(
        &Key::from_str("namespace_one"),
        &Key::from_str("example_u8"),
    );
    assert!(result.is_err());

    assert_eq!(result.err().unwrap(), Error::KeyNotFound);

    assert_eq!(
        nvs.statistics().unwrap(),
        NvsStatistics {
            pages: PageStatistics {
                empty: 1,
                active: 0,
                full: 3,
                erasing: 0,
                corrupted: 0,
            },
            entries_per_page: vec![
                EntryStatistics {
                    empty: 0,
                    written: 125,
                    erased: 1,
                    illegal: 0,
                },
                EntryStatistics {
                    empty: 0,
                    written: 126,
                    erased: 0,
                    illegal: 0,
                },
                EntryStatistics {
                    empty: 105,
                    written: 21,
                    erased: 0,
                    illegal: 0,
                },
                EntryStatistics {
                    empty: 126,
                    written: 0,
                    erased: 0,
                    illegal: 0,
                }
            ],
            entries_overall: EntryStatistics {
                empty: 231,
                written: 272,
                erased: 1,
                illegal: 0,
            },
        }
    );
}

// TODO: when reading a multi-page-blob and the bounds don't match, mark the entry as corrupt

// TODO: when reading a single-page-blob and the bounds don't match, mark the entry as corrupt (covers str as well)

// TODO: when the CRC is invalid, mark the entry as corrupt