Expand full Companion terminal tooling
This commit is contained in:
1 parent
88ba5b26cb
commit
4e91cc9487
10 files changed
+1235
-41
No files matched your search
@@ -29,17 +29,128 @@ enum class RecentRepeaterGetMatch : uint8_t {
|
||||
Invalid,
|
||||
};
|
||||
|
||||
enum class TerminalChannelCommandMatch : uint8_t {
|
||||
NoMatch = 0,
|
||||
Valid,
|
||||
MissingSelector,
|
||||
MissingMessage,
|
||||
};
|
||||
|
||||
enum class TerminalArgumentCommandMatch : uint8_t {
|
||||
NoMatch = 0,
|
||||
Valid,
|
||||
MissingArgument,
|
||||
};
|
||||
|
||||
struct RecentRepeaterGetQuery {
|
||||
int page;
|
||||
uint8_t search_prefix[3];
|
||||
uint8_t search_prefix_len;
|
||||
};
|
||||
|
||||
struct TerminalChannelMessage {
|
||||
const char* selector;
|
||||
size_t selector_len;
|
||||
const char* text;
|
||||
};
|
||||
|
||||
inline const char* skipRecentRepeaterSpaces(const char* text) {
|
||||
while (text != nullptr && (*text == ' ' || *text == '\t')) text++;
|
||||
return text;
|
||||
}
|
||||
|
||||
inline TerminalArgumentCommandMatch parseTerminalArgumentCommand(
|
||||
const char* command, const char* verb, const char*& argument) {
|
||||
argument = nullptr;
|
||||
if (command == nullptr || verb == nullptr || *verb == 0) {
|
||||
return TerminalArgumentCommandMatch::NoMatch;
|
||||
}
|
||||
|
||||
const size_t verb_len = strlen(verb);
|
||||
for (size_t i = 0; i < verb_len; i++) {
|
||||
char actual = command[i];
|
||||
char expected = verb[i];
|
||||
if (actual >= 'A' && actual <= 'Z') actual += 'a' - 'A';
|
||||
if (expected >= 'A' && expected <= 'Z') expected += 'a' - 'A';
|
||||
if (actual != expected) return TerminalArgumentCommandMatch::NoMatch;
|
||||
}
|
||||
if (command[verb_len] != 0 && command[verb_len] != ' '
|
||||
&& command[verb_len] != '\t') {
|
||||
return TerminalArgumentCommandMatch::NoMatch;
|
||||
}
|
||||
|
||||
const char* cursor = skipRecentRepeaterSpaces(command + verb_len);
|
||||
if (*cursor == 0) return TerminalArgumentCommandMatch::MissingArgument;
|
||||
argument = cursor;
|
||||
return TerminalArgumentCommandMatch::Valid;
|
||||
}
|
||||
|
||||
// Return true once terminal input has reached a login password. The caller
|
||||
// can still retain the real bytes for command handling while echoing '*'.
|
||||
inline bool shouldMaskTerminalInput(const char* line) {
|
||||
line = skipRecentRepeaterSpaces(line);
|
||||
const char* password = nullptr;
|
||||
return parseTerminalArgumentCommand(line, "login", password)
|
||||
== TerminalArgumentCommandMatch::Valid;
|
||||
}
|
||||
|
||||
inline TerminalChannelCommandMatch parseTerminalChannelMessage(
|
||||
const char* command, TerminalChannelMessage& message) {
|
||||
message.selector = nullptr;
|
||||
message.selector_len = 0;
|
||||
message.text = nullptr;
|
||||
if (command == nullptr || strncmp(command, "channel", 7) != 0) {
|
||||
return TerminalChannelCommandMatch::NoMatch;
|
||||
}
|
||||
|
||||
const char* cursor = command + 7;
|
||||
if (*cursor != 0 && *cursor != ' ' && *cursor != '\t') {
|
||||
return TerminalChannelCommandMatch::NoMatch;
|
||||
}
|
||||
cursor = skipRecentRepeaterSpaces(cursor);
|
||||
if (*cursor == 0) return TerminalChannelCommandMatch::MissingSelector;
|
||||
|
||||
message.selector = cursor;
|
||||
while (*cursor != 0 && *cursor != ' ' && *cursor != '\t') cursor++;
|
||||
message.selector_len = static_cast<size_t>(cursor - message.selector);
|
||||
cursor = skipRecentRepeaterSpaces(cursor);
|
||||
if (*cursor == 0) return TerminalChannelCommandMatch::MissingMessage;
|
||||
|
||||
message.text = cursor;
|
||||
return TerminalChannelCommandMatch::Valid;
|
||||
}
|
||||
|
||||
inline bool parseTerminalChannelIndex(const TerminalChannelMessage& message,
|
||||
size_t max_channels,
|
||||
size_t& channel_index) {
|
||||
if (message.selector == nullptr || message.selector_len == 0
|
||||
|| max_channels == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t value = 0;
|
||||
for (size_t i = 0; i < message.selector_len; i++) {
|
||||
const char c = message.selector[i];
|
||||
if (c < '0' || c > '9') return false;
|
||||
const size_t digit = static_cast<size_t>(c - '0');
|
||||
if (digit >= max_channels
|
||||
|| value > (max_channels - 1 - digit) / 10) {
|
||||
return false;
|
||||
}
|
||||
value = value * 10 + digit;
|
||||
}
|
||||
if (value >= max_channels) return false;
|
||||
channel_index = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool terminalChannelNameMatches(const TerminalChannelMessage& message,
|
||||
const char* channel_name) {
|
||||
return message.selector != nullptr && channel_name != nullptr
|
||||
&& strlen(channel_name) == message.selector_len
|
||||
&& memcmp(channel_name, message.selector, message.selector_len) == 0;
|
||||
}
|
||||
|
||||
inline int recentRepeaterHexNibble(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
struct RoundTripTracePath {
|
||||
uint8_t hash_size;
|
||||
uint8_t hop_count;
|
||||
size_t byte_len;
|
||||
};
|
||||
|
||||
enum class RawTracePathParseResult : uint8_t {
|
||||
Valid,
|
||||
MissingHashSize,
|
||||
InvalidHashSize,
|
||||
MissingPrefixes,
|
||||
InvalidPrefix,
|
||||
TooManyHops,
|
||||
RouteTooLong,
|
||||
};
|
||||
|
||||
struct RawTracePath {
|
||||
uint8_t hash_size;
|
||||
uint8_t hop_count;
|
||||
size_t byte_len;
|
||||
};
|
||||
|
||||
inline bool isRawTracePathSeparator(char c) {
|
||||
return c == ' ' || c == '\t' || c == ',';
|
||||
}
|
||||
|
||||
inline int rawTraceHexNibble(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse: <hash-size> <prefix>[,<prefix> ...]
|
||||
// Prefix separators may be commas, spaces, tabs, or any mixture of them.
|
||||
// TRACE uses power-of-two hash sizes, so only 1, 2, and 4 are accepted.
|
||||
inline RawTracePathParseResult parseRawTracePath(const char* input,
|
||||
uint8_t* output,
|
||||
size_t output_capacity,
|
||||
uint8_t max_hops,
|
||||
RawTracePath& result) {
|
||||
result.hash_size = 0;
|
||||
result.hop_count = 0;
|
||||
result.byte_len = 0;
|
||||
|
||||
if (input == nullptr) return RawTracePathParseResult::MissingHashSize;
|
||||
while (*input == ' ' || *input == '\t') input++;
|
||||
if (*input == 0) return RawTracePathParseResult::MissingHashSize;
|
||||
|
||||
const char width_char = *input++;
|
||||
if (width_char != '1' && width_char != '2' && width_char != '4') {
|
||||
return RawTracePathParseResult::InvalidHashSize;
|
||||
}
|
||||
if (*input != 0 && !isRawTracePathSeparator(*input)) {
|
||||
return RawTracePathParseResult::InvalidHashSize;
|
||||
}
|
||||
const uint8_t hash_size = static_cast<uint8_t>(width_char - '0');
|
||||
result.hash_size = hash_size;
|
||||
|
||||
while (isRawTracePathSeparator(*input)) input++;
|
||||
if (*input == 0) return RawTracePathParseResult::MissingPrefixes;
|
||||
if (output == nullptr || output_capacity < hash_size) {
|
||||
return RawTracePathParseResult::RouteTooLong;
|
||||
}
|
||||
|
||||
const size_t expected_chars = static_cast<size_t>(hash_size) * 2;
|
||||
size_t offset = 0;
|
||||
uint8_t hop_count = 0;
|
||||
while (*input != 0) {
|
||||
const char* token = input;
|
||||
size_t token_len = 0;
|
||||
while (input[token_len] != 0
|
||||
&& !isRawTracePathSeparator(input[token_len])) {
|
||||
token_len++;
|
||||
}
|
||||
if (token_len != expected_chars) {
|
||||
return RawTracePathParseResult::InvalidPrefix;
|
||||
}
|
||||
if (hop_count >= max_hops) {
|
||||
return RawTracePathParseResult::TooManyHops;
|
||||
}
|
||||
if (offset + hash_size > output_capacity) {
|
||||
return RawTracePathParseResult::RouteTooLong;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < hash_size; i++) {
|
||||
const int high = rawTraceHexNibble(token[i * 2]);
|
||||
const int low = rawTraceHexNibble(token[i * 2 + 1]);
|
||||
if (high < 0 || low < 0) {
|
||||
return RawTracePathParseResult::InvalidPrefix;
|
||||
}
|
||||
output[offset++] = static_cast<uint8_t>((high << 4) | low);
|
||||
}
|
||||
hop_count++;
|
||||
input += token_len;
|
||||
while (isRawTracePathSeparator(*input)) input++;
|
||||
}
|
||||
|
||||
result.hash_size = hash_size;
|
||||
result.hop_count = hop_count;
|
||||
result.byte_len = offset;
|
||||
return RawTracePathParseResult::Valid;
|
||||
}
|
||||
|
||||
// Build the route used by Companion clients for a trace that returns to its
|
||||
// origin: saved path, optional repeater/room endpoint, then the saved path in
|
||||
// reverse. Stored three-byte paths are traced with their first two bytes per
|
||||
// hop because the TRACE flag format has no unambiguous three-byte mode.
|
||||
inline bool buildRoundTripTracePath(const uint8_t* saved_path,
|
||||
uint8_t encoded_path_len,
|
||||
const uint8_t* endpoint_hash,
|
||||
bool include_endpoint,
|
||||
uint8_t* output,
|
||||
size_t output_capacity,
|
||||
RoundTripTracePath& result) {
|
||||
result.hash_size = 0;
|
||||
result.hop_count = 0;
|
||||
result.byte_len = 0;
|
||||
|
||||
const size_t saved_hash_size = (encoded_path_len >> 6) + 1;
|
||||
const size_t saved_hop_count = encoded_path_len & 63;
|
||||
if (saved_hash_size == 0 || saved_hash_size > 3
|
||||
|| (saved_hop_count > 0 && saved_path == nullptr)
|
||||
|| (include_endpoint && endpoint_hash == nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t trace_hash_size = saved_hash_size == 3 ? 2 : saved_hash_size;
|
||||
const size_t route_hop_count = saved_hop_count * 2
|
||||
+ (include_endpoint ? 1 : 0);
|
||||
if (route_hop_count == 0 || route_hop_count > 255
|
||||
|| trace_hash_size > output_capacity
|
||||
|| route_hop_count > output_capacity / trace_hash_size
|
||||
|| output == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t offset = 0;
|
||||
for (size_t i = 0; i < saved_hop_count; i++) {
|
||||
memcpy(&output[offset], &saved_path[i * saved_hash_size], trace_hash_size);
|
||||
offset += trace_hash_size;
|
||||
}
|
||||
if (include_endpoint) {
|
||||
memcpy(&output[offset], endpoint_hash, trace_hash_size);
|
||||
offset += trace_hash_size;
|
||||
}
|
||||
for (size_t i = saved_hop_count; i > 0; i--) {
|
||||
memcpy(&output[offset], &saved_path[(i - 1) * saved_hash_size],
|
||||
trace_hash_size);
|
||||
offset += trace_hash_size;
|
||||
}
|
||||
|
||||
result.hash_size = static_cast<uint8_t>(trace_hash_size);
|
||||
result.hop_count = static_cast<uint8_t>(route_hop_count);
|
||||
result.byte_len = offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline uint8_t traceFlagsForHashSize(uint8_t hash_size) {
|
||||
return hash_size == 1 ? 0
|
||||
: hash_size == 2 ? 1
|
||||
: hash_size == 4 ? 2
|
||||
: 0xFF;
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
Reference in new issue
Block a user