Browse Source

encrypt overhead fix (33→36, noencrypt_len), mtu default 1280, connmonitor window, total_decrypted for TCP, check.sh

topo_upd
evgeny 2 months ago
parent
commit
ca176e1f24
  1. 1
      .gitignore
  2. 4
      AGENTS.md
  3. 21
      check.sh
  4. 6
      src/chat/chat_core.h
  5. 4
      src/chat/chat_event.c
  6. 2
      src/chat/chat_event.h
  7. 172
      src/chat/chat_status.c
  8. 4
      src/config_parser.c
  9. 2
      src/routing_layer/topo_group.c
  10. 2
      src/transport_layer/auto_socket.c
  11. 7
      src/transport_layer/etcp.c
  12. 18
      src/transport_layer/etcp_connections.c
  13. 7
      src/transport_layer/etcp_connections.h
  14. 20
      src/transport_layer/node_conn_direct.c
  15. 7
      src/transport_layer/pkt_normalizer.c
  16. 2
      src/transport_layer/stcp_link.c
  17. 6
      tools/chatgui-android/AGENTS.md
  18. 19
      tools/chatgui-android/build.sh
  19. 1
      tools/chatgui/CMakeLists.txt
  20. 4
      tools/chatgui/src/animtimer.cpp
  21. 2
      tools/chatgui/src/audiodevicesettingspage.cpp
  22. 8
      tools/chatgui/src/audiorecorder.cpp
  23. 13
      tools/chatgui/src/channellist.cpp
  24. 1
      tools/chatgui/src/channellist.h
  25. 189
      tools/chatgui/src/connmonitorwindow.cpp
  26. 34
      tools/chatgui/src/connmonitorwindow.h
  27. 6
      tools/chatgui/src/main.cpp
  28. 27
      tools/chatgui/src/mainwindow.cpp
  29. 3
      tools/chatgui/src/mainwindow.h
  30. 2
      tools/chatgui/src/media_blocks.cpp
  31. 19
      tools/chatgui/src/messagedelegate.cpp
  32. 2
      tools/chatgui/src/messagedelegate.h
  33. 6
      tools/chatgui/src/messagelist.cpp
  34. 40
      tools/chatgui/src/settingsdialog.cpp
  35. 3
      tools/chatgui/src/settingsdialog.h
  36. 6
      tools/chatgui/src/sound_manager.cpp
  37. 10
      tools/chatgui/src/voiceplayback.cpp
  38. 8
      tools/chatgui/src/voiceplayback.h
  39. 10
      tools/chatgui/transport/gui_bridge.h
  40. 18
      tools/chatgui/transport/gui_bridge_impl.cpp
  41. 18
      tools/chatgui/transport/utun_node.cpp

1
.gitignore vendored

@ -120,3 +120,4 @@ build_win.log
src/lwip_orig/
lib/timeout/
utun.pid
.check_time

4
AGENTS.md

@ -96,7 +96,7 @@ ASAN_OPTIONS=detect_leaks=0:halt_on_error=0 ./src/utun -f -p /tmp/utun.pid -l /t
### Run All Tests
```bash
make check # Run all tests via automake, логи в tests/logs/ - запускай тесты с ПОЛНЫМ ВЫОДОМ, не урезая его ( grep итд) - иначе будешь 10 раз повторять зпауск что жрёт много времени
./check.sh # сборка + тесты (только результаты, при ошибке — хвост)
```
```powershell
powershell check.bat # Windows, запускает каждый .exe из tests/
@ -695,7 +695,7 @@ C-ядро: `libutun_lite` (выборочная компиляция нужны
1. Add new source file to `src/Makefile.am` under `utun_SOURCES`
2. Add test file to `tests/Makefile.am` under `check_PROGRAMS`
3. Use existing patterns from similar modules
4. Run `make check` after changes
5. Run `./check.sh` after changes
5. Commit with descriptive message in appropriate language
## chatgui: отправка сообщений через xdotool

21
check.sh

@ -0,0 +1,21 @@
#!/bin/bash
set -eo pipefail
cd "$(dirname "$0")"
LAST=0; [ -f .check_time ] && read -r LAST < .check_time
TMO=$(( LAST + 30 ))
[ $TMO -lt 90 ] && TMO=90
START=$(date +%s)
TMP=$(mktemp)
timeout $TMO make check -j4 > "$TMP" 2>&1 || {
tail -40 "$TMP"; rm -f "$TMP"
[ $(date +%s) -ge $((START + TMO)) ] && echo "TIMEOUT (${TMO}s)" >&2
exit 1
}
grep -E '\[PASS|\[FAIL|\[SKIP|Test Results|Failed tests|Log directory|^====' "$TMP"
rm -f "$TMP"
ELAPSED=$(( $(date +%s) - START ))
echo "Time: ${ELAPSED}s"
echo "$ELAPSED" > .check_time

6
src/chat/chat_core.h

@ -103,6 +103,12 @@ int chat_core_update_local_attrs(const char* ch_id, uint64_t ts,
/* Сбор статуса (NTP + connections) и отправка в GUI */
void chat_core_collect_status_trampoline(void* arg);
/* Сбор списка ETCP-соединений и отправка в GUI (binary packet) */
void chat_core_collect_conn_list_trampoline(void* arg);
/* Сбор полных метрик одного соединения (arg = u_malloc uint64_t peer_node_id) */
void chat_core_collect_conn_metrics_trampoline(void* arg);
/* ── Headless CLI API ── */
/** Вариант для сборки без UTUN_INSTANCE (использует sqlite3* и node_id) */

4
src/chat/chat_event.c

@ -33,8 +33,10 @@ void chat_event_post(int type, const uint8_t* data, int len) {
[12]="SERVICE_STARTED", [13]="SERVICE_STOPPED",
[14]="ATTACHMENT_DOWNLOADED", [15]="DOWNLOAD_PROGRESS",
[16]="NODEINFO_UPDATED", [17]="LINK_STATUS_CHANGED",
[18]="INVITE_RECEIVED", [19]="LOCAL_SOCKETS",
[20]="CONN_LIST", [21]="CONN_METRICS",
};
const char* n = (type >= 1 && type <= 17) ? names[type] : "?";
const char* n = (type >= 1 && type <= 21) ? names[type] : "?";
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "chat_event: %s(%d) data=%d bytes", n, type, len);
if (data && len > 0) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DEBUG, " ", data, len);
}

2
src/chat/chat_event.h

@ -38,6 +38,8 @@ extern "C" {
#define CHAT_EVT_LINK_STATUS_CHANGED 17 /* [peer_id:8][link_id:1][state:1][status:1][links_up:1][initialized:1] */
#define CHAT_EVT_INVITE_RECEIVED 18 /* [ch_id_len:1][ch_id][name_len:1][name][inviter_node_id:8][inviter_name_len:1][inviter_name] */
#define CHAT_EVT_LOCAL_SOCKETS 19 /* JSON: [{"si":id,"nm":"...","ad":"...","po":port,"pt":"TCP|UDP","st":0|1|2,"lk":n},...] */
#define CHAT_EVT_CONN_LIST 20 /* [count:2][entry:46B]* — список ETCP-соединений */
#define CHAT_EVT_CONN_METRICS 21 /* текст — полный дамп метрик выбранного соединения */
typedef void (*chat_event_handler_fn)(int type, const uint8_t* data, int len);

172
src/chat/chat_status.c

@ -10,9 +10,11 @@
#include "../utun_instance.h"
#include "../ntp_time.h"
#include "../../lib/ll_queue.h"
#include "../../lib/mem.h"
#include "../../lib/platform_compat.h"
#include "../transport_layer/etcp.h"
#include "../transport_layer/etcp_connections.h"
#include "../transport_layer/pkt_normalizer.h"
static const char* nat_type_str(uint8_t t) {
switch (t) { case 0: return "UNKNOWN"; case 1: return "EIM"; case 2: return "STRICT"; case 3: return "DIRECT"; default: return "?"; }
@ -112,7 +114,177 @@ static void chat_core_collect_status(void) {
chat_event_post(CHAT_EVT_STATUS_REFRESH, (const uint8_t*)buf, off);
}
/* ── Бинарный список соединений: [count:2][entry:46B]* ── */
/* entry: peer_node_id:8 flags:1 link_count:1 rtt:2 inflight_kb:2 name:32 */
#define CONN_LIST_ENTRY_SIZE 46
static void collect_conn_list(void) {
if (!g_cc.initialized || !g_cc.inst || !g_cc.inst->connections) return;
uint16_t count = 0;
struct ll_entry* entry = g_cc.inst->connections->head;
while (entry) { count++; entry = entry->next; }
if (count > 250) count = 250;
size_t buf_sz = 2 + (size_t)count * CONN_LIST_ENTRY_SIZE;
uint8_t* buf = (uint8_t*)u_malloc(buf_sz);
if (!buf) return;
uint16_t* hdr = (uint16_t*)buf;
*hdr = count;
uint8_t* p = buf + 2;
entry = g_cc.inst->connections->head;
for (uint16_t i = 0; i < count && entry; i++, entry = entry->next) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data;
if (!ce || !ce->conn) { memset(p, 0, CONN_LIST_ENTRY_SIZE); p += CONN_LIST_ENTRY_SIZE; continue; }
struct ETCP_CONN* conn = ce->conn;
memcpy(p, &ce->peer_node_id, 8); p += 8;
uint8_t flags = 0;
if (conn->links_up) flags |= 1;
if (conn->initialized) flags |= 2;
*p++ = flags;
uint8_t lc = 0;
struct ETCP_LINK* tl = conn->links; while (tl) { lc++; tl = tl->next; }
*p++ = lc;
uint16_t rtt = 0;
if (conn->links) rtt = conn->links->rtt_last;
memcpy(p, &rtt, 2); p += 2;
uint16_t inflight_kb = (uint16_t)(conn->unacked_bytes / 1024);
memcpy(p, &inflight_kb, 2); p += 2;
char name[32]; memset(name, 0, 32);
get_node_name(ce->peer_node_id, name, 31);
if (!name[0]) snprintf(name, 32, "%016llX", (unsigned long long)ce->peer_node_id);
memcpy(p, name, 32); p += 32;
}
chat_event_post(CHAT_EVT_CONN_LIST, buf, (int)buf_sz);
u_free(buf);
}
/* ── Полный текстовый дамп одного соединения ── */
static void collect_conn_metrics(uint64_t peer_node_id) {
char buf[8192]; int off = 0;
if (!g_cc.initialized || !g_cc.inst || !g_cc.inst->connections) {
off = snprintf(buf, sizeof(buf), "uTun not initialized\n");
chat_event_post(CHAT_EVT_CONN_METRICS, (const uint8_t*)buf, off);
return;
}
struct ll_entry* e = queue_find_data_by_index(g_cc.inst->connections, (const uint8_t*)&peer_node_id);
if (!e) {
off = snprintf(buf, sizeof(buf), "Connection %016llX not found\n", (unsigned long long)peer_node_id);
chat_event_post(CHAT_EVT_CONN_METRICS, (const uint8_t*)buf, off);
return;
}
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
if (!ce || !ce->conn) {
off = snprintf(buf, sizeof(buf), "Connection %016llX has null data\n", (unsigned long long)peer_node_id);
chat_event_post(CHAT_EVT_CONN_METRICS, (const uint8_t*)buf, off);
return;
}
struct ETCP_CONN* conn = ce->conn;
uint64_t my_id = g_cc.inst->node_id;
char peername[64]; get_node_name(peer_node_id, peername, sizeof(peername));
if (!peername[0]) snprintf(peername, sizeof(peername), "%016llX", (unsigned long long)peer_node_id);
/* Count links */
int link_count = 0, links_up_count = 0;
struct ETCP_LINK* tl = conn->links; while (tl) { if (tl->link_status) links_up_count++; link_count++; tl = tl->next; }
off += snprintf(buf + off, sizeof(buf) - off,
"=== PEER: %04llX\u2192%04llX [%s] ===\n"
"Status: %s Links: %d/%d Initialized: %s Session: 0x%X MTU: %d\n"
"Reinit: %u Reset: %u Tx_state: %d Routing_exchange: %d\n"
"\n--- ETCP ---\n"
"RTT last/avg10: %.1f/%.1f ms Jitter: %.1f ms\n"
"Bytes sent: %u Retrans: %u ACKs: %u\n"
"Unacked bytes: %u Max inflight: %u\n"
"RX dup: %u TX dup: %u\n"
"IDs: next_tx=%u last_rx=%u last_del=%u rx_ack_till=%u\n"
"\n--- Queues ---\n",
(unsigned long long)(my_id & 0xFFFF), (unsigned long long)(peer_node_id & 0xFFFF), peername,
conn->links_up ? "UP" : "DOWN", links_up_count, link_count,
conn->initialized ? "yes" : "no", conn->session_id, conn->mtu,
conn->reinit_count, conn->reset_count, conn->tx_state, conn->routing_exchange_active,
conn->rtt_last / 10.0f, conn->rtt_avg_10 / 10.0f, conn->jitter / 10.0f,
conn->bytes_sent_total, conn->retransmissions_count, conn->ack_packets_count,
conn->unacked_bytes, conn->max_inflight,
conn->rx_dup_count, conn->tx_dup_count,
conn->next_tx_id, conn->last_rx_id, conn->last_delivered_id, conn->rx_ack_till);
if (conn->input_queue) off += snprintf(buf + off, sizeof(buf) - off, "input_q: %uB/%dp ", (unsigned)queue_total_bytes(conn->input_queue), queue_entry_count(conn->input_queue));
if (conn->input_send_q) off += snprintf(buf + off, sizeof(buf) - off, "send_q: %uB/%dp ", (unsigned)queue_total_bytes(conn->input_send_q), queue_entry_count(conn->input_send_q));
if (conn->input_wait_ack) off += snprintf(buf + off, sizeof(buf) - off, "wait_ack: %uB/%dp\n", (unsigned)queue_total_bytes(conn->input_wait_ack), queue_entry_count(conn->input_wait_ack));
if (conn->ack_q) off += snprintf(buf + off, sizeof(buf) - off, "ack_q: %uB/%dp ", (unsigned)queue_total_bytes(conn->ack_q), queue_entry_count(conn->ack_q));
if (conn->recv_q) off += snprintf(buf + off, sizeof(buf) - off, "recv_q: %uB/%dp ", (unsigned)queue_total_bytes(conn->recv_q), queue_entry_count(conn->recv_q));
if (conn->output_queue) off += snprintf(buf + off, sizeof(buf) - off, "out_q: %uB/%dp\n", (unsigned)queue_total_bytes(conn->output_queue), queue_entry_count(conn->output_queue));
off += snprintf(buf + off, sizeof(buf) - off, "\n--- ACK Debug ---\n"
"hit_inf=%u hit_sndq=%u miss=%u link_wait=%u\n",
conn->cnt_ack_hit_inf, conn->cnt_ack_hit_sndq, conn->cnt_ack_miss, conn->cnt_link_wait);
/* Normalizer */
if (conn->normalizer) {
struct PKTNORM* pn = conn->normalizer;
off += snprintf(buf + off, sizeof(buf) - off,
"\n--- Normalizer ---\n"
"in: %llu pkts / %llu B out: %llu pkts / %llu B\n"
"frag_size=%u data_ptr=%u data_size=%u alloc_err=%u logic_err=%u\n",
(unsigned long long)pn->in_total_pkts, (unsigned long long)pn->in_total_bytes,
(unsigned long long)pn->out_total_pkts, (unsigned long long)pn->out_total_bytes,
pn->frag_size, pn->data_ptr, pn->data_size, pn->alloc_errors, pn->logic_errors);
}
/* Links */
int link_idx = 0;
struct ETCP_LINK* link = conn->links;
while (link) {
const char* is_tcp_str = link->is_tcp ? "TCP" : "UDP";
char local_str[54], remote_str[54];
strncpy(local_str, link->conn ? sockaddr_storage_to_str(&link->conn->local_addr).str : "stcp", sizeof(local_str) - 1);
strncpy(remote_str, sockaddr_storage_to_str(&link->remote_addr).str, sizeof(remote_str) - 1);
off += snprintf(buf + off, sizeof(buf) - off,
"\n--- LINK#%d: %s %s NAT=%s ---\n"
"addr: %s -> %s\n"
"RTT: %.1f ms TT: %.1f ms\n"
"Keepalive: sent=%u recv=%u period=%ums\n"
"Inflight: %u / %u B (%u pkts)\n"
"Encrypt: sent=%zu err=%zu Decrypt: rcvd=%zu err=%zu\n",
link_idx, link->link_status ? "UP" : "DOWN", is_tcp_str, nat_type_str(link->nat_type),
local_str, remote_str,
link->rtt_last / 10.0f, link->tt_last / 10.0f,
link->keepalive_sent_count, link->keepalive_recv_count, link->keepalive_interval,
link->inflight_bytes, link->inflight_lim_bytes, link->inflight_packets,
link->total_encrypted, link->encrypt_errors, link->total_decrypted, link->decrypt_errors);
link = link->next; link_idx++;
}
chat_event_post(CHAT_EVT_CONN_METRICS, (const uint8_t*)buf, off);
}
void chat_core_collect_status_trampoline(void* arg) {
(void)arg;
chat_core_collect_status();
}
void chat_core_collect_conn_list_trampoline(void* arg) {
(void)arg;
collect_conn_list();
}
void chat_core_collect_conn_metrics_trampoline(void* arg) {
uint64_t peer_id;
memcpy(&peer_id, arg, sizeof(peer_id));
u_free(arg);
collect_conn_metrics(peer_id);
}

4
src/config_parser.c

@ -846,7 +846,8 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
// Set default values
cfg->global.name[0] = '\0';
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
cfg->global.mtu = 1280; // Default MTU: safe for IPv6 tunnels
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
cfg->global.keepalive_interval = 200; // Default 0.2 s
cfg->global.keepalive_adaptive = 1; // Default: adaptive enabled
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
@ -1131,6 +1132,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
struct utun_config *cfg = u_calloc(1, sizeof(struct utun_config));
if (!cfg) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "failed to allocate memory for config structure"); return NULL; }
cfg->global.name[0] = '\0';
cfg->global.mtu = 1280;
cfg->global.keepalive_timeout = 2000;
cfg->global.keepalive_interval = 200;
cfg->global.keepalive_adaptive = 1;

2
src/routing_layer/topo_group.c

@ -510,7 +510,7 @@ void topo_group_remove_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
struct ETCP_CONN* topo_group_find_conn_for_node(struct TOPO_GROUP* group, uint64_t node_id) {
if (!group) return NULL;
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, node_id);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "find_conn_for_node %016llx: nq=%p paths=%p head=%p",
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "find_conn_for_node %016llx: nq=%p paths=%p head=%p",
(unsigned long long)node_id, (void*)nq, nq ? (void*)nq->paths : NULL, (nq && nq->paths) ? (void*)nq->paths->head : NULL);
if (!nq || !nq->paths || !nq->paths->head) return NULL;
struct ll_entry* e = nq->paths->head;

2
src/transport_layer/auto_socket.c

@ -204,7 +204,7 @@ static int create_iface_udp_socket(struct AUTO_SOCKET* as, uint32_t ifindex, con
server.type = type;
server.netif_index = ifindex;
server.transport = 0;
server.mtu = inst->config ? inst->config->global.mtu : 1500;
server.mtu = inst->config ? inst->config->global.mtu : 1280;
if (family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&server.ip;

7
src/transport_layer/etcp.c

@ -1832,7 +1832,12 @@ void etcp_update_mtu(struct ETCP_CONN* etcp) {
if (!has_links) new_mtu = ETCP_RFC791_MIN_MTU;
if (new_mtu != etcp->mtu) {
etcp->mtu = new_mtu;
if (etcp->normalizer) etcp->normalizer->frag_size = etcp->mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
if (etcp->normalizer) {
int max_udp = IP_UDP_OVERHEAD_V4;
for (struct ETCP_LINK* l = etcp->links; l; l = l->next)
if (l->remote_addr.ss_family == AF_INET6) max_udp = IP_UDP_OVERHEAD_V6;
etcp->normalizer->frag_size = etcp->mtu - ACK_REZERV - max_udp - UDP_SC_HDR_SIZE;
}
}
}

18
src/transport_layer/etcp_connections.c

@ -924,7 +924,7 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
int mtu;
mtu = conn ? conn->mtu : STCP_MAX_MSG_SIZE;
if (is_tcp) { mtu = STCP_MAX_MSG_SIZE; link->mtu_local = mtu; link->mtu = mtu; link->inflight_lim_bytes = mtu * 4; link->handshake_minsize = 100; link->handshake_maxsize = mtu; }
else { if (mtu == 0) mtu = 1500; if (mtu > PACKET_DATA_MAX_MTU) mtu = PACKET_DATA_MAX_MTU; link->mtu_local = mtu; link->mtu = mtu; link->inflight_lim_bytes = mtu * 4; link->handshake_minsize = 100; link->handshake_maxsize = mtu; }
else { if (mtu == 0) mtu = 1280; if (mtu > PACKET_DATA_MAX_MTU) mtu = PACKET_DATA_MAX_MTU; link->mtu_local = mtu; link->mtu = mtu; link->inflight_lim_bytes = mtu * 4; link->handshake_minsize = 100; link->handshake_maxsize = mtu; }
/* mtu init moved above */
etcp_update_mtu(etcp);
link->initialized = 0;
@ -1155,6 +1155,7 @@ static int etcp_tcp_send(struct ETCP_DGRAM* dgram) {
if (link->send_hook) rc = (int)link->send_hook(0, dgram->data, dgram->data_len, NULL, 0, link, link->send_hook_ctx);
else if (link->tcp_link) rc = stcp_link_send(link->tcp_link, dgram->data, dgram->data_len) == 0 ? (int)dgram->data_len : -1;
else rc = -1;
if (rc > 0) link->total_encrypted += (size_t)rc;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TCP send: link=%d dlen=%d rc=%d via=%s",
link->etcp->log_name, link->local_link_id, dgram->data_len, rc,
link->send_hook ? "hook" : (link->tcp_link ? "stcp" : "NONE"));
@ -1182,9 +1183,10 @@ int etcp_encrypt_send(struct ETCP_DGRAM* dgram) {
// Размер зашифрованной части не должен превышать UDP payload = MTU - заголовки
int errcode=0;
sc_context_t* sc = &dgram->link->etcp->crypto_ctx;
int len=dgram->data_len-dgram->noencrypt_len;// не забываем добавить timestamp (2 bytes)
int wire_ov = WIRE_OVERHEAD(dgram->link->remote_addr.ss_family);
if (len<0 || len>dgram->link->mtu - wire_ov) { dgram->link->send_errors++; errcode=1; goto es_err; }
int len=dgram->data_len-dgram->noencrypt_len;
int udp_ov = (dgram->link->remote_addr.ss_family == AF_INET6) ? IP_UDP_OVERHEAD_V6 : IP_UDP_OVERHEAD_V4;
int wire_ov = udp_ov + SC_ENCRYPT_OVERHEAD;
if (len<0 || len + (int)dgram->noencrypt_len > (int)(dgram->link->mtu - wire_ov)) { dgram->link->send_errors++; errcode=1; goto es_err; }
uint8_t enc_buf[1600];
size_t enc_buf_len=0;
dgram->timestamp=get_current_timestamp();
@ -1195,8 +1197,8 @@ int etcp_encrypt_send(struct ETCP_DGRAM* dgram) {
if (enc_buf_len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "eencryption failed for node %016llx", (unsigned long long)dgram->link->etcp->instance->node_id);
dgram->link->send_errors++; errcode=2; goto es_err; }
if (enc_buf_len + dgram->noencrypt_len > (size_t)(dgram->link->mtu - UDP_HDR_SIZE)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "packet too long len=%d ne_len=%d", enc_buf_len, dgram->noencrypt_len);
if (enc_buf_len + dgram->noencrypt_len > (size_t)(dgram->link->mtu - udp_ov)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "packet too long enc=%zu ne=%d mtu=%d", enc_buf_len, dgram->noencrypt_len, dgram->link->mtu);
dgram->link->send_errors++; errcode=3; goto es_err; }
memcpy(enc_buf+enc_buf_len, dgram->data+len, dgram->noencrypt_len);
@ -1229,7 +1231,7 @@ static int etcp_send_ping_raw(struct ETCP_DGRAM* dgram, socket_t fd, sc_context_
return -1;
}
int len = dgram->data_len - dgram->noencrypt_len;
if (len < 0 || len > PACKET_DATA_SIZE - UDP_HDR_SIZE) {
if (len < 0 || len > PACKET_DATA_SIZE - IP_UDP_OVERHEAD_V4) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "ping packet data invalid len=%d", len);
return -1;
}
@ -1242,7 +1244,7 @@ static int etcp_send_ping_raw(struct ETCP_DGRAM* dgram, socket_t fd, sc_context_
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "encryption failed for ping");
return -1;
}
if (enc_buf_len + dgram->noencrypt_len > (size_t)(PACKET_DATA_SIZE - UDP_HDR_SIZE)) {
if (enc_buf_len + dgram->noencrypt_len > (size_t)(PACKET_DATA_SIZE - IP_UDP_OVERHEAD_V4)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "ping packet too long enc=%zu ne=%d", enc_buf_len, dgram->noencrypt_len);
return -1;
}

7
src/transport_layer/etcp_connections.h

@ -16,12 +16,11 @@ extern "C" {
#include <stdint.h>
#include <stddef.h>
#define UDP_HDR_SIZE 28// размер udp header + ethernet заголовков (ipv4) [для ipv6 = 48 байт]
#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
#define SC_ENCRYPT_OVERHEAD (3 + SC_TAG_SIZE) // timestamp(2)+flag_up(1)+AES-CCM tag(16) — поверх plaintext
#define IP_UDP_OVERHEAD_V4 (20 + 8) // IP(20)+UDP(8)
#define IP_UDP_OVERHEAD_V6 (40 + 8) // IP(40)+UDP(8)
#define SC_ENCRYPT_OVERHEAD (ETCP_ENCRYPTED_HDR_SIZE + SC_NONCE_SIZE + SC_CRC32_SIZE + SC_TAG_SIZE) // 3+13+4+16=36
#define UDP_SC_HDR_SIZE (SC_NONCE_SIZE + SC_TAG_SIZE + SC_CRC32_SIZE + 5) // 13+16+4+5=38 — sc overhead без ETCP_HDR + etcp framing
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
#define WIRE_OVERHEAD(fam) ((fam) == AF_INET6 ? IP_UDP_OVERHEAD_V6 + SC_ENCRYPT_OVERHEAD : IP_UDP_OVERHEAD_V4 + SC_ENCRYPT_OVERHEAD)
#define INFLIGHT_LIM_MIN 8192 // 8K

20
src/transport_layer/node_conn_direct.c

@ -885,7 +885,7 @@ void node_conn_direct_force_close(struct NODE_CONN_DIRECT* h) {
h->entry = NULL; u_free(h); return;
}
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close node=0x%016llx remaining=%d",
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close node=0x%016llx remaining=%d",
(unsigned long long)node_id, entry->handle_count);
if (entry->handle_count == 0) {
@ -898,28 +898,28 @@ void node_conn_direct_force_close(struct NODE_CONN_DIRECT* h) {
if (entry->connect_timer) { uasync_cancel_timeout(entry->ua, entry->connect_timer); entry->connect_timer = NULL; }
if (entry->fin_wait_timer) { uasync_cancel_timeout(entry->ua, entry->fin_wait_timer); entry->fin_wait_timer = NULL; }
if (conn && conn->state != 2 && conn->instance) {
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close: cleaning conn=%p", conn);
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: cleaning conn=%p", conn);
conn->fin_wait = 0;
conn->fin_wait_clear_cb = NULL; conn->fin_wait_clear_arg = NULL;
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing ncd_init_cb");
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing ncd_init_cb");
etcp_conn_remove_cbk(conn, ncd_init_cb, entry);
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing ncd_up_cb");
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing ncd_up_cb");
etcp_conn_remove_cbk(conn, ncd_up_cb, entry);
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing ncd_down_cb");
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing ncd_down_cb");
etcp_conn_remove_cbk(conn, ncd_down_cb, entry);
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close: scheduling deferred close");
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: scheduling deferred close");
if (conn->state != 2) uasync_call_soon(entry->ua, conn, ncd_deferred_close_conn);
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing from registry");
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing from registry");
ncd_registry_remove(conn->instance, entry);
}
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close: freeing entry=%p", entry);
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: freeing entry=%p", entry);
u_free(entry);
}
h->entry = NULL;
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close: freeing h=%p", h);
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: freeing h=%p", h);
u_free(h);
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close: done");
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: done");
}
/*

7
src/transport_layer/pkt_normalizer.c

@ -39,8 +39,11 @@ struct PKTNORM* pn_init(struct ETCP_CONN* etcp) {
pn->etcp = etcp;
pn->ua = etcp->instance->ua;
pn->frag_size = etcp->mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
int min_frag = UDP_HDR_SIZE + UDP_SC_HDR_SIZE + ACK_REZERV;
int max_udp = IP_UDP_OVERHEAD_V4;
for (struct ETCP_LINK* l = etcp->links; l; l = l->next)
if (l->remote_addr.ss_family == AF_INET6) max_udp = IP_UDP_OVERHEAD_V6;
pn->frag_size = etcp->mtu - ACK_REZERV - max_udp - UDP_SC_HDR_SIZE;
int min_frag = max_udp + UDP_SC_HDR_SIZE + ACK_REZERV;
if (etcp->mtu < min_frag || pn->frag_size > etcp->mtu) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "MTU %d too small (min %d)", etcp->mtu, min_frag);
pn->frag_size = etcp->mtu;

2
src/transport_layer/stcp_link.c

@ -54,6 +54,7 @@ static void link_rx_cb(struct ll_queue *q, void *arg) {
struct ETCP_LINK *l = link->etcp_link;
if (e->len >= 3) { uint16_t pp = e->dgram[1] | ((uint16_t)e->dgram[2] << 8); l->keepalive_timeout = (uint32_t)pp * KA_TIMEOUT_MULT; }
l->recv_keepalive = 1; l->remote_keepalive = 1; l->link_status = 1; l->keepalive_recv_count++;
l->total_decrypted += e->len;
l->last_recv_local_time = get_time_tb();
queue_dgram_free(e); queue_entry_free(e);
} else {
@ -62,6 +63,7 @@ static void link_rx_cb(struct ll_queue *q, void *arg) {
if (pkt) {
pkt->link = link->etcp_link; pkt->data_len = (uint16_t)e->len; pkt->noencrypt_len = 0;
if (e->len > 0) memcpy(pkt->data, e->dgram, e->len);
link->etcp_link->total_decrypted += e->len;
etcp_conn_input(pkt);
}
queue_dgram_free(e); queue_entry_free(e);

6
tools/chatgui-android/AGENTS.md

@ -137,6 +137,12 @@ sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;29.0.14206865"
## Сборка
```
./build.sh # APK
./build.sh install # сборка + прошивка на подключённый телефон
./build.sh install <SN> # на конкретный девайс
```
### APK (Android)
```bash

19
tools/chatgui-android/build.sh

@ -0,0 +1,19 @@
#!/bin/bash
set -eo pipefail
cd "$(dirname "$0")"
export ANDROID_HOME="${ANDROID_HOME:-/home/user/android}"
echo "Building..."
TMP=$(mktemp)
if ! ./gradlew assembleDebug > "$TMP" 2>&1; then
cat "$TMP"
rm -f "$TMP"
exit 1
fi
rm -f "$TMP"
echo "Build OK"
if [ "$1" = "install" ]; then
DEV=${2:-$(adb devices 2>/dev/null | awk 'NR==2{print $1}')}
ADB=adb; [ -n "$DEV" ] && ADB="adb -s $DEV"
$ADB install -r app/build/outputs/apk/debug/app-debug.apk
echo "Install OK"
fi

1
tools/chatgui/CMakeLists.txt

@ -122,6 +122,7 @@ add_executable(chatgui
src/media_blocks.cpp
src/audiodevicesettingspage.cpp
src/storagesettingspage.cpp
src/connmonitorwindow.cpp
transport/utun_node.cpp
transport/node_config.cpp
transport/config_updater.cpp

4
tools/chatgui/src/animtimer.cpp

@ -16,7 +16,7 @@ AnimTimer *AnimTimer::instance() {
void AnimTimer::shutdown() {
instance()->m_timer.stop();
instance()->m_icons.clear();
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "AnimTimer: shutdown");
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "AnimTimer: shutdown");
}
void AnimTimer::registerIcon(const QString &key, LottieIcon *icon) {
@ -53,7 +53,7 @@ void AnimTimer::onTimer() {
void AnimTimer::logRegisteredIcons() const {
for (auto it = m_icons.constBegin(); it != m_icons.constEnd(); ++it) {
LottieIcon *icon = it.value();
GUI_INFO("LottieIcon: path=%s frames=%d size=%dx%d tgsSize=%d valid=%d",
GUI_DEBUG("LottieIcon: path=%s frames=%d size=%dx%d tgsSize=%d valid=%d",
qPrintable(it.key()), icon->frameCount(),
icon->originalSize().width(), icon->originalSize().height(),
icon->tgsSize(), (int)icon->isValid());

2
tools/chatgui/src/audiodevicesettingspage.cpp

@ -236,7 +236,7 @@ void AudioDeviceSettingsPage::applyAndSave() {
m_savedCompressorEnabled = compEnabled;
m_savedCompressorMaxGainDb = compMaxGain;
m_savedCompressorRiseRate = compRiseRate;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "saved output=%d input=%d codec=%d compressor=%d gain=%ddB rise=%.1fx",
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "saved output=%d input=%d codec=%d compressor=%d gain=%ddB rise=%.1fx",
outIdx, inIdx, codecPreset, compEnabled, compMaxGain, (float)compRiseRate);
if (m_recorder) {

8
tools/chatgui/src/audiorecorder.cpp

@ -78,7 +78,7 @@ bool AudioRecorder::init() {
m_device = new ma_device;
std::memset(m_device, 0, sizeof(*m_device));
m_initialized = true;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "initialized (sampleRate=%d, channels=%d)", m_sampleRate, m_channels);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "initialized (sampleRate=%d, channels=%d)", m_sampleRate, m_channels);
return true;
}
@ -91,7 +91,7 @@ void AudioRecorder::shutdown() {
}
if (m_device) { delete m_device; m_device = nullptr; }
m_initialized = false;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "shutdown");
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "shutdown");
}
void AudioRecorder::startRecording() {
@ -122,7 +122,7 @@ void AudioRecorder::startRecording() {
ma_result r = ma_context_get_devices(ctx, nullptr, nullptr, &pCaptureDevices, &captureCount);
if (r == MA_SUCCESS && (ma_uint32)m_captureDeviceIndex < captureCount) {
config.capture.pDeviceID = &pCaptureDevices[m_captureDeviceIndex].id;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "using capture device[%d]: %s", m_captureDeviceIndex,
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "using capture device[%d]: %s", m_captureDeviceIndex,
pCaptureDevices[m_captureDeviceIndex].name);
}
}
@ -196,5 +196,5 @@ size_t AudioRecorder::bufferSize() const {
void AudioRecorder::setCompressorEnabled(bool enabled) {
m_compressorEnabled = enabled;
if (m_compressor) audio_compressor_set_enabled(m_compressor, enabled);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "compressor %s", enabled ? "enabled" : "disabled");
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "compressor %s", enabled ? "enabled" : "disabled");
}

13
tools/chatgui/src/channellist.cpp

@ -155,6 +155,15 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
connect(gearBtn, &QPushButton::clicked, this, [this] { emit settingsRequested(); });
auto *monitorBtn = new QPushButton(QString::fromUtf8("\xF0\x9F\x94\x8D"), footer); /* 🔍 */
monitorBtn->setFixedSize(28, 28);
monitorBtn->setToolTip(QString::fromUtf8("Connection Monitor"));
monitorBtn->setStyleSheet(
"QPushButton { border: none; font-size: 16px; background: transparent; color: gray; }"
"QPushButton:hover { color: palette(text); }");
fl->addWidget(monitorBtn);
connect(monitorBtn, &QPushButton::clicked, this, [this] { emit connMonitorRequested(); });
fl->addStretch();
auto *createBtn = new QPushButton("GRP", footer);
@ -222,7 +231,7 @@ void ChannelList::loadChannels() {
item->setData(ch.lastMsgAt, ChannelLastMsgAtRole);
item->setData(makeChannelIcon(colors[ci % 6], letter), ChannelIconRole);
m_model->appendRow(item);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "ChannelList::loadChannels row=%d cid='%s' name='%s' unread=%d lastMsgAt=%lld",
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "ChannelList::loadChannels row=%d cid='%s' name='%s' unread=%d lastMsgAt=%lld",
m_model->rowCount()-1, qPrintable(ch.channelId), qPrintable(ch.name), ch.unreadCount,
(long long)ch.lastMsgAt);
ci++;
@ -266,7 +275,7 @@ void ChannelList::setChannelPeersOnline(const QString& channelId, int count) {
}
}
if (!found)
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, " NOT FOUND in model");
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, " NOT FOUND in model");
}
void ChannelList::setAutoConnectActive(bool active) {

1
tools/chatgui/src/channellist.h

@ -27,6 +27,7 @@ signals:
void joinRequested();
void createGroupRequested();
void settingsRequested();
void connMonitorRequested();
private:
QListView *m_listView;

189
tools/chatgui/src/connmonitorwindow.cpp

@ -0,0 +1,189 @@
// connmonitorwindow.cpp — ETCP connection monitor (standalone, non-modal window)
#include "connmonitorwindow.h"
#include "../transport/gui_bridge.h"
extern "C" {
#include "chat/chat_core.h"
#include "../../lib/mem.h"
}
#include <QVBoxLayout>
#include <QSplitter>
#include <QLabel>
#include <QFont>
#include <QScrollBar>
#include <QHash>
#include <QApplication>
#include <cstring>
#define CONN_LIST_ENTRY_SIZE 46
static ConnMonitorWindow* g_activeConnMonitor = nullptr;
static void connListCb(const uint8_t* data, int len) {
if (g_activeConnMonitor) g_activeConnMonitor->onConnList(data, len);
}
static void connMetricsCb(const uint8_t* data, int len) {
if (g_activeConnMonitor) g_activeConnMonitor->onConnMetrics(data, len);
}
ConnMonitorWindow::ConnMonitorWindow(QWidget* parent)
: QDialog(parent)
{
setWindowTitle("Connection Monitor");
resize(900, 550);
setAttribute(Qt::WA_DeleteOnClose);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
auto* mainLayout = new QVBoxLayout(this);
mainLayout->setSpacing(4);
mainLayout->setContentsMargins(8, 8, 8, 8);
auto* title = new QLabel("Active ETCP Connections (10 fps)", this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
mainLayout->addWidget(title);
auto* splitter = new QSplitter(Qt::Horizontal, this);
splitter->setStyleSheet(
"QSplitter::handle { background: palette(mid); width: 2px; }");
m_connList = new QListWidget(splitter);
m_connList->setFixedWidth(280);
m_connList->setFont(QFont("monospace", 9));
m_connList->setStyleSheet(
"QListWidget { border: 1px solid palette(mid); border-radius: 4px;"
" background: palette(base); }"
"QListWidget::item { padding: 4px 6px; }"
"QListWidget::item:selected { background: #3390EC; color: white; }");
m_details = new QPlainTextEdit(splitter);
m_details->setReadOnly(true);
m_details->setFont(QFont("monospace", 9));
m_details->setStyleSheet(
"QPlainTextEdit { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 8px; background: palette(base); }");
m_details->setPlaceholderText("Select a connection to view metrics");
splitter->addWidget(m_connList);
splitter->addWidget(m_details);
splitter->setStretchFactor(0, 0);
splitter->setStretchFactor(1, 1);
mainLayout->addWidget(splitter, 1);
m_timer = new QTimer(this);
m_timer->setInterval(100);
connect(m_timer, &QTimer::timeout, this, &ConnMonitorWindow::onTimerTick);
connect(m_connList, &QListWidget::currentItemChanged,
this, &ConnMonitorWindow::onConnSelected);
g_activeConnMonitor = this;
gui_bridge_set_conn_list_cb(connListCb);
gui_bridge_set_conn_metrics_cb(connMetricsCb);
m_timer->start();
}
ConnMonitorWindow::~ConnMonitorWindow() {
m_timer->stop();
if (g_activeConnMonitor == this) g_activeConnMonitor = nullptr;
}
void ConnMonitorWindow::showEvent(QShowEvent* event) {
QDialog::showEvent(event);
m_timer->start();
}
void ConnMonitorWindow::hideEvent(QHideEvent* event) {
m_timer->stop();
QDialog::hideEvent(event);
}
void ConnMonitorWindow::onTimerTick() {
if (m_details->textCursor().hasSelection()) return;
if (QApplication::mouseButtons()) return;
gui_bridge_post_uasync_fn(chat_core_collect_conn_list_trampoline, nullptr);
if (m_selectedNodeId) requestMetricsForSelected();
}
void ConnMonitorWindow::onConnSelected(QListWidgetItem* current, QListWidgetItem* previous) {
(void)previous;
if (!current) { m_selectedNodeId = 0; m_details->clear(); return; }
m_selectedNodeId = (uint64_t)current->data(Qt::UserRole).toULongLong();
requestMetricsForSelected();
}
void ConnMonitorWindow::requestMetricsForSelected() {
void* arg = u_malloc(sizeof(uint64_t));
if (!arg) return;
memcpy(arg, &m_selectedNodeId, sizeof(uint64_t));
gui_bridge_post_uasync_fn(chat_core_collect_conn_metrics_trampoline, arg);
}
void ConnMonitorWindow::onConnList(const uint8_t* data, int len) {
if (len < 2) return;
uint16_t count; memcpy(&count, data, 2);
const uint8_t* p = data + 2;
QHash<uint64_t, QListWidgetItem*> existing;
for (int i = 0; i < m_connList->count(); i++) {
auto* item = m_connList->item(i);
existing[(uint64_t)item->data(Qt::UserRole).toULongLong()] = item;
}
m_connList->blockSignals(true);
for (uint16_t i = 0; i < count && (p + CONN_LIST_ENTRY_SIZE) <= (data + len); i++) {
uint64_t nodeId; memcpy(&nodeId, p, 8); p += 8;
uint8_t flags = *p++;
uint8_t linkCount = *p++;
uint16_t rtt; memcpy(&rtt, p, 2); p += 2;
p += 2; /* inflightKb */
char name[33]; memcpy(name, p, 32); name[32] = '\0'; p += 32;
if (!nodeId) continue;
bool linksUp = (flags & 1) != 0;
bool initialized = (flags & 2) != 0;
QString color = linksUp ? "#2E7D32" : "#C62828";
QString status = linksUp ? "UP" : (initialized ? "init" : "DOWN");
QString nodeHex = QString("%1").arg(nodeId, 16, 16, QChar('0')).right(16).left(4);
QString rttStr = rtt > 0 ? QString::number(rtt / 10.0f, 'f', 1) : "-";
QString text = QString("[%1] %2 %3 rtt=%4ms")
.arg(nodeHex)
.arg(QString::fromUtf8(name).left(16), -16)
.arg(status + QString(" %1L").arg(linkCount), -10)
.arg(rttStr);
auto it = existing.find(nodeId);
if (it != existing.end()) {
auto* item = *it;
item->setText(text);
item->setForeground(QColor(color));
existing.erase(it);
} else {
auto* item = new QListWidgetItem(text, m_connList);
item->setData(Qt::UserRole, QVariant((qulonglong)nodeId));
item->setForeground(QColor(color));
}
}
for (auto it = existing.begin(); it != existing.end(); ++it)
delete *it;
m_connList->blockSignals(false);
if (!m_selectedNodeId && m_connList->count() > 0) {
m_connList->setCurrentRow(0);
onConnSelected(m_connList->item(0), nullptr);
}
}
void ConnMonitorWindow::onConnMetrics(const uint8_t* data, int len) {
if (len <= 0) return;
int scrollVal = m_details->verticalScrollBar()->value();
m_details->setPlainText(QString::fromUtf8((const char*)data, len));
m_details->verticalScrollBar()->setValue(scrollVal);
}

34
tools/chatgui/src/connmonitorwindow.h

@ -0,0 +1,34 @@
// connmonitorwindow.h — ETCP connection monitor (standalone, non-modal window)
#pragma once
#include <QDialog>
#include <QListWidget>
#include <QPlainTextEdit>
#include <QTimer>
#include <cstdint>
class ConnMonitorWindow : public QDialog {
Q_OBJECT
public:
explicit ConnMonitorWindow(QWidget* parent = nullptr);
~ConnMonitorWindow() override;
void onConnList(const uint8_t* data, int len);
void onConnMetrics(const uint8_t* data, int len);
protected:
void showEvent(QShowEvent* event) override;
void hideEvent(QHideEvent* event) override;
private slots:
void onTimerTick();
void onConnSelected(QListWidgetItem* current, QListWidgetItem* previous);
private:
void requestMetricsForSelected();
QListWidget* m_connList;
QPlainTextEdit* m_details;
QTimer* m_timer;
uint64_t m_selectedNodeId = 0;
};

6
tools/chatgui/src/main.cpp

@ -40,10 +40,10 @@ static BOOL WINAPI console_ctrl_handler(DWORD dwCtrlType) {
static struct ShutdownSentinel {
~ShutdownSentinel() {
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "main: static destructors done");
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "main: static destructors done");
#ifdef _WIN32
DWORD threads = count_threads();
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "main: threads at exit=%lu", (unsigned long)threads);
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "main: threads at exit=%lu", (unsigned long)threads);
if (threads > 4) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "main: %lu hanging threads, TerminateProcess", (unsigned long)threads);
fflush(nullptr);
@ -128,6 +128,6 @@ int main(int argc, char *argv[]) {
#endif
int ret = app.exec();
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "main: app.exec() returned %d", ret);
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "main: app.exec() returned %d", ret);
return ret;
}

27
tools/chatgui/src/mainwindow.cpp

@ -8,6 +8,7 @@
#include "joindialog.h"
#include "settingsdialog.h"
#include "channelsettingsdialog.h"
#include "connmonitorwindow.h"
#include "creategroupdialog.h"
#include "invite_link.h"
#include "sound_manager.h"
@ -249,7 +250,7 @@ void MainWindow::setupSoundFromConfig() {
audio_compressor_configure(m_recorder->compressor(), &compCfg);
}
}
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "AudioRecorder initialized (captureDevice=%d, opusPreset=%d, compressor=%d)",
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "AudioRecorder initialized (captureDevice=%d, opusPreset=%d, compressor=%d)",
m_recorder->captureDeviceIndex(), VoiceEncoder::preset(),
m_recorder->isCompressorEnabled() ? 1 : 0);
}
@ -308,6 +309,7 @@ void MainWindow::setupConnects() {
connect(m_channelList, &ChannelList::joinRequested, this, &MainWindow::showJoinDialog);
connect(m_channelList, &ChannelList::createGroupRequested, this, &MainWindow::onCreateGroupRequested);
connect(m_channelList, &ChannelList::settingsRequested, this, &MainWindow::showSettings);
connect(m_channelList, &ChannelList::connMonitorRequested, this, &MainWindow::showConnMonitor);
connect(m_channelList, &ChannelList::channelSettingsRequested, this, &MainWindow::showChannelSettings);
connect(m_accountList, &AccountList::memberRenamed,
m_messageList, &MessageList::reloadLocalNicks);
@ -428,6 +430,17 @@ void MainWindow::showSettings() {
reloadChannels();
}
void MainWindow::showConnMonitor() {
if (m_connMonitor) {
m_connMonitor->raise();
m_connMonitor->activateWindow();
return;
}
m_connMonitor = new ConnMonitorWindow(nullptr);
connect(m_connMonitor, &QDialog::destroyed, this, [this] { m_connMonitor = nullptr; });
m_connMonitor->show();
}
void MainWindow::onMessageReceived(const char* ch_id, int ch_id_len, uint64_t author) {
QString cid = QString::fromUtf8(ch_id, ch_id_len);
if (m_db && author != m_db->myNodeId())
@ -598,14 +611,16 @@ void MainWindow::autoPlayTimerTick() {
QString fullPath = autoPlayMediaDir() + QStringLiteral("/") + item.channelId
+ QStringLiteral("/") + fp;
if (QFile::exists(fullPath)) {
QString chId = item.channelId;
qint64 msgId = item.msgId;
float dur = item.durationSec;
m_autoPlayQueue.dequeue();
m_autoPlayDownloading.remove(item.msgId);
m_autoPlayDownloading.remove(msgId);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "auto-play: playing ch=%s msg=%lld file=%s dur=%.1fs",
qPrintable(item.channelId), (long long)item.msgId, qPrintable(fullPath),
(double)item.durationSec);
qPrintable(chId), (long long)msgId, qPrintable(fullPath), (double)dur);
MessageDelegate::stopAllAutoPlay();
VoicePlayer::playOpusFile(fullPath);
MessageDelegate::startAutoPlay(fullPath, item.durationSec);
float actualDur = VoicePlayer::playOpusFile(fullPath);
MessageDelegate::startAutoPlay(fullPath, actualDur);
return;
}
}

3
tools/chatgui/src/mainwindow.h

@ -13,6 +13,7 @@ class AccountList;
class DbManager;
class UtunNode;
class AudioRecorder;
class ConnMonitorWindow;
struct AutoPlayItem {
QString channelId;
@ -41,6 +42,7 @@ private slots:
void showJoinDialog();
void onCreateGroupRequested();
void showSettings();
void showConnMonitor();
void showChannelSettings(const QString& channelId);
void autoPlayTimerTick();
@ -79,6 +81,7 @@ private:
QString m_pendingChannelSelect;
QString m_currentChannelId;
AudioRecorder *m_recorder = nullptr;
ConnMonitorWindow *m_connMonitor = nullptr;
QQueue<AutoPlayItem> m_autoPlayQueue;
QTimer* m_autoPlayTimer = nullptr;

2
tools/chatgui/src/media_blocks.cpp

@ -284,7 +284,7 @@ MediaBlockInfo media_split_to_blocks(const QString& srcFile,
if (removeSrc) QFile::remove(srcFile);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "media_split_to_blocks: %s -> %d blocks (%lld bytes each) in %s",
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "media_split_to_blocks: %s -> %d blocks (%lld bytes each) in %s",
qPrintable(fi.fileName()), info.numBlocks, (long long)info.blockSize, qPrintable(mediaDir));
return info;
}

19
tools/chatgui/src/messagedelegate.cpp

@ -153,14 +153,16 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
if (!st.playing && !st.paused) {
/* Start playback then seek */
float actualDur = VoicePlayer::playOpusFile(filePath);
if (actualDur <= 0.0f) return false;
targetSec = clickFrac * actualDur;
st.playing = true;
st.paused = false;
st.durationSec = dur;
st.durationSec = actualDur;
st.startMs = now - (qint64)(targetSec * 1000.0);
st.pausedPosSec = targetSec;
st.displayFrac = (targetSec > 0 && dur > 0) ? (targetSec / dur) : 0;
st.displayFrac = (targetSec > 0 && actualDur > 0) ? (targetSec / actualDur) : 0;
st.displayElapsed = targetSec;
VoicePlayer::playOpusFile(filePath);
int sr = SoundManager::instance()->currentPcmSampleRate();
if (sr > 0)
SoundManager::instance()->seekCurrentPcm((unsigned long long)(targetSec * sr));
@ -222,15 +224,16 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
SoundManager::instance()->stopCurrentPcm();
}
float actualDur = VoicePlayer::playOpusFile(filePath);
if (actualDur <= 0.0f) return false;
st.playing = true;
st.paused = false;
st.pausedPosSec = 0;
st.startMs = QDateTime::currentMSecsSinceEpoch();
st.durationSec = dur;
st.durationSec = actualDur;
st.displayFrac = 0;
st.displayElapsed = 0;
VoicePlayer::playOpusFile(filePath);
s_voiceTimer->start();
model->setData(index, 1, MsgVoicePlayStateRole);
return true;
@ -850,8 +853,6 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
QColor idle(0x70, 0x70, 0x78, 130);
painter->setBrush((isPlaying || isPaused) ? (before ? played : unplayed) : idle);
painter->drawRect(QRect(x, y, bw, barH));
if (i == 0) GUI_DEBUG("voice paint: wfN=%d wfW=%d logV=%.2f barH=%d",
wfN, wfR.width(), logV, barH);
}
}
@ -1187,12 +1188,12 @@ void MessageDelegate::drawAvatar(QPainter *p, const Layout &L,
p->restore();
}
void MessageDelegate::startAutoPlay(const QString& filePath, float durationSec) {
void MessageDelegate::startAutoPlay(const QString& filePath, float actualDurationSec) {
ensureVoiceTimer();
VoicePlayState& st = s_voiceStates[filePath];
st.playing = true;
st.paused = false;
st.durationSec = (durationSec > 0) ? durationSec : 1.0f;
st.durationSec = (actualDurationSec > 0) ? actualDurationSec : 1.0f;
st.startMs = QDateTime::currentMSecsSinceEpoch();
st.pausedPosSec = 0.0f;
st.displayFrac = 0.0f;

2
tools/chatgui/src/messagedelegate.h

@ -48,7 +48,7 @@ public:
const QStyleOptionViewItem &option,
const QModelIndex &index) override;
static void startAutoPlay(const QString& filePath, float durationSec);
static void startAutoPlay(const QString& filePath, float actualDurationSec);
static void stopAllAutoPlay();
private:

6
tools/chatgui/src/messagelist.cpp

@ -166,8 +166,6 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
wf.append((float)idx / 63.0f);
}
item->setData(wf, MsgVoiceWaveformRole);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "setVoiceMessageRoles: voice dur=%dms wf=%d exists=%d",
durMs, wf.size(), QFile::exists(fp));
}
}
if (ct == "application/octet-stream") {
@ -659,9 +657,9 @@ void MessageList::updateReadPosition() {
void MessageList::cleanupAnimations() {
size_t n = s_icons.size();
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "MessageList: cleanupAnimations clearing %zu icons", n);
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "MessageList: cleanupAnimations clearing %zu icons", n);
s_icons.clear();
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "MessageList: cleanupAnimations done");
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "MessageList: cleanupAnimations done");
}
void MessageList::updateMessageProgress(const QString& chId, int64_t msgId,

40
tools/chatgui/src/settingsdialog.cpp

@ -7,6 +7,7 @@
#include "soundsettingspage.h"
#include "audiodevicesettingspage.h"
#include "storagesettingspage.h"
#include "connmonitorwindow.h"
#include "../transport/gui_bridge.h"
#include "../../lib/mem.h"
#include "../db/db_manager.h"
@ -31,7 +32,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
{
setWindowTitle("Settings");
setMinimumSize(760, 460);
setModal(true);
setWindowModality(Qt::WindowModal);
auto* mainLayout = new QHBoxLayout(this);
mainLayout->setSpacing(0);
@ -48,6 +49,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
m_categoryList->addItem("Profile");
m_categoryList->addItem("Storage");
m_categoryList->addItem("Network");
m_categoryList->addItem("Connections");
m_categoryList->addItem("Database");
m_categoryList->addItem("Status");
m_categoryList->addItem("Nodes");
@ -105,6 +107,22 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
m_networkPage = new NetworkSettingsPage(m_pages);
m_pages->addWidget(m_networkPage);
// Connections page — button to open standalone window
{
auto* connPage = new QWidget(m_pages);
auto* connLayout = new QVBoxLayout(connPage);
connLayout->setAlignment(Qt::AlignCenter);
auto* hintLabel = new QLabel("Live ETCP connection monitor\nwith per-link BBR statistics", connPage);
hintLabel->setAlignment(Qt::AlignCenter);
hintLabel->setStyleSheet("font-size: 12px; color: palette(text); padding: 20px;");
connLayout->addWidget(hintLabel);
auto* openBtn = new QPushButton("Open Connection Monitor", connPage);
openBtn->setStyleSheet("QPushButton { padding: 10px 20px; font-size: 13px; }");
connect(openBtn, &QPushButton::clicked, this, &SettingsDialog::onOpenConnMonitor);
connLayout->addWidget(openBtn);
m_pages->addWidget(connPage);
}
// Database page
m_databasePage = new DatabaseSettingsPage(db, m_pages);
m_pages->addWidget(m_databasePage);
@ -187,11 +205,11 @@ void SettingsDialog::loadProfileFromConfig(const QString& path) {
void SettingsDialog::onCategoryChanged(int row) {
m_pages->setCurrentIndex(row);
if (row == 1) m_storagePage->loadFromDb();
if (row == 3) m_databasePage->refreshStatus();
if (row == 4) m_statusPage->refreshStatus();
if (row == 5) m_nodesPage->refreshNodes();
if (row == 6) m_soundPage->loadFromDb();
if (row == 7) m_audioDevicePage->loadFromDb();
if (row == 4) m_databasePage->refreshStatus();
if (row == 5) m_statusPage->refreshStatus();
if (row == 6) m_nodesPage->refreshNodes();
if (row == 7) m_soundPage->loadFromDb();
if (row == 8) m_audioDevicePage->loadFromDb();
}
void SettingsDialog::onSave() {
@ -268,3 +286,13 @@ void SettingsDialog::onSave() {
}
accept();
}
void SettingsDialog::onOpenConnMonitor() {
if (m_connMonitorWindow) {
m_connMonitorWindow->raise();
m_connMonitorWindow->activateWindow();
return;
}
m_connMonitorWindow = new ConnMonitorWindow(nullptr);
m_connMonitorWindow->show();
}

3
tools/chatgui/src/settingsdialog.h

@ -13,6 +13,7 @@ class NetworkSettingsPage;
class DatabaseSettingsPage;
class StatusPage;
class NodesPage;
class ConnMonitorWindow;
class SoundSettingsPage;
class AudioDeviceSettingsPage;
class StorageSettingsPage;
@ -29,6 +30,7 @@ public:
private slots:
void onCategoryChanged(int row);
void onSave();
void onOpenConnMonitor();
private:
void loadProfileFromConfig(const QString& path);
@ -45,6 +47,7 @@ private:
DatabaseSettingsPage* m_databasePage;
StatusPage* m_statusPage;
NodesPage* m_nodesPage;
ConnMonitorWindow* m_connMonitorWindow = nullptr;
SoundSettingsPage* m_soundPage;
AudioDeviceSettingsPage* m_audioDevicePage;
StorageSettingsPage* m_storagePage;

6
tools/chatgui/src/sound_manager.cpp

@ -11,7 +11,7 @@ SoundManager* SoundManager::instance() {
}
SoundManager::~SoundManager() {
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "SoundManager: static destructor entered");
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: static destructor entered");
shutdown();
}
@ -43,14 +43,14 @@ void SoundManager::shutdown() {
}
m_sounds.clear();
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "SoundManager: before ma_engine_uninit engine=%p", (void*)m_engine);
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: before ma_engine_uninit engine=%p", (void*)m_engine);
if (m_engine) { ma_engine_uninit(m_engine); delete m_engine; m_engine = nullptr; }
if (m_currentPcmSound) { delete m_currentPcmSound; m_currentPcmSound = nullptr; }
if (m_currentPcmBuffer) {
ma_audio_buffer* buf = (ma_audio_buffer*)m_currentPcmBuffer;
ma_audio_buffer_uninit(buf); delete buf; m_currentPcmBuffer = nullptr;
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "SoundManager: after ma_engine_uninit");
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: after ma_engine_uninit");
m_initialized = false;
}

10
tools/chatgui/src/voiceplayback.cpp

@ -75,7 +75,7 @@ int VoicePlayer::decodeOpusFile(const QString& filePath, std::vector<int16_t>& o
return frameCount;
}
void VoicePlayer::playOpusFile(const QString& filePath) {
float VoicePlayer::playOpusFile(const QString& filePath) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFile: %s", qPrintable(filePath));
std::vector<int16_t> pcm;
int sampleRate, channels;
@ -83,26 +83,28 @@ void VoicePlayer::playOpusFile(const QString& filePath) {
if (decodeOpusFile(filePath, pcm, sampleRate, channels, duration) <= 0) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFile: decode failed for %s", qPrintable(filePath));
return;
return 0.0f;
}
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFile: decoded %zu samples %dHz %dch %.1fs",
pcm.size(), sampleRate, channels, duration);
SoundManager::instance()->playRawPcm(pcm.data(), pcm.size() / channels, sampleRate, channels);
return duration;
}
void VoicePlayer::playOpusFromReader(MediaBlockReader& reader) {
float VoicePlayer::playOpusFromReader(MediaBlockReader& reader) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFromReader: %s", qPrintable(reader.debugPath()));
std::vector<int16_t> pcm;
int sampleRate, channels;
float duration;
if (decodeOpusFromReader(reader, pcm, sampleRate, channels, duration) <= 0) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFromReader: decode failed");
return;
return 0.0f;
}
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFromReader: decoded %zu samples %dHz %dch %.1fs",
pcm.size(), sampleRate, channels, duration);
SoundManager::instance()->playRawPcm(pcm.data(), pcm.size() / channels, sampleRate, channels);
return duration;
}
int VoicePlayer::decodeOpusFromReader(MediaBlockReader& reader, std::vector<int16_t>& outPcm,

8
tools/chatgui/src/voiceplayback.h

@ -8,10 +8,10 @@ class MediaBlockReader;
class VoicePlayer {
public:
static void playOpusFile(const QString& filePath);
static float playOpusFile(const QString& filePath);
static int decodeOpusFile(const QString& filePath, std::vector<int16_t>& outPcm,
int& outSampleRate, int& outChannels, float& outDurationSec);
static void playOpusFromReader(MediaBlockReader& reader);
int& outSampleRate, int& outChannels, float& outDurationSec);
static float playOpusFromReader(MediaBlockReader& reader);
static int decodeOpusFromReader(MediaBlockReader& reader, std::vector<int16_t>& outPcm,
int& outSampleRate, int& outChannels, float& outDurationSec);
int& outSampleRate, int& outChannels, float& outDurationSec);
};

10
tools/chatgui/transport/gui_bridge.h

@ -28,6 +28,8 @@ struct TOPO_GROUP_NODE;
#define GUI_EVT_ATTACHMENT_DOWNLOADED 14 /* data: [ch_id_len:1][ch_id:var][msg_id:8] */
#define GUI_EVT_DOWNLOAD_PROGRESS 15 /* data: [ch_id_len:1][ch_id:var][msg_id:8][blocks_done:4][num_blocks:4] */
#define GUI_EVT_NODEINFO_UPDATE 16 /* data: node_id:8 conn_presence:1 conn_up:1 best_rtt_packed:2 */
#define GUI_EVT_CONN_LIST 20 /* data: [count:2][entry:46B]* — binary conn list */
#define GUI_EVT_CONN_METRICS 21 /* data: text dump of selected connection metrics */
/* ── API ── */
@ -100,6 +102,14 @@ void gui_bridge_set_nodeinfo_update_cb(gui_nodeinfo_update_fn cb);
typedef void (*gui_invite_received_fn)(const uint8_t* data, int len);
void gui_bridge_set_invite_received_cb(gui_invite_received_fn cb);
/* Callback для списка ETCP-соединений (вызывается из GUI-потока) */
typedef void (*gui_conn_list_fn)(const uint8_t* data, int len);
void gui_bridge_set_conn_list_cb(gui_conn_list_fn cb);
/* Callback для метрик выбранного соединения (вызывается из GUI-потока) */
typedef void (*gui_conn_metrics_fn)(const uint8_t* data, int len);
void gui_bridge_set_conn_metrics_cb(gui_conn_metrics_fn cb);
/* uTun nodeinfo callback — registered via utun_add_nodeinfo_cbk, called from uasync thread */
void gui_nodeinfo_cb_impl(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq, void* arg);

18
tools/chatgui/transport/gui_bridge_impl.cpp

@ -39,6 +39,8 @@ static gui_attachment_downloaded_fn g_attachment_downloaded_cb = nullptr;
static gui_download_progress_fn g_download_progress_cb = nullptr;
static gui_nodeinfo_update_fn g_nodeinfo_update_cb = nullptr;
static gui_invite_received_fn g_invite_received_cb = nullptr;
static gui_conn_list_fn g_conn_list_cb = nullptr;
static gui_conn_metrics_fn g_conn_metrics_cb = nullptr;
static struct UASYNC* g_ua = nullptr;
/* ── GuiBridgeReceiver implementation ── */
@ -139,7 +141,7 @@ void GuiBridgeReceiver::processPost(int eventType, QByteArray data) {
}
break;
case GUI_EVT_DB_READY:
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: DB_READY (chat DB tables created)");
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: DB_READY (chat DB tables created)");
if (g_db_ready_cb) g_db_ready_cb();
break;
case GUI_EVT_STATUS_REFRESH:
@ -180,6 +182,12 @@ void GuiBridgeReceiver::processPost(int eventType, QByteArray data) {
case 18: /* CHAT_EVT_INVITE_RECEIVED */
if (g_invite_received_cb) g_invite_received_cb(d, dlen);
break;
case GUI_EVT_CONN_LIST:
if (g_conn_list_cb) g_conn_list_cb(d, dlen);
break;
case GUI_EVT_CONN_METRICS:
if (g_conn_metrics_cb) g_conn_metrics_cb(d, dlen);
break;
default:
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: unknown event type %d", eventType);
break;
@ -277,6 +285,14 @@ void gui_bridge_set_invite_received_cb(gui_invite_received_fn cb) {
g_invite_received_cb = cb;
}
void gui_bridge_set_conn_list_cb(gui_conn_list_fn cb) {
g_conn_list_cb = cb;
}
void gui_bridge_set_conn_metrics_cb(gui_conn_metrics_fn cb) {
g_conn_metrics_cb = cb;
}
} /* extern "C" */
/* ── uTun nodeinfo callback (called from uasync thread) ── */

18
tools/chatgui/transport/utun_node.cpp

@ -102,7 +102,7 @@ void UtunNode::stop() {
void UtunNode::finalize() {
if (!m_instance) return;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "finalize: cleanup start");
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "finalize: cleanup start");
etcp_router_bind(m_instance, ETCP_RT_ID_CHAT, nullptr);
chat_sync_destroy(m_instance);
chat_core_destroy(m_instance);
@ -110,7 +110,7 @@ void UtunNode::finalize() {
m_instance = nullptr;
uasync_destroy(m_ua, 0);
m_ua = nullptr;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "finalize: cleanup done");
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "finalize: cleanup done");
}
void UtunNode::send(uint64_t dstNodeId, const QByteArray& data) {
@ -302,7 +302,7 @@ void UtunNode::runLoop() {
debug_level_t lvl = debug_level_from_name(lvl_name);
if (cat != DEBUG_CATEGORY_NONE && lvl != DEBUG_LEVEL_NONE) {
g_debug_config.category_levels[cat] = lvl;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "debug category: %s=%s",
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "debug category: %s=%s",
debug_get_category_name(cat),
lvl_name);
}
@ -359,7 +359,7 @@ void UtunNode::runLoop() {
sqlite3_exec(m_instance->topo_sqlite_db, "PRAGMA synchronous=NORMAL", NULL, NULL, NULL);
sqlite3_exec(m_instance->topo_sqlite_db, "PRAGMA wal_autocheckpoint=10000", NULL, NULL, NULL);
topo_node_sqlite_init(m_instance->topo_sqlite_db);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "SQLite (shared): %s db=%p", db_file, (void*)m_instance->topo_sqlite_db);
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SQLite (shared): %s db=%p", db_file, (void*)m_instance->topo_sqlite_db);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "SQLite open failed: %s rc=%d", db_file, rc);
if (m_instance->topo_sqlite_db) { sqlite3_close(m_instance->topo_sqlite_db); m_instance->topo_sqlite_db = NULL; }
@ -380,7 +380,7 @@ void UtunNode::runLoop() {
}
{ int sock_count = 0; for (struct ETCP_SOCKET* s = m_instance->etcp_sockets; s; s = s->next) sock_count++;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "utun_node: after instance_init, etcp_sockets=%d conns=%d",
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "utun_node: after instance_init, etcp_sockets=%d conns=%d",
sock_count, queue_entry_count(m_instance->connections)); }
etcp_router_bind(m_instance, ETCP_RT_ID_CHAT, recvCallback);
@ -397,8 +397,8 @@ void UtunNode::runLoop() {
chat_sync_init(m_instance);
chat_core_sync_my_addresses();
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "chat_core + chat_sync initialized");
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "utun_node: entering poll loop");
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "chat_core + chat_sync initialized");
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "utun_node: entering poll loop");
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
@ -407,11 +407,11 @@ void UtunNode::runLoop() {
uasync_poll(ua, 100);
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "runLoop: poll exit m_stop=%d", (int)m_stop);
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "runLoop: poll exit m_stop=%d", (int)m_stop);
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "runLoop: emitting stopped");
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "runLoop: emitting stopped");
QMetaObject::invokeMethod(this, [this] { emit stopped(); });
}

Loading…
Cancel
Save