* refactor: moved ACL out of SensorMesh -> ClientACL
This commit is contained in:
@@ -71,78 +71,6 @@ static File openAppend(FILESYSTEM* _fs, const char* fname) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
void SensorMesh::loadContacts() {
|
|
||||||
num_contacts = 0;
|
|
||||||
if (_fs->exists("/s_contacts")) {
|
|
||||||
#if defined(RP2040_PLATFORM)
|
|
||||||
File file = _fs->open("/s_contacts", "r");
|
|
||||||
#else
|
|
||||||
File file = _fs->open("/s_contacts");
|
|
||||||
#endif
|
|
||||||
if (file) {
|
|
||||||
bool full = false;
|
|
||||||
while (!full) {
|
|
||||||
ContactInfo c;
|
|
||||||
uint8_t pub_key[32];
|
|
||||||
uint8_t unused[6];
|
|
||||||
|
|
||||||
bool success = (file.read(pub_key, 32) == 32);
|
|
||||||
success = success && (file.read((uint8_t *) &c.permissions, 1) == 1);
|
|
||||||
success = success && (file.read(unused, 6) == 6);
|
|
||||||
success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1);
|
|
||||||
success = success && (file.read(c.out_path, 64) == 64);
|
|
||||||
success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
|
||||||
c.last_timestamp = 0; // transient
|
|
||||||
c.last_activity = 0;
|
|
||||||
|
|
||||||
if (!success) break; // EOF
|
|
||||||
|
|
||||||
c.id = mesh::Identity(pub_key);
|
|
||||||
if (num_contacts < MAX_CONTACTS) {
|
|
||||||
contacts[num_contacts++] = c;
|
|
||||||
} else {
|
|
||||||
full = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SensorMesh::saveContacts() {
|
|
||||||
File file = openWrite(_fs, "/s_contacts");
|
|
||||||
if (file) {
|
|
||||||
uint8_t unused[5];
|
|
||||||
memset(unused, 0, sizeof(unused));
|
|
||||||
|
|
||||||
for (int i = 0; i < num_contacts; i++) {
|
|
||||||
auto c = &contacts[i];
|
|
||||||
if (c->permissions == 0) continue; // skip deleted entries
|
|
||||||
|
|
||||||
bool success = (file.write(c->id.pub_key, 32) == 32);
|
|
||||||
success = success && (file.write((uint8_t *) &c->permissions, 1) == 1);
|
|
||||||
success = success && (file.write(unused, 6) == 6);
|
|
||||||
success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1);
|
|
||||||
success = success && (file.write(c->out_path, 64) == 64);
|
|
||||||
success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
|
||||||
|
|
||||||
if (!success) break; // write failed
|
|
||||||
}
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t getDataSize(uint8_t type) {
|
static uint8_t getDataSize(uint8_t type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case LPP_GPS:
|
case LPP_GPS:
|
||||||
@@ -295,8 +223,8 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint
|
|||||||
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 < acl.getNumClients() && ofs + 7 <= sizeof(reply_data) - 4; i++) {
|
||||||
auto c = &contacts[i];
|
auto c = acl.getClientByIdx(i);
|
||||||
if (c->permissions == 0) continue; // skip deleted entries
|
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;
|
||||||
@@ -318,63 +246,7 @@ mesh::Packet* SensorMesh::createSelfAdvert() {
|
|||||||
return createAdvert(self_id, app_data, app_data_len);
|
return createAdvert(self_id, app_data, app_data_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactInfo* SensorMesh::getContact(const uint8_t* pubkey, int key_len) {
|
void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) {
|
||||||
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;
|
|
||||||
ContactInfo* oldest = &contacts[MAX_CONTACTS - 1];
|
|
||||||
for (int i = 0; i < num_contacts; i++) {
|
|
||||||
if (id.matches(contacts[i].id)) return &contacts[i]; // already known
|
|
||||||
if (!contacts[i].isAdmin() && contacts[i].last_activity < min_time) {
|
|
||||||
oldest = &contacts[i];
|
|
||||||
min_time = oldest->last_activity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ContactInfo* c;
|
|
||||||
if (num_contacts < MAX_CONTACTS) {
|
|
||||||
c = &contacts[num_contacts++];
|
|
||||||
} else {
|
|
||||||
c = oldest; // evict least active contact
|
|
||||||
}
|
|
||||||
memset(c, 0, sizeof(*c));
|
|
||||||
c->permissions = init_perms;
|
|
||||||
c->id = id;
|
|
||||||
c->out_path_len = -1; // initially out_path is unknown
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
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 {
|
|
||||||
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
|
|
||||||
self_id.calcSharedSecret(c->shared_secret, pubkey);
|
|
||||||
}
|
|
||||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger saveContacts()
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SensorMesh::sendAlert(ContactInfo* c, Trigger* t) {
|
|
||||||
int text_len = strlen(t->text);
|
int text_len = strlen(t->text);
|
||||||
|
|
||||||
uint8_t data[MAX_PACKET_PAYLOAD];
|
uint8_t data[MAX_PACKET_PAYLOAD];
|
||||||
@@ -457,9 +329,9 @@ 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) {
|
||||||
ContactInfo* client;
|
ClientInfo* client;
|
||||||
if (data[0] == 0) { // blank password, just check if sender is in ACL
|
if (data[0] == 0) { // blank password, just check if sender is in ACL
|
||||||
client = getContact(sender.pub_key, PUB_KEY_SIZE);
|
client = acl.getClient(sender.pub_key, PUB_KEY_SIZE);
|
||||||
if (client == NULL) {
|
if (client == NULL) {
|
||||||
#if MESH_DEBUG
|
#if MESH_DEBUG
|
||||||
MESH_DEBUG_PRINTLN("Login, sender not in ACL");
|
MESH_DEBUG_PRINTLN("Login, sender not in ACL");
|
||||||
@@ -474,7 +346,7 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t*
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
client = putContact(sender, PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO); // add to contacts (if not already known)
|
client = acl.putClient(sender, PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO); // add to contacts (if not already known)
|
||||||
if (sender_timestamp <= client->last_timestamp) {
|
if (sender_timestamp <= client->last_timestamp) {
|
||||||
MESH_DEBUG_PRINTLN("Possible login replay attack!");
|
MESH_DEBUG_PRINTLN("Possible login replay attack!");
|
||||||
return 0; // FATAL: client table is full -OR- replay attack
|
return 0; // FATAL: client table is full -OR- replay attack
|
||||||
@@ -527,7 +399,8 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r
|
|||||||
int hex_len = min(sp - hex, PUB_KEY_SIZE*2);
|
int hex_len = min(sp - hex, PUB_KEY_SIZE*2);
|
||||||
if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) {
|
if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) {
|
||||||
uint8_t perms = atoi(sp);
|
uint8_t perms = atoi(sp);
|
||||||
if (applyContactPermissions(pubkey, hex_len / 2, perms)) {
|
if (acl.applyPermissions(self_id, pubkey, hex_len / 2, perms)) {
|
||||||
|
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger acl.save()
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "Err - invalid params");
|
strcpy(reply, "Err - invalid params");
|
||||||
@@ -538,8 +411,8 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r
|
|||||||
}
|
}
|
||||||
} else if (sender_timestamp == 0 && strcmp(command, "get acl") == 0) {
|
} else if (sender_timestamp == 0 && strcmp(command, "get acl") == 0) {
|
||||||
Serial.println("ACL:");
|
Serial.println("ACL:");
|
||||||
for (int i = 0; i < num_contacts; i++) {
|
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||||
auto c = &contacts[i];
|
auto c = acl.getClientByIdx(i);
|
||||||
if (c->permissions == 0) continue; // skip deleted entries
|
if (c->permissions == 0) continue; // skip deleted entries
|
||||||
|
|
||||||
Serial.printf("%02X ", c->permissions);
|
Serial.printf("%02X ", c->permissions);
|
||||||
@@ -595,8 +468,8 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con
|
|||||||
|
|
||||||
int SensorMesh::searchPeersByHash(const uint8_t* hash) {
|
int SensorMesh::searchPeersByHash(const uint8_t* hash) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (int i = 0; i < num_contacts && n < MAX_SEARCH_RESULTS; i++) {
|
for (int i = 0; i < acl.getNumClients() && n < MAX_SEARCH_RESULTS; i++) {
|
||||||
if (contacts[i].id.isHashMatch(hash)) {
|
if (acl.getClientByIdx(i)->id.isHashMatch(hash)) {
|
||||||
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
|
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -605,15 +478,15 @@ int SensorMesh::searchPeersByHash(const uint8_t* hash) {
|
|||||||
|
|
||||||
void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) {
|
void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) {
|
||||||
int i = matching_peer_indexes[peer_idx];
|
int i = matching_peer_indexes[peer_idx];
|
||||||
if (i >= 0 && i < num_contacts) {
|
if (i >= 0 && i < acl.getNumClients()) {
|
||||||
// lookup pre-calculated shared_secret
|
// lookup pre-calculated shared_secret
|
||||||
memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE);
|
memcpy(dest_secret, acl.getClientByIdx(i)->shared_secret, PUB_KEY_SIZE);
|
||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i);
|
MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
void SensorMesh::sendAckTo(const ClientInfo& 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) sendFlood(ack, TXT_ACK_DELAY);
|
||||||
@@ -632,34 +505,34 @@ void SensorMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
|||||||
|
|
||||||
void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) {
|
void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) {
|
||||||
int i = matching_peer_indexes[sender_idx];
|
int i = matching_peer_indexes[sender_idx];
|
||||||
if (i < 0 || i >= num_contacts) {
|
if (i < 0 || i >= acl.getNumClients()) {
|
||||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: Invalid sender idx: %d", i);
|
MESH_DEBUG_PRINTLN("onPeerDataRecv: Invalid sender idx: %d", i);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactInfo& from = contacts[i];
|
ClientInfo* from = acl.getClientByIdx(i);
|
||||||
|
|
||||||
if (type == PAYLOAD_TYPE_REQ) { // request (from a known contact)
|
if (type == PAYLOAD_TYPE_REQ) { // request (from a known contact)
|
||||||
uint32_t timestamp;
|
uint32_t timestamp;
|
||||||
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() ? 0xFF : 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;
|
||||||
from.last_activity = getRTCClock()->getCurrentTime();
|
from->last_activity = getRTCClock()->getCurrentTime();
|
||||||
|
|
||||||
if (packet->isRouteFlood()) {
|
if (packet->isRouteFlood()) {
|
||||||
// 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, 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 {
|
||||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, reply_data, reply_len);
|
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from->id, secret, reply_data, 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);
|
sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||||
}
|
}
|
||||||
@@ -668,30 +541,30 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
|||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
|
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
|
||||||
}
|
}
|
||||||
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && from.isAdmin()) { // a CLI command
|
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && from->isAdmin()) { // a CLI command
|
||||||
uint32_t sender_timestamp;
|
uint32_t sender_timestamp;
|
||||||
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||||
uint flags = (data[4] >> 2); // message attempt number, and other flags
|
uint flags = (data[4] >> 2); // message attempt number, and other flags
|
||||||
|
|
||||||
if (sender_timestamp > from.last_timestamp) { // prevent replay attacks
|
if (sender_timestamp > from->last_timestamp) { // prevent replay attacks
|
||||||
if (flags == TXT_TYPE_PLAIN) {
|
if (flags == TXT_TYPE_PLAIN) {
|
||||||
bool handled = handleIncomingMsg(from, sender_timestamp, &data[5], flags, len - 5);
|
bool handled = handleIncomingMsg(*from, sender_timestamp, &data[5], flags, len - 5);
|
||||||
if (handled) { // if msg was handled then send an ack
|
if (handled) { // if msg was handled then send an ack
|
||||||
uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it
|
uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it
|
||||||
mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), from.id.pub_key, PUB_KEY_SIZE);
|
mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), from->id.pub_key, PUB_KEY_SIZE);
|
||||||
|
|
||||||
if (packet->isRouteFlood()) {
|
if (packet->isRouteFlood()) {
|
||||||
// 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) sendFlood(path, TXT_ACK_DELAY);
|
||||||
} else {
|
} else {
|
||||||
sendAckTo(from, ack_hash);
|
sendAckTo(*from, ack_hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (flags == TXT_TYPE_CLI_DATA) {
|
} else if (flags == TXT_TYPE_CLI_DATA) {
|
||||||
from.last_timestamp = sender_timestamp;
|
from->last_timestamp = sender_timestamp;
|
||||||
from.last_activity = getRTCClock()->getCurrentTime();
|
from->last_activity = getRTCClock()->getCurrentTime();
|
||||||
|
|
||||||
// len can be > original length, but 'text' will be padded with zeroes
|
// len can be > original length, but 'text' will be padded with zeroes
|
||||||
data[len] = 0; // need to make a C string again, with null terminator
|
data[len] = 0; // need to make a C string again, with null terminator
|
||||||
@@ -711,12 +584,12 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
|||||||
memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique
|
memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique
|
||||||
temp[4] = (TXT_TYPE_CLI_DATA << 2);
|
temp[4] = (TXT_TYPE_CLI_DATA << 2);
|
||||||
|
|
||||||
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len);
|
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from->id, secret, temp, 5 + text_len);
|
||||||
if (reply) {
|
if (reply) {
|
||||||
if (from.out_path_len < 0) {
|
if (from->out_path_len < 0) {
|
||||||
sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
|
sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
|
||||||
} else {
|
} else {
|
||||||
sendDirect(reply, from.out_path, from.out_path_len, CLI_REPLY_DELAY_MILLIS);
|
sendDirect(reply, from->out_path, from->out_path_len, CLI_REPLY_DELAY_MILLIS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -729,7 +602,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SensorMesh::handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) {
|
bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) {
|
||||||
MESH_DEBUG_PRINT("handleIncomingMsg: unhandled msg from ");
|
MESH_DEBUG_PRINT("handleIncomingMsg: unhandled msg from ");
|
||||||
#ifdef MESH_DEBUG
|
#ifdef MESH_DEBUG
|
||||||
mesh::Utils::printHex(Serial, from.id.pub_key, PUB_KEY_SIZE);
|
mesh::Utils::printHex(Serial, from.id.pub_key, PUB_KEY_SIZE);
|
||||||
@@ -740,21 +613,21 @@ bool SensorMesh::handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_
|
|||||||
|
|
||||||
bool SensorMesh::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) {
|
bool SensorMesh::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) {
|
||||||
int i = matching_peer_indexes[sender_idx];
|
int i = matching_peer_indexes[sender_idx];
|
||||||
if (i < 0 || i >= num_contacts) {
|
if (i < 0 || i >= acl.getNumClients()) {
|
||||||
MESH_DEBUG_PRINTLN("onPeerPathRecv: Invalid sender idx: %d", i);
|
MESH_DEBUG_PRINTLN("onPeerPathRecv: Invalid sender idx: %d", i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactInfo& from = contacts[i];
|
ClientInfo* from = acl.getClientByIdx(i);
|
||||||
|
|
||||||
MESH_DEBUG_PRINTLN("PATH to contact, path_len=%d", (uint32_t) path_len);
|
MESH_DEBUG_PRINTLN("PATH to contact, path_len=%d", (uint32_t) path_len);
|
||||||
// NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path.
|
// NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path.
|
||||||
// FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?)
|
// FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?)
|
||||||
memcpy(from.out_path, path, from.out_path_len = path_len); // store a copy of path, for sendDirect()
|
memcpy(from->out_path, path, from->out_path_len = path_len); // store a copy of path, for sendDirect()
|
||||||
from.last_activity = getRTCClock()->getCurrentTime();
|
from->last_activity = getRTCClock()->getCurrentTime();
|
||||||
|
|
||||||
// REVISIT: maybe make ALL out_paths non-persisted to minimise flash writes??
|
// REVISIT: maybe make ALL out_paths non-persisted to minimise flash writes??
|
||||||
if (from.isAdmin()) {
|
if (from->isAdmin()) {
|
||||||
// only do saveContacts() (of this out_path change) if this is an admin
|
// only do saveContacts() (of this out_path change) if this is an admin
|
||||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||||
}
|
}
|
||||||
@@ -781,7 +654,6 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
|||||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||||
{
|
{
|
||||||
num_contacts = 0;
|
|
||||||
next_local_advert = next_flood_advert = 0;
|
next_local_advert = next_flood_advert = 0;
|
||||||
dirty_contacts_expiry = 0;
|
dirty_contacts_expiry = 0;
|
||||||
last_read_time = 0;
|
last_read_time = 0;
|
||||||
@@ -815,7 +687,7 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
|||||||
// load persisted prefs
|
// load persisted prefs
|
||||||
_cli.loadPrefs(_fs);
|
_cli.loadPrefs(_fs);
|
||||||
|
|
||||||
loadContacts();
|
acl.load(_fs);
|
||||||
|
|
||||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||||
@@ -967,13 +839,13 @@ void SensorMesh::loop() {
|
|||||||
if (millisHasNowPassed(t->send_expiry)) { // next send needed?
|
if (millisHasNowPassed(t->send_expiry)) { // next send needed?
|
||||||
if (t->attempt >= 4) { // max attempts reached, try next contact
|
if (t->attempt >= 4) { // max attempts reached, try next contact
|
||||||
t->curr_contact_idx++;
|
t->curr_contact_idx++;
|
||||||
if (t->curr_contact_idx >= num_contacts) { // no more contacts to try?
|
if (t->curr_contact_idx >= acl.getNumClients()) { // no more contacts to try?
|
||||||
num_alert_tasks--; // remove t from queue
|
num_alert_tasks--; // remove t from queue
|
||||||
for (int i = 0; i < num_alert_tasks; i++) {
|
for (int i = 0; i < num_alert_tasks; i++) {
|
||||||
alert_tasks[i] = alert_tasks[i + 1];
|
alert_tasks[i] = alert_tasks[i + 1];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto c = &contacts[t->curr_contact_idx];
|
auto c = acl.getClientByIdx(t->curr_contact_idx);
|
||||||
uint16_t pri_mask = (t->pri == HIGH_PRI_ALERT) ? PERM_RECV_ALERTS_HI : PERM_RECV_ALERTS_LO;
|
uint16_t pri_mask = (t->pri == HIGH_PRI_ALERT) ? PERM_RECV_ALERTS_HI : PERM_RECV_ALERTS_LO;
|
||||||
|
|
||||||
if (c->permissions & pri_mask) { // contact wants alert
|
if (c->permissions & pri_mask) { // contact wants alert
|
||||||
@@ -986,8 +858,8 @@ void SensorMesh::loop() {
|
|||||||
// next contact tested in next ::loop()
|
// next contact tested in next ::loop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (t->curr_contact_idx < num_contacts) {
|
} else if (t->curr_contact_idx < acl.getNumClients()) {
|
||||||
auto c = &contacts[t->curr_contact_idx]; // send next attempt
|
auto c = acl.getClientByIdx(t->curr_contact_idx); // send next attempt
|
||||||
sendAlert(c, t); // NOTE: modifies attempt, expected_acks[] and send_expiry
|
sendAlert(c, t); // NOTE: modifies attempt, expected_acks[] and send_expiry
|
||||||
} else {
|
} else {
|
||||||
// contact list has likely been modified while waiting for alert ACK, cancel this task
|
// contact list has likely been modified while waiting for alert ACK, cancel this task
|
||||||
@@ -998,7 +870,7 @@ void SensorMesh::loop() {
|
|||||||
|
|
||||||
// is there are pending dirty contacts write needed?
|
// is there are pending dirty contacts write needed?
|
||||||
if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) {
|
if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) {
|
||||||
saveContacts();
|
acl.save(_fs);
|
||||||
dirty_contacts_expiry = 0;
|
dirty_contacts_expiry = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,15 +20,10 @@
|
|||||||
#include <helpers/AdvertDataHelpers.h>
|
#include <helpers/AdvertDataHelpers.h>
|
||||||
#include <helpers/TxtDataHelpers.h>
|
#include <helpers/TxtDataHelpers.h>
|
||||||
#include <helpers/CommonCLI.h>
|
#include <helpers/CommonCLI.h>
|
||||||
|
#include <helpers/ClientACL.h>
|
||||||
#include <RTClib.h>
|
#include <RTClib.h>
|
||||||
#include <target.h>
|
#include <target.h>
|
||||||
|
|
||||||
#define PERM_ACL_ROLE_MASK 3 // lower 2 bits
|
|
||||||
#define PERM_ACL_GUEST 0
|
|
||||||
#define PERM_ACL_READ_ONLY 1
|
|
||||||
#define PERM_ACL_READ_WRITE 2
|
|
||||||
#define PERM_ACL_ADMIN 3
|
|
||||||
|
|
||||||
#define PERM_RESERVED1 (1 << 2)
|
#define PERM_RESERVED1 (1 << 2)
|
||||||
#define PERM_RESERVED2 (1 << 3)
|
#define PERM_RESERVED2 (1 << 3)
|
||||||
#define PERM_RESERVED3 (1 << 4)
|
#define PERM_RESERVED3 (1 << 4)
|
||||||
@@ -36,18 +31,6 @@
|
|||||||
#define PERM_RECV_ALERTS_LO (1 << 6) // low priority alerts
|
#define PERM_RECV_ALERTS_LO (1 << 6) // low priority alerts
|
||||||
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
|
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
|
||||||
|
|
||||||
struct ContactInfo {
|
|
||||||
mesh::Identity id;
|
|
||||||
uint8_t permissions;
|
|
||||||
int8_t out_path_len;
|
|
||||||
uint8_t out_path[MAX_PATH_SIZE];
|
|
||||||
uint8_t shared_secret[PUB_KEY_SIZE];
|
|
||||||
uint32_t last_timestamp; // by THEIR clock (transient)
|
|
||||||
uint32_t last_activity; // by OUR clock (transient)
|
|
||||||
|
|
||||||
bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; }
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifndef FIRMWARE_BUILD_DATE
|
#ifndef FIRMWARE_BUILD_DATE
|
||||||
#define FIRMWARE_BUILD_DATE "1 Sep 2025"
|
#define FIRMWARE_BUILD_DATE "1 Sep 2025"
|
||||||
#endif
|
#endif
|
||||||
@@ -58,8 +41,6 @@ struct ContactInfo {
|
|||||||
|
|
||||||
#define FIRMWARE_ROLE "sensor"
|
#define FIRMWARE_ROLE "sensor"
|
||||||
|
|
||||||
#define MAX_CONTACTS 20
|
|
||||||
|
|
||||||
#define MAX_SEARCH_RESULTS 8
|
#define MAX_SEARCH_RESULTS 8
|
||||||
#define MAX_CONCURRENT_ALERTS 4
|
#define MAX_CONCURRENT_ALERTS 4
|
||||||
|
|
||||||
@@ -141,16 +122,15 @@ protected:
|
|||||||
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 onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
||||||
virtual bool handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len);
|
virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len);
|
||||||
void sendAckTo(const ContactInfo& dest, uint32_t ack_hash);
|
void sendAckTo(const ClientInfo& dest, uint32_t ack_hash);
|
||||||
private:
|
private:
|
||||||
FILESYSTEM* _fs;
|
FILESYSTEM* _fs;
|
||||||
unsigned long next_local_advert, next_flood_advert;
|
unsigned long next_local_advert, next_flood_advert;
|
||||||
NodePrefs _prefs;
|
NodePrefs _prefs;
|
||||||
CommonCLI _cli;
|
CommonCLI _cli;
|
||||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||||
ContactInfo contacts[MAX_CONTACTS];
|
ClientACL acl;
|
||||||
int num_contacts;
|
|
||||||
unsigned long dirty_contacts_expiry;
|
unsigned long dirty_contacts_expiry;
|
||||||
CayenneLPP telemetry;
|
CayenneLPP telemetry;
|
||||||
uint32_t last_read_time;
|
uint32_t last_read_time;
|
||||||
@@ -163,15 +143,10 @@ private:
|
|||||||
uint8_t pending_sf;
|
uint8_t pending_sf;
|
||||||
uint8_t pending_cr;
|
uint8_t pending_cr;
|
||||||
|
|
||||||
void loadContacts();
|
|
||||||
void saveContacts();
|
|
||||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data);
|
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data);
|
||||||
uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len);
|
uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len);
|
||||||
mesh::Packet* createSelfAdvert();
|
mesh::Packet* createSelfAdvert();
|
||||||
ContactInfo* getContact(const uint8_t* pubkey, int key_len);
|
|
||||||
ContactInfo* putContact(const mesh::Identity& id, uint8_t init_perms);
|
|
||||||
bool applyContactPermissions(const uint8_t* pubkey, int key_len, uint8_t perms);
|
|
||||||
|
|
||||||
void sendAlert(ContactInfo* c, Trigger* t);
|
void sendAlert(const ClientInfo* c, Trigger* t);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ void LocalIdentity::sign(uint8_t* sig, const uint8_t* message, int msg_len) cons
|
|||||||
ed25519_sign(sig, message, msg_len, pub_key, prv_key);
|
ed25519_sign(sig, message, msg_len, pub_key, prv_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalIdentity::calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) {
|
void LocalIdentity::calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) const {
|
||||||
ed25519_key_exchange(secret, other_pub_key, prv_key);
|
ed25519_key_exchange(secret, other_pub_key, prv_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public:
|
|||||||
* \param secret OUT - the 'shared secret' (must be PUB_KEY_SIZE bytes)
|
* \param secret OUT - the 'shared secret' (must be PUB_KEY_SIZE bytes)
|
||||||
* \param other_pub_key IN - the public key of second party in the exchange (must be PUB_KEY_SIZE bytes)
|
* \param other_pub_key IN - the public key of second party in the exchange (must be PUB_KEY_SIZE bytes)
|
||||||
*/
|
*/
|
||||||
void calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key);
|
void calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) const;
|
||||||
|
|
||||||
bool readFrom(Stream& s);
|
bool readFrom(Stream& s);
|
||||||
bool writeTo(Stream& s) const;
|
bool writeTo(Stream& s) const;
|
||||||
|
|||||||
128
src/helpers/ClientACL.cpp
Normal file
128
src/helpers/ClientACL.cpp
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
#include "ClientACL.h"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientACL::load(FILESYSTEM* _fs) {
|
||||||
|
num_clients = 0;
|
||||||
|
if (_fs->exists("/s_contacts")) {
|
||||||
|
#if defined(RP2040_PLATFORM)
|
||||||
|
File file = _fs->open("/s_contacts", "r");
|
||||||
|
#else
|
||||||
|
File file = _fs->open("/s_contacts");
|
||||||
|
#endif
|
||||||
|
if (file) {
|
||||||
|
bool full = false;
|
||||||
|
while (!full) {
|
||||||
|
ClientInfo c;
|
||||||
|
uint8_t pub_key[32];
|
||||||
|
uint8_t unused[6];
|
||||||
|
|
||||||
|
bool success = (file.read(pub_key, 32) == 32);
|
||||||
|
success = success && (file.read((uint8_t *) &c.permissions, 1) == 1);
|
||||||
|
success = success && (file.read(unused, 6) == 6);
|
||||||
|
success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1);
|
||||||
|
success = success && (file.read(c.out_path, 64) == 64);
|
||||||
|
success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||||
|
c.last_timestamp = 0; // transient
|
||||||
|
c.last_activity = 0;
|
||||||
|
|
||||||
|
if (!success) break; // EOF
|
||||||
|
|
||||||
|
c.id = mesh::Identity(pub_key);
|
||||||
|
if (num_clients < MAX_CLIENTS) {
|
||||||
|
clients[num_clients++] = c;
|
||||||
|
} else {
|
||||||
|
full = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientACL::save(FILESYSTEM* _fs) {
|
||||||
|
File file = openWrite(_fs, "/s_contacts");
|
||||||
|
if (file) {
|
||||||
|
uint8_t unused[6];
|
||||||
|
memset(unused, 0, sizeof(unused));
|
||||||
|
|
||||||
|
for (int i = 0; i < num_clients; i++) {
|
||||||
|
auto c = &clients[i];
|
||||||
|
if (c->permissions == 0) continue; // skip deleted entries
|
||||||
|
|
||||||
|
bool success = (file.write(c->id.pub_key, 32) == 32);
|
||||||
|
success = success && (file.write((uint8_t *) &c->permissions, 1) == 1);
|
||||||
|
success = success && (file.write(unused, 6) == 6);
|
||||||
|
success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1);
|
||||||
|
success = success && (file.write(c->out_path, 64) == 64);
|
||||||
|
success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||||
|
|
||||||
|
if (!success) break; // write failed
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientInfo* ClientACL::getClient(const uint8_t* pubkey, int key_len) {
|
||||||
|
for (int i = 0; i < num_clients; i++) {
|
||||||
|
if (memcmp(pubkey, clients[i].id.pub_key, key_len) == 0) return &clients[i]; // already known
|
||||||
|
}
|
||||||
|
return NULL; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientInfo* ClientACL::putClient(const mesh::Identity& id, uint8_t init_perms) {
|
||||||
|
uint32_t min_time = 0xFFFFFFFF;
|
||||||
|
ClientInfo* oldest = &clients[MAX_CLIENTS - 1];
|
||||||
|
for (int i = 0; i < num_clients; i++) {
|
||||||
|
if (id.matches(clients[i].id)) return &clients[i]; // already known
|
||||||
|
if (!clients[i].isAdmin() && clients[i].last_activity < min_time) {
|
||||||
|
oldest = &clients[i];
|
||||||
|
min_time = oldest->last_activity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientInfo* c;
|
||||||
|
if (num_clients < MAX_CLIENTS) {
|
||||||
|
c = &clients[num_clients++];
|
||||||
|
} else {
|
||||||
|
c = oldest; // evict least active contact
|
||||||
|
}
|
||||||
|
memset(c, 0, sizeof(*c));
|
||||||
|
c->permissions = init_perms;
|
||||||
|
c->id = id;
|
||||||
|
c->out_path_len = -1; // initially out_path is unknown
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClientACL::applyPermissions(const mesh::LocalIdentity& self_id, const uint8_t* pubkey, int key_len, uint8_t perms) {
|
||||||
|
ClientInfo* c;
|
||||||
|
if ((perms & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { // guest role is not persisted in contacts
|
||||||
|
c = getClient(pubkey, key_len);
|
||||||
|
if (c == NULL) return false; // partial pubkey not found
|
||||||
|
|
||||||
|
num_clients--; // delete from contacts[]
|
||||||
|
int i = c - clients;
|
||||||
|
while (i < num_clients) {
|
||||||
|
clients[i] = clients[i + 1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (key_len < PUB_KEY_SIZE) return false; // need complete pubkey when adding/modifying
|
||||||
|
|
||||||
|
mesh::Identity id(pubkey);
|
||||||
|
c = putClient(id, 0);
|
||||||
|
|
||||||
|
c->permissions = perms; // update their permissions
|
||||||
|
self_id.calcSharedSecret(c->shared_secret, pubkey);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
47
src/helpers/ClientACL.h
Normal file
47
src/helpers/ClientACL.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h> // needed for PlatformIO
|
||||||
|
#include <Mesh.h>
|
||||||
|
#include <helpers/IdentityStore.h>
|
||||||
|
|
||||||
|
#define PERM_ACL_ROLE_MASK 3 // lower 2 bits
|
||||||
|
#define PERM_ACL_GUEST 0
|
||||||
|
#define PERM_ACL_READ_ONLY 1
|
||||||
|
#define PERM_ACL_READ_WRITE 2
|
||||||
|
#define PERM_ACL_ADMIN 3
|
||||||
|
|
||||||
|
struct ClientInfo {
|
||||||
|
mesh::Identity id;
|
||||||
|
uint8_t permissions;
|
||||||
|
int8_t out_path_len;
|
||||||
|
uint8_t out_path[MAX_PATH_SIZE];
|
||||||
|
uint8_t shared_secret[PUB_KEY_SIZE];
|
||||||
|
uint32_t last_timestamp; // by THEIR clock (transient)
|
||||||
|
uint32_t last_activity; // by OUR clock (transient)
|
||||||
|
|
||||||
|
bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef MAX_CLIENTS
|
||||||
|
#define MAX_CLIENTS 20
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class ClientACL {
|
||||||
|
ClientInfo clients[MAX_CLIENTS];
|
||||||
|
int num_clients;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ClientACL() {
|
||||||
|
memset(clients, 0, sizeof(clients));
|
||||||
|
num_clients = 0;
|
||||||
|
}
|
||||||
|
void load(FILESYSTEM* _fs);
|
||||||
|
void save(FILESYSTEM* _fs);
|
||||||
|
|
||||||
|
ClientInfo* getClient(const uint8_t* pubkey, int key_len);
|
||||||
|
ClientInfo* putClient(const mesh::Identity& id, uint8_t init_perms);
|
||||||
|
bool applyPermissions(const mesh::LocalIdentity& self_id, const uint8_t* pubkey, int key_len, uint8_t perms);
|
||||||
|
|
||||||
|
int getNumClients() const { return num_clients; }
|
||||||
|
ClientInfo* getClientByIdx(int idx) { return &clients[idx]; }
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user