Public Access
LoRa Scanner: MeshCore by default, and its public channel read
The MeshCore preset is the default for a new device, in the Settings and in the radio before the Scanner is opened. The setting now accepts it: it only took the 7 Meshtastic presets, so MeshCore picked in the App was not saved. Messages on MeshCore's public channel are decrypted with the channel's published key (AES-128, checked with 2 bytes of HMAC-SHA256): the row shows who says they sent it and the start of the text, the details the sender, the time and the whole text. Other channels and private messages stay encrypted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
#include "meshcore_channel.h"
|
||||
#include "meshcore_packet.h"
|
||||
#include "meshtastic_header.h"
|
||||
#include "meshtastic_presets.h"
|
||||
@@ -22,14 +24,31 @@ std::string hex(const uint8_t* p, size_t n) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// `room` characters at most: a node's name is cut to fit.
|
||||
// Onto lines of `cols` characters at most, broken at spaces where there are some.
|
||||
void wrapInto(std::vector<std::string>& lines, std::string text, size_t cols) {
|
||||
while (text.size() > cols) {
|
||||
size_t cut = text.rfind(' ', cols);
|
||||
if (cut == std::string::npos || cut == 0) cut = cols;
|
||||
lines.push_back(text.substr(0, cut));
|
||||
text.erase(0, text[cut] == ' ' ? cut + 1 : cut);
|
||||
}
|
||||
if (!text.empty()) lines.push_back(text);
|
||||
}
|
||||
|
||||
// `room` characters at most: a node's name or a message is cut to fit.
|
||||
std::string meshcoreRow(const meshcore::Packet& m, size_t room) {
|
||||
std::string out = meshcore::payloadShort(m.type);
|
||||
std::string hops = m.hops ? " " + std::to_string(m.hops) + "h" : "";
|
||||
meshcore::Advert a;
|
||||
meshcore::Discover d;
|
||||
meshcore::ChannelText t;
|
||||
if (meshcore::parseDiscover(m, d)) {
|
||||
out = d.response ? "here " + hex(d.id, 2) : "who's there?";
|
||||
} else if (meshcore::readChannelText(m, t)) {
|
||||
out = (t.sender.empty() ? "" : t.sender + ": ") + t.text;
|
||||
out = out.substr(0, room > hops.size() ? room - hops.size() : 0);
|
||||
while (!out.empty() && out.back() == ' ') out.pop_back();
|
||||
return out + hops;
|
||||
} else if (meshcore::parseAdvert(m, a)) {
|
||||
size_t used = out.size() + 1 + hops.size();
|
||||
out += " " + (a.name.empty() ? hex(a.key, 2) : a.name.substr(0, room > used ? room - used : 0));
|
||||
@@ -90,6 +109,14 @@ std::vector<std::string> meshcoreLines(const uint8_t* data, size_t len) {
|
||||
} else if (m.addressed() && m.payloadLen >= 4) {
|
||||
std::snprintf(s, sizeof s, "From ..%02x to ..%02x, %u B encrypted", m.payload[1], m.payload[0], static_cast<unsigned>(m.payloadLen - 4));
|
||||
lines.push_back(s);
|
||||
} else if (meshcore::ChannelText t; meshcore::readChannelText(m, t)) {
|
||||
lines.push_back(std::string("On the ") + t.channel + " channel" + (t.sender.empty() ? "" : ", from " + t.sender));
|
||||
std::time_t sent = t.timestamp;
|
||||
std::tm tm{};
|
||||
gmtime_r(&sent, &tm);
|
||||
std::strftime(s, sizeof s, "Sent %Y-%m-%d %H:%M:%S UTC", &tm);
|
||||
lines.push_back(s);
|
||||
wrapInto(lines, t.text, 38);
|
||||
} else if (m.group() && m.payloadLen >= 3) {
|
||||
std::snprintf(s, sizeof s, "Channel 0x%02x%s, %u B encrypted", m.payload[0], m.payload[0] == meshcore::kPublicChannelHash ? " (public?)" : "",
|
||||
static_cast<unsigned>(m.payloadLen - 3));
|
||||
|
||||
@@ -22,14 +22,15 @@ struct PacketSummary {
|
||||
|
||||
// One list row, at most about 36 characters: "21:45:07 -97 6.2 5678>all 1/3". Meshtastic nodes by their
|
||||
// default short name (the last 4 hex digits); headerLines() has the full numbers. MeshCore: what
|
||||
// it is, then who ("adv Brussels-R1", "txt a3>7f", "chan #11", "who's there?", "here 18a6") and the hops in its path ("2h").
|
||||
// it is, then who ("adv Brussels-R1", "txt a3>7f", "chan #11", "who's there?", "here 18a6") and the hops in its path ("2h");
|
||||
// a message on the public channel is read: "Alice: hello fro 2h".
|
||||
std::string row(const PacketSummary& p, const std::string& time);
|
||||
|
||||
// Eight bytes a line, with the printable ones: "0000 ff ff ff ff 78 56 34 12 ....xV4.".
|
||||
std::vector<std::string> hexDump(const uint8_t* data, size_t len);
|
||||
|
||||
// What is in clear, one field a line: the Meshtastic header, or MeshCore's header, path and (for an
|
||||
// advert) who the node says it is. Empty when the packet can't be read as that.
|
||||
// advert) who the node says it is, and a message on its public channel. Empty when the packet can't be read as that.
|
||||
std::vector<std::string> headerLines(const uint8_t* data, size_t len, Protocol protocol = Protocol::Meshtastic);
|
||||
|
||||
} // namespace roro::lora
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
namespace roro::lora {
|
||||
|
||||
// What the LoRa Scanner can be told to listen to by name: the Meshtastic presets allowed in
|
||||
// EU_868, LongFast (the default) first, then MeshCore's EU/UK (Narrow). The order is kept: the
|
||||
// EU_868, LongFast first, then MeshCore's EU/UK (Narrow), the default. The order is kept: the
|
||||
// chosen one is stored by its number.
|
||||
struct ScanPreset {
|
||||
const char* name;
|
||||
@@ -19,6 +19,7 @@ struct ScanPreset {
|
||||
Protocol protocol;
|
||||
};
|
||||
|
||||
constexpr size_t kDefaultScanPreset = 7; // MeshCore: the Settings' default says the same
|
||||
size_t scanPresetCount();
|
||||
ScanPreset scanPreset(size_t i); // i < scanPresetCount()
|
||||
// By name, whatever its case. False when there is none.
|
||||
|
||||
Reference in New Issue
Block a user