diff --git a/src/helpers/MorseSettings.h b/src/helpers/MorseSettings.h index b33fd78ea..fbc710483 100644 --- a/src/helpers/MorseSettings.h +++ b/src/helpers/MorseSettings.h @@ -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; } diff --git a/src/helpers/PersistentStoreFormat.h b/src/helpers/PersistentStoreFormat.h index cbc788a68..d9e041b5b 100644 --- a/src/helpers/PersistentStoreFormat.h +++ b/src/helpers/PersistentStoreFormat.h @@ -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; diff --git a/test/test_morse_cli.py b/test/test_morse_cli.py index 2c2bf625b..2d25802ba 100644 --- a/test/test_morse_cli.py +++ b/test/test_morse_cli.py @@ -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 +#include +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") diff --git a/test/test_persistent_store_format/test_persistent_store_format.cpp b/test/test_persistent_store_format/test_persistent_store_format.cpp index bef87b2ad..083603ea6 100644 --- a/test/test_persistent_store_format/test_persistent_store_format.cpp +++ b/test/test_persistent_store_format/test_persistent_store_format.cpp @@ -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));