198 lines
8.9 KiB
Python
198 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Compile the complete disabled management implementation at its FS boundary."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
HEADER = r'''
|
|
#pragma once
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#define OUT_PATH_UNKNOWN 255
|
|
struct TransportKey { unsigned marker = 17; };
|
|
struct Filesystem {
|
|
unsigned reads = 0;
|
|
bool exists(const char* path) {
|
|
// No management/data.tx probe, recovery or write is permitted when off.
|
|
assert(!strcmp(path, "/fleet_channel"));
|
|
++reads;
|
|
return true;
|
|
}
|
|
};
|
|
using FILESYSTEM = Filesystem;
|
|
struct Callbacks { bool supportsFleetControl() const { return true; } };
|
|
namespace mesh {
|
|
struct Mesh {};
|
|
struct RegionMap {};
|
|
struct FleetChannel {
|
|
static inline unsigned live = 0;
|
|
FleetChannel(FILESYSTEM*, Callbacks*, RegionMap*) { ++live; }
|
|
~FleetChannel() { --live; }
|
|
bool handleConfig(const char*, char* reply, size_t size) {
|
|
snprintf(reply, size, "fleet remains available"); return true;
|
|
}
|
|
};
|
|
}
|
|
struct CommonCLI {
|
|
mesh::Mesh* _management_mesh = nullptr;
|
|
FILESYSTEM* _management_fs = nullptr;
|
|
mesh::RegionMap region;
|
|
mesh::RegionMap* _region_map = ®ion;
|
|
Callbacks callbacks;
|
|
Callbacks* _callbacks = &callbacks;
|
|
mesh::FleetChannel* _fleet_channel = nullptr;
|
|
unsigned beacon_starts = 0;
|
|
~CommonCLI() { delete _fleet_channel; }
|
|
void beginBeacons() { ++beacon_starts; }
|
|
void beginManagement(mesh::Mesh&, FILESYSTEM*);
|
|
void loopManagement();
|
|
bool handleFleetCommand(const char*, char*);
|
|
bool getDataTxPath(const uint8_t*&, uint8_t&) const;
|
|
bool resolveDataTxScope(TransportKey&, char*, size_t, bool*) const;
|
|
bool adoptLegacyDataTxPath(const uint8_t*, uint8_t);
|
|
bool setDataTxPath(const char*, char*, size_t);
|
|
bool handleDataTxCommand(char*, char*);
|
|
bool handleManagementCommand(char*, char*);
|
|
};
|
|
'''
|
|
|
|
HARNESS = r'''
|
|
#include "CommonCLI.h"
|
|
#include <string>
|
|
#include <vector>
|
|
int main() {
|
|
Filesystem fs;
|
|
mesh::Mesh mesh;
|
|
{
|
|
CommonCLI cli;
|
|
cli.beginManagement(mesh, &fs);
|
|
assert(cli._management_mesh == &mesh && cli._management_fs == &fs);
|
|
assert(cli.beacon_starts == MESH_ENABLE_TX_BEACONS);
|
|
assert(mesh::FleetChannel::live == MESH_ENABLE_FLEET_CONTROL);
|
|
assert(fs.reads == MESH_ENABLE_FLEET_CONTROL);
|
|
cli.loopManagement();
|
|
assert(fs.reads == MESH_ENABLE_FLEET_CONTROL);
|
|
#if MESH_ENABLE_FLEET_CONTROL
|
|
char fleet_reply[160] = {};
|
|
assert(cli.handleFleetCommand("get fleet.stats", fleet_reply));
|
|
assert(!strcmp(fleet_reply, "fleet remains available"));
|
|
#endif
|
|
const uint8_t byte = 42;
|
|
const uint8_t* path = &byte;
|
|
uint8_t length = 3;
|
|
assert(!cli.getDataTxPath(path, length));
|
|
assert(path == nullptr && length == OUT_PATH_UNKNOWN);
|
|
assert(!cli.adoptLegacyDataTxPath(&byte, 1));
|
|
TransportKey key;
|
|
char name[8] = "old";
|
|
bool ambiguous = true;
|
|
assert(!cli.resolveDataTxScope(key, name, sizeof(name), &ambiguous));
|
|
assert(!*name && !ambiguous && key.marker == 17);
|
|
assert(!cli.resolveDataTxScope(key, nullptr, 0, nullptr));
|
|
for (const char* command : {"ver", "board", "stats-core", "stats-radio",
|
|
"stats-packets", "get usb.logging", "get radio2",
|
|
"get password", "host cpu-temp", "get data.txExtra"}) {
|
|
char input[160], reply[160] = "not handled";
|
|
strcpy(input, command);
|
|
assert(!cli.handleManagementCommand(input, reply));
|
|
assert(!strcmp(input, command) && !strcmp(reply, "not handled"));
|
|
}
|
|
for (const char* command : {"get mgmt", "get mgmt.password", "set mgmt.enabled on",
|
|
"get mgmt.path", "set mgmt.path direct",
|
|
"get data.tx", "get data.tx.path", "set data.tx region none"}) {
|
|
char input[160], reply[160] = {};
|
|
strcpy(input, command);
|
|
assert(cli.handleManagementCommand(input, reply));
|
|
assert(!strcmp(input, command));
|
|
assert(!strcmp(reply, !strncmp(command + 4, "data.tx", 7)
|
|
? "Err - data.tx unavailable" : "ERR: management unavailable"));
|
|
}
|
|
for (size_t length : {size_t(0), size_t(5), size_t(96), size_t(200)}) {
|
|
const std::string original = "set mgmt.password " + std::string(length, 's');
|
|
std::vector<char> guarded(original.size() + 9, '\x5A');
|
|
char* command = guarded.data() + 4;
|
|
memcpy(command, original.c_str(), original.size() + 1);
|
|
char reply[160] = {};
|
|
assert(cli.handleManagementCommand(command, reply));
|
|
assert(!strcmp(reply, "ERR: management unavailable"));
|
|
assert(!memcmp(command, "set mgmt.password ", 18));
|
|
for (size_t i = 18; i <= original.size(); ++i) assert(command[i] == 0);
|
|
for (size_t i = 0; i < 4; ++i) assert(guarded[i] == '\x5A');
|
|
for (size_t i = original.size() + 5; i < guarded.size(); ++i) assert(guarded[i] == '\x5A');
|
|
}
|
|
char guarded_reply[12]; memset(guarded_reply, '\x5A', sizeof(guarded_reply));
|
|
assert(!cli.setDataTxPath("direct", guarded_reply + 2, 6));
|
|
assert(guarded_reply[0] == '\x5A' && guarded_reply[1] == '\x5A');
|
|
assert(guarded_reply[7] == 0 && guarded_reply[8] == '\x5A');
|
|
assert(fs.reads == MESH_ENABLE_FLEET_CONTROL);
|
|
}
|
|
assert(mesh::FleetChannel::live == 0);
|
|
puts("PASS: disabled management fails closed, wipes passwords and retains Fleet/Beacon context");
|
|
}
|
|
'''
|
|
|
|
|
|
class ManagementFeatureGateTest(unittest.TestCase):
|
|
def test_default_is_enabled_and_explicit_values_are_boolean(self):
|
|
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
|
|
self.assertIsNotNone(compiler, "a host C++17 compiler is required")
|
|
header = ROOT / "src/helpers/ManagementFeatureConfig.h"
|
|
for platform in (None, "STM32_PLATFORM", "NRF52_PLATFORM", "ESP32_PLATFORM", "RP2040_PLATFORM"):
|
|
for value in (None, 0, 1, -1, 2):
|
|
with self.subTest(platform=platform, value=value):
|
|
command = [compiler, "-E", "-P", "-x", "c++", "-include", str(header)]
|
|
if platform:
|
|
command.append("-D" + platform + "=1")
|
|
if value is not None:
|
|
command.append("-DMESH_ENABLE_MANAGEMENT_REPORTING=" + str(value))
|
|
command.append("-")
|
|
result = subprocess.run(command, input="MESH_ENABLE_MANAGEMENT_REPORTING\n",
|
|
capture_output=True, text=True, timeout=10)
|
|
if value in (-1, 2):
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("must be 0 or 1", result.stderr)
|
|
else:
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(result.stdout.strip(), str(1 if value is None else value))
|
|
|
|
def test_whole_disabled_translation_units_have_no_crypto_or_storage_dependency(self):
|
|
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
|
|
self.assertIsNotNone(compiler, "a host C++17 compiler is required")
|
|
with tempfile.TemporaryDirectory(prefix="management-off-") as directory:
|
|
work = Path(directory)
|
|
(work / "CommonCLI.h").write_text(HEADER, encoding="ascii")
|
|
(work / "test.cpp").write_text(HARNESS, encoding="ascii")
|
|
for name in ("CommonCLI_Management.cpp", "ManagementReporter.cpp"):
|
|
shutil.copyfile(ROOT / "src/helpers" / name, work / name)
|
|
sanitizers = (["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
|
|
"-fno-pie", "-no-pie"] if sys.platform.startswith("linux") else [])
|
|
for beacons, fleet in ((0, 0), (0, 1), (1, 0), (1, 1)):
|
|
with self.subTest(beacons=beacons, fleet=fleet):
|
|
executable = work / ("test-%u-%u" % (beacons, fleet))
|
|
result = subprocess.run([
|
|
compiler, "-std=c++17", "-O1", "-Wall", "-Wextra", "-Werror", *sanitizers,
|
|
"-DMESH_ENABLE_MANAGEMENT_REPORTING=0", "-DMESH_ENABLE_TX_BEACONS=" + str(beacons),
|
|
"-DMESH_ENABLE_FLEET_CONTROL=" + str(fleet), "-I", str(work),
|
|
"-I", str(ROOT / "src/helpers"), str(work / "CommonCLI_Management.cpp"),
|
|
str(work / "ManagementReporter.cpp"), str(work / "test.cpp"), "-o", str(executable),
|
|
], capture_output=True, text=True, timeout=60)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
result = subprocess.run([str(executable)], capture_output=True, text=True, timeout=15)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
self.assertIn("PASS: disabled management fails closed", result.stdout)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|