2020-02-06 23:55:45 +00:00
|
|
|
// 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.
|
|
|
|
|
2020-02-21 03:27:26 +00:00
|
|
|
#include "pw_kvs/internal/entry.h"
|
2020-02-14 16:46:36 +00:00
|
|
|
|
2020-02-14 22:31:26 +00:00
|
|
|
#include <string_view>
|
|
|
|
|
2020-02-06 23:55:45 +00:00
|
|
|
#include "gtest/gtest.h"
|
2020-02-14 17:10:53 +00:00
|
|
|
#include "pw_kvs/alignment.h"
|
2020-02-14 22:31:26 +00:00
|
|
|
#include "pw_kvs/crc16_checksum.h"
|
2020-02-06 23:55:45 +00:00
|
|
|
#include "pw_kvs/flash_memory.h"
|
2020-02-14 17:10:53 +00:00
|
|
|
#include "pw_kvs/in_memory_fake_flash.h"
|
2020-02-14 22:31:26 +00:00
|
|
|
#include "pw_kvs_private/byte_utils.h"
|
|
|
|
#include "pw_span/span.h"
|
2020-02-06 23:55:45 +00:00
|
|
|
|
2020-02-20 01:17:51 +00:00
|
|
|
namespace pw::kvs::internal {
|
2020-02-06 23:55:45 +00:00
|
|
|
namespace {
|
|
|
|
|
2020-02-14 22:31:26 +00:00
|
|
|
using std::byte;
|
|
|
|
using std::string_view;
|
2020-02-14 17:10:53 +00:00
|
|
|
|
|
|
|
TEST(Entry, Size_RoundsUpToAlignment) {
|
2020-02-14 22:31:26 +00:00
|
|
|
FakeFlashBuffer<64, 2> flash(16);
|
|
|
|
FlashPartition partition(&flash, 0, flash.sector_count());
|
|
|
|
|
2020-02-06 23:55:45 +00:00
|
|
|
for (size_t alignment_bytes = 1; alignment_bytes <= 4096; ++alignment_bytes) {
|
2020-02-14 17:10:53 +00:00
|
|
|
const size_t align = AlignUp(alignment_bytes, Entry::kMinAlignmentBytes);
|
|
|
|
|
|
|
|
for (size_t value : {size_t(0), align - 1, align, align + 1, 2 * align}) {
|
2020-02-14 22:31:26 +00:00
|
|
|
Entry entry = Entry::Valid(
|
|
|
|
partition, 0, 9, nullptr, "k", {nullptr, value}, alignment_bytes, 0);
|
2020-02-14 17:10:53 +00:00
|
|
|
ASSERT_EQ(AlignUp(sizeof(EntryHeader) + 1 /* key */ + value, align),
|
|
|
|
entry.size());
|
|
|
|
}
|
|
|
|
|
2020-02-14 22:31:26 +00:00
|
|
|
Entry entry =
|
|
|
|
Entry::Tombstone(partition, 0, 9, nullptr, "k", alignment_bytes, 0);
|
2020-02-14 17:10:53 +00:00
|
|
|
ASSERT_EQ(AlignUp(sizeof(EntryHeader) + 1 /* key */, align), entry.size());
|
2020-02-06 23:55:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-14 17:10:53 +00:00
|
|
|
TEST(Entry, Construct_ValidEntry) {
|
2020-02-14 22:31:26 +00:00
|
|
|
FakeFlashBuffer<64, 2> flash(16);
|
|
|
|
FlashPartition partition(&flash, 0, flash.sector_count());
|
|
|
|
|
2020-02-14 17:10:53 +00:00
|
|
|
auto entry = Entry::Valid(
|
2020-02-14 22:31:26 +00:00
|
|
|
partition, 1, 9, nullptr, "k", as_bytes(span("123")), 1, 9876);
|
2020-02-06 23:55:45 +00:00
|
|
|
|
|
|
|
EXPECT_FALSE(entry.deleted());
|
|
|
|
EXPECT_EQ(entry.magic(), 9u);
|
2020-02-14 17:10:53 +00:00
|
|
|
EXPECT_EQ(entry.value_size(), sizeof("123"));
|
2020-02-20 01:17:51 +00:00
|
|
|
EXPECT_EQ(entry.transaction_id(), 9876u);
|
2020-02-06 23:55:45 +00:00
|
|
|
}
|
|
|
|
|
2020-02-14 17:10:53 +00:00
|
|
|
TEST(Entry, Construct_Tombstone) {
|
2020-02-14 22:31:26 +00:00
|
|
|
FakeFlashBuffer<64, 2> flash(16);
|
|
|
|
FlashPartition partition(&flash, 0, flash.sector_count());
|
|
|
|
|
|
|
|
auto entry = Entry::Tombstone(partition, 1, 99, nullptr, "key", 1, 123);
|
2020-02-06 23:55:45 +00:00
|
|
|
|
|
|
|
EXPECT_TRUE(entry.deleted());
|
|
|
|
EXPECT_EQ(entry.magic(), 99u);
|
2020-02-14 17:10:53 +00:00
|
|
|
EXPECT_EQ(entry.value_size(), 0u);
|
2020-02-20 01:17:51 +00:00
|
|
|
EXPECT_EQ(entry.transaction_id(), 123u);
|
2020-02-06 23:55:45 +00:00
|
|
|
}
|
|
|
|
|
2020-02-14 22:31:26 +00:00
|
|
|
constexpr auto kHeader1 = ByteStr(
|
|
|
|
"\x0d\xf0\x0d\x60" // magic
|
|
|
|
"\xC5\x65\x00\x00" // checksum (CRC16)
|
|
|
|
"\x01" // alignment
|
|
|
|
"\x05" // key length
|
|
|
|
"\x06\x00" // value size
|
|
|
|
"\x99\x98\x97\x96" // version
|
|
|
|
);
|
|
|
|
|
|
|
|
constexpr auto kKey1 = ByteStr("key45");
|
|
|
|
constexpr auto kValue1 = ByteStr("VALUE!");
|
|
|
|
constexpr auto kPadding1 = ByteStr("\0\0\0\0\0");
|
|
|
|
|
|
|
|
constexpr auto kEntry1 = AsBytes(kHeader1, kKey1, kValue1, kPadding1);
|
|
|
|
|
|
|
|
class ValidEntryInFlash : public ::testing::Test {
|
|
|
|
protected:
|
|
|
|
ValidEntryInFlash() : flash_(kEntry1), partition_(&flash_) {
|
|
|
|
EXPECT_EQ(Status::OK, Entry::Read(partition_, 0, &entry_));
|
|
|
|
}
|
|
|
|
|
|
|
|
FakeFlashBuffer<1024, 4> flash_;
|
|
|
|
FlashPartition partition_;
|
|
|
|
Entry entry_;
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_F(ValidEntryInFlash, PassesChecksumVerification) {
|
|
|
|
ChecksumCrc16 checksum;
|
|
|
|
EXPECT_EQ(Status::OK, entry_.VerifyChecksumInFlash(&checksum));
|
|
|
|
EXPECT_EQ(Status::OK, entry_.VerifyChecksum(&checksum, "key45", kValue1));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(ValidEntryInFlash, HeaderContents) {
|
|
|
|
EXPECT_EQ(entry_.magic(), 0x600DF00Du);
|
|
|
|
EXPECT_EQ(entry_.key_length(), 5u);
|
|
|
|
EXPECT_EQ(entry_.value_size(), 6u);
|
2020-02-20 01:17:51 +00:00
|
|
|
EXPECT_EQ(entry_.transaction_id(), 0x96979899u);
|
2020-02-14 22:31:26 +00:00
|
|
|
EXPECT_FALSE(entry_.deleted());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(ValidEntryInFlash, ReadKey) {
|
|
|
|
Entry::KeyBuffer key = {};
|
|
|
|
auto result = entry_.ReadKey(key);
|
|
|
|
|
|
|
|
ASSERT_EQ(Status::OK, 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(as_writable_bytes(span(value)));
|
|
|
|
|
|
|
|
ASSERT_EQ(Status::OK, result.status());
|
|
|
|
EXPECT_EQ(result.size(), entry_.value_size());
|
|
|
|
EXPECT_STREQ(value, "VALUE!");
|
|
|
|
}
|
|
|
|
|
2020-02-19 00:54:31 +00:00
|
|
|
TEST_F(ValidEntryInFlash, ReadValue_BufferTooSmall) {
|
2020-02-14 22:31:26 +00:00
|
|
|
char value[3] = {};
|
|
|
|
auto result = entry_.ReadValue(as_writable_bytes(span(value)));
|
|
|
|
|
|
|
|
ASSERT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
|
|
|
|
EXPECT_EQ(3u, result.size());
|
|
|
|
EXPECT_EQ(value[0], 'V');
|
|
|
|
EXPECT_EQ(value[1], 'A');
|
|
|
|
EXPECT_EQ(value[2], 'L');
|
|
|
|
}
|
|
|
|
|
2020-02-19 00:54:31 +00:00
|
|
|
TEST_F(ValidEntryInFlash, ReadValue_WithOffset) {
|
|
|
|
char value[3] = {};
|
|
|
|
auto result = entry_.ReadValue(as_writable_bytes(span(value)), 3);
|
|
|
|
|
|
|
|
ASSERT_EQ(Status::OK, 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(as_writable_bytes(span(value)), 4);
|
|
|
|
|
|
|
|
ASSERT_EQ(Status::RESOURCE_EXHAUSTED, 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(as_writable_bytes(span(value)), 6);
|
|
|
|
|
|
|
|
ASSERT_EQ(Status::OK, result.status());
|
|
|
|
EXPECT_EQ(0u, result.size());
|
|
|
|
EXPECT_EQ(value[0], '?');
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(ValidEntryInFlash, ReadValue_WithOffset_PastEnd) {
|
|
|
|
char value[16] = {};
|
|
|
|
auto result = entry_.ReadValue(as_writable_bytes(span(value)), 7);
|
|
|
|
|
|
|
|
EXPECT_EQ(Status::OUT_OF_RANGE, result.status());
|
|
|
|
EXPECT_EQ(0u, result.size());
|
|
|
|
}
|
|
|
|
|
2020-02-14 22:31:26 +00:00
|
|
|
TEST(ValidEntry, Write) {
|
|
|
|
FakeFlashBuffer<1024, 4> flash;
|
|
|
|
FlashPartition partition(&flash);
|
|
|
|
ChecksumCrc16 checksum;
|
|
|
|
|
|
|
|
Entry entry = Entry::Valid(
|
|
|
|
partition, 53, 0x600DF00Du, &checksum, "key45", kValue1, 32, 0x96979899u);
|
|
|
|
|
|
|
|
auto result = entry.Write("key45", kValue1);
|
|
|
|
EXPECT_EQ(Status::OK, result.status());
|
|
|
|
EXPECT_EQ(32u, result.size());
|
|
|
|
EXPECT_EQ(std::memcmp(&flash.buffer()[53], kEntry1.data(), kEntry1.size()),
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr auto kHeader2 = ByteStr(
|
|
|
|
"\x0d\xf0\x0d\x60" // magic
|
|
|
|
"\xd5\xf5\x00\x00" // checksum (CRC16)
|
|
|
|
"\x00" // alignment
|
|
|
|
"\x01" // key length
|
|
|
|
"\xff\xff" // value size
|
|
|
|
"\x00\x01\x02\x03" // key version
|
|
|
|
);
|
|
|
|
|
|
|
|
constexpr auto kKeyAndPadding2 = ByteStr("K\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
|
|
|
|
|
|
|
|
class TombstoneEntryInFlash : public ::testing::Test {
|
|
|
|
protected:
|
|
|
|
TombstoneEntryInFlash()
|
|
|
|
: flash_(AsBytes(kHeader2, kKeyAndPadding2)), partition_(&flash_) {
|
|
|
|
EXPECT_EQ(Status::OK, Entry::Read(partition_, 0, &entry_));
|
|
|
|
}
|
|
|
|
|
|
|
|
FakeFlashBuffer<1024, 4> flash_;
|
|
|
|
FlashPartition partition_;
|
|
|
|
Entry entry_;
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_F(TombstoneEntryInFlash, PassesChecksumVerification) {
|
|
|
|
ChecksumCrc16 checksum;
|
|
|
|
EXPECT_EQ(Status::OK, entry_.VerifyChecksumInFlash(&checksum));
|
|
|
|
EXPECT_EQ(Status::OK, entry_.VerifyChecksum(&checksum, "K", {}));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(TombstoneEntryInFlash, HeaderContents) {
|
|
|
|
EXPECT_EQ(entry_.magic(), 0x600DF00Du);
|
|
|
|
EXPECT_EQ(entry_.key_length(), 1u);
|
|
|
|
EXPECT_EQ(entry_.value_size(), 0u);
|
2020-02-20 01:17:51 +00:00
|
|
|
EXPECT_EQ(entry_.transaction_id(), 0x03020100u);
|
2020-02-14 22:31:26 +00:00
|
|
|
EXPECT_TRUE(entry_.deleted());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(TombstoneEntryInFlash, ReadKey) {
|
|
|
|
Entry::KeyBuffer key = {};
|
|
|
|
auto result = entry_.ReadKey(key);
|
|
|
|
|
|
|
|
ASSERT_EQ(Status::OK, 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(as_writable_bytes(span(value)));
|
|
|
|
|
|
|
|
ASSERT_EQ(Status::OK, result.status());
|
|
|
|
EXPECT_EQ(0u, result.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(TombstoneEntry, Write) {
|
|
|
|
FakeFlashBuffer<1024, 4> flash;
|
|
|
|
FlashPartition partition(&flash);
|
|
|
|
ChecksumCrc16 checksum;
|
|
|
|
|
|
|
|
Entry entry = Entry::Tombstone(
|
|
|
|
partition, 16, 0x600DF00Du, &checksum, "K", 16, 0x03020100);
|
|
|
|
|
|
|
|
auto result = entry.Write("K", {});
|
|
|
|
EXPECT_EQ(Status::OK, result.status());
|
|
|
|
EXPECT_EQ(32u, result.size());
|
|
|
|
EXPECT_EQ(std::memcmp(&flash.buffer()[16],
|
|
|
|
AsBytes(kHeader2, kKeyAndPadding2).data(),
|
|
|
|
kEntry1.size()),
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(Entry, Checksum_NoChecksumRequiresZero) {
|
|
|
|
FakeFlashBuffer<1024, 4> flash(kEntry1);
|
|
|
|
FlashPartition partition(&flash);
|
|
|
|
Entry entry;
|
|
|
|
ASSERT_EQ(Status::OK, Entry::Read(partition, 0, &entry));
|
|
|
|
|
|
|
|
EXPECT_EQ(Status::DATA_LOSS, entry.VerifyChecksumInFlash(nullptr));
|
|
|
|
EXPECT_EQ(Status::DATA_LOSS, entry.VerifyChecksum(nullptr, {}, {}));
|
|
|
|
|
|
|
|
std::memset(&flash.buffer()[4], 0, 4); // set the checksum field to 0
|
|
|
|
ASSERT_EQ(Status::OK, Entry::Read(partition, 0, &entry));
|
|
|
|
EXPECT_EQ(Status::OK, entry.VerifyChecksumInFlash(nullptr));
|
|
|
|
EXPECT_EQ(Status::OK, entry.VerifyChecksum(nullptr, {}, {}));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(Entry, Checksum_ChecksPadding) {
|
|
|
|
FakeFlashBuffer<1024, 4> flash(
|
|
|
|
AsBytes(kHeader1, kKey1, kValue1, ByteStr("\0\0\0\0\1")));
|
|
|
|
FlashPartition partition(&flash);
|
|
|
|
Entry entry;
|
|
|
|
ASSERT_EQ(Status::OK, Entry::Read(partition, 0, &entry));
|
|
|
|
|
|
|
|
// Last byte in padding is a 1; should fail.
|
|
|
|
ChecksumCrc16 checksum;
|
|
|
|
EXPECT_EQ(Status::DATA_LOSS, entry.VerifyChecksumInFlash(&checksum));
|
|
|
|
|
|
|
|
// The in-memory verification fills in 0s for the padding.
|
|
|
|
EXPECT_EQ(Status::OK, entry.VerifyChecksum(&checksum, "key45", kValue1));
|
|
|
|
|
|
|
|
flash.buffer()[kEntry1.size() - 1] = byte{0};
|
|
|
|
EXPECT_EQ(Status::OK, entry.VerifyChecksumInFlash(&checksum));
|
|
|
|
}
|
2020-02-06 23:55:45 +00:00
|
|
|
} // namespace
|
2020-02-20 01:17:51 +00:00
|
|
|
} // namespace pw::kvs::internal
|