1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_blob_store/blob_store.h"
16
17 #include <array>
18 #include <cstddef>
19 #include <cstring>
20
21 #include "gtest/gtest.h"
22 #include "pw_kvs/crc16_checksum.h"
23 #include "pw_kvs/fake_flash_memory.h"
24 #include "pw_kvs/flash_memory.h"
25 #include "pw_kvs/test_key_value_store.h"
26 #include "pw_log/log.h"
27 #include "pw_random/xor_shift.h"
28 #include "pw_span/span.h"
29
30 #ifndef PW_FLASH_TEST_ALIGNMENT
31 #define PW_FLASH_TEST_ALIGNMENT 1
32 #endif
33
34 namespace pw::blob_store {
35 namespace {
36
37 class BlobStoreTest : public ::testing::Test {
38 protected:
39 static constexpr char kBlobTitle[] = "TestBlobBlock";
40
BlobStoreTest()41 BlobStoreTest() : flash_(kFlashAlignment), partition_(&flash_) {}
42
InitFlashTo(span<const std::byte> contents)43 void InitFlashTo(span<const std::byte> contents) {
44 ASSERT_EQ(OkStatus(), partition_.Erase());
45 std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
46 }
47
InitSourceBufferToRandom(uint64_t seed,size_t init_size_bytes=kBlobDataSize)48 void InitSourceBufferToRandom(uint64_t seed,
49 size_t init_size_bytes = kBlobDataSize) {
50 ASSERT_LE(init_size_bytes, source_buffer_.size());
51 random::XorShiftStarRng64 rng(seed);
52
53 std::memset(source_buffer_.data(),
54 static_cast<int>(flash_.erased_memory_content()),
55 source_buffer_.size());
56 rng.Get(span(source_buffer_).first(init_size_bytes));
57 }
58
InitSourceBufferToFill(char fill,size_t fill_size_bytes=kBlobDataSize)59 void InitSourceBufferToFill(char fill,
60 size_t fill_size_bytes = kBlobDataSize) {
61 ASSERT_LE(fill_size_bytes, source_buffer_.size());
62 std::memset(source_buffer_.data(),
63 static_cast<int>(flash_.erased_memory_content()),
64 source_buffer_.size());
65 std::memset(source_buffer_.data(), fill, fill_size_bytes);
66 }
67
68 // Fill the source buffer with random pattern based on given seed, written to
69 // BlobStore in specified chunk size.
WriteTestBlock(size_t write_size_bytes=kBlobDataSize)70 void WriteTestBlock(size_t write_size_bytes = kBlobDataSize) {
71 ASSERT_LE(write_size_bytes, source_buffer_.size());
72 constexpr size_t kBufferSize = 256;
73 kvs::ChecksumCrc16 checksum;
74
75 ConstByteSpan write_data = span(source_buffer_).first(write_size_bytes);
76
77 BlobStoreBuffer<kBufferSize> blob(
78 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
79 EXPECT_EQ(OkStatus(), blob.Init());
80
81 BlobStore::BlobWriterWithBuffer writer(blob);
82 EXPECT_EQ(OkStatus(), writer.Open());
83 ASSERT_EQ(OkStatus(), writer.Write(write_data));
84 EXPECT_EQ(OkStatus(), writer.Close());
85
86 // Use reader to check for valid data.
87 BlobStore::BlobReader reader(blob);
88 ASSERT_EQ(OkStatus(), reader.Open());
89 Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
90 ASSERT_TRUE(result.ok());
91 EXPECT_EQ(write_size_bytes, result.value().size_bytes());
92 VerifyFlash(result.value().first(write_size_bytes));
93 VerifyFlash(flash_.buffer().first(write_size_bytes));
94 EXPECT_EQ(OkStatus(), reader.Close());
95 }
96
97 // Open a new blob instance and read the blob using the given read chunk size.
ChunkReadTest(size_t read_chunk_size)98 void ChunkReadTest(size_t read_chunk_size) {
99 kvs::ChecksumCrc16 checksum;
100
101 VerifyFlash(flash_.buffer());
102
103 constexpr size_t kBufferSize = 16;
104 BlobStoreBuffer<kBufferSize> blob(
105 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
106 EXPECT_EQ(OkStatus(), blob.Init());
107
108 // Use reader to check for valid data.
109 BlobStore::BlobReader reader1(blob);
110 ASSERT_EQ(OkStatus(), reader1.Open());
111 Result<ConstByteSpan> possible_blob = reader1.GetMemoryMappedBlob();
112 ASSERT_TRUE(possible_blob.ok());
113 VerifyFlash(possible_blob.value());
114 EXPECT_EQ(OkStatus(), reader1.Close());
115
116 BlobStore::BlobReader reader(blob);
117 ASSERT_EQ(OkStatus(), reader.Open());
118
119 std::array<std::byte, kBlobDataSize> read_buffer;
120
121 ByteSpan read_span = read_buffer;
122 while (read_span.size_bytes() > 0) {
123 size_t read_size = std::min(read_span.size_bytes(), read_chunk_size);
124
125 PW_LOG_DEBUG("Do write of %u bytes, %u bytes remain",
126 static_cast<unsigned>(read_size),
127 static_cast<unsigned>(read_span.size_bytes()));
128
129 ASSERT_EQ(read_span.size_bytes(), reader.ConservativeReadLimit());
130 auto result = reader.Read(read_span.first(read_size));
131 ASSERT_EQ(result.status(), OkStatus());
132 read_span = read_span.subspan(read_size);
133 }
134 EXPECT_EQ(OkStatus(), reader.Close());
135
136 VerifyFlash(read_buffer);
137 }
138
VerifyFlash(ConstByteSpan verify_bytes,size_t offset=0)139 void VerifyFlash(ConstByteSpan verify_bytes, size_t offset = 0) {
140 // Should be defined as same size.
141 EXPECT_EQ(source_buffer_.size(), flash_.buffer().size_bytes());
142
143 // Can't allow it to march off the end of source_buffer_.
144 ASSERT_LE((verify_bytes.size_bytes() + offset), source_buffer_.size());
145
146 for (size_t i = 0; i < verify_bytes.size_bytes(); i++) {
147 ASSERT_EQ(source_buffer_[i + offset], verify_bytes[i]);
148 }
149 }
150
151 static constexpr size_t kFlashAlignment = PW_FLASH_TEST_ALIGNMENT;
152 static constexpr size_t kSectorSize = 2048;
153 static constexpr size_t kSectorCount = 2;
154 static constexpr size_t kBlobDataSize = (kSectorCount * kSectorSize);
155
156 kvs::FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_;
157 kvs::FlashPartition partition_;
158 std::array<std::byte, kBlobDataSize> source_buffer_;
159 };
160
TEST_F(BlobStoreTest,Init_Ok)161 TEST_F(BlobStoreTest, Init_Ok) {
162 // TODO(davidrogers): Do init test with flash/kvs explicitly in the different
163 // possible entry states.
164 constexpr size_t kBufferSize = 256;
165 BlobStoreBuffer<kBufferSize> blob(
166 "Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
167 EXPECT_EQ(OkStatus(), blob.Init());
168 }
169
TEST_F(BlobStoreTest,Writer_ConservativeLimits)170 TEST_F(BlobStoreTest, Writer_ConservativeLimits) {
171 constexpr size_t kBufferSize = 256;
172 BlobStoreBuffer<kBufferSize> blob(
173 "Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
174 ASSERT_EQ(OkStatus(), blob.Init());
175
176 BlobStore::BlobWriterWithBuffer writer(blob);
177 ASSERT_EQ(OkStatus(), writer.Open());
178 EXPECT_EQ(writer.ConservativeReadLimit(), 0u);
179 EXPECT_EQ(writer.ConservativeWriteLimit(), kSectorSize * kSectorCount);
180 ASSERT_EQ(OkStatus(), writer.Close());
181
182 BlobStore::DeferredWriterWithBuffer deferred_writer(blob);
183 ASSERT_EQ(OkStatus(), deferred_writer.Open());
184 EXPECT_EQ(deferred_writer.ConservativeReadLimit(), 0u);
185 EXPECT_EQ(deferred_writer.ConservativeWriteLimit(), kBufferSize);
186 }
187
188 // Write to the blob using a flash_write_size_bytes smaller than the
189 // buffer size. Use Write operations smaller than flash_write_size_bytes
190 // to ensure it checks the internal buffering path.
TEST_F(BlobStoreTest,OversizedWriteBuffer)191 TEST_F(BlobStoreTest, OversizedWriteBuffer) {
192 size_t write_size_bytes = 8;
193 ASSERT_LE(write_size_bytes, source_buffer_.size());
194 constexpr size_t kBufferSize = 256;
195 kvs::ChecksumCrc16 checksum;
196
197 InitSourceBufferToRandom(0x123d123);
198
199 ConstByteSpan write_data = span(source_buffer_);
200 ConstByteSpan original_source = span(source_buffer_);
201
202 EXPECT_EQ(OkStatus(), partition_.Erase());
203
204 BlobStoreBuffer<kBufferSize> blob(
205 kBlobTitle, partition_, &checksum, kvs::TestKvs(), 64);
206 EXPECT_EQ(OkStatus(), blob.Init());
207
208 BlobStore::BlobWriterWithBuffer writer(blob);
209 EXPECT_EQ(OkStatus(), writer.Open());
210 while (write_data.size_bytes() > 0) {
211 ASSERT_EQ(OkStatus(), writer.Write(write_data.first(write_size_bytes)));
212 write_data = write_data.subspan(write_size_bytes);
213 }
214 EXPECT_EQ(OkStatus(), writer.Close());
215
216 // Use reader to check for valid data.
217 BlobStore::BlobReader reader(blob);
218 ASSERT_EQ(OkStatus(), reader.Open());
219 Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
220 ASSERT_TRUE(result.ok());
221 EXPECT_EQ(original_source.size_bytes(), result.value().size_bytes());
222 VerifyFlash(result.value());
223 VerifyFlash(flash_.buffer());
224 EXPECT_EQ(OkStatus(), reader.Close());
225 }
226
TEST_F(BlobStoreTest,Reader_ConservativeLimits)227 TEST_F(BlobStoreTest, Reader_ConservativeLimits) {
228 InitSourceBufferToRandom(0x11309);
229 WriteTestBlock();
230
231 kvs::ChecksumCrc16 checksum;
232 constexpr size_t kBufferSize = 16;
233 BlobStoreBuffer<kBufferSize> blob(
234 "TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
235 EXPECT_EQ(OkStatus(), blob.Init());
236 EXPECT_TRUE(blob.HasData());
237 BlobStore::BlobReader reader(blob);
238 ASSERT_EQ(OkStatus(), reader.Open());
239
240 EXPECT_EQ(kBlobDataSize, reader.ConservativeReadLimit());
241 EXPECT_EQ(0u, reader.ConservativeWriteLimit());
242 }
243
TEST_F(BlobStoreTest,IsOpen)244 TEST_F(BlobStoreTest, IsOpen) {
245 constexpr size_t kBufferSize = 256;
246 BlobStoreBuffer<kBufferSize> blob(
247 "Blob_open", partition_, nullptr, kvs::TestKvs(), kBufferSize);
248 EXPECT_EQ(OkStatus(), blob.Init());
249
250 BlobStore::DeferredWriterWithBuffer deferred_writer(blob);
251 EXPECT_EQ(false, deferred_writer.IsOpen());
252 EXPECT_EQ(OkStatus(), deferred_writer.Open());
253 EXPECT_EQ(true, deferred_writer.IsOpen());
254 EXPECT_EQ(OkStatus(), deferred_writer.Close());
255 EXPECT_EQ(false, deferred_writer.IsOpen());
256
257 BlobStore::BlobWriterWithBuffer writer(blob);
258 EXPECT_EQ(false, writer.IsOpen());
259 EXPECT_EQ(OkStatus(), writer.Open());
260 EXPECT_EQ(true, writer.IsOpen());
261
262 EXPECT_FALSE(blob.HasData());
263
264 // Need to write something, so the blob reader is able to open.
265 std::array<std::byte, 64> tmp_buffer = {};
266 EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
267 EXPECT_EQ(OkStatus(), writer.Close());
268 EXPECT_EQ(false, writer.IsOpen());
269
270 EXPECT_TRUE(blob.HasData());
271 BlobStore::BlobReader reader(blob);
272 EXPECT_EQ(false, reader.IsOpen());
273 ASSERT_EQ(OkStatus(), reader.Open());
274 EXPECT_EQ(true, reader.IsOpen());
275 EXPECT_EQ(OkStatus(), reader.Close());
276 EXPECT_EQ(false, reader.IsOpen());
277 }
278
279 // Write to the blob using no write buffer size. Write operations must be
280 // multiples of flash_write_size_bytes.
TEST_F(BlobStoreTest,NoWriteBuffer_1Alignment)281 TEST_F(BlobStoreTest, NoWriteBuffer_1Alignment) {
282 if (kFlashAlignment > 1) {
283 // Test not valid for flash alignments greater than 1.
284 return;
285 }
286
287 const size_t kWriteSizeBytes = 1;
288 kvs::ChecksumCrc16 checksum;
289
290 InitSourceBufferToRandom(0xaabd123);
291
292 ConstByteSpan write_data = span(source_buffer_);
293 ConstByteSpan original_source = span(source_buffer_);
294
295 EXPECT_EQ(OkStatus(), partition_.Erase());
296
297 BlobStore blob(kBlobTitle,
298 partition_,
299 &checksum,
300 kvs::TestKvs(),
301 span<std::byte>(),
302 kWriteSizeBytes);
303 EXPECT_EQ(OkStatus(), blob.Init());
304
305 BlobStore::BlobWriterWithBuffer writer(blob);
306 EXPECT_EQ(OkStatus(), writer.Open());
307
308 size_t test_write_size[] = {1, 1, 2, 4, 32, 128};
309
310 for (size_t size : test_write_size) {
311 ASSERT_EQ(OkStatus(), writer.Write(write_data.first(size)));
312 write_data = write_data.subspan(size);
313 }
314
315 while (write_data.size_bytes() > 0) {
316 const size_t finish_write_size = 8;
317 ASSERT_EQ(OkStatus(), writer.Write(write_data.first(finish_write_size)));
318 write_data = write_data.subspan(finish_write_size);
319 }
320 EXPECT_EQ(write_data.size_bytes(), 0U);
321 EXPECT_EQ(OkStatus(), writer.Close());
322
323 // Use reader to check for valid data.
324 BlobStore::BlobReader reader(blob);
325 ASSERT_EQ(OkStatus(), reader.Open());
326 Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
327 ASSERT_TRUE(result.ok());
328 EXPECT_EQ(original_source.size_bytes(), result.value().size_bytes());
329 VerifyFlash(result.value());
330 VerifyFlash(flash_.buffer());
331 EXPECT_EQ(OkStatus(), reader.Close());
332 }
333
334 // Write to the blob using no write buffer size. Write operations must be
335 // multiples of flash_write_size_bytes.
TEST_F(BlobStoreTest,NoWriteBuffer_16Alignment)336 TEST_F(BlobStoreTest, NoWriteBuffer_16Alignment) {
337 if (kFlashAlignment > 16) {
338 // Test not valid for flash alignments greater than 16.
339 return;
340 }
341
342 const size_t kWriteSizeBytes = 16;
343 kvs::ChecksumCrc16 checksum;
344
345 InitSourceBufferToRandom(0x6745d123);
346
347 ConstByteSpan write_data = span(source_buffer_);
348 ConstByteSpan original_source = span(source_buffer_);
349
350 EXPECT_EQ(OkStatus(), partition_.Erase());
351
352 BlobStore blob(kBlobTitle,
353 partition_,
354 &checksum,
355 kvs::TestKvs(),
356 span<std::byte>(),
357 kWriteSizeBytes);
358 EXPECT_EQ(OkStatus(), blob.Init());
359
360 BlobStore::BlobWriterWithBuffer writer(blob);
361 EXPECT_EQ(OkStatus(), writer.Open());
362 ASSERT_EQ(Status::InvalidArgument(), writer.Write(write_data.first(1)));
363 ASSERT_EQ(Status::InvalidArgument(),
364 writer.Write(write_data.first(kWriteSizeBytes / 2)));
365
366 ASSERT_EQ(OkStatus(), writer.Write(write_data.first(4 * kWriteSizeBytes)));
367 write_data = write_data.subspan(4 * kWriteSizeBytes);
368
369 ASSERT_EQ(Status::InvalidArgument(), writer.Write(write_data.first(1)));
370 ASSERT_EQ(Status::InvalidArgument(),
371 writer.Write(write_data.first(kWriteSizeBytes / 2)));
372
373 while (write_data.size_bytes() > 0) {
374 ASSERT_EQ(OkStatus(), writer.Write(write_data.first(kWriteSizeBytes)));
375 write_data = write_data.subspan(kWriteSizeBytes);
376 }
377 EXPECT_EQ(OkStatus(), writer.Close());
378
379 // Use reader to check for valid data.
380 BlobStore::BlobReader reader(blob);
381 ASSERT_EQ(OkStatus(), reader.Open());
382 Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
383 ASSERT_TRUE(result.ok());
384 EXPECT_EQ(original_source.size_bytes(), result.value().size_bytes());
385 VerifyFlash(result.value());
386 VerifyFlash(flash_.buffer());
387 EXPECT_EQ(OkStatus(), reader.Close());
388 }
389
TEST_F(BlobStoreTest,FileName)390 TEST_F(BlobStoreTest, FileName) {
391 InitSourceBufferToRandom(0x8675309);
392 WriteTestBlock();
393 constexpr std::string_view kFileName("my_file_1.bin");
394 std::array<std::byte, 64> tmp_buffer = {};
395 static_assert(kFileName.size() <= tmp_buffer.size());
396 kvs::ChecksumCrc16 checksum;
397 constexpr size_t kBufferSize = 256;
398 {
399 // Create/init a blob store in a nested scope so it can be re-initialized
400 // later when checking the read.
401 BlobStoreBuffer<kBufferSize> blob(
402 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
403 EXPECT_EQ(OkStatus(), blob.Init());
404
405 BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
406
407 EXPECT_EQ(OkStatus(), writer.Open());
408 EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
409 EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
410 EXPECT_EQ(OkStatus(), writer.Close());
411 EXPECT_EQ(OkStatus(),
412 kvs::TestKvs().acquire()->Get(kBlobTitle, tmp_buffer).status());
413 }
414
415 BlobStoreBuffer<kBufferSize> blob(
416 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
417 EXPECT_EQ(OkStatus(), blob.Init());
418
419 // Ensure the file name can be read from a reader.
420 BlobStore::BlobReader reader(blob);
421 ASSERT_EQ(OkStatus(), reader.Open());
422
423 memset(tmp_buffer.data(), 0, tmp_buffer.size());
424 StatusWithSize sws = reader.GetFileName(
425 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
426
427 EXPECT_EQ(OkStatus(), sws.status());
428 ASSERT_EQ(kFileName.size(), sws.size());
429 EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), kFileName.size()), 0);
430 }
431
TEST_F(BlobStoreTest,FileNameUndersizedRead)432 TEST_F(BlobStoreTest, FileNameUndersizedRead) {
433 InitSourceBufferToRandom(0x8675309);
434 WriteTestBlock();
435 constexpr std::string_view kFileName("my_file_1.bin");
436 std::array<std::byte, 4> tmp_buffer = {};
437 static_assert(kFileName.size() > tmp_buffer.size());
438
439 kvs::ChecksumCrc16 checksum;
440 constexpr size_t kBufferSize = 256;
441 BlobStoreBuffer<kBufferSize> blob(
442 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
443 EXPECT_EQ(OkStatus(), blob.Init());
444
445 BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
446
447 EXPECT_EQ(OkStatus(), writer.Open());
448 EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
449 EXPECT_EQ(OkStatus(), writer.Write(as_bytes(span("some interesting data"))));
450 EXPECT_EQ(OkStatus(), writer.Close());
451
452 // Ensure the file name can be read from a reader.
453 BlobStore::BlobReader reader(blob);
454 ASSERT_EQ(OkStatus(), reader.Open());
455
456 StatusWithSize sws = reader.GetFileName(
457 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
458 EXPECT_EQ(Status::ResourceExhausted(), sws.status());
459 ASSERT_EQ(tmp_buffer.size(), sws.size());
460 EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), sws.size()), 0);
461 }
462
TEST_F(BlobStoreTest,FileNameUndersizedSet)463 TEST_F(BlobStoreTest, FileNameUndersizedSet) {
464 InitSourceBufferToRandom(0x8675309);
465 WriteTestBlock();
466 constexpr std::string_view kFileName("my_file_1.bin");
467
468 kvs::ChecksumCrc16 checksum;
469 constexpr size_t kBufferSize = 256;
470 BlobStoreBuffer<kBufferSize> blob(
471 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
472 EXPECT_EQ(OkStatus(), blob.Init());
473
474 BlobStore::BlobWriterWithBuffer<2> writer(blob);
475
476 EXPECT_EQ(OkStatus(), writer.Open());
477 EXPECT_EQ(Status::ResourceExhausted(), writer.SetFileName(kFileName));
478 EXPECT_EQ(OkStatus(), writer.Close());
479 }
480
TEST_F(BlobStoreTest,FileNameInvalidation)481 TEST_F(BlobStoreTest, FileNameInvalidation) {
482 InitSourceBufferToRandom(0x8675309);
483 WriteTestBlock();
484
485 constexpr std::string_view kFileName("sliced_cheese.png");
486 std::array<std::byte, 64> tmp_buffer = {};
487 static_assert(kFileName.size() <= tmp_buffer.size());
488
489 kvs::ChecksumCrc16 checksum;
490 constexpr size_t kBufferSize = 256;
491 BlobStoreBuffer<kBufferSize> blob(
492 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
493 EXPECT_EQ(OkStatus(), blob.Init());
494
495 BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
496
497 EXPECT_EQ(OkStatus(), writer.Open());
498 EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
499 EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
500 EXPECT_EQ(OkStatus(), writer.Discard());
501 EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
502 EXPECT_EQ(OkStatus(), writer.Close());
503
504 // Check that the file name was discarded by Discard().
505 memset(tmp_buffer.data(), 0, tmp_buffer.size());
506 BlobStore::BlobReader reader(blob);
507 ASSERT_EQ(OkStatus(), reader.Open());
508 StatusWithSize sws = reader.GetFileName(
509 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
510 EXPECT_EQ(Status::NotFound(), sws.status());
511 ASSERT_EQ(0u, sws.size());
512 }
513
TEST_F(BlobStoreTest,NoFileName)514 TEST_F(BlobStoreTest, NoFileName) {
515 InitSourceBufferToRandom(0x8675309);
516 WriteTestBlock();
517
518 std::array<std::byte, 64> tmp_buffer = {};
519 kvs::ChecksumCrc16 checksum;
520 constexpr size_t kBufferSize = 256;
521 BlobStoreBuffer<kBufferSize> blob(
522 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
523 EXPECT_EQ(OkStatus(), blob.Init());
524
525 // Ensure blobs with no file names work as expected.
526 BlobStore::BlobReader reader(blob);
527 ASSERT_EQ(OkStatus(), reader.Open());
528
529 StatusWithSize sws = reader.GetFileName(
530 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
531 EXPECT_EQ(Status::NotFound(), sws.status());
532 ASSERT_EQ(0u, sws.size());
533 }
534
TEST_F(BlobStoreTest,V1MetadataBackwardsCompatible)535 TEST_F(BlobStoreTest, V1MetadataBackwardsCompatible) {
536 constexpr size_t kWriteSize = 25;
537 WriteTestBlock(kWriteSize);
538
539 kvs::ChecksumCrc16 checksum;
540 constexpr size_t kBufferSize = 16;
541 BlobStoreBuffer<kBufferSize> blob(
542 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
543 EXPECT_EQ(OkStatus(), blob.Init());
544
545 // Read the written data in the current format.
546 internal::BlobMetadataHeader current_metadata;
547 ASSERT_EQ(OkStatus(),
548 kvs::TestKvs().acquire()->Get(kBlobTitle, ¤t_metadata));
549
550 // Re-save only the V1 metadata contents.
551 ASSERT_EQ(
552 OkStatus(),
553 kvs::TestKvs().acquire()->Put(kBlobTitle, current_metadata.v1_metadata));
554
555 // Ensure the BlobStore's contents aren't invalid.
556 BlobStore::BlobReader reader(blob);
557 ASSERT_EQ(OkStatus(), reader.Open());
558 ASSERT_EQ(kWriteSize, reader.ConservativeReadLimit());
559 ASSERT_EQ(current_metadata.v1_metadata.data_size_bytes,
560 reader.ConservativeReadLimit());
561 }
562
TEST_F(BlobStoreTest,Discard)563 TEST_F(BlobStoreTest, Discard) {
564 InitSourceBufferToRandom(0x8675309);
565 WriteTestBlock();
566 constexpr char blob_title[] = "TestBlobBlock";
567 std::array<std::byte, 64> tmp_buffer = {};
568
569 kvs::ChecksumCrc16 checksum;
570
571 // TODO(davidrogers): Do this test with flash/kvs in the different entry state
572 // combinations.
573
574 constexpr size_t kBufferSize = 256;
575 BlobStoreBuffer<kBufferSize> blob(
576 blob_title, partition_, &checksum, kvs::TestKvs(), kBufferSize);
577 EXPECT_EQ(OkStatus(), blob.Init());
578
579 EXPECT_TRUE(blob.HasData());
580
581 BlobStore::BlobWriterWithBuffer writer(blob);
582
583 EXPECT_EQ(OkStatus(), writer.Open());
584 EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
585
586 // Blob should NOT be valid to read, because the write data was only buffered,
587 // and has not been written to flash yet.
588 EXPECT_FALSE(blob.HasData());
589
590 // The write does an implicit erase so there should be no key for this blob.
591 EXPECT_EQ(Status::NotFound(),
592 kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
593 EXPECT_EQ(OkStatus(), writer.Close());
594
595 EXPECT_TRUE(blob.HasData());
596
597 EXPECT_EQ(OkStatus(),
598 kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
599
600 EXPECT_EQ(OkStatus(), writer.Open());
601 EXPECT_EQ(OkStatus(), writer.Discard());
602 EXPECT_EQ(OkStatus(), writer.Close());
603
604 EXPECT_FALSE(blob.HasData());
605
606 EXPECT_EQ(Status::NotFound(),
607 kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
608 }
609
TEST_F(BlobStoreTest,MultipleErase)610 TEST_F(BlobStoreTest, MultipleErase) {
611 constexpr size_t kBufferSize = 256;
612 BlobStoreBuffer<kBufferSize> blob(
613 "Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
614 EXPECT_EQ(OkStatus(), blob.Init());
615
616 BlobStore::BlobWriterWithBuffer writer(blob);
617 EXPECT_EQ(OkStatus(), writer.Open());
618
619 EXPECT_EQ(OkStatus(), writer.Erase());
620 EXPECT_EQ(OkStatus(), writer.Erase());
621 EXPECT_EQ(OkStatus(), writer.Erase());
622 }
623
TEST_F(BlobStoreTest,OffsetRead)624 TEST_F(BlobStoreTest, OffsetRead) {
625 InitSourceBufferToRandom(0x11309);
626 WriteTestBlock();
627
628 constexpr size_t kOffset = 10;
629 ASSERT_LT(kOffset, kBlobDataSize);
630
631 kvs::ChecksumCrc16 checksum;
632
633 char name[16] = "TestBlobBlock";
634 constexpr size_t kBufferSize = 16;
635 BlobStoreBuffer<kBufferSize> blob(
636 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
637 EXPECT_EQ(OkStatus(), blob.Init());
638 BlobStore::BlobReader reader(blob);
639 ASSERT_EQ(OkStatus(), reader.Open(kOffset));
640
641 std::array<std::byte, kBlobDataSize - kOffset> read_buffer;
642 ByteSpan read_span = read_buffer;
643 ASSERT_EQ(read_span.size_bytes(), reader.ConservativeReadLimit());
644
645 auto result = reader.Read(read_span);
646 ASSERT_EQ(result.status(), OkStatus());
647 EXPECT_EQ(OkStatus(), reader.Close());
648 VerifyFlash(read_buffer, kOffset);
649 }
650
TEST_F(BlobStoreTest,SeekOffsetRead)651 TEST_F(BlobStoreTest, SeekOffsetRead) {
652 InitSourceBufferToRandom(0x11309);
653 WriteTestBlock();
654
655 constexpr size_t kOffset = 10;
656 ASSERT_LT(kOffset, kBlobDataSize);
657
658 kvs::ChecksumCrc16 checksum;
659
660 char name[16] = "TestBlobBlock";
661 constexpr size_t kBufferSize = 16;
662 BlobStoreBuffer<kBufferSize> blob(
663 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
664 EXPECT_EQ(OkStatus(), blob.Init());
665 BlobStore::BlobReader reader(blob);
666 ASSERT_EQ(OkStatus(), reader.Open());
667 ASSERT_EQ(OkStatus(), reader.Seek(kOffset));
668
669 std::array<std::byte, kBlobDataSize - kOffset> read_buffer;
670 ByteSpan read_span = read_buffer;
671 ASSERT_EQ(read_span.size_bytes(), reader.ConservativeReadLimit());
672
673 auto result = reader.Read(read_span);
674 ASSERT_EQ(result.status(), OkStatus());
675 EXPECT_EQ(OkStatus(), reader.Close());
676 VerifyFlash(read_buffer, kOffset);
677 }
678
TEST_F(BlobStoreTest,InvalidReadOffset)679 TEST_F(BlobStoreTest, InvalidReadOffset) {
680 InitSourceBufferToRandom(0x11309);
681 WriteTestBlock();
682
683 constexpr size_t kOffset = kBlobDataSize;
684
685 kvs::ChecksumCrc16 checksum;
686
687 char name[16] = "TestBlobBlock";
688 constexpr size_t kBufferSize = 16;
689 BlobStoreBuffer<kBufferSize> blob(
690 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
691 EXPECT_EQ(OkStatus(), blob.Init());
692 BlobStore::BlobReader reader(blob);
693 ASSERT_EQ(Status::InvalidArgument(), reader.Open(kOffset));
694 }
695
TEST_F(BlobStoreTest,ReadSeekClosedReader)696 TEST_F(BlobStoreTest, ReadSeekClosedReader) {
697 InitSourceBufferToRandom(0x11309);
698 WriteTestBlock();
699
700 kvs::ChecksumCrc16 checksum;
701
702 char name[16] = "TestBlobBlock";
703 constexpr size_t kBufferSize = 16;
704 BlobStoreBuffer<kBufferSize> blob(
705 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
706 EXPECT_EQ(OkStatus(), blob.Init());
707 BlobStore::BlobReader reader(blob);
708 ASSERT_EQ(OkStatus(), reader.Open());
709 ASSERT_EQ(OkStatus(), reader.Close());
710
711 EXPECT_EQ(Status::FailedPrecondition(), reader.Seek(0));
712
713 std::byte read_buffer[32];
714 EXPECT_EQ(Status::FailedPrecondition(), reader.Read(read_buffer).status());
715 }
716
TEST_F(BlobStoreTest,InvalidSeekOffset)717 TEST_F(BlobStoreTest, InvalidSeekOffset) {
718 InitSourceBufferToRandom(0x11309);
719 WriteTestBlock();
720
721 constexpr size_t kOffset = kBlobDataSize;
722
723 kvs::ChecksumCrc16 checksum;
724
725 char name[16] = "TestBlobBlock";
726 constexpr size_t kBufferSize = 16;
727 BlobStoreBuffer<kBufferSize> blob(
728 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
729 EXPECT_EQ(OkStatus(), blob.Init());
730 BlobStore::BlobReader reader(blob);
731 ASSERT_EQ(OkStatus(), reader.Open());
732 ASSERT_EQ(Status::OutOfRange(), reader.Seek(kOffset));
733 }
734
735 // Write a block to blob and close with part of a write buffer with unflushed
736 // data.
TEST_F(BlobStoreTest,WriteBufferWithRemainderInBuffer)737 TEST_F(BlobStoreTest, WriteBufferWithRemainderInBuffer) {
738 InitSourceBufferToRandom(0x11309);
739
740 kvs::ChecksumCrc16 checksum;
741 constexpr size_t kBufferSize = 256;
742 BlobStoreBuffer<kBufferSize> blob(
743 "TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
744 EXPECT_EQ(OkStatus(), blob.Init());
745
746 const size_t write_size_bytes = kBlobDataSize - 10;
747 ConstByteSpan write_data = span(source_buffer_).first(write_size_bytes);
748
749 BlobStore::BlobWriterWithBuffer writer(blob);
750 EXPECT_EQ(OkStatus(), writer.Open());
751 ASSERT_EQ(OkStatus(), writer.Write(write_data));
752 EXPECT_EQ(OkStatus(), writer.Close());
753
754 BlobStore::BlobReader reader(blob);
755 ASSERT_EQ(OkStatus(), reader.Open());
756 EXPECT_EQ(write_size_bytes, reader.ConservativeReadLimit());
757 }
758
759 // Test reading with a read buffer larger than the available data in the blob.
TEST_F(BlobStoreTest,ReadBufferIsLargerThanData)760 TEST_F(BlobStoreTest, ReadBufferIsLargerThanData) {
761 InitSourceBufferToRandom(0x57326);
762
763 constexpr size_t kWriteBytes = 64;
764 WriteTestBlock(kWriteBytes);
765
766 kvs::ChecksumCrc16 checksum;
767
768 char name[16] = "TestBlobBlock";
769 constexpr size_t kBufferSize = 16;
770 BlobStoreBuffer<kBufferSize> blob(
771 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
772 EXPECT_EQ(OkStatus(), blob.Init());
773 BlobStore::BlobReader reader(blob);
774 ASSERT_EQ(OkStatus(), reader.Open());
775 EXPECT_EQ(kWriteBytes, reader.ConservativeReadLimit());
776
777 std::array<std::byte, kWriteBytes + 10> read_buffer;
778 ByteSpan read_span = read_buffer;
779
780 auto result = reader.Read(read_span);
781 ASSERT_EQ(result.status(), OkStatus());
782 EXPECT_EQ(OkStatus(), reader.Close());
783 }
784
TEST_F(BlobStoreTest,ChunkRead1)785 TEST_F(BlobStoreTest, ChunkRead1) {
786 InitSourceBufferToRandom(0x8675309);
787 WriteTestBlock();
788 ChunkReadTest(1);
789 }
790
TEST_F(BlobStoreTest,ChunkRead3)791 TEST_F(BlobStoreTest, ChunkRead3) {
792 InitSourceBufferToFill(0);
793 WriteTestBlock();
794 ChunkReadTest(3);
795 }
796
TEST_F(BlobStoreTest,ChunkRead4)797 TEST_F(BlobStoreTest, ChunkRead4) {
798 InitSourceBufferToFill(1);
799 WriteTestBlock();
800 ChunkReadTest(4);
801 }
802
TEST_F(BlobStoreTest,ChunkRead5)803 TEST_F(BlobStoreTest, ChunkRead5) {
804 InitSourceBufferToFill(0xff);
805 WriteTestBlock();
806 ChunkReadTest(5);
807 }
808
TEST_F(BlobStoreTest,ChunkRead16)809 TEST_F(BlobStoreTest, ChunkRead16) {
810 InitSourceBufferToRandom(0x86);
811 WriteTestBlock();
812 ChunkReadTest(16);
813 }
814
TEST_F(BlobStoreTest,ChunkRead64)815 TEST_F(BlobStoreTest, ChunkRead64) {
816 InitSourceBufferToRandom(0x9);
817 WriteTestBlock();
818 ChunkReadTest(64);
819 }
820
TEST_F(BlobStoreTest,ChunkReadFull)821 TEST_F(BlobStoreTest, ChunkReadFull) {
822 InitSourceBufferToRandom(0x9);
823 WriteTestBlock();
824 ChunkReadTest(kBlobDataSize);
825 }
826
TEST_F(BlobStoreTest,PartialBufferThenClose)827 TEST_F(BlobStoreTest, PartialBufferThenClose) {
828 // Do write of only a partial chunk, which will only have bytes in buffer
829 // (none written to flash) at close.
830 size_t data_bytes = 12;
831 InitSourceBufferToRandom(0x111, data_bytes);
832 WriteTestBlock(data_bytes);
833
834 // Do write with several full chunks and then some partial.
835 data_bytes = 158;
836 InitSourceBufferToRandom(0x3222, data_bytes);
837 }
838
839 // Test to do write/close, write/close multiple times.
TEST_F(BlobStoreTest,MultipleWrites)840 TEST_F(BlobStoreTest, MultipleWrites) {
841 InitSourceBufferToRandom(0x1121);
842 WriteTestBlock();
843 InitSourceBufferToRandom(0x515);
844 WriteTestBlock();
845 InitSourceBufferToRandom(0x4321);
846 WriteTestBlock();
847 }
848
849 } // namespace
850 } // namespace pw::blob_store
851