LoRa Scanner: MeshCore by default, and its public channel read
CI / build (pull_request) Successful in 2m19s
Site / build (pull_request) Successful in 9s

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:
2026-10-09 22:19:14 +02:00
co-authored by Claude Opus 5.5
parent 3339d9ee59
commit 305818466f
18 changed files with 344 additions and 21 deletions
+64 -1
View File
@@ -4,6 +4,8 @@
#include <string>
#include <vector>
#include "aes128.h"
#include "meshcore_channel.h"
#include "meshcore_packet.h"
#include "packet_view.h"
#include "scan_presets.h"
@@ -187,9 +189,67 @@ void test_discover_as_heard() {
TEST_ASSERT_EQUAL_STRING("It heard the search at 11.2 dB SNR", lines[4].c_str());
}
void test_aes128_decrypts_the_standard_block() { // FIPS 197, appendix C.1
uint8_t key[16], in[16] = {0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30, 0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a}, out[16];
for (int i = 0; i < 16; ++i) key[i] = static_cast<uint8_t>(i);
Aes128(key).decryptBlock(in, out);
for (int i = 0; i < 16; ++i) TEST_ASSERT_EQUAL_HEX8(i * 0x11, out[i]);
}
// Messages on the public channel, relayed twice, encrypted and signed with openssl:
// "Alice: hello from Brussels" and "Zo\xc3\xa9: \xc3\xa7a va ?", both sent at 1791575000.
static const uint8_t kHello[] = {0x15, 0x02, 0xa3, 0x7f, 0x11, 0x20, 0x14, 0x90, 0xb7, 0xe1, 0x1d, 0x46, 0x5f,
0x3f, 0xac, 0x4f, 0x98, 0xa8, 0xc6, 0x07, 0x79, 0x4c, 0x96, 0x19, 0x5e, 0x54,
0x60, 0x67, 0x2b, 0x2e, 0xec, 0x0e, 0xe4, 0x6a, 0xbc, 0x8b, 0x51, 0x38, 0xfd};
static const uint8_t kAccents[] = {0x15, 0x02, 0xa3, 0x7f, 0x11, 0x19, 0xdd, 0x85, 0x58, 0xc5, 0x73, 0x2d, 0x55,
0x4d, 0xf4, 0xfd, 0x02, 0x47, 0xdc, 0xfa, 0xa2, 0xce, 0x3b, 0xcb, 0xb1, 0x8e,
0x8f, 0x66, 0xc7, 0xd6, 0x61, 0x30, 0x3c, 0xbe, 0x29, 0xd7, 0xb8, 0x08, 0x26};
void test_public_channel_message() {
TEST_ASSERT_EQUAL_HEX8(kPublicChannelHash, channelHash(channel(0)));
Packet p;
ChannelText t;
TEST_ASSERT_TRUE(parsePacket(kHello, sizeof kHello, p));
TEST_ASSERT_TRUE(readChannelText(p, t));
TEST_ASSERT_EQUAL_STRING("Public", t.channel);
TEST_ASSERT_EQUAL_UINT32(1791575000, t.timestamp);
TEST_ASSERT_EQUAL_STRING("Alice", t.sender.c_str());
TEST_ASSERT_EQUAL_STRING("hello from Brussels", t.text.c_str());
TEST_ASSERT_TRUE(parsePacket(kAccents, sizeof kAccents, p));
TEST_ASSERT_TRUE(readChannelText(p, t));
TEST_ASSERT_EQUAL_STRING("Zo?", t.sender.c_str()); // one '?' for a character outside ASCII
TEST_ASSERT_EQUAL_STRING("?a va ?", t.text.c_str());
// One bit wrong and the check fails: nothing is made up from it.
std::vector<uint8_t> bad(kHello, kHello + sizeof kHello);
bad[20] ^= 1;
TEST_ASSERT_TRUE(parsePacket(bad.data(), bad.size(), p));
TEST_ASSERT_FALSE(readChannelText(p, t));
// Another channel with the same first byte, or not whole blocks: left encrypted.
bad.assign(kHello, kHello + sizeof kHello - 1);
TEST_ASSERT_TRUE(parsePacket(bad.data(), bad.size(), p));
TEST_ASSERT_FALSE(readChannelText(p, t));
}
void test_public_channel_message_shown() {
lora::PacketSummary s{kHello, sizeof kHello, -97, 6.2f, true, lora::Protocol::MeshCore};
std::string row = lora::row(s, "21:45:07");
TEST_ASSERT_EQUAL_STRING("21:45:07 -97 6.2 Alice: hello from 2h", row.c_str());
TEST_ASSERT_LESS_OR_EQUAL(38, row.size());
auto lines = lora::headerLines(kHello, sizeof kHello, lora::Protocol::MeshCore);
TEST_ASSERT_EQUAL_size_t(5, lines.size());
TEST_ASSERT_EQUAL_STRING("MeshCore channel message, flood", lines[0].c_str());
TEST_ASSERT_EQUAL_STRING("Through a3 7f", lines[1].c_str());
TEST_ASSERT_EQUAL_STRING("On the Public channel, from Alice", lines[2].c_str());
TEST_ASSERT_EQUAL_STRING("Sent 2026-10-09 19:43:20 UTC", lines[3].c_str());
TEST_ASSERT_EQUAL_STRING("hello from Brussels", lines[4].c_str());
}
void test_scan_presets() {
TEST_ASSERT_EQUAL_size_t(8, lora::scanPresetCount());
lora::ScanPreset p = lora::scanPreset(0); // the default stays first: it is stored by number
TEST_ASSERT_EQUAL_STRING("MeshCore", lora::scanPreset(lora::kDefaultScanPreset).name);
lora::ScanPreset p = lora::scanPreset(0); // the order stays: a preset is stored by number
TEST_ASSERT_EQUAL_STRING("LongFast", p.name);
TEST_ASSERT_EQUAL_UINT32(869525000u, p.frequencyHz);
TEST_ASSERT_EQUAL_HEX8(0x2B, p.syncWord);
@@ -218,6 +278,9 @@ int main() {
RUN_TEST(test_rows);
RUN_TEST(test_detail_lines);
RUN_TEST(test_discover_as_heard);
RUN_TEST(test_aes128_decrypts_the_standard_block);
RUN_TEST(test_public_channel_message);
RUN_TEST(test_public_channel_message_shown);
RUN_TEST(test_scan_presets);
return UNITY_END();
}