mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-08-07 17:33:16 +02:00
fix(cli): clamp delay factors to the firmware ranges
Firmware rejects rxdelay outside 0-20 and txdelay/direct.txdelay outside 0-2.0; the CLI only rejected negatives, so a remote admin could set delay factors far beyond what any firmware node would accept. Apply the firmware ranges with the firmware error strings and state the ranges in the help text. Existing configs with out-of-range values are untouched — only new CLI sets are gated. Also adds an end-to-end regression test running every set command against the real ConfigManager on a temp config file.
This commit is contained in:
@@ -56,7 +56,7 @@ class MeshCLI:
|
||||
self.config_manager.live_update_daemon(sections)
|
||||
return True
|
||||
|
||||
def _get_security_config(self):
|
||||
def _get_security_config(self) -> Dict[str, Any]:
|
||||
"""Return the repeater login security section (the one LoginHelper reads)."""
|
||||
security = self.repeater_config.get("security")
|
||||
return security if isinstance(security, dict) else {}
|
||||
@@ -357,9 +357,9 @@ class MeshCLI:
|
||||
" set flood.max <hops> Max flood hops (max 64)\n"
|
||||
" set path.hash.mode <0-2> Path hash mode (0=1B,1=2B,2=3B)\n"
|
||||
" set loop.detect <off|minimal|moderate|strict> Flood loop detection\n"
|
||||
" set rxdelay <val> RX delay base (>=0)\n"
|
||||
" set txdelay <val> TX delay factor (>=0)\n"
|
||||
" set direct.txdelay <val> Direct TX delay (>=0)\n"
|
||||
" set rxdelay <val> RX delay base (0-20)\n"
|
||||
" set txdelay <val> TX delay factor (0-2)\n"
|
||||
" set direct.txdelay <val> Direct TX delay (0-2)\n"
|
||||
" set multi.acks <n> Multi-ack count\n"
|
||||
" set int.thresh <dbm> Interference threshold\n"
|
||||
" set agc.reset.interval <n> AGC reset (rounded to x4)"
|
||||
@@ -772,8 +772,8 @@ class MeshCLI:
|
||||
|
||||
elif key == "rxdelay":
|
||||
delay = float(value)
|
||||
if delay < 0:
|
||||
return "Error: cannot be negative"
|
||||
if delay < 0 or delay > 20.0:
|
||||
return "Error, must be 0-20"
|
||||
self.config.setdefault("delays", {})["rx_delay_base"] = delay
|
||||
if not self._save_config_and_apply(["repeater", "delays"]):
|
||||
return "Error: Failed to save config"
|
||||
@@ -781,8 +781,8 @@ class MeshCLI:
|
||||
|
||||
elif key == "txdelay":
|
||||
delay = float(value)
|
||||
if delay < 0:
|
||||
return "Error: cannot be negative"
|
||||
if delay < 0 or delay > 2.0:
|
||||
return "Error, must be 0-2"
|
||||
self.config.setdefault("delays", {})["tx_delay_factor"] = delay
|
||||
if not self._save_config_and_apply(["repeater", "delays"]):
|
||||
return "Error: Failed to save config"
|
||||
@@ -790,8 +790,8 @@ class MeshCLI:
|
||||
|
||||
elif key == "direct.txdelay":
|
||||
delay = float(value)
|
||||
if delay < 0:
|
||||
return "Error: cannot be negative"
|
||||
if delay < 0 or delay > 2.0:
|
||||
return "Error, must be 0-2"
|
||||
self.config.setdefault("delays", {})["direct_tx_delay_factor"] = delay
|
||||
if not self._save_config_and_apply(["repeater", "delays"]):
|
||||
return "Error: Failed to save config"
|
||||
|
||||
@@ -227,9 +227,13 @@ def test_cmd_set_updates_and_validation_errors():
|
||||
assert cfg["repeater"]["send_advert_interval_hours"] == 12
|
||||
assert cfg["repeater"]["flood_advert_interval_hours"] == 24
|
||||
assert cli._cmd_set("flood.max 100") == "Error: max 64"
|
||||
assert cli._cmd_set("rxdelay -1") == "Error: cannot be negative"
|
||||
assert cli._cmd_set("txdelay -1") == "Error: cannot be negative"
|
||||
assert cli._cmd_set("direct.txdelay -1") == "Error: cannot be negative"
|
||||
assert cli._cmd_set("rxdelay -1") == "Error, must be 0-20"
|
||||
assert cli._cmd_set("rxdelay 20.5") == "Error, must be 0-20"
|
||||
assert cli._cmd_set("rxdelay 20") == "OK"
|
||||
assert cli._cmd_set("txdelay -1") == "Error, must be 0-2"
|
||||
assert cli._cmd_set("txdelay 2.5") == "Error, must be 0-2"
|
||||
assert cli._cmd_set("direct.txdelay -1") == "Error, must be 0-2"
|
||||
assert cli._cmd_set("direct.txdelay 2.5") == "Error, must be 0-2"
|
||||
|
||||
assert cli._cmd_set("agc.reset.interval 10") == "OK - interval rounded to 8"
|
||||
assert cli._cmd_set("bad") == "Error: Missing value"
|
||||
@@ -545,3 +549,60 @@ def test_cmd_get_flood_advert_interval_reads_engine_key():
|
||||
|
||||
cfg["repeater"]["send_advert_interval_hours"] = 6
|
||||
assert cli._cmd_get("flood.advert.interval") == "> 6"
|
||||
|
||||
|
||||
def test_cli_set_commands_persist_with_real_config_manager(tmp_path):
|
||||
"""Every CLI set command must work against the real ConfigManager bool save
|
||||
contract: reply OK and leave the change on disk."""
|
||||
import yaml
|
||||
|
||||
from repeater.config_manager import ConfigManager
|
||||
|
||||
config_path = tmp_path / "config.yaml"
|
||||
cfg = _base_config()
|
||||
manager = ConfigManager(config_path=str(config_path), config=cfg, daemon_instance=None)
|
||||
cli = MeshCLI(str(config_path), cfg, manager)
|
||||
|
||||
commands = [
|
||||
"af 1.5",
|
||||
"name node-b",
|
||||
"repeat off",
|
||||
"lat 10.5",
|
||||
"lon -3.25",
|
||||
"radio 869.618 250 9 6",
|
||||
"freq 915.125",
|
||||
"tx 17",
|
||||
"guest.password gpw",
|
||||
"owner.info Bob|Lab",
|
||||
"allow.read.only on",
|
||||
"advert.interval 90",
|
||||
"flood.advert.interval 12",
|
||||
"flood.max 8",
|
||||
"path.hash.mode 1",
|
||||
"loop.detect moderate",
|
||||
"rxdelay 4.5",
|
||||
"txdelay 1.5",
|
||||
"direct.txdelay 0.25",
|
||||
"multi.acks 1",
|
||||
"int.thresh -110",
|
||||
"agc.reset.interval 8",
|
||||
]
|
||||
for command in commands:
|
||||
reply = cli._cmd_set(command)
|
||||
assert reply.startswith("OK"), f"set {command!r} replied {reply!r}"
|
||||
|
||||
assert cli._cmd_password("password newadminpw") == "password now: newadminpw"
|
||||
|
||||
saved = yaml.safe_load(config_path.read_text())
|
||||
assert saved["repeater"]["airtime_factor"] == 1.5
|
||||
assert saved["repeater"]["node_name"] == "node-b"
|
||||
assert saved["repeater"]["mode"] == "monitor"
|
||||
assert saved["radio"]["frequency"] == 915125000
|
||||
assert saved["radio"]["bandwidth"] == 250000
|
||||
assert saved["repeater"]["security"]["guest_password"] == "gpw"
|
||||
assert saved["repeater"]["security"]["admin_password"] == "newadminpw"
|
||||
assert saved["repeater"]["security"]["allow_read_only"] is True
|
||||
assert saved["repeater"]["send_advert_interval_hours"] == 12
|
||||
assert saved["delays"]["rx_delay_base"] == 4.5
|
||||
assert saved["delays"]["tx_delay_factor"] == 1.5
|
||||
assert saved["delays"]["direct_tx_delay_factor"] == 0.25
|
||||
|
||||
Reference in New Issue
Block a user