diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 5d48f4c6..c39d3198 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -20,7 +20,7 @@ jobs: uses: ./.github/actions/setup-build-environment - name: Run Unit Tests - run: pio test -e native -e native_kiss_modem -vv + run: pio test -e native -e native_kiss_modem -e native_kiss_modem_no_crypto -vv - name: Upload Test Results # Upload test results even if the test step failed. diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 7dcfc8d7..875c5a50 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -303,12 +303,15 @@ void KissModem::processFrame() { void KissModem::handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint16_t len) { switch (sub_cmd) { +#if !defined(KISS_NO_CRYPTO) case HW_CMD_GET_IDENTITY: handleGetIdentity(); break; +#endif case HW_CMD_GET_RANDOM: handleGetRandom(data, len); break; +#if !defined(KISS_NO_CRYPTO) case HW_CMD_VERIFY_SIGNATURE: handleVerifySignature(data, len); break; @@ -327,6 +330,7 @@ void KissModem::handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint case HW_CMD_HASH: handleHash(data, len); break; +#endif case HW_CMD_SET_RADIO: handleSetRadio(data, len); break; @@ -462,9 +466,11 @@ void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, } } +#if !defined(KISS_NO_CRYPTO) void KissModem::handleGetIdentity() { writeHardwareFrame(HW_RESP(HW_CMD_GET_IDENTITY), _identity.pub_key, PUB_KEY_SIZE); } +#endif void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { if (len < 1) { @@ -483,6 +489,7 @@ void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { writeHardwareFrame(HW_RESP(HW_CMD_GET_RANDOM), buf, requested); } +#if !defined(KISS_NO_CRYPTO) void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { if (len < PUB_KEY_SIZE + SIGNATURE_SIZE + 1) { writeHardwareError(HW_ERR_INVALID_LENGTH); @@ -570,6 +577,7 @@ void KissModem::handleHash(const uint8_t* data, uint16_t len) { mesh::Utils::sha256(hash, 32, data, len); writeHardwareFrame(HW_RESP(HW_CMD_HASH), hash, 32); } +#endif void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { if (len < 10) { diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index a23e459b..2a5e3102 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -166,14 +166,18 @@ class KissModem { void handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint16_t len); void processTx(); +#if !defined(KISS_NO_CRYPTO) void handleGetIdentity(); +#endif void handleGetRandom(const uint8_t* data, uint16_t len); +#if !defined(KISS_NO_CRYPTO) void handleVerifySignature(const uint8_t* data, uint16_t len); void handleSignData(const uint8_t* data, uint16_t len); void handleEncryptData(const uint8_t* data, uint16_t len); void handleDecryptData(const uint8_t* data, uint16_t len); void handleKeyExchange(const uint8_t* data, uint16_t len); void handleHash(const uint8_t* data, uint16_t len); +#endif void handleSetRadio(const uint8_t* data, uint16_t len); void handleSetTxPower(const uint8_t* data, uint16_t len); void handleGetRadio(); diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 5836a694..a88efe48 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -1,9 +1,10 @@ #include #include #include -#include #include "KissModem.h" +#if !defined(KISS_NO_CRYPTO) +#include #if defined(NRF52_PLATFORM) #include #elif defined(RP2040_PLATFORM) @@ -13,6 +14,7 @@ #else #include #endif +#endif #if defined(KISS_UART_RX) && defined(KISS_UART_TX) #include @@ -33,6 +35,7 @@ void halt() { while (1) ; } +#if !defined(KISS_NO_CRYPTO) void loadOrCreateIdentity() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) InternalFS.begin(); @@ -56,6 +59,7 @@ void loadOrCreateIdentity() { store.save("_main", identity); } } +#endif void onSetRadio(float freq, float bw, uint8_t sf, uint8_t cr) { radio_driver.setParams(freq, bw, sf, cr); @@ -85,7 +89,9 @@ void setup() { radio_driver.begin(); rng.begin(radio_driver.getRngSeed()); +#if !defined(KISS_NO_CRYPTO) loadOrCreateIdentity(); +#endif sensors.begin(); diff --git a/platformio.ini b/platformio.ini index e78124a4..72c718f5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -164,7 +164,7 @@ build_flags = -std=c++17 -I src -I test/mocks test_build_src = yes -test_ignore = test_kiss_modem +test_ignore = test_kiss_modem, test_kiss_modem_no_crypto build_src_filter = -<*> +<../src/Utils.cpp> @@ -187,3 +187,19 @@ build_src_filter = +<../examples/kiss_modem/KissModem.cpp> lib_deps = google/googletest @ 1.17.0 + +[env:native_kiss_modem_no_crypto] +platform = native +test_framework = googletest +build_flags = -std=c++17 + -D KISS_NO_CRYPTO=1 + -I test/mocks + -I src + -I examples/kiss_modem +test_build_src = yes +test_filter = test_kiss_modem_no_crypto +build_src_filter = + -<*> + +<../examples/kiss_modem/KissModem.cpp> +lib_deps = + google/googletest @ 1.17.0 diff --git a/test/test_kiss_modem_no_crypto/test_no_crypto.cpp b/test/test_kiss_modem_no_crypto/test_no_crypto.cpp new file mode 100644 index 00000000..d7ed85e5 --- /dev/null +++ b/test/test_kiss_modem_no_crypto/test_no_crypto.cpp @@ -0,0 +1,160 @@ +#include + +#include +#include + +#include "KissModem.h" + +class SimpleStream : public Stream { +public: + void pushRx(const std::vector& bytes) { + for (uint8_t b : bytes) { + _rx.push(b); + } + } + + const std::vector& writesSnapshot() const { return _writes; } + + int availableForWrite() override { return 4096; } + + size_t write(const uint8_t* buffer, size_t size) override { + for (size_t i = 0; i < size; i++) { + _writes.push_back(buffer[i]); + } + return size; + } + + size_t write(uint8_t b) override { return write(&b, 1); } + + int available() override { return static_cast(_rx.size()); } + + int read() override { + if (_rx.empty()) return -1; + int b = _rx.front(); + _rx.pop(); + return b; + } + +private: + std::queue _rx; + std::vector _writes; +}; + +class FakeRNG : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override { + for (size_t i = 0; i < sz; i++) { + dest[i] = (uint8_t)i; + } + } +}; + +class FakeRadio : public mesh::Radio { +public: + bool isReceiving() override { return false; } + uint32_t getEstAirtimeFor(uint16_t) override { return 10; } + bool startSendRaw(const uint8_t*, uint16_t) override { return true; } + bool isSendComplete() override { return true; } + void onSendFinished() override {} + int16_t getNoiseFloor() override { return -120; } +}; + +class FakeBoard : public mesh::MainBoard { +public: + uint16_t getBattMilliVolts() override { return 4200; } + float getMCUTemperature() override { return 24.0f; } + const char* getManufacturerName() override { return "test-board"; } + void reboot() override {} +}; + +class FakeSensors : public SensorManager { +public: + bool querySensors(uint8_t, CayenneLPP&) override { return false; } +}; + +static std::vector hwFrame(uint8_t sub_cmd, const std::vector& payload = {}) { + std::vector frame = {KISS_FEND, KISS_CMD_SETHARDWARE, sub_cmd}; + frame.insert(frame.end(), payload.begin(), payload.end()); + frame.push_back(KISS_FEND); + return frame; +} + +class KissModemNoCryptoFixture : public ::testing::Test { +protected: + SimpleStream serial; + mesh::LocalIdentity identity; + FakeRNG rng; + FakeRadio radio; + FakeBoard board; + FakeSensors sensors; + KissModem modem; + + KissModemNoCryptoFixture() + : modem(serial, identity, rng, radio, board, sensors) { + modem.begin(); + } +}; + +TEST_F(KissModemNoCryptoFixture, PingStillWorksWhenCryptoDisabled) { + serial.pushRx(hwFrame(HW_CMD_PING)); + modem.loop(); + + const std::vector expected = { + KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP(HW_CMD_PING), KISS_FEND}; + EXPECT_EQ(serial.writesSnapshot(), expected); +} + +TEST_F(KissModemNoCryptoFixture, GetRadioStillWorksWhenCryptoDisabled) { + serial.pushRx(hwFrame(HW_CMD_GET_RADIO)); + modem.loop(); + + std::vector expected = {KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP(HW_CMD_GET_RADIO)}; + expected.insert(expected.end(), 10, 0x00); // zero-initialized RadioConfig + expected.push_back(KISS_FEND); + EXPECT_EQ(serial.writesSnapshot(), expected); +} + +TEST_F(KissModemNoCryptoFixture, GetRandomStillWorksWhenCryptoDisabled) { + serial.pushRx(hwFrame(HW_CMD_GET_RANDOM, {0x04})); + modem.loop(); + + const std::vector expected = { + KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP(HW_CMD_GET_RANDOM), 0x00, 0x01, 0x02, 0x03, KISS_FEND}; + EXPECT_EQ(serial.writesSnapshot(), expected); +} + +class KissModemDisabledCommandTest : public ::testing::TestWithParam { +protected: + SimpleStream serial; + mesh::LocalIdentity identity; + FakeRNG rng; + FakeRadio radio; + FakeBoard board; + FakeSensors sensors; + KissModem modem; + + KissModemDisabledCommandTest() + : modem(serial, identity, rng, radio, board, sensors) { + modem.begin(); + } +}; + +TEST_P(KissModemDisabledCommandTest, ReturnsUnknownCommandWhenCryptoDisabled) { + serial.pushRx(hwFrame(GetParam())); + modem.loop(); + + const std::vector expected = { + KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP_ERROR, HW_ERR_UNKNOWN_CMD, KISS_FEND}; + EXPECT_EQ(serial.writesSnapshot(), expected); +} + +INSTANTIATE_TEST_SUITE_P( + AllCryptoCommands, KissModemDisabledCommandTest, + ::testing::Values( + HW_CMD_GET_IDENTITY, HW_CMD_VERIFY_SIGNATURE, HW_CMD_SIGN_DATA, HW_CMD_ENCRYPT_DATA, + HW_CMD_DECRYPT_DATA, HW_CMD_KEY_EXCHANGE, HW_CMD_HASH)); + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}