diff --git a/contact/utilities/save_to_radio.py b/contact/utilities/save_to_radio.py index a541148..57a8135 100644 --- a/contact/utilities/save_to_radio.py +++ b/contact/utilities/save_to_radio.py @@ -2,7 +2,6 @@ from meshtastic.protobuf import channel_pb2 from google.protobuf.message import Message import logging import base64 -import time DEVICE_REBOOT_KEYS = {"button_gpio", "buzzer_gpio", "role", "rebroadcast_mode"} POWER_REBOOT_KEYS = { @@ -100,22 +99,14 @@ def save_changes(interface, modified_settings, menu_state, node=None): # Filter out empty keys valid_keys = [key for key in admin_keys if key and key.strip() and key != b""] - if not valid_keys: - logging.warning("No valid admin keys provided. Skipping admin key update.") - else: - # Clear existing keys if needed - if security_config.admin_key: - logging.info("Clearing existing admin keys...") - del security_config.admin_key[:] - node.writeConfig("security") - time.sleep(2) # Give time for device to process - - # Append new keys - for key in valid_keys: - logging.info(f"Adding admin key: {key}") - security_config.admin_key.append(key) - node.writeConfig("security") - logging.info("Admin keys updated successfully!") + # Update the repeated field in memory, then send one complete + # security config. Sending an empty list first can revoke the key + # authorizing this remote-admin session before a second write can + # restore the remaining keys. + del security_config.admin_key[:] + security_config.admin_key.extend(valid_keys) + node.writeConfig("security") + logging.info("Admin keys updated successfully (%d key(s)).", len(valid_keys)) # Backup 'admin_key' before removing it admin_key_backup = modified_settings.get("admin_key", None) diff --git a/tests/test_save_to_radio.py b/tests/test_save_to_radio.py index c022581..b9c7970 100644 --- a/tests/test_save_to_radio.py +++ b/tests/test_save_to_radio.py @@ -55,6 +55,20 @@ class SaveToRadioTests(unittest.TestCase): self.assertTrue(reconnect_required) + def test_save_changes_replaces_remote_admin_keys_in_one_write(self) -> None: + interface, node = self.build_interface() + key_one = b"1" * 32 + key_two = b"2" * 32 + key_three = b"3" * 32 + node.localConfig.security.admin_key = [key_one, key_two, key_three] + menu_state = SimpleNamespace(menu_path=["Main Menu", "Radio Settings", "Security"]) + + reconnect_required = save_changes(interface, {"admin_key": [key_one, key_two]}, menu_state, node=node) + + self.assertTrue(reconnect_required) + self.assertEqual(node.localConfig.security.admin_key, [key_one, key_two]) + node.writeConfig.assert_called_once_with("security") + def test_save_changes_returns_true_only_for_rebooting_device_fields(self) -> None: interface, node = self.build_interface() menu_state = SimpleNamespace(menu_path=["Main Menu", "Radio Settings", "Device"])