* DynamicConfigSerializer fixes, and unit tests

This commit is contained in:
Scott Powell
2026-08-24 19:42:55 +10:00
parent 845242f4aa
commit 8ccc9928a8
6 changed files with 115 additions and 41 deletions
+1
View File
@@ -171,6 +171,7 @@ build_src_filter =
+<../src/Utils.cpp> +<../src/Utils.cpp>
+<../src/Packet.cpp> +<../src/Packet.cpp>
+<../src/helpers/ConfigSerializer.cpp> +<../src/helpers/ConfigSerializer.cpp>
+<../src/helpers/DynamicConfigSerializer.cpp>
lib_deps = lib_deps =
google/googletest @ 1.17.0 google/googletest @ 1.17.0
+2 -2
View File
@@ -104,13 +104,13 @@ private:
float getBandwidth() const override { return _parent->bw; } float getBandwidth() const override { return _parent->bw; }
void setBandwidth(float bw) override { _parent->bw = bw; markDirty(); } void setBandwidth(float bw) override { _parent->bw = bw; markDirty(); }
uint8_t getSpreadFactor() const override { return _parent->sf; } 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; } uint8_t getCodingRate() const override { return _parent->cr; }
void setCodingRate(uint8_t cr) override { _parent->cr = cr; markDirty(); } void setCodingRate(uint8_t cr) override { _parent->cr = cr; markDirty(); }
float getAirtimeFactor() const override { return _parent->airtime_factor; } float getAirtimeFactor() const override { return _parent->airtime_factor; }
void setAirtimeFactor(float af) override { _parent->airtime_factor = af; markDirty(); } void setAirtimeFactor(float af) override { _parent->airtime_factor = af; markDirty(); }
bool isCadEnabled() const override { return _parent->cad_enabled; } 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; } uint8_t getIntThresh() const override { return _parent->interference_threshold; }
void setIntThresh(uint8_t t) override { _parent->interference_threshold = t; markDirty(); } void setIntThresh(uint8_t t) override { _parent->interference_threshold = t; markDirty(); }
uint8_t getRxGain() const override { return _parent->rx_boosted_gain; } uint8_t getRxGain() const override { return _parent->rx_boosted_gain; }
+3 -1
View File
@@ -19,6 +19,7 @@ class ConfigSerializer {
int8_t _depth; int8_t _depth;
bool _dirty = false; bool _dirty = false;
protected:
enum OP { READ, WRITE }; enum OP { READ, WRITE };
class Context { class Context {
@@ -39,13 +40,14 @@ class ConfigSerializer {
const char* getToken() const { return rd_buf; } const char* getToken() const { return rd_buf; }
bool keyMatch(int8_t depth, const char* key) { return strcmp(key, _keys[depth]) == 0; } 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); } void setKey(uint8_t depth, const char* key) { strcpy(_keys[depth], key); }
const char* getKey(uint8_t depth) { return _keys[depth]; }
}; };
Context* _context = NULL; Context* _context = NULL;
int8_t getDepth() const { return _depth; }
void writeComma(); void writeComma();
protected:
ConfigSerializer() { } ConfigSerializer() { }
void def(const char* key, char* value, size_t max_len); // max_len inclusive of null void def(const char* key, char* value, size_t max_len); // max_len inclusive of null
+17 -4
View File
@@ -6,7 +6,7 @@
#define KEY_SEP_CHAR ':' #define KEY_SEP_CHAR ':'
#define KEY_SEP_STR ":" #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; if (_fallback && _fallback->setByKey(key, value)) return true;
// TODO: guard for bad chars (':' or '|') // 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) // now append new key/value (if it fits)
if (strlen(new_config) + strlen(key) + strlen(value) + 2 < sizeof(_config)-1) { 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);
strcat(new_config, KEY_SEP_STR); strcat(new_config, KEY_SEP_STR);
strcat(new_config, value); strcat(new_config, value);
strcpy(_config, new_config); // commit new serialized string strcpy(_config, new_config); // commit new serialized string
markDirty();
return true; return true;
} }
return false; // didn't fit in _config[] 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) { bool DynamicConfigSerializer::getByKey(const char* key, char* value, size_t max_len) {
if (_fallback && _fallback->getByKey(key, value, max_len)) return true; 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() { void DynamicConfigSerializer::structure() {
if (_context->op() == OP::WRITE) {
char tmp[MAX_DYNAMIC_CONFG]; char tmp[MAX_DYNAMIC_CONFG];
strcpy(tmp, _config); // make a (modifiable) copy strcpy(tmp, _config); // make a (modifiable) copy
const char* parts[8]; const char* parts[8];
int n = mesh::Utils::parseTextParts(tmp, parts, 8, PROP_SEP_CHAR); int n = mesh::Utils::parseTextParts(tmp, parts, 8, PROP_SEP_CHAR);
// dynamically call def()'s
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
char* item = (char *) parts[i]; char* item = (char *) parts[i];
char* eq = strchr(item, KEY_SEP_CHAR); char* eq = strchr(item, KEY_SEP_CHAR);
if (eq) { if (eq) {
*eq = 0; // replace separator with null terminator *eq = 0; // replace separator with null terminator
def(item, eq + 1, MAX_DYNAMIC_CONFG/2); // maximum HALF of total for individual property value def(item, eq + 1, MAX_DYNAMIC_CONFG/2);
} }
} }
} else {
setByKeyPrv(_context->getKey(getDepth()), _context->getToken());
}
} }
+3
View File
@@ -1,3 +1,4 @@
#pragma once
#include "ConfigSerializer.h" #include "ConfigSerializer.h"
#include "KeyValueStore.h" #include "KeyValueStore.h"
@@ -9,6 +10,8 @@ class DynamicConfigSerializer : public ConfigSerializer, public KeyValueStore {
char _config[MAX_DYNAMIC_CONFG]; char _config[MAX_DYNAMIC_CONFG];
KeyValueStore* _fallback; KeyValueStore* _fallback;
bool setByKeyPrv(const char* key, const char* value);
protected: protected:
void structure() override; void structure() override;
@@ -1,13 +1,6 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "helpers/ConfigSerializer.h" #include "helpers/ConfigSerializer.h"
#include "helpers/DynamicConfigSerializer.h"
class NativeFileSystem {
public:
void mkdir(const char*) { }
};
#define FILESYSTEM NativeFileSystem
#include "helpers/CommonCLI.h"
#undef FILESYSTEM
#define TEST_INT_S "56" #define TEST_INT_S "56"
#define TEST_INT 56 #define TEST_INT 56
@@ -192,28 +185,90 @@ TEST(ConfigSerializer, LoadSerial_IgnoreUnknowns) {
EXPECT_TRUE(match); EXPECT_TRUE(match);
} }
TEST(NodePrefs, FemGainSettingsRoundTrip) { TEST(DynamicConfigSerializer, GetSet_Basic) {
NodePrefs saved; DynamicConfigSerializer data;
saved.radio_fem_rxgain = 0;
saved.radio_fem_txgain = 1;
MockPrintStream output; bool s1 = data.setByKey("age", "11");
ASSERT_TRUE(saved.saveSerial(output)); bool s2 = data.setByKey("name", "Scott");
EXPECT_TRUE(s1 && s2);
std::string serialised(reinterpret_cast<const char*>(output.getBytes()), output.getLength()); char tmp[32];
EXPECT_NE(std::string::npos, serialised.find("fem_rxgain:0")); bool g1 = data.getByKey("age", tmp, 31);
EXPECT_NE(std::string::npos, serialised.find("fem_txgain:1")); EXPECT_TRUE(g1);
EXPECT_STREQ("11", tmp);
MockInputStream input(serialised.c_str()); bool g2 = data.getByKey("name", tmp, 31);
NodePrefs loaded; EXPECT_TRUE(g2);
loaded.radio_fem_rxgain = 1; EXPECT_STREQ("Scott", tmp);
loaded.radio_fem_txgain = 0;
ASSERT_TRUE(loaded.loadSerial(input));
EXPECT_EQ(0, loaded.radio_fem_rxgain);
EXPECT_EQ(1, loaded.radio_fem_txgain);
} }
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 ─────────────────────────────────────────────────────── // ── main ───────────────────────────────────────────────────────