|
@@ -41,16 +41,21 @@
|
|
|
#define TXT_ACK_DELAY 200
|
|
#define TXT_ACK_DELAY 200
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
-#define FIRMWARE_VER_LEVEL 1
|
|
|
|
|
|
|
+#define FIRMWARE_VER_LEVEL 2
|
|
|
|
|
|
|
|
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
|
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
|
|
#define REQ_TYPE_KEEP_ALIVE 0x02
|
|
#define REQ_TYPE_KEEP_ALIVE 0x02
|
|
|
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
|
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
|
|
#define REQ_TYPE_GET_ACCESS_LIST 0x05
|
|
#define REQ_TYPE_GET_ACCESS_LIST 0x05
|
|
|
#define REQ_TYPE_GET_NEIGHBOURS 0x06
|
|
#define REQ_TYPE_GET_NEIGHBOURS 0x06
|
|
|
|
|
+#define REQ_TYPE_GET_OWNER_INFO 0x07 // FIRMWARE_VER_LEVEL >= 2
|
|
|
|
|
|
|
|
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
|
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
|
|
|
|
|
|
|
|
|
+#define ANON_REQ_TYPE_REGIONS 0x01
|
|
|
|
|
+#define ANON_REQ_TYPE_OWNER 0x02
|
|
|
|
|
+#define ANON_REQ_TYPE_BASIC 0x03 // just remote clock
|
|
|
|
|
+
|
|
|
#define CLI_REPLY_DELAY_MILLIS 600
|
|
#define CLI_REPLY_DELAY_MILLIS 600
|
|
|
|
|
|
|
|
#define LAZY_CONTACTS_WRITE_DELAY 5000
|
|
#define LAZY_CONTACTS_WRITE_DELAY 5000
|
|
@@ -139,6 +144,64 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr
|
|
|
return 13; // reply length
|
|
return 13; // reply length
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) {
|
|
|
|
|
+ if (anon_limiter.allow(rtc_clock.getCurrentTime())) {
|
|
|
|
|
+ // request data has: {reply-path-len}{reply-path}
|
|
|
|
|
+ reply_path_len = *data++ & 0x3F;
|
|
|
|
|
+ memcpy(reply_path, data, reply_path_len);
|
|
|
|
|
+ // data += reply_path_len;
|
|
|
|
|
+
|
|
|
|
|
+ memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag
|
|
|
|
|
+ uint32_t now = getRTCClock()->getCurrentTime();
|
|
|
|
|
+ memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness)
|
|
|
|
|
+
|
|
|
|
|
+ return 8 + region_map.exportNamesTo((char *) &reply_data[8], sizeof(reply_data) - 12, REGION_DENY_FLOOD); // reply length
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) {
|
|
|
|
|
+ if (anon_limiter.allow(rtc_clock.getCurrentTime())) {
|
|
|
|
|
+ // request data has: {reply-path-len}{reply-path}
|
|
|
|
|
+ reply_path_len = *data++ & 0x3F;
|
|
|
|
|
+ memcpy(reply_path, data, reply_path_len);
|
|
|
|
|
+ // data += reply_path_len;
|
|
|
|
|
+
|
|
|
|
|
+ memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag
|
|
|
|
|
+ uint32_t now = getRTCClock()->getCurrentTime();
|
|
|
|
|
+ memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness)
|
|
|
|
|
+ sprintf((char *) &reply_data[8], "%s\n%s", _prefs.node_name, _prefs.owner_info);
|
|
|
|
|
+
|
|
|
|
|
+ return 8 + strlen((char *) &reply_data[8]); // reply length
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) {
|
|
|
|
|
+ if (anon_limiter.allow(rtc_clock.getCurrentTime())) {
|
|
|
|
|
+ // request data has: {reply-path-len}{reply-path}
|
|
|
|
|
+ reply_path_len = *data++ & 0x3F;
|
|
|
|
|
+ memcpy(reply_path, data, reply_path_len);
|
|
|
|
|
+ // data += reply_path_len;
|
|
|
|
|
+
|
|
|
|
|
+ memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag
|
|
|
|
|
+ uint32_t now = getRTCClock()->getCurrentTime();
|
|
|
|
|
+ memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness)
|
|
|
|
|
+ reply_data[8] = 0; // features
|
|
|
|
|
+#ifdef WITH_RS232_BRIDGE
|
|
|
|
|
+ reply_data[8] |= 0x01; // is bridge, type UART
|
|
|
|
|
+#elif WITH_ESPNOW_BRIDGE
|
|
|
|
|
+ reply_data[8] |= 0x03; // is bridge, type ESP-NOW
|
|
|
|
|
+#endif
|
|
|
|
|
+ if (_prefs.disable_fwd) { // is this repeater currently disabled
|
|
|
|
|
+ reply_data[8] |= 0x80; // is disabled
|
|
|
|
|
+ }
|
|
|
|
|
+ // TODO: add some kind of moving-window utilisation metric, so can query 'how busy' is this repeater
|
|
|
|
|
+ return 9; // reply length
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) {
|
|
int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) {
|
|
|
// uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
|
// uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
|
|
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
|
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
|
@@ -163,7 +226,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
|
|
stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups();
|
|
stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups();
|
|
|
stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups();
|
|
stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups();
|
|
|
stats.total_rx_air_time_secs = getReceiveAirTime() / 1000;
|
|
stats.total_rx_air_time_secs = getReceiveAirTime() / 1000;
|
|
|
-
|
|
|
|
|
|
|
+ stats.n_recv_errors = radio_driver.getPacketsRecvErrors();
|
|
|
memcpy(&reply_data[4], &stats, sizeof(stats));
|
|
memcpy(&reply_data[4], &stats, sizeof(stats));
|
|
|
|
|
|
|
|
return 4 + sizeof(stats); // reply_len
|
|
return 4 + sizeof(stats); // reply_len
|
|
@@ -296,6 +359,9 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
|
|
|
|
|
|
|
return reply_offset;
|
|
return reply_offset;
|
|
|
}
|
|
}
|
|
|
|
|
+ } else if (payload[0] == REQ_TYPE_GET_OWNER_INFO) {
|
|
|
|
|
+ sprintf((char *) &reply_data[4], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info);
|
|
|
|
|
+ return 4 + strlen((char *) &reply_data[4]);
|
|
|
}
|
|
}
|
|
|
return 0; // unknown command
|
|
return 0; // unknown command
|
|
|
}
|
|
}
|
|
@@ -448,12 +514,18 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
|
|
|
|
|
|
|
data[len] = 0; // ensure null terminator
|
|
data[len] = 0; // ensure null terminator
|
|
|
uint8_t reply_len;
|
|
uint8_t reply_len;
|
|
|
|
|
+
|
|
|
|
|
+ reply_path_len = -1;
|
|
|
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
|
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
|
|
reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood());
|
|
reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood());
|
|
|
- //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes
|
|
|
|
|
- // TODO
|
|
|
|
|
|
|
+ } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) {
|
|
|
|
|
+ reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]);
|
|
|
|
|
+ } else if (data[4] == ANON_REQ_TYPE_OWNER && packet->isRouteDirect()) {
|
|
|
|
|
+ reply_len = handleAnonOwnerReq(sender, timestamp, &data[5]);
|
|
|
|
|
+ } else if (data[4] == ANON_REQ_TYPE_BASIC && packet->isRouteDirect()) {
|
|
|
|
|
+ reply_len = handleAnonClockReq(sender, timestamp, &data[5]);
|
|
|
} else {
|
|
} else {
|
|
|
- reply_len = 0; // unknown request type
|
|
|
|
|
|
|
+ reply_len = 0; // unknown/invalid request type
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (reply_len == 0) return; // invalid request
|
|
if (reply_len == 0) return; // invalid request
|
|
@@ -463,9 +535,12 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
|
|
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
|
|
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
|
|
|
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
|
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
|
|
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
|
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
|
|
- } else {
|
|
|
|
|
|
|
+ } else if (reply_path_len < 0) {
|
|
|
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
|
|
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
|
|
|
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY);
|
|
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY);
|
|
|
|
|
+ } 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);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -637,7 +712,9 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
|
|
|
|
|
|
|
|
void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
|
void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
|
|
uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits
|
|
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())) {
|
|
|
|
|
|
|
+ if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6
|
|
|
|
|
+ && !_prefs.disable_fwd && discover_limiter.allow(rtc_clock.getCurrentTime())
|
|
|
|
|
+ ) {
|
|
|
int i = 1;
|
|
int i = 1;
|
|
|
uint8_t filter = packet->payload[i++];
|
|
uint8_t filter = packet->payload[i++];
|
|
|
uint32_t tag;
|
|
uint32_t tag;
|
|
@@ -667,8 +744,9 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
|
|
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), region_map(key_store), temp_map(key_store),
|
|
|
|
|
- discover_limiter(4, 120) // max 4 every 2 minutes
|
|
|
|
|
|
|
+ _cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store),
|
|
|
|
|
+ discover_limiter(4, 120), // max 4 every 2 minutes
|
|
|
|
|
+ anon_limiter(4, 180) // max 4 every 3 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
|
|
@@ -730,7 +808,7 @@ 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, self_id);
|
|
|
// TODO: key_store.begin();
|
|
// TODO: key_store.begin();
|
|
|
region_map.load(_fs);
|
|
region_map.load(_fs);
|
|
|
|
|
|
|
@@ -776,10 +854,14 @@ bool MyMesh::formatFileSystem() {
|
|
|
#endif
|
|
#endif
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void MyMesh::sendSelfAdvertisement(int delay_millis) {
|
|
|
|
|
|
|
+void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) {
|
|
|
mesh::Packet *pkt = createSelfAdvert();
|
|
mesh::Packet *pkt = createSelfAdvert();
|
|
|
if (pkt) {
|
|
if (pkt) {
|
|
|
- sendFlood(pkt, delay_millis);
|
|
|
|
|
|
|
+ if (flood) {
|
|
|
|
|
+ sendFlood(pkt, delay_millis);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sendZeroHop(pkt, delay_millis);
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!");
|
|
MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!");
|
|
|
}
|
|
}
|
|
@@ -890,7 +972,6 @@ void MyMesh::formatPacketStatsReply(char *reply) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
|
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
|
|
- self_id = new_id;
|
|
|
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
|
IdentityStore store(*_fs, "");
|
|
IdentityStore store(*_fs, "");
|
|
|
#elif defined(ESP32)
|
|
#elif defined(ESP32)
|
|
@@ -900,7 +981,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
|
|
#else
|
|
#else
|
|
|
#error "need to define saveIdentity()"
|
|
#error "need to define saveIdentity()"
|
|
|
#endif
|
|
#endif
|
|
|
- store.save("_main", self_id);
|
|
|
|
|
|
|
+ store.save("_main", new_id);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void MyMesh::clearStats() {
|
|
void MyMesh::clearStats() {
|
|
@@ -991,8 +1072,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
|
|
|
|
|
|
|
const char* parts[4];
|
|
const char* parts[4];
|
|
|
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
|
|
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
|
|
|
- if (n == 1 && sender_timestamp == 0) {
|
|
|
|
|
- region_map.exportTo(Serial);
|
|
|
|
|
|
|
+ if (n == 1) {
|
|
|
|
|
+ region_map.exportTo(reply, 160);
|
|
|
} else if (n >= 2 && strcmp(parts[1], "load") == 0) {
|
|
} else if (n >= 2 && strcmp(parts[1], "load") == 0) {
|
|
|
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
|
|
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
|
|
|
memset(load_stack, 0, sizeof(load_stack));
|
|
memset(load_stack, 0, sizeof(load_stack));
|
|
@@ -1065,6 +1146,25 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
|
|
} else {
|
|
} else {
|
|
|
strcpy(reply, "Err - not found");
|
|
strcpy(reply, "Err - not found");
|
|
|
}
|
|
}
|
|
|
|
|
+ } else if (n >= 3 && strcmp(parts[1], "list") == 0) {
|
|
|
|
|
+ uint8_t mask = 0;
|
|
|
|
|
+ bool invert = false;
|
|
|
|
|
+
|
|
|
|
|
+ if (strcmp(parts[2], "allowed") == 0) {
|
|
|
|
|
+ mask = REGION_DENY_FLOOD;
|
|
|
|
|
+ invert = false; // list regions that DON'T have DENY flag
|
|
|
|
|
+ } else if (strcmp(parts[2], "denied") == 0) {
|
|
|
|
|
+ mask = REGION_DENY_FLOOD;
|
|
|
|
|
+ invert = true; // list regions that DO have DENY flag
|
|
|
|
|
+ } else {
|
|
|
|
|
+ strcpy(reply, "Err - use 'allowed' or 'denied'");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int len = region_map.exportNamesTo(reply, 160, mask, invert);
|
|
|
|
|
+ if (len == 0) {
|
|
|
|
|
+ strcpy(reply, "-none-");
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
strcpy(reply, "Err - ??");
|
|
strcpy(reply, "Err - ??");
|
|
|
}
|
|
}
|