pw_kvs: Get checksums working; enable test
- Allow checksums to verify data larger than their internal state. This
facilitates using a CRC16 in a uint32_t.
- Move checksum functions to the EntryHeader class, since it ultimately
stores the checksums and is included with it.
- Enable one checksum test. Further tests are needed.
- Make some tweaks to the Get overload for objects.
Change-Id: I989b72905e140794f75c8f8619aaab1623f6f195
diff --git a/pw_kvs/BUILD b/pw_kvs/BUILD
index 69a13ec..0f054f8 100644
--- a/pw_kvs/BUILD
+++ b/pw_kvs/BUILD
@@ -27,6 +27,7 @@
srcs = [
"checksum.cc",
"flash_memory.cc",
+ "format.cc",
"key_value_store.cc",
"pw_kvs_private/format.h",
"pw_kvs_private/macros.h",
diff --git a/pw_kvs/BUILD.gn b/pw_kvs/BUILD.gn
index 0c17d87..5179386 100644
--- a/pw_kvs/BUILD.gn
+++ b/pw_kvs/BUILD.gn
@@ -30,6 +30,7 @@
sources = [
"checksum.cc",
"flash_memory.cc",
+ "format.cc",
"key_value_store.cc",
"pw_kvs_private/format.h",
"pw_kvs_private/macros.h",
diff --git a/pw_kvs/checksum.cc b/pw_kvs/checksum.cc
index 9f14c90..f5db069 100644
--- a/pw_kvs/checksum.cc
+++ b/pw_kvs/checksum.cc
@@ -21,14 +21,12 @@
using std::byte;
Status ChecksumAlgorithm::Verify(span<const byte> checksum) const {
- if (checksum.size() != size_bytes()) {
+ if (checksum.size() < size_bytes()) {
return Status::INVALID_ARGUMENT;
}
-
- if (std::memcmp(state_.data(), checksum.data(), state_.size()) != 0) {
+ if (std::memcmp(state_.data(), checksum.data(), size_bytes()) != 0) {
return Status::DATA_LOSS;
}
-
return Status::OK;
}
diff --git a/pw_kvs/checksum_test.cc b/pw_kvs/checksum_test.cc
index e5333ad..fbf228e 100644
--- a/pw_kvs/checksum_test.cc
+++ b/pw_kvs/checksum_test.cc
@@ -43,13 +43,25 @@
TEST(Checksum, Verify_InvalidSize) {
ChecksumCrc16 algo;
EXPECT_EQ(Status::INVALID_ARGUMENT, algo.Verify({}));
- EXPECT_EQ(Status::INVALID_ARGUMENT, algo.Verify(as_bytes(span(kString))));
+ EXPECT_EQ(Status::INVALID_ARGUMENT,
+ algo.Verify(as_bytes(span(kString.substr(0, 1)))));
+}
+
+TEST(Checksum, Verify_LargerState_ComparesToTruncatedData) {
+ byte crc[3] = {byte{0x84}, byte{0xC1}, byte{0x33}};
+ ChecksumCrc16 algo;
+ ASSERT_GT(sizeof(crc), algo.size_bytes());
+
+ algo.Update(as_bytes(span(kString)));
+
+ EXPECT_EQ(Status::OK, algo.Verify(crc));
}
TEST(Checksum, Reset) {
ChecksumCrc16 crc_algo;
crc_algo.Update(as_bytes(span(kString)));
crc_algo.Reset();
+
EXPECT_EQ(crc_algo.state()[0], byte{0xFF});
EXPECT_EQ(crc_algo.state()[1], byte{0xFF});
}
diff --git a/pw_kvs/format.cc b/pw_kvs/format.cc
new file mode 100644
index 0000000..739783d
--- /dev/null
+++ b/pw_kvs/format.cc
@@ -0,0 +1,93 @@
+// 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_private/format.h"
+
+#include "pw_kvs_private/macros.h"
+
+namespace pw::kvs {
+
+using std::byte;
+using std::string_view;
+
+EntryHeader::EntryHeader(uint32_t magic,
+ ChecksumAlgorithm* algorithm,
+ string_view key,
+ span<const byte> value,
+ uint32_t key_version)
+ : magic_(magic),
+ checksum_(kNoChecksum),
+ key_value_length_(value.size() << kValueLengthShift |
+ (key.size() & kKeyLengthMask)),
+ key_version_(key_version) {
+ if (algorithm != nullptr) {
+ CalculateChecksum(algorithm, key, value);
+ std::memcpy(&checksum_,
+ algorithm->state().data(),
+ std::min(algorithm->size_bytes(), sizeof(checksum_)));
+ }
+}
+
+Status EntryHeader::VerifyChecksum(ChecksumAlgorithm* algorithm,
+ string_view key,
+ span<const byte> value) const {
+ if (algorithm == nullptr) {
+ return checksum() == kNoChecksum ? Status::OK : Status::DATA_LOSS;
+ }
+ CalculateChecksum(algorithm, key, value);
+ return algorithm->Verify(checksum_bytes());
+}
+
+Status EntryHeader::VerifyChecksumInFlash(
+ FlashPartition* partition,
+ FlashPartition::Address header_address,
+ ChecksumAlgorithm* algorithm,
+ string_view key) const {
+ if (algorithm == nullptr) {
+ return checksum() == kNoChecksum ? Status::OK : Status::DATA_LOSS;
+ }
+
+ CalculateChecksum(algorithm, key);
+
+ // Read the value piece-by-piece into a small buffer.
+ // TODO: This read may be unaligned. The partition can handle this, but
+ // consider creating a API that skips the intermediate buffering.
+ byte buffer[32];
+
+ size_t bytes_to_read = value_length();
+ FlashPartition::Address address =
+ header_address + sizeof(*this) + key_length();
+
+ while (bytes_to_read > 0u) {
+ const size_t read_size = std::min(sizeof(buffer), bytes_to_read);
+ TRY(partition->Read(address, read_size, buffer));
+ address += read_size;
+ algorithm->Update(buffer, read_size);
+ }
+
+ return algorithm->Verify(checksum_bytes());
+}
+
+void EntryHeader::CalculateChecksum(ChecksumAlgorithm* algorithm,
+ const string_view key,
+ span<const byte> value) const {
+ algorithm->Reset();
+ algorithm->Update(reinterpret_cast<const byte*>(this) +
+ offsetof(EntryHeader, key_value_length_),
+ sizeof(*this) - offsetof(EntryHeader, key_value_length_));
+ algorithm->Update(as_bytes(span(key)));
+ algorithm->Update(value);
+}
+
+} // namespace pw::kvs
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index fdf5e14..0b6a6a3 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -32,11 +32,6 @@
using Address = FlashPartition::Address;
-constexpr union {
- byte bytes[4];
- uint32_t value;
-} kNoChecksum{};
-
// constexpr uint32_t SixFiveFiveNineNine(std::string_view string) {
constexpr uint32_t HashKey(std::string_view string) {
uint32_t hash = 0;
@@ -144,7 +139,7 @@
DBG("Header: ");
DBG(" Address = 0x%zx", size_t(entry_address));
DBG(" Magic = 0x%zx", size_t(header.magic()));
- DBG(" Checksum = 0x%zx", size_t(header.checksum_as_uint32()));
+ DBG(" Checksum = 0x%zx", size_t(header.checksum()));
DBG(" Key length = 0x%zx", size_t(header.key_length()));
DBG(" Value length = 0x%zx", size_t(header.value_length()));
DBG(" Entry size = 0x%zx", size_t(header.entry_size()));
@@ -171,7 +166,8 @@
TRY(ReadEntryKey(key_descriptor, header.key_length(), key_buffer.data()));
const string_view key(key_buffer.data(), header.key_length());
- TRY(ValidateEntryChecksumInFlash(header, key, key_descriptor));
+ TRY(header.VerifyChecksumInFlash(
+ &partition_, key_descriptor.address, entry_header_format_.checksum, key));
key_descriptor.key_hash = HashKey(key);
DBG("Key hash: %zx (%zu)",
@@ -257,7 +253,9 @@
StatusWithSize result = ReadEntryValue(*key_descriptor, header, value_buffer);
if (result.ok() && options_.verify_on_read) {
- return ValidateEntryChecksum(header, key, value_buffer);
+ return header.VerifyChecksum(entry_header_format_.checksum,
+ key,
+ value_buffer.subspan(0, result.size()));
}
return result;
}
@@ -290,7 +288,7 @@
const KeyValueStore::Entry& KeyValueStore::Iterator::operator*() {
const KeyDescriptor& descriptor = entry_.kvs_.key_descriptor_list_[index_];
- std::memset(entry_.key_buffer_.data(), 0, sizeof(entry_.key_buffer_));
+ std::memset(entry_.key_buffer_.data(), 0, entry_.key_buffer_.size());
EntryHeader header;
if (entry_.kvs_.ReadEntryHeader(descriptor, &header).ok()) {
@@ -313,35 +311,19 @@
return StatusWithSize(header.value_length());
}
-Status KeyValueStore::ValidateEntryChecksumInFlash(
- const EntryHeader& header,
- const string_view key,
- const KeyDescriptor& entry) const {
- if (entry_header_format_.checksum == nullptr) {
- return header.checksum_as_uint32() == 0 ? Status::OK : Status::DATA_LOSS;
+Status KeyValueStore::FixedSizeGet(std::string_view key,
+ byte* value,
+ size_t size_bytes) const {
+ // Ensure that the size of the stored value matches the size of the type.
+ // Otherwise, report error. This check avoids potential memory corruption.
+ StatusWithSize result = ValueSize(key);
+ if (!result.ok()) {
+ return result.status();
}
-
- auto& checksum = *entry_header_format_.checksum;
- checksum.Reset();
- checksum.Update(header.DataForChecksum());
- checksum.Update(as_bytes(span(key)));
-
- // Read the value piece-by-piece into a small buffer.
- // TODO: This read may be unaligned. The partition can handle this, but
- // consider creating a API that skips the intermediate buffering.
- byte buffer[32];
-
- size_t bytes_to_read = header.value_length();
- Address address = entry.address + sizeof(header) + header.key_length();
-
- while (bytes_to_read > 0u) {
- const size_t read_size = std::min(sizeof(buffer), bytes_to_read);
- TRY(partition_.Read(address, read_size, buffer));
- address += read_size;
- checksum.Update(buffer, read_size);
+ if (result.size() != size_bytes) {
+ return Status::INVALID_ARGUMENT;
}
-
- return Status::OK;
+ return Get(key, span(value, size_bytes)).status();
}
Status KeyValueStore::InvalidOperation(string_view key) const {
@@ -412,18 +394,6 @@
return StatusWithSize(read_size);
}
-Status KeyValueStore::ValidateEntryChecksum(const EntryHeader& header,
- string_view key,
- span<const byte> value) const {
- if (entry_header_format_.checksum == nullptr) {
- return header.checksum_as_uint32() == kNoChecksum.value ? Status::OK
- : Status::DATA_LOSS;
- }
-
- CalculateEntryChecksum(header, key, value);
- return entry_header_format_.checksum->Verify(header.checksum());
-}
-
Status KeyValueStore::WriteEntryForExistingKey(KeyDescriptor* key_descriptor,
string_view key,
span<const byte> value) {
@@ -593,9 +563,9 @@
span<const byte> value) {
// write header, key, and value
const EntryHeader header(entry_header_format_.magic,
- CalculateEntryChecksum(header, key, value),
- key.size(),
- value.size(),
+ entry_header_format_.checksum,
+ key,
+ value,
key_descriptor->key_version + 1);
DBG("Appending entry with key version: %zx", size_t(header.key_version()));
@@ -635,22 +605,6 @@
return Status::UNIMPLEMENTED;
}
-span<const byte> KeyValueStore::CalculateEntryChecksum(
- const EntryHeader& header,
- const string_view key,
- span<const byte> value) const {
- if (entry_header_format_.checksum == nullptr) {
- return kNoChecksum.bytes;
- }
-
- auto& checksum = *entry_header_format_.checksum;
- checksum.Reset();
- checksum.Update(header.DataForChecksum());
- checksum.Update(as_bytes(span(key)));
- checksum.Update(value.data(), value.size_bytes());
- return checksum.state();
-}
-
void KeyValueStore::LogDebugInfo() {
const size_t sector_count = partition_.sector_count();
const size_t sector_size_bytes = partition_.sector_size_bytes();
diff --git a/pw_kvs/key_value_store_test.cc b/pw_kvs/key_value_store_test.cc
index 8d979fa..ed2ccc8 100644
--- a/pw_kvs/key_value_store_test.cc
+++ b/pw_kvs/key_value_store_test.cc
@@ -390,8 +390,6 @@
#define ASSERT_OK(expr) ASSERT_EQ(Status::OK, expr)
#define EXPECT_OK(expr) EXPECT_EQ(Status::OK, expr)
-#define AS_SIZE(x) static_cast<size_t>(x)
-
TEST(InMemoryKvs, DISABLED_WriteOneKeyMultipleTimes) {
// Create and erase the fake flash. It will persist across reloads.
Flash flash;
@@ -417,7 +415,7 @@
uint32_t written_value;
EXPECT_EQ(kvs.size(), (reload == 0) ? 0 : 1u);
for (uint32_t i = 0; i < num_writes; ++i) {
- INF("PUT #%zu for key %s with value %zu", AS_SIZE(i), key, AS_SIZE(i));
+ INF("PUT #%zu for key %s with value %zu", size_t(i), key, size_t(i));
written_value = i + 0xfc; // Prevent accidental pass with zero.
EXPECT_OK(kvs.Put(key, written_value));
@@ -1196,7 +1194,7 @@
}
#endif
-TEST_F(KeyValueStoreTest, DISABLED_DifferentValueSameCrc16) {
+TEST_F(KeyValueStoreTest, DifferentValueSameCrc16) {
const char kKey[] = "k";
// With the key and our CRC16 algorithm these both have CRC of 0x82AE
// Given they are the same size and same key, the KVS will need to check
@@ -1214,7 +1212,7 @@
ASSERT_EQ(Status::OK, kvs_.Put(kKey, kValue2));
// Read it back and check it is correct
- char value[3];
+ char value[3] = {};
ASSERT_EQ(Status::OK, kvs_.Get(kKey, &value));
ASSERT_EQ(std::memcmp(value, kValue2, sizeof(value)), 0);
}
diff --git a/pw_kvs/public/pw_kvs/checksum.h b/pw_kvs/public/pw_kvs/checksum.h
index 9f974b3..b1f1658 100644
--- a/pw_kvs/public/pw_kvs/checksum.h
+++ b/pw_kvs/public/pw_kvs/checksum.h
@@ -39,7 +39,9 @@
// Returns the size of the checksum state.
constexpr size_t size_bytes() const { return state_.size(); }
- // Compares a calculated checksum to this checksum's current state.
+ // Compares a calculated checksum to this checksum's current state. The
+ // checksum must be at least as large as size_bytes(). If it is larger, bytes
+ // beyond size_bytes() are ignored.
Status Verify(span<const std::byte> checksum) const;
protected:
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index 2b17a97..89ae31c 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -49,7 +49,7 @@
std::bool_constant<internal::ConvertsToSpan<std::remove_reference_t<T>>(0)>;
// Internal-only persistent storage header format.
-struct EntryHeader;
+class EntryHeader;
struct EntryHeaderFormat {
uint32_t magic; // unique identifier
@@ -91,21 +91,19 @@
StatusWithSize Get(std::string_view key, span<std::byte> value) const;
- template <typename T>
- Status Get(const std::string_view& key, T* value) {
- static_assert(std::is_trivially_copyable<T>(), "KVS values must copyable");
- static_assert(!std::is_pointer<T>(), "KVS values cannot be pointers");
+ // This overload of Get accepts a pointer to a trivially copyable object.
+ // const T& is used instead of T* to prevent arrays from satisfying this
+ // overload. To call Get with an array, pass as_writable_bytes(span(array)),
+ // or pass a pointer to the array instead of the array itself.
+ template <typename Pointer,
+ typename = std::enable_if_t<std::is_pointer_v<Pointer>>>
+ Status Get(const std::string_view& key, const Pointer& pointer) const {
+ using T = std::remove_reference_t<std::remove_pointer_t<Pointer>>;
- // Ensure that the size of the stored value matches the size of the type.
- // Otherwise, report error. This check avoids potential memory corruption.
- StatusWithSize result = ValueSize(key);
- if (!result.ok()) {
- return result.status();
- }
- if (result.size() != sizeof(T)) {
- return Status::INVALID_ARGUMENT;
- }
- return Get(key, as_writable_bytes(span(value, 1))).status();
+ static_assert(std::is_trivially_copyable<T>(), "Values must be copyable");
+ static_assert(!std::is_pointer<T>(), "Values cannot be pointers");
+
+ return FixedSizeGet(key, reinterpret_cast<std::byte*>(pointer), sizeof(T));
}
Status Put(std::string_view key, span<const std::byte> value);
@@ -136,9 +134,10 @@
return kvs_.Get(key(), value_buffer).status();
}
- template <typename T>
- Status Get(T* value) const {
- return kvs_.Get(key(), value);
+ template <typename Pointer,
+ typename = std::enable_if_t<std::is_pointer_v<Pointer>>>
+ Status Get(const Pointer& pointer) const {
+ return kvs_.Get(key(), pointer);
}
StatusWithSize ValueSize() const { return kvs_.ValueSize(key()); }
@@ -219,6 +218,10 @@
}
};
+ Status FixedSizeGet(std::string_view key,
+ std::byte* value,
+ size_t size_bytes) const;
+
Status InvalidOperation(std::string_view key) const;
static constexpr bool InvalidKey(std::string_view key) {
@@ -244,10 +247,6 @@
const EntryHeader& header,
span<std::byte> value) const;
- Status ValidateEntryChecksum(const EntryHeader& header,
- std::string_view key,
- span<const std::byte> value) const;
-
Status LoadEntry(Address entry_address, Address* next_entry_address);
Status AppendNewOrOverwriteStaleExistingDescriptor(
const KeyDescriptor& key_descriptor);
@@ -287,11 +286,6 @@
Status VerifyEntry(SectorDescriptor* sector, KeyDescriptor* key_descriptor);
- span<const std::byte> CalculateEntryChecksum(
- const EntryHeader& header,
- std::string_view key,
- span<const std::byte> value) const;
-
bool AddressInSector(const SectorDescriptor& sector, Address address) const {
const Address sector_base = SectorBaseAddress(§or);
const Address sector_end = sector_base + partition_.sector_size_bytes();
diff --git a/pw_kvs/pw_kvs_private/format.h b/pw_kvs/pw_kvs_private/format.h
index 9cd470c..a1867e4 100644
--- a/pw_kvs/pw_kvs_private/format.h
+++ b/pw_kvs/pw_kvs_private/format.h
@@ -11,40 +11,40 @@
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
+
+// This file defines classes for managing the in-flash format for KVS entires.
#pragma once
#include <cstddef>
#include <cstdint>
#include <cstring>
+#include <string_view>
+#include "pw_kvs/checksum.h"
+#include "pw_kvs/flash_memory.h"
#include "pw_span/span.h"
namespace pw::kvs {
-// In-flash header format.
-struct EntryHeader {
- EntryHeader() : key_value_length_(0) {}
+// EntryHeader represents a key-value entry as stored in flash.
+class EntryHeader {
+ public:
+ EntryHeader() = default;
EntryHeader(uint32_t magic,
- span<const std::byte> checksum,
- size_t key_length,
- size_t value_length,
- uint32_t key_version)
- : magic_(magic),
- checksum_(0),
- key_value_length_(value_length << kValueLengthShift |
- (key_length & kKeyLengthMask)),
- key_version_(key_version) {
- std::memcpy(&checksum_,
- checksum.data(),
- std::min(checksum.size(), sizeof(checksum_)));
- }
+ ChecksumAlgorithm* algorithm,
+ std::string_view key,
+ span<const std::byte> value,
+ uint32_t key_version);
- span<const std::byte> DataForChecksum() const {
- return span(reinterpret_cast<const std::byte*>(this) +
- offsetof(EntryHeader, key_value_length_),
- sizeof(*this) - offsetof(EntryHeader, key_value_length_));
- }
+ Status VerifyChecksum(ChecksumAlgorithm* algorithm,
+ std::string_view key,
+ span<const std::byte> value) const;
+
+ Status VerifyChecksumInFlash(FlashPartition* partition,
+ FlashPartition::Address header_address,
+ ChecksumAlgorithm* algorithm,
+ std::string_view key) const;
size_t entry_size() const {
return sizeof(*this) + key_length() + value_length();
@@ -52,11 +52,7 @@
uint32_t magic() const { return magic_; }
- span<const std::byte> checksum() const {
- return as_bytes(span(&checksum_, 1));
- }
-
- uint32_t checksum_as_uint32() const { return checksum_; }
+ uint32_t checksum() const { return checksum_; }
size_t key_length() const { return key_value_length_ & kKeyLengthMask; }
void set_key_length(uint32_t key_length) {
@@ -72,9 +68,18 @@
uint32_t key_version() const { return key_version_; }
private:
+ static constexpr uint32_t kNoChecksum = 0;
static constexpr uint32_t kKeyLengthMask = 0b111111;
static constexpr uint32_t kValueLengthShift = 8;
+ span<const std::byte> checksum_bytes() const {
+ return as_bytes(span(&checksum_, 1));
+ }
+
+ void CalculateChecksum(ChecksumAlgorithm* algorithm,
+ std::string_view key,
+ span<const std::byte> value = {}) const;
+
uint32_t magic_;
uint32_t checksum_;
// 6 bits, 0: 5 - key - maximum 64 characters