diff --git a/CMakePresets.json b/CMakePresets.json index 678451aa..e2266bfa 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -71,8 +71,9 @@ "W_NULL": "-Wnull-dereference -Wzero-as-null-pointer-constant -Wstrict-null-sentinel", "W_CLASS": "-Wsuggest-override -Woverloaded-virtual", "W_COND": "-Wduplicated-branches -Wduplicated-cond", - "W_REST": "-Wimplicit-fallthrough=5 -Wshadow -Wlogical-op -Wformat=2 -Wundef -Wno-long-long", - "WARNINGS": "$env{W_BASIC} $env{W_CONVERSION} $env{W_CAST} $env{W_NULL} $env{W_CLASS} $env{W_COND} $env{W_REST}" + "W_NO": "-Wno-missing-field-initializers -Wno-long-long", + "W_REST": "-Wimplicit-fallthrough=5 -Wshadow -Wlogical-op -Wformat=2 -Wundef", + "WARNINGS": "$env{W_BASIC} $env{W_CONVERSION} $env{W_CAST} $env{W_NULL} $env{W_CLASS} $env{W_COND} $env{W_NO} $env{W_REST}" } }, { @@ -81,7 +82,7 @@ "inherits": "warnings-unix", "cacheVariables": { "CMAKE_CXX_FLAGS": "$env{WARNINGS}", - "CMAKE_CXX_FLAGS_DEBUG": "-Og -DDEBUG", + "CMAKE_CXX_FLAGS_DEBUG": "-O0 -g -DDEBUG", "CMAKE_CXX_FLAGS_RELEASE": "-Os" } }, diff --git a/Sts1CobcSw/FileSystem/CMakeLists.txt b/Sts1CobcSw/FileSystem/CMakeLists.txt index 44a13b37..87576075 100644 --- a/Sts1CobcSw/FileSystem/CMakeLists.txt +++ b/Sts1CobcSw/FileSystem/CMakeLists.txt @@ -1,10 +1,12 @@ target_sources(Sts1CobcSw_FileSystem PRIVATE LfsWrapper.cpp) -target_link_libraries(Sts1CobcSw_FileSystem PUBLIC littlefs::littlefs Sts1CobcSw_Outcome) -target_link_libraries(Sts1CobcSw_FileSystem PRIVATE Sts1CobcSw_Serial) +target_link_libraries( + Sts1CobcSw_FileSystem PUBLIC etl::etl littlefs::littlefs Sts1CobcSw_Outcome + Sts1CobcSw_ProgramId Sts1CobcSw_Serial +) if(CMAKE_SYSTEM_NAME STREQUAL Generic) target_sources(Sts1CobcSw_FileSystem PRIVATE FileSystem.cpp LfsFlash.cpp) - target_link_libraries(Sts1CobcSw_FileSystem PRIVATE Sts1CobcSw_Periphery) + target_link_libraries(Sts1CobcSw_FileSystem PRIVATE rodos::rodos Sts1CobcSw_Periphery) else() target_sources(Sts1CobcSw_FileSystem PRIVATE LfsRam.cpp) endif() diff --git a/Sts1CobcSw/FileSystem/ErrorsAndResult.hpp b/Sts1CobcSw/FileSystem/ErrorsAndResult.hpp index 5279ee3c..4ef70c09 100644 --- a/Sts1CobcSw/FileSystem/ErrorsAndResult.hpp +++ b/Sts1CobcSw/FileSystem/ErrorsAndResult.hpp @@ -24,6 +24,9 @@ enum class ErrorCode noMemory = LFS_ERR_NOMEM, // No more memory available noAttribute = LFS_ERR_NOATTR, // No data/attr available nameTooLong = LFS_ERR_NAMETOOLONG, // File name too long + fileNotOpen = 1, + unsupportedOperation, + fileLocked, }; diff --git a/Sts1CobcSw/FileSystem/LfsFlash.cpp b/Sts1CobcSw/FileSystem/LfsFlash.cpp index 7e89f03d..20b0508e 100644 --- a/Sts1CobcSw/FileSystem/LfsFlash.cpp +++ b/Sts1CobcSw/FileSystem/LfsFlash.cpp @@ -1,13 +1,14 @@ //! @file -//! @brief Implement a flash storage device for littlefs. +//! @brief Implement a flash memory device for littlefs. -#include // IWYU pragma: associated +#include // IWYU pragma: associated #include #include #include #include +#include #include @@ -29,9 +30,14 @@ auto Lock(lfs_config const * config) -> int; auto Unlock(lfs_config const * config) -> int; -auto readBuffer = flash::Page{}; +auto readBuffer = std::array{}; auto programBuffer = decltype(readBuffer){}; -auto lookaheadBuffer = flash::Page{}; +auto lookaheadBuffer = std::array{}; // NOLINT(*magic-numbers) + +// 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); lfs_config const lfsConfig = lfs_config{.context = nullptr, .read = &Read, @@ -51,7 +57,7 @@ lfs_config const lfsConfig = lfs_config{.context = nullptr, .read_buffer = readBuffer.data(), .prog_buffer = programBuffer.data(), .lookahead_buffer = lookaheadBuffer.data(), - .name_max = LFS_NAME_MAX, + .name_max = maxPathLength, .file_max = LFS_FILE_MAX, .attr_max = LFS_ATTR_MAX, .metadata_max = flash::sectorSize, diff --git a/Sts1CobcSw/FileSystem/LfsMemoryDevice.hpp b/Sts1CobcSw/FileSystem/LfsMemoryDevice.hpp new file mode 100644 index 00000000..4074d2fb --- /dev/null +++ b/Sts1CobcSw/FileSystem/LfsMemoryDevice.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + + +namespace sts1cobcsw::fs +{ +inline constexpr auto lfsCacheSize = 256; +// Our longest path should be strlen("/programs/65536.zip.lock") = 25 characters long +inline constexpr auto maxPathLength = 30; +extern lfs_config const lfsConfig; + + +auto Initialize() -> void; +} diff --git a/Sts1CobcSw/FileSystem/LfsRam.cpp b/Sts1CobcSw/FileSystem/LfsRam.cpp index 651c0934..f06aa423 100644 --- a/Sts1CobcSw/FileSystem/LfsRam.cpp +++ b/Sts1CobcSw/FileSystem/LfsRam.cpp @@ -1,9 +1,9 @@ //! @file -//! @brief Simulate a storage device for littlefs in RAM. +//! @brief Simulate a memory device for littlefs in RAM. //! //! This is useful for testing the file system without using a real flash memory. -#include // IWYU pragma: associated +#include // IWYU pragma: associated #include #include @@ -35,9 +35,14 @@ constexpr auto sectorSize = 4 * 1024; constexpr auto memorySize = 128 * 1024 * 1024; auto memory = std::vector(); -auto readBuffer = std::array{}; +auto readBuffer = std::array{}; auto programBuffer = decltype(readBuffer){}; -auto lookaheadBuffer = std::array{}; +auto lookaheadBuffer = std::array{}; // NOLINT(*magic-numbers) + +// 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); lfs_config const lfsConfig = lfs_config{.context = nullptr, .read = &Read, @@ -57,7 +62,7 @@ lfs_config const lfsConfig = lfs_config{.context = nullptr, .read_buffer = readBuffer.data(), .prog_buffer = programBuffer.data(), .lookahead_buffer = lookaheadBuffer.data(), - .name_max = LFS_NAME_MAX, + .name_max = maxPathLength, .file_max = LFS_FILE_MAX, .attr_max = LFS_ATTR_MAX, .metadata_max = sectorSize, diff --git a/Sts1CobcSw/FileSystem/LfsStorageDevice.hpp b/Sts1CobcSw/FileSystem/LfsStorageDevice.hpp deleted file mode 100644 index 96b6ecc6..00000000 --- a/Sts1CobcSw/FileSystem/LfsStorageDevice.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - - -namespace sts1cobcsw::fs -{ -extern lfs_config const lfsConfig; - - -auto Initialize() -> void; -} diff --git a/Sts1CobcSw/FileSystem/LfsWrapper.cpp b/Sts1CobcSw/FileSystem/LfsWrapper.cpp index 28393c2c..ec793b55 100644 --- a/Sts1CobcSw/FileSystem/LfsWrapper.cpp +++ b/Sts1CobcSw/FileSystem/LfsWrapper.cpp @@ -1,4 +1,3 @@ -#include #include #include @@ -10,13 +9,221 @@ namespace sts1cobcsw::fs auto lfs = lfs_t{}; -[[nodiscard]] auto Mount() -> Result +// FIXME: For some reason this allocates 1024 bytes on the heap. With LFS_NO_MALLOC defined, it +// crashes with a SEGFAULT. +auto Mount() -> Result { auto error = lfs_mount(&lfs, &lfsConfig); if(error == 0) { return outcome_v2::success(); } + error = lfs_format(&lfs, &lfsConfig); + if(error != 0) + { + return static_cast(error); + } + error = lfs_mount(&lfs, &lfsConfig); + if(error == 0) + { + return outcome_v2::success(); + } return static_cast(error); } + + +auto Unmount() -> Result +{ + auto error = lfs_unmount(&lfs); + if(error != 0) + { + return static_cast(error); + } + return outcome_v2::success(); +} + + +auto Open(Path const & path, unsigned int flags) -> Result +{ + auto file = File(); + // We need to create a temporary with Path(path) here since append() modifies the object and we + // don't want to change the original path + file.lockFilePath_ = Path(path).append(".lock"); + auto createLockFileResult = file.CreateLockFile(); + if(createLockFileResult.has_error()) + { + return ErrorCode::fileLocked; + } + auto error = lfs_file_opencfg( + &lfs, &file.lfsFile_, path.c_str(), static_cast(flags), &file.lfsFileConfig_); + if(error == 0) + { + file.path_ = path; + file.openFlags_ = flags; + file.isOpen_ = true; + return file; + } + return static_cast(error); +} + + +File::File(File && other) noexcept +{ + MoveConstructFrom(&other); +} + + +auto File::operator=(File && other) noexcept -> File & +{ + if(this != &other) + { + MoveConstructFrom(&other); + } + return *this; +} + + +File::~File() +{ + // Only close the file if it is not in a default initialized or moved-from state + if(not path_.empty()) + { + (void)Close(); + } +} + + +auto File::Size() const -> Result +{ + if(not isOpen_) + { + return ErrorCode::fileNotOpen; + } + auto size = lfs_file_size(&lfs, &lfsFile_); + if(size >= 0) + { + return size; + } + return static_cast(size); +} + + +auto File::Close() const -> Result +{ + if(not isOpen_) + { + return outcome_v2::success(); + } + auto error = lfs_remove(&lfs, lockFilePath_.c_str()); + if(error != 0) + { + return static_cast(error); + } + return CloseAndKeepLockFile(); +} + + +// This function handles the weird way in which lfs_file_t objects are moved. It is more efficient +// than using the usual copy-and-swap idiom, because the latter would require to open and close the +// file more often. +auto File::MoveConstructFrom(File * other) noexcept -> void +{ + if(other->path_.empty()) + { + return; + } + auto error = lfs_file_opencfg(&lfs, + &lfsFile_, + other->path_.c_str(), + static_cast(other->openFlags_), + &lfsFileConfig_); + if(error == 0) + { + path_ = other->path_; + lockFilePath_ = other->lockFilePath_; + openFlags_ = other->openFlags_; + isOpen_ = true; + } + (void)other->CloseAndKeepLockFile(); + other->path_ = ""; + other->lockFilePath_ = ""; + other->openFlags_ = 0; + other->lfsFile_ = {}; +} + + +auto File::CreateLockFile() const noexcept -> Result +{ + auto lfsLockFile = lfs_file_t{}; + auto lockFileBuffer = std::array{}; + auto lfsLockFileConfig = lfs_file_config{.buffer = lockFileBuffer.data()}; + auto error = lfs_file_opencfg(&lfs, + &lfsLockFile, + lockFilePath_.c_str(), + static_cast(LFS_O_RDWR) | LFS_O_CREAT | LFS_O_EXCL, + &lfsLockFileConfig); + if(error == 0) + { + error = lfs_file_close(&lfs, &lfsLockFile); + } + if(error == 0) + { + return outcome_v2::success(); + } + return static_cast(error); +} + + +auto File::Read(void * buffer, std::size_t size) const -> Result +{ + if(not isOpen_) + { + return ErrorCode::fileNotOpen; + } + if((openFlags_ & LFS_O_RDONLY) == 0U) + { + return ErrorCode::unsupportedOperation; + } + auto nReadBytes = lfs_file_read(&lfs, &lfsFile_, buffer, size); + if(nReadBytes >= 0) + { + return nReadBytes; + } + return static_cast(nReadBytes); +} + + +auto File::Write(void const * buffer, std::size_t size) -> Result +{ + if(not isOpen_) + { + return ErrorCode::fileNotOpen; + } + if((openFlags_ & LFS_O_WRONLY) == 0U) + { + return ErrorCode::unsupportedOperation; + } + auto nWrittenBytes = lfs_file_write(&lfs, &lfsFile_, buffer, size); + if(nWrittenBytes >= 0) + { + return nWrittenBytes; + } + return static_cast(nWrittenBytes); +} + + +auto File::CloseAndKeepLockFile() const -> Result +{ + if(not isOpen_) + { + return outcome_v2::success(); + } + auto error = lfs_file_close(&lfs, &lfsFile_); + if(error != 0) + { + return static_cast(error); + } + isOpen_ = false; + return outcome_v2::success(); +} } diff --git a/Sts1CobcSw/FileSystem/LfsWrapper.hpp b/Sts1CobcSw/FileSystem/LfsWrapper.hpp index 03f77216..f441c3aa 100644 --- a/Sts1CobcSw/FileSystem/LfsWrapper.hpp +++ b/Sts1CobcSw/FileSystem/LfsWrapper.hpp @@ -2,9 +2,68 @@ #include +#include +#include + +#include + +#include + +#include +#include +#include namespace sts1cobcsw::fs { +using Path = etl::string; + +class File; + + [[nodiscard]] auto Mount() -> Result; +[[nodiscard]] auto Unmount() -> Result; +[[nodiscard]] auto Open(Path const & path, unsigned int flags) -> Result; + + +// TODO: Consider moving this class to a separate file +class File +{ +public: + File(File const &) = delete; + File(File && other) noexcept; + auto operator=(File const &) -> File & = delete; + auto operator=(File && other) noexcept -> File &; + ~File(); + + friend auto Open(Path const & path, unsigned int flags) -> Result; + + template + [[nodiscard]] auto Read(std::span data) const -> Result; + template + [[nodiscard]] auto Write(std::span data) -> Result; + [[nodiscard]] auto Size() const -> Result; + [[nodiscard]] auto Close() const -> Result; + + +private: + // Only allow creation of File class through friend function Open() + File() = default; + auto MoveConstructFrom(File * other) noexcept -> void; + [[nodiscard]] auto CreateLockFile() const noexcept -> Result; + [[nodiscard]] auto Read(void * buffer, std::size_t size) const -> Result; + [[nodiscard]] auto Write(void const * buffer, std::size_t size) -> Result; + [[nodiscard]] auto CloseAndKeepLockFile() const -> Result; + + Path path_ = ""; + Path lockFilePath_ = ""; + unsigned int openFlags_ = 0; + mutable lfs_file_t lfsFile_ = {}; + mutable bool isOpen_ = false; + std::array buffer_ = {}; + lfs_file_config lfsFileConfig_ = {.buffer = buffer_.data()}; +}; } + + +#include // IWYU pragma: keep diff --git a/Sts1CobcSw/FileSystem/LfsWrapper.ipp b/Sts1CobcSw/FileSystem/LfsWrapper.ipp new file mode 100644 index 00000000..01437545 --- /dev/null +++ b/Sts1CobcSw/FileSystem/LfsWrapper.ipp @@ -0,0 +1,20 @@ +#pragma once + +#include + + +namespace sts1cobcsw::fs +{ +template +auto File::Read(std::span data) const -> Result +{ + return Read(data.data(), data.size()); +} + + +template +auto File::Write(std::span data) -> Result +{ + return Write(data.data(), data.size()); +} +} diff --git a/Sts1CobcSw/FramSections/CMakeLists.txt b/Sts1CobcSw/FramSections/CMakeLists.txt index 0d43c107..c23e107d 100644 --- a/Sts1CobcSw/FramSections/CMakeLists.txt +++ b/Sts1CobcSw/FramSections/CMakeLists.txt @@ -1,3 +1,4 @@ target_link_libraries( - Sts1CobcSw_FramSections INTERFACE Sts1CobcSw_Periphery Sts1CobcSw_Serial Sts1CobcSw_Utility + Sts1CobcSw_FramSections INTERFACE rodos::without_main_on_linux Sts1CobcSw_Periphery + Sts1CobcSw_Serial Sts1CobcSw_Utility ) diff --git a/Tests/UnitTests/CMakeLists.txt b/Tests/UnitTests/CMakeLists.txt index a9d61247..8254defe 100644 --- a/Tests/UnitTests/CMakeLists.txt +++ b/Tests/UnitTests/CMakeLists.txt @@ -15,7 +15,9 @@ target_link_libraries( ) add_program(LfsRam LfsRam.test.cpp) -target_link_libraries(Sts1CobcSwTests_LfsRam PRIVATE Catch2::Catch2WithMain Sts1CobcSw_FileSystem) +target_link_libraries( + Sts1CobcSwTests_LfsRam PRIVATE Catch2::Catch2WithMain littlefs::littlefs Sts1CobcSw_FileSystem +) add_program(MajorityVote MajorityVote.test.cpp) target_link_libraries( @@ -77,6 +79,19 @@ add_test(NAME EdacVariable COMMAND Sts1CobcSwTests_EdacVariable) # Disable warnings about memcpy() of non-trivially copiable type EdacVariable<> target_compile_options(Sts1CobcSwTests_EdacVariable PRIVATE -Wno-class-memaccess) +add_program(FramRingArray FramRingArray.test.cpp UnitTestThread.cpp) +target_link_libraries( + Sts1CobcSwTests_FramRingArray PRIVATE rodos::rodos Sts1CobcSw_FramSections Sts1CobcSw_Periphery + Sts1CobcSw_Serial +) +add_test(NAME FramRingArray COMMAND Sts1CobcSwTests_FramRingArray) + +add_program(LfsWrapper LfsWrapper.test.cpp UnitTestThread.cpp) +target_link_libraries( + Sts1CobcSwTests_LfsWrapper PRIVATE rodos::rodos littlefs::littlefs Sts1CobcSw_FileSystem +) +add_test(NAME LfsWrapper COMMAND Sts1CobcSwTests_FramRingArray) + add_program(PersistentVariables PersistentVariables.test.cpp UnitTestThread.cpp) target_link_libraries( Sts1CobcSwTests_PersistentVariables @@ -85,13 +100,6 @@ target_link_libraries( ) add_test(NAME PersistentVariables COMMAND Sts1CobcSwTests_PersistentVariables) -add_program(FramRingArray FramRingArray.test.cpp UnitTestThread.cpp) -target_link_libraries( - Sts1CobcSwTests_FramRingArray PRIVATE rodos::rodos Sts1CobcSw_FramSections Sts1CobcSw_Periphery - Sts1CobcSw_Serial -) -add_test(NAME FramRingArray COMMAND Sts1CobcSwTests_FramRingArray) - # --- All unit tests --- get_property( diff --git a/Tests/UnitTests/LfsRam.test.cpp b/Tests/UnitTests/LfsRam.test.cpp index 254cc3d9..42da8b06 100644 --- a/Tests/UnitTests/LfsRam.test.cpp +++ b/Tests/UnitTests/LfsRam.test.cpp @@ -1,11 +1,11 @@ -#include +#include #include #include -TEST_CASE("RAM storage device") +TEST_CASE("RAM memory device") { sts1cobcsw::fs::Initialize(); auto lfs = lfs_t{}; diff --git a/Tests/UnitTests/LfsWrapper.test.cpp b/Tests/UnitTests/LfsWrapper.test.cpp new file mode 100644 index 00000000..1204fb9e --- /dev/null +++ b/Tests/UnitTests/LfsWrapper.test.cpp @@ -0,0 +1,79 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +using sts1cobcsw::Byte; +using sts1cobcsw::Span; +using sts1cobcsw::fs::Path; +using sts1cobcsw::operator""_b; // NOLINT(misc-unused-using-decls) + + +auto RunUnitTest() -> void +{ + Require(true); + + sts1cobcsw::fs::Initialize(); + auto mountResult = sts1cobcsw::fs::Mount(); + Require(not mountResult.has_error()); + + auto filePath = Path("/MyFile"); + auto openResult = sts1cobcsw::fs::Open(filePath, LFS_O_WRONLY | LFS_O_CREAT); + Require(openResult.has_value()); + auto & writeableFile = openResult.value(); + + // Reopening the file should fail + auto reopenResult = sts1cobcsw::fs::Open(filePath, LFS_O_WRONLY | LFS_O_CREAT); + Require(reopenResult.has_error()); + + // Empty file should have size 0 + auto sizeResult = writeableFile.Size(); + Require(sizeResult.has_value()); + Require(sizeResult.value() == 0); + + auto writeData = std::array{0xAA_b, 0xBB_b, 0xCC_b, 0xDD_b}; + auto writeResult = writeableFile.Write(Span(writeData)); + Require(writeResult.has_value()); + Require(writeResult.value() == sizeof(writeData)); + + // Read() should fail since the file is only opened for writing + auto readData = std::array{0x11_b, 0x22_b, 0x33_b, 0x44_b}; + auto readResult = writeableFile.Read(Span(&readData)); + Require(readResult.has_error()); + + auto closeResult = writeableFile.Close(); + Require(not closeResult.has_error()); + + openResult = sts1cobcsw::fs::Open(filePath, LFS_O_RDONLY); + Require(openResult.has_value()); + auto & readableFile = openResult.value(); + + sizeResult = readableFile.Size(); + Require(sizeResult.has_value()); + Require(sizeResult.value() == sizeof(writeData)); + + readResult = readableFile.Read(Span(&readData)); + Require(readResult.has_value()); + Require(readResult.value() == sizeof(readData)); + Require(readData == writeData); + + // Write() should fail since the file is only opened for reading + writeResult = readableFile.Write(Span(writeData)); + Require(writeResult.has_error()); + + closeResult = readableFile.Close(); + Require(not closeResult.has_error()); + + auto unmountResult = sts1cobcsw::fs::Unmount(); + Require(not unmountResult.has_error()); +} diff --git a/iwyu.imp b/iwyu.imp index 89732e72..aa41bb1f 100644 --- a/iwyu.imp +++ b/iwyu.imp @@ -47,6 +47,7 @@ # Instead of all our .ipp files include the .hpp ones { include: ["\"Sts1CobcSw/Edu/Types.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/FileSystem/FileSystem.ipp\"", "private", "", "public"] }, + { include: ["\"Sts1CobcSw/FileSystem/LfsWrapper.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/FramSections/RingArray.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/FramSections/PersistentVariables.ipp\"", "private", "", "public"] }, { include: ["\"Sts1CobcSw/FramSections/Subsections.ipp\"", "private", "", "public"] }, @@ -70,7 +71,7 @@ { include: ["\"Sts1CobcSw/Edu/Types.hpp\"", "public", "", "public"] }, { include: ["\"Sts1CobcSw/FileSystem/ErrorsAndResult.hpp\"", "public", "", "public"] }, { include: ["\"Sts1CobcSw/FileSystem/FileSystem.hpp\"", "public", "", "public"] }, - { include: ["\"Sts1CobcSw/FileSystem/LfsStorageDevice.hpp\"", "public", "", "public"] }, + { include: ["\"Sts1CobcSw/FileSystem/LfsMemoryDevice.hpp\"", "public", "", "public"] }, { include: ["\"Sts1CobcSw/FileSystem/LfsWrapper.hpp\"", "public", "", "public"] }, { include: ["\"Sts1CobcSw/FramSections/FramLayout.hpp\"", "public", "", "public"] }, { include: ["\"Sts1CobcSw/FramSections/RingArray.hpp\"", "public", "", "public"] },