Skip to content

Commit b61ada8

Browse files
author
Seppo Takalo
committed
TDBStore: Keep copy of reserved data on both areas.
Change the "reserved data" logic so that every time we erase and area, the content of reserved data is then immediately copied to newly erased area. This keeps two copies of the data. When data is requested, return only if checksum is matching. When data is written, only allow if BOTH checksums are incorrect, meaning that areas are either corrupted or erased. Only exception is TDBStore::reset() which erases all keys and reserved data. Removed all logic that tried to detect, if reserved are was erased or corrupted. Rely entirely on checksum. Add moduletest for reserved data.
1 parent f0c1ff5 commit b61ada8

File tree

3 files changed

+103
-108
lines changed

3 files changed

+103
-108
lines changed

UNITTESTS/moduletests/storage/kvstore/TDBStore/moduletest.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,21 @@ TEST_F(TDBStoreModuleTest, set_deinit_init_get)
9696
EXPECT_EQ(tdb.remove("key"), MBED_SUCCESS);
9797
}
9898
}
99+
100+
TEST_F(TDBStoreModuleTest, reserved_data)
101+
{
102+
char data[20];
103+
char buf[20];
104+
for (int i = 0; i < sizeof(buf); ++i) {
105+
data[i] = rand();
106+
}
107+
EXPECT_EQ(tdb.reserved_data_set(data, sizeof(data)), MBED_SUCCESS);
108+
EXPECT_EQ(tdb.reserved_data_get(buf, sizeof(buf)), MBED_SUCCESS);
109+
EXPECT_EQ(0, memcmp(data, buf, sizeof(buf)));
110+
EXPECT_EQ(tdb.reserved_data_set(data, 1024), MBED_ERROR_INVALID_SIZE);
111+
EXPECT_EQ(tdb.reserved_data_set(data, sizeof(data)), MBED_ERROR_WRITE_FAILED);
112+
EXPECT_EQ(tdb.deinit(), MBED_SUCCESS);
113+
EXPECT_EQ(tdb.init(), MBED_SUCCESS);
114+
EXPECT_EQ(tdb.reserved_data_get(buf, sizeof(buf)), MBED_SUCCESS);
115+
EXPECT_EQ(0, memcmp(data, buf, sizeof(buf)));
116+
}

features/storage/kvstore/tdbstore/TDBStore.cpp

Lines changed: 72 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -836,33 +836,11 @@ int TDBStore::garbage_collection()
836836
int ret;
837837
size_t ind;
838838

839-
ret = check_erase_before_write(1 - _active_area, 0, _master_record_offset + _master_record_size);
839+
ret = reset_area(1 - _active_area);
840840
if (ret) {
841841
return ret;
842842
}
843843

844-
ret = do_reserved_data_get(0, RESERVED_AREA_SIZE);
845-
846-
if (!ret) {
847-
// Copy reserved data
848-
to_offset = 0;
849-
reserved_size = _master_record_offset;
850-
851-
while (reserved_size) {
852-
chunk_size = std::min(work_buf_size, reserved_size);
853-
ret = read_area(_active_area, to_offset, chunk_size, _work_buf);
854-
if (ret) {
855-
return ret;
856-
}
857-
ret = write_area(1 - _active_area, to_offset, chunk_size, _work_buf);
858-
if (ret) {
859-
return ret;
860-
}
861-
to_offset += chunk_size;
862-
reserved_size -= chunk_size;
863-
}
864-
}
865-
866844
to_offset = _master_record_offset + _master_record_size;
867845

868846
// Initialize in case table is empty
@@ -1064,7 +1042,7 @@ int TDBStore::init()
10641042
// Master record may be either corrupt or erased - either way erase it
10651043
// (this will do nothing if already erased)
10661044
if (ret == MBED_ERROR_INVALID_DATA_DETECTED) {
1067-
if (check_erase_before_write(area, _master_record_offset, _master_record_size, true)) {
1045+
if (reset_area(area)) {
10681046
MBED_ERROR(MBED_ERROR_READ_FAILED, "TDBSTORE: Unable to reset area at init");
10691047
}
10701048
area_state[area] = TDBSTORE_AREA_STATE_EMPTY;
@@ -1129,11 +1107,9 @@ int TDBStore::init()
11291107
}
11301108
}
11311109

1132-
reserved_ret = do_reserved_data_get(0, RESERVED_AREA_SIZE);
1133-
1134-
// If we either have a corrupt record somewhere, or the reserved area is corrupt,
1135-
// perform garbage collection to salvage all preceding records and/or clean reserved area.
1136-
if ((ret == MBED_ERROR_INVALID_DATA_DETECTED) || (reserved_ret == MBED_ERROR_INVALID_DATA_DETECTED)) {
1110+
// If we either have a corrupt record somewhere
1111+
// perform garbage collection to salvage all preceding records.
1112+
if ((ret == MBED_ERROR_INVALID_DATA_DETECTED)) {
11371113
ret = garbage_collection();
11381114
if (ret) {
11391115
MBED_ERROR(ret, "TDBSTORE: Unable to perform GC at init");
@@ -1184,8 +1160,19 @@ int TDBStore::deinit()
11841160

11851161
int TDBStore::reset_area(uint8_t area)
11861162
{
1163+
uint8_t buf[RESERVED_AREA_SIZE + sizeof(reserved_trailer_t)];
1164+
int ret;
1165+
bool copy_reserved_data = do_reserved_data_get(buf, sizeof(buf), 0, buf + RESERVED_AREA_SIZE) == MBED_SUCCESS;
1166+
11871167
// Erase reserved area and master record
1188-
return check_erase_before_write(area, 0, _master_record_offset + _master_record_size, true);
1168+
ret = check_erase_before_write(area, 0, _master_record_offset + _master_record_size, true);
1169+
if (ret) {
1170+
return ret;
1171+
}
1172+
if (copy_reserved_data) {
1173+
ret = write_area(area, 0, sizeof(buf), buf);
1174+
}
1175+
return ret;
11891176
}
11901177

11911178
int TDBStore::reset()
@@ -1201,7 +1188,7 @@ int TDBStore::reset()
12011188

12021189
// Reset both areas
12031190
for (area = 0; area < _num_areas; area++) {
1204-
ret = reset_area(area);
1191+
ret = check_erase_before_write(area, 0, _master_record_offset + _master_record_size, true);
12051192
if (ret) {
12061193
goto end;
12071194
}
@@ -1348,116 +1335,97 @@ void TDBStore::update_all_iterators(bool added, uint32_t ram_table_ind)
13481335
int TDBStore::reserved_data_set(const void *reserved_data, size_t reserved_data_buf_size)
13491336
{
13501337
reserved_trailer_t trailer;
1351-
int os_ret, ret = MBED_SUCCESS;
1338+
int ret;
13521339

13531340
if (reserved_data_buf_size > RESERVED_AREA_SIZE) {
13541341
return MBED_ERROR_INVALID_SIZE;
13551342
}
13561343

13571344
_mutex.lock();
13581345

1359-
ret = do_reserved_data_get(0, RESERVED_AREA_SIZE);
1360-
if ((ret == MBED_SUCCESS) || (ret == MBED_ERROR_INVALID_DATA_DETECTED)) {
1346+
ret = do_reserved_data_get(0, 0);
1347+
if (ret == MBED_SUCCESS) {
13611348
ret = MBED_ERROR_WRITE_FAILED;
13621349
goto end;
1363-
} else if (ret != MBED_ERROR_ITEM_NOT_FOUND) {
1364-
goto end;
1365-
}
1366-
1367-
ret = write_area(_active_area, 0, reserved_data_buf_size, reserved_data);
1368-
if (ret) {
1369-
goto end;
13701350
}
13711351

13721352
trailer.trailer_size = sizeof(trailer);
13731353
trailer.data_size = reserved_data_buf_size;
13741354
trailer.crc = calc_crc(initial_crc, reserved_data_buf_size, reserved_data);
13751355

1376-
ret = write_area(_active_area, RESERVED_AREA_SIZE, sizeof(trailer), &trailer);
1377-
if (ret) {
1378-
goto end;
1379-
}
1380-
1381-
os_ret = _buff_bd->sync();
1382-
if (os_ret) {
1383-
ret = MBED_ERROR_WRITE_FAILED;
1384-
goto end;
1356+
/*
1357+
* Write to both areas
1358+
* Both must success, as they are required to be erased when TDBStore initializes
1359+
* its area
1360+
*/
1361+
for (int i = 0; i < _num_areas; ++i) {
1362+
ret = write_area(i, 0, reserved_data_buf_size, reserved_data);
1363+
if (ret) {
1364+
goto end;
1365+
}
1366+
ret = write_area(i, RESERVED_AREA_SIZE, sizeof(trailer), &trailer);
1367+
if (ret) {
1368+
goto end;
1369+
}
1370+
ret = _buff_bd->sync();
1371+
if (ret) {
1372+
goto end;
1373+
}
13851374
}
1386-
1375+
ret = MBED_SUCCESS;
13871376
end:
13881377
_mutex.unlock();
13891378
return ret;
13901379
}
13911380

1392-
int TDBStore::do_reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, size_t *actual_data_size)
1381+
int TDBStore::do_reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, size_t *actual_data_size, void *copy_trailer)
13931382
{
13941383
reserved_trailer_t trailer;
1395-
uint8_t *buf;
1384+
uint8_t buf[RESERVED_AREA_SIZE];
13961385
int ret;
1397-
bool erased = true;
1398-
size_t actual_size;
1399-
uint32_t crc = initial_crc;
1400-
uint32_t offset;
1401-
uint8_t blank = _buff_bd->get_erase_value();
1402-
1403-
ret = read_area(_active_area, RESERVED_AREA_SIZE, sizeof(trailer), &trailer);
1404-
if (ret) {
1405-
return ret;
1406-
}
1386+
uint32_t crc;
14071387

1408-
buf = reinterpret_cast <uint8_t *>(&trailer);
1409-
for (uint32_t i = 0; i < sizeof(trailer); i++) {
1410-
if (buf[i] != blank) {
1411-
erased = false;
1412-
break;
1388+
/*
1389+
* Try to keep reserved data identical on both areas, therefore
1390+
* we can return any of these data, if the checmsum is correct.
1391+
*/
1392+
for (int i = 0; i < _num_areas; ++i) {
1393+
ret = read_area(i, RESERVED_AREA_SIZE, sizeof(trailer), &trailer);
1394+
if (ret) {
1395+
return ret;
14131396
}
1414-
}
14151397

1416-
if (!erased) {
1417-
actual_size = trailer.data_size;
1418-
if (actual_data_size) {
1419-
*actual_data_size = actual_size;
1398+
// First validy check: is the trailer header size correct
1399+
if (trailer.trailer_size != sizeof(trailer)) {
1400+
continue;
14201401
}
1421-
if (reserved_data_buf_size < actual_size) {
1422-
return MBED_ERROR_INVALID_SIZE;
1402+
// Second validy check: Is the data too big (corrupt header)
1403+
if (trailer.data_size > RESERVED_AREA_SIZE) {
1404+
continue;
14231405
}
1424-
} else {
1425-
actual_size = std::min((size_t) RESERVED_AREA_SIZE, reserved_data_buf_size);
1426-
}
1427-
1428-
if (reserved_data) {
1429-
buf = reinterpret_cast <uint8_t *>(reserved_data);
1430-
} else {
1431-
buf = _work_buf;
1432-
}
1433-
1434-
offset = 0;
14351406

1436-
while (actual_size) {
1437-
uint32_t chunk = std::min(work_buf_size, (uint32_t) actual_size);
1438-
ret = read_area(_active_area, offset, chunk, buf + offset);
1407+
// Next, verify the checksum
1408+
ret = read_area(i, 0, trailer.data_size, buf);
14391409
if (ret) {
14401410
return ret;
14411411
}
1442-
for (uint32_t i = 0; i < chunk; i++) {
1443-
if (buf[i] != blank) {
1444-
erased = false;
1445-
break;
1412+
crc = calc_crc(initial_crc, trailer.data_size, buf);
1413+
if (crc == trailer.crc) {
1414+
// Correct data, copy it and return to caller
1415+
if (reserved_data) {
1416+
memcpy(reserved_data, buf, trailer.data_size);
14461417
}
1418+
if (actual_data_size) {
1419+
*actual_data_size = trailer.data_size;
1420+
}
1421+
if (copy_trailer) {
1422+
memcpy(copy_trailer, &trailer, sizeof(trailer));
1423+
}
1424+
return MBED_SUCCESS;
14471425
}
1448-
1449-
crc = calc_crc(crc, chunk, buf + offset);
1450-
offset += chunk;
1451-
actual_size -= chunk;
14521426
}
14531427

1454-
if (erased) {
1455-
return MBED_ERROR_ITEM_NOT_FOUND;
1456-
} else if (crc != trailer.crc) {
1457-
return MBED_ERROR_INVALID_DATA_DETECTED;
1458-
}
1459-
1460-
return MBED_SUCCESS;
1428+
return MBED_ERROR_ITEM_NOT_FOUND;
14611429
}
14621430

14631431
int TDBStore::reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, size_t *actual_data_size)

features/storage/kvstore/tdbstore/TDBStore.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "features/storage/blockdevice/BlockDevice.h"
2424
#include "features/storage/blockdevice/BufferedBlockDevice.h"
2525
#include "PlatformMutex.h"
26+
#include "mbed_error.h"
2627

2728
namespace mbed {
2829

@@ -74,7 +75,7 @@ class TDBStore : public KVStore {
7475

7576

7677
/**
77-
* @brief Reset TDBStore contents (clear all keys)
78+
* @brief Reset TDBStore contents (clear all keys) and reserved data
7879
*
7980
* @returns MBED_SUCCESS Success.
8081
* MBED_ERROR_NOT_READY Not initialized.
@@ -338,6 +339,8 @@ class TDBStore : public KVStore {
338339

339340
/**
340341
* @brief Reset an area (erase its start).
342+
* This erases master record, but preserves the
343+
* reserved area data.
341344
*
342345
* @param[in] area Area.
343346
*
@@ -524,16 +527,22 @@ class TDBStore : public KVStore {
524527

525528
/**
526529
* @brief Get data from reserved area - worker function.
530+
* This verifies that reserved data on both areas have
531+
* correct checksums. If given pointer is not NULL, also
532+
* write the reserved data to buffer. If checksums are not
533+
* valid, return error code, and don't write anything to any
534+
* pointers.
527535
*
528-
* @param[in] reserved_data Reserved data buffer (0 to return nothing).
536+
* @param[out] reserved_data Reserved data buffer (NULL to return nothing).
529537
* @param[in] reserved_data_buf_size
530538
* Reserved data buffer size.
531-
* @param[in] actual_data_size Return data size.
539+
* @param[out] actual_data_size If not NULL, return actual data size.
540+
* @param[out] copy_trailer If not NULL, copy the trailer content to given buffer.
532541
*
533542
* @returns 0 on success or a negative error code on failure
534543
*/
535544
int do_reserved_data_get(void *reserved_data, size_t reserved_data_buf_size,
536-
size_t *actual_data_size = 0);
545+
size_t *actual_data_size = 0, void *copy_trailer = 0);
537546

538547
/**
539548
* @brief Update all iterators after adding or deleting of keys.

0 commit comments

Comments
 (0)