mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-11-04 09:47:16 +00:00
148 lines
6.9 KiB
C++
148 lines
6.9 KiB
C++
#define BOARD_HAS_PSRAM 1
|
|
#define MAX_NEIGHBOURS 254
|
|
#define PUB_KEY_SIZE 32
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <initializer_list>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <helpers/MQTTPayloadBuilder.h>
|
|
namespace mesh {
|
|
struct Identity { uint8_t pub_key[32]; };
|
|
struct Utils {
|
|
static void toHex(char* out, const uint8_t* key, size_t size) {
|
|
for(size_t i=0;i<size;++i)snprintf(out+2*i,3,"%02x",key[i]);
|
|
}
|
|
};
|
|
}
|
|
struct MQTTBridge {
|
|
@BRIDGE_CONSTANTS@
|
|
};
|
|
struct MQTTMessageBuilder : MQTTPayloadBuilder {
|
|
static size_t measureNeighborsMessageEntry(const NeighborsMessageEntry& entry) {
|
|
size_t measured=MQTTPayloadBuilder::measureNeighborsMessageEntry(entry);
|
|
#ifndef TEST_PUBLICATION_NEGATIVE_CONTROL
|
|
// Production discovery calls this measurement before advancing its cursor.
|
|
// Check the bound against its actual fields as well as formatter cases.
|
|
assert(measured>=MQTTBridge::NEIGHBORS_MIN_DISCOVERY_ENTRY_JSON_BYTES);
|
|
#endif
|
|
return measured;
|
|
}
|
|
};
|
|
struct Clock { uint32_t getCurrentTime() { return UINT32_MAX; } };
|
|
struct MyMesh {
|
|
enum { ND_UNSENT, ND_QUEUED, ND_PENDING, ND_RESPONDED, ND_TIMEOUT, ND_SEND_FAILED };
|
|
@LAYOUT@
|
|
uint8_t neighbor_discover_next=0, neighbor_discover_count=254;
|
|
uint8_t neighbor_discover_publish_count=0;
|
|
size_t neighbor_discover_json_size=0;
|
|
bool neighbor_discover_truncated=false, finished=false;
|
|
Clock clock;
|
|
Clock* getRTCClock() { return &clock; }
|
|
void touchNeighbourHeard(const mesh::Identity&, uint32_t, float, int16_t) {}
|
|
void finishNeighborDiscover() { finished=true; }
|
|
bool completeNeighborDiscoverEntry();
|
|
bool handleNeighborDiscoverResponse(int,const uint8_t*,size_t,float,int16_t);
|
|
};
|
|
struct LegacyMesh {
|
|
enum { ND_UNSENT, ND_QUEUED, ND_PENDING, ND_RESPONDED, ND_TIMEOUT, ND_SEND_FAILED };
|
|
@LEGACY_LAYOUT@
|
|
uint8_t neighbor_discover_next=0, neighbor_discover_count=254;
|
|
uint8_t neighbor_discover_publish_count=0;
|
|
size_t neighbor_discover_json_size=0;
|
|
bool neighbor_discover_truncated=false, finished=false;
|
|
Clock clock;
|
|
Clock* getRTCClock() { return &clock; }
|
|
void touchNeighbourHeard(const mesh::Identity&, uint32_t, float, int16_t) {}
|
|
void finishNeighborDiscover() { finished=true; }
|
|
bool completeNeighborDiscoverEntry();
|
|
bool handleNeighborDiscoverResponse(int,const uint8_t*,size_t,float,int16_t);
|
|
};
|
|
@METHODS@
|
|
@LEGACY_METHODS@
|
|
template<class Mesh> std::string collect(Mesh& mesh,size_t scope_bytes,int state,int rssi,float snr) {
|
|
const char* timestamp="2026-10-10T01:23:45Z";
|
|
const std::string own_key(64,'a');
|
|
mesh.neighbor_discover_json_size=MQTTPayloadBuilder::measureNeighborsMessageBase(
|
|
"Seattle",own_key.c_str(),timestamp,"sea;pdx","sea",254);
|
|
std::vector<std::string> keys; keys.reserve(254);
|
|
std::vector<MQTTPayloadBuilder::NeighborsMessageEntry> entries;
|
|
for(int i=0;i<254;++i) {
|
|
auto& row=mesh.neighbor_discover[i];
|
|
memset(row.id.pub_key,uint8_t(i),sizeof(row.id.pub_key));
|
|
row.status=uint8_t(state); row.snr=int8_t(snr*4); row.rssi=int16_t(rssi);
|
|
if(state==Mesh::ND_RESPONDED) {
|
|
row.status=Mesh::ND_PENDING; row.tag=123;
|
|
std::vector<uint8_t> response(8+scope_bytes,'x');
|
|
memcpy(response.data(),&row.tag,4);
|
|
assert(mesh.handleNeighborDiscoverResponse(i,response.data(),response.size(),snr,int16_t(rssi)));
|
|
}
|
|
if(!mesh.completeNeighborDiscoverEntry())break;
|
|
char key[65]; mesh::Utils::toHex(key,row.id.pub_key,32); keys.emplace_back(key);
|
|
entries.push_back({keys.back().c_str(),row.snr/4.0f,UINT32_MAX,
|
|
mesh.neighbor_discover_scopes[i],state==Mesh::ND_RESPONDED ? "responded"
|
|
: (state==Mesh::ND_SEND_FAILED ? "send_failed" : "timeout"),rssi,false});
|
|
}
|
|
assert(mesh.finished && mesh.neighbor_discover_truncated);
|
|
assert(entries.size()==mesh.neighbor_discover_publish_count);
|
|
JsonDocument document;
|
|
std::vector<char> json(MQTTBridge::NEIGHBORS_JSON_BUFFER_SIZE);
|
|
int length=MQTTPayloadBuilder::buildNeighborsMessage(document,"Seattle",own_key.c_str(),timestamp,
|
|
"sea;pdx","sea",entries.data(),int(entries.size()),json.data(),json.size(),254,
|
|
int(entries.size())+1,true);
|
|
assert(length>0 && size_t(length)<json.size());
|
|
assert(document["total_neighbors"].as<int>()==254 && document["truncated"].as<bool>());
|
|
assert(document["neighbors"].as<JsonArray>().size()==entries.size());
|
|
return std::string(json.data(),size_t(length));
|
|
}
|
|
int main() {
|
|
MyMesh memory={}; LegacyMesh legacy_memory={};
|
|
static_assert(sizeof(memory.neighbor_discover)/sizeof(memory.neighbor_discover[0])==254,
|
|
"keep the complete neighbor snapshot capacity");
|
|
#ifndef TEST_PUBLICATION_NEGATIVE_CONTROL
|
|
assert(sizeof(MyMesh::NeighborDiscoverEntry)==44);
|
|
assert(sizeof(LegacyMesh::NeighborDiscoverEntry)==48);
|
|
assert(sizeof(memory.neighbor_discover_scopes)==72*96);
|
|
assert(sizeof(legacy_memory.neighbor_discover_scopes)-sizeof(memory.neighbor_discover_scopes)==17472);
|
|
assert(sizeof(legacy_memory.neighbor_discover)-sizeof(memory.neighbor_discover)==1016);
|
|
#else
|
|
(void)legacy_memory;
|
|
#endif
|
|
const std::string key(64,'0');
|
|
MQTTPayloadBuilder::NeighborsMessageEntry minimum={key.c_str(),0,0,"","",0,false};
|
|
assert(MQTTPayloadBuilder::measureNeighborsMessageEntry(minimum)==128);
|
|
#ifndef TEST_PUBLICATION_NEGATIVE_CONTROL
|
|
minimum.heard_secs_ago=UINT32_MAX; minimum.status="timeout";
|
|
assert(MQTTPayloadBuilder::measureNeighborsMessageEntry(minimum)==144);
|
|
assert(MQTTBridge::NEIGHBORS_MIN_DISCOVERY_ENTRY_JSON_BYTES<=144);
|
|
for(int rssi : {0,-1,-100,INT16_MIN})
|
|
for(float snr : {0.0f,-32.0f,12.75f})
|
|
for(const char* status : {"responded","timeout","send_failed"}) {
|
|
auto entry=minimum; entry.rssi=rssi;
|
|
entry.snr=snr; entry.status=status;
|
|
assert(MQTTPayloadBuilder::measureNeighborsMessageEntry(entry)>=MQTTBridge::NEIGHBORS_MIN_DISCOVERY_ENTRY_JSON_BYTES);
|
|
}
|
|
#endif
|
|
for(size_t scope : {size_t(0),size_t(1),size_t(31),size_t(95),size_t(200)})
|
|
for(int state : {MyMesh::ND_RESPONDED,MyMesh::ND_TIMEOUT,MyMesh::ND_SEND_FAILED})
|
|
for(int rssi : {0,-100})
|
|
for(float snr : {0.0f,12.75f}) {
|
|
MyMesh current={}; LegacyMesh baseline={};
|
|
auto actual=collect(current,scope,state,rssi,snr);
|
|
auto old=collect(baseline,scope,state,rssi,snr);
|
|
assert(actual==old && current.neighbor_discover_publish_count==baseline.neighbor_discover_publish_count);
|
|
assert(current.neighbor_discover_next<MyMesh::NEIGHBOR_SCOPE_RESULTS);
|
|
}
|
|
// Explicit bounds reject an unexpected late response and safely finish an
|
|
// invalid cursor, even if future serializer changes violate the size proof.
|
|
memory.neighbor_discover_next=MyMesh::NEIGHBOR_SCOPE_RESULTS;
|
|
uint8_t response[8]={};
|
|
assert(!memory.handleNeighborDiscoverResponse(int(MyMesh::NEIGHBOR_SCOPE_RESULTS),response,8,0,0));
|
|
assert(!memory.completeNeighborDiscoverEntry() && memory.finished && memory.neighbor_discover_truncated);
|
|
puts("real neighbor serialization and complete publication checks passed");
|
|
}
|