Skip to content

Commit

Permalink
Add integrity checks to file system (#345)
Browse files Browse the repository at this point in the history
  • Loading branch information
PatrickKa authored Feb 11, 2025
2 parents 5f1f3dd + 85e9fcf commit 6cff6ed
Show file tree
Hide file tree
Showing 7 changed files with 533 additions and 127 deletions.
83 changes: 60 additions & 23 deletions Sts1CobcSw/FileSystem/LfsFlash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@

#include <strong_type/difference.hpp>

#include <littlefs/lfs_util.h>

#include <rodos_no_using_namespace.h>

#include <algorithm>
#include <array>
#include <cstring>
#include <span>


namespace sts1cobcsw::fs
{
namespace
{
auto Read(lfs_config const * config,
lfs_block_t blockNo,
lfs_off_t offset,
Expand All @@ -39,14 +44,24 @@ constexpr auto pageProgramTimeout = 5 * ms;
// max. 400 ms acc. W25Q01JV datasheet (lfs_config.block_size = flash::sectorSize)
constexpr auto blockEraseTimeout = 500 * ms;

constexpr auto erasedValue = 0xFF_b;
constexpr auto initialCrcValue = 0;

constexpr auto readSize = flash::pageSize - crcSize;
constexpr auto blockSize = flash::sectorSize - (flash::sectorSize / flash::pageSize * crcSize);


auto readBuffer = std::array<Byte, lfsCacheSize>{};
auto programBuffer = decltype(readBuffer){};
auto lookaheadBuffer = std::array<Byte, 64>{}; // NOLINT(*magic-numbers)

auto semaphore = RODOS::Semaphore();
}

// littlefs requires the lookaheadBuffer size to be a multiple of 8
static_assert(lookaheadBuffer.size() % 8 == 0); // NOLINT(*magic-numbers)
// littlefs requires the cacheSize to be a multiple of the read_size and prog_size, i.e., pageSize
static_assert(lfsCacheSize % flash::pageSize == 0);
// littlefs requires the cacheSize to be a multiple of the read_size (and prog_size)
static_assert(lfsCacheSize % readSize == 0);

lfs_config const lfsConfig = lfs_config{.context = nullptr,
.read = &Read,
Expand All @@ -55,9 +70,9 @@ lfs_config const lfsConfig = lfs_config{.context = nullptr,
.sync = &Sync,
.lock = &Lock,
.unlock = &Unlock,
.read_size = flash::pageSize,
.prog_size = flash::pageSize,
.block_size = flash::sectorSize,
.read_size = readSize,
.prog_size = readSize,
.block_size = blockSize,
.block_count = flash::nSectors,
.block_cycles = 200,
.cache_size = readBuffer.size(),
Expand All @@ -69,50 +84,71 @@ lfs_config const lfsConfig = lfs_config{.context = nullptr,
.name_max = maxPathLength,
.file_max = LFS_FILE_MAX,
.attr_max = LFS_ATTR_MAX,
.metadata_max = flash::sectorSize,
.metadata_max = blockSize,
.inline_max = 0};

auto semaphore = RODOS::Semaphore();


auto Initialize() -> void
{
flash::Initialize();
}


namespace
{
auto Read(lfs_config const * config,
lfs_block_t blockNo,
lfs_block_t blockNo, // NOLINT(bugprone-easily-swappable-parameters)
lfs_off_t offset,
void * buffer,
lfs_size_t size) -> int
{
// The following only works if read_size == pageSize
auto startAddress = blockNo * config->block_size + offset;
// Read page for page and check the CRC of each one
for(auto i = 0U; i < size; i += config->read_size)
{
auto page = flash::ReadPage(startAddress + i);
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
std::copy(page.begin(), page.end(), (static_cast<Byte *>(buffer) + i));
auto pageNo = (i + offset) / config->read_size;
auto pageAddress =
static_cast<std::uint32_t>(blockNo * flash::sectorSize + pageNo * flash::pageSize);
auto page = flash::ReadPage(pageAddress);
auto pageIsErased =
std::all_of(page.begin(), page.end(), [](auto byte) { return byte == erasedValue; });
// Check the CRC only if the page is not erased
if(not pageIsErased)
{
std::uint32_t crc; // NOLINT(*init-variables)
static_assert(sizeof(crc) == crcSize);
std::memcpy(&crc, &page[config->read_size], sizeof(crc));
auto computedCrc = lfs_crc(initialCrcValue, page.data(), config->read_size);
if(crc != computedCrc)
{
return LFS_ERR_CORRUPT;
}
}
// NOLINTNEXTLINE(*pointer-arithmetic)
std::copy_n(page.begin(), config->read_size, static_cast<Byte *>(buffer) + i);
}
return 0;
}


auto Program(lfs_config const * config,
lfs_block_t blockNo,
lfs_block_t blockNo, // NOLINT(bugprone-easily-swappable-parameters)
lfs_off_t offset,
void const * buffer,
lfs_size_t size) -> int
{
// The following only works if prog_size == pageSize
auto startAddress = blockNo * config->block_size + offset;
for(auto i = 0U; i < size; i += config->prog_size)
{
auto page = flash::Page{};
std::copy((static_cast<Byte const *>(buffer) + i), // NOLINT
(static_cast<Byte const *>(buffer) + i + config->prog_size), // NOLINT
page.begin());
flash::ProgramPage(startAddress + i, std::span(page));
// NOLINTNEXTLINE(*pointer-arithmetic)
std::copy_n(static_cast<Byte const *>(buffer) + i, config->prog_size, page.begin());
auto crc = lfs_crc(initialCrcValue, page.data(), config->prog_size);
static_assert(sizeof(crc) == crcSize);
std::memcpy(&page[config->prog_size], &crc, sizeof(crc));

auto pageNo = (i + offset) / config->prog_size;
auto pageAddress =
static_cast<std::uint32_t>(blockNo * flash::sectorSize + pageNo * flash::pageSize);
flash::ProgramPage(pageAddress, std::span(page));
auto waitWhileBusyResult = flash::WaitWhileBusy(pageProgramTimeout);
if(waitWhileBusyResult.has_error())
{
Expand All @@ -123,9 +159,9 @@ auto Program(lfs_config const * config,
}


auto Erase(lfs_config const * config, lfs_block_t blockNo) -> int
auto Erase([[maybe_unused]] lfs_config const * config, lfs_block_t blockNo) -> int
{
flash::EraseSector(blockNo * config->block_size);
flash::EraseSector(blockNo * flash::sectorSize);
auto waitWhileBusyResult = flash::WaitWhileBusy(blockEraseTimeout);
if(waitWhileBusyResult.has_error())
{
Expand Down Expand Up @@ -159,3 +195,4 @@ auto Unlock([[maybe_unused]] lfs_config const * config) -> int
return 0;
}
}
}
6 changes: 5 additions & 1 deletion Sts1CobcSw/FileSystem/LfsMemoryDevice.hpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#pragma once


#include <littlefs/lfs.h>

#include <cstdint>


namespace sts1cobcsw::fs
{
inline constexpr auto lfsCacheSize = 256;
inline constexpr auto crcSize = sizeof(std::uint32_t);
inline constexpr auto lfsCacheSize = 256 - crcSize;
// Our longest path should be strlen("/programs/65536.zip.lock") = 25 characters long
inline constexpr auto maxPathLength = 30;
extern lfs_config const lfsConfig;
Expand Down
97 changes: 79 additions & 18 deletions Sts1CobcSw/FileSystem/LfsRam.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,26 @@
//! This is useful for testing the file system without using a real flash memory.

#include <Sts1CobcSw/FileSystem/LfsMemoryDevice.hpp> // IWYU pragma: associated
#include <Sts1CobcSw/FileSystem/LfsRam.hpp>
#include <Sts1CobcSw/Serial/Byte.hpp>

#include <littlefs/lfs.h>
#include <littlefs/lfs_util.h>

#include <rodos_no_using_namespace.h>

#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <span>
#include <vector>


namespace sts1cobcsw::fs
{
namespace
{
// Before globals because lfsConfig needs the declarations
auto Read(lfs_config const * config,
lfs_block_t blockNo,
Expand All @@ -32,19 +41,28 @@ auto Lock(lfs_config const * config) -> int;
auto Unlock(lfs_config const * config) -> int;


constexpr auto erasedValue = 0xFF_b;
constexpr auto initialCrcValue = 0;

constexpr auto pageSize = 256;
constexpr auto readSize = pageSize - crcSize;
constexpr auto sectorSize = 4 * 1024;
constexpr auto blockSize = sectorSize * readSize / pageSize;
constexpr auto memorySize = 128 * 1024 * 1024;

auto memory = std::vector<Byte>();

auto readBuffer = std::array<Byte, lfsCacheSize>{};
auto programBuffer = decltype(readBuffer){};
auto lookaheadBuffer = std::array<Byte, 64>{}; // NOLINT(*magic-numbers)

auto semaphore = RODOS::Semaphore();
void (*programFinishedHandler)() = nullptr;
}

// littlefs requires the lookaheadBuffer size to be a multiple of 8
static_assert(lookaheadBuffer.size() % 8 == 0); // NOLINT(*magic-numbers)
// littlefs requires the cacheSize to be a multiple of the read_size and prog_size, i.e., pageSize
static_assert(lfsCacheSize % pageSize == 0);
// littlefs requires the cacheSize to be a multiple of the read_size (and prog_size)
static_assert(lfsCacheSize % readSize == 0);

lfs_config const lfsConfig = lfs_config{.context = nullptr,
.read = &Read,
Expand All @@ -53,9 +71,9 @@ lfs_config const lfsConfig = lfs_config{.context = nullptr,
.sync = &Sync,
.lock = &Lock,
.unlock = &Unlock,
.read_size = pageSize,
.prog_size = pageSize,
.block_size = sectorSize,
.read_size = readSize,
.prog_size = readSize,
.block_size = blockSize,
.block_count = memorySize / sectorSize,
.block_cycles = 200,
.cache_size = readBuffer.size(),
Expand All @@ -67,45 +85,87 @@ lfs_config const lfsConfig = lfs_config{.context = nullptr,
.name_max = maxPathLength,
.file_max = LFS_FILE_MAX,
.attr_max = LFS_ATTR_MAX,
.metadata_max = sectorSize,
.metadata_max = blockSize,
.inline_max = 0};

auto semaphore = RODOS::Semaphore();
std::vector<Byte> memory = std::vector<Byte>();


auto Initialize() -> void
{
memory.resize(memorySize, 0xFF_b); // NOLINT(*magic-numbers*)
memory.resize(memorySize, erasedValue);
}


auto SetProgramFinishedHandler(void (*handler)()) -> void
{
programFinishedHandler = handler;
}


namespace
{
auto Read(lfs_config const * config,
lfs_block_t blockNo,
lfs_block_t blockNo, // NOLINT(bugprone-easily-swappable-parameters)
lfs_off_t offset,
void * buffer,
lfs_size_t size) -> int
{
auto start = static_cast<int>(blockNo * config->block_size + offset);
std::copy_n(memory.begin() + start, size, static_cast<Byte *>(buffer));
// Read page for page and check the CRC of each one
for(auto i = 0U; i < size; i += config->read_size)
{
auto pageNo = (i + offset) / config->read_size;
auto pageAddress = blockNo * sectorSize + pageNo * pageSize;
auto page = std::span(&memory[pageAddress], pageSize);
auto pageIsErased =
std::all_of(page.begin(), page.end(), [](auto byte) { return byte == erasedValue; });
// Check the CRC only if the page is not erased
if(not pageIsErased)
{
std::uint32_t crc; // NOLINT(*init-variables)
static_assert(sizeof(crc) == crcSize);
std::memcpy(&crc, &page[config->read_size], sizeof(crc));
auto computedCrc = lfs_crc(initialCrcValue, page.data(), config->read_size);
if(crc != computedCrc)
{
return LFS_ERR_CORRUPT;
}
}
// NOLINTNEXTLINE(*pointer-arithmetic)
std::copy_n(page.begin(), config->read_size, static_cast<Byte *>(buffer) + i);
}
return 0;
}


auto Program(lfs_config const * config,
lfs_block_t blockNo,
lfs_block_t blockNo, // NOLINT(bugprone-easily-swappable-parameters)
lfs_off_t offset,
void const * buffer,
lfs_size_t size) -> int
{
auto start = static_cast<int>(blockNo * config->block_size + offset);
std::copy_n(static_cast<Byte const *>(buffer), size, memory.begin() + start);
for(lfs_size_t i = 0; i < size; i += config->prog_size)
{
auto pageNo = (i + offset) / config->prog_size;
auto pageAddress = blockNo * sectorSize + pageNo * pageSize;
auto page = std::span(&memory[pageAddress], pageSize);
// NOLINTNEXTLINE(*pointer-arithmetic)
std::copy_n(static_cast<Byte const *>(buffer) + i, config->prog_size, page.begin());
auto crc = lfs_crc(initialCrcValue, page.data(), config->prog_size);
static_assert(sizeof(crc) == crcSize);
std::memcpy(&page[config->prog_size], &crc, sizeof(crc));
}
if(programFinishedHandler != nullptr)
{
programFinishedHandler();
}
return 0;
}


auto Erase(lfs_config const * config, lfs_block_t blockNo) -> int
auto Erase([[maybe_unused]] lfs_config const * config, lfs_block_t blockNo) -> int
{
auto start = static_cast<int>(blockNo * config->block_size);
std::fill_n(memory.begin() + start, config->block_size, 0xFF_b); // NOLINT(*magic-numbers*)
std::fill_n(memory.begin() + static_cast<int>(blockNo * sectorSize), sectorSize, erasedValue);
return 0;
}

Expand All @@ -129,3 +189,4 @@ auto Unlock([[maybe_unused]] lfs_config const * config) -> int
return 0;
}
}
}
15 changes: 15 additions & 0 deletions Sts1CobcSw/FileSystem/LfsRam.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once


#include <Sts1CobcSw/Serial/Byte.hpp>

#include <vector>


namespace sts1cobcsw::fs
{
extern std::vector<Byte> memory;


auto SetProgramFinishedHandler(void (*handler)()) -> void;
}
7 changes: 5 additions & 2 deletions Tests/UnitTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,16 @@ add_test(NAME FramVector COMMAND Sts1CobcSwTests_FramVector)
add_program(LfsRam LfsRam.test.cpp UnitTestThread.cpp)
target_link_libraries(
Sts1CobcSwTests_LfsRam PRIVATE rodos::rodos littlefs::littlefs Sts1CobcSw_FileSystem
Sts1CobcSw_Serial
)
add_test(NAME LfsRam COMMAND Sts1CobcSwTests_LfsRam)

add_program(LfsWrapper LfsWrapper.test.cpp UnitTestThread.cpp)
target_link_libraries(
Sts1CobcSwTests_LfsWrapper PRIVATE rodos::rodos littlefs::littlefs Sts1CobcSw_FileSystem
Sts1CobcSwTests_LfsWrapper PRIVATE rodos::rodos etl::etl littlefs::littlefs
Sts1CobcSw_FileSystem Sts1CobcSw_Serial Sts1CobcSw_Utility
)
add_test(NAME LfsWrapper COMMAND Sts1CobcSwTests_FramRingArray)
add_test(NAME LfsWrapper COMMAND Sts1CobcSwTests_LfsWrapper)

add_program(PersistentVariables PersistentVariables.test.cpp UnitTestThread.cpp)
target_link_libraries(
Expand Down
Loading

0 comments on commit 6cff6ed

Please sign in to comment.