mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-14 20:35:33 +00:00
123 lines
6.1 KiB
Python
123 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Execute the real ACL page handler, ACL storage, and role dispatch branches."""
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
from test_replay_reset_integration import extract_braced
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FIXTURE = ROOT / "test/fixtures/client_acl_cli"
|
|
ACL_MOCKS = ROOT / "test/fixtures/client_acl_spiffs/mocks"
|
|
ROLES = {
|
|
"repeater": ROOT / "examples/simple_repeater/MyMesh.cpp",
|
|
"room": ROOT / "examples/simple_room_server/MyMesh.cpp",
|
|
"sensor": ROOT / "examples/simple_sensor/SensorMesh.cpp",
|
|
}
|
|
|
|
|
|
def role_handler(role, source):
|
|
# Compile the actual timestamp normalization, prefix handling, manager
|
|
# guard, and both ACL branches. Unrelated radio/board handlers are omitted.
|
|
owner = "SensorMesh" if role == "sensor" else "MyMesh"
|
|
body = extract_braced(source, f"void {owner}::handleCommand(uint32_t sender_timestamp,")
|
|
zero_guard = re.search(r"if \([^\n]+sender_timestamp == 0\) sender_timestamp = 1;", body).group()
|
|
prefix = extract_braced(body, "if (strlen(command) > 4 && command[2] == '|')")
|
|
paged = extract_braced(body, "if (mesh::cli::handleACLGet(")
|
|
local = extract_braced(body, 'if (sender_timestamp == 0 && strcmp(command, "get acl") == 0)')
|
|
# The room source opens its next conditional branch's preprocessor guard
|
|
# immediately before the closing brace of the local ACL branch.
|
|
local = re.sub(r"\n#if defined\(WITH_MQTT_NEIGHBORS\)\n\s*}$", "\n}", local)
|
|
guard = ""
|
|
if role == "repeater":
|
|
guard_start = body.rindex("if (sender && !sender->isAdmin())", 0,
|
|
body.index("mesh::cli::handleACLGet("))
|
|
guard = extract_braced(body[guard_start:], "if (sender && !sender->isAdmin())")
|
|
return f"""
|
|
static void {role}Command(ClientACL& acl, ClientInfo* sender,
|
|
uint32_t sender_timestamp, char* command, char* reply) {{
|
|
const int gpio_client_index = sender == nullptr ? -1 : 0;
|
|
(void)gpio_client_index;
|
|
{zero_guard}
|
|
while (*command == ' ') ++command;
|
|
{prefix}
|
|
mesh::cli::normalizeCommandVerb(command);
|
|
{guard}
|
|
{paged} else {local} else strcpy(reply, "unhandled");
|
|
}}
|
|
"""
|
|
|
|
|
|
class ClientAclCliTest(unittest.TestCase):
|
|
def test_executable_pages_and_role_dispatch(self):
|
|
compiler = shutil.which("g++") or shutil.which("clang++")
|
|
if compiler is None:
|
|
self.skipTest("a host C++17 compiler is required")
|
|
sources = {role: path.read_text() for role, path in ROLES.items()}
|
|
utils = (ROOT / "src/Utils.cpp").read_text()
|
|
hex_chars = re.search(r"static const char hex_chars\[\].*;", utils).group()
|
|
generated = "namespace mesh {\n" + hex_chars + "\n"
|
|
generated += extract_braced(utils, "void Utils::toHex(") + "\n"
|
|
generated += extract_braced(utils, "void Utils::printHex(") + "\n}\n"
|
|
for signature in (
|
|
"static bool commandFamilyMatches(", "static bool isCommonManagerReadOnlyAllowed(",
|
|
"static bool isRegionMgrAllowed(", "static bool isFilterMgrAllowed(",
|
|
):
|
|
generated += extract_braced(sources["repeater"], signature) + "\n"
|
|
for role, source in sources.items():
|
|
generated += role_handler(role, source)
|
|
with tempfile.TemporaryDirectory(prefix=".tmp-acl-cli-", dir=ROOT) as directory:
|
|
work = Path(directory)
|
|
# Only declarations are mocked; both hex conversion definitions
|
|
# above come from production Utils.cpp, with the real hex alphabet.
|
|
(work / "Utils.h").write_text("""#pragma once
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
class Stream;
|
|
namespace mesh { class Utils { public:
|
|
static void toHex(char*, const uint8_t*, size_t);
|
|
static void printHex(Stream&, const uint8_t*, size_t);
|
|
}; }
|
|
""")
|
|
(work / "production.h").write_text(generated)
|
|
for clients in (32, 256):
|
|
with self.subTest(max_clients=clients):
|
|
binary = work / f"acl-cli-{clients}"
|
|
result = subprocess.run([
|
|
compiler, "-std=c++17", "-Wall", "-Wextra", "-DESP32=1",
|
|
"-DESP32_PLATFORM=1", "-DMESH_ENABLE_FLOOD_RULE_ENGINE=1",
|
|
f"-DMAX_CLIENTS={clients}", f"-I{work}", f"-I{ACL_MOCKS}",
|
|
f"-I{ROOT / 'src'}", str(FIXTURE / "test_client_acl_cli.cpp"),
|
|
"-o", str(binary),
|
|
], capture_output=True, text=True, timeout=60)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
|
|
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
|
|
self.assertIn("9 ACL CLI checks passed", checked.stdout)
|
|
|
|
def test_remote_dispatch_stays_behind_existing_admin_guards(self):
|
|
for role, path in ROLES.items():
|
|
with self.subTest(role=role):
|
|
source = path.read_text()
|
|
handler = extract_braced(source, f"void {'SensorMesh' if role == 'sensor' else 'MyMesh'}::handleCommand(uint32_t sender_timestamp,")
|
|
self.assertLess(handler.index("mesh::cli::handleACLGet("),
|
|
handler.index('if (sender_timestamp == 0 && strcmp(command, "get acl") == 0)'))
|
|
self.assertIn("reply, 160 - 3,", handler)
|
|
if role == "repeater":
|
|
self.assertLess(handler.index("if (sender && !sender->isAdmin())"),
|
|
handler.index("mesh::cli::handleACLGet("))
|
|
elif role == "room":
|
|
receiver = extract_braced(source, "void MyMesh::onPeerDataRecv(")
|
|
admin = extract_braced(receiver, "if (client->isAdmin())")
|
|
self.assertIn("handleCommand(sender_timestamp,", admin)
|
|
else:
|
|
admin = extract_braced(source, "if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && from->isAdmin())")
|
|
self.assertIn("handleCommand(sender_timestamp,", admin)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|