mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-02 14:53:44 +00:00
Add the framed BLE mOTA transport and companion control path, complete the full-profile build and runtime fixes, and document and test the OTA workflow.
698 lines
27 KiB
C++
698 lines
27 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>
|
|
#include <helpers/radiolib/RadioPowerLimits.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, ParsesSignedIntegersStrictly) {
|
|
int32_t value = 0;
|
|
|
|
EXPECT_TRUE(mesh::cli::parseIntegerStrict("22", value));
|
|
EXPECT_EQ(22, value);
|
|
EXPECT_TRUE(mesh::cli::parseIntegerStrict(" -9 ", value));
|
|
EXPECT_EQ(-9, value);
|
|
EXPECT_TRUE(mesh::cli::parseIntegerStrict("+12", value));
|
|
EXPECT_EQ(12, value);
|
|
EXPECT_TRUE(mesh::cli::parseIntegerStrict("-2147483648", value));
|
|
EXPECT_EQ(INT32_MIN, value);
|
|
EXPECT_TRUE(mesh::cli::parseIntegerStrict("2147483647", value));
|
|
EXPECT_EQ(INT32_MAX, value);
|
|
}
|
|
|
|
TEST(CLICommandUtils, RejectsInvalidOrOverflowingIntegers) {
|
|
int32_t value = 123;
|
|
|
|
EXPECT_FALSE(mesh::cli::parseIntegerStrict(nullptr, value));
|
|
EXPECT_FALSE(mesh::cli::parseIntegerStrict("", value));
|
|
EXPECT_FALSE(mesh::cli::parseIntegerStrict("-", value));
|
|
EXPECT_FALSE(mesh::cli::parseIntegerStrict("1.0", value));
|
|
EXPECT_FALSE(mesh::cli::parseIntegerStrict("12dBm", value));
|
|
EXPECT_FALSE(mesh::cli::parseIntegerStrict("2147483648", value));
|
|
EXPECT_FALSE(mesh::cli::parseIntegerStrict("-2147483649", value));
|
|
EXPECT_EQ(123, value);
|
|
}
|
|
|
|
TEST(CLICommandUtils, ParsesRadioTuplesWithoutFloatScanf) {
|
|
float frequency = 0.0f;
|
|
float bandwidth = 0.0f;
|
|
uint8_t spreading_factor = 0;
|
|
uint8_t coding_rate = 0;
|
|
uint32_t timeout_minutes = 0;
|
|
|
|
EXPECT_TRUE(mesh::cli::parseRadioTupleStrict(
|
|
"910.525,62.5,7,5", frequency, bandwidth,
|
|
spreading_factor, coding_rate));
|
|
EXPECT_FLOAT_EQ(910.525f, frequency);
|
|
EXPECT_FLOAT_EQ(62.5f, bandwidth);
|
|
EXPECT_EQ(7, spreading_factor);
|
|
EXPECT_EQ(5, coding_rate);
|
|
|
|
EXPECT_TRUE(mesh::cli::parseTemporaryRadioTupleStrict(
|
|
" 909.950 , 250 , 5 , 5 , 120 ", frequency, bandwidth,
|
|
spreading_factor, coding_rate, timeout_minutes));
|
|
EXPECT_FLOAT_EQ(909.950f, frequency);
|
|
EXPECT_FLOAT_EQ(250.0f, bandwidth);
|
|
EXPECT_EQ(5, spreading_factor);
|
|
EXPECT_EQ(5, coding_rate);
|
|
EXPECT_EQ(120u, timeout_minutes);
|
|
}
|
|
|
|
TEST(CLICommandUtils, RejectsMalformedRadioTuples) {
|
|
float frequency = 123.0f;
|
|
float bandwidth = 456.0f;
|
|
uint8_t spreading_factor = 7;
|
|
uint8_t coding_rate = 5;
|
|
uint32_t timeout_minutes = 60;
|
|
|
|
EXPECT_FALSE(mesh::cli::parseRadioTupleStrict(
|
|
nullptr, frequency, bandwidth, spreading_factor, coding_rate));
|
|
EXPECT_FALSE(mesh::cli::parseRadioTupleStrict(
|
|
"910.525,62.5,7", frequency, bandwidth,
|
|
spreading_factor, coding_rate));
|
|
EXPECT_FALSE(mesh::cli::parseRadioTupleStrict(
|
|
"910.525,62.5,7,5,1", frequency, bandwidth,
|
|
spreading_factor, coding_rate));
|
|
EXPECT_FALSE(mesh::cli::parseRadioTupleStrict(
|
|
"910.525x,62.5,7,5", frequency, bandwidth,
|
|
spreading_factor, coding_rate));
|
|
EXPECT_FALSE(mesh::cli::parseRadioTupleStrict(
|
|
"910.525,62.5,256,5", frequency, bandwidth,
|
|
spreading_factor, coding_rate));
|
|
|
|
EXPECT_FALSE(mesh::cli::parseTemporaryRadioTupleStrict(
|
|
"909.950,250,5,5,1,99", frequency, bandwidth,
|
|
spreading_factor, coding_rate, timeout_minutes));
|
|
EXPECT_FALSE(mesh::cli::parseTemporaryRadioTupleStrict(
|
|
"909.950,250,5,5,1x", frequency, bandwidth,
|
|
spreading_factor, coding_rate, timeout_minutes));
|
|
EXPECT_FALSE(mesh::cli::parseTemporaryRadioTupleStrict(
|
|
"909.950,250,5,5,2147483648", frequency, bandwidth,
|
|
spreading_factor, coding_rate, timeout_minutes));
|
|
|
|
EXPECT_FLOAT_EQ(123.0f, frequency);
|
|
EXPECT_FLOAT_EQ(456.0f, bandwidth);
|
|
EXPECT_EQ(7, spreading_factor);
|
|
EXPECT_EQ(5, coding_rate);
|
|
EXPECT_EQ(60u, timeout_minutes);
|
|
}
|
|
|
|
TEST(RadioPowerLimits, LeavesUnspecifiedBackendRangeToDriver) {
|
|
EXPECT_EQ(INT8_MIN, mesh::minLoRaTxPowerForFrequency(915.0f));
|
|
EXPECT_EQ(INT8_MAX, mesh::maxLoRaTxPowerForFrequency(915.0f));
|
|
EXPECT_TRUE(mesh::isLoRaTxPowerValid(INT8_MIN, 915.0f));
|
|
EXPECT_TRUE(mesh::isLoRaTxPowerValid(INT8_MAX, 915.0f));
|
|
EXPECT_FALSE(mesh::isLoRaTxPowerValid(128, 915.0f));
|
|
EXPECT_EQ(INT8_MAX, mesh::clampLoRaTxPower(200, 915.0f));
|
|
}
|
|
|
|
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();
|
|
}
|