sync with vanilla dev

This commit is contained in:
liquidraver committed 2026-04-17 13:21:30 +02:00
1 parent ba4b86752d
commit d2cec84100
11 files changed
+692 -568

No files matched your search

+63 -16
View File
@@ -69,7 +69,7 @@ LOG_MODULE_REGISTER(zephcore_companion, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#define CMD_SEND_BINARY_REQ 0x32
#define CMD_FACTORY_RESET 0x33
#define CMD_SEND_PATH_DISCOVERY 0x34
#define CMD_SET_FLOOD_SCOPE 0x36
#define CMD_SET_FLOOD_SCOPE_KEY 0x36 /* v8+ (renamed from CMD_SET_FLOOD_SCOPE) */
#define CMD_SEND_CONTROL_DATA 0x37
#define CMD_GET_STATS 0x38
#define CMD_SEND_ANON_REQ 0x39
@@ -78,6 +78,8 @@ LOG_MODULE_REGISTER(zephcore_companion, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#define CMD_GET_ALLOWED_REPEAT_FREQ 0x3C
#define CMD_SET_PATH_HASH_MODE 0x3D
#define CMD_SEND_CHANNEL_DATA 0x3E
#define CMD_SET_DEFAULT_FLOOD_SCOPE 0x3F /* v11+ */
#define CMD_GET_DEFAULT_FLOOD_SCOPE 0x40 /* v11+ */
/* Response packet types */
#define PACKET_OK 0x00
@@ -108,6 +110,7 @@ LOG_MODULE_REGISTER(zephcore_companion, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#define PACKET_AUTOADD_CONFIG 0x19
#define PACKET_ALLOWED_REPEAT_FREQ 0x1A
#define PACKET_CHANNEL_DATA_RECV 0x1B
#define PACKET_DEFAULT_FLOOD_SCOPE 0x1C
#define MAX_CHANNEL_DATA_LENGTH (MAX_FRAME_SIZE - 9)
@@ -204,28 +207,36 @@ bool CompanionMesh::allowPacketForward(const mesh::Packet *packet)
return prefs.client_repeat != 0;
}
void CompanionMesh::sendFloodScoped(const ContactInfo &recipient, mesh::Packet *pkt, uint32_t delay_millis)
void CompanionMesh::sendFloodScoped(const TransportKey &scope, mesh::Packet *pkt, uint32_t delay_millis)
{
if (_send_scope.isNull()) {
if (scope.isNull()) {
sendFlood(pkt, delay_millis, prefs.path_hash_mode + 1);
} else {
uint16_t codes[2];
codes[0] = _send_scope.calcTransportCode(pkt);
codes[0] = scope.calcTransportCode(pkt);
codes[1] = 0;
sendFlood(pkt, codes, delay_millis, prefs.path_hash_mode + 1);
}
}
void CompanionMesh::sendFloodScoped(const ContactInfo &recipient, mesh::Packet *pkt, uint32_t delay_millis)
{
/* TODO: dynamic _send_scope, depending on recipient and current 'home' Region */
TransportKey default_scope;
memcpy(default_scope.key, prefs.default_scope_key, sizeof(default_scope.key));
const TransportKey &scope = _send_scope.isNull() ? default_scope : _send_scope;
sendFloodScoped(scope, pkt, delay_millis);
}
void CompanionMesh::sendFloodScoped(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t delay_millis)
{
if (_send_scope.isNull()) {
sendFlood(pkt, delay_millis, prefs.path_hash_mode + 1);
} else {
uint16_t codes[2];
codes[0] = _send_scope.calcTransportCode(pkt);
codes[1] = 0;
sendFlood(pkt, codes, delay_millis, prefs.path_hash_mode + 1);
}
/* TODO: have per-channel send_scope */
TransportKey default_scope;
memcpy(default_scope.key, prefs.default_scope_key, sizeof(default_scope.key));
const TransportKey &scope = _send_scope.isNull() ? default_scope : _send_scope;
sendFloodScoped(scope, pkt, delay_millis);
}
bool CompanionMesh::onContactPathRecv(ContactInfo &from, uint8_t *in_path, uint8_t in_path_len,
@@ -1780,7 +1791,9 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
if (adv) {
/* Optional param: data[1] == 1 means flood, else zero-hop */
if (len >= 2 && data[1] == 1) {
sendFlood(adv, (uint32_t)0, prefs.path_hash_mode + 1);
TransportKey default_scope;
memcpy(default_scope.key, prefs.default_scope_key, sizeof(default_scope.key));
sendFloodScoped(default_scope, adv, (uint32_t)0);
} else {
sendZeroHop(adv);
}
@@ -1952,7 +1965,7 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
static const uint8_t version[20] = "v1.14.1-zephyr";
uint8_t rsp[82];
rsp[0] = PACKET_DEVICE_INFO;
rsp[1] = 10; // FIRMWARE_VER_CODE - v10 = path_hash_mode support
rsp[1] = 11; // FIRMWARE_VER_CODE - v11 = CMD_SET/GET_DEFAULT_FLOOD_SCOPE
rsp[2] = (MAX_CONTACTS / 2 > 255) ? 255 : (MAX_CONTACTS / 2); // protocol byte, app multiplies by 2
rsp[3] = MAX_GROUP_CHANNELS;
put_le32(&rsp[4], prefs.ble_pin ? prefs.ble_pin : 123456); // BLE PIN
@@ -2604,8 +2617,8 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
}
return true;
case CMD_SET_FLOOD_SCOPE:
/* Flood scope: [cmd][0][16-byte key] or [cmd][0] (null key) */
case CMD_SET_FLOOD_SCOPE_KEY:
/* Set current send_scope key: [cmd][0][16-byte key] or [cmd][0] (null key) */
if (len >= 2 && data[1] == 0) {
if (len >= 2 + 16) {
memcpy(_send_scope.key, &data[2], sizeof(_send_scope.key));
@@ -2618,6 +2631,40 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
}
return true;
case CMD_SET_DEFAULT_FLOOD_SCOPE:
/* Set default flood scope: [cmd][name:31][key:16] or [cmd] alone (clear) */
if (len >= 1 + 31 + 16) {
int n = strnlen((const char *)&data[1], 31);
if (n > 0 && n < 31) {
memset(prefs.default_scope_name, 0, sizeof(prefs.default_scope_name));
memcpy(prefs.default_scope_name, &data[1], n);
memcpy(prefs.default_scope_key, &data[1 + 31], 16);
_store->savePrefs(prefs);
sendPacketOk();
} else {
sendPacketError(ERR_ILLEGAL_ARG);
}
} else {
memset(prefs.default_scope_name, 0, sizeof(prefs.default_scope_name));
memset(prefs.default_scope_key, 0, sizeof(prefs.default_scope_key));
_store->savePrefs(prefs);
sendPacketOk();
}
return true;
case CMD_GET_DEFAULT_FLOOD_SCOPE: {
uint8_t rsp[1 + 31 + 16];
rsp[0] = PACKET_DEFAULT_FLOOD_SCOPE;
if (strlen(prefs.default_scope_name) > 0) {
memcpy(&rsp[1], prefs.default_scope_name, 31);
memcpy(&rsp[1 + 31], prefs.default_scope_key, 16);
writeFrame(rsp, sizeof(rsp));
} else {
writeFrame(rsp, 1); /* no name or key means null */
}
return true;
}
case CMD_SEND_CONTROL_DATA:
/* Control data: [cmd][flags | 0x80][data...] */
if (len >= 2 && (data[1] & 0x80) != 0) {
+1 -17
View File
@@ -87,9 +87,6 @@ typedef void (*RadioReconfigureCallback)(void);
/* BLE PIN change callback */
typedef void (*PinChangeCallback)(uint32_t new_pin);
/* Callback for scheduling background save (called instead of blocking) */
typedef void (*SaveScheduleCallback)(void);
/**
* CompanionMesh: Application layer for ZephCore Companion device
*
@@ -141,19 +138,6 @@ public:
*/
void setPinChangeCallback(PinChangeCallback cb) { _pin_change_cb = cb; }
/**
* Set callback for scheduling background contact saves.
* When set, flushDirtyContacts() submits a work item instead of blocking.
*/
void setSaveScheduleCallback(SaveScheduleCallback cb) { _save_schedule_cb = cb; }
/**
* Synchronous flush — saves contacts + channels to flash on the calling
* thread. Use ONLY before reboot / factory reset where we MUST block
* until the write completes.
*/
void flushAllSync();
/**
* Continue contact iteration (call each main loop iteration).
* Returns true if contacts are still being sent.
@@ -255,6 +239,7 @@ protected:
uint8_t *extra, uint8_t extra_len) override;
/* Flood scope - scoped sending for region filtering */
void sendFloodScoped(const TransportKey &scope, mesh::Packet *pkt, uint32_t delay_millis);
void sendFloodScoped(const ContactInfo &recipient, mesh::Packet *pkt, uint32_t delay_millis = 0) override;
void sendFloodScoped(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t delay_millis = 0) override;
@@ -290,7 +275,6 @@ private:
GetBatteryCallback _batt_cb;
RadioReconfigureCallback _radio_reconfig_cb;
PinChangeCallback _pin_change_cb;
SaveScheduleCallback _save_schedule_cb;
/* Contact iteration state */
bool _contact_iter_active;
+67 -9
View File
@@ -437,6 +437,30 @@ bool RepeaterMesh::isLooped(const mesh::Packet* packet, const uint8_t max_counte
return n >= max_counters[hash_size];
}
void RepeaterMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis, uint8_t path_hash_size) {
if (scope.isNull()) {
sendFlood(pkt, delay_millis, path_hash_size);
} else {
uint16_t codes[2];
codes[0] = scope.calcTransportCode(pkt);
codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region?
sendFlood(pkt, codes, delay_millis, path_hash_size);
}
}
void RepeaterMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size) {
if (recv_pkt_region && !recv_pkt_region->isWildcard()) { // if _request_ packet scope is known, send reply with same scope
TransportKey scope;
if (region_map.getTransportKeysFor(*recv_pkt_region, &scope, 1) > 0) {
sendFloodScoped(scope, packet, delay_millis, path_hash_size);
} else {
sendFlood(packet, delay_millis, path_hash_size); // send un-scoped
}
} else {
sendFlood(packet, delay_millis, path_hash_size); // send un-scoped
}
}
bool RepeaterMesh::allowPacketForward(const mesh::Packet* packet) {
if (_prefs.disable_fwd) return false;
if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false;
@@ -586,10 +610,10 @@ void RepeaterMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, c
if (packet->isRouteFlood()) {
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
} else if (reply_path_len == OUT_PATH_UNKNOWN) {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
if (reply) sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
} else {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY);
@@ -656,14 +680,14 @@ void RepeaterMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
if (packet->isRouteFlood()) {
mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
} else {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
if (reply) {
if (client->out_path_len != OUT_PATH_UNKNOWN) {
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
} else {
sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
}
}
}
@@ -691,7 +715,7 @@ void RepeaterMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
mesh::Packet* ack = createAck(ack_hash);
if (ack) {
if (client->out_path_len == OUT_PATH_UNKNOWN) {
sendFlood(ack, TXT_ACK_DELAY, packet->getPathHashSize());
sendFloodReply(ack, TXT_ACK_DELAY, packet->getPathHashSize());
} else {
sendDirect(ack, client->out_path, client->out_path_len, TXT_ACK_DELAY);
}
@@ -717,7 +741,7 @@ void RepeaterMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
auto reply_pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len);
if (reply_pkt) {
if (client->out_path_len == OUT_PATH_UNKNOWN) {
sendFlood(reply_pkt, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize());
sendFloodReply(reply_pkt, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize());
} else {
sendDirect(reply_pkt, client->out_path, client->out_path_len, CLI_REPLY_DELAY_MILLIS);
}
@@ -823,6 +847,7 @@ RepeaterMesh::RepeaterMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Mil
_logging = false;
region_load_active = false;
recv_pkt_region = nullptr;
memset(default_scope.key, 0, sizeof(default_scope.key));
pending_discover_tag = 0;
pending_discover_until = 0;
@@ -869,6 +894,14 @@ void RepeaterMesh::begin(RepeaterDataStore* store) {
acl.load(_store->getAclPath(), self_id);
region_map.load(_store->getRegionsPath());
// establish default-scope from persisted default region (if any)
{
RegionEntry* r = region_map.getDefaultRegion();
if (r) {
region_map.getTransportKeysFor(*r, &default_scope, 1);
}
}
/* NOTE: Radio configuration is handled by SX126xRadio adapter using
* LoRaConfig defaults. The repeater uses the same radio params as companion.
* Dynamic radio reconfiguration (via CLI) is not yet supported - radio
@@ -994,7 +1027,7 @@ void RepeaterMesh::sendSelfAdvertisement(int delay_millis, bool flood) {
mesh::Packet* pkt = createSelfAdvert();
if (pkt) {
if (flood) {
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
sendFloodScoped(default_scope, pkt, delay_millis, _prefs.path_hash_mode + 1);
} else {
sendZeroHop(pkt, delay_millis);
}
@@ -1264,6 +1297,30 @@ void RepeaterMesh::handleCommand(uint32_t sender_timestamp, char* command, char*
} else if (n == 2 && strcmp(parts[1], "home") == 0) {
auto home = region_map.getHomeRegion();
sprintf(reply, " home is %s", home ? home->name : "*");
} else if (n >= 3 && strcmp(parts[1], "default") == 0) {
if (strcmp(parts[2], "<null>") == 0) {
region_map.setDefaultRegion(nullptr);
memset(default_scope.key, 0, sizeof(default_scope.key));
region_map.save(_store->getRegionsPath()); // persist in one atomic step
sprintf(reply, " default scope is now <null>");
} else {
auto def = region_map.findByNamePrefix(parts[2]);
if (def == nullptr) {
def = region_map.putRegion(parts[2], 0); // auto-create the default region
}
if (def) {
def->flags = 0; // make sure allow flood enabled
region_map.setDefaultRegion(def);
region_map.getTransportKeysFor(*def, &default_scope, 1);
region_map.save(_store->getRegionsPath()); // persist in one atomic step
sprintf(reply, " default scope is now %s", def->name);
} else {
strcpy(reply, "Err - region table full");
}
}
} else if (n == 2 && strcmp(parts[1], "default") == 0) {
auto def = region_map.getDefaultRegion();
sprintf(reply, " default scope is %s", def ? def->name : "<null>");
} else if (n >= 3 && strcmp(parts[1], "put") == 0) {
auto parent = n >= 4 ? region_map.findByNamePrefix(parts[3]) : &region_map.getWildcard();
if (parent == nullptr) {
@@ -1273,7 +1330,8 @@ void RepeaterMesh::handleCommand(uint32_t sender_timestamp, char* command, char*
if (region == nullptr) {
strcpy(reply, "Err - unable to put");
} else {
strcpy(reply, "OK");
region->flags = 0; // New default: enable flood
strcpy(reply, "OK - (flood allowed)");
}
}
} else if (n >= 3 && strcmp(parts[1], "remove") == 0) {
@@ -1561,7 +1619,7 @@ void RepeaterMesh::loop() {
if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
mesh::Packet* pkt = createSelfAdvert();
if (pkt) sendFlood(pkt, (uint32_t)0, _prefs.path_hash_mode + 1);
if (pkt) sendFloodScoped(default_scope, pkt, (uint32_t)0, _prefs.path_hash_mode + 1);
updateFloodAdvertTimer();
updateAdvertTimer();
} else if (next_local_advert && millisHasNowPassed(next_local_advert)) {
+5 -1
View File
@@ -94,6 +94,7 @@ class RepeaterMesh : public mesh::Mesh, public CommonCLICallbacks {
RegionMap region_map, temp_map;
RegionEntry* load_stack[8];
RegionEntry* recv_pkt_region;
TransportKey default_scope;
RateLimiter discover_limiter, anon_limiter;
uint32_t pending_discover_tag;
unsigned long pending_discover_until;
@@ -122,13 +123,14 @@ class RepeaterMesh : public mesh::Mesh, public CommonCLICallbacks {
#endif
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
void sendNodeDiscoverReq();
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
uint8_t handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len);
mesh::Packet* createSelfAdvert();
void sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis, uint8_t path_hash_size);
void sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size);
protected:
uint8_t getDutyCyclePercent() const override {
@@ -187,6 +189,8 @@ public:
void begin(RepeaterDataStore* store);
void sendNodeDiscoverReq();
/* CommonCLICallbacks */
const char* getFirmwareVer() override { return FIRMWARE_VERSION; }
const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }