mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-02 15:03:44 +00:00
Expose the Full Companion terminal over TCP and add an authenticated, replay-resistant host command service with documented CPU temperature, reboot, and allowlisted program examples.\n\nImprove LoRa OTA diagnostics and persistent CLI handling, reduce clock correction drift to ten minutes, and make mOTA stream flush behavior an explicit transport policy with regression coverage.
596 lines
23 KiB
C++
596 lines
23 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <helpers/CLICommandUtils.h>
|
|
#include <helpers/ContactListOrder.h>
|
|
#include <helpers/TerminalCommandTracker.h>
|
|
#include <helpers/TerminalDisplayFilter.h>
|
|
#include <helpers/WiFiPowerSave.h>
|
|
|
|
TEST(ContactListOrder, SortsFavoritesBeforeNewerNonFavorites) {
|
|
EXPECT_LT(mesh::compareContactListOrder(0x01, 100, 0x00, 200), 0);
|
|
EXPECT_GT(mesh::compareContactListOrder(0x00, 200, 0x01, 100), 0);
|
|
}
|
|
|
|
TEST(ContactListOrder, SortsEachGroupByNewestAdvertisement) {
|
|
EXPECT_LT(mesh::compareContactListOrder(0x01, 200, 0x01, 100), 0);
|
|
EXPECT_GT(mesh::compareContactListOrder(0x00, 100, 0x00, 200), 0);
|
|
EXPECT_EQ(0, mesh::compareContactListOrder(0x01, 100, 0x01, 100));
|
|
}
|
|
|
|
TEST(CLICommandUtils, LabelsInboundDirectRouteAsRouted) {
|
|
EXPECT_STREQ("ROUTED", mesh::cli::terminalInboundRouteLabel(true));
|
|
EXPECT_STREQ("FLOOD", mesh::cli::terminalInboundRouteLabel(false));
|
|
}
|
|
|
|
TEST(TerminalDisplayFilter, UsesQuietDefaultsWithEmergencyVisible) {
|
|
mesh::TerminalDisplayFilter filter;
|
|
|
|
EXPECT_FALSE(filter.shouldShowAdvert());
|
|
EXPECT_FALSE(filter.shouldShowChannel(false));
|
|
EXPECT_TRUE(filter.shouldShowChannel(true));
|
|
}
|
|
|
|
TEST(TerminalDisplayFilter, KeepsChannelAndEmergencyControlsIndependent) {
|
|
mesh::TerminalDisplayFilter filter;
|
|
|
|
filter.setEnabled(mesh::TerminalDisplayCategory::Channels, true);
|
|
filter.setEnabled(mesh::TerminalDisplayCategory::Emergency, false);
|
|
filter.setEnabled(mesh::TerminalDisplayCategory::Adverts, true);
|
|
|
|
EXPECT_TRUE(filter.shouldShowAdvert());
|
|
EXPECT_TRUE(filter.shouldShowChannel(false));
|
|
EXPECT_FALSE(filter.shouldShowChannel(true));
|
|
}
|
|
|
|
TEST(TerminalDisplayFilter, ParsesStatusAndUpdatesCaseInsensitively) {
|
|
using mesh::TerminalDisplayCategory;
|
|
using mesh::TerminalDisplayParseResult;
|
|
mesh::TerminalDisplayCommand command;
|
|
|
|
EXPECT_EQ(TerminalDisplayParseResult::StatusAll,
|
|
mesh::parseTerminalDisplayCommand(NULL, command));
|
|
EXPECT_EQ(TerminalDisplayParseResult::StatusOne,
|
|
mesh::parseTerminalDisplayCommand(" channels ", command));
|
|
EXPECT_EQ(TerminalDisplayCategory::Channels, command.category);
|
|
EXPECT_EQ(TerminalDisplayParseResult::Updated,
|
|
mesh::parseTerminalDisplayCommand(" Emergency OFF ", command));
|
|
EXPECT_EQ(TerminalDisplayCategory::Emergency, command.category);
|
|
EXPECT_FALSE(command.enabled);
|
|
EXPECT_EQ(TerminalDisplayParseResult::Updated,
|
|
mesh::parseTerminalDisplayCommand("advertisements on", command));
|
|
EXPECT_EQ(TerminalDisplayCategory::Adverts, command.category);
|
|
EXPECT_TRUE(command.enabled);
|
|
}
|
|
|
|
TEST(TerminalDisplayFilter, RejectsUnknownCategoriesAndValues) {
|
|
mesh::TerminalDisplayCommand command;
|
|
|
|
EXPECT_EQ(mesh::TerminalDisplayParseResult::InvalidCategory,
|
|
mesh::parseTerminalDisplayCommand("messages on", command));
|
|
EXPECT_EQ(mesh::TerminalDisplayParseResult::InvalidValue,
|
|
mesh::parseTerminalDisplayCommand("channels maybe", command));
|
|
EXPECT_EQ(mesh::TerminalDisplayParseResult::InvalidValue,
|
|
mesh::parseTerminalDisplayCommand("emergency off extra", command));
|
|
}
|
|
|
|
TEST(TerminalCommandTracker, MatchesReplyAndReportsRoundTrip) {
|
|
mesh::TerminalCommandTracker<4> tracker;
|
|
const uint8_t target[] = {0x11, 0x22, 0x33, 0x44};
|
|
const uint8_t other[] = {0x11, 0x22, 0x33, 0x45};
|
|
uint32_t elapsed = 99;
|
|
|
|
ASSERT_TRUE(tracker.begin(target, 1000, 3000));
|
|
EXPECT_FALSE(tracker.begin(target, 1100, 3000));
|
|
EXPECT_FALSE(tracker.takeReply(other, 1500, elapsed));
|
|
EXPECT_EQ(0UL, elapsed);
|
|
EXPECT_TRUE(tracker.isPending());
|
|
EXPECT_TRUE(tracker.takeReply(target, 1750, elapsed));
|
|
EXPECT_EQ(750UL, elapsed);
|
|
EXPECT_FALSE(tracker.isPending());
|
|
}
|
|
|
|
TEST(TerminalCommandTracker, ExpiresSafelyAcrossMillisRollover) {
|
|
mesh::TerminalCommandTracker<4> tracker;
|
|
const uint8_t target[] = {0xAA, 0xBB, 0xCC, 0xDD};
|
|
uint32_t elapsed = 0;
|
|
|
|
ASSERT_TRUE(tracker.begin(target, 0xFFFFFFF0UL, 32));
|
|
EXPECT_FALSE(tracker.expire(0x0000000FUL, elapsed));
|
|
EXPECT_TRUE(tracker.expire(0x00000010UL, elapsed));
|
|
EXPECT_EQ(32UL, elapsed);
|
|
EXPECT_FALSE(tracker.isPending());
|
|
}
|
|
|
|
TEST(CLICommandUtils, NormalizesCapitalizedSetVerb) {
|
|
char command[] = "Set altpath 600000,0d2784,F8DADA";
|
|
|
|
mesh::cli::normalizeCommandVerb(command);
|
|
|
|
EXPECT_STREQ("set altpath 600000,0d2784,F8DADA", command);
|
|
}
|
|
|
|
TEST(CLICommandUtils, NormalizesAnyCommandVerbCase) {
|
|
char get_command[] = "GET outpath";
|
|
char clear_command[] = "cLeAr recent.repeater";
|
|
|
|
mesh::cli::normalizeCommandVerb(get_command);
|
|
mesh::cli::normalizeCommandVerb(clear_command);
|
|
|
|
EXPECT_STREQ("get outpath", get_command);
|
|
EXPECT_STREQ("clear recent.repeater", clear_command);
|
|
}
|
|
|
|
TEST(CLICommandUtils, PreservesCaseSensitiveArguments) {
|
|
char command[] = "SET name RidgeNode-A";
|
|
|
|
mesh::cli::normalizeCommandVerb(command);
|
|
|
|
EXPECT_STREQ("set name RidgeNode-A", command);
|
|
}
|
|
|
|
TEST(CLICommandUtils, HandlesLeadingWhitespaceAndSingleWordCommands) {
|
|
char spaced_command[] = " Get outpath";
|
|
char single_command[] = "PowerSaving";
|
|
|
|
mesh::cli::normalizeCommandVerb(spaced_command);
|
|
mesh::cli::normalizeCommandVerb(single_command);
|
|
|
|
EXPECT_STREQ(" get outpath", spaced_command);
|
|
EXPECT_STREQ("powersaving", single_command);
|
|
}
|
|
|
|
TEST(CLICommandUtils, ParsesTerminalChannelMessagesWithUtf8Text) {
|
|
using mesh::cli::TerminalChannelCommandMatch;
|
|
mesh::cli::TerminalChannelMessage message;
|
|
|
|
EXPECT_EQ(TerminalChannelCommandMatch::Valid,
|
|
mesh::cli::parseTerminalChannelMessage(
|
|
"channel #rgdata Hello from Eugene 👋", message));
|
|
EXPECT_EQ(7u, message.selector_len);
|
|
EXPECT_EQ(0, memcmp("#rgdata", message.selector, message.selector_len));
|
|
EXPECT_STREQ("Hello from Eugene 👋", message.text);
|
|
EXPECT_TRUE(mesh::cli::terminalChannelNameMatches(message, "#rgdata"));
|
|
EXPECT_FALSE(mesh::cli::terminalChannelNameMatches(message, "#RGDATA"));
|
|
}
|
|
|
|
TEST(CLICommandUtils, ParsesTerminalChannelSlotsStrictly) {
|
|
using mesh::cli::TerminalChannelCommandMatch;
|
|
mesh::cli::TerminalChannelMessage message;
|
|
size_t channel_index = 99;
|
|
|
|
EXPECT_EQ(TerminalChannelCommandMatch::Valid,
|
|
mesh::cli::parseTerminalChannelMessage(
|
|
"channel 2 Slot message", message));
|
|
EXPECT_TRUE(mesh::cli::parseTerminalChannelIndex(message, 8,
|
|
channel_index));
|
|
EXPECT_EQ(2u, channel_index);
|
|
|
|
EXPECT_EQ(TerminalChannelCommandMatch::Valid,
|
|
mesh::cli::parseTerminalChannelMessage(
|
|
"channel 8 Out of range", message));
|
|
EXPECT_FALSE(mesh::cli::parseTerminalChannelIndex(message, 8,
|
|
channel_index));
|
|
|
|
EXPECT_EQ(TerminalChannelCommandMatch::Valid,
|
|
mesh::cli::parseTerminalChannelMessage(
|
|
"channel 2name Named channel", message));
|
|
EXPECT_FALSE(mesh::cli::parseTerminalChannelIndex(message, 8,
|
|
channel_index));
|
|
EXPECT_TRUE(mesh::cli::terminalChannelNameMatches(message, "2name"));
|
|
}
|
|
|
|
TEST(CLICommandUtils, RejectsIncompleteTerminalChannelMessages) {
|
|
using mesh::cli::TerminalChannelCommandMatch;
|
|
mesh::cli::TerminalChannelMessage message;
|
|
|
|
EXPECT_EQ(TerminalChannelCommandMatch::MissingSelector,
|
|
mesh::cli::parseTerminalChannelMessage("channel", message));
|
|
EXPECT_EQ(TerminalChannelCommandMatch::MissingMessage,
|
|
mesh::cli::parseTerminalChannelMessage("channel #rgdata", message));
|
|
EXPECT_EQ(TerminalChannelCommandMatch::NoMatch,
|
|
mesh::cli::parseTerminalChannelMessage("channels", message));
|
|
EXPECT_EQ(TerminalChannelCommandMatch::NoMatch,
|
|
mesh::cli::parseTerminalChannelMessage("channelized test", message));
|
|
}
|
|
|
|
TEST(CLICommandUtils, ParsesTerminalLoginAndRemoteCommandArguments) {
|
|
using mesh::cli::TerminalArgumentCommandMatch;
|
|
const char* argument = nullptr;
|
|
|
|
EXPECT_EQ(TerminalArgumentCommandMatch::Valid,
|
|
mesh::cli::parseTerminalArgumentCommand(
|
|
"login admin password", "login", argument));
|
|
EXPECT_STREQ("admin password", argument);
|
|
|
|
EXPECT_EQ(TerminalArgumentCommandMatch::Valid,
|
|
mesh::cli::parseTerminalArgumentCommand(
|
|
"LOGIN CaseSensitivePassword", "login", argument));
|
|
EXPECT_STREQ("CaseSensitivePassword", argument);
|
|
|
|
EXPECT_EQ(TerminalArgumentCommandMatch::Valid,
|
|
mesh::cli::parseTerminalArgumentCommand(
|
|
"cmd\tget stats", "cmd", argument));
|
|
EXPECT_STREQ("get stats", argument);
|
|
|
|
EXPECT_EQ(TerminalArgumentCommandMatch::MissingArgument,
|
|
mesh::cli::parseTerminalArgumentCommand(
|
|
"login ", "login", argument));
|
|
EXPECT_EQ(nullptr, argument);
|
|
EXPECT_EQ(TerminalArgumentCommandMatch::NoMatch,
|
|
mesh::cli::parseTerminalArgumentCommand(
|
|
"logins password", "login", argument));
|
|
}
|
|
|
|
TEST(CLICommandUtils, ParsesCapitalizedRecipientWithRepeatedWhitespace) {
|
|
using mesh::cli::TerminalArgumentCommandMatch;
|
|
char command[] = "To SEA Mercerwood";
|
|
const char* recipient = nullptr;
|
|
|
|
mesh::cli::normalizeCommandVerb(command);
|
|
|
|
EXPECT_STREQ("to SEA Mercerwood", command);
|
|
EXPECT_EQ(TerminalArgumentCommandMatch::Valid,
|
|
mesh::cli::parseTerminalArgumentCommand(
|
|
command, "to", recipient));
|
|
EXPECT_STREQ("SEA Mercerwood", recipient);
|
|
}
|
|
|
|
TEST(CLICommandUtils, ParsesTerminalDirectAndExplicitPaths) {
|
|
using mesh::cli::TerminalPathMode;
|
|
using mesh::cli::TerminalPathParseResult;
|
|
uint8_t route[64] = {};
|
|
mesh::cli::TerminalPath path;
|
|
|
|
EXPECT_EQ(TerminalPathParseResult::Valid,
|
|
mesh::cli::parseTerminalPath(
|
|
"direct", route, sizeof(route), 63, path));
|
|
EXPECT_EQ(TerminalPathMode::Direct, path.mode);
|
|
EXPECT_EQ(0, path.encoded_len);
|
|
EXPECT_EQ(0, path.hop_count);
|
|
EXPECT_EQ(0u, path.byte_len);
|
|
|
|
EXPECT_EQ(TerminalPathParseResult::Valid,
|
|
mesh::cli::parseTerminalPath(
|
|
"clear ", route, sizeof(route), 63, path));
|
|
EXPECT_EQ(TerminalPathMode::Clear, path.mode);
|
|
|
|
EXPECT_EQ(TerminalPathParseResult::Valid,
|
|
mesh::cli::parseTerminalPath(
|
|
" A1B2C3, d4e5f6,010203 ", route, sizeof(route), 63,
|
|
path));
|
|
EXPECT_EQ(TerminalPathMode::Explicit, path.mode);
|
|
EXPECT_EQ(3, path.hash_size);
|
|
EXPECT_EQ(3, path.hop_count);
|
|
EXPECT_EQ(0x83, path.encoded_len);
|
|
EXPECT_EQ(9u, path.byte_len);
|
|
const uint8_t expected[] = {
|
|
0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6, 0x01, 0x02, 0x03,
|
|
};
|
|
EXPECT_EQ(0, memcmp(expected, route, sizeof(expected)));
|
|
}
|
|
|
|
TEST(CLICommandUtils, ParsesSpaceSeparatedTerminalPath) {
|
|
using mesh::cli::TerminalPathMode;
|
|
using mesh::cli::TerminalPathParseResult;
|
|
uint8_t route[6] = {};
|
|
mesh::cli::TerminalPath path;
|
|
|
|
ASSERT_EQ(TerminalPathParseResult::Valid,
|
|
mesh::cli::parseTerminalPath(
|
|
"7773D0 7E7662", route, sizeof(route), 63, path));
|
|
|
|
const uint8_t expected[] = {0x77, 0x73, 0xD0, 0x7E, 0x76, 0x62};
|
|
EXPECT_EQ(TerminalPathMode::Explicit, path.mode);
|
|
EXPECT_EQ(3, path.hash_size);
|
|
EXPECT_EQ(2, path.hop_count);
|
|
EXPECT_EQ(0x82, path.encoded_len);
|
|
EXPECT_EQ(sizeof(expected), path.byte_len);
|
|
EXPECT_EQ(0, memcmp(expected, route, sizeof(expected)));
|
|
}
|
|
|
|
TEST(CLICommandUtils, RejectsMalformedTerminalPaths) {
|
|
using mesh::cli::TerminalPathParseResult;
|
|
uint8_t route[4] = {};
|
|
mesh::cli::TerminalPath path;
|
|
|
|
EXPECT_EQ(TerminalPathParseResult::Missing,
|
|
mesh::cli::parseTerminalPath(
|
|
nullptr, route, sizeof(route), 63, path));
|
|
EXPECT_EQ(TerminalPathParseResult::Missing,
|
|
mesh::cli::parseTerminalPath(
|
|
" ", route, sizeof(route), 63, path));
|
|
EXPECT_EQ(TerminalPathParseResult::InvalidPrefix,
|
|
mesh::cli::parseTerminalPath(
|
|
"GG", route, sizeof(route), 63, path));
|
|
EXPECT_EQ(TerminalPathParseResult::InvalidPrefix,
|
|
mesh::cli::parseTerminalPath(
|
|
"AA,", route, sizeof(route), 63, path));
|
|
EXPECT_EQ(TerminalPathParseResult::MixedPrefixSize,
|
|
mesh::cli::parseTerminalPath(
|
|
"AA,BBBB", route, sizeof(route), 63, path));
|
|
EXPECT_EQ(TerminalPathParseResult::InvalidPrefix,
|
|
mesh::cli::parseTerminalPath(
|
|
"AA,,BB", route, sizeof(route), 63, path));
|
|
EXPECT_EQ(TerminalPathParseResult::TooManyHops,
|
|
mesh::cli::parseTerminalPath(
|
|
"AA,BB", route, sizeof(route), 1, path));
|
|
EXPECT_EQ(TerminalPathParseResult::RouteTooLong,
|
|
mesh::cli::parseTerminalPath(
|
|
"A1B2C3,D4E5F6", route, sizeof(route), 63, path));
|
|
}
|
|
|
|
TEST(CLICommandUtils, MasksTerminalPasswordInput) {
|
|
EXPECT_FALSE(mesh::cli::shouldMaskTerminalInput("login"));
|
|
EXPECT_FALSE(mesh::cli::shouldMaskTerminalInput("login "));
|
|
EXPECT_TRUE(mesh::cli::shouldMaskTerminalInput("login s"));
|
|
EXPECT_TRUE(mesh::cli::shouldMaskTerminalInput("LOGIN s"));
|
|
EXPECT_TRUE(mesh::cli::shouldMaskTerminalInput(" login secret phrase"));
|
|
EXPECT_FALSE(mesh::cli::shouldMaskTerminalInput("set wifi.pwd"));
|
|
EXPECT_FALSE(mesh::cli::shouldMaskTerminalInput("set wifi.pwd "));
|
|
EXPECT_TRUE(mesh::cli::shouldMaskTerminalInput("set wifi.pwd s"));
|
|
EXPECT_TRUE(mesh::cli::shouldMaskTerminalInput("SET WIFI.PWD secret phrase"));
|
|
EXPECT_TRUE(mesh::cli::shouldMaskTerminalInput(" set wifi.pwd secret"));
|
|
EXPECT_FALSE(mesh::cli::shouldMaskTerminalInput("cmd login secret"));
|
|
EXPECT_FALSE(mesh::cli::shouldMaskTerminalInput("cmd set wifi.pwd secret"));
|
|
EXPECT_FALSE(mesh::cli::shouldMaskTerminalInput("set wifi.pwd-status secret"));
|
|
EXPECT_FALSE(mesh::cli::shouldMaskTerminalInput("login-status"));
|
|
|
|
const char* login = " login secret phrase";
|
|
EXPECT_STREQ("secret phrase", mesh::cli::terminalPasswordInput(login));
|
|
const char* wifi = "set wifi.pwd secret phrase";
|
|
EXPECT_STREQ("secret phrase", mesh::cli::terminalPasswordInput(wifi));
|
|
EXPECT_EQ(nullptr, mesh::cli::terminalPasswordInput("set wifi.pwd "));
|
|
}
|
|
|
|
TEST(CLICommandUtils, BackspaceErasesAsciiAndUtf8Characters) {
|
|
char ascii[] = "trace path 2 7773";
|
|
size_t length = strlen(ascii);
|
|
|
|
length = mesh::cli::eraseLastTerminalInput(ascii, length);
|
|
EXPECT_STREQ("trace path 2 777", ascii);
|
|
EXPECT_EQ(strlen(ascii), length);
|
|
|
|
char utf8[] = "channel Public hi 👋";
|
|
length = strlen(utf8);
|
|
length = mesh::cli::eraseLastTerminalInput(utf8, length);
|
|
EXPECT_STREQ("channel Public hi ", utf8);
|
|
EXPECT_EQ(strlen(utf8), length);
|
|
|
|
EXPECT_EQ(0u, mesh::cli::eraseLastTerminalInput(nullptr, 0));
|
|
EXPECT_EQ(0u, mesh::cli::eraseLastTerminalInput(utf8, 0));
|
|
}
|
|
|
|
TEST(CLICommandUtils, ParsesStrictDecimalValues) {
|
|
float value = 0.0f;
|
|
|
|
EXPECT_TRUE(mesh::cli::parseDecimalStrict("910.525", value));
|
|
EXPECT_FLOAT_EQ(910.525f, value);
|
|
EXPECT_TRUE(mesh::cli::parseDecimalStrict(" -122.25 ", value));
|
|
EXPECT_FLOAT_EQ(-122.25f, value);
|
|
EXPECT_TRUE(mesh::cli::parseDecimalStrict("+.5", value));
|
|
EXPECT_FLOAT_EQ(0.5f, value);
|
|
EXPECT_TRUE(mesh::cli::parseDecimalStrict("7.", value));
|
|
EXPECT_FLOAT_EQ(7.0f, value);
|
|
}
|
|
|
|
TEST(CLICommandUtils, RejectsNonDecimalOrOverflowValues) {
|
|
float value = 123.0f;
|
|
|
|
EXPECT_FALSE(mesh::cli::parseDecimalStrict(nullptr, value));
|
|
EXPECT_FALSE(mesh::cli::parseDecimalStrict("", value));
|
|
EXPECT_FALSE(mesh::cli::parseDecimalStrict(".", value));
|
|
EXPECT_FALSE(mesh::cli::parseDecimalStrict("1.2x", value));
|
|
EXPECT_FALSE(mesh::cli::parseDecimalStrict("1e3", value));
|
|
EXPECT_FALSE(mesh::cli::parseDecimalStrict("4294967296", value));
|
|
EXPECT_FLOAT_EQ(123.0f, value);
|
|
}
|
|
|
|
TEST(CLICommandUtils, ClassifiesStandaloneWiFiGetKeysExactly) {
|
|
using mesh::cli::StandaloneWiFiKey;
|
|
|
|
EXPECT_EQ(StandaloneWiFiKey::SSID,
|
|
mesh::cli::classifyStandaloneWiFiGet("wifi.ssid"));
|
|
EXPECT_EQ(StandaloneWiFiKey::Status,
|
|
mesh::cli::classifyStandaloneWiFiGet("wifi.status"));
|
|
EXPECT_EQ(StandaloneWiFiKey::PowerSave,
|
|
mesh::cli::classifyStandaloneWiFiGet("wifi.powersave"));
|
|
EXPECT_EQ(StandaloneWiFiKey::CLI,
|
|
mesh::cli::classifyStandaloneWiFiGet("wifi.cli"));
|
|
EXPECT_EQ(StandaloneWiFiKey::None,
|
|
mesh::cli::classifyStandaloneWiFiGet("wifi.pwd"));
|
|
EXPECT_EQ(StandaloneWiFiKey::None,
|
|
mesh::cli::classifyStandaloneWiFiGet("wifi.status.extra"));
|
|
}
|
|
|
|
TEST(CLICommandUtils, ClassifiesStandaloneWiFiSetKeysAndPreservesValues) {
|
|
using mesh::cli::StandaloneWiFiKey;
|
|
const char* value = nullptr;
|
|
|
|
EXPECT_EQ(StandaloneWiFiKey::SSID,
|
|
mesh::cli::classifyStandaloneWiFiSet(
|
|
"wifi.ssid Slow Fi", &value));
|
|
EXPECT_STREQ("Slow Fi", value);
|
|
|
|
EXPECT_EQ(StandaloneWiFiKey::Password,
|
|
mesh::cli::classifyStandaloneWiFiSet("wifi.pwd", &value));
|
|
EXPECT_STREQ("", value);
|
|
|
|
EXPECT_EQ(StandaloneWiFiKey::PowerSave,
|
|
mesh::cli::classifyStandaloneWiFiSet(
|
|
"wifi.powersave max", &value));
|
|
EXPECT_STREQ("max", value);
|
|
|
|
EXPECT_EQ(StandaloneWiFiKey::CLI,
|
|
mesh::cli::classifyStandaloneWiFiSet("wifi.cli on", &value));
|
|
EXPECT_STREQ("on", value);
|
|
|
|
EXPECT_EQ(StandaloneWiFiKey::None,
|
|
mesh::cli::classifyStandaloneWiFiSet(
|
|
"wifi.powersaver max", &value));
|
|
EXPECT_EQ(nullptr, value);
|
|
}
|
|
|
|
TEST(CLICommandUtils, ValidatesStandaloneWiFiValues) {
|
|
EXPECT_TRUE(mesh::cli::standaloneWiFiSSIDValid("SlowFi"));
|
|
EXPECT_TRUE(mesh::cli::standaloneWiFiSSIDValid(
|
|
"1234567890123456789012345678901"));
|
|
EXPECT_FALSE(mesh::cli::standaloneWiFiSSIDValid(""));
|
|
EXPECT_FALSE(mesh::cli::standaloneWiFiSSIDValid(
|
|
"12345678901234567890123456789012"));
|
|
|
|
EXPECT_TRUE(mesh::cli::standaloneWiFiPasswordValid(""));
|
|
EXPECT_TRUE(mesh::cli::standaloneWiFiPasswordValid(
|
|
"123456789012345678901234567890123456789012345678901234567890123"));
|
|
EXPECT_FALSE(mesh::cli::standaloneWiFiPasswordValid(
|
|
"1234567890123456789012345678901234567890123456789012345678901234"));
|
|
|
|
uint8_t power_save = 99;
|
|
EXPECT_TRUE(mesh::cli::parseStandaloneWiFiPowerSave("min", power_save));
|
|
EXPECT_EQ(0, power_save);
|
|
EXPECT_TRUE(mesh::cli::parseStandaloneWiFiPowerSave("none", power_save));
|
|
EXPECT_EQ(1, power_save);
|
|
EXPECT_TRUE(mesh::cli::parseStandaloneWiFiPowerSave("max", power_save));
|
|
EXPECT_EQ(2, power_save);
|
|
EXPECT_FALSE(mesh::cli::parseStandaloneWiFiPowerSave("off", power_save));
|
|
}
|
|
|
|
TEST(CLICommandUtils, EnforcesWiFiSleepForBluetoothCoexistence) {
|
|
EXPECT_EQ(mesh::wifi::kPowerSaveMin,
|
|
mesh::wifi::effectivePowerSave(mesh::wifi::kPowerSaveMin, true));
|
|
EXPECT_EQ(mesh::wifi::kPowerSaveMin,
|
|
mesh::wifi::effectivePowerSave(mesh::wifi::kPowerSaveNone, true));
|
|
EXPECT_EQ(mesh::wifi::kPowerSaveMax,
|
|
mesh::wifi::effectivePowerSave(mesh::wifi::kPowerSaveMax, true));
|
|
EXPECT_EQ(mesh::wifi::kPowerSaveNone,
|
|
mesh::wifi::effectivePowerSave(mesh::wifi::kPowerSaveNone, false));
|
|
EXPECT_EQ(mesh::wifi::kPowerSaveMin,
|
|
mesh::wifi::effectivePowerSave(99, true));
|
|
EXPECT_EQ(mesh::wifi::kPowerSaveNone,
|
|
mesh::wifi::effectivePowerSave(99, false));
|
|
}
|
|
|
|
TEST(CLICommandUtils, DefaultsWiFiPowerSaveToNoneWithoutProfileOverride) {
|
|
EXPECT_EQ(mesh::wifi::kPowerSaveNone, mesh::wifi::kDefaultPowerSave);
|
|
}
|
|
|
|
TEST(CLICommandUtils, MatchesDiscoverNeighborsWithoutPrefixCollisions) {
|
|
using mesh::cli::NoArgCommandMatch;
|
|
|
|
EXPECT_EQ(NoArgCommandMatch::Exact,
|
|
mesh::cli::matchNoArgCommand(
|
|
"discover.neighbors", "discover.neighbors"));
|
|
EXPECT_EQ(NoArgCommandMatch::Exact,
|
|
mesh::cli::matchNoArgCommand(
|
|
"discover.neighbors ", "discover.neighbors"));
|
|
EXPECT_EQ(NoArgCommandMatch::HasArguments,
|
|
mesh::cli::matchNoArgCommand(
|
|
"discover.neighbors now", "discover.neighbors"));
|
|
EXPECT_EQ(NoArgCommandMatch::NoMatch,
|
|
mesh::cli::matchNoArgCommand(
|
|
"discover.neighbors.extra", "discover.neighbors"));
|
|
}
|
|
|
|
TEST(CLICommandUtils, ParsesRecentRepeaterListAndPageQueries) {
|
|
using mesh::cli::RecentRepeaterGetMatch;
|
|
mesh::cli::RecentRepeaterGetQuery query;
|
|
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Valid,
|
|
mesh::cli::parseRecentRepeaterGet("recent.repeater", query));
|
|
EXPECT_EQ(1, query.page);
|
|
EXPECT_EQ(0, query.search_prefix_len);
|
|
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Valid,
|
|
mesh::cli::parseRecentRepeaterGet(
|
|
"recent.repeaters page 17", query));
|
|
EXPECT_EQ(17, query.page);
|
|
EXPECT_EQ(0, query.search_prefix_len);
|
|
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Valid,
|
|
mesh::cli::parseRecentRepeaterGet("recent.repeaters 0", query));
|
|
EXPECT_EQ(1, query.page);
|
|
}
|
|
|
|
TEST(CLICommandUtils, ParsesRecentRepeaterHexSearchesAndOptionalPages) {
|
|
using mesh::cli::RecentRepeaterGetMatch;
|
|
mesh::cli::RecentRepeaterGetQuery query;
|
|
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Valid,
|
|
mesh::cli::parseRecentRepeaterGet(
|
|
"recent.repeaters search 86", query));
|
|
ASSERT_EQ(1, query.search_prefix_len);
|
|
EXPECT_EQ(0x86, query.search_prefix[0]);
|
|
EXPECT_EQ(1, query.page);
|
|
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Valid,
|
|
mesh::cli::parseRecentRepeaterGet(
|
|
"recent.repeaters search 860c 2", query));
|
|
ASSERT_EQ(2, query.search_prefix_len);
|
|
EXPECT_EQ(0x86, query.search_prefix[0]);
|
|
EXPECT_EQ(0x0c, query.search_prefix[1]);
|
|
EXPECT_EQ(2, query.page);
|
|
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Valid,
|
|
mesh::cli::parseRecentRepeaterGet(
|
|
"recent.repeater search 860CCA page 3", query));
|
|
ASSERT_EQ(3, query.search_prefix_len);
|
|
EXPECT_EQ(0x86, query.search_prefix[0]);
|
|
EXPECT_EQ(0x0c, query.search_prefix[1]);
|
|
EXPECT_EQ(0xca, query.search_prefix[2]);
|
|
EXPECT_EQ(3, query.page);
|
|
}
|
|
|
|
TEST(CLICommandUtils, RejectsMalformedRecentRepeaterSearches) {
|
|
using mesh::cli::RecentRepeaterGetMatch;
|
|
mesh::cli::RecentRepeaterGetQuery query;
|
|
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Invalid,
|
|
mesh::cli::parseRecentRepeaterGet(
|
|
"recent.repeaters search", query));
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Invalid,
|
|
mesh::cli::parseRecentRepeaterGet(
|
|
"recent.repeaters search 860", query));
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Invalid,
|
|
mesh::cli::parseRecentRepeaterGet(
|
|
"recent.repeaters search 8G", query));
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Invalid,
|
|
mesh::cli::parseRecentRepeaterGet(
|
|
"recent.repeaters search 860C extra", query));
|
|
EXPECT_EQ(RecentRepeaterGetMatch::Invalid,
|
|
mesh::cli::parseRecentRepeaterGet(
|
|
"recent.repeaters nope", query));
|
|
EXPECT_EQ(RecentRepeaterGetMatch::NoMatch,
|
|
mesh::cli::parseRecentRepeaterGet(
|
|
"recent.repeaters.extra", query));
|
|
}
|
|
|
|
TEST(CLICommandUtils, FormatsRecentRepeaterAgeWithCompactSuffix) {
|
|
char age[12];
|
|
|
|
mesh::cli::formatRecentRepeaterAge(age, sizeof(age), 0);
|
|
EXPECT_STREQ("0s", age);
|
|
mesh::cli::formatRecentRepeaterAge(age, sizeof(age), 59);
|
|
EXPECT_STREQ("59s", age);
|
|
mesh::cli::formatRecentRepeaterAge(age, sizeof(age), 60);
|
|
EXPECT_STREQ("1m", age);
|
|
mesh::cli::formatRecentRepeaterAge(age, sizeof(age), 3599);
|
|
EXPECT_STREQ("59m", age);
|
|
mesh::cli::formatRecentRepeaterAge(age, sizeof(age), 3600);
|
|
EXPECT_STREQ("1h", age);
|
|
mesh::cli::formatRecentRepeaterAge(age, sizeof(age), 26UL * 3600UL);
|
|
EXPECT_STREQ("26h", age);
|
|
}
|
|
|
|
TEST(CLICommandUtils, FormatsUsefulUnknownSettingErrors) {
|
|
char reply[64] = "";
|
|
|
|
mesh::cli::formatUnknownSetting(reply, sizeof(reply), "wifi.typo");
|
|
|
|
EXPECT_STREQ("Error: unknown setting: wifi.typo", reply);
|
|
EXPECT_EQ(nullptr, strstr(reply, "??:"));
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|