mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-11-04 10:57:27 +00:00
163 lines
7.0 KiB
Python
163 lines
7.0 KiB
Python
"""Exercise per-sender bridge routing with the real parser and storage writers."""
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
import test_bridge_ingress_settings as settings
|
|
|
|
|
|
HARNESS = r'''
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <vector>
|
|
#include "BridgeIngressSettings.h"
|
|
#if !MESH_ENABLE_BRIDGE_INGRESS
|
|
#error "Raw bridge transport flags must activate settings before CommonCLI.h"
|
|
#endif
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
#define ATOMIC_FILE_WRITER_IMPLEMENTATION
|
|
#include "AtomicFileWriter.h"
|
|
#endif
|
|
#include "settings_under_test.cpp"
|
|
|
|
int main() {
|
|
using namespace mesh::bridge;
|
|
MemoryFS fs;
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
fs.rename_replaces = true;
|
|
#endif
|
|
const char* path = "/bridge_routes";
|
|
const std::vector<uint8_t> legacy = {9, 8, 7, 6, 5};
|
|
fs.files["/com_prefs"] = legacy;
|
|
loadIngressSettings(&fs);
|
|
assert(fs.files.size() == 1);
|
|
auto route = ingressRoute();
|
|
assert(route.profile == IngressProfile::Radio && !route.explicit_profile);
|
|
char reply[160];
|
|
auto run = [&](const char* command) {
|
|
strcpy(reply, "untouched");
|
|
return handleIngressCommand(command, reply, sizeof(reply));
|
|
};
|
|
auto okay = [&](const char* command) {
|
|
assert(run(command) && !strncmp(reply, "OK", 2));
|
|
};
|
|
auto rejects = [&](const char* command) {
|
|
assert(run(command) && !strncmp(reply, "Error:", 6));
|
|
};
|
|
okay("set bridge.ingress off");
|
|
assert(fs.files.at("/bridge_ingress")[3] == 2); // legacy v1 remains readable
|
|
assert(ingressRoute().profile == IngressProfile::Off);
|
|
assert(run("get bridge.ingress") && !strcmp(reply, "> off"));
|
|
|
|
IngressSource media_mac, usa_mac, media_udp, usa_udp, wrong_port;
|
|
assert(parseIngressSource("mac:02:10:20:30:40:50", media_mac));
|
|
assert(parseIngressSource("mac:02:10:20:30:40:51", usa_mac));
|
|
assert(parseIngressSource("udp:192.168.1.50:4404", media_udp));
|
|
assert(parseIngressSource("udp:192.168.1.51:4404", usa_udp));
|
|
assert(parseIngressSource("udp:192.168.1.50:4405", wrong_port));
|
|
okay("set bridge.route.1 mac:02:10:20:30:40:50 radio2");
|
|
okay("set bridge.route.2 mac:02:10:20:30:40:51 radio");
|
|
okay("set bridge.route.3 udp:192.168.1.50:4404 radio2");
|
|
okay("set bridge.route.4 udp:192.168.1.51:4404 radio");
|
|
for (const auto* source : {&media_mac, &media_udp}) {
|
|
route = ingressRoute(source);
|
|
assert(route.profile == IngressProfile::Radio2 && route.explicit_profile);
|
|
}
|
|
for (const auto* source : {&usa_mac, &usa_udp}) {
|
|
route = ingressRoute(source);
|
|
assert(route.profile == IngressProfile::Radio && route.explicit_profile);
|
|
}
|
|
route = ingressRoute(&wrong_port);
|
|
assert(route.profile == IngressProfile::Off && !route.explicit_profile);
|
|
assert(run("get bridge.route") && strstr(reply, "4/8"));
|
|
assert(run("get bridge.route.1") && !strcmp(reply, "> 1 mac:02:10:20:30:40:50 radio2"));
|
|
assert(run("get bridge.route.3") && !strcmp(reply, "> 3 udp:192.168.1.50:4404 radio2"));
|
|
const auto good = fs.files.at(path);
|
|
assert(good.size() == RoutesImageSize);
|
|
loadIngressSettings(&fs);
|
|
assert(ingressRoute(&media_mac).profile == IngressProfile::Radio2);
|
|
assert(ingressRoute(&usa_udp).profile == IngressProfile::Radio);
|
|
assert(fs.files.at("/com_prefs") == legacy);
|
|
|
|
for (const char* bad : {
|
|
"set bridge.route.0 mac:02:10:20:30:40:50 radio", "set bridge.route.9 mac:02:10:20:30:40:50 radio",
|
|
"set bridge.route.1 mac:02:10:20:30:40:50 both", "set bridge.route.1 mac:02:10:20:30:40:50 radio junk",
|
|
"set bridge.route.5 mac:02:10:20:30:40:50 radio", "set bridge.route.1 mac:FF:FF:FF:FF:FF:FF radio",
|
|
"set bridge.route.1 mac:00:00:00:00:00:00 radio", "set bridge.route.1 udp:0.0.0.0:4404 radio",
|
|
"set bridge.route.1 udp:224.1.2.3:4404 radio", "set bridge.route.1 udp:192.168.1.50:0 radio",
|
|
"set bridge.route.1 udp:192.168.1.50:65536 radio", "set bridge.route.1 udp:192.168.1.500:4404 radio",
|
|
"get bridge.route.1 extra", "del bridge.route.1 extra", "set bridge.route.1"}) {
|
|
rejects(bad);
|
|
assert(fs.files.at(path) == good && ingressRoute(&media_mac).profile == IngressProfile::Radio2);
|
|
}
|
|
assert(!run("get bridge.router") && !strcmp(reply, "untouched"));
|
|
assert(!run("set bridge.routes.1 mac:02:10:20:30:40:50 radio"));
|
|
char bounded[] = {'x', 'x', 'x', 'z'};
|
|
assert(handleIngressCommand("get bridge.route.1", bounded, 3));
|
|
assert(bounded[2] == 0 && bounded[3] == 'z');
|
|
|
|
fs.fail_write = true;
|
|
rejects("set bridge.route.1 mac:02:10:20:30:40:50 radio");
|
|
assert(fs.files.at(path) == good && ingressRoute(&media_mac).profile == IngressProfile::Radio2);
|
|
fs.fail_write = false;
|
|
fs.fail_write_after = 7;
|
|
rejects("del bridge.route.1");
|
|
assert(fs.files.at(path) == good && ingressRoute(&media_mac).profile == IngressProfile::Radio2);
|
|
fs.fail_write_after = -1;
|
|
|
|
// Every corrupt or incomplete routes image blocks matched AND unmatched inputs.
|
|
for (size_t bit = 0; bit < good.size() * 8; ++bit) {
|
|
fs.files[path] = good;
|
|
fs.files[path][bit / 8] ^= uint8_t(1U << (bit % 8));
|
|
loadIngressSettings(&fs);
|
|
assert(ingressRoute(&media_mac).profile == IngressProfile::Blocked);
|
|
assert(ingressRoute(&wrong_port).profile == IngressProfile::Blocked);
|
|
}
|
|
rejects("set bridge.route.1 mac:02:10:20:30:40:50 radio2");
|
|
okay("clear bridge.routes");
|
|
assert(ingressRoute(&media_mac).profile == IngressProfile::Off);
|
|
for (size_t size = 0; size < good.size(); ++size) {
|
|
fs.files[path] = std::vector<uint8_t>(good.begin(), good.begin() + size);
|
|
loadIngressSettings(&fs);
|
|
assert(ingressRoute().profile == IngressProfile::Blocked);
|
|
}
|
|
okay("clear bridge.routes");
|
|
fs.files[path] = good;
|
|
loadIngressSettings(&fs);
|
|
|
|
// A write can publish before its readback fails. Block until storage reloads.
|
|
fs.fail_after_publication = true;
|
|
rejects("set bridge.route.1 mac:02:10:20:30:40:50 off");
|
|
assert(ingressRoute(&media_mac).profile == IngressProfile::Blocked);
|
|
fs.fail_after_publication = false;
|
|
fs.fail_read_open = false;
|
|
rejects("clear bridge.routes");
|
|
loadIngressSettings(&fs);
|
|
route = ingressRoute(&media_mac);
|
|
assert(route.profile == IngressProfile::Off && route.explicit_profile);
|
|
okay("del bridge.route.1");
|
|
route = ingressRoute(&media_mac);
|
|
assert(route.profile == IngressProfile::Off && !route.explicit_profile);
|
|
okay("clear bridge.routes");
|
|
okay("set bridge.ingress radio2");
|
|
assert(fs.files.at("/bridge_ingress")[3] == 1);
|
|
assert(ingressRoute().profile == IngressProfile::Radio2);
|
|
okay("set bridge.ingress radio");
|
|
fs.files.erase(path);
|
|
loadIngressSettings(&fs);
|
|
assert(ingressRoute().profile == IngressProfile::Radio && !ingressRoute().explicit_profile);
|
|
assert(fs.files.at("/com_prefs") == legacy);
|
|
}
|
|
'''
|
|
|
|
|
|
class BridgeSourceRouteTests(unittest.TestCase):
|
|
def test_saved_source_routes_and_faults_across_filesystems(self):
|
|
# Reuse the real filesystem/writer fixture and its eight transport /
|
|
# platform builds. Only the test main changes; production code is copied.
|
|
with patch.object(settings, "HARNESS", HARNESS):
|
|
settings.BridgeIngressSettingsTests().test_real_settings_and_transactions_across_filesystems()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|