178 lines
7.7 KiB
C++
178 lines
7.7 KiB
C++
#include "ManagementReporter.h"
|
|
#include "CommonCLI.h"
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <new>
|
|
#include <string>
|
|
|
|
static bool fail_nothrow_allocation = false;
|
|
static unsigned failed_allocations = 0;
|
|
|
|
// Exercise the standard public nothrow allocation-failure path, not a
|
|
// substituted ManagementReporter constructor or command implementation.
|
|
void* operator new(size_t size, const std::nothrow_t&) noexcept {
|
|
if (fail_nothrow_allocation) { ++failed_allocations; return nullptr; }
|
|
try { return ::operator new(size); } catch (const std::bad_alloc&) { return nullptr; }
|
|
}
|
|
|
|
namespace mesh {
|
|
UsbLoggingStatus usbLoggingStatus() { return UsbLoggingStatus(); }
|
|
}
|
|
|
|
#include "production_path_parser.inc"
|
|
|
|
// The route parser has independent source-linked storage tests. Its routing
|
|
// boundary records what this wrapper delegates; management commands do not
|
|
// depend on or change route state in this focused fixture.
|
|
bool CommonCLI::handleDataTxCommand(char* command, char* reply) {
|
|
data_commands.emplace_back(command);
|
|
if (!strncmp(command, "set data.tx path ", 17)) {
|
|
uint8_t path[MAX_PATH_SIZE], path_len = OUT_PATH_UNKNOWN;
|
|
const char* error = nullptr;
|
|
if (!mesh::parseDataPath(command + 17, path, path_len, error)) {
|
|
snprintf(reply, 160, "%s", error ? error : "Err - invalid path");
|
|
return true;
|
|
}
|
|
}
|
|
if (!strncmp(command, "get data.tx", 11) || !strncmp(command, "set data.tx", 11)) {
|
|
strcpy(reply, "route boundary");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#include "production_wrapper.inc"
|
|
|
|
struct Fixture {
|
|
MemoryFS fs;
|
|
mesh::Mesh mesh;
|
|
mesh::MainBoard board;
|
|
SensorManager sensors;
|
|
ClientACL acl;
|
|
NodePrefs prefs;
|
|
CommonCLICallbacks callbacks;
|
|
CommonCLI cli;
|
|
|
|
Fixture() {
|
|
cli._board = &board; cli._sensors = &sensors; cli._acl = &acl;
|
|
cli._prefs = &prefs; cli._callbacks = &callbacks;
|
|
}
|
|
~Fixture() { delete cli._management; }
|
|
void enableContext() { cli._management_mesh = &mesh; cli._management_fs = &fs; }
|
|
};
|
|
|
|
static void password(Fixture& fixture, const std::string& value,
|
|
const char* expected_reply) {
|
|
unsigned char guarded[320]; memset(guarded, 0xa5, sizeof(guarded));
|
|
char* command = reinterpret_cast<char*>(guarded + 8);
|
|
memcpy(command, "set mgmt.password ", 18);
|
|
memcpy(command + 18, value.c_str(), value.size() + 1);
|
|
const size_t length = 18 + value.size();
|
|
char reply[160] = {};
|
|
assert(fixture.cli.handleManagementCommand(command, reply));
|
|
assert(!strcmp(reply, expected_reply));
|
|
assert(!memcmp(command, "set mgmt.password ", 18));
|
|
for (size_t index = 18; index <= length; ++index) {
|
|
if (command[index] != 0) {
|
|
fprintf(stderr, "password byte %zu survived reply '%s'\n", index - 18, reply);
|
|
assert(false);
|
|
}
|
|
}
|
|
for (size_t index = 0; index < 8; ++index) assert(guarded[index] == 0xa5);
|
|
for (size_t index = 8 + length + 1; index < sizeof(guarded); ++index)
|
|
assert(guarded[index] == 0xa5);
|
|
}
|
|
|
|
int main() {
|
|
for (unsigned missing : {0u, 1u, 2u}) {
|
|
Fixture fixture;
|
|
if (missing == 1) fixture.cli._management_mesh = &fixture.mesh;
|
|
if (missing == 2) fixture.cli._management_fs = &fixture.fs;
|
|
for (size_t length : {0u, 5u, 12u, 96u, 97u, 200u})
|
|
password(fixture, std::string(length, 's'), "ERR: management unavailable");
|
|
assert(!fixture.cli._management && fixture.fs.files.empty());
|
|
}
|
|
{
|
|
Fixture fixture; fixture.enableContext(); fail_nothrow_allocation = true;
|
|
const auto before = failed_allocations;
|
|
for (size_t length : {0u, 5u, 12u, 96u, 97u, 200u})
|
|
password(fixture, std::string(length, 's'), "ERR: management unavailable");
|
|
assert(failed_allocations == before + 6 && !fixture.cli._management);
|
|
assert(fixture.fs.files.empty()); fail_nothrow_allocation = false;
|
|
password(fixture, "a real management password", "OK");
|
|
assert(fixture.cli._management && fixture.fs.files.count("/management"));
|
|
}
|
|
{
|
|
Fixture fixture; fixture.enableContext();
|
|
for (size_t length : {0u, 5u, 97u, 200u})
|
|
password(fixture, std::string(length, 's'), "ERR: password must be 12..96 bytes");
|
|
assert(fixture.fs.files.empty());
|
|
password(fixture, "a real management password", "OK");
|
|
fixture.fs.fail_write = true;
|
|
password(fixture, "another management password", "ERR: save failed; reporting stopped");
|
|
fixture.fs.fail_write = false;
|
|
password(fixture, "third management password", "ERR: management state fault; repair storage/reboot first");
|
|
}
|
|
{
|
|
Fixture fixture;
|
|
for (const char* untouched : {"get mgmt", "set mgmt.enabled on", "get mgmt.password",
|
|
"set mgmt.passwordExtra secret", "set mgmt.password\tsecret"}) {
|
|
char command[160], reply[160] = {}; strcpy(command, untouched);
|
|
assert(fixture.cli.handleManagementCommand(command, reply));
|
|
assert(!strcmp(command, untouched));
|
|
assert(!strcmp(reply, "ERR: management unavailable"));
|
|
}
|
|
for (const char* unknown : {"get mgm", "set mgm.password secret", "get management",
|
|
"set management.password secret"}) {
|
|
char command[160], reply[160] = "not handled"; strcpy(command, unknown);
|
|
assert(!fixture.cli.handleManagementCommand(command, reply));
|
|
assert(!strcmp(command, unknown) && !strcmp(reply, "not handled"));
|
|
}
|
|
for (const char* alias : {"get mgmt.path", "set mgmt.path 2:12abcd34"}) {
|
|
char command[160], reply[160] = {}; strcpy(command, alias);
|
|
assert(fixture.cli.handleManagementCommand(command, reply));
|
|
assert(!strcmp(reply, "route boundary"));
|
|
assert(fixture.cli.data_commands.back()
|
|
== (!strncmp(alias, "get", 3) ? "get data.tx path" : "set data.tx path 2:12abcd34"));
|
|
assert(!fixture.cli._management);
|
|
}
|
|
const std::string largest_spec = "1:" + std::string(126, '1') + std::string(14, ' ');
|
|
const std::string largest_alias = "set mgmt.path " + largest_spec;
|
|
assert(largest_alias.size() == 156);
|
|
char boundary[160], boundary_reply[160] = {}; strcpy(boundary, largest_alias.c_str());
|
|
assert(fixture.cli.handleManagementCommand(boundary, boundary_reply));
|
|
assert(!strcmp(boundary_reply, "route boundary"));
|
|
// Exact expanded capacity is accepted, including its null terminator.
|
|
// trimDataRoute mutates the delegated buffer after validation.
|
|
assert(fixture.cli.data_commands.back().size() == 159);
|
|
// This fits the 159-byte ordinary repeater CLI input limit. Expanding the
|
|
// alias prefix by three bytes must not discard malformed suffix bytes and
|
|
// turn an invalid setting into an accepted 63-hop direct route.
|
|
const std::string spec = largest_spec + "bad";
|
|
std::string direct = spec;
|
|
uint8_t path[MAX_PATH_SIZE], path_len = OUT_PATH_UNKNOWN;
|
|
const char* error = nullptr;
|
|
assert(!mesh::parseDataPath(&direct[0], path, path_len, error));
|
|
const std::string alias = "set mgmt.path " + spec;
|
|
assert(alias.size() == 159);
|
|
char command[160], reply[160] = {}; strcpy(command, alias.c_str());
|
|
const auto delegated = fixture.cli.data_commands.size();
|
|
assert(fixture.cli.handleManagementCommand(command, reply));
|
|
if (!strcmp(reply, "route boundary")) {
|
|
fprintf(stderr, "malformed alias accepted after truncation: '%s'\n", reply);
|
|
assert(false);
|
|
}
|
|
assert(!strcmp(reply, "Err - data.tx path too long"));
|
|
// The wrapper probes the original command once before recognizing aliases;
|
|
// rejection must not make a second call with a truncated expanded alias.
|
|
assert(fixture.cli.data_commands.size() == delegated + 1);
|
|
for (size_t excess : {1u, 2u, 100u}) {
|
|
std::string long_alias = largest_alias + std::string(excess, ' ');
|
|
char long_reply[160] = {};
|
|
assert(fixture.cli.handleManagementCommand(&long_alias[0], long_reply));
|
|
assert(!strcmp(long_reply, "Err - data.tx path too long"));
|
|
}
|
|
}
|
|
}
|