You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
589 lines
20 KiB
589 lines
20 KiB
// Copyright 2020 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_kvs/internal/entry.h"
|
|
|
|
#include <span>
|
|
#include <string_view>
|
|
|
|
#include "gtest/gtest.h"
|
|
#include "pw_bytes/array.h"
|
|
#include "pw_kvs/alignment.h"
|
|
#include "pw_kvs/checksum.h"
|
|
#include "pw_kvs/crc16_checksum.h"
|
|
#include "pw_kvs/fake_flash_memory.h"
|
|
#include "pw_kvs/flash_memory.h"
|
|
#include "pw_kvs/format.h"
|
|
|
|
namespace pw::kvs::internal {
|
|
namespace {
|
|
|
|
using std::byte;
|
|
using std::string_view;
|
|
|
|
// For magic value always use a random 32 bit integer rather than a human
|
|
// readable 4 bytes. See pw_kvs/format.h for more information.
|
|
constexpr EntryFormat kFormat{0x961c2ff9, nullptr};
|
|
|
|
TEST(Entry, Size_RoundsUpToAlignment) {
|
|
// Use FakeFlashMemory, rather than FakeFlashMemoryBuffer, so the class gets
|
|
// tested/used directly.
|
|
std::array<std::byte, 64 * 2> buffer;
|
|
|
|
// Flash alignment needs to be 1 due to how the partition is used in this
|
|
// test.
|
|
FakeFlashMemory flash(buffer, 64, 2, 1);
|
|
|
|
for (size_t alignment_bytes = 1; alignment_bytes <= 4096; ++alignment_bytes) {
|
|
FlashPartition partition(&flash, 0, flash.sector_count(), alignment_bytes);
|
|
const size_t align = AlignUp(alignment_bytes, Entry::kMinAlignmentBytes);
|
|
|
|
for (size_t value : {size_t(0), align - 1, align, align + 1, 2 * align}) {
|
|
Entry entry =
|
|
Entry::Valid(partition, 0, kFormat, "k", {nullptr, value}, 0);
|
|
|
|
ASSERT_EQ(AlignUp(sizeof(EntryHeader) + 1 /* key */ + value, align),
|
|
entry.size());
|
|
}
|
|
|
|
Entry entry = Entry::Tombstone(partition, 0, kFormat, "k", 0);
|
|
ASSERT_EQ(AlignUp(sizeof(EntryHeader) + 1 /* key */, align), entry.size());
|
|
}
|
|
}
|
|
|
|
TEST(Entry, Construct_ValidEntry) {
|
|
FakeFlashMemoryBuffer<64, 2> flash(16);
|
|
FlashPartition partition(&flash, 0, flash.sector_count());
|
|
|
|
auto entry = Entry::Valid(
|
|
partition, 1, kFormat, "k", std::as_bytes(std::span("123")), 9876);
|
|
|
|
EXPECT_FALSE(entry.deleted());
|
|
EXPECT_EQ(entry.magic(), kFormat.magic);
|
|
EXPECT_EQ(entry.value_size(), sizeof("123"));
|
|
EXPECT_EQ(entry.transaction_id(), 9876u);
|
|
}
|
|
|
|
TEST(Entry, Construct_Tombstone) {
|
|
FakeFlashMemoryBuffer<64, 2> flash(16);
|
|
FlashPartition partition(&flash, 0, flash.sector_count());
|
|
|
|
auto entry = Entry::Tombstone(partition, 1, kFormat, "key", 123);
|
|
|
|
EXPECT_TRUE(entry.deleted());
|
|
EXPECT_EQ(entry.magic(), kFormat.magic);
|
|
EXPECT_EQ(entry.value_size(), 0u);
|
|
EXPECT_EQ(entry.transaction_id(), 123u);
|
|
}
|
|
|
|
// For magic value always use a unique random 32 bit integer rather than a human
|
|
// readable 4 bytes. See pw_kvs/format.h for more information.
|
|
constexpr uint32_t kMagicWithChecksum = 0xad165142;
|
|
constexpr uint32_t kTransactionId1 = 0x96979899;
|
|
|
|
constexpr auto kKey1 = bytes::String("key45");
|
|
constexpr auto kValue1 = bytes::String("VALUE!");
|
|
constexpr auto kPadding1 = bytes::String("\0\0\0\0\0");
|
|
|
|
constexpr auto kHeader1 = bytes::Concat(kMagicWithChecksum,
|
|
uint32_t(0x23aa), // checksum (CRC16)
|
|
uint8_t(1), // alignment (32 B)
|
|
uint8_t(kKey1.size()), // key length
|
|
uint16_t(kValue1.size()), // value size
|
|
kTransactionId1 // transaction ID
|
|
);
|
|
|
|
constexpr auto kEntryWithoutPadding1 = bytes::Concat(kHeader1, kKey1, kValue1);
|
|
constexpr auto kEntry1 = bytes::Concat(kEntryWithoutPadding1, kPadding1);
|
|
static_assert(kEntry1.size() == 32);
|
|
|
|
ChecksumCrc16 default_checksum;
|
|
constexpr EntryFormat kFormatWithChecksum{kMagicWithChecksum,
|
|
&default_checksum};
|
|
constexpr internal::EntryFormats kFormats(kFormatWithChecksum);
|
|
|
|
class ValidEntryInFlash : public ::testing::Test {
|
|
protected:
|
|
ValidEntryInFlash() : flash_(kEntry1), partition_(&flash_) {
|
|
EXPECT_EQ(OkStatus(), Entry::Read(partition_, 0, kFormats, &entry_));
|
|
}
|
|
|
|
FakeFlashMemoryBuffer<1024, 4> flash_;
|
|
FlashPartition partition_;
|
|
Entry entry_;
|
|
};
|
|
|
|
TEST_F(ValidEntryInFlash, PassesChecksumVerification) {
|
|
EXPECT_EQ(OkStatus(), entry_.VerifyChecksumInFlash());
|
|
EXPECT_EQ(OkStatus(), entry_.VerifyChecksum("key45", kValue1));
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, HeaderContents) {
|
|
EXPECT_EQ(entry_.magic(), kMagicWithChecksum);
|
|
EXPECT_EQ(entry_.key_length(), 5u);
|
|
EXPECT_EQ(entry_.value_size(), 6u);
|
|
EXPECT_EQ(entry_.transaction_id(), kTransactionId1);
|
|
EXPECT_FALSE(entry_.deleted());
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, ReadKey) {
|
|
Entry::KeyBuffer key = {};
|
|
auto result = entry_.ReadKey(key);
|
|
|
|
ASSERT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(result.size(), entry_.key_length());
|
|
EXPECT_STREQ(key.data(), "key45");
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, ReadValue) {
|
|
char value[32] = {};
|
|
auto result = entry_.ReadValue(std::as_writable_bytes(std::span(value)));
|
|
|
|
ASSERT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(result.size(), entry_.value_size());
|
|
EXPECT_STREQ(value, "VALUE!");
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, ReadValue_BufferTooSmall) {
|
|
char value[3] = {};
|
|
auto result = entry_.ReadValue(std::as_writable_bytes(std::span(value)));
|
|
|
|
ASSERT_EQ(Status::ResourceExhausted(), result.status());
|
|
EXPECT_EQ(3u, result.size());
|
|
EXPECT_EQ(value[0], 'V');
|
|
EXPECT_EQ(value[1], 'A');
|
|
EXPECT_EQ(value[2], 'L');
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, ReadValue_WithOffset) {
|
|
char value[3] = {};
|
|
auto result = entry_.ReadValue(std::as_writable_bytes(std::span(value)), 3);
|
|
|
|
ASSERT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(3u, result.size());
|
|
EXPECT_EQ(value[0], 'U');
|
|
EXPECT_EQ(value[1], 'E');
|
|
EXPECT_EQ(value[2], '!');
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, ReadValue_WithOffset_BufferTooSmall) {
|
|
char value[1] = {};
|
|
auto result = entry_.ReadValue(std::as_writable_bytes(std::span(value)), 4);
|
|
|
|
ASSERT_EQ(Status::ResourceExhausted(), result.status());
|
|
EXPECT_EQ(1u, result.size());
|
|
EXPECT_EQ(value[0], 'E');
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, ReadValue_WithOffset_EmptyRead) {
|
|
char value[16] = {'?'};
|
|
auto result = entry_.ReadValue(std::as_writable_bytes(std::span(value)), 6);
|
|
|
|
ASSERT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(0u, result.size());
|
|
EXPECT_EQ(value[0], '?');
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, ReadValue_WithOffset_PastEnd) {
|
|
char value[16] = {};
|
|
auto result = entry_.ReadValue(std::as_writable_bytes(std::span(value)), 7);
|
|
|
|
EXPECT_EQ(Status::OutOfRange(), result.status());
|
|
EXPECT_EQ(0u, result.size());
|
|
}
|
|
|
|
TEST(ValidEntry, Write) {
|
|
FakeFlashMemoryBuffer<1024, 4> flash;
|
|
FlashPartition partition(&flash, 0, flash.sector_count(), 32);
|
|
|
|
Entry entry = Entry::Valid(
|
|
partition, 64, kFormatWithChecksum, "key45", kValue1, kTransactionId1);
|
|
|
|
auto result = entry.Write("key45", kValue1);
|
|
EXPECT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(32u, result.size());
|
|
EXPECT_EQ(std::memcmp(&flash.buffer()[64], kEntry1.data(), kEntry1.size()),
|
|
0);
|
|
}
|
|
|
|
constexpr auto kHeader2 = bytes::String(
|
|
"\x42\x51\x16\xad" // magic
|
|
"\xba\xb3\x00\x00" // checksum (CRC16)
|
|
"\x00" // alignment
|
|
"\x01" // key length
|
|
"\xff\xff" // value size
|
|
"\x00\x01\x02\x03" // transaction ID
|
|
);
|
|
|
|
constexpr auto kKeyAndPadding2 =
|
|
bytes::String("K\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
|
|
|
|
class TombstoneEntryInFlash : public ::testing::Test {
|
|
protected:
|
|
TombstoneEntryInFlash()
|
|
: flash_(bytes::Concat(kHeader2, kKeyAndPadding2)), partition_(&flash_) {
|
|
EXPECT_EQ(OkStatus(), Entry::Read(partition_, 0, kFormats, &entry_));
|
|
}
|
|
|
|
FakeFlashMemoryBuffer<1024, 4> flash_;
|
|
FlashPartition partition_;
|
|
Entry entry_;
|
|
};
|
|
|
|
TEST_F(TombstoneEntryInFlash, PassesChecksumVerification) {
|
|
EXPECT_EQ(OkStatus(), entry_.VerifyChecksumInFlash());
|
|
EXPECT_EQ(OkStatus(), entry_.VerifyChecksum("K", {}));
|
|
}
|
|
|
|
TEST_F(TombstoneEntryInFlash, HeaderContents) {
|
|
EXPECT_EQ(entry_.magic(), kMagicWithChecksum);
|
|
EXPECT_EQ(entry_.key_length(), 1u);
|
|
EXPECT_EQ(entry_.value_size(), 0u);
|
|
EXPECT_EQ(entry_.transaction_id(), 0x03020100u);
|
|
EXPECT_TRUE(entry_.deleted());
|
|
}
|
|
|
|
TEST_F(TombstoneEntryInFlash, ReadKey) {
|
|
Entry::KeyBuffer key = {};
|
|
auto result = entry_.ReadKey(key);
|
|
|
|
ASSERT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(result.size(), entry_.key_length());
|
|
EXPECT_STREQ(key.data(), "K");
|
|
}
|
|
|
|
TEST_F(TombstoneEntryInFlash, ReadValue) {
|
|
char value[32] = {};
|
|
auto result = entry_.ReadValue(std::as_writable_bytes(std::span(value)));
|
|
|
|
ASSERT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(0u, result.size());
|
|
}
|
|
|
|
TEST(TombstoneEntry, Write) {
|
|
FakeFlashMemoryBuffer<1024, 4> flash;
|
|
FlashPartition partition(&flash);
|
|
ChecksumCrc16 checksum;
|
|
|
|
Entry entry =
|
|
Entry::Tombstone(partition, 16, kFormatWithChecksum, "K", 0x03020100);
|
|
|
|
auto result = entry.Write("K", {});
|
|
EXPECT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(32u, result.size());
|
|
EXPECT_EQ(std::memcmp(&flash.buffer()[16],
|
|
bytes::Concat(kHeader2, kKeyAndPadding2).data(),
|
|
kEntry1.size()),
|
|
0);
|
|
}
|
|
|
|
TEST(Entry, Checksum_NoChecksumRequiresZero) {
|
|
FakeFlashMemoryBuffer<1024, 4> flash(kEntry1);
|
|
FlashPartition partition(&flash);
|
|
Entry entry;
|
|
|
|
const EntryFormat format{kMagicWithChecksum, nullptr};
|
|
const internal::EntryFormats formats(format);
|
|
|
|
ASSERT_EQ(OkStatus(), Entry::Read(partition, 0, formats, &entry));
|
|
|
|
EXPECT_EQ(Status::DataLoss(), entry.VerifyChecksumInFlash());
|
|
EXPECT_EQ(Status::DataLoss(), entry.VerifyChecksum({}, {}));
|
|
|
|
std::memset(&flash.buffer()[4], 0, 4); // set the checksum field to 0
|
|
ASSERT_EQ(OkStatus(), Entry::Read(partition, 0, formats, &entry));
|
|
EXPECT_EQ(OkStatus(), entry.VerifyChecksumInFlash());
|
|
EXPECT_EQ(OkStatus(), entry.VerifyChecksum({}, {}));
|
|
}
|
|
|
|
TEST(Entry, Checksum_ChecksPadding) {
|
|
FakeFlashMemoryBuffer<1024, 4> flash(
|
|
bytes::Concat(kHeader1, kKey1, kValue1, bytes::String("\0\0\0\0\1")));
|
|
FlashPartition partition(&flash);
|
|
Entry entry;
|
|
ASSERT_EQ(OkStatus(), Entry::Read(partition, 0, kFormats, &entry));
|
|
|
|
// Last byte in padding is a 1; should fail.
|
|
EXPECT_EQ(Status::DataLoss(), entry.VerifyChecksumInFlash());
|
|
|
|
// The in-memory verification fills in 0s for the padding.
|
|
EXPECT_EQ(OkStatus(), entry.VerifyChecksum("key45", kValue1));
|
|
|
|
flash.buffer()[kEntry1.size() - 1] = byte{0};
|
|
EXPECT_EQ(OkStatus(), entry.VerifyChecksumInFlash());
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, Update_SameFormat_TransactionIdIsUpdated) {
|
|
ASSERT_EQ(OkStatus(),
|
|
entry_.Update(kFormatWithChecksum, kTransactionId1 + 3));
|
|
|
|
EXPECT_EQ(kFormatWithChecksum.magic, entry_.magic());
|
|
EXPECT_EQ(0u, entry_.address());
|
|
EXPECT_EQ(kTransactionId1 + 3, entry_.transaction_id());
|
|
EXPECT_FALSE(entry_.deleted());
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash,
|
|
Update_DifferentFormat_MagicAndTransactionIdAreUpdated) {
|
|
ASSERT_EQ(OkStatus(), entry_.Update(kFormat, kTransactionId1 + 6));
|
|
|
|
EXPECT_EQ(kFormat.magic, entry_.magic());
|
|
EXPECT_EQ(0u, entry_.address());
|
|
EXPECT_EQ(kTransactionId1 + 6, entry_.transaction_id());
|
|
EXPECT_FALSE(entry_.deleted());
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, Update_ReadError_WithChecksumIsError) {
|
|
flash_.InjectReadError(FlashError::Unconditional(Status::Aborted()));
|
|
|
|
EXPECT_EQ(Status::Aborted(),
|
|
entry_.Update(kFormatWithChecksum, kTransactionId1 + 1));
|
|
}
|
|
|
|
// For magic value always use a random 32 bit integer rather than a human
|
|
// readable 4 bytes. See pw_kvs/format.h for more information.
|
|
constexpr EntryFormat kNoChecksumFormat{.magic = 0x721bad24,
|
|
.checksum = nullptr};
|
|
|
|
TEST_F(ValidEntryInFlash, Update_ReadError_NoChecksumIsOkay) {
|
|
flash_.InjectReadError(FlashError::Unconditional(Status::Aborted()));
|
|
|
|
EXPECT_EQ(OkStatus(), entry_.Update(kNoChecksumFormat, kTransactionId1 + 1));
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, Copy) {
|
|
auto result = entry_.Copy(123);
|
|
|
|
EXPECT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(entry_.size(), result.size());
|
|
EXPECT_EQ(0,
|
|
std::memcmp(
|
|
&flash_.buffer().data()[123], kEntry1.data(), kEntry1.size()));
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, Copy_ReadError) {
|
|
flash_.InjectReadError(FlashError::Unconditional(Status::Unimplemented()));
|
|
auto result = entry_.Copy(kEntry1.size());
|
|
EXPECT_EQ(Status::Unimplemented(), result.status());
|
|
EXPECT_EQ(0u, result.size());
|
|
}
|
|
|
|
constexpr uint32_t ByteSum(std::span<const byte> bytes, uint32_t value = 0) {
|
|
for (byte b : bytes) {
|
|
value += unsigned(b);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// Sums the bytes, adding one to each byte so that zeroes change the checksum.
|
|
class ChecksumSummation final : public ChecksumAlgorithm {
|
|
public:
|
|
ChecksumSummation()
|
|
: ChecksumAlgorithm(std::as_bytes(std::span(&sum_, 1))), sum_(0) {}
|
|
|
|
void Reset() override { sum_ = 0; }
|
|
|
|
void Update(std::span<const byte> data) override {
|
|
for (byte b : data) {
|
|
sum_ += unsigned(b) + 1; // Add 1 so zero-value bytes affect checksum.
|
|
}
|
|
}
|
|
|
|
private:
|
|
uint32_t sum_;
|
|
} sum_checksum;
|
|
|
|
// For magic value always use a random 32 bit integer rather than a human
|
|
// readable 4 bytes. See pw_kvs/format.h for more information.
|
|
constexpr uint32_t kMagicWithSum = 0x6093aadb;
|
|
constexpr EntryFormat kFormatWithSum{kMagicWithSum, &sum_checksum};
|
|
constexpr internal::EntryFormats kFormatsWithSum(kFormatWithSum);
|
|
|
|
template <size_t alignment>
|
|
constexpr auto MakeNewFormatWithSumEntry() {
|
|
constexpr uint8_t alignment_units = (alignment + 15) / 16 - 1;
|
|
constexpr size_t size = AlignUp(kEntryWithoutPadding1.size(), alignment);
|
|
|
|
constexpr uint32_t checksum =
|
|
ByteSum(bytes::Concat(kFormatWithSum.magic)) + 0 /* checksum */ +
|
|
alignment_units + kKey1.size() + kValue1.size() +
|
|
ByteSum(bytes::Concat(kTransactionId1 + 1)) + ByteSum(kKey1) +
|
|
ByteSum(kValue1) + size /* +1 for each byte in the checksum */;
|
|
|
|
constexpr auto kNewHeader1 =
|
|
bytes::Concat(kFormatWithSum.magic, // magic
|
|
checksum, // checksum (byte sum)
|
|
alignment_units, // alignment (in 16 B units)
|
|
uint8_t(kKey1.size()), // key length
|
|
uint16_t(kValue1.size()), // value size
|
|
kTransactionId1 + 1); // transaction ID
|
|
constexpr size_t padding = Padding(kEntryWithoutPadding1.size(), alignment);
|
|
return bytes::Concat(
|
|
kNewHeader1, kKey1, kValue1, bytes::Initialized<padding>(0));
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, UpdateAndCopy_DifferentFormatSmallerAlignment) {
|
|
// Uses 16-bit alignment, smaller than the original entry's alignment.
|
|
ASSERT_EQ(OkStatus(), entry_.Update(kFormatWithSum, kTransactionId1 + 1));
|
|
|
|
StatusWithSize result = entry_.Copy(kEntry1.size());
|
|
ASSERT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(kEntry1.size(), result.size());
|
|
|
|
constexpr auto new_data = MakeNewFormatWithSumEntry<16>();
|
|
static_assert(new_data.size() == 32);
|
|
|
|
EXPECT_EQ(
|
|
0,
|
|
std::memcmp(
|
|
&flash_.buffer()[kEntry1.size()], new_data.data(), new_data.size()));
|
|
Entry new_entry;
|
|
ASSERT_EQ(OkStatus(),
|
|
Entry::Read(partition_, 32, kFormatsWithSum, &new_entry));
|
|
EXPECT_EQ(OkStatus(), new_entry.VerifyChecksumInFlash());
|
|
EXPECT_EQ(kFormatWithSum.magic, new_entry.magic());
|
|
EXPECT_EQ(kTransactionId1 + 1, new_entry.transaction_id());
|
|
}
|
|
|
|
TEST(ValidEntryInFlash, UpdateAndCopy_DifferentFormatSameAlignment) {
|
|
// Use 32-bit alignment, the same as the original entry's alignment.
|
|
FakeFlashMemoryBuffer<1024, 4> flash(kEntry1);
|
|
FlashPartition partition(&flash, 0, 4, 32);
|
|
Entry entry;
|
|
ASSERT_EQ(OkStatus(), Entry::Read(partition, 0, kFormats, &entry));
|
|
|
|
ASSERT_EQ(OkStatus(), entry.Update(kFormatWithSum, kTransactionId1 + 1));
|
|
|
|
StatusWithSize result = entry.Copy(32);
|
|
ASSERT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(AlignUp(kEntry1.size(), 32), result.size());
|
|
|
|
constexpr auto new_data = MakeNewFormatWithSumEntry<32>();
|
|
static_assert(new_data.size() == 32);
|
|
|
|
EXPECT_EQ(0,
|
|
std::memcmp(&flash.buffer()[32], new_data.data(), new_data.size()));
|
|
|
|
Entry new_entry;
|
|
ASSERT_EQ(OkStatus(),
|
|
Entry::Read(partition, 32, kFormatsWithSum, &new_entry));
|
|
EXPECT_EQ(OkStatus(), new_entry.VerifyChecksumInFlash());
|
|
EXPECT_EQ(kTransactionId1 + 1, new_entry.transaction_id());
|
|
}
|
|
|
|
TEST(ValidEntryInFlash, UpdateAndCopy_DifferentFormatLargerAlignment) {
|
|
// Use 64-bit alignment, larger than the original entry's alignment.
|
|
FakeFlashMemoryBuffer<1024, 4> flash(kEntry1);
|
|
FlashPartition partition(&flash, 0, 4, 64);
|
|
Entry entry;
|
|
ASSERT_EQ(OkStatus(), Entry::Read(partition, 0, kFormats, &entry));
|
|
|
|
ASSERT_EQ(OkStatus(), entry.Update(kFormatWithSum, kTransactionId1 + 1));
|
|
|
|
StatusWithSize result = entry.Copy(64);
|
|
ASSERT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(AlignUp(kEntry1.size(), 64), result.size());
|
|
|
|
constexpr auto new_data = MakeNewFormatWithSumEntry<64>();
|
|
static_assert(new_data.size() == 64);
|
|
|
|
EXPECT_EQ(0,
|
|
std::memcmp(&flash.buffer()[64], new_data.data(), new_data.size()));
|
|
|
|
Entry new_entry;
|
|
ASSERT_EQ(OkStatus(),
|
|
Entry::Read(partition, 64, kFormatsWithSum, &new_entry));
|
|
EXPECT_EQ(OkStatus(), new_entry.VerifyChecksumInFlash());
|
|
EXPECT_EQ(kTransactionId1 + 1, new_entry.transaction_id());
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, UpdateAndCopy_NoChecksum_UpdatesToNewFormat) {
|
|
// For magic value always use a random 32 bit integer rather than a human
|
|
// readable 4 bytes. See pw_kvs/format.h for more information.
|
|
constexpr EntryFormat no_checksum{.magic = 0x43fae18f, .checksum = nullptr};
|
|
|
|
ASSERT_EQ(OkStatus(), entry_.Update(no_checksum, kTransactionId1 + 1));
|
|
|
|
auto result = entry_.Copy(kEntry1.size());
|
|
ASSERT_EQ(OkStatus(), result.status());
|
|
EXPECT_EQ(kEntry1.size(), result.size());
|
|
|
|
constexpr auto kNewHeader1 =
|
|
bytes::Concat(no_checksum.magic, // magic
|
|
uint32_t(0), // checksum (none)
|
|
uint8_t(0), // alignment (changed to 16 B from 32)
|
|
uint8_t(kKey1.size()), // key length
|
|
uint16_t(kValue1.size()), // value size
|
|
kTransactionId1 + 1); // transaction ID
|
|
constexpr auto kNewEntry1 =
|
|
bytes::Concat(kNewHeader1, kKey1, kValue1, kPadding1);
|
|
|
|
EXPECT_EQ(0,
|
|
std::memcmp(&flash_.buffer()[kEntry1.size()],
|
|
kNewEntry1.data(),
|
|
kNewEntry1.size()));
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, UpdateAndCopyMultple_DifferentFormat) {
|
|
ASSERT_EQ(OkStatus(), entry_.Update(kFormatWithSum, kTransactionId1 + 6));
|
|
|
|
FlashPartition::Address new_address = entry_.size();
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
StatusWithSize copy_result = entry_.Copy(new_address + (i * entry_.size()));
|
|
ASSERT_EQ(OkStatus(), copy_result.status());
|
|
ASSERT_EQ(kEntry1.size(), copy_result.size());
|
|
}
|
|
|
|
for (int j = 0; j < 10; j++) {
|
|
Entry entry;
|
|
FlashPartition::Address read_address = (new_address + (j * entry_.size()));
|
|
ASSERT_EQ(OkStatus(),
|
|
Entry::Read(partition_, read_address, kFormatsWithSum, &entry));
|
|
|
|
EXPECT_EQ(OkStatus(), entry.VerifyChecksumInFlash());
|
|
EXPECT_EQ(kFormatWithSum.magic, entry.magic());
|
|
EXPECT_EQ(read_address, entry.address());
|
|
EXPECT_EQ(kTransactionId1 + 6, entry.transaction_id());
|
|
EXPECT_FALSE(entry.deleted());
|
|
}
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, DifferentFormat_UpdatedCopy_FailsWithWrongMagic) {
|
|
ASSERT_EQ(OkStatus(), entry_.Update(kFormatWithSum, kTransactionId1 + 6));
|
|
|
|
FlashPartition::Address new_address = entry_.size();
|
|
|
|
StatusWithSize copy_result = entry_.Copy(new_address);
|
|
ASSERT_EQ(OkStatus(), copy_result.status());
|
|
ASSERT_EQ(kEntry1.size(), copy_result.size());
|
|
|
|
Entry entry;
|
|
ASSERT_EQ(Status::DataLoss(),
|
|
Entry::Read(partition_, new_address, kFormats, &entry));
|
|
}
|
|
|
|
TEST_F(ValidEntryInFlash, UpdateAndCopy_WriteError) {
|
|
flash_.InjectWriteError(FlashError::Unconditional(Status::Cancelled()));
|
|
|
|
ASSERT_EQ(OkStatus(), entry_.Update(kNoChecksumFormat, kTransactionId1 + 1));
|
|
|
|
auto result = entry_.Copy(kEntry1.size());
|
|
EXPECT_EQ(Status::Cancelled(), result.status());
|
|
EXPECT_EQ(kEntry1.size(), result.size());
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace pw::kvs::internal
|