@@ -909,7 +909,7 @@ bool BaseChatMesh::removeContact(ContactInfo& contact) {
909909 }
910910 if (idx >= num_contacts) return false ; // not found
911911
912- session_keys. remove (contact.id .pub_key ); // also remove session key if any
912+ removeSessionKey (contact.id .pub_key ); // also remove session key if any
913913
914914 // remove from contacts array and parallel nonce tracking
915915 num_contacts--;
@@ -1024,6 +1024,41 @@ void BaseChatMesh::loop() {
10241024 }
10251025}
10261026
1027+ // --- Session key flash-backed wrappers ---
1028+
1029+ SessionKeyEntry* BaseChatMesh::findSessionKey (const uint8_t * pub_key) {
1030+ auto entry = session_keys.findByPrefix (pub_key);
1031+ if (entry) return entry;
1032+
1033+ // Cache miss — try flash
1034+ uint8_t flags; uint16_t nonce;
1035+ uint8_t sk[SESSION_KEY_SIZE ], psk[SESSION_KEY_SIZE ];
1036+ if (!loadSessionKeyRecordFromFlash (pub_key, &flags, &nonce, sk, psk)) return nullptr ;
1037+
1038+ // Save dirty evictee before overwriting
1039+ if (session_keys.isFull () && session_keys_dirty) {
1040+ mergeAndSaveSessionKeys ();
1041+ }
1042+ session_keys.applyLoaded (pub_key, flags, nonce, sk, psk);
1043+ return session_keys.findByPrefix (pub_key);
1044+ }
1045+
1046+ SessionKeyEntry* BaseChatMesh::allocateSessionKey (const uint8_t * pub_key) {
1047+ // Check RAM and flash first
1048+ auto entry = findSessionKey (pub_key);
1049+ if (entry) return entry;
1050+
1051+ // Not found anywhere — save dirty evictee before allocating
1052+ if (session_keys.isFull () && session_keys_dirty) {
1053+ mergeAndSaveSessionKeys ();
1054+ }
1055+ return session_keys.allocate (pub_key);
1056+ }
1057+
1058+ void BaseChatMesh::removeSessionKey (const uint8_t * pub_key) {
1059+ session_keys.remove (pub_key);
1060+ }
1061+
10271062// --- Session key support (Phase 2 — initiator) ---
10281063
10291064static bool canUseSessionKey (const SessionKeyEntry* entry) {
@@ -1039,7 +1074,7 @@ static bool canUseSessionKey(const SessionKeyEntry* entry) {
10391074}
10401075
10411076const uint8_t * BaseChatMesh::getEncryptionKeyFor (const ContactInfo& contact) {
1042- auto entry = session_keys. findByPrefix (contact.id .pub_key );
1077+ auto entry = findSessionKey (contact.id .pub_key );
10431078 if (canUseSessionKey (entry)) {
10441079 return entry->session_key ;
10451080 }
@@ -1048,7 +1083,7 @@ const uint8_t* BaseChatMesh::getEncryptionKeyFor(const ContactInfo& contact) {
10481083
10491084uint16_t BaseChatMesh::getEncryptionNonceFor (const ContactInfo& contact) {
10501085 uint16_t nonce = 0 ;
1051- auto entry = session_keys. findByPrefix (contact.id .pub_key );
1086+ auto entry = findSessionKey (contact.id .pub_key );
10521087 if (canUseSessionKey (entry)) {
10531088 ++entry->nonce ;
10541089 if (entry->sends_since_last_recv < 255 ) entry->sends_since_last_recv ++;
@@ -1062,7 +1097,7 @@ uint16_t BaseChatMesh::getEncryptionNonceFor(const ContactInfo& contact) {
10621097 int idx = &contact - contacts;
10631098 if (idx >= 0 && idx < num_contacts)
10641099 contacts[idx].flags &= ~CONTACT_FLAG_AEAD ;
1065- session_keys. remove (contact.id .pub_key );
1100+ removeSessionKey (contact.id .pub_key );
10661101 onSessionKeysUpdated ();
10671102 // nonce = 0 (ECB)
10681103 } else if (entry->sends_since_last_recv >= SESSION_KEY_ECB_THRESHOLD ) {
@@ -1091,7 +1126,7 @@ bool BaseChatMesh::shouldInitiateSessionKey(const ContactInfo& contact) {
10911126 // Need a known path to send the request
10921127 if (contact.out_path_len < 0 ) return false ;
10931128
1094- auto entry = session_keys. findByPrefix (contact.id .pub_key );
1129+ auto entry = findSessionKey (contact.id .pub_key );
10951130
10961131 // Don't trigger if negotiation already in progress
10971132 if (entry && entry->state == SESSION_STATE_INIT_SENT ) return false ;
@@ -1127,7 +1162,7 @@ bool BaseChatMesh::shouldInitiateSessionKey(const ContactInfo& contact) {
11271162}
11281163
11291164bool BaseChatMesh::initiateSessionKeyNegotiation (const ContactInfo& contact) {
1130- auto entry = session_keys. allocate (contact.id .pub_key );
1165+ auto entry = allocateSessionKey (contact.id .pub_key );
11311166 if (!entry) return false ;
11321167
11331168 // Don't start a new negotiation if one is already pending
@@ -1163,7 +1198,7 @@ bool BaseChatMesh::handleSessionKeyResponse(ContactInfo& contact, const uint8_t*
11631198 if (len < 5 + PUB_KEY_SIZE ) return false ;
11641199 if (data[4 ] != RESP_TYPE_SESSION_KEY_ACCEPT ) return false ;
11651200
1166- auto entry = session_keys. findByPrefix (contact.id .pub_key );
1201+ auto entry = findSessionKey (contact.id .pub_key );
11671202 if (!entry || entry->state != SESSION_STATE_INIT_SENT ) return false ;
11681203
11691204 const uint8_t * ephemeral_pub_B = &data[5 ];
@@ -1225,7 +1260,7 @@ uint8_t BaseChatMesh::handleIncomingSessionKeyInit(ContactInfo& from, const uint
12251260 memset (ephemeral_secret, 0 , PUB_KEY_SIZE );
12261261
12271262 // 4. Store in pool (dual-decode: new key active, old key still valid)
1228- auto entry = session_keys. allocate (from.id .pub_key );
1263+ auto entry = allocateSessionKey (from.id .pub_key );
12291264 if (!entry) return 0 ;
12301265
12311266 if (entry->state == SESSION_STATE_ACTIVE || entry->state == SESSION_STATE_DUAL_DECODE ) {
@@ -1288,7 +1323,7 @@ void BaseChatMesh::checkSessionKeyTimeouts() {
12881323const uint8_t * BaseChatMesh::getPeerSessionKey (int peer_idx) {
12891324 int i = matching_peer_indexes[peer_idx];
12901325 if (i >= 0 && i < num_contacts) {
1291- auto entry = session_keys. findByPrefix (contacts[i].id .pub_key );
1326+ auto entry = findSessionKey (contacts[i].id .pub_key );
12921327 // Also try decode during INIT_SENT renegotiation (nonce > 1 means prior key exists)
12931328 if (entry && (entry->state == SESSION_STATE_ACTIVE || entry->state == SESSION_STATE_DUAL_DECODE
12941329 || (entry->state == SESSION_STATE_INIT_SENT && entry->nonce > 1 )))
@@ -1300,7 +1335,7 @@ const uint8_t* BaseChatMesh::getPeerSessionKey(int peer_idx) {
13001335const uint8_t * BaseChatMesh::getPeerPrevSessionKey (int peer_idx) {
13011336 int i = matching_peer_indexes[peer_idx];
13021337 if (i >= 0 && i < num_contacts) {
1303- auto entry = session_keys. findByPrefix (contacts[i].id .pub_key );
1338+ auto entry = findSessionKey (contacts[i].id .pub_key );
13041339 if (entry && entry->state == SESSION_STATE_DUAL_DECODE )
13051340 return entry->prev_session_key ;
13061341 }
@@ -1310,7 +1345,7 @@ const uint8_t* BaseChatMesh::getPeerPrevSessionKey(int peer_idx) {
13101345void BaseChatMesh::onSessionKeyDecryptSuccess (int peer_idx) {
13111346 int i = matching_peer_indexes[peer_idx];
13121347 if (i >= 0 && i < num_contacts) {
1313- auto entry = session_keys. findByPrefix (contacts[i].id .pub_key );
1348+ auto entry = findSessionKey (contacts[i].id .pub_key );
13141349 if (entry) {
13151350 bool changed = (entry->state == SESSION_STATE_DUAL_DECODE );
13161351 if (changed) {
0 commit comments