@@ -971,7 +971,7 @@ bool BaseChatMesh::removeContact(ContactInfo& contact) {
971971 }
972972 if (idx >= num_contacts) return false ; // not found
973973
974- session_keys. remove (contact.id .pub_key ); // also remove session key if any
974+ removeSessionKey (contact.id .pub_key ); // also remove session key if any
975975
976976 // remove from contacts array and parallel nonce tracking
977977 num_contacts--;
@@ -1086,6 +1086,41 @@ void BaseChatMesh::loop() {
10861086 }
10871087}
10881088
1089+ // --- Session key flash-backed wrappers ---
1090+
1091+ SessionKeyEntry* BaseChatMesh::findSessionKey (const uint8_t * pub_key) {
1092+ auto entry = session_keys.findByPrefix (pub_key);
1093+ if (entry) return entry;
1094+
1095+ // Cache miss — try flash
1096+ uint8_t flags; uint16_t nonce;
1097+ uint8_t sk[SESSION_KEY_SIZE ], psk[SESSION_KEY_SIZE ];
1098+ if (!loadSessionKeyRecordFromFlash (pub_key, &flags, &nonce, sk, psk)) return nullptr ;
1099+
1100+ // Save dirty evictee before overwriting
1101+ if (session_keys.isFull () && session_keys_dirty) {
1102+ mergeAndSaveSessionKeys ();
1103+ }
1104+ session_keys.applyLoaded (pub_key, flags, nonce, sk, psk);
1105+ return session_keys.findByPrefix (pub_key);
1106+ }
1107+
1108+ SessionKeyEntry* BaseChatMesh::allocateSessionKey (const uint8_t * pub_key) {
1109+ // Check RAM and flash first
1110+ auto entry = findSessionKey (pub_key);
1111+ if (entry) return entry;
1112+
1113+ // Not found anywhere — save dirty evictee before allocating
1114+ if (session_keys.isFull () && session_keys_dirty) {
1115+ mergeAndSaveSessionKeys ();
1116+ }
1117+ return session_keys.allocate (pub_key);
1118+ }
1119+
1120+ void BaseChatMesh::removeSessionKey (const uint8_t * pub_key) {
1121+ session_keys.remove (pub_key);
1122+ }
1123+
10891124// --- Session key support (Phase 2 — initiator) ---
10901125
10911126static bool canUseSessionKey (const SessionKeyEntry* entry) {
@@ -1101,7 +1136,7 @@ static bool canUseSessionKey(const SessionKeyEntry* entry) {
11011136}
11021137
11031138const uint8_t * BaseChatMesh::getEncryptionKeyFor (const ContactInfo& contact) {
1104- auto entry = session_keys. findByPrefix (contact.id .pub_key );
1139+ auto entry = findSessionKey (contact.id .pub_key );
11051140 if (canUseSessionKey (entry)) {
11061141 return entry->session_key ;
11071142 }
@@ -1110,7 +1145,7 @@ const uint8_t* BaseChatMesh::getEncryptionKeyFor(const ContactInfo& contact) {
11101145
11111146uint16_t BaseChatMesh::getEncryptionNonceFor (const ContactInfo& contact) {
11121147 uint16_t nonce = 0 ;
1113- auto entry = session_keys. findByPrefix (contact.id .pub_key );
1148+ auto entry = findSessionKey (contact.id .pub_key );
11141149 if (canUseSessionKey (entry)) {
11151150 ++entry->nonce ;
11161151 if (entry->sends_since_last_recv < 255 ) entry->sends_since_last_recv ++;
@@ -1124,7 +1159,7 @@ uint16_t BaseChatMesh::getEncryptionNonceFor(const ContactInfo& contact) {
11241159 int idx = &contact - contacts;
11251160 if (idx >= 0 && idx < num_contacts)
11261161 contacts[idx].flags &= ~CONTACT_FLAG_AEAD ;
1127- session_keys. remove (contact.id .pub_key );
1162+ removeSessionKey (contact.id .pub_key );
11281163 onSessionKeysUpdated ();
11291164 // nonce = 0 (ECB)
11301165 } else if (entry->sends_since_last_recv >= SESSION_KEY_ECB_THRESHOLD ) {
@@ -1153,7 +1188,7 @@ bool BaseChatMesh::shouldInitiateSessionKey(const ContactInfo& contact) {
11531188 // Need a known path to send the request
11541189 if (contact.out_path_len < 0 ) return false ;
11551190
1156- auto entry = session_keys. findByPrefix (contact.id .pub_key );
1191+ auto entry = findSessionKey (contact.id .pub_key );
11571192
11581193 // Don't trigger if negotiation already in progress
11591194 if (entry && entry->state == SESSION_STATE_INIT_SENT ) return false ;
@@ -1189,7 +1224,7 @@ bool BaseChatMesh::shouldInitiateSessionKey(const ContactInfo& contact) {
11891224}
11901225
11911226bool BaseChatMesh::initiateSessionKeyNegotiation (const ContactInfo& contact) {
1192- auto entry = session_keys. allocate (contact.id .pub_key );
1227+ auto entry = allocateSessionKey (contact.id .pub_key );
11931228 if (!entry) return false ;
11941229
11951230 // Don't start a new negotiation if one is already pending
@@ -1225,7 +1260,7 @@ bool BaseChatMesh::handleSessionKeyResponse(ContactInfo& contact, const uint8_t*
12251260 if (len < 5 + PUB_KEY_SIZE ) return false ;
12261261 if (data[4 ] != RESP_TYPE_SESSION_KEY_ACCEPT ) return false ;
12271262
1228- auto entry = session_keys. findByPrefix (contact.id .pub_key );
1263+ auto entry = findSessionKey (contact.id .pub_key );
12291264 if (!entry || entry->state != SESSION_STATE_INIT_SENT ) return false ;
12301265
12311266 const uint8_t * ephemeral_pub_B = &data[5 ];
@@ -1287,7 +1322,7 @@ uint8_t BaseChatMesh::handleIncomingSessionKeyInit(ContactInfo& from, const uint
12871322 memset (ephemeral_secret, 0 , PUB_KEY_SIZE );
12881323
12891324 // 4. Store in pool (dual-decode: new key active, old key still valid)
1290- auto entry = session_keys. allocate (from.id .pub_key );
1325+ auto entry = allocateSessionKey (from.id .pub_key );
12911326 if (!entry) return 0 ;
12921327
12931328 if (entry->state == SESSION_STATE_ACTIVE || entry->state == SESSION_STATE_DUAL_DECODE ) {
@@ -1350,7 +1385,7 @@ void BaseChatMesh::checkSessionKeyTimeouts() {
13501385const uint8_t * BaseChatMesh::getPeerSessionKey (int peer_idx) {
13511386 int i = matching_peer_indexes[peer_idx];
13521387 if (i >= 0 && i < num_contacts) {
1353- auto entry = session_keys. findByPrefix (contacts[i].id .pub_key );
1388+ auto entry = findSessionKey (contacts[i].id .pub_key );
13541389 // Also try decode during INIT_SENT renegotiation (nonce > 1 means prior key exists)
13551390 if (entry && (entry->state == SESSION_STATE_ACTIVE || entry->state == SESSION_STATE_DUAL_DECODE
13561391 || (entry->state == SESSION_STATE_INIT_SENT && entry->nonce > 1 )))
@@ -1362,7 +1397,7 @@ const uint8_t* BaseChatMesh::getPeerSessionKey(int peer_idx) {
13621397const uint8_t * BaseChatMesh::getPeerPrevSessionKey (int peer_idx) {
13631398 int i = matching_peer_indexes[peer_idx];
13641399 if (i >= 0 && i < num_contacts) {
1365- auto entry = session_keys. findByPrefix (contacts[i].id .pub_key );
1400+ auto entry = findSessionKey (contacts[i].id .pub_key );
13661401 if (entry && entry->state == SESSION_STATE_DUAL_DECODE )
13671402 return entry->prev_session_key ;
13681403 }
@@ -1372,7 +1407,7 @@ const uint8_t* BaseChatMesh::getPeerPrevSessionKey(int peer_idx) {
13721407void BaseChatMesh::onSessionKeyDecryptSuccess (int peer_idx) {
13731408 int i = matching_peer_indexes[peer_idx];
13741409 if (i >= 0 && i < num_contacts) {
1375- auto entry = session_keys. findByPrefix (contacts[i].id .pub_key );
1410+ auto entry = findSessionKey (contacts[i].id .pub_key );
13761411 if (entry) {
13771412 bool changed = (entry->state == SESSION_STATE_DUAL_DECODE );
13781413 if (changed) {
0 commit comments