Skip to content

Commit cc8cb9e

Browse files
authored
Merge pull request #39 from OpenIPC/test/flash-regression
Add regression tests for flash write bugs
2 parents 638e3c7 + b5630d9 commit cc8cb9e

File tree

2 files changed

+587
-0
lines changed

2 files changed

+587
-0
lines changed

agent/test_agent.c

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,183 @@ static void test_cobs_matches_python(void) {
342342
ASSERT(mock_tx[i] != 0x00, "python compat: no internal zeros");
343343
}
344344

345+
/* ---------- Regression tests ---------- */
346+
347+
/*
348+
* Bug: cobs_decode() stripped trailing 0x00 bytes. When a packet's CRC32
349+
* had 0x00 as the MSB, the decoded output was 1 byte short → CRC mismatch.
350+
* This caused ~1/256 packets to fail deterministically based on data content.
351+
* Root cause of ALL flash write failures before the fix.
352+
*/
353+
static void test_cobs_trailing_zero_preserved(void) {
354+
/* Build payloads where CRC32 MSB is 0x00 (trailing zero after LE encode).
355+
* The COBS roundtrip must preserve the trailing zero. */
356+
for (int trial = 0; trial < 1000; trial++) {
357+
/* Construct a packet: [cmd] [data...] [crc32 LE] */
358+
uint8_t payload[64];
359+
uint32_t plen = 5 + (trial % 20); /* varying lengths */
360+
payload[0] = 0x82; /* RSP_DATA */
361+
for (uint32_t i = 1; i < plen; i++)
362+
payload[i] = (uint8_t)((trial * 7 + i * 13) & 0xFF);
363+
364+
/* Compute CRC and append */
365+
uint32_t c = crc32(0, payload, plen);
366+
payload[plen + 0] = (c >> 0) & 0xFF;
367+
payload[plen + 1] = (c >> 8) & 0xFF;
368+
payload[plen + 2] = (c >> 16) & 0xFF;
369+
payload[plen + 3] = (c >> 24) & 0xFF;
370+
uint32_t total = plen + 4;
371+
372+
/* COBS encode → decode roundtrip */
373+
uint8_t enc[256], dec[256];
374+
uint32_t enc_len = cobs_encode(payload, total, enc);
375+
uint32_t dec_len = cobs_decode(enc, enc_len, dec);
376+
377+
ASSERT(dec_len == total, "cobs trailing zero: length preserved");
378+
ASSERT(memcmp(payload, dec, total) == 0,
379+
"cobs trailing zero: data preserved");
380+
}
381+
}
382+
383+
/*
384+
* Bug: CRC32 extraction used `cp[3] << 24` where cp[3] >= 128.
385+
* uint8_t promotes to int, and left-shifting a signed int into the sign
386+
* bit is undefined behavior. With ASAN this manifests as a runtime error.
387+
* Fix: cast to (uint32_t) before shifting.
388+
*/
389+
static void test_crc32_high_byte_shift(void) {
390+
/* Test CRC32 values where the MSB is >= 0x80 (signed bit set).
391+
* These exercise the (uint32_t) cast in CRC extraction. */
392+
uint8_t data_a[] = {0x03, 0x00, 0x00}; /* CMD_WRITE + padding */
393+
uint32_t c = crc32(0, data_a, 3);
394+
395+
/* Verify the CRC packs/unpacks correctly through all 4 bytes */
396+
uint8_t packed[4];
397+
packed[0] = (c >> 0) & 0xFF;
398+
packed[1] = (c >> 8) & 0xFF;
399+
packed[2] = (c >> 16) & 0xFF;
400+
packed[3] = (c >> 24) & 0xFF;
401+
402+
uint32_t unpacked = (uint32_t)packed[0]
403+
| ((uint32_t)packed[1] << 8)
404+
| ((uint32_t)packed[2] << 16)
405+
| ((uint32_t)packed[3] << 24);
406+
ASSERT(unpacked == c, "crc32 high byte: pack/unpack roundtrip");
407+
408+
/* Test with every possible MSB value */
409+
for (int msb = 0; msb < 256; msb++) {
410+
packed[3] = (uint8_t)msb;
411+
uint32_t val = (uint32_t)packed[0]
412+
| ((uint32_t)packed[1] << 8)
413+
| ((uint32_t)packed[2] << 16)
414+
| ((uint32_t)packed[3] << 24);
415+
ASSERT((val >> 24) == (uint32_t)msb, "crc32 high byte shift");
416+
}
417+
}
418+
419+
/*
420+
* End-to-end: for ALL possible payload data (varying byte values that
421+
* produce different CRC patterns), verify proto_send → proto_recv roundtrip.
422+
* This catches the COBS trailing zero bug: ~1/256 CRC values have MSB=0x00.
423+
*/
424+
static void test_cobs_roundtrip_all_crc_patterns(void) {
425+
/* Send 256 different 8-byte payloads through proto_send/recv.
426+
* Each produces a different CRC32, covering all possible MSB values. */
427+
for (int i = 0; i < 256; i++) {
428+
mock_reset();
429+
430+
uint8_t data[8];
431+
for (int j = 0; j < 8; j++)
432+
data[j] = (uint8_t)((i * 37 + j * 53) & 0xFF);
433+
434+
proto_send(RSP_DATA, data, 8);
435+
436+
memcpy(mock_rx, mock_tx, mock_tx_len);
437+
mock_rx_len = mock_tx_len;
438+
mock_rx_pos = 0;
439+
440+
uint8_t recv_buf[MAX_PAYLOAD + 16];
441+
uint32_t recv_len = 0;
442+
uint8_t cmd = proto_recv(recv_buf, &recv_len, 1000);
443+
444+
ASSERT(cmd == RSP_DATA, "crc pattern roundtrip: cmd");
445+
ASSERT(recv_len == 8, "crc pattern roundtrip: len");
446+
ASSERT(memcmp(recv_buf, data, 8) == 0,
447+
"crc pattern roundtrip: data");
448+
}
449+
}
450+
451+
/*
452+
* page_is_ff helper: verify it correctly identifies all-0xFF pages
453+
* and rejects pages with even a single non-0xFF byte.
454+
*/
455+
static int page_is_ff_test(const uint8_t *data, uint32_t len) {
456+
const uint32_t *w = (const uint32_t *)data;
457+
for (uint32_t i = 0; i < len / 4; i++)
458+
if (w[i] != 0xFFFFFFFF) return 0;
459+
return 1;
460+
}
461+
462+
static void test_page_is_ff(void) {
463+
uint8_t page[256];
464+
465+
/* All 0xFF → true */
466+
memset(page, 0xFF, 256);
467+
ASSERT(page_is_ff_test(page, 256) == 1, "page_is_ff: all FF");
468+
469+
/* All 0x00 → false */
470+
memset(page, 0x00, 256);
471+
ASSERT(page_is_ff_test(page, 256) == 0, "page_is_ff: all 00");
472+
473+
/* Single non-FF byte at each position */
474+
for (int pos = 0; pos < 256; pos++) {
475+
memset(page, 0xFF, 256);
476+
page[pos] = 0xFE;
477+
ASSERT(page_is_ff_test(page, 256) == 0,
478+
"page_is_ff: single non-FF byte");
479+
}
480+
481+
/* Random data → false */
482+
for (int i = 0; i < 256; i++) page[i] = (uint8_t)i;
483+
ASSERT(page_is_ff_test(page, 256) == 0, "page_is_ff: random data");
484+
}
485+
486+
/*
487+
* Sector bitmap: verify bit indexing matches the host Python implementation.
488+
* Bit N of bitmap = sector N. LSB-first within each byte.
489+
*/
490+
static void test_sector_bitmap(void) {
491+
uint8_t bitmap[32];
492+
memset(bitmap, 0, 32);
493+
494+
/* Set specific sectors */
495+
bitmap[0] |= (1 << 0); /* sector 0 */
496+
bitmap[0] |= (1 << 7); /* sector 7 */
497+
bitmap[1] |= (1 << 0); /* sector 8 */
498+
bitmap[31] |= (1 << 7); /* sector 255 */
499+
500+
/* Check sector_has_data equivalent */
501+
ASSERT((bitmap[0 / 8] & (1 << (0 % 8))) != 0, "bitmap: sector 0 set");
502+
ASSERT((bitmap[7 / 8] & (1 << (7 % 8))) != 0, "bitmap: sector 7 set");
503+
ASSERT((bitmap[8 / 8] & (1 << (8 % 8))) != 0, "bitmap: sector 8 set");
504+
ASSERT((bitmap[255 / 8] & (1 << (255 % 8))) != 0, "bitmap: sector 255 set");
505+
506+
/* Check unset sectors */
507+
ASSERT((bitmap[1 / 8] & (1 << (1 % 8))) == 0, "bitmap: sector 1 unset");
508+
ASSERT((bitmap[128 / 8] & (1 << (128 % 8))) == 0, "bitmap: sector 128 unset");
509+
ASSERT((bitmap[254 / 8] & (1 << (254 % 8))) == 0, "bitmap: sector 254 unset");
510+
511+
/* All-ones bitmap */
512+
memset(bitmap, 0xFF, 32);
513+
for (int s = 0; s < 256; s++)
514+
ASSERT((bitmap[s / 8] & (1 << (s % 8))) != 0, "bitmap: all set");
515+
516+
/* All-zeros bitmap */
517+
memset(bitmap, 0, 32);
518+
for (int s = 0; s < 256; s++)
519+
ASSERT((bitmap[s / 8] & (1 << (s % 8))) == 0, "bitmap: all clear");
520+
}
521+
345522
/* ---------- main ---------- */
346523

347524
int main(void) {
@@ -375,6 +552,13 @@ int main(void) {
375552
printf("Cross-compatibility:\n");
376553
test_cobs_matches_python();
377554

555+
printf("Regression:\n");
556+
test_cobs_trailing_zero_preserved();
557+
test_crc32_high_byte_shift();
558+
test_cobs_roundtrip_all_crc_patterns();
559+
test_page_is_ff();
560+
test_sector_bitmap();
561+
378562
printf("\n%d/%d tests passed\n", tests_passed, tests_run);
379563
return tests_passed == tests_run ? 0 : 1;
380564
}

0 commit comments

Comments
 (0)