Merge branch 'transportcodes' into dev
This commit is contained in:
@@ -50,6 +50,8 @@
|
|||||||
#define CMD_SEND_BINARY_REQ 50
|
#define CMD_SEND_BINARY_REQ 50
|
||||||
#define CMD_FACTORY_RESET 51
|
#define CMD_FACTORY_RESET 51
|
||||||
#define CMD_SEND_PATH_DISCOVERY_REQ 52
|
#define CMD_SEND_PATH_DISCOVERY_REQ 52
|
||||||
|
#define CMD_SET_FLOOD_SCOPE 54 // v8+
|
||||||
|
#define CMD_SEND_CONTROL_DATA 55 // v8+
|
||||||
|
|
||||||
#define RESP_CODE_OK 0
|
#define RESP_CODE_OK 0
|
||||||
#define RESP_CODE_ERR 1
|
#define RESP_CODE_ERR 1
|
||||||
@@ -99,6 +101,7 @@
|
|||||||
#define PUSH_CODE_TELEMETRY_RESPONSE 0x8B
|
#define PUSH_CODE_TELEMETRY_RESPONSE 0x8B
|
||||||
#define PUSH_CODE_BINARY_RESPONSE 0x8C
|
#define PUSH_CODE_BINARY_RESPONSE 0x8C
|
||||||
#define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D
|
#define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D
|
||||||
|
#define PUSH_CODE_CONTROL_DATA 0x8E // v8+
|
||||||
|
|
||||||
#define ERR_CODE_UNSUPPORTED_CMD 1
|
#define ERR_CODE_UNSUPPORTED_CMD 1
|
||||||
#define ERR_CODE_NOT_FOUND 2
|
#define ERR_CODE_NOT_FOUND 2
|
||||||
@@ -378,6 +381,35 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
||||||
|
// REVISIT: try to determine which Region (from transport_codes[1]) that Sender is indicating for replies/responses
|
||||||
|
// if unknown, fallback to finding Region from transport_codes[0], the 'scope' used by Sender
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||||
|
// TODO: dynamic send_scope, depending on recipient and current 'home' Region
|
||||||
|
if (send_scope.isNull()) {
|
||||||
|
sendFlood(pkt, delay_millis);
|
||||||
|
} else {
|
||||||
|
uint16_t codes[2];
|
||||||
|
codes[0] = send_scope.calcTransportCode(pkt);
|
||||||
|
codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region?
|
||||||
|
sendFlood(pkt, codes, delay_millis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||||
|
// TODO: have per-channel send_scope
|
||||||
|
if (send_scope.isNull()) {
|
||||||
|
sendFlood(pkt, delay_millis);
|
||||||
|
} else {
|
||||||
|
uint16_t codes[2];
|
||||||
|
codes[0] = send_scope.calcTransportCode(pkt);
|
||||||
|
codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region?
|
||||||
|
sendFlood(pkt, codes, delay_millis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
|
void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
|
||||||
const char *text) {
|
const char *text) {
|
||||||
markConnectionActive(from); // in case this is from a server, and we have a connection
|
markConnectionActive(from); // in case this is from a server, and we have a connection
|
||||||
@@ -596,6 +628,26 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i
|
|||||||
return BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len);
|
return BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MyMesh::onControlDataRecv(mesh::Packet *packet) {
|
||||||
|
if (packet->payload_len + 4 > sizeof(out_frame)) {
|
||||||
|
MESH_DEBUG_PRINTLN("onControlDataRecv(), payload_len too long: %d", packet->payload_len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
out_frame[i++] = PUSH_CODE_CONTROL_DATA;
|
||||||
|
out_frame[i++] = (int8_t)(_radio->getLastSNR() * 4);
|
||||||
|
out_frame[i++] = (int8_t)(_radio->getLastRSSI());
|
||||||
|
out_frame[i++] = packet->path_len;
|
||||||
|
memcpy(&out_frame[i], packet->payload, packet->payload_len);
|
||||||
|
i += packet->payload_len;
|
||||||
|
|
||||||
|
if (_serial->isConnected()) {
|
||||||
|
_serial->writeFrame(out_frame, i);
|
||||||
|
} else {
|
||||||
|
MESH_DEBUG_PRINTLN("onControlDataRecv(), data received while app offline");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MyMesh::onRawDataRecv(mesh::Packet *packet) {
|
void MyMesh::onRawDataRecv(mesh::Packet *packet) {
|
||||||
if (packet->payload_len + 4 > sizeof(out_frame)) {
|
if (packet->payload_len + 4 > sizeof(out_frame)) {
|
||||||
MESH_DEBUG_PRINTLN("onRawDataRecv(), payload_len too long: %d", packet->payload_len);
|
MESH_DEBUG_PRINTLN("onRawDataRecv(), payload_len too long: %d", packet->payload_len);
|
||||||
@@ -663,6 +715,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
|||||||
sign_data = NULL;
|
sign_data = NULL;
|
||||||
dirty_contacts_expiry = 0;
|
dirty_contacts_expiry = 0;
|
||||||
memset(advert_paths, 0, sizeof(advert_paths));
|
memset(advert_paths, 0, sizeof(advert_paths));
|
||||||
|
memset(send_scope.key, 0, sizeof(send_scope.key));
|
||||||
|
|
||||||
// defaults
|
// defaults
|
||||||
memset(&_prefs, 0, sizeof(_prefs));
|
memset(&_prefs, 0, sizeof(_prefs));
|
||||||
@@ -1485,6 +1538,21 @@ void MyMesh::handleCmdFrame(size_t len) {
|
|||||||
} else {
|
} else {
|
||||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||||
}
|
}
|
||||||
|
} else if (cmd_frame[0] == CMD_SET_FLOOD_SCOPE && len >= 2 && cmd_frame[1] == 0) {
|
||||||
|
if (len >= 2 + 16) {
|
||||||
|
memcpy(send_scope.key, &cmd_frame[2], sizeof(send_scope.key)); // set curr scope TransportKey
|
||||||
|
} else {
|
||||||
|
memset(send_scope.key, 0, sizeof(send_scope.key)); // set scope to null
|
||||||
|
}
|
||||||
|
writeOKFrame();
|
||||||
|
} else if (cmd_frame[0] == CMD_SEND_CONTROL_DATA && len >= 2 && (cmd_frame[1] & 0x80) != 0) {
|
||||||
|
auto resp = createControlData(&cmd_frame[1], len - 1);
|
||||||
|
if (resp) {
|
||||||
|
sendZeroHop(resp);
|
||||||
|
writeOKFrame();
|
||||||
|
} else {
|
||||||
|
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||||
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "AbstractUITask.h"
|
#include "AbstractUITask.h"
|
||||||
|
|
||||||
/*------------ Frame Protocol --------------*/
|
/*------------ Frame Protocol --------------*/
|
||||||
#define FIRMWARE_VER_CODE 7
|
#define FIRMWARE_VER_CODE 8
|
||||||
|
|
||||||
#ifndef FIRMWARE_BUILD_DATE
|
#ifndef FIRMWARE_BUILD_DATE
|
||||||
#define FIRMWARE_BUILD_DATE "2 Oct 2025"
|
#define FIRMWARE_BUILD_DATE "2 Oct 2025"
|
||||||
@@ -68,6 +68,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <helpers/BaseChatMesh.h>
|
#include <helpers/BaseChatMesh.h>
|
||||||
|
#include <helpers/TransportKeyStore.h>
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------------------- */
|
||||||
|
|
||||||
@@ -106,6 +107,10 @@ protected:
|
|||||||
int getInterferenceThreshold() const override;
|
int getInterferenceThreshold() const override;
|
||||||
int calcRxDelay(float score, uint32_t air_time) const override;
|
int calcRxDelay(float score, uint32_t air_time) const override;
|
||||||
uint8_t getExtraAckTransmitCount() const override;
|
uint8_t getExtraAckTransmitCount() const override;
|
||||||
|
bool filterRecvFloodPacket(mesh::Packet* packet) override;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
|
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
|
||||||
bool isAutoAddEnabled() const override;
|
bool isAutoAddEnabled() const override;
|
||||||
@@ -128,6 +133,7 @@ protected:
|
|||||||
uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
|
uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
|
||||||
uint8_t len, uint8_t *reply) override;
|
uint8_t len, uint8_t *reply) override;
|
||||||
void onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) override;
|
void onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) override;
|
||||||
|
void onControlDataRecv(mesh::Packet *packet) override;
|
||||||
void onRawDataRecv(mesh::Packet *packet) override;
|
void onRawDataRecv(mesh::Packet *packet) override;
|
||||||
void onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags,
|
void onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags,
|
||||||
const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) override;
|
const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) override;
|
||||||
@@ -191,6 +197,8 @@ private:
|
|||||||
uint32_t sign_data_len;
|
uint32_t sign_data_len;
|
||||||
unsigned long dirty_contacts_expiry;
|
unsigned long dirty_contacts_expiry;
|
||||||
|
|
||||||
|
TransportKey send_scope;
|
||||||
|
|
||||||
uint8_t cmd_frame[MAX_FRAME_SIZE + 1];
|
uint8_t cmd_frame[MAX_FRAME_SIZE + 1];
|
||||||
uint8_t out_frame[MAX_FRAME_SIZE + 1];
|
uint8_t out_frame[MAX_FRAME_SIZE + 1];
|
||||||
CayenneLPP telemetry;
|
CayenneLPP telemetry;
|
||||||
|
|||||||
@@ -306,6 +306,10 @@ File MyMesh::openAppend(const char *fname) {
|
|||||||
bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||||
if (_prefs.disable_fwd) return false;
|
if (_prefs.disable_fwd) return false;
|
||||||
if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false;
|
if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false;
|
||||||
|
if (packet->isRouteFlood() && recv_pkt_region == NULL) {
|
||||||
|
MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,6 +408,23 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
|||||||
return getRNG()->nextInt(0, 5*t + 1);
|
return getRNG()->nextInt(0, 5*t + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
|
||||||
|
// just try to determine region for packet (apply later in allowPacketForward())
|
||||||
|
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
|
||||||
|
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
|
||||||
|
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
|
||||||
|
if (region_map.getWildcard().flags & REGION_DENY_FLOOD) {
|
||||||
|
recv_pkt_region = NULL;
|
||||||
|
} else {
|
||||||
|
recv_pkt_region = ®ion_map.getWildcard();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
recv_pkt_region = NULL;
|
||||||
|
}
|
||||||
|
// do normal processing
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
|
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
|
||||||
uint8_t *data, size_t len) {
|
uint8_t *data, size_t len) {
|
||||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin
|
if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin
|
||||||
@@ -412,7 +433,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
|||||||
memcpy(×tamp, data, 4);
|
memcpy(×tamp, data, 4);
|
||||||
|
|
||||||
data[len] = 0; // ensure null terminator
|
data[len] = 0; // ensure null terminator
|
||||||
uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
uint8_t reply_len;
|
||||||
|
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
||||||
|
reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||||
|
//} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes
|
||||||
|
// TODO
|
||||||
|
} else {
|
||||||
|
reply_len = 0; // unknown request type
|
||||||
|
}
|
||||||
|
|
||||||
if (reply_len == 0) return; // invalid request
|
if (reply_len == 0) return; // invalid request
|
||||||
|
|
||||||
@@ -590,10 +618,42 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CTL_TYPE_NODE_DISCOVER_REQ 0x80
|
||||||
|
#define CTL_TYPE_NODE_DISCOVER_RESP 0x90
|
||||||
|
|
||||||
|
void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
||||||
|
uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits
|
||||||
|
if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 && discover_limiter.allow(rtc_clock.getCurrentTime())) {
|
||||||
|
int i = 1;
|
||||||
|
uint8_t filter = packet->payload[i++];
|
||||||
|
uint32_t tag;
|
||||||
|
memcpy(&tag, &packet->payload[i], 4); i += 4;
|
||||||
|
uint32_t since;
|
||||||
|
if (packet->payload_len >= i+4) { // optional since field
|
||||||
|
memcpy(&since, &packet->payload[i], 4); i += 4;
|
||||||
|
} else {
|
||||||
|
since = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((filter & (1 << ADV_TYPE_REPEATER)) != 0 && _prefs.discovery_mod_timestamp >= since) {
|
||||||
|
uint8_t data[6 + PUB_KEY_SIZE];
|
||||||
|
data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_REPEATER; // low 4-bits for node type
|
||||||
|
data[1] = packet->_snr; // let sender know the inbound SNR ( x 4)
|
||||||
|
memcpy(&data[2], &tag, 4); // include tag from request, for client to match to
|
||||||
|
memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE);
|
||||||
|
auto resp = createControlData(data, sizeof(data));
|
||||||
|
if (resp) {
|
||||||
|
sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
|
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
|
||||||
mesh::RTCClock &rtc, mesh::MeshTables &tables)
|
mesh::RTCClock &rtc, mesh::MeshTables &tables)
|
||||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||||
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store),
|
||||||
|
discover_limiter(4, 120) // max 4 every 2 minutes
|
||||||
#if defined(WITH_RS232_BRIDGE)
|
#if defined(WITH_RS232_BRIDGE)
|
||||||
, bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc)
|
, bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc)
|
||||||
#endif
|
#endif
|
||||||
@@ -607,6 +667,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
|||||||
dirty_contacts_expiry = 0;
|
dirty_contacts_expiry = 0;
|
||||||
set_radio_at = revert_radio_at = 0;
|
set_radio_at = revert_radio_at = 0;
|
||||||
_logging = false;
|
_logging = false;
|
||||||
|
region_load_active = false;
|
||||||
|
|
||||||
#if MAX_NEIGHBOURS
|
#if MAX_NEIGHBOURS
|
||||||
memset(neighbours, 0, sizeof(neighbours));
|
memset(neighbours, 0, sizeof(neighbours));
|
||||||
@@ -652,8 +713,9 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
|||||||
_fs = fs;
|
_fs = fs;
|
||||||
// load persisted prefs
|
// load persisted prefs
|
||||||
_cli.loadPrefs(_fs);
|
_cli.loadPrefs(_fs);
|
||||||
|
|
||||||
acl.load(_fs);
|
acl.load(_fs);
|
||||||
|
// TODO: key_store.begin();
|
||||||
|
region_map.load(_fs);
|
||||||
|
|
||||||
#if defined(WITH_BRIDGE)
|
#if defined(WITH_BRIDGE)
|
||||||
if (_prefs.bridge_enabled) {
|
if (_prefs.bridge_enabled) {
|
||||||
@@ -829,8 +891,41 @@ void MyMesh::clearStats() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
|
void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
|
||||||
while (*command == ' ')
|
if (region_load_active) {
|
||||||
command++; // skip leading spaces
|
if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation
|
||||||
|
region_map = temp_map; // copy over the temp instance as new current map
|
||||||
|
region_load_active = false;
|
||||||
|
|
||||||
|
sprintf(reply, "OK - loaded %d regions", region_map.getCount());
|
||||||
|
} else {
|
||||||
|
char *np = command;
|
||||||
|
while (*np == ' ') np++; // skip indent
|
||||||
|
int indent = np - command;
|
||||||
|
|
||||||
|
char *ep = np;
|
||||||
|
while (RegionMap::is_name_char(*ep)) ep++;
|
||||||
|
if (*ep) { *ep++ = 0; } // set null terminator for end of name
|
||||||
|
|
||||||
|
while (*ep && *ep != 'F') ep++; // look for (optional) flags
|
||||||
|
|
||||||
|
if (indent > 0 && indent < 8 && strlen(np) > 0) {
|
||||||
|
auto parent = load_stack[indent - 1];
|
||||||
|
if (parent) {
|
||||||
|
auto old = region_map.findByName(np);
|
||||||
|
auto nw = temp_map.putRegion(np, parent->id, old ? old->id : 0); // carry-over the current ID (if name already exists)
|
||||||
|
if (nw) {
|
||||||
|
nw->flags = old ? old->flags : (*ep == 'F' ? 0 : REGION_DENY_FLOOD); // carry-over flags from curr
|
||||||
|
|
||||||
|
load_stack[indent] = nw; // keep pointers to parent regions, to resolve parent_id's
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reply[0] = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*command == ' ') command++; // skip leading spaces
|
||||||
|
|
||||||
if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
|
if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
|
||||||
memcpy(reply, command, 3); // reflect the prefix back
|
memcpy(reply, command, 3); // reflect the prefix back
|
||||||
@@ -872,6 +967,88 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
|||||||
Serial.printf("\n");
|
Serial.printf("\n");
|
||||||
}
|
}
|
||||||
reply[0] = 0;
|
reply[0] = 0;
|
||||||
|
} else if (memcmp(command, "region", 6) == 0) {
|
||||||
|
reply[0] = 0;
|
||||||
|
|
||||||
|
const char* parts[4];
|
||||||
|
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
|
||||||
|
if (n == 1 && sender_timestamp == 0) {
|
||||||
|
region_map.exportTo(Serial);
|
||||||
|
} else if (n >= 2 && strcmp(parts[1], "load") == 0) {
|
||||||
|
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
|
||||||
|
memset(load_stack, 0, sizeof(load_stack));
|
||||||
|
load_stack[0] = &temp_map.getWildcard();
|
||||||
|
region_load_active = true;
|
||||||
|
} else if (n >= 2 && strcmp(parts[1], "save") == 0) {
|
||||||
|
_prefs.discovery_mod_timestamp = rtc_clock.getCurrentTime(); // this node is now 'modified' (for discovery info)
|
||||||
|
savePrefs();
|
||||||
|
bool success = region_map.save(_fs);
|
||||||
|
strcpy(reply, success ? "OK" : "Err - save failed");
|
||||||
|
} else if (n >= 3 && strcmp(parts[1], "allowf") == 0) {
|
||||||
|
auto region = region_map.findByNamePrefix(parts[2]);
|
||||||
|
if (region) {
|
||||||
|
region->flags &= ~REGION_DENY_FLOOD;
|
||||||
|
strcpy(reply, "OK");
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - unknown region");
|
||||||
|
}
|
||||||
|
} else if (n >= 3 && strcmp(parts[1], "denyf") == 0) {
|
||||||
|
auto region = region_map.findByNamePrefix(parts[2]);
|
||||||
|
if (region) {
|
||||||
|
region->flags |= REGION_DENY_FLOOD;
|
||||||
|
strcpy(reply, "OK");
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - unknown region");
|
||||||
|
}
|
||||||
|
} else if (n >= 3 && strcmp(parts[1], "get") == 0) {
|
||||||
|
auto region = region_map.findByNamePrefix(parts[2]);
|
||||||
|
if (region) {
|
||||||
|
auto parent = region_map.findById(region->parent);
|
||||||
|
if (parent && parent->id != 0) {
|
||||||
|
sprintf(reply, " %s (%s) %s", region->name, parent->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
|
||||||
|
} else {
|
||||||
|
sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - unknown region");
|
||||||
|
}
|
||||||
|
} else if (n >= 3 && strcmp(parts[1], "home") == 0) {
|
||||||
|
auto home = region_map.findByNamePrefix(parts[2]);
|
||||||
|
if (home) {
|
||||||
|
region_map.setHomeRegion(home);
|
||||||
|
sprintf(reply, " home is now %s", home->name);
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - unknown region");
|
||||||
|
}
|
||||||
|
} 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], "put") == 0) {
|
||||||
|
auto parent = n >= 4 ? region_map.findByNamePrefix(parts[3]) : ®ion_map.getWildcard();
|
||||||
|
if (parent == NULL) {
|
||||||
|
strcpy(reply, "Err - unknown parent");
|
||||||
|
} else {
|
||||||
|
auto region = region_map.putRegion(parts[2], parent->id);
|
||||||
|
if (region == NULL) {
|
||||||
|
strcpy(reply, "Err - unable to put");
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "OK");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (n >= 3 && strcmp(parts[1], "remove") == 0) {
|
||||||
|
auto region = region_map.findByName(parts[2]);
|
||||||
|
if (region) {
|
||||||
|
if (region_map.removeRegion(*region)) {
|
||||||
|
strcpy(reply, "OK");
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - not empty");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - not found");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - ??");
|
||||||
|
}
|
||||||
} else{
|
} else{
|
||||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
#include <helpers/StaticPoolPacketManager.h>
|
#include <helpers/StaticPoolPacketManager.h>
|
||||||
#include <helpers/StatsFormatHelper.h>
|
#include <helpers/StatsFormatHelper.h>
|
||||||
#include <helpers/TxtDataHelpers.h>
|
#include <helpers/TxtDataHelpers.h>
|
||||||
|
#include <helpers/RegionMap.h>
|
||||||
|
#include "RateLimiter.h"
|
||||||
|
|
||||||
#ifdef WITH_BRIDGE
|
#ifdef WITH_BRIDGE
|
||||||
extern AbstractBridge* bridge;
|
extern AbstractBridge* bridge;
|
||||||
@@ -87,6 +89,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
|||||||
CommonCLI _cli;
|
CommonCLI _cli;
|
||||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||||
ClientACL acl;
|
ClientACL acl;
|
||||||
|
TransportKeyStore key_store;
|
||||||
|
RegionMap region_map, temp_map;
|
||||||
|
RegionEntry* load_stack[8];
|
||||||
|
RegionEntry* recv_pkt_region;
|
||||||
|
RateLimiter discover_limiter;
|
||||||
|
bool region_load_active;
|
||||||
unsigned long dirty_contacts_expiry;
|
unsigned long dirty_contacts_expiry;
|
||||||
#if MAX_NEIGHBOURS
|
#if MAX_NEIGHBOURS
|
||||||
NeighbourInfo neighbours[MAX_NEIGHBOURS];
|
NeighbourInfo neighbours[MAX_NEIGHBOURS];
|
||||||
@@ -144,12 +152,15 @@ protected:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool filterRecvFloodPacket(mesh::Packet* pkt) override;
|
||||||
|
|
||||||
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
|
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
|
||||||
int searchPeersByHash(const uint8_t* hash) override;
|
int searchPeersByHash(const uint8_t* hash) override;
|
||||||
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
|
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
|
||||||
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len);
|
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len);
|
||||||
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
||||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||||
|
void onControlDataRecv(mesh::Packet* packet) override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
|
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
|
||||||
|
|||||||
23
examples/simple_repeater/RateLimiter.h
Normal file
23
examples/simple_repeater/RateLimiter.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
class RateLimiter {
|
||||||
|
uint32_t _start_timestamp;
|
||||||
|
uint32_t _secs;
|
||||||
|
uint16_t _maximum, _count;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RateLimiter(uint16_t maximum, uint32_t secs): _maximum(maximum), _secs(secs), _start_timestamp(0), _count(0) { }
|
||||||
|
|
||||||
|
bool allow(uint32_t now) {
|
||||||
|
if (now < _start_timestamp + _secs) {
|
||||||
|
_count++;
|
||||||
|
if (_count > _maximum) return false; // deny
|
||||||
|
} else { // time window now expired
|
||||||
|
_start_timestamp = now;
|
||||||
|
_count = 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -91,14 +91,16 @@ void loop() {
|
|||||||
if (c != '\n') {
|
if (c != '\n') {
|
||||||
command[len++] = c;
|
command[len++] = c;
|
||||||
command[len] = 0;
|
command[len] = 0;
|
||||||
|
Serial.print(c);
|
||||||
}
|
}
|
||||||
Serial.print(c);
|
if (c == '\r') break;
|
||||||
}
|
}
|
||||||
if (len == sizeof(command)-1) { // command buffer full
|
if (len == sizeof(command)-1) { // command buffer full
|
||||||
command[sizeof(command)-1] = '\r';
|
command[sizeof(command)-1] = '\r';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||||
|
Serial.print('\n');
|
||||||
command[len - 1] = 0; // replace newline with C string null terminator
|
command[len - 1] = 0; // replace newline with C string null terminator
|
||||||
char reply[160];
|
char reply[160];
|
||||||
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||||
|
|||||||
@@ -449,7 +449,14 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con
|
|||||||
memcpy(×tamp, data, 4);
|
memcpy(×tamp, data, 4);
|
||||||
|
|
||||||
data[len] = 0; // ensure null terminator
|
data[len] = 0; // ensure null terminator
|
||||||
uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
uint8_t reply_len;
|
||||||
|
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
||||||
|
reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||||
|
//} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes
|
||||||
|
// TODO
|
||||||
|
} else {
|
||||||
|
reply_len = 0; // unknown request type
|
||||||
|
}
|
||||||
|
|
||||||
if (reply_len == 0) return; // invalid request
|
if (reply_len == 0) return; // invalid request
|
||||||
|
|
||||||
|
|||||||
51
src/Mesh.cpp
51
src/Mesh.cpp
@@ -68,6 +68,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
|||||||
return ACTION_RELEASE;
|
return ACTION_RELEASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_CONTROL && (pkt->payload[0] & 0x80) != 0) {
|
||||||
|
if (pkt->path_len == 0) {
|
||||||
|
onControlDataRecv(pkt);
|
||||||
|
}
|
||||||
|
// just zero-hop control packets allowed (for this subset of payloads)
|
||||||
|
return ACTION_RELEASE;
|
||||||
|
}
|
||||||
|
|
||||||
if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) {
|
if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) {
|
||||||
if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) {
|
if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) {
|
||||||
if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
|
if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
|
||||||
@@ -90,6 +98,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
|||||||
return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard.
|
return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pkt->isRouteFlood() && filterRecvFloodPacket(pkt)) return ACTION_RELEASE;
|
||||||
|
|
||||||
DispatcherAction action = ACTION_RELEASE;
|
DispatcherAction action = ACTION_RELEASE;
|
||||||
|
|
||||||
switch (pkt->getPayloadType()) {
|
switch (pkt->getPayloadType()) {
|
||||||
@@ -587,6 +597,22 @@ Packet* Mesh::createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags) {
|
|||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Packet* Mesh::createControlData(const uint8_t* data, size_t len) {
|
||||||
|
if (len > sizeof(Packet::payload)) return NULL; // invalid arg
|
||||||
|
|
||||||
|
Packet* packet = obtainNewPacket();
|
||||||
|
if (packet == NULL) {
|
||||||
|
MESH_DEBUG_PRINTLN("%s Mesh::createControlData(): error, packet pool empty", getLogDateTime());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
packet->header = (PAYLOAD_TYPE_CONTROL << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
|
||||||
|
|
||||||
|
memcpy(packet->payload, data, len);
|
||||||
|
packet->payload_len = len;
|
||||||
|
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) {
|
void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) {
|
||||||
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||||
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
|
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
|
||||||
@@ -610,6 +636,31 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) {
|
|||||||
sendPacket(packet, pri, delay_millis);
|
sendPacket(packet, pri, delay_millis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) {
|
||||||
|
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||||
|
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
packet->header &= ~PH_ROUTE_MASK;
|
||||||
|
packet->header |= ROUTE_TYPE_TRANSPORT_FLOOD;
|
||||||
|
packet->transport_codes[0] = transport_codes[0];
|
||||||
|
packet->transport_codes[1] = transport_codes[1];
|
||||||
|
packet->path_len = 0;
|
||||||
|
|
||||||
|
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||||
|
|
||||||
|
uint8_t pri;
|
||||||
|
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
|
||||||
|
pri = 2;
|
||||||
|
} else if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT) {
|
||||||
|
pri = 3; // de-prioritie these
|
||||||
|
} else {
|
||||||
|
pri = 1;
|
||||||
|
}
|
||||||
|
sendPacket(packet, pri, delay_millis);
|
||||||
|
}
|
||||||
|
|
||||||
void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uint32_t delay_millis) {
|
void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uint32_t delay_millis) {
|
||||||
packet->header &= ~PH_ROUTE_MASK;
|
packet->header &= ~PH_ROUTE_MASK;
|
||||||
packet->header |= ROUTE_TYPE_DIRECT;
|
packet->header |= ROUTE_TYPE_DIRECT;
|
||||||
|
|||||||
18
src/Mesh.h
18
src/Mesh.h
@@ -43,6 +43,12 @@ protected:
|
|||||||
*/
|
*/
|
||||||
DispatcherAction routeRecvPacket(Packet* packet);
|
DispatcherAction routeRecvPacket(Packet* packet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Called _before_ the packet is dispatched to the on..Recv() methods.
|
||||||
|
* \returns true, if given packet should be NOT be processed.
|
||||||
|
*/
|
||||||
|
virtual bool filterRecvFloodPacket(Packet* packet) { return false; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Check whether this packet should be forwarded (re-transmitted) or not.
|
* \brief Check whether this packet should be forwarded (re-transmitted) or not.
|
||||||
* Is sub-classes responsibility to make sure given packet is only transmitted ONCE (by this node)
|
* Is sub-classes responsibility to make sure given packet is only transmitted ONCE (by this node)
|
||||||
@@ -128,6 +134,11 @@ protected:
|
|||||||
*/
|
*/
|
||||||
virtual void onPathRecv(Packet* packet, Identity& sender, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { }
|
virtual void onPathRecv(Packet* packet, Identity& sender, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A control packet has been received.
|
||||||
|
*/
|
||||||
|
virtual void onControlDataRecv(Packet* packet) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A packet with PAYLOAD_TYPE_RAW_CUSTOM has been received.
|
* \brief A packet with PAYLOAD_TYPE_RAW_CUSTOM has been received.
|
||||||
*/
|
*/
|
||||||
@@ -180,12 +191,19 @@ public:
|
|||||||
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
|
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
|
||||||
Packet* createRawData(const uint8_t* data, size_t len);
|
Packet* createRawData(const uint8_t* data, size_t len);
|
||||||
Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0);
|
Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0);
|
||||||
|
Packet* createControlData(const uint8_t* data, size_t len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief send a locally-generated Packet with flood routing
|
* \brief send a locally-generated Packet with flood routing
|
||||||
*/
|
*/
|
||||||
void sendFlood(Packet* packet, uint32_t delay_millis=0);
|
void sendFlood(Packet* packet, uint32_t delay_millis=0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief send a locally-generated Packet with flood routing
|
||||||
|
* \param transport_codes array of 2 codes to attach to packet
|
||||||
|
*/
|
||||||
|
void sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief send a locally-generated Packet with Direct routing
|
* \brief send a locally-generated Packet with Direct routing
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace mesh {
|
|||||||
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
|
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
|
||||||
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop
|
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop
|
||||||
#define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets
|
#define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets
|
||||||
|
#define PAYLOAD_TYPE_CONTROL 0x0B // a control/discovery packet
|
||||||
//...
|
//...
|
||||||
#define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc
|
#define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,13 @@
|
|||||||
#define TXT_ACK_DELAY 200
|
#define TXT_ACK_DELAY 200
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void BaseChatMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||||
|
sendFlood(pkt, delay_millis);
|
||||||
|
}
|
||||||
|
void BaseChatMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||||
|
sendFlood(pkt, delay_millis);
|
||||||
|
}
|
||||||
|
|
||||||
mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name) {
|
mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name) {
|
||||||
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
||||||
uint8_t app_data_len;
|
uint8_t app_data_len;
|
||||||
@@ -34,7 +41,7 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl
|
|||||||
void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
||||||
if (dest.out_path_len < 0) {
|
if (dest.out_path_len < 0) {
|
||||||
mesh::Packet* ack = createAck(ack_hash);
|
mesh::Packet* ack = createAck(ack_hash);
|
||||||
if (ack) sendFlood(ack, TXT_ACK_DELAY);
|
if (ack) sendFloodScoped(dest, ack, TXT_ACK_DELAY);
|
||||||
} else {
|
} else {
|
||||||
uint32_t d = TXT_ACK_DELAY;
|
uint32_t d = TXT_ACK_DELAY;
|
||||||
if (getExtraAckTransmitCount() > 0) {
|
if (getExtraAckTransmitCount() > 0) {
|
||||||
@@ -175,7 +182,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
|||||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
||||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||||
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
||||||
if (path) sendFlood(path, TXT_ACK_DELAY);
|
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
|
||||||
} else {
|
} else {
|
||||||
sendAckTo(from, ack_hash);
|
sendAckTo(from, ack_hash);
|
||||||
}
|
}
|
||||||
@@ -186,7 +193,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
|||||||
if (packet->isRouteFlood()) {
|
if (packet->isRouteFlood()) {
|
||||||
// let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra)
|
// let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra)
|
||||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0);
|
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0);
|
||||||
if (path) sendFlood(path);
|
if (path) sendFloodScoped(from, path);
|
||||||
}
|
}
|
||||||
} else if (flags == TXT_TYPE_SIGNED_PLAIN) {
|
} else if (flags == TXT_TYPE_SIGNED_PLAIN) {
|
||||||
if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date
|
if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date
|
||||||
@@ -202,7 +209,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
|||||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
||||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||||
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
||||||
if (path) sendFlood(path, TXT_ACK_DELAY);
|
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
|
||||||
} else {
|
} else {
|
||||||
sendAckTo(from, ack_hash);
|
sendAckTo(from, ack_hash);
|
||||||
}
|
}
|
||||||
@@ -218,14 +225,14 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
|||||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||||
PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len);
|
PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len);
|
||||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
if (path) sendFloodScoped(from, path, SERVER_RESPONSE_DELAY);
|
||||||
} else {
|
} else {
|
||||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len);
|
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len);
|
||||||
if (reply) {
|
if (reply) {
|
||||||
if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT
|
if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||||
sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY);
|
sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY);
|
||||||
} else {
|
} else {
|
||||||
sendFlood(reply, SERVER_RESPONSE_DELAY);
|
sendFloodScoped(from, reply, SERVER_RESPONSE_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,7 +353,7 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp,
|
|||||||
|
|
||||||
int rc;
|
int rc;
|
||||||
if (recipient.out_path_len < 0) {
|
if (recipient.out_path_len < 0) {
|
||||||
sendFlood(pkt);
|
sendFloodScoped(recipient, pkt);
|
||||||
txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t));
|
txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t));
|
||||||
rc = MSG_SEND_SENT_FLOOD;
|
rc = MSG_SEND_SENT_FLOOD;
|
||||||
} else {
|
} else {
|
||||||
@@ -372,7 +379,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest
|
|||||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||||
int rc;
|
int rc;
|
||||||
if (recipient.out_path_len < 0) {
|
if (recipient.out_path_len < 0) {
|
||||||
sendFlood(pkt);
|
sendFloodScoped(recipient, pkt);
|
||||||
txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t));
|
txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t));
|
||||||
rc = MSG_SEND_SENT_FLOOD;
|
rc = MSG_SEND_SENT_FLOOD;
|
||||||
} else {
|
} else {
|
||||||
@@ -398,7 +405,7 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& chan
|
|||||||
|
|
||||||
auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len);
|
auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len);
|
||||||
if (pkt) {
|
if (pkt) {
|
||||||
sendFlood(pkt);
|
sendFloodScoped(channel, pkt);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -460,7 +467,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password,
|
|||||||
if (pkt) {
|
if (pkt) {
|
||||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||||
if (recipient.out_path_len < 0) {
|
if (recipient.out_path_len < 0) {
|
||||||
sendFlood(pkt);
|
sendFloodScoped(recipient, pkt);
|
||||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||||
return MSG_SEND_SENT_FLOOD;
|
return MSG_SEND_SENT_FLOOD;
|
||||||
} else {
|
} else {
|
||||||
@@ -487,7 +494,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_
|
|||||||
if (pkt) {
|
if (pkt) {
|
||||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||||
if (recipient.out_path_len < 0) {
|
if (recipient.out_path_len < 0) {
|
||||||
sendFlood(pkt);
|
sendFloodScoped(recipient, pkt);
|
||||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||||
return MSG_SEND_SENT_FLOOD;
|
return MSG_SEND_SENT_FLOOD;
|
||||||
} else {
|
} else {
|
||||||
@@ -514,7 +521,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u
|
|||||||
if (pkt) {
|
if (pkt) {
|
||||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||||
if (recipient.out_path_len < 0) {
|
if (recipient.out_path_len < 0) {
|
||||||
sendFlood(pkt);
|
sendFloodScoped(recipient, pkt);
|
||||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||||
return MSG_SEND_SENT_FLOOD;
|
return MSG_SEND_SENT_FLOOD;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ protected:
|
|||||||
virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0;
|
virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0;
|
||||||
virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len);
|
virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len);
|
||||||
|
|
||||||
|
virtual void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0);
|
||||||
|
virtual void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0);
|
||||||
|
|
||||||
// storage concepts, for sub-classes to override/implement
|
// storage concepts, for sub-classes to override/implement
|
||||||
virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented
|
virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented
|
||||||
virtual bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) { return false; }
|
virtual bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) { return false; }
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
|||||||
file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
||||||
file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
||||||
file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161
|
file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161
|
||||||
// 162
|
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||||
|
// 166
|
||||||
|
|
||||||
// sanitise bad pref values
|
// sanitise bad pref values
|
||||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||||
@@ -146,7 +147,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
|||||||
file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
||||||
file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
||||||
file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
|
file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
|
||||||
// 162
|
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||||
|
// 166
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ struct NodePrefs { // persisted to file
|
|||||||
uint8_t gps_enabled;
|
uint8_t gps_enabled;
|
||||||
uint32_t gps_interval; // in seconds
|
uint32_t gps_interval; // in seconds
|
||||||
uint8_t advert_loc_policy;
|
uint8_t advert_loc_policy;
|
||||||
|
uint32_t discovery_mod_timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CommonCLICallbacks {
|
class CommonCLICallbacks {
|
||||||
|
|||||||
237
src/helpers/RegionMap.cpp
Normal file
237
src/helpers/RegionMap.cpp
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
#include "RegionMap.h"
|
||||||
|
#include <helpers/TxtDataHelpers.h>
|
||||||
|
#include <SHA256.h>
|
||||||
|
|
||||||
|
RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) {
|
||||||
|
next_id = 1; num_regions = 0; home_id = 0;
|
||||||
|
wildcard.id = wildcard.parent = 0;
|
||||||
|
wildcard.flags = 0; // default behaviour, allow flood and direct
|
||||||
|
strcpy(wildcard.name, "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegionMap::is_name_char(char c) {
|
||||||
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#';
|
||||||
|
}
|
||||||
|
|
||||||
|
static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||||
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||||
|
_fs->remove(filename);
|
||||||
|
return _fs->open(filename, FILE_O_WRITE);
|
||||||
|
#elif defined(RP2040_PLATFORM)
|
||||||
|
return _fs->open(filename, "w");
|
||||||
|
#else
|
||||||
|
return _fs->open(filename, "w", true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegionMap::load(FILESYSTEM* _fs) {
|
||||||
|
if (_fs->exists("/regions2")) {
|
||||||
|
#if defined(RP2040_PLATFORM)
|
||||||
|
File file = _fs->open("/regions2", "r");
|
||||||
|
#else
|
||||||
|
File file = _fs->open("/regions2");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
uint8_t pad[128];
|
||||||
|
|
||||||
|
num_regions = 0; next_id = 1; home_id = 0;
|
||||||
|
|
||||||
|
bool success = file.read(pad, 5) == 5; // reserved header
|
||||||
|
success = success && file.read((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id);
|
||||||
|
success = success && file.read((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
|
||||||
|
success = success && file.read((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
while (num_regions < MAX_REGION_ENTRIES) {
|
||||||
|
auto r = ®ions[num_regions];
|
||||||
|
|
||||||
|
success = file.read((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
|
||||||
|
success = success && file.read((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
|
||||||
|
success = success && file.read((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
|
||||||
|
success = success && file.read((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
|
||||||
|
success = success && file.read(pad, sizeof(pad)) == sizeof(pad);
|
||||||
|
|
||||||
|
if (!success) break; // EOF
|
||||||
|
|
||||||
|
if (r->id >= next_id) { // make sure next_id is valid
|
||||||
|
next_id = r->id + 1;
|
||||||
|
}
|
||||||
|
num_regions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; // failed
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegionMap::save(FILESYSTEM* _fs) {
|
||||||
|
File file = openWrite(_fs, "/regions2");
|
||||||
|
if (file) {
|
||||||
|
uint8_t pad[128];
|
||||||
|
memset(pad, 0, sizeof(pad));
|
||||||
|
|
||||||
|
bool success = file.write(pad, 5) == 5; // reserved header
|
||||||
|
success = success && file.write((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id);
|
||||||
|
success = success && file.write((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
|
||||||
|
success = success && file.write((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
for (int i = 0; i < num_regions; i++) {
|
||||||
|
auto r = ®ions[i];
|
||||||
|
|
||||||
|
success = file.write((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
|
||||||
|
success = success && file.write((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
|
||||||
|
success = success && file.write((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
|
||||||
|
success = success && file.write((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
|
||||||
|
success = success && file.write(pad, sizeof(pad)) == sizeof(pad);
|
||||||
|
if (!success) break; // write failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false; // failed
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t id) {
|
||||||
|
const char* sp = name; // check for illegal name chars
|
||||||
|
while (*sp) {
|
||||||
|
if (!is_name_char(*sp)) return NULL; // error
|
||||||
|
sp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto region = findByName(name);
|
||||||
|
if (region) {
|
||||||
|
if (region->id == parent_id) return NULL; // ERROR: invalid parent!
|
||||||
|
|
||||||
|
region->parent = parent_id; // re-parent / move this region in the hierarchy
|
||||||
|
} else {
|
||||||
|
if (id == 0 && num_regions >= MAX_REGION_ENTRIES) return NULL; // full!
|
||||||
|
|
||||||
|
region = ®ions[num_regions++]; // alloc new RegionEntry
|
||||||
|
region->flags = REGION_DENY_FLOOD; // DENY by default
|
||||||
|
region->id = id == 0 ? next_id++ : id;
|
||||||
|
StrHelper::strncpy(region->name, name, sizeof(region->name));
|
||||||
|
region->parent = parent_id;
|
||||||
|
}
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) {
|
||||||
|
for (int i = 0; i < num_regions; i++) {
|
||||||
|
auto region = ®ions[i];
|
||||||
|
if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param)
|
||||||
|
TransportKey keys[4];
|
||||||
|
int num;
|
||||||
|
if (region->name[0] == '#') { // auto hashtag region
|
||||||
|
_store->getAutoKeyFor(region->id, region->name, keys[0]);
|
||||||
|
num = 1;
|
||||||
|
} else {
|
||||||
|
num = _store->loadKeysFor(region->id, keys, 4);
|
||||||
|
}
|
||||||
|
for (int j = 0; j < num; j++) {
|
||||||
|
uint16_t code = keys[j].calcTransportCode(packet);
|
||||||
|
if (packet->transport_codes[0] == code) { // a match!!
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL; // no matches
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionEntry* RegionMap::findByName(const char* name) {
|
||||||
|
if (strcmp(name, "*") == 0) return &wildcard;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_regions; i++) {
|
||||||
|
auto region = ®ions[i];
|
||||||
|
if (strcmp(name, region->name) == 0) return region;
|
||||||
|
}
|
||||||
|
return NULL; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionEntry* RegionMap::findByNamePrefix(const char* prefix) {
|
||||||
|
if (strcmp(prefix, "*") == 0) return &wildcard;
|
||||||
|
|
||||||
|
RegionEntry* partial = NULL;
|
||||||
|
for (int i = 0; i < num_regions; i++) {
|
||||||
|
auto region = ®ions[i];
|
||||||
|
if (strcmp(prefix, region->name) == 0) return region; // is a complete match, preference this one
|
||||||
|
if (memcmp(prefix, region->name, strlen(prefix)) == 0) {
|
||||||
|
partial = region;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return partial;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionEntry* RegionMap::findById(uint16_t id) {
|
||||||
|
if (id == 0) return &wildcard; // special root Region
|
||||||
|
|
||||||
|
for (int i = 0; i < num_regions; i++) {
|
||||||
|
auto region = ®ions[i];
|
||||||
|
if (region->id == id) return region;
|
||||||
|
}
|
||||||
|
return NULL; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionEntry* RegionMap::getHomeRegion() {
|
||||||
|
return findById(home_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegionMap::setHomeRegion(const RegionEntry* home) {
|
||||||
|
home_id = home ? home->id : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegionMap::removeRegion(const RegionEntry& region) {
|
||||||
|
if (region.id == 0) return false; // failed (cannot remove the wildcard Region)
|
||||||
|
|
||||||
|
int i; // first check region has no child regions
|
||||||
|
for (i = 0; i < num_regions; i++) {
|
||||||
|
if (regions[i].parent == region.id) return false; // failed (must remove child Regions first)
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
while (i < num_regions) {
|
||||||
|
if (region.id == regions[i].id) break;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i >= num_regions) return false; // failed (not found)
|
||||||
|
|
||||||
|
num_regions--; // remove from regions array
|
||||||
|
while (i < num_regions) {
|
||||||
|
regions[i] = regions[i + 1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return true; // success
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegionMap::clear() {
|
||||||
|
num_regions = 0;
|
||||||
|
return true; // success
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& out) const {
|
||||||
|
for (int i = 0; i < indent; i++) {
|
||||||
|
out.print(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent->flags & REGION_DENY_FLOOD) {
|
||||||
|
out.printf("%s%s\n", parent->name, parent->id == home_id ? "^" : "");
|
||||||
|
} else {
|
||||||
|
out.printf("%s%s F\n", parent->name, parent->id == home_id ? "^" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < num_regions; i++) {
|
||||||
|
auto r = ®ions[i];
|
||||||
|
if (r->parent == parent->id) {
|
||||||
|
printChildRegions(indent + 1, r, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegionMap::exportTo(Stream& out) const {
|
||||||
|
printChildRegions(0, &wildcard, out); // recursive
|
||||||
|
}
|
||||||
52
src/helpers/RegionMap.h
Normal file
52
src/helpers/RegionMap.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h> // needed for PlatformIO
|
||||||
|
#include <Packet.h>
|
||||||
|
#include "TransportKeyStore.h"
|
||||||
|
|
||||||
|
#ifndef MAX_REGION_ENTRIES
|
||||||
|
#define MAX_REGION_ENTRIES 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define REGION_DENY_FLOOD 0x01
|
||||||
|
#define REGION_DENY_DIRECT 0x02 // reserved for future
|
||||||
|
|
||||||
|
struct RegionEntry {
|
||||||
|
uint16_t id;
|
||||||
|
uint16_t parent;
|
||||||
|
uint8_t flags;
|
||||||
|
char name[31];
|
||||||
|
};
|
||||||
|
|
||||||
|
class RegionMap {
|
||||||
|
TransportKeyStore* _store;
|
||||||
|
uint16_t next_id, home_id;
|
||||||
|
uint16_t num_regions;
|
||||||
|
RegionEntry regions[MAX_REGION_ENTRIES];
|
||||||
|
RegionEntry wildcard;
|
||||||
|
|
||||||
|
void printChildRegions(int indent, const RegionEntry* parent, Stream& out) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RegionMap(TransportKeyStore& store);
|
||||||
|
|
||||||
|
static bool is_name_char(char c);
|
||||||
|
|
||||||
|
bool load(FILESYSTEM* _fs);
|
||||||
|
bool save(FILESYSTEM* _fs);
|
||||||
|
|
||||||
|
RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0);
|
||||||
|
RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask);
|
||||||
|
RegionEntry& getWildcard() { return wildcard; }
|
||||||
|
RegionEntry* findByName(const char* name);
|
||||||
|
RegionEntry* findByNamePrefix(const char* prefix);
|
||||||
|
RegionEntry* findById(uint16_t id);
|
||||||
|
RegionEntry* getHomeRegion(); // NOTE: can be NULL
|
||||||
|
void setHomeRegion(const RegionEntry* home);
|
||||||
|
bool removeRegion(const RegionEntry& region);
|
||||||
|
bool clear();
|
||||||
|
void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; }
|
||||||
|
int getCount() const { return num_regions; }
|
||||||
|
|
||||||
|
void exportTo(Stream& out) const;
|
||||||
|
};
|
||||||
92
src/helpers/TransportKeyStore.cpp
Normal file
92
src/helpers/TransportKeyStore.cpp
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#include "TransportKeyStore.h"
|
||||||
|
#include <SHA256.h>
|
||||||
|
|
||||||
|
uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const {
|
||||||
|
uint16_t code;
|
||||||
|
SHA256 sha;
|
||||||
|
sha.resetHMAC(key, sizeof(key));
|
||||||
|
uint8_t type = packet->getPayloadType();
|
||||||
|
sha.update(&type, 1);
|
||||||
|
sha.update(packet->payload, packet->payload_len);
|
||||||
|
sha.finalizeHMAC(key, sizeof(key), &code, 2);
|
||||||
|
if (code == 0) { // reserve codes 0000 and FFFF
|
||||||
|
code++;
|
||||||
|
} else if (code == 0xFFFF) {
|
||||||
|
code--;
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransportKey::isNull() const {
|
||||||
|
for (int i = 0; i < sizeof(key); i++) {
|
||||||
|
if (key[i]) return false;
|
||||||
|
}
|
||||||
|
return true; // key is all zeroes
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransportKeyStore::putCache(uint16_t id, const TransportKey& key) {
|
||||||
|
if (num_cache < MAX_TKS_ENTRIES) {
|
||||||
|
cache_ids[num_cache] = id;
|
||||||
|
cache_keys[num_cache] = key;
|
||||||
|
num_cache++;
|
||||||
|
} else {
|
||||||
|
// TODO: evict oldest cache entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransportKeyStore::getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest) {
|
||||||
|
for (int i = 0; i < num_cache; i++) { // first, check cache
|
||||||
|
if (cache_ids[i] == id) { // cache hit!
|
||||||
|
dest = cache_keys[i];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// calc key for publicly-known hashtag region name
|
||||||
|
SHA256 sha;
|
||||||
|
sha.update(name, strlen(name));
|
||||||
|
sha.finalize(&dest.key, sizeof(dest.key));
|
||||||
|
|
||||||
|
putCache(id, dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TransportKeyStore::loadKeysFor(uint16_t id, TransportKey keys[], int max_num) {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < num_cache && n < max_num; i++) { // first, check cache
|
||||||
|
if (cache_ids[i] == id) {
|
||||||
|
keys[n++] = cache_keys[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n > 0) return n; // cache hit!
|
||||||
|
|
||||||
|
// TODO: retrieve from difficult-to-copy keystore
|
||||||
|
|
||||||
|
// store in cache (if room)
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
putCache(id, keys[i]);
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransportKeyStore::saveKeysFor(uint16_t id, const TransportKey keys[], int num) {
|
||||||
|
invalidateCache();
|
||||||
|
|
||||||
|
// TODO: update hardware keystore
|
||||||
|
|
||||||
|
return false; // failed
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransportKeyStore::removeKeys(uint16_t id) {
|
||||||
|
invalidateCache();
|
||||||
|
|
||||||
|
// TODO: remove from hardware keystore
|
||||||
|
|
||||||
|
return false; // failed
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransportKeyStore::clear() {
|
||||||
|
invalidateCache();
|
||||||
|
|
||||||
|
// TODO: clear hardware keystore
|
||||||
|
|
||||||
|
return false; // failed
|
||||||
|
}
|
||||||
31
src/helpers/TransportKeyStore.h
Normal file
31
src/helpers/TransportKeyStore.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h> // needed for PlatformIO
|
||||||
|
#include <Packet.h>
|
||||||
|
#include <helpers/IdentityStore.h>
|
||||||
|
|
||||||
|
struct TransportKey {
|
||||||
|
uint8_t key[16];
|
||||||
|
|
||||||
|
uint16_t calcTransportCode(const mesh::Packet* packet) const;
|
||||||
|
bool isNull() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_TKS_ENTRIES 16
|
||||||
|
|
||||||
|
class TransportKeyStore {
|
||||||
|
uint16_t cache_ids[MAX_TKS_ENTRIES];
|
||||||
|
TransportKey cache_keys[MAX_TKS_ENTRIES];
|
||||||
|
int num_cache;
|
||||||
|
|
||||||
|
void putCache(uint16_t id, const TransportKey& key);
|
||||||
|
void invalidateCache() { num_cache = 0; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
TransportKeyStore() { num_cache = 0; }
|
||||||
|
void getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest);
|
||||||
|
int loadKeysFor(uint16_t id, TransportKey keys[], int max_num);
|
||||||
|
bool saveKeysFor(uint16_t id, const TransportKey keys[], int num);
|
||||||
|
bool removeKeys(uint16_t id);
|
||||||
|
bool clear();
|
||||||
|
};
|
||||||
@@ -19,6 +19,13 @@ void StrHelper::strzcpy(char* dest, const char* src, size_t buf_sz) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StrHelper::isBlank(const char* str) {
|
||||||
|
while (*str) {
|
||||||
|
if (*str++ != ' ') return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
union int32_Float_t
|
union int32_Float_t
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ public:
|
|||||||
static void strncpy(char* dest, const char* src, size_t buf_sz);
|
static void strncpy(char* dest, const char* src, size_t buf_sz);
|
||||||
static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls
|
static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls
|
||||||
static const char* ftoa(float f);
|
static const char* ftoa(float f);
|
||||||
|
static bool isBlank(const char* str);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user