Keep compact contacts within the STM32 flash boundary

This commit is contained in:
mikecarper committed 2026-10-19 02:11:47 -07:00
1 parent 036661e7e4
commit 025eadb39f
4 files changed
+66 -13

No files matched your search

+6 -11
View File
@@ -34,26 +34,21 @@ inline uint32_t morseSettingsCRC(const uint8_t* data, size_t size) {
inline void encodeMorseSettings(const MorseSettings& prefs,
uint8_t (&image)[MorseSettingsImageSize]) {
image[0] = 'M'; image[1] = 'C'; image[2] = 1; image[3] = prefs.wpm;
for (unsigned i = 0; i < 4; ++i)
image[4 + i] = uint8_t(prefs.frequency_khz >> (i * 8));
storage::writeLE32(image + 4, prefs.frequency_khz);
const uint32_t crc = morseSettingsCRC(image, 8);
for (unsigned i = 0; i < 4; ++i) image[8 + i] = uint8_t(crc >> (i * 8));
storage::writeLE32(image + 8, crc);
}
inline bool decodeMorseSettings(const uint8_t* image, size_t size,
MorseSettings& prefs) {
if (!image || size != MorseSettingsImageSize || image[0] != 'M'
|| image[1] != 'C' || image[2] != 1) return false;
uint32_t frequency = 0, crc = 0;
for (unsigned i = 0; i < 4; ++i) {
frequency |= uint32_t(image[4 + i]) << (i * 8);
crc |= uint32_t(image[8 + i]) << (i * 8);
}
const uint32_t crc = morseSettingsCRC(image, 8);
if (crc != storage::readLE32(image + 8)) return false;
MorseSettings candidate;
candidate.frequency_khz = frequency;
candidate.frequency_khz = storage::readLE32(image + 4);
candidate.wpm = image[3];
if (crc != morseSettingsCRC(image, 8) || !validMorseSettings(candidate))
return false;
if (!validMorseSettings(candidate)) return false;
prefs = candidate;
return true;
}
+2 -2
View File
@@ -131,8 +131,8 @@ inline void writeLE32(uint8_t* dest, uint32_t value) {
inline uint32_t updateCRC32(uint32_t crc, const uint8_t* data, size_t len) {
while (len-- > 0) {
crc ^= *data++;
for (uint8_t bit = 0; bit < 8; bit++) {
crc = (crc >> 1) ^ ((crc & 1) ? 0xEDB88320UL : 0);
for (unsigned bit = 0; bit < 8; bit++) {
crc = (crc >> 1) ^ (0xEDB88320UL & (0U - (crc & 1U)));
}
}
return crc;
+31
View File
@@ -11,6 +11,37 @@ ROOT = Path(__file__).resolve().parents[1]
class MorseCLITests(unittest.TestCase):
def test_settings_codec_matches_independent_images_and_unaligned_input(self):
cases = []
for frequency in (100000, 0x020304, 902321, 1000000):
for speed in (1, 25, 60):
prefix = b"MC\x01" + bytes([speed]) + frequency.to_bytes(4, "little")
golden = prefix + (zlib.crc32(prefix) ^ 0xffffffff).to_bytes(4, "little")
cases.append("{%d,%d,{%s}}" % (
frequency, speed, ",".join(str(byte) for byte in golden)))
code = r'''
#include <helpers/MorseSettings.h>
#include <cassert>
struct Case { uint32_t frequency; uint8_t speed; uint8_t expected[12]; };
int main() {
const Case cases[] = {@CASES@};
for (const auto& item : cases) {
mesh::MorseSettings prefs;
prefs.frequency_khz = item.frequency; prefs.wpm = item.speed;
uint8_t encoded[mesh::MorseSettingsImageSize];
mesh::encodeMorseSettings(prefs, encoded);
assert(memcmp(encoded, item.expected, sizeof(encoded)) == 0);
alignas(4) uint8_t unaligned[13];
memcpy(unaligned + 1, item.expected, sizeof(item.expected));
mesh::MorseSettings decoded;
assert(mesh::decodeMorseSettings(unaligned + 1, sizeof(item.expected), decoded));
assert(decoded.frequency_khz == item.frequency && decoded.wpm == item.speed);
}
}
'''.replace("@CASES@", ",".join(cases))
fs_mock = (ROOT / "test/fixtures/radio_profiles/mocks/helpers/IdentityStore.h").read_text(encoding="ascii")
compile_run(self, code, headers={"helpers/IdentityStore.h": fs_mock})
def test_exact_shared_handler_strict_arguments_bounded_replies_and_rollback(self):
code = (ROOT / "test/fixtures/morse_cli.cpp").read_text(encoding="ascii")
fs_mock = (ROOT / "test/fixtures/radio_profiles/mocks/helpers/IdentityStore.h").read_text(encoding="ascii")
@@ -36,6 +36,33 @@ TEST(PersistentStoreFormat, CRCDetectsPayloadChanges) {
EXPECT_EQ(split, expected);
}
TEST(PersistentStoreFormat, CRCMatchesIndependentVectorsAndEverySplit) {
EXPECT_EQ(updateCRC32(0xFFFFFFFFUL, nullptr, 0), 0xFFFFFFFFUL);
EXPECT_EQ(updateCRC32(0x12345678UL, nullptr, 0), 0x12345678UL);
const uint8_t check[] = "123456789";
EXPECT_EQ(updateCRC32(0xFFFFFFFFUL, check, sizeof(check) - 1),
0x340BC6D9UL);
uint8_t payload[256];
for (unsigned i = 0; i < sizeof(payload); ++i) payload[i] = uint8_t(i);
// Independent Python zlib vectors, converted to the store's no-final-xor
// convention: zlib.crc32(payload, seed ^ 0xffffffff) ^ 0xffffffff.
const struct { uint32_t seed, expected; } vectors[] = {
{0xFFFFFFFFUL, 0xD6FA738CUL},
{0x00000000UL, 0x2493092BUL},
{0x12345678UL, 0x8906DCD5UL},
};
for (const auto& vector : vectors) {
EXPECT_EQ(updateCRC32(vector.seed, payload, sizeof(payload)),
vector.expected);
for (size_t split = 0; split <= sizeof(payload); ++split) {
uint32_t crc = updateCRC32(vector.seed, payload, split);
crc = updateCRC32(crc, payload + split, sizeof(payload) - split);
EXPECT_EQ(crc, vector.expected);
}
}
}
TEST(PersistentStoreFormat, OccupancyCanBeDerivedFromCrcProtectedRecords) {
uint8_t empty[CONTACT_RECORD_SIZE] = {};
EXPECT_FALSE(contactRecordHasData(empty));