Files
MeshCore/test/test_companion_channel_alias.py
T

1078 lines
54 KiB
Python

"""Execute Companion channel aliases with independent SHA-256 test vectors."""
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import tempfile
import unittest
from cpp_source import body
ROOT = Path(__file__).resolve().parents[1]
HASH_NAMES = (
"#emergency", "#r2-emergency", "#mt-emergency",
"#custom", "#r2-custom", "#mt-custom",
"#EMERGENCY", "#Emergency", "emergency", "Emergency", "#emergency-test",
)
HARNESS = r'''
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <initializer_list>
#include <helpers/CompanionChannelAlias.h>
struct DigestVector { const char* text; uint8_t digest[32]; };
static const DigestVector vectors[] = { @VECTORS@ };
struct KeyDigestVector { uint8_t key[32]; size_t length; uint8_t digest[32]; };
static const KeyDigestVector key_vectors[] = { @KEY_VECTORS@ };
static void sha256(uint8_t* out, size_t out_length, const uint8_t* in, int in_length) {
assert(out && in && in_length >= 0 && out_length <= 32);
for (const auto& vector : vectors) {
if (strlen(vector.text) == static_cast<size_t>(in_length)
&& !memcmp(vector.text, in, in_length)) {
memcpy(out, vector.digest, out_length);
return;
}
}
for (const auto& vector : key_vectors) {
if (vector.length == static_cast<size_t>(in_length) && !memcmp(vector.key, in, in_length)) {
memcpy(out, vector.digest, out_length);
return;
}
}
assert(!"unexpected input to independent SHA-256 oracle");
}
static void hashtagKey(const char* name, uint8_t out[32]) {
memset(out, 0, 32);
sha256(out, 16, reinterpret_cast<const uint8_t*>(name), strlen(name));
}
static void assertAlias(const char* name, CompanionChannelKind kind,
bool hashtag, const char* tail) {
const auto parsed = parseCompanionChannelAlias(name);
assert(parsed.kind == kind);
assert(parsed.hashtag == hashtag);
assert(parsed.tail && !strcmp(parsed.tail, tail));
}
static void grammar() {
assertAlias("", CompanionChannelKind::MeshCore, false, "");
assert(parseCompanionChannelAlias(nullptr).kind == CompanionChannelKind::Invalid);
assertAlias("Public", CompanionChannelKind::MeshCore, false, "Public");
assertAlias("#emergency", CompanionChannelKind::MeshCore, true, "emergency");
assertAlias("r2-Public", CompanionChannelKind::Radio2, false, "Public");
assertAlias("#r2-emergency", CompanionChannelKind::Radio2, true, "emergency");
assertAlias("mt-LongFast", CompanionChannelKind::Meshtext, false, "LongFast");
assertAlias("#mt-emergency", CompanionChannelKind::Meshtext, true, "emergency");
// Routing markers must be complete, leading and case-sensitive.
assertAlias("r2", CompanionChannelKind::MeshCore, false, "r2");
assertAlias("mt", CompanionChannelKind::MeshCore, false, "mt");
assertAlias("R2-Public", CompanionChannelKind::MeshCore, false, "R2-Public");
assertAlias("#MT-emergency", CompanionChannelKind::MeshCore, true, "MT-emergency");
assertAlias("room-r2-Public", CompanionChannelKind::MeshCore, false, "room-r2-Public");
for (const auto* name : {"r2-", "#r2-", "mt-", "#mt-",
"r2-#room", "mt-#room", "#r2-#room", "#mt-#room",
"r2-r2-room", "r2-mt-room", "mt-r2-room", "mt-mt-room",
"#r2-r2-room", "#r2-mt-room", "#mt-r2-room", "#mt-mt-room"}) {
assert(parseCompanionChannelAlias(name).kind == CompanionChannelKind::Invalid);
}
puts("PASS grammar");
}
static void canonical() {
struct Case { const char* display; const char* canonical; };
const Case cases[] = {{"Public", "Public"}, {"#emergency", "#emergency"},
{"r2-Public", "Public"}, {"#r2-emergency", "#emergency"},
{"mt-LongFast", "LongFast"}, {"#mt-emergency", "#emergency"}};
for (const auto& item : cases) {
char output[40];
assert(companionChannelCanonicalName(item.display, output, sizeof(output)));
assert(!strcmp(output, item.canonical));
// The exact terminating-NUL boundary must be accepted, never truncated.
assert(companionChannelCanonicalName(item.display, output, strlen(item.canonical) + 1));
assert(!strcmp(output, item.canonical));
assert(!companionChannelCanonicalName(item.display, output, strlen(item.canonical)));
}
char output[40];
assert(!companionChannelCanonicalName("#mt-", output, sizeof(output)));
assert(!companionChannelCanonicalName("#r2-mt-room", output, sizeof(output)));
assert(!companionChannelCanonicalName("#r2-emergency", nullptr, 40));
assert(!companionChannelCanonicalName(nullptr, output, sizeof(output)));
assert(!companionChannelCanonicalName("#r2-emergency", output, 0));
puts("PASS canonical");
}
static void keys() {
uint8_t canonical[32], alias[32], key[32], before[32];
hashtagKey("#emergency", canonical);
// Fixed published emergency vector also checks the Python-generated oracle.
const uint8_t emergency[16] = {0xe1,0xad,0x57,0x8d,0x25,0x10,0x8e,0x34,
0x48,0x08,0xf3,0x0d,0xfd,0xaa,0xf9,0x26};
assert(!memcmp(canonical, emergency, sizeof(emergency)));
for (const auto* name : {"#r2-emergency", "#mt-emergency"}) {
hashtagKey(name, alias);
memcpy(key, alias, 32);
resolveCompanionChannelKey(name, key, sha256);
assert(!memcmp(key, canonical, 32));
// A second resolution of a wire copy must not derive a new key.
resolveCompanionChannelKey(name, key, sha256);
assert(!memcmp(key, canonical, 32));
assert(memcmp(alias, canonical, 16));
}
hashtagKey("#custom", canonical);
for (const auto* name : {"#r2-custom", "#mt-custom"}) {
hashtagKey(name, key);
resolveCompanionChannelKey(name, key, sha256);
assert(!memcmp(key, canonical, 32));
}
// Unprefixed hashtag keys and non-hashtag channel keys retain exact bytes.
hashtagKey("#emergency", key); memcpy(before, key, 32);
resolveCompanionChannelKey("#emergency", key, sha256);
assert(!memcmp(key, before, 32));
for (const auto* name : {"Public", "r2-Public", "mt-LongFast",
"#r2-emergency", "#mt-emergency", "#r2-mt-room"}) {
for (size_t i = 0; i < 16; ++i) key[i] = static_cast<uint8_t>(i + 1);
memset(key + 16, 0, 16); memcpy(before, key, 32);
resolveCompanionChannelKey(name, key, sha256);
assert(!memcmp(key, before, 32));
// A 256-bit custom PSK whose first half happens to equal the alias hash
// must remain a 256-bit key, rather than being replaced by a public key.
hashtagKey("#r2-emergency", key);
for (size_t i = 16; i < 32; ++i) key[i] = static_cast<uint8_t>(i + 1);
memcpy(before, key, 32);
resolveCompanionChannelKey(name, key, sha256);
assert(!memcmp(key, before, 32));
}
puts("PASS keys");
}
static void receive() {
for (const auto* name : {"Public", "#emergency"}) {
assert(companionChannelReceivePriority(name, 0) == 0);
assert(companionChannelReceivePriority(name, 1) == 1);
assert(companionChannelReceivePriority(name, 0, false) == 0);
assert(companionChannelReceivePriority(name, 1, false) == 1);
}
for (const auto* name : {"r2-Public", "#r2-emergency"}) {
assert(companionChannelReceivePriority(name, 0) == -1);
assert(companionChannelReceivePriority(name, 1) == 0);
assert(companionChannelReceivePriority(name, 0, false) == 0);
assert(companionChannelReceivePriority(name, 1, false) == 1);
}
for (const auto* name : {"mt-LongFast", "#mt-emergency"}) {
assert(companionChannelReceivePriority(name, 0) == -1);
assert(companionChannelReceivePriority(name, 1) == -1);
// Without a configured radio2, either prefix behaves as ordinary MeshCore.
assert(companionChannelReceivePriority(name, 0, false) == 0);
assert(companionChannelReceivePriority(name, 1, false) == 1);
}
for (const auto* name : {"#mt-", "#r2-mt-emergency"}) {
for (uint8_t origin : {0, 1}) {
assert(companionChannelReceivePriority(name, origin, true) == -1);
assert(companionChannelReceivePriority(name, origin, false) == -1);
}
}
puts("PASS receive");
}
int main(int argc, char** argv) {
assert(argc == 2);
if (!strcmp(argv[1], "grammar")) grammar();
else if (!strcmp(argv[1], "canonical")) canonical();
else if (!strcmp(argv[1], "keys")) keys();
else if (!strcmp(argv[1], "receive")) receive();
else assert(!"unknown test case");
}
'''
RUNTIME_HARNESS = r'''
#include <RadioProfiles.h>
#define MAX_GROUP_CHANNELS 5
#define MAX_PACKET_PAYLOAD 255
namespace mesh {
struct GroupChannel { uint8_t secret[32] = {}, hash[1] = {}, tx_radio = RADIO_TX_AUTO; };
struct Packet { uint8_t radio_origin = 0, tx_radio = RADIO_TX_AUTO;
uint8_t payload[MAX_PACKET_PAYLOAD] = {}; int payload_len = 0; };
struct Utils {
static void sha256(uint8_t* out, size_t length, const uint8_t* in, int in_length) {
::sha256(out, length, in, in_length);
}
static bool isZeroes(const uint8_t* bytes, size_t length) {
for (size_t i = 0; i < length; ++i) if (bytes[i]) return false;
return true;
}
// The native test exercises the caller's authenticated-key gating, while
// substituting only the cipher primitive with an explicit key-match oracle.
static int MACThenDecrypt(const uint8_t* key, uint8_t*, const uint8_t* data, int length) {
return length == 16 && !memcmp(key, data, 16) ? 1 : 0;
}
};
}
struct ChannelDetails { mesh::GroupChannel channel; char name[32] = {}; };
struct Radio { mesh::RadioProfiles config; bool present = true;
mesh::RadioProfiles* profiles() { return present ? &config : nullptr; } };
class BaseChatMesh {
public:
ChannelDetails stored[MAX_GROUP_CHANNELS]; unsigned delivered = 0;
bool getChannel(int index, ChannelDetails& channel) {
if (index < 0 || index >= MAX_GROUP_CHANNELS) return false;
channel = stored[index]; return true;
}
bool setChannel(int index, const ChannelDetails& channel) {
if (index < 0 || index >= MAX_GROUP_CHANNELS) return false;
stored[index] = channel;
mesh::Utils::sha256(stored[index].channel.hash, sizeof(stored[index].channel.hash),
channel.channel.secret,
mesh::Utils::isZeroes(channel.channel.secret + 16, 16) ? 16 : 32);
return true;
}
void onGroupDataRecv(mesh::Packet*, uint8_t, const mesh::GroupChannel&, uint8_t*, size_t) {
++delivered;
}
};
class MyMesh : public BaseChatMesh {
public:
Radio radio; Radio* _radio = &radio;
bool isAliasRadio2Defined() const;
bool resolveChannelAlias(ChannelDetails&) const;
bool getChannel(int, ChannelDetails&);
bool setChannel(int, const ChannelDetails&);
int searchChannelsForPacket(const mesh::Packet*, const uint8_t*, mesh::GroupChannel[], int);
int findChannelIdxForPacket(const mesh::GroupChannel&, const mesh::Packet*);
bool allowGroupPacketOnRadio(mesh::Packet*);
void onGroupDataRecv(mesh::Packet*, uint8_t, const mesh::GroupChannel&, uint8_t*, size_t);
@SAVE_GETTER@
};
@METHODS@
static ChannelDetails channel(const char* name) {
ChannelDetails value;
strcpy(value.name, name);
hashtagKey(name, value.channel.secret);
return value;
}
int main() {
MyMesh node;
node.radio.config.secondary.mode = mesh::RadioProfileMode::RxTx;
auto ordinary = channel("#emergency");
auto secondary = channel("#r2-emergency");
auto mt = channel("#mt-emergency");
ordinary.channel.tx_radio = mesh::RADIO_TX_BOTH;
secondary.channel.tx_radio = mesh::RADIO_TX_PRIMARY;
mt.channel.tx_radio = mesh::RADIO_TX_BOTH;
assert(node.setChannel(0, ordinary));
assert(node.setChannel(1, secondary));
assert(node.setChannel(2, mt));
ChannelDetails wire, raw;
assert(node.getChannel(1, wire));
assert(wire.channel.tx_radio == mesh::RADIO_TX_SECONDARY);
assert(!memcmp(wire.channel.secret, ordinary.channel.secret, 32));
uint8_t expected_hash;
sha256(&expected_hash, 1, ordinary.channel.secret, 16);
assert(wire.channel.hash[0] == expected_hash);
assert(node.getChannelForSave(1, raw));
assert(!strcmp(raw.name, secondary.name));
assert(!memcmp(raw.channel.secret, secondary.channel.secret, 32));
assert(raw.channel.tx_radio == mesh::RADIO_TX_PRIMARY);
// Applying the raw GET/persistence representation again must be idempotent.
assert(node.setChannel(1, raw) && node.getChannel(1, wire));
assert(!memcmp(wire.channel.secret, ordinary.channel.secret, 32));
assert(node.getChannel(2, wire));
assert(wire.channel.tx_radio == mesh::RADIO_TX_OFF);
assert(!memcmp(wire.channel.secret, ordinary.channel.secret, 32));
assert(node.getChannelForSave(2, raw));
assert(!memcmp(raw.channel.secret, mt.channel.secret, 32));
assert(raw.channel.tx_radio == mesh::RADIO_TX_BOTH);
// Both Rx and RxTx define the secondary profile. The real RadioProfiles
// transmit permission remains distinct from the channel's routing policy.
for (auto mode : {mesh::RadioProfileMode::Rx, mesh::RadioProfileMode::RxTx}) {
node.radio.config.secondary.mode = mode;
assert(node.isAliasRadio2Defined());
assert(node.getChannel(0, wire) && wire.channel.tx_radio == mesh::RADIO_TX_BOTH);
assert(node.getChannel(1, wire) && wire.channel.tx_radio == mesh::RADIO_TX_SECONDARY);
assert(node.getChannel(2, wire) && wire.channel.tx_radio == mesh::RADIO_TX_OFF);
assert(node.radio.config.canTransmit(1) == (mode == mesh::RadioProfileMode::RxTx));
}
mesh::Packet packet;
packet.payload[0] = wire.channel.hash[0]; packet.payload_len = 17;
memcpy(packet.payload + 1, ordinary.channel.secret, 16);
mesh::GroupChannel matches[4];
assert(node.allowGroupPacketOnRadio(&packet));
assert(packet.tx_radio == mesh::RADIO_TX_AUTO); // the ordinary primary slot wins
assert(node.searchChannelsForPacket(&packet, packet.payload, matches, 4) == 1);
assert(node.findChannelIdxForPacket(matches[0], &packet) == 0);
packet.radio_origin = 1;
assert(node.allowGroupPacketOnRadio(&packet));
assert(packet.tx_radio == mesh::RADIO_TX_SECONDARY); // bound before local delivery
assert(node.searchChannelsForPacket(&packet, packet.payload, matches, 4) == 1);
assert(node.findChannelIdxForPacket(matches[0], &packet) == 1);
uint8_t data = 0;
node.onGroupDataRecv(&packet, 0, matches[0], &data, 1);
assert(node.delivered == 1 && packet.tx_radio == mesh::RADIO_TX_SECONDARY);
ChannelDetails empty;
assert(node.setChannel(0, empty));
packet.tx_radio = mesh::RADIO_TX_AUTO;
assert(node.allowGroupPacketOnRadio(&packet));
assert(packet.tx_radio == mesh::RADIO_TX_SECONDARY); // alias-only secondary admission
packet.radio_origin = 0;
packet.tx_radio = mesh::RADIO_TX_AUTO;
assert(!node.allowGroupPacketOnRadio(&packet)); // authenticated alias on wrong radio
assert(packet.tx_radio == mesh::RADIO_TX_AUTO);
packet.payload[1] ^= 1;
assert(node.allowGroupPacketOnRadio(&packet)); // hash match alone is insufficient
packet.payload[1] ^= 1;
assert(node.setChannel(1, empty));
for (uint8_t origin : {0, 1}) {
packet.radio_origin = origin;
assert(node.searchChannelsForPacket(&packet, packet.payload, matches, 4) == 0);
assert(node.findChannelIdxForPacket(wire.channel, &packet) == -1);
assert(!node.allowGroupPacketOnRadio(&packet)); // configured MT cannot enter MeshCore
uint8_t blocked_data = 0;
const auto before_delivery = node.delivered;
node.onGroupDataRecv(&packet, 0, wire.channel, &blocked_data, 1);
assert(node.delivered == before_delivery);
}
// Both disabled secondary profile and an absent profile ignore routing markers.
for (bool absent : {false, true}) {
node.radio.config.secondary.mode = mesh::RadioProfileMode::Off; node.radio.present = !absent;
assert(!node.isAliasRadio2Defined());
assert(node.getChannel(2, wire) && wire.channel.tx_radio == mesh::RADIO_TX_BOTH);
assert(!memcmp(wire.channel.secret, ordinary.channel.secret, 32));
for (uint8_t origin : {0, 1}) {
packet.radio_origin = origin;
assert(node.allowGroupPacketOnRadio(&packet));
assert(node.searchChannelsForPacket(&packet, packet.payload, matches, 4) == 1);
assert(node.findChannelIdxForPacket(matches[0], &packet) == 2);
}
assert(node.setChannel(1, secondary) && node.getChannel(1, wire));
assert(wire.channel.tx_radio == mesh::RADIO_TX_PRIMARY);
assert(!memcmp(wire.channel.secret, ordinary.channel.secret, 32));
assert(node.setChannel(1, empty));
}
// Custom private keys survive both wire resolution and storage access.
ChannelDetails custom = secondary;
for (size_t i = 0; i < 16; ++i) custom.channel.secret[i] = static_cast<uint8_t>(i + 1);
assert(node.setChannel(3, custom) && node.getChannel(3, wire));
assert(!memcmp(wire.channel.secret, custom.channel.secret, 32));
assert(node.getChannelForSave(3, raw));
assert(!memcmp(raw.channel.secret, custom.channel.secret, 32));
custom = secondary;
for (size_t i = 16; i < 32; ++i) custom.channel.secret[i] = static_cast<uint8_t>(i + 1);
assert(node.setChannel(3, custom) && node.getChannel(3, wire));
assert(!memcmp(wire.channel.secret, custom.channel.secret, 32));
assert(node.getChannelForSave(3, raw));
assert(!memcmp(raw.channel.secret, custom.channel.secret, 32));
strcpy(custom.name, "#r2-mt-room");
assert(!node.setChannel(3, custom));
assert(!node.getChannel(-1, wire) && !node.setChannel(MAX_GROUP_CHANNELS, custom));
puts("PASS runtime raw/wire keys, receive aliases and radio2 fallback");
}
'''
COMMAND_STUBS = r'''
struct StrHelper {
static void strncpy(char* out, const char* in, size_t length) {
assert(length > 0);
::strncpy(out, in, length - 1); out[length - 1] = 0;
}
};
struct Serial {
uint8_t frame[128] = {}; int length = 0;
bool connected = true;
unsigned pushes = 0;
bool isConnected() const { return connected; }
void writeFrame(const uint8_t* data, int size) {
assert(size >= 0 && size <= static_cast<int>(sizeof(frame)));
memcpy(frame, data, size); length = size;
}
};
'''
COMMAND_MEMBERS = r'''
Serial serial; Serial* _serial = &serial;
bool save_result = true, ok = false; int error = -1; unsigned save_calls = 0;
bool saveChannels() { ++save_calls; return save_result; }
void writeErrFrame(uint8_t code) { error = code; }
void writeOKFrame() { ok = true; }
void seedEmergencyChannelNotice(uint8_t, const ChannelDetails&) {}
void command(uint8_t*, int);
'''
NOTICE_STUBS = r'''
#include <cmath>
#include <cstdlib>
#include <string>
#include <vector>
#include <helpers/CompanionFrameLimits.h>
// Expected failures in the negative controls must not open Windows CRT dialogs.
#undef assert
#define assert(condition) do { if(!(condition)) { \
std::fprintf(stderr,"notice fixture assertion: %s\n",#condition);std::exit(91); \
} } while(false)
using std::isfinite;
#define COMPANION_FEATURE_NOTIFICATIONS 1
#define COMPANION_FEATURE_TEXT_TERMINAL 1
#define DISPLAY_CLASS NoticeDisplay
#define MESH_DEBUG_PRINTLN(...) ((void)0)
#define OFFLINE_QUEUE_SIZE 4
#define MESH_ENABLE_ONE_KEY_DM @ONE_KEY@
#define ONE_KEY_DM_SHARED_OFFLINE_QUEUE @HELD@
#define MESH_ENABLE_MESHTEXT_CHAT 1
#define PUB_KEY_SIZE 32
#define MAX_TEXT_LEN 160
#define ONE_KEY_DM_ID_SIZE 8
#define ROUTE_TYPE_DIRECT 2
#define ROUTE_TYPE_FLOOD 1
static bool checking_ack_order=false,ack_written=false;
struct NoticeCommandScope {
NoticeCommandScope(){checking_ack_order=true;ack_written=false;}
~NoticeCommandScope(){checking_ack_order=false;}
};
uint32_t millis(){return 123;}
enum class UIEventType { channelMessage };
struct NoticeUI {
unsigned notifications=0,messages=0;
bool override_active=false,override_screen=false,last_quiet=false;
void syncMessageQueue(int,int=-1){assert(!checking_ack_order || ack_written);}
void setMessageNotificationOverride(bool active,bool screen=false){override_active=active;override_screen=screen;}
void notify(UIEventType){++notifications;}
void newMsg(uint8_t,const char*,const char*,int,int,const char*,int){
++messages;last_quiet=override_active && override_screen;
}
};
struct NoticeNotifications {
unsigned calls=0;
bool message(void*,const uint8_t*,bool,uint32_t){++calls;return false;}
bool overridesScreen()const{return false;}
};
struct NoticeClock {uint32_t epoch=1792012345;uint32_t getCurrentTime()const{return epoch;}};
struct NoticeTerminal {unsigned echoes=0;void printf(const char*,...){++echoes;}};
struct NoticeTerminalDisplay {bool shouldShowChannel(bool)const{return true;}};
'''
NOTICE_DM_STUBS = r'''
struct ContactInfo {
struct {uint8_t pub_key[PUB_KEY_SIZE]={};} id;
const uint8_t* getSharedSecret(int)const{return id.pub_key;}
};
'''
NOTICE_MEMBERS = r'''
NoticeUI ui; NoticeUI* _ui=&ui;
NoticeNotifications _notifications;
NoticeClock clock; NoticeClock* getRTCClock(){return &clock;}
NoticeTerminal terminal;NoticeTerminalDisplay _terminal_display;
bool hasTerminalOutput()const{return true;}NoticeTerminal& terminalOutput(){return terminal;}
uint8_t app_target_ver=3,out_frame[MAX_FRAME_SIZE]={};
uint8_t pending_emergency_notices[(MAX_GROUP_CHANNELS+7)/8]={};
#if MESH_ENABLE_ONE_KEY_DM
unsigned held_dm_count=0;
unsigned verified_pending_count=0;
uint8_t verified_pending_keys[15][PUB_KEY_SIZE]={};
struct HeldOneKeyDM {
uint8_t sender_key[PUB_KEY_SIZE]={},id[8]={};
mesh::Packet packet;
} held_dms[15];
ContactInfo held_contact;
int self_id=0;
unsigned held_deliveries=0,held_acks=0;
ContactInfo* lookupPersistentContactByPubKey(const uint8_t*,size_t){return &held_contact;}
void forgetVerifiedPendingSender(const uint8_t*){verified_pending_count=0;}
bool wasDeliveredOneKeyDM(const uint8_t*,const uint8_t*)const{return false;}
void rememberDeliveredOneKeyDM(const uint8_t*,const uint8_t*){}
void onMessageRecv(const ContactInfo&,mesh::Packet*,uint32_t,const char*){
const uint8_t dm[]={7,99};assert(addToOfflineQueue(dm,sizeof(dm)));++held_deliveries;
}
void sendAckTo(const ContactInfo&,const uint8_t*,size_t){++held_acks;}
void releaseHeldOneKeyDMs();
#endif
struct Frame {uint8_t len=0,buf[MAX_FRAME_SIZE]={};bool isChannelMsg()const;};
Frame offline_queue[OFFLINE_QUEUE_SIZE];
int offline_queue_len=0,offline_queue_head=0;
bool reject_next_enqueue=false;
#if ONE_KEY_DM_SHARED_OFFLINE_QUEUE
Frame& heldDMFrameAt(uint8_t);
void removeHeldOneKeyDM(uint8_t);
#endif
int getOfflineQueueCapacity()const;
Frame& offlineQueueFrameAt(int);
bool addToOfflineQueue(const uint8_t*,int);
void popOfflineQueue();
int peekOfflineQueue(uint8_t*);
int getFromOfflineQueue(uint8_t*);
void seedEmergencyChannelNotice(uint8_t,const ChannelDetails&);
void servicePendingChannelNotices();
bool queueChannelText(uint8_t,const uint8_t[32],float,uint8_t,uint32_t,const char*,bool,bool=true);
bool _mt_delivery_config_dirty=false,_mt_profile_dirty=false,valid_transport=true;
struct Seen {void clear(){}} _mt_seen;
bool isMeshtextChannel(const ChannelDetails& value)const{
return parseCompanionChannelAlias(value.name).kind==CompanionChannelKind::Meshtext;
}
bool validateMeshtextChannelChange(int,const ChannelDetails&){return valid_transport;}
void refreshMeshtextProfile(){}
'''
NOTICE_CASES = r'''
static const char notice[]="#EMERGENCY: This is an emergency channel. Do not use for testing or small talk.";
static bool pending(const MyMesh& node,unsigned index){return node.pending_emergency_notices[index/8]&(1U<<(index%8));}
static unsigned effectiveCapacity(const MyMesh& node){
return node.getOfflineQueueCapacity()
#if ONE_KEY_DM_SHARED_OFFLINE_QUEUE
-node.held_dm_count
#endif
;
}
static void verifyNotice(MyMesh& node,unsigned index,int queue_position=-1){
if(queue_position<0) queue_position=node.offline_queue_len-1;
const auto& frame=node.offlineQueueFrameAt(queue_position);
const unsigned base=node.app_target_ver>=3 ? 4:1;
const unsigned text_at=base+7;
assert(frame.buf[0]==(node.app_target_ver>=3 ? RESP_CODE_CHANNEL_MSG_RECV_V3:RESP_CODE_CHANNEL_MSG_RECV));
if(base==4) assert(frame.buf[1]==0 && frame.buf[2]==0 && frame.buf[3]==0);
assert(frame.buf[base]==index && frame.buf[base+1]==0xff && frame.buf[base+2]==TXT_TYPE_PLAIN);
uint32_t timestamp=0;memcpy(&timestamp,frame.buf+base+3,sizeof(timestamp));
assert(timestamp==node.clock.epoch && frame.len==text_at+strlen(notice));
assert(!memcmp(frame.buf+text_at,notice,strlen(notice)));
assert(node.ui.last_quiet && !node.ui.override_active && !node.ui.override_screen);
assert(node.ui.notifications==0 && node._notifications.calls==0);
// An ASCII-origin mOTA owner can leave the terminal output registered.
// A quiet local seed must not inject ASCII into that binary folder stream.
assert(node.terminal.echoes==0);
assert(!pending(node,index));
}
static void fillRealMessages(MyMesh& node){
for(unsigned i=0;i<effectiveCapacity(node);++i){
uint8_t frame[]={RESP_CODE_CHANNEL_MSG_RECV,uint8_t(40+i)};
assert(node.addToOfflineQueue(frame,sizeof(frame)));
}
}
static void drainRealMessages(MyMesh& node,unsigned first,unsigned count){
uint8_t frame[MAX_FRAME_SIZE]={};
for(unsigned i=0;i<count;++i){
assert(node.getFromOfflineQueue(frame)==2 && frame[0]==RESP_CODE_CHANNEL_MSG_RECV && frame[1]==first+i);
node.servicePendingChannelNotices();
if(i+1<count) assert(node.ui.messages==0);
}
}
int main(){
for(uint8_t app:{uint8_t(1),uint8_t(3)}) for(bool connected:{false,true})
for(const char* name:{"#emergency","#r2-emergency"}){
MyMesh node;node.app_target_ver=app;node.serial.connected=connected;
ChannelDetails empty{};expectGet(node,9,empty);
auto emergency=channel(name);
applySet(node,9,emergency);
assert(node.ok && node.offline_queue_len==1 && node.ui.messages==1);
verifyNotice(node,9);
assert(node.serial.length==(connected ? 1:0));
if(connected) assert(node.serial.frame[0]==PUSH_CODE_MSG_WAITING);
// A label/key/routing edit and app replay are not new joins.
applySet(node,9,emergency);assert(node.offline_queue_len==1);
auto renamed=channel(!strcmp(name,"#emergency") ? "#r2-emergency":"#emergency");
applySet(node,9,renamed);assert(node.offline_queue_len==1);
for(unsigned i=0;i<16;++i) renamed.channel.secret[i]=uint8_t(i+1);
applySet(node,9,renamed);assert(node.offline_queue_len==1);
renamed.channel.tx_radio=mesh::RADIO_TX_PRIMARY;assert(node.setChannel(9,renamed));
applySet(node,9,renamed);node.servicePendingChannelNotices();
assert(node.offline_queue_len==1 && node.ui.messages==1);
// Later app download sees exactly one ordinary channel-text envelope.
uint8_t frame[MAX_FRAME_SIZE]={};
assert(node.getFromOfflineQueue(frame)>0 && node.offline_queue_len==0);
node.servicePendingChannelNotices();assert(node.offline_queue_len==0);
}
for(const char* name:{"#custom","#EMERGENCY","#Emergency","emergency","Emergency","#emergency-test","#mt-emergency"}){
MyMesh node;applySet(node,0,channel(name));
assert(node.ok && node.offline_queue_len==0 && !pending(node,0));
node.servicePendingChannelNotices();assert(node.offline_queue_len==0);
}
{
// The 32-byte SET remains Meshtext-only; it must not seed a MeshCore notice.
MyMesh node;auto mt=channel("#mt-emergency");
const auto alias=channel("#r2-emergency");memcpy(mt.channel.secret,alias.channel.secret,16);
for(unsigned i=16;i<32;++i) mt.channel.secret[i]=uint8_t(i+1);
applySet(node,0,mt,32);assert(node.ok && node.offline_queue_len==0 && !pending(node,0));
}
{
MyMesh node;auto original=channel("#custom");assert(node.setChannel(0,original));
node.save_result=false;applySet(node,0,channel("#emergency"));
assert(node.error==ERR_CODE_FILE_IO_ERROR && !node.ok && node.offline_queue_len==0 && !pending(node,0));
expectRaw(node,0,original);node.save_result=true;
applySet(node,MAX_GROUP_CHANNELS,channel("#emergency"));
assert(node.error==ERR_CODE_NOT_FOUND && node.offline_queue_len==0);
auto invalid=original;strcpy(invalid.name,"#r2-mt-emergency");applySet(node,0,invalid);
assert(node.error==ERR_CODE_ILLEGAL_ARG && node.offline_queue_len==0);
node.valid_transport=false;applySet(node,0,channel("#emergency"));
assert(node.error==ERR_CODE_BAD_STATE && node.offline_queue_len==0 && !pending(node,0));
}
for(bool cancel:{false,true}){
MyMesh node;fillRealMessages(node);const unsigned full=node.offline_queue_len;
applySet(node,16,channel("#emergency"));
assert(node.ok && pending(node,16) && node.offline_queue_len==int(full));
node.servicePendingChannelNotices();node.servicePendingChannelNotices();
// Even a queue of evictable channel messages must remain untouched.
for(unsigned i=0;i<full;++i){const auto& f=node.offlineQueueFrameAt(i);assert(f.len==2 && f.buf[1]==40+i);}
if(cancel){ChannelDetails deleted{};applySet(node,16,deleted);assert(node.ok && !pending(node,16));}
drainRealMessages(node,40,full);
if(cancel) assert(node.offline_queue_len==0 && node.ui.messages==0);
else {assert(node.offline_queue_len==1);verifyNotice(node,16);}
const int length=node.offline_queue_len;
node.servicePendingChannelNotices();assert(node.offline_queue_len==length);
}
{
MyMesh node;fillRealMessages(node);
applySet(node,8,channel("#emergency"));applySet(node,16,channel("#r2-emergency"));
assert(pending(node,8) && pending(node,16));drainRealMessages(node,40,effectiveCapacity(node));
assert(!pending(node,8) && pending(node,16));verifyNotice(node,8);
node.servicePendingChannelNotices();assert(node.offline_queue_len==1 && pending(node,16));
node.popOfflineQueue();node.servicePendingChannelNotices();assert(!pending(node,16));verifyNotice(node,16);
const int length=node.offline_queue_len;node.servicePendingChannelNotices();assert(node.offline_queue_len==length);
}
{
// A notice must not consume the last slot and cause the next real arrival
// to evict older real channel traffic while retaining the local notice.
MyMesh node;
for(unsigned i=0;i<3;++i){
const uint8_t real[]={RESP_CODE_CHANNEL_MSG_RECV,uint8_t(40+i)};
assert(node.addToOfflineQueue(real,sizeof(real)));
}
applySet(node,9,channel("#emergency"));
assert(pending(node,9) && node.offline_queue_len==3);
const uint8_t dm[]={7,99};assert(node.addToOfflineQueue(dm,sizeof(dm)));
assert(node.offline_queue_len==4 && node.ui.messages==0);
drainRealMessages(node,40,3);
uint8_t frame[MAX_FRAME_SIZE]={};
assert(node.getFromOfflineQueue(frame)==2 && frame[0]==7 && frame[1]==99);
node.servicePendingChannelNotices();verifyNotice(node,9);
// Once materialized in an empty inbox, the notice is the first evictable
// channel frame, so later overflow preserves every subsequent real DM.
for(unsigned i=0;i<4;++i){
const uint8_t real_dm[]={7,uint8_t(100+i)};
assert(node.addToOfflineQueue(real_dm,sizeof(real_dm)));
}
for(unsigned i=0;i<4;++i){
assert(node.getFromOfflineQueue(frame)==2 && frame[0]==7 && frame[1]==100+i);
}
}
#if MESH_ENABLE_ONE_KEY_DM
{
// The same held-message rule applies to separate and shared queue builds.
MyMesh node;node.held_dm_count=1;node.verified_pending_count=1;
#if ONE_KEY_DM_SHARED_OFFLINE_QUEUE
auto& held=node.heldDMFrameAt(0);
uint32_t timestamp=123;memcpy(held.buf,&timestamp,sizeof(timestamp));
held.buf[8]=0xff;held.buf[10]=4;memcpy(held.buf+11,"real",5);held.len=16;
#else
auto& packet=node.held_dms[0].packet;
const uint8_t data[]={1,2,3,4,0,'r','e','a','l'};
memcpy(packet.payload+2,data,sizeof(data));packet.payload_len=sizeof(data)+2;
#endif
applySet(node,9,channel("#emergency"));
assert(pending(node,9) && node.offline_queue_len==0);
node.servicePendingChannelNotices();assert(node.offline_queue_len==0);
node.releaseHeldOneKeyDMs();
assert(node.held_dm_count==0 && node.verified_pending_count==0);
assert(node.held_deliveries==1 && node.held_acks==1 && node.offline_queue_len==1);
node.servicePendingChannelNotices();assert(node.offline_queue_len==1 && pending(node,9));
uint8_t frame[MAX_FRAME_SIZE]={};
assert(node.getFromOfflineQueue(frame)==2 && frame[0]==7 && frame[1]==99);
node.servicePendingChannelNotices();verifyNotice(node,9);
}
{
// A verified sender without a held payload has no real message to drain.
MyMesh node;node.verified_pending_count=1;applySet(node,9,channel("#emergency"));
assert(node.offline_queue_len==1);verifyNotice(node,9);
}
#endif
{
// A rejected queue write must retain the pending bit for a later retry.
MyMesh node;node.reject_next_enqueue=true;applySet(node,9,channel("#emergency"));
assert(node.ok && pending(node,9) && node.offline_queue_len==0 && node.ui.messages==0);
node.servicePendingChannelNotices();assert(node.offline_queue_len==1);verifyNotice(node,9);
}
{
// Loading/reapplying saved channels during startup is not an app join.
MyMesh reboot;assert(reboot.setChannel(9,channel("#emergency")));
reboot.servicePendingChannelNotices();assert(reboot.offline_queue_len==0 && !pending(reboot,9));
ChannelDetails deleted{};applySet(reboot,9,deleted);applySet(reboot,9,channel("#emergency"));
assert(reboot.ok && reboot.offline_queue_len==1);verifyNotice(reboot,9);
}
{
// Optional suppression must not mute normal incoming channel messages.
MyMesh node;node.serial.connected=false;auto ordinary=channel("#emergency");
assert(node.setChannel(0,ordinary));
assert(node.queueChannelText(0,ordinary.channel.secret,0,0,123,"Alice: real traffic",true));
assert(node._notifications.calls==1 && node.ui.notifications==1 && !node.ui.last_quiet);
assert(node.terminal.echoes==1);
}
puts("PASS notice persisted join, legacy/V3 local history, quiet delivery, retry and cancellation");
}
'''
COMMAND_HARNESS = r'''
void MyMesh::command(uint8_t* cmd_frame, int len) {
uint8_t out_frame[128] = {};
error = -1; ok = false; serial.length = 0;
@COMMAND_BRANCHES@
}
static void applySet(MyMesh& node, uint8_t index, const ChannelDetails& value, int key_bytes = 16) {
uint8_t frame[66] = {CMD_SET_CHANNEL, index};
memcpy(frame + 2, value.name, 32);
memcpy(frame + 34, value.channel.secret, key_bytes);
node.command(frame, 34 + key_bytes);
}
static void expectRaw(MyMesh& node, uint8_t index, const ChannelDetails& expected) {
ChannelDetails raw;
assert(node.getChannelForSave(index, raw));
assert(!strcmp(raw.name, expected.name));
assert(!memcmp(raw.channel.secret, expected.channel.secret, 32));
assert(raw.channel.tx_radio == expected.channel.tx_radio);
}
static void expectGet(MyMesh& node, uint8_t index, const ChannelDetails& expected) {
uint8_t command[2] = {CMD_GET_CHANNEL, index};
node.command(command, sizeof(command));
assert(node.error == -1 && !node.ok && node.serial.length == 50);
assert(node.serial.frame[0] == RESP_CODE_CHANNEL_INFO && node.serial.frame[1] == index);
assert(!strcmp(reinterpret_cast<const char*>(node.serial.frame + 2), expected.name));
assert(!memcmp(node.serial.frame + 34, expected.channel.secret, 16));
}
int main() {
MyMesh node;
node.radio.config.secondary.mode = mesh::RadioProfileMode::RxTx;
auto original = channel("#r2-emergency");
original.channel.tx_radio = mesh::RADIO_TX_BOTH;
assert(node.setChannel(0, original));
expectGet(node, 0, original);
ChannelDetails wire;
assert(node.getChannel(0, wire));
assert(memcmp(node.serial.frame + 34, wire.channel.secret, 16)); // GET is not the wire key
// Switching the display alias without changing the canonical key preserves
// the user's raw manual policy, not the alias's effective secondary policy.
auto ordinary = channel("#emergency");
applySet(node, 0, ordinary);
assert(node.ok && node.error == -1 && node.save_calls == 1);
ordinary.channel.tx_radio = mesh::RADIO_TX_BOTH;
expectRaw(node, 0, ordinary);
applySet(node, 0, original);
assert(node.ok && node.error == -1 && node.save_calls == 2);
expectRaw(node, 0, original);
expectGet(node, 0, original);
// Failed persistence restores the exact app representation. In particular,
// it must not replace the stored prefixed hashtag key with its wire key.
node.save_result = false;
const auto replacement = channel("#r2-custom");
applySet(node, 0, replacement);
assert(!node.ok && node.error == ERR_CODE_FILE_IO_ERROR && node.save_calls == 3);
expectRaw(node, 0, original);
expectGet(node, 0, original);
node.save_result = true;
// A genuinely different private PSK resets manual routing to AUTO.
auto private_channel = original;
for (size_t i = 0; i < 16; ++i) private_channel.channel.secret[i] = static_cast<uint8_t>(i + 1);
private_channel.channel.tx_radio = mesh::RADIO_TX_AUTO;
applySet(node, 0, private_channel);
assert(node.ok && node.error == -1 && node.save_calls == 4);
expectRaw(node, 0, private_channel);
assert(node.getChannel(0, wire));
assert(!memcmp(wire.channel.secret, private_channel.channel.secret, 32));
// Reapplying an unchanged private key retains an explicitly saved policy.
private_channel.channel.tx_radio = mesh::RADIO_TX_PRIMARY;
assert(node.setChannel(0, private_channel));
applySet(node, 0, private_channel);
assert(node.ok && node.error == -1 && node.save_calls == 5);
expectRaw(node, 0, private_channel);
auto invalid = private_channel;
strcpy(invalid.name, "#r2-mt-emergency");
applySet(node, 0, invalid);
assert(!node.ok && node.error == ERR_CODE_ILLEGAL_ARG && node.save_calls == 5);
expectRaw(node, 0, private_channel);
const auto mt = channel("#mt-emergency");
applySet(node, 0, mt);
assert(!node.ok && node.error == ERR_CODE_UNSUPPORTED_CMD && node.save_calls == 5);
expectRaw(node, 0, private_channel);
// Disabled or absent radio2 permits the MT display marker only as an
// ordinary MeshCore channel; it does not activate Meshtext transport.
node.radio.config.secondary.mode = mesh::RadioProfileMode::Off;
applySet(node, 0, mt);
assert(node.ok && node.error == -1 && node.save_calls == 6);
expectRaw(node, 0, mt);
expectGet(node, 0, mt);
assert(node.getChannel(0, wire));
assert(wire.channel.tx_radio == mesh::RADIO_TX_AUTO);
assert(!memcmp(wire.channel.secret, ordinary.channel.secret, 32));
node.radio.present = false;
applySet(node, 0, mt);
assert(node.ok && node.error == -1 && node.save_calls == 7);
expectRaw(node, 0, mt);
// Binary framing remains 128-bit only; larger keys are not silently cut.
applySet(node, 0, mt, 32);
assert(!node.ok && node.error == ERR_CODE_UNSUPPORTED_CMD && node.save_calls == 7);
expectRaw(node, 0, mt);
applySet(node, MAX_GROUP_CHANNELS, mt);
assert(!node.ok && node.error == ERR_CODE_NOT_FOUND && node.save_calls == 7);
uint8_t bad_get[] = {CMD_GET_CHANNEL, MAX_GROUP_CHANNELS};
node.command(bad_get, sizeof(bad_get));
assert(node.error == ERR_CODE_NOT_FOUND && node.serial.length == 0);
puts("PASS command raw GET, canonical-key SET, rollback and unsupported transport");
}
'''
def oracle_vectors():
vectors = ",\n".join(
"{" + json.dumps(name) + ",{" + ",".join(map(str, hashlib.sha256(name.encode()).digest())) + "}}"
for name in HASH_NAMES
)
keys = [hashlib.sha256(name.encode()).digest()[:16] for name in HASH_NAMES]
keys += [bytes(16), bytes(range(1, 17)),
hashlib.sha256(b"#r2-emergency").digest()[:16] + bytes(range(17, 33))]
key_vectors = ",\n".join(
"{{" + ",".join(map(str, key)) + "}," + str(len(key)) + ",{" +
",".join(map(str, hashlib.sha256(key).digest())) + "}}" for key in keys
)
return vectors, key_vectors
class CompanionChannelAliasTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
if not compiler:
raise unittest.SkipTest("a host C++ compiler is required")
cls.temporary = tempfile.TemporaryDirectory(prefix="meshcore-channel-alias-")
cls.addClassCleanup(cls.temporary.cleanup)
work = Path(cls.temporary.name)
cls.binary = work / ("alias.exe" if sys.platform == "win32" else "alias")
vectors, key_vectors = oracle_vectors()
source = work / "alias.cpp"
source.write_text(HARNESS.replace("@VECTORS@", vectors).replace("@KEY_VECTORS@", key_vectors), encoding="utf-8")
sanitizers = (["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
"-fno-pie", "-no-pie"] if sys.platform.startswith("linux") else [])
built = subprocess.run(
[compiler, "-std=c++11", "-Wall", "-Wextra", "-Werror", *sanitizers,
"-I" + str(ROOT / "src"), str(source), "-o", str(cls.binary)],
capture_output=True, text=True, timeout=60,
)
if built.returncode:
raise AssertionError(built.stdout + built.stderr)
def run_case(self, case):
checked = subprocess.run([str(self.binary), case], capture_output=True, text=True, timeout=30)
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
self.assertIn("PASS " + case, checked.stdout)
def test_prefix_grammar(self):
self.run_case("grammar")
def test_canonical_names_and_output_bounds(self):
self.run_case("canonical")
def test_independent_sha256_vectors_and_custom_keys(self):
self.run_case("keys")
def test_receive_priority_and_unconfigured_radio2_fallback(self):
self.run_case("receive")
def test_core_admission_precedes_group_seen_and_direct_forward(self):
core = (ROOT / "src/Mesh.cpp").read_text(encoding="utf-8")
received = body(core, "DispatcherAction Mesh::onRecvPacket(Packet* pkt)")
gate = re.search(
r"if\s*\(\(pkt->getPayloadType\(\)\s*==\s*PAYLOAD_TYPE_GRP_TXT\s*"
r"\|\|\s*pkt->getPayloadType\(\)\s*==\s*PAYLOAD_TYPE_GRP_DATA\)\s*"
r"&&\s*!allowGroupPacketOnRadio\(pkt\)\)\s*return ACTION_RELEASE;", received,
)
self.assertIsNotNone(gate, "both encrypted group types must pass admission")
# TRACE has an unrelated earlier direct block; check the general
# direct-path branch, whose relaying runs before local group delivery.
direct = received.index("if (pkt->isRouteDirect() && pkt->getPathHashCount() > 0)")
group = received.index("case PAYLOAD_TYPE_GRP_DATA:")
group_seen = received.index("_tables->markSeen(pkt);", group)
candidates = received.index("searchChannelsForPacket(pkt, &channel_hash, channels, 4)", group)
delivery = received.index("onGroupDataRecv(pkt,", candidates)
self.assertLess(gate.end(), direct)
self.assertLess(gate.end(), group_seen)
self.assertLess(group_seen, candidates)
self.assertLess(candidates, delivery)
header = (ROOT / "src/Mesh.h").read_text(encoding="utf-8")
self.assertRegex(header, r"virtual bool allowGroupPacketOnRadio\(Packet\* packet\)\s*\{ return true; \}")
def test_production_channel_access_and_receive_resolution(self):
companion = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text(encoding="utf-8")
header = (ROOT / "examples/companion_radio/MyMesh.h").read_text(encoding="utf-8")
signatures = (
"bool MyMesh::isAliasRadio2Defined(", "bool MyMesh::resolveChannelAlias(",
"bool MyMesh::getChannel(", "bool MyMesh::setChannel(",
"int MyMesh::searchChannelsForPacket(", "int MyMesh::findChannelIdxForPacket(",
"bool MyMesh::allowGroupPacketOnRadio(", "void MyMesh::onGroupDataRecv(",
)
methods = "\n".join(body(companion, signature) for signature in signatures)
save_getter = body(header, "bool getChannelForSave(uint8_t").replace("override", "")
shared = HARNESS[:HARNESS.index("static void assertAlias(")]
vectors, key_vectors = oracle_vectors()
source_text = (shared + RUNTIME_HARNESS).replace("@VECTORS@", vectors).replace(
"@KEY_VECTORS@", key_vectors).replace("@METHODS@", methods).replace("@SAVE_GETTER@", save_getter)
self.run_native_program(source_text, "runtime")
def test_production_binary_get_set_raw_keys_and_rollback(self):
companion = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text(encoding="utf-8")
header = (ROOT / "examples/companion_radio/MyMesh.h").read_text(encoding="utf-8")
signatures = ("bool MyMesh::isAliasRadio2Defined(", "bool MyMesh::resolveChannelAlias(",
"bool MyMesh::getChannel(", "bool MyMesh::setChannel(")
methods = "\n".join(body(companion, signature) for signature in signatures)
save_getter = body(header, "bool getChannelForSave(uint8_t").replace("override", "")
command_signatures = (
"if (cmd_frame[0] == CMD_GET_CHANNEL && len >= 2)",
"if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 32)",
"if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 16)",
)
branches = " else ".join(body(companion, signature) for signature in command_signatures)
macros = ("CMD_GET_CHANNEL", "CMD_SET_CHANNEL", "RESP_CODE_CHANNEL_INFO",
"ERR_CODE_UNSUPPORTED_CMD", "ERR_CODE_NOT_FOUND", "ERR_CODE_FILE_IO_ERROR",
"ERR_CODE_ILLEGAL_ARG")
constants = "\n".join(re.search(r"^#define\s+" + name + r"\s+[^\r\n]+", companion,
flags=re.MULTILINE).group(0) for name in macros)
shared = HARNESS[:HARNESS.index("static void assertAlias(")]
fixture = RUNTIME_HARNESS[:RUNTIME_HARNESS.index("\nint main()")]
fixture = fixture.replace("class MyMesh :", COMMAND_STUBS + "\nclass MyMesh :")
fixture = fixture.replace("@SAVE_GETTER@", save_getter + "\n" + COMMAND_MEMBERS)
vectors, key_vectors = oracle_vectors()
source_text = (shared + constants + "\n" + fixture + COMMAND_HARNESS).replace(
"@VECTORS@", vectors).replace("@KEY_VECTORS@", key_vectors).replace(
"@METHODS@", methods).replace("@COMMAND_BRANCHES@", branches)
self.run_native_program(source_text, "command")
def test_persisted_emergency_join_seeds_quiet_local_history_and_defers_without_eviction(self):
companion = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text(encoding="utf-8")
header = (ROOT / "examples/companion_radio/MyMesh.h").read_text(encoding="utf-8")
signatures = (
"bool MyMesh::isAliasRadio2Defined(", "bool MyMesh::resolveChannelAlias(",
"bool MyMesh::getChannel(", "bool MyMesh::setChannel(",
"static bool isEmergencyNoticeChannelName(",
"void MyMesh::seedEmergencyChannelNotice(", "void MyMesh::servicePendingChannelNotices(",
"bool MyMesh::queueChannelText(", "bool MyMesh::Frame::isChannelMsg() const",
"int MyMesh::getOfflineQueueCapacity() const", "MyMesh::Frame& MyMesh::offlineQueueFrameAt(",
"bool MyMesh::addToOfflineQueue(", "void MyMesh::popOfflineQueue(",
"int MyMesh::peekOfflineQueue(", "int MyMesh::getFromOfflineQueue(",
)
methods = "\n".join(body(companion, signature) for signature in signatures)
# Keep the real admission/eviction code while exposing a deterministic
# transport/storage rejection boundary for the pending-bit retry case.
queue_signature = "bool MyMesh::addToOfflineQueue("
queue_method = body(companion, queue_signature)
methods = methods.replace(queue_method, queue_method.replace(
"{", "{\n if (reject_next_enqueue) { reject_next_enqueue=false; return false; }", 1))
methods += "\n#if MESH_ENABLE_ONE_KEY_DM\n" + body(
companion, "void MyMesh::releaseHeldOneKeyDMs()") + "\n#endif\n"
methods += "\n#if ONE_KEY_DM_SHARED_OFFLINE_QUEUE\n" + "\n".join(
body(companion, signature) for signature in (
"MyMesh::Frame& MyMesh::heldDMFrameAt(",
"void MyMesh::removeHeldOneKeyDM(",
)) + "\n#endif\n"
branches = " else ".join(body(companion, signature) for signature in (
"if (cmd_frame[0] == CMD_GET_CHANNEL && len >= 2)",
"if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 32)",
"if (cmd_frame[0] == CMD_SET_CHANNEL && len >= 2 + 32 + 16)",
))
macros = (
"CMD_GET_CHANNEL", "CMD_SET_CHANNEL", "RESP_CODE_CHANNEL_INFO",
"RESP_CODE_CHANNEL_MSG_RECV", "RESP_CODE_CHANNEL_MSG_RECV_V3", "RESP_CODE_CHANNEL_DATA_RECV",
"PUSH_CODE_MSG_WAITING", "ERR_CODE_UNSUPPORTED_CMD", "ERR_CODE_NOT_FOUND",
"ERR_CODE_FILE_IO_ERROR", "ERR_CODE_ILLEGAL_ARG", "ERR_CODE_BAD_STATE",
)
constants = "\n".join(re.search(r"^#define\s+" + name + r"\s+[^\r\n]+", companion,
flags=re.MULTILINE).group(0) for name in macros)
text_helpers = (ROOT / "src/helpers/TxtDataHelpers.h").read_text(encoding="utf-8")
constants += "\n" + re.search(r"^#define\s+TXT_TYPE_PLAIN\s+[^\r\n]+", text_helpers,
flags=re.MULTILINE).group(0)
save_getter = body(header, "bool getChannelForSave(uint8_t").replace("override", "")
members = COMMAND_MEMBERS.replace(
" void seedEmergencyChannelNotice(uint8_t, const ChannelDetails&) {}\n", "")
members = members.replace("void writeOKFrame() { ok = true; }",
"void writeOKFrame() { ok = true; ack_written = true; }")
fixture = RUNTIME_HARNESS[:RUNTIME_HARNESS.index("\nint main()")]
fixture = fixture.replace("#define MAX_GROUP_CHANNELS 5", "#define MAX_GROUP_CHANNELS 17")
fixture = fixture.replace("class MyMesh :", COMMAND_STUBS + NOTICE_DM_STUBS + "\nclass MyMesh :")
fixture = fixture.replace(
"uint8_t radio_origin = 0, tx_radio = RADIO_TX_AUTO;",
"uint8_t radio_origin = 0, tx_radio = RADIO_TX_AUTO, header = ROUTE_TYPE_FLOOD, path_len = 0;\n"
" int8_t _snr = 0; bool isRouteFlood()const{return header==ROUTE_TYPE_FLOOD;}")
fixture = fixture.replace(
" static bool isZeroes(",
" static void sha256(uint8_t* out,size_t n,const uint8_t*,size_t,const uint8_t*,size_t){memset(out,0,n);}\n"
" static bool isZeroes(")
# Held-message release retains the real queue and FIFO control flow.
# The cipher boundary uses an explicitly prepared plaintext packet.
fixture = fixture.replace(
"static int MACThenDecrypt(const uint8_t* key, uint8_t*, const uint8_t* data, int length) {\n"
" return length == 16 && !memcmp(key, data, 16) ? 1 : 0;",
"static int MACThenDecrypt(const uint8_t* key, uint8_t* out, const uint8_t* data, int length) {\n"
" memcpy(out,data,length);return length;")
fixture = fixture.replace("@SAVE_GETTER@", save_getter + "\n" + members + NOTICE_MEMBERS)
command_helpers = COMMAND_HARNESS[:COMMAND_HARNESS.index("\nint main()")]
command_helpers = command_helpers.replace("uint8_t out_frame[128] = {};",
"NoticeCommandScope ack_scope; uint8_t out_frame[128] = {};")
shared = HARNESS[:HARNESS.index("static void assertAlias(")]
vectors, key_vectors = oracle_vectors()
program = (shared + constants + "\n" + NOTICE_STUBS + fixture + command_helpers + NOTICE_CASES)
program = program.replace("@VECTORS@", vectors).replace("@KEY_VECTORS@", key_vectors)
program = program.replace("@METHODS@", methods).replace("@COMMAND_BRANCHES@", branches)
for one_key, held in ((0, 0), (1, 0), (1, 1)):
with self.subTest(one_key_dm=one_key, shared_held_dm_queue=held):
configured = program.replace("@ONE_KEY@", str(one_key)).replace("@HELD@", str(held))
self.run_native_program(configured, "notice",
extra_flags=("-Wno-unused-parameter",))
configured = program.replace("@ONE_KEY@", "1").replace("@HELD@", "0")
# Prove that quiet delivery, an empty inbox, and real held-message
# priority are each enforced by the executable fixture.
self.assertIn("true, false)) pending", program)
self.run_native_program(configured.replace(
"true, false)) pending", "true, true)) pending", 1), "notice",
extra_flags=("-Wno-unused-parameter",), expect_failure="node.ui.last_quiet")
self.assertIn("if (offline_queue_len != 0) return;", program)
self.run_native_program(configured.replace(
"if (offline_queue_len != 0) return;",
"if (offline_queue_len >= getOfflineQueueCapacity()) return;", 1), "notice",
extra_flags=("-Wno-unused-parameter",), expect_failure="node.ui.messages==0")
self.assertIn("if (held_dm_count != 0) return;", program)
self.run_native_program(configured.replace(
"if (held_dm_count != 0) return;", "", 1), "notice",
extra_flags=("-Wno-unused-parameter",), expect_failure="pending(node,9) && node.offline_queue_len==0")
self.run_native_program(configured.replace(
"true, false)) pending &= ~bit;", "true, false)); pending &= ~bit;", 1), "notice",
extra_flags=("-Wno-unused-parameter", "-Wno-empty-body", "-Wno-misleading-indentation"),
expect_failure="node.ok && pending(node,9)")
loop = body(companion, "void MyMesh::loop()")
self.assertLess(loop.index("releaseHeldOneKeyDMs()"), loop.index("servicePendingChannelNotices()"))
def run_native_program(self, source_text, expected, extra_flags=(), expect_failure=None):
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
with tempfile.TemporaryDirectory(prefix="meshcore-channel-runtime-") as directory:
work = Path(directory)
source = work / "runtime.cpp"
source.write_text(source_text, encoding="utf-8")
binary = work / ("runtime.exe" if sys.platform == "win32" else "runtime")
sanitizers = (["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
"-fno-pie", "-no-pie"] if sys.platform.startswith("linux") else [])
built = subprocess.run(
[compiler, "-std=c++11", "-Wall", "-Wextra", "-Werror", *extra_flags, *sanitizers,
"-I" + str(ROOT / "src"), str(source), "-o", str(binary)],
capture_output=True, text=True, timeout=60,
)
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=30)
if expect_failure:
self.assertEqual(checked.returncode, 91, checked.stdout + checked.stderr)
self.assertIn(expect_failure, checked.stderr)
else:
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
self.assertIn("PASS " + expected, checked.stdout)
if __name__ == "__main__":
unittest.main()