SIGINT on Linux can be delivered to any thread (not just worker).
signal_handler uses g_currentNode->requestStop(); with thread_local,
g_currentNode was nullptr in non-worker threads — Ctrl-C did nothing.
Removing thread_local fixes signal delivery: g_currentNode is set once
before signal handler is registered, pointer read is atomic on x86/x64.
Signal handler via g_shutdown + uasync_wakeup ensures clean uasync poll exit.
updateReadPosition() now saves both lastReadMsgId and lastPosMsgId on every
scroll stop (300ms debounce), not just when read position changes.
connect(stopped, quit) triggers graceful Qt shutdown after uasync cleanup.
Add author_node_id:8 to GUI_EVT_MSG_RECEIVED data format.
Callback now includes uint64_t author parameter.
SoundManager::play('msg_incoming') only when author != myNodeId.
chat_channel.c: new chat_core_create_channel_auto(name) — generates
X25519/Ed25519 keys, random channel_id, Ed25519 signature, creates channel.
chat_core_create_channel_auto_trampoline for gui_bridge_post_uasync_fn.
mainwindow.cpp: onCreateGroupRequested() reduced from 100 to 15 lines.
Removed openssl/evp.h and openssl/rand.h includes from UI code.
- channellist.cpp: selectionChanged guard now checks currentIndex().row()
is within model->rowCount() to avoid re-selecting stale index after
model clear, which caused both channels to stay blue when switching.
- chat_sync.c: cs_refresh_channels now posts CHANNEL_PEERS_ONLINE
for all channels after loading, fixing online count showing only
on the last connected group.
- chat_sync.c: after invite trampoline, check if connection to invite
node is already UP and trigger cs_on_conn_up to send CHANNEL_INFO_REQ.
Fixes invite not completing when connection is reused (Fix 1 side effect).
- mainwindow.cpp: use name.toUtf8().constData() instead of qPrintable()
to preserve UTF-8 encoding on Windows (Russian channel names).
- chat_sync.c: move pending_invite clear from INFO_RESP to WELCOME,
post GUI_EVT_CONNECT_RESULT on invite success in cs_handle_welcome
- etcp_connections.c: NORM_DECRYPT_TRY/CRYPTO_DECRYPT_OK DEBUG_DEBUG->TRACE
- secure_channel.c: ENCRYPT/DECRYPT DEBUG_DEBUG->TRACE
chat_core_ensure_channel_ready() had a duplicate CREATE TABLE peers_*
with OLD schema (no node_RTT, node_type, connected). Since it was called
from 4 places WITHOUT topo_node_sqlite_channel_put() first, the old
schema was created first, and IF NOT EXISTS in the new DDL was a no-op.
Removed the entire old block. Only topo_node_sqlite_channel_put() now
creates peers_* tables with the full schema.
Also removed ORDER BY p.node_RTT from get_public_peers/get_local_peers —
ordering not needed for sequential connect attempts.
Добавлено:
- conn_mgr_connect_from_invite() — подключение к новому узлу по invite-данным
(INIT → INVITE_INFO_REQ/RESP → проверка членства в группе → сохранение в БД)
- topo_group_connect.c — авто-подключение при старте CHAT-группы:
Phase 1: одновременный connect всех connected=1 пиров, таймаут, отмена незавершённых
Phase 2: последовательный перебор публичных адресов до первого успеха
- Счётчик active_conn_count с проверкой nodeinfo-путей (UP/DOWN)
- restart() при phase=DONE и count==0
БД:
- connected INTEGER NOT NULL DEFAULT 0 в peers_<ch> + ALTER TABLE для существующих
- topo_node_sqlite_set_connected / get_connected_peers / get_public_peers
Chatgui:
- Включён topo_group (убран utun_instance_set_topo_group_enabled(0))
- Создание TOPO_GROUP_TYPE_CHAT при channel_ready и при старте
- Invite connect через conn_mgr_connect_from_invite
- Удалён chat_conn_mgr (из сборки) + весь старый auto-connect код
- etcp_router: SVC_ROUTE_HDR (+8B), ETCP_ROUTER_CONN/TRANSIT_QUEUE хеши расширены под group_id
- Все caller'ы (proxy, routing, conn_mgr, nat, chatgui, tests) обновлены
- topo_group: глобальный реестр TOPO_NODE по node_id, ref_count=0 при создании
- BGP-обмен активируется для всех групп (а не только дефолтной)
- Протокол: group_id добавлен в WITHDRAW/TABLE_REQ/TABLE_COMPLETE/ERR_GROUP_MISMATCH
- Per-connection коллбэки заменены на instance-level conn_status
- msg_transport полностью удалён из проекта
57/57 тестов
- topo_node_sqlite_member_put: remove joined_at column from INSERT (not in server schema, duplicates join_ts)
- chat_core: rename peers table column name→userinfo to match server schema + add ALTER TABLE migration
- chat_sync: remove 3 redundant INSERT INTO nodes that wrote raw JSON userinfo into nodes.name, overwriting correct plain name from topo_node_sqlite_node_update_verified
- config_updater.{h,cpp}: single implementation — config_update_in_lines
for batch updates + config_update_option for file-level. REPLACE/APPEND
modes, section auto-creation
- node_config.cpp: generateIdentity() now derives node_id via
SHA256(pubkey) (like sc_derive_node_id_from_pubkey) instead of random.
save() uses config_update_in_lines for line-level update when file
exists, saveFull() only for new file and addServer/addClient.
Removed duplicate findSectionBounds/updateOptionInSection/configUpdateOption
- test_config_updater.cpp: Q_ASSERT replaced with VERIFY macro — Q_ASSERT
in Release mode does not evaluate the expression, causing false positives.
Test now works in both Debug and Release
- Remove is_dm column from channels table (dm detection by member count)
- Rename member name column to userinfo — extensible json_flat format
- Add json_flat.c/h library for compact JSON serialization
- Fix join_sig message format: use ch_x25519+ch_ed25519 instead of channel_id string
- Remove name from join_msg body (now in userinfo via json_flat)
- Update chat_core, chat_sync, member_sync, db_manager to new format
- Add topo_strategy.txt — архитектурные заметки по топологии
- chat_conn_mgr.c/h: new connection manager (1 per instance, global in g_cc)
- cm_rotation() — single rotation method: random addr pick, INIT, retry
- 10 parallel slots per node (CM_MAX_PARALLEL), evict worst RTT on excess
- DEAD slot reuse before close — close only when replacement ready
- per-connection DOWN callback via etcp_conn_add_down_cbk
- background ping cycle: random node→random addr RTT measurement
- chat_core.c: added g_cc.conn_mgr field, init/destroy/getter
- chat_sync.c: migrate to new API, add_node() at WELCOME/JOIN/PEER_UPSERT
- Removed old ca_state/ca_ctx/ca_* single-flight code
- Sound files loaded from filesystem (sound_dir in INI config, absolute or relative)
- Per-event enable/disable + volume stored in DB (ui_state table)
- SoundManager: setEvent(), effective volume = master * per-event
- SoundSettingsPage UI: 4 event rows (checkbox + slider)
- chat_core_save_ui_state() for async DB writes via uasync thread
- DbManager::getUiStateInt() for reading settings from GUI thread
- Default sounds copied to sounds/ dir next to DB, config entries written
INSERT OR REPLACE deletes the old row and inserts a new one,
resetting unspecified columns (online, update_ts) to DEFAULT 0.
This was wiping online=1 set by cs_on_peer_status_changed/member_sync_set_online.
Replaced 5 INSERT OR REPLACE INTO nodes with INSERT INTO ... ON CONFLICT
DO UPDATE SET — only updates the specified columns, preserving online status.
Files:
- chat_core.c:638 (invite flow)
- chat_sync.c:1045 (CHANNEL_INFO_RESP), 1193 (JOIN), 1449 (PEER_UPSERT)
- topo_node_sqlite.c:121 (BGP node_put)
- cs_handle_channel_join: call cs_post_channel_online immediately after adding joiner
- member_sync_start with _on_invite_sync_done callback: on MT_OK push online=1 via merkle_sync_push_update
- DEBUG per-peer online status in cs_post_channel_online
- cs_on_conn_up: call cs_on_peer_status_changed(peer,1) before return
in invite path (was skipped, leaving inviter at online=0 in DB)
- cs_handle_welcome: remove unconditional set_online(0) — online
status is managed by conn_up/conn_down, not by WELCOME
- cs_handle_welcome: add cs_post_channel_online() after WELCOME
to send PEERS_ONLINE to GUI when channel is fully loaded
- cs_post_channel_online: add DEBUG log for PEERS_ONLINE event
- db_sync peer_check: suppress spam log when no peers connected
- chat_core_sync_my_addresses: DELETE + INSERT с подсчётом строк, CHECK verify
- chat_core_connect_from_invite: DELETE + INSERT с IP:port
- member_sync_put: DELETE + INSERT с IP:port, warn если addrs_data=NULL
- cs_handle_channel_info_resp: адрес инвайтера из ETCP link
- cs_handle_channel_join: адреса джойнера
- cs_handle_peer_upsert: адреса пира
- cs_handle_welcome: адреса из WELCOME
Все логи с префиксом [ADDR_SYNC] в DEBUG_CATEGORY_DEBUG
- mainwindow.cpp: swap setupMessaging() before setupNode() so g_receiver is created first
- gui_bridge_impl.cpp: add DEBUG_WARN when gui_bridge_post drops event due to NULL g_receiver
- utun_node.cpp: open topo_sqlite_db with FULLMUTEX + all PRAGMAs (WAL, FK, NORMAL sync, checkpoint)
- db_sync.c: reuse inst->topo_sqlite_db if available (shared_db flag)
- chat_core.c: reuse inst->topo_sqlite_db if available (shared_db flag)
- db_sync_destroy/chat_core_destroy: skip close on shared handle
- Only topo_groups_destroy closes the shared handle
- Fallback: each module opens own connection if topo_sqlite_db is NULL
ready_cbks/etcp_conn_set_ready_cbk/add_ready/remove_ready → init_cbks/..._init_cbk
ca_ready_cb → ca_init_cb
cm_direct_ready_cb → cm_direct_init_cb
cm_reverse_ready_cb → cm_reverse_init_cb
ntp_node_on_conn_ready → ntp_node_on_conn_init
connect_ready_cb → connect_init_cb
init accurately describes when conn->initialized=1, before keepalives establish UP.
Also fire etcp_fire_conn_status INIT at this point instead of create time.