mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-20 16:12:38 +00:00
111 lines
5.1 KiB
Python
111 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Execute legacy ACL replies through the production route and replay guards."""
|
|
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
from test_replay_reset_integration import extract_braced
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FIXTURE = ROOT / "test/fixtures/client_acl_response"
|
|
|
|
# Unmodified official decoder, meshcore-dev/meshcore_py commit
|
|
# 1e6fdd47a36ae32c30246a8486b7a9492665579f, src/meshcore/parsing.py (MIT).
|
|
# https://github.com/meshcore-dev/meshcore_py/blob/1e6fdd47a36ae32c30246a8486b7a9492665579f/src/meshcore/parsing.py
|
|
def parse_acl(buf):
|
|
i = 0
|
|
res = []
|
|
while i + 7 <= len(buf):
|
|
key = buf[i : i + 6].hex()
|
|
perm = buf[i + 6]
|
|
if key != "000000000000":
|
|
res.append({"key": key, "perm": perm})
|
|
i = i + 7
|
|
return res
|
|
|
|
|
|
def production_acl_methods():
|
|
source = (ROOT / "examples/simple_repeater/MyMesh.cpp").read_text()
|
|
handler = extract_braced(source, "int MyMesh::handleRequest(")
|
|
prefix = handler[:handler.index(" if (payload[0] == REQ_TYPE_GET_STATUS)")]
|
|
acl = extract_braced(handler, "if (payload[0] == REQ_TYPE_GET_ACCESS_LIST)")
|
|
generated = prefix + acl + "\nreturn 0;\n}\n"
|
|
receive = extract_braced(source, "if (type == PAYLOAD_TYPE_REQ) { // request")
|
|
generated += """
|
|
void MyMesh::receive(mesh::Packet* packet, uint8_t* data, size_t len) {
|
|
ClientInfo* client = &sender;
|
|
const uint8_t type = PAYLOAD_TYPE_REQ;
|
|
uint8_t secret[PUB_KEY_SIZE] = {};
|
|
""" + receive + "\n}\n"
|
|
mesh_source = (ROOT / "src/Mesh.cpp").read_text()
|
|
generated += "namespace mesh {\n"
|
|
generated += "#define MAX_COMBINED_PATH (MAX_PACKET_PAYLOAD - 2 - CIPHER_BLOCK_SIZE)\n"
|
|
for signature in (
|
|
"Packet* Mesh::createPathReturn(const Identity& dest,",
|
|
"Packet* Mesh::createPathReturn(const uint8_t* dest_hash,",
|
|
"Packet* Mesh::createDatagram(",
|
|
):
|
|
generated += extract_braced(mesh_source, signature) + "\n"
|
|
packet_source = (ROOT / "src/Packet.cpp").read_text()
|
|
for signature in ("Packet::Packet()", "bool Packet::isValidPathLen("):
|
|
generated += extract_braced(packet_source, signature) + "\n"
|
|
generated += "}\n"
|
|
return generated
|
|
|
|
|
|
class ClientAclResponseTest(unittest.TestCase):
|
|
def test_production_acl_route_admission_and_replay(self):
|
|
compiler = shutil.which("g++") or shutil.which("clang++")
|
|
if compiler is None:
|
|
self.skipTest("a host C++17 compiler is required")
|
|
generated = production_acl_methods()
|
|
with tempfile.TemporaryDirectory(prefix=".tmp-acl-response-", dir=ROOT) as directory:
|
|
work = Path(directory)
|
|
(work / "production.inc").write_text(generated, encoding="ascii")
|
|
binary = work / "acl-response.exe"
|
|
command = [compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter",
|
|
f"-I{work}", f"-I{ROOT / 'src'}",
|
|
str(FIXTURE / "test_client_acl_response.cpp"), "-o", str(binary)]
|
|
if sys.platform.startswith("linux"):
|
|
command[1:1] = ["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
|
|
"-fno-pie", "-no-pie"]
|
|
built = subprocess.run(command, capture_output=True, text=True, timeout=60)
|
|
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
|
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
|
|
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
|
|
self.assertIn("ACL route and replay checks passed", checked.stdout)
|
|
decoded = 0
|
|
for line in checked.stdout.splitlines():
|
|
if not line.startswith("LEGACY:"):
|
|
continue
|
|
_, expected, body = line.split(":")
|
|
result = parse_acl(bytes.fromhex(body))
|
|
self.assertEqual(len(result), int(expected))
|
|
self.assertTrue(all(entry["perm"] == 3 for entry in result))
|
|
decoded += 1
|
|
self.assertGreaterEqual(decoded, 10)
|
|
|
|
def test_admission_result_and_default_capacity_wiring(self):
|
|
source = (ROOT / "examples/simple_repeater/MyMesh.cpp").read_text()
|
|
header = (ROOT / "examples/simple_repeater/MyMesh.h").read_text()
|
|
wrapper = extract_braced(source, "bool MyMesh::sendClientReply(")
|
|
self.assertIn("return sendClientReplyWithFallbackScope(", wrapper)
|
|
self.assertIn("bool sendClientReply(", header)
|
|
self.assertIn("size_t reply_capacity = MAX_PACKET_PAYLOAD - CIPHER_MAC_SIZE - (CIPHER_BLOCK_SIZE - 1)", header)
|
|
self.assertIn("if (len < 5) return;", source)
|
|
|
|
def test_same_clock_generates_login_and_binary_request_tags(self):
|
|
source = (ROOT / "src/helpers/BaseChatMesh.cpp").read_text()
|
|
for signature in ("int BaseChatMesh::sendLogin(",
|
|
"bool BaseChatMesh::allocateRequestTag("):
|
|
body = extract_braced(source, signature)
|
|
self.assertEqual(body.count("getRTCClock()->getCurrentTimeUnique()"), 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|