mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-22 18:51:16 +00:00
Add full Companion builds and terminal path routing
This commit is contained in:
1 parent
4e91cc9487
commit
f40615ab56
8 files changed
+350
-8
No files matched your search
@@ -88,6 +88,7 @@ Commands:
|
||||
build-full-esp32-logging-firmwares: Build only feature-complete ESP32 profiles with up to 254 neighbors, logging, MQTT disabled, LoRa OTA, and expanded dual-OTA partitions.
|
||||
build-matching-firmwares <build-match-spec>: Build all firmwares for build targets containing the string given for <build-match-spec>.
|
||||
build-companion-firmwares: Build all companion firmwares for all build targets.
|
||||
build-full-companion-firmwares: Build all full Companion firmwares for supported ESP32 and nRF52 targets.
|
||||
build-repeater-firmwares: Build all repeater firmwares with 254 neighbors, except DRAM-limited targets that retain 50.
|
||||
build-room-server-firmwares: Build all chat room server firmwares for all build targets.
|
||||
build-sensor-firmwares: Build all sensor firmwares for all build targets.
|
||||
@@ -127,6 +128,9 @@ $ bash build.sh build-full-esp32-logging-firmwares
|
||||
Build all companion firmwares
|
||||
$ bash build.sh build-companion-firmwares
|
||||
|
||||
Build all full Companion firmwares
|
||||
$ bash build.sh build-full-companion-firmwares
|
||||
|
||||
Build all repeater firmwares
|
||||
$ bash build.sh build-repeater-firmwares
|
||||
|
||||
@@ -475,6 +479,7 @@ prompt_for_build_mode() {
|
||||
"Build all sensor firmwares"
|
||||
"Build only FULL ESP32 MQTT firmwares (all features, MQTT, and LoRa OTA)"
|
||||
"Build only FULL ESP32 logging firmwares (all features, logging, no MQTT, and LoRa OTA)"
|
||||
"Build all full Companion firmwares (all available transports and host-backed LoRa OTA)"
|
||||
)
|
||||
|
||||
echo "No command provided. Select a build action:"
|
||||
@@ -525,6 +530,10 @@ prompt_for_build_mode() {
|
||||
SELECTED_COMMAND_ARGS=(build-full-esp32-logging-firmwares)
|
||||
return 0
|
||||
;;
|
||||
10)
|
||||
SELECTED_COMMAND_ARGS=(build-full-companion-firmwares)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
@@ -2690,6 +2699,17 @@ resolve_companion_firmwares() {
|
||||
get_pio_envs_for_variant_role companion
|
||||
}
|
||||
|
||||
resolve_full_companion_firmwares() {
|
||||
local env_name
|
||||
|
||||
for env_name in "${SUPPORTED_PIO_ENVS[@]}"; do
|
||||
if is_supported_build_env "$env_name" \
|
||||
&& is_companion_radio_full_target "$env_name"; then
|
||||
printf '%s\n' "$env_name"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
resolve_repeater_firmwares() {
|
||||
get_pio_envs_for_variant_role repeater
|
||||
}
|
||||
@@ -2734,6 +2754,9 @@ get_bulk_build_resolver_name() {
|
||||
build-companion-firmwares)
|
||||
echo "resolve_companion_firmwares"
|
||||
;;
|
||||
build-full-companion-firmwares)
|
||||
echo "resolve_full_companion_firmwares"
|
||||
;;
|
||||
build-repeater-firmwares)
|
||||
echo "resolve_repeater_firmwares"
|
||||
;;
|
||||
|
||||
@@ -39,6 +39,14 @@ bash build.sh build-firmware RAK_4631_companion_radio_full \
|
||||
--firmware-version v1.17.0
|
||||
```
|
||||
|
||||
To build every available full Companion target, select the corresponding
|
||||
interactive menu item or run:
|
||||
|
||||
```bash
|
||||
bash build.sh build-full-companion-firmwares \
|
||||
--firmware-version v1.17.0
|
||||
```
|
||||
|
||||
Artifacts are written to `out/` by default.
|
||||
|
||||
On 4 MB ESP32 boards, the full target uses a single 3 MB application
|
||||
@@ -105,16 +113,20 @@ The terminal supports Companion chat commands, including `channels`,
|
||||
channels
|
||||
channel #rgdata Hello from Eugene 👋
|
||||
to Hilltop Repeater
|
||||
path A1B2C3,D4E5F6
|
||||
login my-admin-password
|
||||
cmd ver
|
||||
trace
|
||||
```
|
||||
|
||||
The `to` command selects the remote-administration target. Login passwords are
|
||||
masked during entry and limited by the radio protocol to 15 UTF-8 bytes. Wait
|
||||
for the asynchronous login result before using `cmd`; command replies appear
|
||||
as `CLI -> from <name>`. Remote ACL permissions determine which commands the
|
||||
target accepts.
|
||||
The `to` command selects the remote-administration target. `path` shows its
|
||||
saved outgoing route; `path direct`, `path clear`, or a comma-separated list
|
||||
such as `path A1B2C3,D4E5F6` changes the route used by subsequent `login`,
|
||||
`send`, and `cmd` commands. Every hop must use the same 2-, 4-, or 6-digit
|
||||
hexadecimal width. Login passwords are masked during entry and limited by the
|
||||
radio protocol to 15 UTF-8 bytes. Wait for the asynchronous login result before
|
||||
using `cmd`; command replies appear as `CLI -> from <name>`. Remote ACL
|
||||
permissions determine which commands the target accepts.
|
||||
|
||||
With no argument, `trace` uses the current `to` recipient. A name-prefix
|
||||
argument traces that contact directly without changing the current recipient.
|
||||
|
||||
@@ -132,13 +132,45 @@ to {name-prefix}
|
||||
```
|
||||
Sets the recipient to the _first_ matching contact (in 'list') by the name prefix. (ie. you don't have to type whole name)
|
||||
|
||||
```
|
||||
path
|
||||
```
|
||||
Shows the saved outgoing path for the current `to` recipient. This command and
|
||||
all path changes require a recipient to be selected first.
|
||||
|
||||
```
|
||||
path direct
|
||||
path clear
|
||||
path {hop-hash[,hop-hash...]}
|
||||
```
|
||||
Sets the outgoing path used by subsequent `login`, `send`, and `cmd` commands.
|
||||
`direct` selects a zero-hop route. `clear` forgets the saved route, causing the
|
||||
next operation to use flood routing and allowing normal path discovery to
|
||||
learn a replacement.
|
||||
|
||||
Explicit paths use comma-separated hop hashes. Each hop must contain exactly
|
||||
2, 4, or 6 hexadecimal digits, and every hop in one path must use the same
|
||||
width. Spaces around commas and hexadecimal letter case do not matter. The
|
||||
setting is saved with the selected contact.
|
||||
|
||||
For example:
|
||||
|
||||
```text
|
||||
to Hilltop Repeater
|
||||
path A1B2C3,D4E5F6
|
||||
path
|
||||
login my-admin-password
|
||||
```
|
||||
|
||||
```
|
||||
login {admin-password}
|
||||
```
|
||||
Sends a remote login request to the current recipient. Select a repeater,
|
||||
room, or other remotely managed node with `to {name-prefix}` first. The
|
||||
password is masked with `*` while it is entered and must be 1-15 UTF-8 bytes;
|
||||
longer passwords are rejected instead of truncated.
|
||||
longer passwords are rejected instead of truncated. Login uses the route shown
|
||||
by `path`: a known or explicitly set route is direct, while an unknown route is
|
||||
flooded.
|
||||
|
||||
Login results arrive asynchronously. A successful modern response displays
|
||||
the remote ACL permissions byte and server protocol level. A wrong password,
|
||||
@@ -219,7 +251,8 @@ eventually report a timeout.
|
||||
```
|
||||
reset path
|
||||
```
|
||||
Resets the path to current recipient, for new path discovery.
|
||||
Resets the path to current recipient, for new path discovery. This is retained
|
||||
as an alias for `path clear`.
|
||||
|
||||
```
|
||||
public {text}
|
||||
|
||||
@@ -418,7 +418,8 @@ static bool deserializeContactRecord(
|
||||
memcpy(&c.gps_lon, &in[offset], 4);
|
||||
c.id = mesh::Identity(pub_key);
|
||||
c.shared_secret_valid = false;
|
||||
return c.out_path_len == OUT_PATH_UNKNOWN || c.out_path_len <= MAX_PATH_SIZE;
|
||||
return c.out_path_len == OUT_PATH_UNKNOWN
|
||||
|| mesh::Packet::isValidPathLen(c.out_path_len);
|
||||
}
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
|
||||
@@ -3463,6 +3463,80 @@ ContactInfo* MyMesh::getTerminalRecipient() {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
void MyMesh::printTerminalPath(const ContactInfo& recipient) {
|
||||
Serial.printf(" Path to %s: ", recipient.name);
|
||||
if (recipient.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
Serial.print("unknown (next login uses FLOOD)\r\n");
|
||||
return;
|
||||
}
|
||||
if (!mesh::Packet::isValidPathLen(recipient.out_path_len)) {
|
||||
Serial.print("invalid\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t hash_size = (recipient.out_path_len >> 6) + 1;
|
||||
const uint8_t hop_count = recipient.out_path_len & 63;
|
||||
if (hop_count == 0) {
|
||||
Serial.print("direct (zero hop; login uses DIRECT)\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t hop = 0; hop < hop_count; hop++) {
|
||||
if (hop != 0) Serial.print(',');
|
||||
mesh::Utils::printHex(Serial,
|
||||
&recipient.out_path[(size_t)hop * hash_size],
|
||||
hash_size);
|
||||
}
|
||||
Serial.printf(" (%u %s, %u-byte hashes; login uses DIRECT)\r\n",
|
||||
(unsigned)hop_count, hop_count == 1 ? "hop" : "hops",
|
||||
(unsigned)hash_size);
|
||||
}
|
||||
|
||||
void MyMesh::handleTerminalPath(ContactInfo& recipient,
|
||||
const char* path_spec) {
|
||||
if (path_spec == NULL) {
|
||||
printTerminalPath(recipient);
|
||||
return;
|
||||
}
|
||||
|
||||
mesh::cli::TerminalPath path;
|
||||
const mesh::cli::TerminalPathParseResult parsed =
|
||||
mesh::cli::parseTerminalPath(path_spec, _terminal_tmp_buf,
|
||||
sizeof(recipient.out_path), 63, path);
|
||||
switch (parsed) {
|
||||
case mesh::cli::TerminalPathParseResult::Valid:
|
||||
break;
|
||||
case mesh::cli::TerminalPathParseResult::Missing:
|
||||
Serial.print(" ERROR: use path <direct|clear|hop[,hop...]>\r\n");
|
||||
return;
|
||||
case mesh::cli::TerminalPathParseResult::InvalidPrefix:
|
||||
Serial.print(" ERROR: each path hop must be 2, 4, or 6 hex digits\r\n");
|
||||
return;
|
||||
case mesh::cli::TerminalPathParseResult::MixedPrefixSize:
|
||||
Serial.print(" ERROR: all path hops must use the same width\r\n");
|
||||
return;
|
||||
case mesh::cli::TerminalPathParseResult::InvalidSeparator:
|
||||
Serial.print(" ERROR: separate path hops with commas\r\n");
|
||||
return;
|
||||
case mesh::cli::TerminalPathParseResult::TooManyHops:
|
||||
Serial.print(" ERROR: path must contain at most 63 hops\r\n");
|
||||
return;
|
||||
case mesh::cli::TerminalPathParseResult::RouteTooLong:
|
||||
Serial.print(" ERROR: path is too long\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memset(recipient.out_path, 0, sizeof(recipient.out_path));
|
||||
if (path.mode == mesh::cli::TerminalPathMode::Clear) {
|
||||
recipient.out_path_len = OUT_PATH_UNKNOWN;
|
||||
} else {
|
||||
recipient.out_path_len = mesh::Packet::copyPath(
|
||||
recipient.out_path, _terminal_tmp_buf, path.encoded_len);
|
||||
}
|
||||
scheduleContactWrite(recipient);
|
||||
printTerminalPath(recipient);
|
||||
}
|
||||
|
||||
void MyMesh::rememberTerminalAck(ContactInfo& recipient, const char* text,
|
||||
uint32_t message_timestamp, uint32_t expected_ack,
|
||||
uint32_t est_timeout,
|
||||
@@ -3818,6 +3892,9 @@ void MyMesh::handleTerminalCommand(char* command) {
|
||||
const mesh::cli::TerminalArgumentCommandMatch command_match =
|
||||
mesh::cli::parseTerminalArgumentCommand(command, "cmd",
|
||||
remote_command);
|
||||
const char* path_spec = NULL;
|
||||
const mesh::cli::TerminalArgumentCommandMatch path_match =
|
||||
mesh::cli::parseTerminalArgumentCommand(command, "path", path_spec);
|
||||
|
||||
if (strcmp(command, "channels") == 0) {
|
||||
listTerminalChannels();
|
||||
@@ -3924,6 +4001,17 @@ void MyMesh::handleTerminalCommand(char* command) {
|
||||
} else {
|
||||
Serial.print(" No recipient selected\r\n");
|
||||
}
|
||||
} else if (path_match
|
||||
!= mesh::cli::TerminalArgumentCommandMatch::NoMatch) {
|
||||
ContactInfo* recipient = getTerminalRecipient();
|
||||
if (recipient == NULL) {
|
||||
Serial.print(" ERROR: no recipient selected (use 'to' first)\r\n");
|
||||
} else {
|
||||
handleTerminalPath(
|
||||
*recipient,
|
||||
path_match == mesh::cli::TerminalArgumentCommandMatch::Valid
|
||||
? path_spec : NULL);
|
||||
}
|
||||
} else if (login_match
|
||||
!= mesh::cli::TerminalArgumentCommandMatch::NoMatch) {
|
||||
ContactInfo* recipient = getTerminalRecipient();
|
||||
@@ -4040,6 +4128,7 @@ void MyMesh::handleTerminalCommand(char* command) {
|
||||
Serial.print(" time <epoch-seconds>\r\n");
|
||||
Serial.print(" list [n]\r\n");
|
||||
Serial.print(" to [recipient name or prefix]\r\n");
|
||||
Serial.print(" path [direct|clear|hop[,hop...]]\r\n");
|
||||
Serial.print(" send <text>\r\n");
|
||||
Serial.print(" login <admin-password>\r\n");
|
||||
Serial.print(" cmd <remote-command>\r\n");
|
||||
|
||||
@@ -274,6 +274,8 @@ private:
|
||||
void checkSerialInterface();
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
ContactInfo* getTerminalRecipient();
|
||||
void printTerminalPath(const ContactInfo& recipient);
|
||||
void handleTerminalPath(ContactInfo& recipient, const char* path_spec);
|
||||
void importTerminalCard(char* command);
|
||||
void listTerminalChannels();
|
||||
void sendTerminalChannelMessage(ChannelDetails& channel, const char* text);
|
||||
|
||||
@@ -42,6 +42,22 @@ enum class TerminalArgumentCommandMatch : uint8_t {
|
||||
MissingArgument,
|
||||
};
|
||||
|
||||
enum class TerminalPathMode : uint8_t {
|
||||
Explicit = 0,
|
||||
Direct,
|
||||
Clear,
|
||||
};
|
||||
|
||||
enum class TerminalPathParseResult : uint8_t {
|
||||
Valid = 0,
|
||||
Missing,
|
||||
InvalidPrefix,
|
||||
MixedPrefixSize,
|
||||
InvalidSeparator,
|
||||
TooManyHops,
|
||||
RouteTooLong,
|
||||
};
|
||||
|
||||
struct RecentRepeaterGetQuery {
|
||||
int page;
|
||||
uint8_t search_prefix[3];
|
||||
@@ -54,6 +70,14 @@ struct TerminalChannelMessage {
|
||||
const char* text;
|
||||
};
|
||||
|
||||
struct TerminalPath {
|
||||
TerminalPathMode mode;
|
||||
uint8_t encoded_len;
|
||||
uint8_t hash_size;
|
||||
uint8_t hop_count;
|
||||
size_t byte_len;
|
||||
};
|
||||
|
||||
inline const char* skipRecentRepeaterSpaces(const char* text) {
|
||||
while (text != nullptr && (*text == ' ' || *text == '\t')) text++;
|
||||
return text;
|
||||
@@ -158,6 +182,99 @@ inline int recentRepeaterHexNibble(char c) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline bool terminalPathKeywordMatches(const char* text,
|
||||
const char* keyword) {
|
||||
const size_t keyword_len = strlen(keyword);
|
||||
if (strncmp(text, keyword, keyword_len) != 0) return false;
|
||||
text = skipRecentRepeaterSpaces(text + keyword_len);
|
||||
return *text == 0;
|
||||
}
|
||||
|
||||
// Parse: direct | clear | <hop>[,<hop> ...]
|
||||
// Each explicit hop is a one-, two-, or three-byte hexadecimal prefix. All
|
||||
// hops must use the same width because that width is encoded once for the
|
||||
// complete MeshCore direct path.
|
||||
inline TerminalPathParseResult parseTerminalPath(
|
||||
const char* input, uint8_t* output, size_t output_capacity,
|
||||
uint8_t max_hops, TerminalPath& result) {
|
||||
result.mode = TerminalPathMode::Explicit;
|
||||
result.encoded_len = 0;
|
||||
result.hash_size = 0;
|
||||
result.hop_count = 0;
|
||||
result.byte_len = 0;
|
||||
|
||||
input = skipRecentRepeaterSpaces(input);
|
||||
if (input == nullptr || *input == 0) {
|
||||
return TerminalPathParseResult::Missing;
|
||||
}
|
||||
if (terminalPathKeywordMatches(input, "direct")) {
|
||||
result.mode = TerminalPathMode::Direct;
|
||||
return TerminalPathParseResult::Valid;
|
||||
}
|
||||
if (terminalPathKeywordMatches(input, "clear")) {
|
||||
result.mode = TerminalPathMode::Clear;
|
||||
return TerminalPathParseResult::Valid;
|
||||
}
|
||||
|
||||
uint8_t hash_size = 0;
|
||||
uint8_t hop_count = 0;
|
||||
size_t offset = 0;
|
||||
while (*input != 0) {
|
||||
input = skipRecentRepeaterSpaces(input);
|
||||
const char* token = input;
|
||||
size_t token_len = 0;
|
||||
while (input[token_len] != 0 && input[token_len] != ','
|
||||
&& input[token_len] != ' ' && input[token_len] != '\t') {
|
||||
token_len++;
|
||||
}
|
||||
if (token_len != 2 && token_len != 4 && token_len != 6) {
|
||||
return TerminalPathParseResult::InvalidPrefix;
|
||||
}
|
||||
|
||||
const uint8_t token_hash_size = static_cast<uint8_t>(token_len / 2);
|
||||
if (hash_size == 0) {
|
||||
hash_size = token_hash_size;
|
||||
result.hash_size = hash_size;
|
||||
} else if (token_hash_size != hash_size) {
|
||||
return TerminalPathParseResult::MixedPrefixSize;
|
||||
}
|
||||
if (hop_count >= max_hops) {
|
||||
return TerminalPathParseResult::TooManyHops;
|
||||
}
|
||||
if (output == nullptr || offset + hash_size > output_capacity) {
|
||||
return TerminalPathParseResult::RouteTooLong;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < hash_size; i++) {
|
||||
const int high = recentRepeaterHexNibble(token[i * 2]);
|
||||
const int low = recentRepeaterHexNibble(token[i * 2 + 1]);
|
||||
if (high < 0 || low < 0) {
|
||||
return TerminalPathParseResult::InvalidPrefix;
|
||||
}
|
||||
output[offset++] = static_cast<uint8_t>((high << 4) | low);
|
||||
}
|
||||
hop_count++;
|
||||
input += token_len;
|
||||
input = skipRecentRepeaterSpaces(input);
|
||||
if (*input == 0) break;
|
||||
if (*input != ',') {
|
||||
return TerminalPathParseResult::InvalidSeparator;
|
||||
}
|
||||
input = skipRecentRepeaterSpaces(input + 1);
|
||||
if (*input == 0) {
|
||||
return TerminalPathParseResult::InvalidPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
result.mode = TerminalPathMode::Explicit;
|
||||
result.encoded_len = static_cast<uint8_t>(
|
||||
((hash_size - 1) << 6) | (hop_count & 63));
|
||||
result.hash_size = hash_size;
|
||||
result.hop_count = hop_count;
|
||||
result.byte_len = offset;
|
||||
return TerminalPathParseResult::Valid;
|
||||
}
|
||||
|
||||
inline bool parseRecentRepeaterPage(const char* text, int& page) {
|
||||
text = skipRecentRepeaterSpaces(text);
|
||||
if (text == nullptr || *text == 0) {
|
||||
|
||||
@@ -122,6 +122,71 @@ TEST(CLICommandUtils, ParsesTerminalLoginAndRemoteCommandArguments) {
|
||||
"logins password", "login", argument));
|
||||
}
|
||||
|
||||
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, 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::InvalidSeparator,
|
||||
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, MasksOnlyTerminalLoginPasswordInput) {
|
||||
EXPECT_FALSE(mesh::cli::shouldMaskTerminalInput("login"));
|
||||
EXPECT_FALSE(mesh::cli::shouldMaskTerminalInput("login "));
|
||||
|
||||
Reference in new issue
Block a user