Skip to main content

SimpleGo Protocol Analysis

SimpleX Protocol Analysis - Part 33: Session 36

Contact Lifecycle: Delete, Recreate, Zero Compromise

Document Version: v1 Date: 2026-02-25 Session 36 Status: COMPLETED -- Contact lifecycle fully functional Previous: Part 32 - Session 35 (Multi-Contact Victory) Project: SimpleGo - ESP32 Native SimpleX Client Path: C:\Espressif\projects\simplex_client Build: idf.py build flash monitor -p COM6 License: AGPL-3.0


SESSION 36 SUMMARY

Complete Contact Lifecycle: Create -> Chat -> Delete -> Recreate
No erase-flash required!

4 Sub-Sessions (36a, 36b, 36c, 36d)
12 Git Commits
7 Bugs Fixed (#51-#57)
10 New Lessons (#193-#202)
10 Files Changed

MILESTONE 12: Contact Lifecycle

Date: February 25, 2026

Phase 0: Preparation and Baudrate Fix

UART baudrate raised from 115200 to 921600 (PowerShell one-liner). With 5000+ log lines per session, this reduces UART overhead from ~39s to ~5s (8x speedup). All 4 sdkconfig entries changed simultaneously.


Phase 1: Task 35g -- NTP Timestamps + Bug E (ConnInfo displayName)

NTP Time Sync

SNTP initialization added to main.c after WiFi connect. Max 10s sync timeout, non-blocking. Chat bubbles now show real timestamps (Mon | 14:35) instead of "--:--".

Bug E Fix: Contact Name from ConnInfo JSON

Problem: handle_conninfo() in smp_agent.c decompressed the JSON from the phone, logged it, but never extracted the displayName field.

JSON Format:

{"v":"1-16","event":"x.info","params":{"profile":{"displayName":"Name","fullName":""}}}

Fix: strstr() search for "displayName":"" in decompressed JSON, extract name into contact_t, persist to NVS, update UI header. Fallback path for uncompressed JSON also covered.

Commit: fix(agent): extract displayName from ConnInfo JSON


Phase 2: Hasi's Autonomous Rebuild

Hasi built significantly more than tasked in the first phase:

  • Flow rebuild: No auto-QR, no auto-contact on fresh start
  • Connection acceleration: Delays reduced from 6.5s to 2s
  • Contact list completely rewritten (665 lines)
  • Long-press menu with Delete and Info
  • Logging cleanup (heartbeat 5min instead of 30s, hex dumps removed)

Mausi note: Hasi built features autonomously instead of executing tasks. For Session 37, roles must be enforced more strictly.

Commits:

refactor(flow): remove auto-QR and auto-contact on fresh start
perf(tasks): reduce handshake delays from 6.5s to 2s
feat(ui): redesign contacts screen matching chat layout
feat(ui): add long-press context menu with delete and info
chore(log): reduce verbose debug logging in network and app tasks

Phase 3: Task 36a -- NVS Key Cleanup (Bug #51)

Problem

When deleting a contact, orphaned NVS keys remained in flash storage.

Initial Fix (Hasi)

Hasi implemented only rat_XX deletion.

Mausi Correction

Hasi had only checked the current NVS contents. Mausi read the source code (smp_peer.c line 1131: smp_storage_save_blob_sync("peer_%02x", ...)) and found all four key families:

rat_XX  -- Ratchet state (Root Key, Chain Keys, Header Keys)
peer_XX -- Peer state (Queue IDs, DH Keys, Server Host)
hand_XX -- Handshake state (X3DH Keys)
rq_XX -- Reply Queue state (Auth Private Key)

Lesson #193: Always search the source code for where keys are written, not the current NVS contents. 100x Reading Principle.

Commit: fix(storage): clean all orphaned NVS keys on contact delete


Phase 4: Task 36b -- NVS Cleanup Completed + Format Fix (Bugs #52, #53)

4-Key Cleanup Loop

Extended to both delete paths: remove_contact() and on_popup_delete().

Bug #52: Format Mismatch

cnt_%02x uses hex format, rat_%02u uses decimal format. Identical for indices 0-9, but diverges from 10 onwards (cnt_0a vs rat_10).

Fix: All NVS key formatting unified to %02x (hex).

Bug #53: Parser Double-Underscore

SimpleX changed separator from single underscore to double underscore. Parser must find the last underscore to correctly split agent message types.

Fix: fix(parser): handle double-underscore separator in agent message type detection

Additional Fixes

  • 42d bitmap reset: smp_clear_42d(idx) on contact delete
  • LIST_H macro collision with FreeRTOS renamed to CLIST_H

Commit: fix(tasks): clear 42d bitmap on contact delete for slot reuse


Security Discussion: NVS Keys and Encryption

Prince asked: "Is this a security feature?"

Mausi's Analysis: Yes, critical.

The four NVS key families contain:

KeyContentsCompromise Impact
rat_XXRoot Key, Chain Keys, Header KeysDecrypt past + future messages
peer_XXQueue IDs, DH Keys, Server HostIdentity theft
hand_XXX3DH Handshake KeysFoundation of encryption
rq_XXReply Queue Auth Private KeySend messages as the user

Critical Discovery

NVS is currently NOT encrypted. nvs_flash_init() instead of nvs_flash_secure_init(). All crypto keys stored in plaintext in flash.

Lesson #194: NVS keys are currently NOT encrypted (nvs_flash_init not nvs_flash_secure_init). All crypto keys in plaintext.

Lesson #195: What is not there cannot be stolen. Deletion > trusting encryption. Defense in Depth.

TODO (Production): NVS Encryption (nvs_flash_secure_init + eFuse keys) together with post-quantum crypto (CRYSTALS-Kyber) for Kickstarter phase.


Phase 5: Task 36c -- KEY-before-HELLO Race Condition (Bug #54)

The Smoking Gun

(245267) APP: 42d -- SEND_KEY queued (slot=0)
(245767) peer_send_hello starts
(246797) HELLO sent via Peer (sock 56) <- HELLO FIRST!
(247197) NET Task executes KEY now (sock 54) <- KEY AFTER!

Root Cause

App Task queued KEY to Net Task via Ring Buffer, waited only 500ms blind, then fired HELLO immediately over a separate Peer connection. Two different sockets, no synchronization.

Why the Phone Stays on "connecting"

  1. Phone receives HELLO
  2. Phone wants to respond on Reply Queue
  3. KEY has not arrived yet -> Server does not have Phone's auth key
  4. Server rejects with ERR AUTH
  5. Phone stays on "connecting"

Fix: FreeRTOS TaskNotification

// s_app_task_handle + NOTIFY_KEY_DONE define
// Net Task sends xTaskNotify() after KEY OK/Fail/Timeout (all 3 paths!)
// App Task waits with xTaskNotifyWait(5000ms) instead of vTaskDelay(500ms)
// No deadlock risk because all paths notify

Key Design Decision: xTaskNotify must fire on ALL paths (success, failure, timeout) -- otherwise the waiting task hangs forever.

Evgeny quote confirming: "concurrency is hard."

Lesson #196: TaskNotification is more lightweight than Semaphore for 1:1 task synchronization.

Lesson #197: xTaskNotify must send on ALL paths (success, error, timeout) or the waiting task deadlocks.

Commit: fix(tasks): synchronize KEY-before-HELLO with FreeRTOS TaskNotification


Phase 6: Task 36d -- UI Cleanup on Delete (Bugs #55, #56, #57)

Three problems solved simultaneously:

Bug #55: Chat Bubbles Survive Delete

New function ui_chat_clear_contact(int idx) deletes all LVGL bubble objects with matching contact tag.

Lesson #198: LVGL bubble objects must be explicitly deleted on contact delete -- they survive the contact.

Bug #56: QR Code Flashes After Delete

New function ui_connect_reset() hides QR, shows placeholder, sets status to "Generating...".

Bug #57: Stale QR on "+ New"

ui_connect_reset() also called in on_bar_new() BEFORE smp_request_add_contact().

Lesson #199: QR code widget caches last content -- must be explicitly reset on Delete AND before New.

Commit: fix(ui): clear chat bubbles and reset QR cache on contact delete


Complete Bug List (Session 36)

BugDescriptionRoot CauseFixPhase
EContact name shows placeholderdisplayName not extracted from ConnInfo JSONstrstr() + NVS persist35g
#51Orphaned NVS keys on deleteOnly rat_XX deleted, 3 families missed4-key cleanup loop36a
#52NVS key format mismatch%02u (decimal) vs %02x (hex) diverges at idx 10+Unified to %02x36b
#53Parser fails on INVITATIONSimpleX changed _ to __ separatorFind last underscore36b
#54KEY-HELLO race condition500ms blind delay, 2 sockets, no syncFreeRTOS TaskNotification36c
#55Chat bubbles survive deleteLVGL objects not clearedui_chat_clear_contact()36d
#56QR code flashes after deleteWidget caches last contentui_connect_reset()36d
#57Stale QR on "+ New"No reset before new contact requestui_connect_reset() in on_bar_new()36d

Additional Fixes (Non-Bug)

FixDescription
42d bitmap resetsmp_clear_42d(idx) on contact delete for slot reuse
LIST_H renameFreeRTOS list.h defines LIST_H as include guard -- renamed to CLIST_H
UART baudrate115200 -> 921600 (8x speedup for 5000+ line logs)
Heartbeat interval30s -> 5min (reduced log noise)
Hex dump removalDebug hex dumps stripped from production paths
Connection delays6.5s -> 2s handshake delay reduction

All Lessons Learned (Session 36)

#LessonContext
193Always search source code for where NVS keys are written, not current NVS contents. 100x Reading Principle.36a: Mausi found 4 key families vs Hasi's 1
194NVS is currently NOT encrypted (nvs_flash_init not nvs_flash_secure_init). All crypto keys in plaintext in flash.Security audit
195What is not there cannot be stolen. Deletion > trusting encryption. Defense in Depth.Security principle
196FreeRTOS TaskNotification is more lightweight than Semaphore for 1:1 task synchronizationKEY-HELLO sync
197xTaskNotify must send on ALL paths (success, error, timeout) or the waiting task deadlocks permanentlyDeadlock prevention
198LVGL bubble objects must be explicitly deleted on contact delete -- they survive the contactui_chat_clear_contact
199QR code widget caches last content -- must be explicitly reset on Delete AND before Newui_connect_reset
200FreeRTOS list.h defines LIST_H as include guard -- own macros must not collideCLIST_H rename
201SimpleX changed from single underscore to double underscore separator -- parser must find last underscoreDouble-underscore bug
202UART 115200 at 5000+ log lines = ~39s overhead. 921200 = ~5s. 8x faster, one sdkconfig change.Baudrate fix

Files Changed -- Session 36 Overview

FilePathChanges
main.cmain/NTP init, flow rebuild (no auto-QR/contact)
smp_agent.cmain/protocol/Bug E: displayName from ConnInfo JSON
smp_parser.cmain/protocol/Double-underscore separator fix
smp_tasks.cmain/core/42d bitmap reset, KEY-HELLO TaskNotification, delay reduction, logging
smp_contacts.cmain/state/4-key NVS cleanup in remove_contact()
ui_contacts.cmain/ui/screens/Complete redesign, long-press menu, 4-key NVS cleanup, chat clear, QR reset
ui_chat.cmain/ui/screens/ui_chat_clear_contact(), dynamic header
ui_chat.hmain/ui/screens/Declaration ui_chat_clear_contact()
ui_connect.cmain/ui/screens/ui_connect_reset()
ui_connect.hmain/ui/screens/Declaration ui_connect_reset()

Git Commits (Session 36)

feat(ntp): add SNTP time sync after WiFi connect
fix(agent): extract displayName from ConnInfo JSON
refactor(flow): remove auto-QR and auto-contact on fresh start
perf(tasks): reduce handshake delays from 6.5s to 2s
feat(ui): redesign contacts screen matching chat layout
feat(ui): add long-press context menu with delete and info
chore(log): reduce verbose debug logging in network and app tasks
fix(parser): handle double-underscore separator in agent message type detection
fix(storage): clean all orphaned NVS keys on contact delete
fix(tasks): clear 42d bitmap on contact delete for slot reuse
fix(tasks): synchronize KEY-before-HELLO with FreeRTOS TaskNotification
fix(ui): clear chat bubbles and reset QR cache on contact delete

Open Bugs (after Session 36)

BugDescriptionPriorityTarget
-Umlauts not rendered (LVGL font)P2Session 37
-Server DEL missing on UI deleteP3Session 37+
-First message invisible on fresh contactP4Session 37+
-SPI display glitchesP4Later

Next Session: 37

Main Goals

  1. Encrypted chat history on SD card (architecture + implementation)
  2. LVGL font rebuild with umlauts (Latin-1 Supplement)
  3. Contact list enhancements (last message, timestamp, counter, unread badge)

Dependencies

  • Contact list enhancements depend on SD history (needs data)
  • SD infrastructure already fully prepared
  • Font rebuild is independent and can run in parallel

Architecture Insight: Contact Lifecycle Pattern

SESSION 36 PATTERN: Every lifecycle operation must clean ALL state layers

Create Contact:
-> NVS: rat_XX, peer_XX, hand_XX, rq_XX (4 key families)
-> PSRAM: ratchet slot, handshake slot, contact struct, reply queue
-> UI: contact list entry, chat bubbles, QR code
-> Network: SMP queues on server

Delete Contact:
-> NVS: erase all 4 key families (%02x format!)
-> PSRAM: zero slots, clear contact struct, clear reply queue
-> Bitmap: smp_clear_42d(idx)
-> UI: clear bubbles, reset QR, remove list entry
-> Network: TODO -- server DEL command (Session 37+)

Recreate Contact:
-> All slots clean, no orphaned state
-> New handshake proceeds as if first contact ever
-> No erase-flash required!

Session 36 Statistics

MetricValue
Sub-sessions4 (36a, 36b, 36c, 36d)
Git commits12
Bugs fixed7 (#51-#57) + Bug E
New lessons10 (#193-#202)
Files changed10
Lines rewritten665+ (contact list alone)
UART speedup8x (115200 -> 921600)
Handshake speedup3.25x (6.5s -> 2s)

Part 33 - Session 36 Contact Lifecycle SimpleGo Protocol Analysis Date: February 25, 2026 Bugs: 57 total (all FIXED) + Bug E Lessons: 202 total Milestone 12: Contact Lifecycle