Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions examples/companion_radio/DataStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,36 @@ void DataStore::saveChannels(DataStoreHost* host) {
}
}

void DataStore::loadNonces(DataStoreHost* host) {
File file = openRead(_getContactsChannelsFS(), "/nonces");
if (file) {
uint8_t rec[6]; // 4-byte pub_key prefix + 2-byte nonce
while (file.read(rec, 6) == 6) {
uint16_t nonce;
memcpy(&nonce, &rec[4], 2);
host->onNonceLoaded(rec, nonce);
}
file.close();
}
}

bool DataStore::saveNonces(DataStoreHost* host) {
File file = openWrite(_getContactsChannelsFS(), "/nonces");
if (file) {
int idx = 0;
uint8_t pub_key_prefix[4];
uint16_t nonce;
while (host->getNonceForSave(idx, pub_key_prefix, &nonce)) {
file.write(pub_key_prefix, 4);
file.write((uint8_t*)&nonce, 2);
idx++;
}
file.close();
return true;
}
return false;
}

#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)

#define MAX_ADVERT_PKT_LEN (2 + 32 + PUB_KEY_SIZE + 4 + SIGNATURE_SIZE + MAX_ADVERT_DATA_SIZE)
Expand Down
4 changes: 4 additions & 0 deletions examples/companion_radio/DataStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class DataStoreHost {
virtual bool getContactForSave(uint32_t idx, ContactInfo& contact) =0;
virtual bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) =0;
virtual bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) =0;
virtual bool onNonceLoaded(const uint8_t* pub_key_prefix, uint16_t nonce) { return false; }
virtual bool getNonceForSave(int idx, uint8_t* pub_key_prefix, uint16_t* nonce) { return false; }
};

class DataStore {
Expand Down Expand Up @@ -39,6 +41,8 @@ class DataStore {
void saveContacts(DataStoreHost* host);
void loadChannels(DataStoreHost* host);
void saveChannels(DataStoreHost* host);
void loadNonces(DataStoreHost* host);
bool saveNonces(DataStoreHost* host);
void migrateToSecondaryFS();
uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]);
bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len);
Expand Down
19 changes: 19 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
next_ack_idx = 0;
sign_data = NULL;
dirty_contacts_expiry = 0;
next_nonce_persist = 0;
memset(advert_paths, 0, sizeof(advert_paths));
memset(send_scope.key, 0, sizeof(send_scope.key));

Expand Down Expand Up @@ -864,6 +865,14 @@ void MyMesh::begin(bool has_display) {
resetContacts();
_store->loadContacts(this);
bootstrapRTCfromContacts();

// Load persisted AEAD nonces and apply boot bump if needed
_store->loadNonces(this);
bool dirty_reset = wasDirtyReset(board);
finalizeNonceLoad(dirty_reset);
if (dirty_reset) saveNonces(); // persist bumped nonces immediately
next_nonce_persist = futureMillis(60000);

addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
_store->loadChannels(this);

Expand Down Expand Up @@ -1275,6 +1284,7 @@ void MyMesh::handleCmdFrame(size_t len) {
if (dirty_contacts_expiry) { // is there are pending dirty contacts write needed?
saveContacts();
}
if (isNonceDirty()) saveNonces();
board.reboot();
} else if (cmd_frame[0] == CMD_GET_BATT_AND_STORAGE) {
uint8_t reply[11];
Expand Down Expand Up @@ -1916,6 +1926,7 @@ void MyMesh::checkCLIRescueCmd() {
}

} else if (strcmp(cli_command, "reboot") == 0) {
if (isNonceDirty()) saveNonces();
board.reboot(); // doesn't return
} else {
Serial.println(" Error: unknown command");
Expand Down Expand Up @@ -1967,6 +1978,14 @@ void MyMesh::loop() {
dirty_contacts_expiry = 0;
}

// periodic AEAD nonce persistence
if (next_nonce_persist && millisHasNowPassed(next_nonce_persist)) {
if (isNonceDirty()) {
saveNonces();
}
next_nonce_persist = futureMillis(60000);
}

#ifdef DISPLAY_CLASS
if (_ui) _ui->setHasConnection(_serial->isConnected());
#endif
Expand Down
4 changes: 4 additions & 0 deletions examples/companion_radio/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
bool getContactForSave(uint32_t idx, ContactInfo& contact) override { return getContactByIdx(idx, contact); }
bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) override { return setChannel(channel_idx, ch); }
bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) override { return getChannel(channel_idx, ch); }
bool onNonceLoaded(const uint8_t* pub_key_prefix, uint16_t nonce) override { return applyLoadedNonce(pub_key_prefix, nonce); }
bool getNonceForSave(int idx, uint8_t* pub_key_prefix, uint16_t* nonce) override { return getNonceEntry(idx, pub_key_prefix, nonce); }

void clearPendingReqs() {
pending_login = pending_status = pending_telemetry = pending_discovery = pending_req = 0;
Expand Down Expand Up @@ -180,6 +182,7 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
// helpers, short-cuts
void saveChannels() { _store->saveChannels(this); }
void saveContacts() { _store->saveContacts(this); }
void saveNonces() { if (_store->saveNonces(this)) clearNonceDirty(); }

DataStore* _store;
NodePrefs _prefs;
Expand All @@ -201,6 +204,7 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
uint8_t *sign_data;
uint32_t sign_data_len;
unsigned long dirty_contacts_expiry;
unsigned long next_nonce_persist;

TransportKey send_scope;

Expand Down
47 changes: 44 additions & 3 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,34 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
}
}

uint8_t MyMesh::getPeerFlags(int peer_idx) {
int i = matching_peer_indexes[peer_idx];
if (i >= 0 && i < acl.getNumClients())
return acl.getClientByIdx(i)->flags;
return 0;
}

uint16_t MyMesh::getPeerNextAeadNonce(int peer_idx) {
int i = matching_peer_indexes[peer_idx];
if (i >= 0 && i < acl.getNumClients())
return acl.nextAeadNonceFor(*acl.getClientByIdx(i));
return 0;
}

void MyMesh::onPeerAeadDetected(int peer_idx) {
int i = matching_peer_indexes[peer_idx];
if (i >= 0 && i < acl.getNumClients()) {
auto c = acl.getClientByIdx(i);
if (!(c->flags & CONTACT_FLAG_AEAD)) {
c->flags |= CONTACT_FLAG_AEAD;
if (c->aead_nonce == 0) { // no persisted nonce — seed from RNG to avoid deterministic start
getRNG()->random((uint8_t*)&c->aead_nonce, sizeof(c->aead_nonce));
if (c->aead_nonce == 0) c->aead_nonce = 1;
}
}
}
}

static bool isShare(const mesh::Packet *packet) {
if (packet->hasTransportCodes()) {
return packet->transport_codes[0] == 0 && packet->transport_codes[1] == 0; // codes { 0, 0 } means 'send to nowhere'
Expand Down Expand Up @@ -608,11 +636,11 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len, acl.nextAeadNonceFor(*client));
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
} else {
mesh::Packet *reply =
createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len, acl.nextAeadNonceFor(*client));
if (reply) {
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
Expand Down Expand Up @@ -673,7 +701,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
memcpy(temp, &timestamp, 4); // mostly an extra blob to help make packet_hash unique
temp[4] = (TXT_TYPE_CLI_DATA << 2); // NOTE: legacy was: TXT_TYPE_PLAIN

auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len);
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len, acl.nextAeadNonceFor(*client));
if (reply) {
if (client->out_path_len < 0) {
sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
Expand Down Expand Up @@ -758,6 +786,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
uptime_millis = 0;
next_local_advert = next_flood_advert = 0;
dirty_contacts_expiry = 0;
next_nonce_persist = 0;
set_radio_at = revert_radio_at = 0;
_logging = false;
region_load_active = false;
Expand Down Expand Up @@ -809,6 +838,12 @@ void MyMesh::begin(FILESYSTEM *fs) {
// load persisted prefs
_cli.loadPrefs(_fs);
acl.load(_fs, self_id);
acl.setRNG(getRNG());
acl.loadNonces();
bool dirty_reset = wasDirtyReset(board);
acl.finalizeNonceLoad(dirty_reset);
if (dirty_reset) acl.saveNonces(); // persist bumped nonces immediately
next_nonce_persist = futureMillis(60000);
// TODO: key_store.begin();
region_map.load(_fs);

Expand Down Expand Up @@ -1211,6 +1246,12 @@ void MyMesh::loop() {
dirty_contacts_expiry = 0;
}

// persist dirty AEAD nonces
if (next_nonce_persist && millisHasNowPassed(next_nonce_persist)) {
if (acl.isNonceDirty()) { acl.saveNonces(); }
next_nonce_persist = futureMillis(60000);
}

// update uptime
uint32_t now = millis();
uptime_millis += now - last_millis;
Expand Down
7 changes: 7 additions & 0 deletions examples/simple_repeater/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
RateLimiter discover_limiter, anon_limiter;
bool region_load_active;
unsigned long dirty_contacts_expiry;
unsigned long next_nonce_persist;
#if MAX_NEIGHBOURS
NeighbourInfo neighbours[MAX_NEIGHBOURS];
#endif
Expand Down Expand Up @@ -163,6 +164,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
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;
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
uint8_t getPeerFlags(int peer_idx) override;
uint16_t getPeerNextAeadNonce(int peer_idx) override;
void onPeerAeadDetected(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 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;
Expand All @@ -184,6 +188,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
void savePrefs() override {
_cli.savePrefs(_fs);
}
void onBeforeReboot() override {
if (acl.isNonceDirty()) acl.saveNonces();
}

void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
bool formatFileSystem() override;
Expand Down
49 changes: 45 additions & 4 deletions examples/simple_room_server/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) {
mesh::Utils::sha256((uint8_t *)&client->extra.room.pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE);
client->extra.room.push_post_timestamp = post.post_timestamp;

auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len);
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len, acl.nextAeadNonceFor(*client));
if (reply) {
if (client->out_path_len < 0) {
sendFlood(reply);
Expand Down Expand Up @@ -387,6 +387,34 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
}
}

uint8_t MyMesh::getPeerFlags(int peer_idx) {
int i = matching_peer_indexes[peer_idx];
if (i >= 0 && i < acl.getNumClients())
return acl.getClientByIdx(i)->flags;
return 0;
}

uint16_t MyMesh::getPeerNextAeadNonce(int peer_idx) {
int i = matching_peer_indexes[peer_idx];
if (i >= 0 && i < acl.getNumClients())
return acl.nextAeadNonceFor(*acl.getClientByIdx(i));
return 0;
}

void MyMesh::onPeerAeadDetected(int peer_idx) {
int i = matching_peer_indexes[peer_idx];
if (i >= 0 && i < acl.getNumClients()) {
auto c = acl.getClientByIdx(i);
if (!(c->flags & CONTACT_FLAG_AEAD)) {
c->flags |= CONTACT_FLAG_AEAD;
if (c->aead_nonce == 0) { // no persisted nonce — seed from RNG to avoid deterministic start
getRNG()->random((uint8_t*)&c->aead_nonce, sizeof(c->aead_nonce));
if (c->aead_nonce == 0) c->aead_nonce = 1;
}
}
}
}

void MyMesh::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];
Expand Down Expand Up @@ -480,7 +508,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
// mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key,
// PUB_KEY_SIZE);

auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len);
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len, acl.nextAeadNonceFor(*client));
if (reply) {
if (client->out_path_len < 0) {
sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY);
Expand Down Expand Up @@ -537,10 +565,10 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len, acl.nextAeadNonceFor(*client));
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
} else {
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len, acl.nextAeadNonceFor(*client));
if (reply) {
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
Expand Down Expand Up @@ -592,6 +620,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
uptime_millis = 0;
next_local_advert = next_flood_advert = 0;
dirty_contacts_expiry = 0;
next_nonce_persist = 0;
_logging = false;
set_radio_at = revert_radio_at = 0;

Expand Down Expand Up @@ -638,6 +667,12 @@ void MyMesh::begin(FILESYSTEM *fs) {
_cli.loadPrefs(_fs);

acl.load(_fs, self_id);
acl.setRNG(getRNG());
acl.loadNonces();
bool dirty_reset = wasDirtyReset(board);
acl.finalizeNonceLoad(dirty_reset);
if (dirty_reset) acl.saveNonces(); // persist bumped nonces immediately
next_nonce_persist = futureMillis(60000);

radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_prefs.tx_power_dbm);
Expand Down Expand Up @@ -885,6 +920,12 @@ void MyMesh::loop() {
dirty_contacts_expiry = 0;
}

// persist dirty AEAD nonces
if (next_nonce_persist && millisHasNowPassed(next_nonce_persist)) {
if (acl.isNonceDirty()) { acl.saveNonces(); }
next_nonce_persist = futureMillis(60000);
}

// TODO: periodically check for OLD/inactive entries in known_clients[], and evict

// update uptime
Expand Down
7 changes: 7 additions & 0 deletions examples/simple_room_server/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
ClientACL acl;
CommonCLI _cli;
unsigned long dirty_contacts_expiry;
unsigned long next_nonce_persist;
uint8_t reply_data[MAX_PACKET_PAYLOAD];
unsigned long next_push;
uint16_t _num_posted, _num_post_pushes;
Expand Down Expand Up @@ -148,6 +149,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
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 ;
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
uint8_t getPeerFlags(int peer_idx) override;
uint16_t getPeerNextAeadNonce(int peer_idx) override;
void onPeerAeadDetected(int peer_idx) 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;
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
Expand All @@ -174,6 +178,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
void savePrefs() override {
_cli.savePrefs(_fs);
}
void onBeforeReboot() override {
if (acl.isNonceDirty()) acl.saveNonces();
}

void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
bool formatFileSystem() override;
Expand Down
Loading