From 8ccc9928a83b4035f40ccc7ad99a2fd2de03c093 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 24 Aug 2026 19:42:55 +1000 Subject: [PATCH] * DynamicConfigSerializer fixes, and unit tests --- platformio.ini | 1 + src/helpers/CommonCLI.h | 4 +- src/helpers/ConfigSerializer.h | 4 +- src/helpers/DynamicConfigSerializer.cpp | 39 ++++--- src/helpers/DynamicConfigSerializer.h | 3 + .../test_config_serializer.cpp | 105 +++++++++++++----- 6 files changed, 115 insertions(+), 41 deletions(-) diff --git a/platformio.ini b/platformio.ini index ee502473c..2219c9786 100644 --- a/platformio.ini +++ b/platformio.ini @@ -171,6 +171,7 @@ build_src_filter = +<../src/Utils.cpp> +<../src/Packet.cpp> +<../src/helpers/ConfigSerializer.cpp> + +<../src/helpers/DynamicConfigSerializer.cpp> lib_deps = google/googletest @ 1.17.0 diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 17b3da876..8591cdc14 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -104,13 +104,13 @@ private: float getBandwidth() const override { return _parent->bw; } void setBandwidth(float bw) override { _parent->bw = bw; markDirty(); } uint8_t getSpreadFactor() const override { return _parent->sf; } - void setSpreadFactor(uint8_t sf) override { _parent->sf; markDirty(); } + void setSpreadFactor(uint8_t sf) override { _parent->sf = sf; markDirty(); } uint8_t getCodingRate() const override { return _parent->cr; } void setCodingRate(uint8_t cr) override { _parent->cr = cr; markDirty(); } float getAirtimeFactor() const override { return _parent->airtime_factor; } void setAirtimeFactor(float af) override { _parent->airtime_factor = af; markDirty(); } bool isCadEnabled() const override { return _parent->cad_enabled; } - void setCadEnabled(bool en) override { _parent->cad_enabled; markDirty(); } + void setCadEnabled(bool en) override { _parent->cad_enabled = en; markDirty(); } uint8_t getIntThresh() const override { return _parent->interference_threshold; } void setIntThresh(uint8_t t) override { _parent->interference_threshold = t; markDirty(); } uint8_t getRxGain() const override { return _parent->rx_boosted_gain; } diff --git a/src/helpers/ConfigSerializer.h b/src/helpers/ConfigSerializer.h index 47ab5e81d..e55b1b120 100644 --- a/src/helpers/ConfigSerializer.h +++ b/src/helpers/ConfigSerializer.h @@ -19,6 +19,7 @@ class ConfigSerializer { int8_t _depth; bool _dirty = false; +protected: enum OP { READ, WRITE }; class Context { @@ -39,13 +40,14 @@ class ConfigSerializer { const char* getToken() const { return rd_buf; } bool keyMatch(int8_t depth, const char* key) { return strcmp(key, _keys[depth]) == 0; } void setKey(uint8_t depth, const char* key) { strcpy(_keys[depth], key); } + const char* getKey(uint8_t depth) { return _keys[depth]; } }; Context* _context = NULL; + int8_t getDepth() const { return _depth; } void writeComma(); -protected: ConfigSerializer() { } void def(const char* key, char* value, size_t max_len); // max_len inclusive of null diff --git a/src/helpers/DynamicConfigSerializer.cpp b/src/helpers/DynamicConfigSerializer.cpp index dc08d7d78..b9c6c267e 100644 --- a/src/helpers/DynamicConfigSerializer.cpp +++ b/src/helpers/DynamicConfigSerializer.cpp @@ -6,7 +6,7 @@ #define KEY_SEP_CHAR ':' #define KEY_SEP_STR ":" -bool DynamicConfigSerializer::setByKey(const char* key, const char* value) { +bool DynamicConfigSerializer::setByKeyPrv(const char* key, const char* value) { if (_fallback && _fallback->setByKey(key, value)) return true; // TODO: guard for bad chars (':' or '|') @@ -34,16 +34,26 @@ bool DynamicConfigSerializer::setByKey(const char* key, const char* value) { } // now append new key/value (if it fits) if (strlen(new_config) + strlen(key) + strlen(value) + 2 < sizeof(_config)-1) { + if (new_config[0]) { + strcat(new_config, PROP_SEP_STR); + } strcat(new_config, key); strcat(new_config, KEY_SEP_STR); strcat(new_config, value); strcpy(_config, new_config); // commit new serialized string - markDirty(); return true; } return false; // didn't fit in _config[] } +bool DynamicConfigSerializer::setByKey(const char* key, const char* value) { + if (setByKeyPrv(key, value)) { + markDirty(); + return true; + } + return false; +} + bool DynamicConfigSerializer::getByKey(const char* key, char* value, size_t max_len) { if (_fallback && _fallback->getByKey(key, value, max_len)) return true; @@ -66,19 +76,22 @@ bool DynamicConfigSerializer::getByKey(const char* key, char* value, size_t max_ } void DynamicConfigSerializer::structure() { - char tmp[MAX_DYNAMIC_CONFG]; - strcpy(tmp, _config); // make a (modifiable) copy + if (_context->op() == OP::WRITE) { + char tmp[MAX_DYNAMIC_CONFG]; + strcpy(tmp, _config); // make a (modifiable) copy - const char* parts[8]; - int n = mesh::Utils::parseTextParts(tmp, parts, 8, PROP_SEP_CHAR); + const char* parts[8]; + int n = mesh::Utils::parseTextParts(tmp, parts, 8, PROP_SEP_CHAR); - // dynamically call def()'s - for (int i = 0; i < n; i++) { - char* item = (char *) parts[i]; - char* eq = strchr(item, KEY_SEP_CHAR); - if (eq) { - *eq = 0; // replace separator with null terminator - def(item, eq + 1, MAX_DYNAMIC_CONFG/2); // maximum HALF of total for individual property value + for (int i = 0; i < n; i++) { + char* item = (char *) parts[i]; + char* eq = strchr(item, KEY_SEP_CHAR); + if (eq) { + *eq = 0; // replace separator with null terminator + def(item, eq + 1, MAX_DYNAMIC_CONFG/2); + } } + } else { + setByKeyPrv(_context->getKey(getDepth()), _context->getToken()); } } diff --git a/src/helpers/DynamicConfigSerializer.h b/src/helpers/DynamicConfigSerializer.h index 102f8e97c..e5dc4c7c9 100644 --- a/src/helpers/DynamicConfigSerializer.h +++ b/src/helpers/DynamicConfigSerializer.h @@ -1,3 +1,4 @@ +#pragma once #include "ConfigSerializer.h" #include "KeyValueStore.h" @@ -9,6 +10,8 @@ class DynamicConfigSerializer : public ConfigSerializer, public KeyValueStore { char _config[MAX_DYNAMIC_CONFG]; KeyValueStore* _fallback; + bool setByKeyPrv(const char* key, const char* value); + protected: void structure() override; diff --git a/test/test_config_serializer/test_config_serializer.cpp b/test/test_config_serializer/test_config_serializer.cpp index 27c3c8119..dec554830 100644 --- a/test/test_config_serializer/test_config_serializer.cpp +++ b/test/test_config_serializer/test_config_serializer.cpp @@ -1,13 +1,6 @@ #include #include "helpers/ConfigSerializer.h" - -class NativeFileSystem { -public: - void mkdir(const char*) { } -}; -#define FILESYSTEM NativeFileSystem -#include "helpers/CommonCLI.h" -#undef FILESYSTEM +#include "helpers/DynamicConfigSerializer.h" #define TEST_INT_S "56" #define TEST_INT 56 @@ -192,28 +185,90 @@ TEST(ConfigSerializer, LoadSerial_IgnoreUnknowns) { EXPECT_TRUE(match); } -TEST(NodePrefs, FemGainSettingsRoundTrip) { - NodePrefs saved; - saved.radio_fem_rxgain = 0; - saved.radio_fem_txgain = 1; +TEST(DynamicConfigSerializer, GetSet_Basic) { + DynamicConfigSerializer data; - MockPrintStream output; - ASSERT_TRUE(saved.saveSerial(output)); + bool s1 = data.setByKey("age", "11"); + bool s2 = data.setByKey("name", "Scott"); + EXPECT_TRUE(s1 && s2); - std::string serialised(reinterpret_cast(output.getBytes()), output.getLength()); - EXPECT_NE(std::string::npos, serialised.find("fem_rxgain:0")); - EXPECT_NE(std::string::npos, serialised.find("fem_txgain:1")); + char tmp[32]; + bool g1 = data.getByKey("age", tmp, 31); + EXPECT_TRUE(g1); + EXPECT_STREQ("11", tmp); - MockInputStream input(serialised.c_str()); - NodePrefs loaded; - loaded.radio_fem_rxgain = 1; - loaded.radio_fem_txgain = 0; - - ASSERT_TRUE(loaded.loadSerial(input)); - EXPECT_EQ(0, loaded.radio_fem_rxgain); - EXPECT_EQ(1, loaded.radio_fem_txgain); + bool g2 = data.getByKey("name", tmp, 31); + EXPECT_TRUE(g2); + EXPECT_STREQ("Scott", tmp); } +TEST(DynamicConfigSerializer, Set_Replaces) { + DynamicConfigSerializer data; + + bool s1 = data.setByKey("age", "11"); + bool s2 = data.setByKey("name", "Scott"); + EXPECT_TRUE(s1 && s2); + + bool s3 = data.setByKey("age", "333"); + EXPECT_TRUE(s3); + + char tmp[32]; + bool g1 = data.getByKey("age", tmp, 31); + EXPECT_TRUE(g1); + EXPECT_STREQ("333", tmp); + + bool g2 = data.getByKey("name", tmp, 31); + EXPECT_TRUE(g2); + EXPECT_STREQ("Scott", tmp); +} + +TEST(DynamicConfigSerializer, GetUnknown_Fail) { + DynamicConfigSerializer data; + + bool s1 = data.setByKey("age", "11"); + EXPECT_TRUE(s1); + + char tmp[32]; + bool g2 = data.getByKey("name", tmp, 31); + EXPECT_FALSE(g2); +} + +TEST(DynamicConfigSerializer, SaveCustom_Basic) { + MockPrintStream s; + DynamicConfigSerializer data; + + bool s1 = data.setByKey("age", "11"); + bool s2 = data.setByKey("name", "Scott"); + EXPECT_TRUE(s1 && s2); + + bool success = data.saveSerial(s); + EXPECT_TRUE(success); + + auto l = s.getLength(); + char tmp[128]; + memcpy(tmp, s.getBytes(), l); + tmp[l] = 0; + + const char* expect = "{age:\"11\",name:\"Scott\"}"; + EXPECT_STREQ(expect, tmp); +} + +TEST(DynamicConfigSerializer, LoadCustom_Basic) { + MockInputStream s("{age:\"" TEST_INT_S "\",name:\"Scott\"}"); + DynamicConfigSerializer data; + + bool success = data.loadSerial(s); + EXPECT_TRUE(success); + + char tmp[32]; + bool g1 = data.getByKey("age", tmp, 31); + EXPECT_TRUE(g1); + EXPECT_STREQ(TEST_INT_S, tmp); + + bool g2 = data.getByKey("name", tmp, 31); + EXPECT_TRUE(g2); + EXPECT_STREQ("Scott", tmp); +} // ── main ───────────────────────────────────────────────────────