pw_kvs: Implement AlignedChecksum
- Provide AlignedChecksum class that uses AlignedWriter to ensure that
checksum updates are aligned as required.
- Make Finish() non-virtual and call an optional Finalize() operation
instead.
Change-Id: I5b16468b6a55c0c2c9f2c06e37988970af147a11
diff --git a/pw_kvs/alignment_test.cc b/pw_kvs/alignment_test.cc
index 2b0771b..232a8e3 100644
--- a/pw_kvs/alignment_test.cc
+++ b/pw_kvs/alignment_test.cc
@@ -94,6 +94,8 @@
TEST(AlignedWriter, VaryingLengthWriteCalls) {
static constexpr size_t kAlignment = 10;
+ // The output function checks that the data is properly aligned and matches
+ // the expected value (should always be 123456789_...).
OutputToFunction output([](span<const byte> data) {
EXPECT_EQ(data.size() % kAlignment, 0u);
EXPECT_EQ(kData.substr(0, data.size()),
@@ -102,19 +104,26 @@
return StatusWithSize(data.size());
});
- AlignedWriterBuffer<64> writer(kAlignment, output);
+ AlignedWriterBuffer<32> writer(kAlignment, output);
+ // Write values smaller than the alignment.
EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(0, 1)));
EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(1, 9)));
+
+ // Write values larger than the alignment but smaller than the buffer.
EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(10, 11)));
- EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(21, 20)));
- EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(41, 9)));
- EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(50, 10)));
- EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(60, 30)));
- EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(90, 5)));
- EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(95, 0)));
- EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(95, 4)));
- EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(99, 1)));
+
+ // Exactly fill the remainder of the buffer.
+ EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(21, 11)));
+
+ // Fill the buffer more than once.
+ EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(32, 66)));
+
+ // Write nothing.
+ EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(98, 0)));
+
+ // Write the remaining data.
+ EXPECT_EQ(Status::OK, writer.Write(kBytes.subspan(98, 2)));
auto result = writer.Flush();
EXPECT_EQ(Status::OK, result.status());
diff --git a/pw_kvs/checksum_test.cc b/pw_kvs/checksum_test.cc
index 1939ff3..205defb 100644
--- a/pw_kvs/checksum_test.cc
+++ b/pw_kvs/checksum_test.cc
@@ -67,5 +67,64 @@
EXPECT_EQ(state[1], byte{0xFF});
}
+constexpr size_t kAlignment = 10;
+
+constexpr std::string_view kData =
+ "123456789_123456789_123456789_123456789_123456789_" // 50
+ "123456789_123456789_123456789_123456789_123456789_"; // 100
+const span<const byte> kBytes = as_bytes(span(kData));
+
+class PickyChecksum final : public AlignedChecksum<kAlignment, 32> {
+ public:
+ PickyChecksum() : AlignedChecksum(data_), data_{}, size_(0) {}
+
+ void Reset() override {}
+
+ void FinalizeAligned() override { EXPECT_EQ(kData.size(), size_); }
+
+ void UpdateAligned(span<const std::byte> data) override {
+ ASSERT_EQ(data.size() % kAlignment, 0u);
+ EXPECT_EQ(kData.substr(0, data.size()),
+ std::string_view(reinterpret_cast<const char*>(data.data()),
+ data.size()));
+
+ std::memcpy(&data_[size_], data.data(), data.size());
+ size_ += data.size();
+ }
+
+ private:
+ std::byte data_[kData.size()];
+ size_t size_;
+};
+
+TEST(AlignedChecksum, MaintainsAlignment) {
+ PickyChecksum checksum;
+
+ // Write values smaller than the alignment.
+ checksum.Update(kBytes.subspan(0, 1));
+ checksum.Update(kBytes.subspan(1, 9));
+
+ // Write values larger than the alignment but smaller than the buffer.
+ checksum.Update(kBytes.subspan(10, 11));
+
+ // Exactly fill the remainder of the buffer.
+ checksum.Update(kBytes.subspan(21, 11));
+
+ // Fill the buffer more than once.
+ checksum.Update(kBytes.subspan(32, 66));
+
+ // Write nothing.
+ checksum.Update(kBytes.subspan(98, 0));
+
+ // Write the remaining data.
+ checksum.Update(kBytes.subspan(98, 2));
+
+ auto state = checksum.Finish();
+ EXPECT_EQ(std::string_view(reinterpret_cast<const char*>(state.data()),
+ state.size()),
+ kData);
+ EXPECT_EQ(Status::OK, checksum.Verify(kBytes));
+}
+
} // namespace
} // namespace pw::kvs
diff --git a/pw_kvs/format.cc b/pw_kvs/format.cc
index cb37591..2c09250 100644
--- a/pw_kvs/format.cc
+++ b/pw_kvs/format.cc
@@ -36,10 +36,10 @@
value_length_bytes_(value_length_bytes),
key_version_(key_version) {
if (algorithm != nullptr) {
- CalculateChecksum(algorithm, key, value);
+ span<const byte> checksum = CalculateChecksum(algorithm, key, value);
std::memcpy(&checksum_,
- algorithm->Finish().data(),
- std::min(algorithm->size_bytes(), sizeof(checksum_)));
+ checksum.data(),
+ std::min(checksum.size(), sizeof(checksum_)));
}
// TODO: 0 is an invalid alignment value. There should be an assert for this.
@@ -53,7 +53,6 @@
return checksum() == kNoChecksum ? Status::OK : Status::DATA_LOSS;
}
CalculateChecksum(algorithm, key, value);
- algorithm->Finish();
return algorithm->Verify(checksum_bytes());
}
@@ -104,19 +103,21 @@
address += read_size;
bytes_to_read -= read_size;
}
- algorithm->Finish();
+ algorithm->Finish();
return algorithm->Verify(checksum_bytes());
}
-void EntryHeader::CalculateChecksum(ChecksumAlgorithm* algorithm,
- const string_view key,
- span<const byte> value) const {
+span<const byte> EntryHeader::CalculateChecksum(ChecksumAlgorithm* algorithm,
+ const string_view key,
+ span<const byte> value) const {
algorithm->Reset();
algorithm->Update(reinterpret_cast<const byte*>(this) + checked_data_offset(),
sizeof(*this) - checked_data_offset());
algorithm->Update(as_bytes(span(key)));
algorithm->Update(value);
+
+ return algorithm->Finish();
}
} // namespace pw::kvs
diff --git a/pw_kvs/public/pw_kvs/checksum.h b/pw_kvs/public/pw_kvs/checksum.h
index c7c883a..3acb5a4 100644
--- a/pw_kvs/public/pw_kvs/checksum.h
+++ b/pw_kvs/public/pw_kvs/checksum.h
@@ -15,6 +15,7 @@
#include <cstddef>
+#include "pw_kvs/alignment.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
@@ -28,21 +29,28 @@
// Updates the checksum with the provided data.
virtual void Update(span<const std::byte> data) = 0;
- // Update the checksum from a pointer and size.
+ // Updates the checksum from a pointer and size.
void Update(const void* data, size_t size_bytes) {
return Update(span(static_cast<const std::byte*>(data), size_bytes));
}
// Returns the final result of the checksum. Update() can no longer be called
// after this. The returned span is valid until a call to Reset().
- virtual span<const std::byte> Finish() = 0;
+ //
+ // Finish MUST be called before calling Verify.
+ span<const std::byte> Finish() {
+ Finalize(); // Implemented by derived classes, if required.
+ return state();
+ }
// 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. The
- // checksum must be at least as large as size_bytes(). If it is larger, bytes
- // beyond size_bytes() are ignored.
+ // Compares a calculated checksum to this checksum's state. The checksum must
+ // be at least as large as size_bytes(). If it is larger, bytes beyond
+ // size_bytes() are ignored.
+ //
+ // Finish MUST be called before calling Verify.
Status Verify(span<const std::byte> checksum) const;
protected:
@@ -57,7 +65,40 @@
constexpr span<const std::byte> state() const { return state_; }
private:
+ // Checksums that require finalizing operations may override this method.
+ virtual void Finalize() {}
+
span<const std::byte> state_;
};
+// Calculates a checksum in kAlignmentBytes chunks. Checksum classes can inherit
+// from this and implement UpdateAligned and FinalizeAligned instead of Update
+// and Finalize.
+template <size_t kAlignmentBytes, size_t kBufferSize = kAlignmentBytes>
+class AlignedChecksum : public ChecksumAlgorithm {
+ public:
+ void Update(span<const std::byte> data) final { writer_.Write(data); }
+
+ protected:
+ constexpr AlignedChecksum(span<const std::byte> state)
+ : ChecksumAlgorithm(state),
+ output_(this),
+ writer_(kAlignmentBytes, output_) {}
+
+ ~AlignedChecksum() = default;
+
+ private:
+ void Finalize() final {
+ writer_.Flush();
+ FinalizeAligned();
+ }
+
+ virtual void UpdateAligned(span<const std::byte> data) = 0;
+
+ virtual void FinalizeAligned() = 0;
+
+ OutputToMethod<&AlignedChecksum::UpdateAligned> output_;
+ AlignedWriterBuffer<kBufferSize> writer_;
+};
+
} // namespace pw::kvs
diff --git a/pw_kvs/public/pw_kvs/crc16_checksum.h b/pw_kvs/public/pw_kvs/crc16_checksum.h
index c2a0c7c..51b096b 100644
--- a/pw_kvs/public/pw_kvs/crc16_checksum.h
+++ b/pw_kvs/public/pw_kvs/crc16_checksum.h
@@ -23,14 +23,12 @@
public:
ChecksumCrc16() : ChecksumAlgorithm(as_bytes(span(&crc_, 1))) {}
- void Reset() final { crc_ = checksum::kCcittCrc16DefaultInitialValue; }
+ void Reset() override { crc_ = checksum::kCcittCrc16DefaultInitialValue; }
- void Update(span<const std::byte> data) final {
+ void Update(span<const std::byte> data) override {
crc_ = checksum::CcittCrc16(data, crc_);
}
- span<const std::byte> Finish() final { return state(); }
-
private:
uint16_t crc_ = checksum::kCcittCrc16DefaultInitialValue;
};
diff --git a/pw_kvs/pw_kvs_private/format.h b/pw_kvs/pw_kvs_private/format.h
index db42b12..e837238 100644
--- a/pw_kvs/pw_kvs_private/format.h
+++ b/pw_kvs/pw_kvs_private/format.h
@@ -137,9 +137,9 @@
return as_bytes(span(&checksum_, 1));
}
- void CalculateChecksum(ChecksumAlgorithm* algorithm,
- std::string_view key,
- span<const std::byte> value) const;
+ span<const std::byte> CalculateChecksum(ChecksumAlgorithm* algorithm,
+ std::string_view key,
+ span<const std::byte> value) const;
static constexpr uint8_t alignment_bytes_to_units(size_t alignment_bytes) {
return (alignment_bytes + 15) / 16 - 1; // An alignment of 0 is invalid.