// Copyright 2021 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_file/flat_file_system.h" #include <array> #include <cstddef> #include <cstdint> #include <span> #include <string_view> #include "gtest/gtest.h" #include "pw_bytes/span.h" #include "pw_file/file.pwpb.h" #include "pw_protobuf/decoder.h" #include "pw_rpc/raw/test_method_context.h" #include "pw_status/status.h" #include "pw_status/status_with_size.h" namespace pw::file { namespace { class FakeFile : public FlatFileSystemService::Entry { public: constexpr FakeFile(std::string_view file_name, size_t size, uint32_t file_id) : name_(file_name), size_(size), file_id_(file_id) {} StatusWithSize Name(std::span<char> dest) override { if (name_.empty()) { return StatusWithSize(Status::NotFound(), 0); } size_t bytes_to_copy = std::min(dest.size_bytes(), name_.size()); memcpy(dest.data(), name_.data(), bytes_to_copy); if (bytes_to_copy != name_.size()) { return StatusWithSize(Status::ResourceExhausted(), bytes_to_copy); } return StatusWithSize(OkStatus(), bytes_to_copy); } size_t SizeBytes() override { return size_; } FlatFileSystemService::Entry::FilePermissions Permissions() const override { return FlatFileSystemService::Entry::FilePermissions::NONE; } Status Delete() override { return Status::Unimplemented(); } FlatFileSystemService::Entry::Id FileId() const override { return file_id_; } private: std::string_view name_; size_t size_; uint32_t file_id_; }; bool EntryHasName(FlatFileSystemService::Entry* entry) { std::array<char, 4> expected_name; StatusWithSize file_name_sws = entry->Name(expected_name); return file_name_sws.size() != 0; } // Compares a serialized Path message to a flat file system entry. void ComparePathToEntry(ConstByteSpan serialized_path, FlatFileSystemService::Entry* entry) { std::array<char, 64> expected_name; StatusWithSize file_name_sws = entry->Name(expected_name); // A partial name read shouldn't happen. ASSERT_EQ(OkStatus(), file_name_sws.status()); protobuf::Decoder decoder(serialized_path); while (decoder.Next().ok()) { switch (decoder.FieldNumber()) { case static_cast<uint32_t>(pw::file::Path::Fields::PATH): { std::string_view serialized_name; EXPECT_EQ(OkStatus(), decoder.ReadString(&serialized_name)); size_t name_bytes_to_read = std::min(serialized_name.size(), file_name_sws.size()); EXPECT_EQ(0, memcmp(expected_name.data(), serialized_name.data(), name_bytes_to_read)); break; } case static_cast<uint32_t>(pw::file::Path::Fields::PERMISSIONS): { uint32_t seralized_permissions; EXPECT_EQ(OkStatus(), decoder.ReadUint32(&seralized_permissions)); EXPECT_EQ(static_cast<uint32_t>(entry->Permissions()), seralized_permissions); break; } case static_cast<uint32_t>(pw::file::Path::Fields::SIZE_BYTES): { uint32_t serialized_file_size; EXPECT_EQ(OkStatus(), decoder.ReadUint32(&serialized_file_size)); EXPECT_EQ(static_cast<uint32_t>(entry->SizeBytes()), serialized_file_size); break; } case static_cast<uint32_t>(pw::file::Path::Fields::FILE_ID): { uint32_t serialized_file_id; EXPECT_EQ(OkStatus(), decoder.ReadUint32(&serialized_file_id)); EXPECT_EQ(static_cast<uint32_t>(entry->FileId()), serialized_file_id); break; } default: // unexpected result. // TODO something here. break; } } } size_t ValidateExpectedPaths( std::span<FlatFileSystemService::Entry*> flat_file_system, const rpc::PayloadsView& results) { size_t serialized_path_entry_count = 0; size_t file_system_index = 0; for (ConstByteSpan response : results) { protobuf::Decoder decoder(response); while (decoder.Next().ok()) { constexpr uint32_t kListResponsePathsFieldNumber = static_cast<uint32_t>(pw::file::ListResponse::Fields::PATHS); EXPECT_EQ(decoder.FieldNumber(), kListResponsePathsFieldNumber); if (decoder.FieldNumber() != kListResponsePathsFieldNumber) { return 0; } serialized_path_entry_count++; // Skip any file system entries without names. while (!EntryHasName(flat_file_system[file_system_index])) { file_system_index++; EXPECT_GT(flat_file_system.size(), file_system_index); } // There's a 1:1 mapping in the same order for all files that have a name. ConstByteSpan serialized_path; EXPECT_EQ(OkStatus(), decoder.ReadBytes(&serialized_path)); ComparePathToEntry(serialized_path, flat_file_system[file_system_index++]); } } return serialized_path_entry_count; } TEST(FlatFileSystem, EncodingBufferSizeBytes) { EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(10), 2u /* path nested message key and size */ + 12 /* path */ + 2 /* permissions */ + 6 /* size_bytes */ + 6 /* file_id */); EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(10, 2), 2 * (1u + 1 + 12 + 2 + 6 + 6)); EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(100, 3), 3 * (1u + 1 + 102 + 2 + 6 + 6)); } TEST(FlatFileSystem, List_NoFiles) { PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<1>, List) ctx{std::span<FlatFileSystemService::Entry*>()}; ctx.call(ConstByteSpan()); EXPECT_TRUE(ctx.done()); EXPECT_EQ(OkStatus(), ctx.status()); EXPECT_EQ(0u, ctx.responses().size()); } TEST(FlatFileSystem, List_OneFile) { FakeFile file{"compressed.zip.gz", 2, 1231}; std::array<FlatFileSystemService::Entry*, 1> static_file_system{&file}; PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<20>, List) ctx(static_file_system); ctx.call(ConstByteSpan()); EXPECT_EQ(1u, ValidateExpectedPaths(static_file_system, ctx.responses())); } TEST(FlatFileSystem, List_ThreeFiles) { std::array<FakeFile, 3> files{ {{"SNAP_001", 372, 9}, {"tokens.csv", 808, 15038202}, {"a.txt", 0, 2}}}; std::array<FlatFileSystemService::Entry*, 3> static_file_system{ &files[0], &files[1], &files[2]}; PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List) ctx(static_file_system); ctx.call(ConstByteSpan()); EXPECT_EQ(3u, ValidateExpectedPaths(static_file_system, ctx.responses())); } TEST(FlatFileSystem, List_UnnamedFile) { FakeFile file{"", 0, 0}; std::array<FlatFileSystemService::Entry*, 1> static_file_system{&file}; PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List) ctx(static_file_system); ctx.call(ConstByteSpan()); EXPECT_EQ(0u, ValidateExpectedPaths(static_file_system, ctx.responses())); } TEST(FlatFileSystem, List_FileMissingName) { std::array<FakeFile, 3> files{ {{"SNAP_001", 372, 9}, {"", 808, 15038202}, {"a.txt", 0, 2}}}; std::array<FlatFileSystemService::Entry*, 3> static_file_system{ &files[0], &files[1], &files[2]}; PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List) ctx(static_file_system); ctx.call(ConstByteSpan()); EXPECT_EQ(2u, ValidateExpectedPaths(static_file_system, ctx.responses())); } } // namespace } // namespace pw::file