#include #include #include #include #include #include "gps/gps_flash_journal.h" #define FAKE_FLASH_SIZE 8192u #define REGION_SIZE 2048u #define REGION0 0u #define REGION1 4096u typedef struct { unsigned char bytes[FAKE_FLASH_SIZE]; unsigned int program_calls; unsigned int erase_calls; unsigned int fail_program_call; unsigned int fail_program_after_bytes; bool fail_erase; } fake_flash_t; static void fake_flash_reset(fake_flash_t* flash) { memset(flash, 0, sizeof(*flash)); memset(flash->bytes, 0xff, sizeof(flash->bytes)); } static bool fake_read( void* context, uint32_t address, void* destination, uint32_t length) { fake_flash_t* flash = context; if((address > FAKE_FLASH_SIZE) || (length > (FAKE_FLASH_SIZE - address))) { return false; } memcpy(destination, &flash->bytes[address], length); return true; } static bool fake_program( void* context, uint32_t address, const void* source, uint32_t length) { fake_flash_t* flash = context; flash->program_calls++; if((length == 0u) || (length > GPS_FLASH_JOURNAL_PAGE_SIZE) || ((address / GPS_FLASH_JOURNAL_PAGE_SIZE) != ((address + length - 1u) / GPS_FLASH_JOURNAL_PAGE_SIZE)) || (address > FAKE_FLASH_SIZE) || (length > (FAKE_FLASH_SIZE - address))) { return false; } const unsigned char* data = source; uint32_t programmed = length; bool fail = flash->fail_program_call != 0u && flash->program_calls == flash->fail_program_call; if(fail && (flash->fail_program_after_bytes < programmed)) { programmed = flash->fail_program_after_bytes; } for(uint32_t i = 0u ; i < programmed ; i++) { /* NOR page programming can only clear bits. */ flash->bytes[address + i] &= data[i]; } return !fail; } static bool fake_erase(void* context, uint32_t region_address) { fake_flash_t* flash = context; flash->erase_calls++; if(flash->fail_erase || ((region_address != REGION0) && (region_address != REGION1))) { return false; } memset(&flash->bytes[region_address], 0xff, REGION_SIZE); return true; } static gps_flash_journal_io_t fake_io(fake_flash_t* flash) { const gps_flash_journal_io_t io = { .context = flash, .read = fake_read, .program = fake_program, .erase_region = fake_erase }; return io; } static void init_journal( gps_flash_journal_t* journal, fake_flash_t* flash, uint32_t maximum_payload) { gps_flash_journal_io_t io = fake_io(flash); assert(gps_flash_journal_init( journal, &io, REGION0, REGION1, REGION_SIZE, maximum_payload)); } static void test_crc_vector(void) { static const char vector[] = "123456789"; assert(gps_flash_journal_crc32(vector, 9u) == UINT32_C(0xcbf43926)); } static void test_empty_write_load_and_deduplicate(void) { fake_flash_t flash; fake_flash_reset(&flash); gps_flash_journal_t journal; init_journal(&journal, &flash, 800u); unsigned char output[800]; assert(!gps_flash_journal_load_latest( &journal, output, sizeof(output), NULL, NULL)); unsigned char payload[700]; for(unsigned int i = 0u ; i < sizeof(payload) ; i++) { payload[i] = (unsigned char)(i * 17u); } assert(gps_flash_journal_write( &journal, payload, sizeof(payload)) == GPS_FLASH_JOURNAL_WRITE_COMMITTED); unsigned int programs_after_commit = flash.program_calls; assert(gps_flash_journal_write( &journal, payload, sizeof(payload)) == GPS_FLASH_JOURNAL_WRITE_UNCHANGED); assert(flash.program_calls == programs_after_commit); uint32_t size = 0u; uint64_t sequence = 0u; memset(output, 0, sizeof(output)); assert(gps_flash_journal_load_latest( &journal, output, sizeof(output), &size, &sequence)); assert(size == sizeof(payload)); assert(sequence == 1u); assert(memcmp(output, payload, sizeof(payload)) == 0); /* A CPU reset reconstructs journal state only from flash. */ gps_flash_journal_t rebooted; init_journal(&rebooted, &flash, 800u); memset(output, 0, sizeof(output)); assert(gps_flash_journal_load_latest( &rebooted, output, sizeof(output), &size, &sequence)); assert(sequence == 1u); assert(memcmp(output, payload, sizeof(payload)) == 0); } static void test_rotation_keeps_latest_and_old_bank_until_commit(void) { fake_flash_t flash; fake_flash_reset(&flash); gps_flash_journal_t journal; init_journal(&journal, &flash, 700u); unsigned char payload[700]; for(unsigned int version = 1u ; version <= 4u ; version++) { memset(payload, (int)version, sizeof(payload)); assert(gps_flash_journal_write( &journal, payload, sizeof(payload)) == GPS_FLASH_JOURNAL_WRITE_COMMITTED); } assert(journal.latest_sequence == 4u); assert(flash.erase_calls >= 2u); gps_flash_journal_t rebooted; init_journal(&rebooted, &flash, 700u); unsigned char output[700]; uint64_t sequence = 0u; assert(gps_flash_journal_load_latest( &rebooted, output, sizeof(output), NULL, &sequence)); assert(sequence == 4u); assert(output[0] == 4u && output[699] == 4u); } static void test_every_torn_program_keeps_previous_record(void) { /* Header, three payload pages, and final commit are separate calls. */ for(unsigned int failed_call_in_write = 1u ; failed_call_in_write <= 5u ; failed_call_in_write++) { fake_flash_t flash; fake_flash_reset(&flash); gps_flash_journal_t journal; init_journal(&journal, &flash, 700u); unsigned char old_payload[700]; unsigned char new_payload[700]; memset(old_payload, 0x31, sizeof(old_payload)); memset(new_payload, 0x72, sizeof(new_payload)); assert(gps_flash_journal_write( &journal, old_payload, sizeof(old_payload)) == GPS_FLASH_JOURNAL_WRITE_COMMITTED); flash.fail_program_call = flash.program_calls + failed_call_in_write; flash.fail_program_after_bytes = 1u; assert(gps_flash_journal_write( &journal, new_payload, sizeof(new_payload)) == GPS_FLASH_JOURNAL_WRITE_FAILED); flash.fail_program_call = 0u; gps_flash_journal_t rebooted; init_journal(&rebooted, &flash, 700u); unsigned char output[700]; uint64_t sequence = 0u; assert(gps_flash_journal_load_latest( &rebooted, output, sizeof(output), NULL, &sequence)); assert(sequence == 1u); assert(memcmp(output, old_payload, sizeof(output)) == 0); } } static void test_corrupt_latest_falls_back_to_other_bank(void) { fake_flash_t flash; fake_flash_reset(&flash); gps_flash_journal_t journal; init_journal(&journal, &flash, 700u); unsigned char payload[700]; memset(payload, 0x11, sizeof(payload)); assert(gps_flash_journal_write(&journal, payload, sizeof(payload)) == GPS_FLASH_JOURNAL_WRITE_COMMITTED); memset(payload, 0x22, sizeof(payload)); assert(gps_flash_journal_write(&journal, payload, sizeof(payload)) == GPS_FLASH_JOURNAL_WRITE_COMMITTED); memset(payload, 0x33, sizeof(payload)); assert(gps_flash_journal_write(&journal, payload, sizeof(payload)) == GPS_FLASH_JOURNAL_WRITE_COMMITTED); /* Three 700-byte records force sequence 3 into the second bank. */ assert(journal.active_region == 1u); flash.bytes[REGION1 + GPS_FLASH_JOURNAL_PAGE_SIZE + 100u] ^= 0x01u; gps_flash_journal_t rebooted; init_journal(&rebooted, &flash, 700u); unsigned char output[700]; uint64_t sequence = 0u; assert(gps_flash_journal_load_latest( &rebooted, output, sizeof(output), NULL, &sequence)); assert(sequence == 2u); assert(output[0] == 0x22u); } int main(void) { test_crc_vector(); test_empty_write_load_and_deduplicate(); test_rotation_keeps_latest_and_old_bank_until_commit(); test_every_torn_program_keeps_previous_record(); test_corrupt_latest_falls_back_to_other_bank(); puts("gps flash journal fault-injection tests passed"); return 0; }