|
@@ -243,7 +243,7 @@ static uint8_t putFloat(uint8_t * dest, float value, uint8_t size, uint32_t mult
|
|
|
uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len) {
|
|
uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len) {
|
|
|
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
|
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
|
|
|
|
|
|
|
- if (req_type == REQ_TYPE_GET_TELEMETRY_DATA && (perms & PERM_GET_TELEMETRY) != 0) {
|
|
|
|
|
|
|
+ if (req_type == REQ_TYPE_GET_TELEMETRY_DATA) { // allow all
|
|
|
telemetry.reset();
|
|
telemetry.reset();
|
|
|
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
|
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
|
|
// query other sensors -- target specific
|
|
// query other sensors -- target specific
|
|
@@ -254,7 +254,7 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint
|
|
|
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
|
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
|
|
return 4 + tlen; // reply_len
|
|
return 4 + tlen; // reply_len
|
|
|
}
|
|
}
|
|
|
- if (req_type == REQ_TYPE_GET_AVG_MIN_MAX && (perms & PERM_GET_OTHER_STATS) != 0) {
|
|
|
|
|
|
|
+ if (req_type == REQ_TYPE_GET_AVG_MIN_MAX && (perms & PERM_ACL_ROLE_MASK) >= PERM_ACL_READ_ONLY) {
|
|
|
uint32_t start_secs_ago, end_secs_ago;
|
|
uint32_t start_secs_ago, end_secs_ago;
|
|
|
memcpy(&start_secs_ago, &payload[0], 4);
|
|
memcpy(&start_secs_ago, &payload[0], 4);
|
|
|
memcpy(&end_secs_ago, &payload[4], 4);
|
|
memcpy(&end_secs_ago, &payload[4], 4);
|
|
@@ -288,13 +288,14 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint
|
|
|
}
|
|
}
|
|
|
return ofs;
|
|
return ofs;
|
|
|
}
|
|
}
|
|
|
- if (req_type == REQ_TYPE_GET_ACCESS_LIST && (perms & PERM_ACL_ROLE_MASK) == PERM_ACL_LEVEL3) {
|
|
|
|
|
|
|
+ if (req_type == REQ_TYPE_GET_ACCESS_LIST && (perms & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN) {
|
|
|
uint8_t res1 = payload[0]; // reserved for future (extra query params)
|
|
uint8_t res1 = payload[0]; // reserved for future (extra query params)
|
|
|
uint8_t res2 = payload[1];
|
|
uint8_t res2 = payload[1];
|
|
|
if (res1 == 0 && res2 == 0) {
|
|
if (res1 == 0 && res2 == 0) {
|
|
|
uint8_t ofs = 4;
|
|
uint8_t ofs = 4;
|
|
|
for (int i = 0; i < num_contacts && ofs + 7 <= sizeof(reply_data) - 4; i++) {
|
|
for (int i = 0; i < num_contacts && ofs + 7 <= sizeof(reply_data) - 4; i++) {
|
|
|
auto c = &contacts[i];
|
|
auto c = &contacts[i];
|
|
|
|
|
+ if (c->permissions == 0) continue; // skip deleted entries
|
|
|
memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix
|
|
memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix
|
|
|
reply_data[ofs++] = c->permissions;
|
|
reply_data[ofs++] = c->permissions;
|
|
|
}
|
|
}
|
|
@@ -315,7 +316,14 @@ mesh::Packet* SensorMesh::createSelfAdvert() {
|
|
|
return createAdvert(self_id, app_data, app_data_len);
|
|
return createAdvert(self_id, app_data, app_data_len);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-ContactInfo* SensorMesh::putContact(const mesh::Identity& id) {
|
|
|
|
|
|
|
+ContactInfo* SensorMesh::getContact(const uint8_t* pubkey, int key_len) {
|
|
|
|
|
+ for (int i = 0; i < num_contacts; i++) {
|
|
|
|
|
+ if (memcmp(pubkey, contacts[i].id.pub_key, key_len) == 0) return &contacts[i]; // already known
|
|
|
|
|
+ }
|
|
|
|
|
+ return NULL; // not found
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+ContactInfo* SensorMesh::putContact(const mesh::Identity& id, uint8_t init_perms) {
|
|
|
uint32_t min_time = 0xFFFFFFFF;
|
|
uint32_t min_time = 0xFFFFFFFF;
|
|
|
ContactInfo* oldest = &contacts[MAX_CONTACTS - 1];
|
|
ContactInfo* oldest = &contacts[MAX_CONTACTS - 1];
|
|
|
for (int i = 0; i < num_contacts; i++) {
|
|
for (int i = 0; i < num_contacts; i++) {
|
|
@@ -333,22 +341,35 @@ ContactInfo* SensorMesh::putContact(const mesh::Identity& id) {
|
|
|
c = oldest; // evict least active contact
|
|
c = oldest; // evict least active contact
|
|
|
}
|
|
}
|
|
|
memset(c, 0, sizeof(*c));
|
|
memset(c, 0, sizeof(*c));
|
|
|
|
|
+ c->permissions = init_perms;
|
|
|
c->id = id;
|
|
c->id = id;
|
|
|
c->out_path_len = -1; // initially out_path is unknown
|
|
c->out_path_len = -1; // initially out_path is unknown
|
|
|
return c;
|
|
return c;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void SensorMesh::applyContactPermissions(const uint8_t* pubkey, uint8_t perms) {
|
|
|
|
|
- mesh::Identity id(pubkey);
|
|
|
|
|
- auto c = putContact(id);
|
|
|
|
|
-
|
|
|
|
|
|
|
+bool SensorMesh::applyContactPermissions(const uint8_t* pubkey, int key_len, uint8_t perms) {
|
|
|
|
|
+ ContactInfo* c;
|
|
|
if ((perms & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { // guest role is not persisted in contacts
|
|
if ((perms & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { // guest role is not persisted in contacts
|
|
|
- memset(c, 0, sizeof(*c));
|
|
|
|
|
|
|
+ c = getContact(pubkey, key_len);
|
|
|
|
|
+ if (c == NULL) return false; // partial pubkey not found
|
|
|
|
|
+
|
|
|
|
|
+ num_contacts--; // delete from contacts[]
|
|
|
|
|
+ int i = c - contacts;
|
|
|
|
|
+ while (i < num_contacts) {
|
|
|
|
|
+ contacts[i] = contacts[i + 1];
|
|
|
|
|
+ i++;
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ if (key_len < PUB_KEY_SIZE) return false; // need complete pubkey when adding/modifying
|
|
|
|
|
+
|
|
|
|
|
+ mesh::Identity id(pubkey);
|
|
|
|
|
+ c = putContact(id, 0);
|
|
|
|
|
+
|
|
|
c->permissions = perms; // update their permissions
|
|
c->permissions = perms; // update their permissions
|
|
|
self_id.calcSharedSecret(c->shared_secret, pubkey);
|
|
self_id.calcSharedSecret(c->shared_secret, pubkey);
|
|
|
}
|
|
}
|
|
|
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger saveContacts()
|
|
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger saveContacts()
|
|
|
|
|
+ return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void SensorMesh::sendAlert(ContactInfo* c, Trigger* t) {
|
|
void SensorMesh::sendAlert(ContactInfo* c, Trigger* t) {
|
|
@@ -434,32 +455,43 @@ int SensorMesh::getAGCResetInterval() const {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) {
|
|
uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) {
|
|
|
- if (strcmp((char *) data, _prefs.password) != 0) { // check for valid password
|
|
|
|
|
- #if MESH_DEBUG
|
|
|
|
|
- MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]);
|
|
|
|
|
- #endif
|
|
|
|
|
- return 0;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ ContactInfo* client;
|
|
|
|
|
+ if (data[0] == 0) { // blank password, just check if sender is in ACL
|
|
|
|
|
+ client = getContact(sender.pub_key, PUB_KEY_SIZE);
|
|
|
|
|
+ if (client == NULL) {
|
|
|
|
|
+ #if MESH_DEBUG
|
|
|
|
|
+ MESH_DEBUG_PRINTLN("Login, sender not in ACL");
|
|
|
|
|
+ #endif
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (strcmp((char *) data, _prefs.password) != 0) { // check for valid admin password
|
|
|
|
|
+ #if MESH_DEBUG
|
|
|
|
|
+ MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]);
|
|
|
|
|
+ #endif
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- auto client = putContact(sender); // add to contacts (if not already known)
|
|
|
|
|
- if (sender_timestamp <= client->last_timestamp) {
|
|
|
|
|
- MESH_DEBUG_PRINTLN("Possible login replay attack!");
|
|
|
|
|
- return 0; // FATAL: client table is full -OR- replay attack
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ client = putContact(sender, PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO); // add to contacts (if not already known)
|
|
|
|
|
+ if (sender_timestamp <= client->last_timestamp) {
|
|
|
|
|
+ MESH_DEBUG_PRINTLN("Possible login replay attack!");
|
|
|
|
|
+ return 0; // FATAL: client table is full -OR- replay attack
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- MESH_DEBUG_PRINTLN("Login success!");
|
|
|
|
|
- client->last_timestamp = sender_timestamp;
|
|
|
|
|
- client->last_activity = getRTCClock()->getCurrentTime();
|
|
|
|
|
- client->permissions = PERM_ACL_LEVEL3 | PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO; // initially opt-in to receive alerts (can opt out)
|
|
|
|
|
- memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
|
|
|
|
|
|
|
+ MESH_DEBUG_PRINTLN("Login success!");
|
|
|
|
|
+ client->last_timestamp = sender_timestamp;
|
|
|
|
|
+ client->last_activity = getRTCClock()->getCurrentTime();
|
|
|
|
|
+ client->permissions |= PERM_ACL_ADMIN;
|
|
|
|
|
+ memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
|
|
|
|
|
|
|
|
- dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
|
|
|
|
|
|
+ dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
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
|
|
|
reply_data[4] = RESP_SERVER_LOGIN_OK;
|
|
reply_data[4] = RESP_SERVER_LOGIN_OK;
|
|
|
reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16)
|
|
reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16)
|
|
|
- reply_data[6] = 1; // 1 = is admin
|
|
|
|
|
|
|
+ reply_data[6] = client->isAdmin() ? 1 : 0;
|
|
|
reply_data[7] = client->permissions;
|
|
reply_data[7] = client->permissions;
|
|
|
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
|
|
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
|
|
|
|
|
|
|
@@ -484,16 +516,20 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r
|
|
|
if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8}
|
|
if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8}
|
|
|
char* hex = &command[8];
|
|
char* hex = &command[8];
|
|
|
char* sp = strchr(hex, ' '); // look for separator char
|
|
char* sp = strchr(hex, ' '); // look for separator char
|
|
|
- if (sp == NULL || sp - hex != PUB_KEY_SIZE*2) {
|
|
|
|
|
- strcpy(reply, "Err - bad pubkey len");
|
|
|
|
|
|
|
+ if (sp == NULL) {
|
|
|
|
|
+ strcpy(reply, "Err - bad params");
|
|
|
} else {
|
|
} else {
|
|
|
*sp++ = 0; // replace space with null terminator
|
|
*sp++ = 0; // replace space with null terminator
|
|
|
|
|
|
|
|
uint8_t pubkey[PUB_KEY_SIZE];
|
|
uint8_t pubkey[PUB_KEY_SIZE];
|
|
|
- if (mesh::Utils::fromHex(pubkey, PUB_KEY_SIZE, hex)) {
|
|
|
|
|
|
|
+ int hex_len = min(sp - hex, PUB_KEY_SIZE*2);
|
|
|
|
|
+ if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) {
|
|
|
uint8_t perms = atoi(sp);
|
|
uint8_t perms = atoi(sp);
|
|
|
- applyContactPermissions(pubkey, perms);
|
|
|
|
|
- strcpy(reply, "OK");
|
|
|
|
|
|
|
+ if (applyContactPermissions(pubkey, hex_len / 2, perms)) {
|
|
|
|
|
+ strcpy(reply, "OK");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ strcpy(reply, "Err - invalid params");
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
strcpy(reply, "Err - bad pubkey");
|
|
strcpy(reply, "Err - bad pubkey");
|
|
|
}
|
|
}
|
|
@@ -502,6 +538,7 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r
|
|
|
Serial.println("ACL:");
|
|
Serial.println("ACL:");
|
|
|
for (int i = 0; i < num_contacts; i++) {
|
|
for (int i = 0; i < num_contacts; i++) {
|
|
|
auto c = &contacts[i];
|
|
auto c = &contacts[i];
|
|
|
|
|
+ if (c->permissions == 0) continue; // skip deleted entries
|
|
|
|
|
|
|
|
Serial.printf("%02X ", c->permissions);
|
|
Serial.printf("%02X ", c->permissions);
|
|
|
mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE);
|
|
mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE);
|
|
@@ -569,7 +606,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
|
|
memcpy(×tamp, data, 4);
|
|
memcpy(×tamp, data, 4);
|
|
|
|
|
|
|
|
if (timestamp > from.last_timestamp) { // prevent replay attacks
|
|
if (timestamp > from.last_timestamp) { // prevent replay attacks
|
|
|
- uint8_t reply_len = handleRequest(from.isAdmin() ? 0xFFFF : from.permissions, timestamp, data[4], &data[5], len - 5);
|
|
|
|
|
|
|
+ uint8_t reply_len = handleRequest(from.isAdmin() ? 0xFF : from.permissions, timestamp, data[4], &data[5], len - 5);
|
|
|
if (reply_len == 0) return; // invalid command
|
|
if (reply_len == 0) return; // invalid command
|
|
|
|
|
|
|
|
from.last_timestamp = timestamp;
|
|
from.last_timestamp = timestamp;
|
|
@@ -685,6 +722,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
|
|
dirty_contacts_expiry = 0;
|
|
dirty_contacts_expiry = 0;
|
|
|
last_read_time = 0;
|
|
last_read_time = 0;
|
|
|
num_alert_tasks = 0;
|
|
num_alert_tasks = 0;
|
|
|
|
|
+ set_radio_at = revert_radio_at = 0;
|
|
|
|
|
|
|
|
// defaults
|
|
// defaults
|
|
|
memset(&_prefs, 0, sizeof(_prefs));
|
|
memset(&_prefs, 0, sizeof(_prefs));
|
|
@@ -735,6 +773,16 @@ bool SensorMesh::formatFileSystem() {
|
|
|
#endif
|
|
#endif
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
|
|
|
|
|
+ set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params
|
|
|
|
|
+ pending_freq = freq;
|
|
|
|
|
+ pending_bw = bw;
|
|
|
|
|
+ pending_sf = sf;
|
|
|
|
|
+ pending_cr = cr;
|
|
|
|
|
+
|
|
|
|
|
+ revert_radio_at = futureMillis(2000 + timeout_mins*60*1000); // schedule when to revert radio params
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
void SensorMesh::sendSelfAdvertisement(int delay_millis) {
|
|
void SensorMesh::sendSelfAdvertisement(int delay_millis) {
|
|
|
mesh::Packet* pkt = createSelfAdvert();
|
|
mesh::Packet* pkt = createSelfAdvert();
|
|
|
if (pkt) {
|
|
if (pkt) {
|
|
@@ -810,6 +858,18 @@ void SensorMesh::loop() {
|
|
|
updateAdvertTimer(); // schedule next local advert
|
|
updateAdvertTimer(); // schedule next local advert
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params
|
|
|
|
|
+ set_radio_at = 0; // clear timer
|
|
|
|
|
+ radio_set_params(pending_freq, pending_bw, pending_sf, pending_cr);
|
|
|
|
|
+ MESH_DEBUG_PRINTLN("Temp radio params");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig
|
|
|
|
|
+ revert_radio_at = 0; // clear timer
|
|
|
|
|
+ radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
|
|
|
|
+ MESH_DEBUG_PRINTLN("Radio params restored");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
uint32_t curr = getRTCClock()->getCurrentTime();
|
|
uint32_t curr = getRTCClock()->getCurrentTime();
|
|
|
if (curr >= last_read_time + SENSOR_READ_INTERVAL_SECS) {
|
|
if (curr >= last_read_time + SENSOR_READ_INTERVAL_SECS) {
|
|
|
telemetry.reset();
|
|
telemetry.reset();
|