Files
utun2/src/chat/chat_status.c
T

610 lines
30 KiB
C

/*
* chat_status.c — сбор статуса (NTP + ETCP connections) и отправка
*
* Вынесено из chat_core.c для уменьшения размера модуля.
*/
#include "chat_core_priv.h"
#include "chat_event.h"
#include "chat_member.h"
#include "../member_files/protocol.h"
#include "../utun_instance.h"
#include "../ntp_time.h"
#include "../../lib/ll_queue.h"
#include "../../lib/mem.h"
#include "../../lib/platform_compat.h"
#include "../../lib/strbuf.h"
#include "../transport_layer/etcp.h"
#include "../transport_layer/etcp_connections.h"
#include "../transport_layer/stcp_link.h"
#include "../transport_layer/pkt_normalizer.h"
#include "../routing_layer/topo_node.h"
#include "../routing_layer/topo_node_sqlite.h"
#include "../routing_layer/topo_group.h"
#include <string.h>
struct address_profile_text { char text[80]; };
static struct address_profile_text link_address_profile_text(const struct ETCP_LINK *link) {
static const struct address_profile unknown = {0};
const struct address_profile *profile = &unknown;
const struct TOPO_NODE *peer = topo_node_registry_find(link->etcp->instance->topo_groups, link->etcp->peer_node_id);
uint8_t socket_id = link->remote_socket_id;
/* У входящего TCP source port относится к соединению, не к listener пира. */
if (link->is_tcp && link->is_server) peer = NULL;
if (peer && link->is_tcp && link->remote_addr.ss_family == AF_INET) {
const struct sockaddr_in *target = (const struct sockaddr_in *)&link->remote_addr;
const struct TOPO_ADDR4 *address = peer->v4_addrs;
while (address && (!(address->protocol & TOPO_PROTO_TCP) || address->port != ntohs(target->sin_port) ||
memcmp(address->addr, &target->sin_addr, 4))) address = address->next;
if (address) socket_id = address->socket_id; else peer = NULL;
} else if (peer && link->is_tcp && link->remote_addr.ss_family == AF_INET6) {
const struct sockaddr_in6 *target = (const struct sockaddr_in6 *)&link->remote_addr;
const struct TOPO_ADDR6 *address = peer->v6_addrs;
while (address && (!(address->protocol & TOPO_PROTO_TCP) || address->port != ntohs(target->sin6_port) ||
memcmp(address->addr, &target->sin6_addr, 16))) address = address->next;
if (address) socket_id = address->socket_id; else peer = NULL;
}
if (peer && link->remote_addr.ss_family == AF_INET) {
for (const struct TOPO_SOCKMETA4 *meta = peer->v4_sock_meta; meta; meta = meta->next)
if (meta->id == socket_id) { profile = &meta->address_profile; break; }
} else if (peer && link->remote_addr.ss_family == AF_INET6) {
for (const struct TOPO_SOCKMETA6 *meta = peer->v6_sock_meta; meta; meta = meta->next)
if (meta->id == socket_id) { profile = &meta->address_profile; break; }
}
struct address_profile_text result;
snprintf(result.text, sizeof(result.text), "%s/%s (%s)", address_mapping_name(profile->mapping),
address_inbound_name(profile->inbound), address_profile_source_name(profile->source));
return result;
}
static void get_node_name(struct UTUN_INSTANCE* inst, uint64_t node_id, char* out, size_t sz) {
struct chat_core_ctx* cc = CC(inst);
out[0] = '\0';
if (!cc || !cc->db || node_id == 0) return;
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(cc->db, "SELECT name FROM nodes WHERE node_id=?", -1, &st, NULL) != SQLITE_OK) return;
sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
if (sqlite3_step(st) == SQLITE_ROW) {
const char* n = (const char*)sqlite3_column_text(st, 0);
if (n) snprintf(out, sz, "%s", n);
}
sqlite3_finalize(st);
}
static void chat_core_collect_status(struct UTUN_INSTANCE* inst) {
struct strbuf text = strbuf_new();
if (!inst || !CC(inst) || !CC(inst)->initialized) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "status snapshot: chat unavailable");
if (strbuf_addf(&text, "uTun not initialized\n") < 0) goto failed;
goto post;
}
struct uasync_load load;
if (uasync_get_load(inst->ua, &load) == ERR_OK && load.valid) {
if (strbuf_addf(&text, "uasync CPU: %.1f%%, average %.1f%% (window %.0f ms, age %.0f ms)\n",
load.current_percent, load.average_percent, load.window_us / 1000.0,
(get_time_us() - load.sample_time_us) / 1000.0) < 0) goto failed;
} else if (strbuf_addf(&text, "uasync CPU: unavailable\n") < 0) goto failed;
struct NTP_TIME* ntp = &inst->ntp;
if (strbuf_addf(&text, "=== NTP ===\nEnabled: %s\nSynced: %s\nOffset: %.1f s\n",
ntp->enabled ? "yes" : "no", ntp->synced ? "yes" : "no", ntp->offset_us / 1000000.0) < 0) goto failed;
if (ntp->server_count > 0 && ntp->servers) {
if (strbuf_addf(&text, "Servers: %d\n", ntp->server_count) < 0) goto failed;
for (int i = 0; i < ntp->server_count && ntp->servers[i]; i++)
if (strbuf_addf(&text, " [%d] %s%s\n", i, ntp->servers[i],
i == ntp->server_current ? " (current)" : "") < 0) goto failed;
}
if (ntp->synced && ntp->last_sync_tb > 0) {
uint64_t now_tb = get_time_tb();
uint64_t ago_sec = (now_tb - ntp->last_sync_tb) / 10000;
if (strbuf_addf(&text, "Last sync: %llu sec ago\n", (unsigned long long)ago_sec) < 0) goto failed;
}
int conn_count = 0;
struct ll_entry* entry = inst->connections ? inst->connections->head : NULL;
while (entry) { conn_count++; entry = entry->next; }
if (strbuf_addf(&text, "=== Connections (%d) ===\n", conn_count) < 0) goto failed;
uint64_t my_id = inst->node_id;
entry = inst->connections ? inst->connections->head : NULL;
while (entry) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data;
if (!ce || !ce->conn) { entry = entry->next; continue; }
uint64_t pid = ce->peer_node_id;
struct ETCP_CONN* conn = ce->conn;
int link_count = 0;
struct ETCP_LINK* tl = conn->links;
while (tl) { link_count++; tl = tl->next; }
const char* init_str = conn->initialized ? "" : "(!init)";
char myhex[5], peerhex[5];
snprintf(myhex, sizeof(myhex), "%04llX", (unsigned long long)(my_id & 0xFFFF));
snprintf(peerhex, sizeof(peerhex), "%04llX", (unsigned long long)(pid & 0xFFFF));
char peername[64];
get_node_name(inst, pid, peername, sizeof(peername));
if (peername[0]) {
if (strbuf_addf(&text, "[%s]→[%s] %s ETCP:%s%s(%dL)\n",
myhex, peerhex, peername,
conn->links_up ? "UP" : "DOWN", init_str, link_count) < 0) goto failed;
} else {
if (strbuf_addf(&text, "[%s]→[%s] ETCP:%s%s(%dL)\n",
myhex, peerhex,
conn->links_up ? "UP" : "DOWN", init_str, link_count) < 0) goto failed;
}
int link_idx = 0;
struct ETCP_LINK* link = conn->links;
while (link) {
char rtt_str[32]; rtt_str[0] = '\0';
if (link->rtt_last > 0) snprintf(rtt_str, sizeof(rtt_str), " / rtt=%ums", link->rtt_last / 10);
if (strbuf_addf(&text, " LINK#%d: %s %s %s NAT=%s%s\n",
link_idx,
link->is_tcp ? "TCP" : "UDP",
link->conn ? link->conn->name : "?",
link->link_status ? "UP" : "DOWN",
link_address_profile_text(link).text,
rtt_str) < 0) goto failed;
link = link->next; link_idx++;
}
entry = entry->next;
}
post:
DEBUG_TRACE(DEBUG_CATEGORY_CHAT, "status snapshot: bytes=%zu", text.len);
chat_event_post(inst, CHAT_EVT_STATUS_REFRESH, (const uint8_t*)strbuf_str(&text), (int)text.len);
strbuf_free(&text);
return;
failed:
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "status snapshot: formatting/allocation failed after %zu bytes", text.len);
strbuf_free(&text);
static const uint8_t error[] = "Unable to collect status: formatting/allocation failed\n";
chat_event_post(inst, CHAT_EVT_STATUS_REFRESH, error, sizeof(error) - 1);
}
/* ── Бинарный список соединений: [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(struct UTUN_INSTANCE* inst) {
if (!inst || !CC(inst) || !CC(inst)->initialized || !inst->connections) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "connection list: chat/connections unavailable"); return;
}
uint16_t count = 0;
struct ll_entry* entry = inst->connections->head;
while (entry && count < 250) { count++; entry = entry->next; }
size_t buf_sz = 2 + (size_t)count * CONN_LIST_ENTRY_SIZE;
uint8_t* buf = (uint8_t*)u_malloc(buf_sz);
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "connection list: cannot allocate %zu bytes", buf_sz); return; }
uint16_t* hdr = (uint16_t*)buf;
*hdr = count;
uint8_t* p = buf + 2;
entry = 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;
if (conn->links) {
if (!conn->links->is_server) flags |= 4;
else flags |= 8;
}
*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(inst, 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(inst, CHAT_EVT_CONN_LIST, buf, (int)buf_sz);
u_free(buf);
}
/* ── Полный текстовый дамп одного соединения ── */
static void collect_conn_metrics(struct UTUN_INSTANCE* inst, uint64_t peer_node_id) {
struct strbuf text = strbuf_new();
if (!inst || !CC(inst) || !CC(inst)->initialized || !inst->connections) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "connection metrics: chat/connections unavailable peer=%016llX",
(unsigned long long)peer_node_id);
if (strbuf_addf(&text, "uTun not initialized\n") < 0) goto failed;
goto post;
}
struct ll_entry* e = queue_find_data_by_index(inst->connections, (const uint8_t*)&peer_node_id);
if (!e) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "connection metrics: peer=%016llX not found", (unsigned long long)peer_node_id);
if (strbuf_addf(&text, "Connection %016llX not found\n", (unsigned long long)peer_node_id) < 0) goto failed;
goto post;
}
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
if (!ce || !ce->conn) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "connection metrics: peer=%016llX has null data", (unsigned long long)peer_node_id);
if (strbuf_addf(&text, "Connection %016llX has null data\n", (unsigned long long)peer_node_id) < 0) goto failed;
goto post;
}
struct ETCP_CONN* conn = ce->conn;
uint64_t my_id = inst->node_id;
char peername[64]; get_node_name(inst, peer_node_id, peername, sizeof(peername));
if (!peername[0]) snprintf(peername, sizeof(peername), "%016llX", (unsigned long long)peer_node_id);
int is_server = conn->links ? conn->links->is_server : -1;
const char* role_str = is_server < 0 ? "none" : (is_server ? "server" : "client");
/* 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; }
if (strbuf_addf(&text,
"=== PEER: %04llX\u2192%04llX [%s] ===\n"
"Role: %s\n"
"Status: %s Links: %d/%d Initialized: %s MTU: %d\n"
"Reinit: %u Reset: %u Tx_state: %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,
role_str,
conn->links_up ? "UP" : "DOWN", links_up_count, link_count,
conn->initialized ? "yes" : "no", conn->mtu,
conn->reinit_count, conn->reset_count, conn->tx_state,
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) < 0) goto failed;
if (conn->input_queue && strbuf_addf(&text, "input_q: %uB/%dp ",
(unsigned)queue_total_bytes(conn->input_queue), queue_entry_count(conn->input_queue)) < 0) goto failed;
if (conn->input_send_q && strbuf_addf(&text, "send_q: %uB/%dp ",
(unsigned)queue_total_bytes(conn->input_send_q), queue_entry_count(conn->input_send_q)) < 0) goto failed;
if (conn->input_wait_ack && strbuf_addf(&text, "wait_ack: %uB/%dp\n",
(unsigned)queue_total_bytes(conn->input_wait_ack), queue_entry_count(conn->input_wait_ack)) < 0) goto failed;
unsigned ack_bytes = 0, ack_packets = 0;
for (struct ETCP_LINK* link = conn->links; link; link = link->next) {
ack_bytes += (unsigned)queue_total_bytes(link->ack_q); ack_packets += queue_entry_count(link->ack_q);
}
if (strbuf_addf(&text, "ack_q: %uB/%up ", ack_bytes, ack_packets) < 0) goto failed;
if (conn->recv_q && strbuf_addf(&text, "recv_q: %uB/%dp ",
(unsigned)queue_total_bytes(conn->recv_q), queue_entry_count(conn->recv_q)) < 0) goto failed;
if (conn->output_queue && strbuf_addf(&text, "out_q: %uB/%dp\n",
(unsigned)queue_total_bytes(conn->output_queue), queue_entry_count(conn->output_queue)) < 0) goto failed;
if (strbuf_addf(&text, "\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) < 0) goto failed;
/* Normalizer */
if (conn->normalizer) {
struct PKTNORM* pn = conn->normalizer;
if (strbuf_addf(&text,
"\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) < 0) goto failed;
}
/* Links */
int link_idx = 0;
struct ETCP_LINK* link = conn->links;
while (link) {
const char* is_tcp_str = link->is_tcp ? "TCP" : "UDP";
ip_str_t local = {.str = "stcp"}, remote = sockaddr_storage_to_str(&link->remote_addr);
if (link->conn) local = sockaddr_storage_to_str(link->conn->interface_addr.ss_family ?
&link->conn->interface_addr : &link->conn->local_addr);
if (strbuf_addf(&text,
"\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, link_address_profile_text(link).text,
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) < 0) goto failed;
link = link->next; link_idx++;
}
post:
DEBUG_TRACE(DEBUG_CATEGORY_CHAT, "connection metrics: peer=%016llX bytes=%zu", (unsigned long long)peer_node_id, text.len);
chat_event_post(inst, CHAT_EVT_CONN_METRICS, (const uint8_t*)strbuf_str(&text), (int)text.len);
strbuf_free(&text);
return;
failed:
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "connection metrics: peer=%016llX formatting/allocation failed after %zu bytes",
(unsigned long long)peer_node_id, text.len);
strbuf_free(&text);
static const uint8_t error[] = "Unable to collect connection metrics: formatting/allocation failed\n";
chat_event_post(inst, CHAT_EVT_CONN_METRICS, error, sizeof(error) - 1);
}
void chat_core_collect_status_trampoline(void* arg) {
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)arg;
chat_core_collect_status(inst);
}
/* Снимок снимается в потоке ядра; GUI получает только готовый процент. */
void chat_core_collect_cpu_load_trampoline(void* arg) {
struct UTUN_INSTANCE* inst = arg;
if (!inst || !inst->ua) { DEBUG_WARN(DEBUG_CATEGORY_SYS, "CPU load request: core unavailable"); return; }
struct uasync_load load;
uint8_t data[2] = {0, 0};
if (uasync_get_load(inst->ua, &load) == ERR_OK && load.valid) {
data[0] = 1; data[1] = (uint8_t)(load.average_percent + 0.5);
}
DEBUG_TRACE(DEBUG_CATEGORY_SYS, "CPU load GUI response: valid=%u average=%u%%", data[0], data[1]);
chat_event_post(inst, CHAT_EVT_CPU_LOAD, data, sizeof(data));
}
void chat_core_collect_conn_list_trampoline(void* arg) {
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)arg;
collect_conn_list(inst);
}
void chat_core_collect_conn_metrics_trampoline(void* arg) {
struct chat_node_arg* a = (struct chat_node_arg*)arg;
if (!a) return;
uint64_t peer_id = a->node_id;
collect_conn_metrics(a->inst, peer_id);
u_free(arg);
}
/* Диагностика выбранного участника собирается целиком в uasync; wire описан в chat_member.h. */
#if CHAT_MEMBER_DETAIL_MAX_HOPS != MAX_HOPS
#error "member detail hop limit must match routing"
#endif
/* Проверяем размеры, адресата и все строки до изменения состояния GUI. */
int chat_core_parse_member_detail(const uint8_t* data, size_t len, struct chat_member_detail_view* out) {
if (!data || !out || len < CHAT_MEMBER_DETAIL_HEADER_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member detail: missing header bytes=%zu", len); return -1;
}
struct chat_member_detail_view v = {0};
v.request_id = mf_get64(data); v.group_id = mf_get64(data + 8); v.node_id = mf_get64(data + 16);
v.result = data[24]; v.transport_flags = data[25];
v.own_tcp_active = (uint16_t)data[26] | ((uint16_t)data[27] << 8);
v.hop_count = data[28]; v.link_count = data[29]; v.sock_count = data[30];
size_t expected = CHAT_MEMBER_DETAIL_HEADER_SIZE + (size_t)v.hop_count * 8 +
(size_t)v.link_count * CHAT_MEMBER_DETAIL_LINK_SIZE + (size_t)v.sock_count * CHAT_MEMBER_DETAIL_SOCK_SIZE;
if (!v.request_id || !v.group_id || !v.node_id || v.result > CHAT_MEMBER_DETAIL_ERROR || v.transport_flags > 7 ||
v.hop_count > CHAT_MEMBER_DETAIL_MAX_HOPS || v.link_count > CHAT_MEMBER_DETAIL_MAX_LINKS ||
v.sock_count > CHAT_MEMBER_DETAIL_MAX_SOCKS || expected != len ||
(v.result && (v.transport_flags || v.own_tcp_active || v.hop_count || v.link_count || v.sock_count))) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member detail: invalid packet request=%llu group=%016llx node=%016llx bytes=%zu expected=%zu",
(unsigned long long)v.request_id, (unsigned long long)v.group_id, (unsigned long long)v.node_id, len, expected);
return -1;
}
v.hops = data + CHAT_MEMBER_DETAIL_HEADER_SIZE; v.links = v.hops + v.hop_count * 8;
v.socks = v.links + v.link_count * CHAT_MEMBER_DETAIL_LINK_SIZE;
for (int i = 0; i < v.hop_count; i++) if (!mf_get64(v.hops + i * 8)) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member detail: zero hop index=%d", i); return -1;
}
for (int i = 0; i < v.link_count; i++) {
const uint8_t* link = v.links + i * CHAT_MEMBER_DETAIL_LINK_SIZE;
if (!memchr(link + 43, 0, 20) || link[0] > 1 || link[3] > 1 || link[4] > 1 || link[63] > 1 ||
(link[5] && link[5] != 4 && link[5] != 6) || (link[24] && link[24] != 4 && link[24] != 6)) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member detail: invalid link index=%d", i); return -1;
}
}
for (int i = 0; i < v.sock_count; i++) if (!memchr(v.socks + i * CHAT_MEMBER_DETAIL_SOCK_SIZE + 10, 0, 38)) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member detail: unterminated interface index=%d", i); return -1;
}
*out = v;
return 0;
}
/* Повторяет Qt: name.lastIndexOf("_v") → name.mid(3, idx - 3) */
static void sock_ifname(const char* name, char* out, size_t out_sz) {
out[0] = '\0';
if (!name || !name[0]) return;
const char* last = NULL;
for (const char* q = name; (q = strstr(q, "_v")) != NULL; q++) last = q;
if (!last) return;
int idx = (int)(last - name);
if (idx <= 3) return;
int len = idx - 3;
if (len >= (int)out_sz) len = (int)out_sz - 1;
memcpy(out, name + 3, len);
out[len] = '\0';
}
static void collect_member_detail(const struct chat_member_detail_req* req) {
struct UTUN_INSTANCE* inst = req->inst;
uint64_t node_id = req->node_id;
uint8_t buf[CHAT_MEMBER_DETAIL_MAX_SIZE] = {0};
mf_put64(buf, req->request_id); mf_put64(buf + 8, req->group_id); mf_put64(buf + 16, node_id);
struct TOPO_GROUP* group = inst && inst->topo_groups ? topo_groups_find(inst->topo_groups, req->group_id) : NULL;
if (!req->request_id || !req->group_id || !node_id) buf[24] = CHAT_MEMBER_DETAIL_INVALID;
else if (!inst || !CC(inst) || !CC(inst)->initialized || !group || group->group_type != TOPO_GROUP_TYPE_CHAT)
buf[24] = CHAT_MEMBER_DETAIL_UNAVAILABLE;
if (!buf[24]) {
char table[80], sql[160]; peers_table_name(group->channel_id, table, sizeof(table));
snprintf(sql, sizeof(sql), "SELECT 1 FROM \"%s\" WHERE node_id=?", table);
sqlite3_stmt* st = NULL;
int rc = sqlite3_prepare_v2(CC(inst)->db, sql, -1, &st, NULL);
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
if (rc == SQLITE_OK) rc = sqlite3_step(st);
if (rc == SQLITE_DONE) buf[24] = CHAT_MEMBER_DETAIL_NOT_MEMBER;
else if (rc != SQLITE_ROW) {
buf[24] = CHAT_MEMBER_DETAIL_ERROR;
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member detail: query request=%llu rc=%d: %s",
(unsigned long long)req->request_id, rc, sqlite3_errmsg(CC(inst)->db));
}
sqlite3_finalize(st);
}
if (buf[24]) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "member detail: rejected request=%llu group=%016llx node=%016llx result=%u",
(unsigned long long)req->request_id, (unsigned long long)req->group_id, (unsigned long long)node_id, buf[24]);
chat_event_post(inst, CHAT_EVT_MEMBER_DETAIL, buf, CHAT_MEMBER_DETAIL_HEADER_SIZE); return;
}
uint8_t flags = 0;
struct ETCP_CONN* conn = instance_find_conn(inst, node_id);
if (conn) {
flags |= 1;
if (conn->links_up) flags |= 2;
if (conn->initialized) flags |= 4;
}
uint16_t own_tcp_active = 0;
if (node_id == inst->node_id) {
if (inst->connections) {
for (struct ll_entry* e = inst->connections->head; e; e = e->next) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
if (ce && ce->conn && ce->conn->initialized) {
for (struct ETCP_LINK* lk = ce->conn->links; lk; lk = lk->next)
if (lk->is_tcp) { own_tcp_active++; break; }
}
}
}
if (inst->tcp_connections) {
for (struct ll_entry* e = inst->tcp_connections->head; e; e = e->next) {
struct tcp_conn_entry* te = (struct tcp_conn_entry*)e->data;
if (te && te->etcp_conn && te->etcp_conn->initialized) own_tcp_active++;
}
}
}
uint8_t link_count = 0;
for (struct ETCP_LINK* lk = conn ? conn->links : NULL; lk && link_count < CHAT_MEMBER_DETAIL_MAX_LINKS; lk = lk->next)
link_count++;
uint8_t sock_count = 0;
for (struct ETCP_SOCKET* s = inst->etcp_sockets; s && sock_count < CHAT_MEMBER_DETAIL_MAX_SOCKS; s = s->next)
sock_count++;
struct TOPO_GROUP_NODE* node = topo_node_find_by_id(group, node_id);
uint8_t hop_count = 0;
uint64_t* hops = topo_node_best_hop_list(node, &hop_count, NULL);
buf[25] = flags; buf[26] = (uint8_t)own_tcp_active; buf[27] = (uint8_t)(own_tcp_active >> 8);
buf[28] = hop_count; buf[29] = link_count; buf[30] = sock_count;
uint8_t* p = buf + CHAT_MEMBER_DETAIL_HEADER_SIZE;
for (int i = 0; i < hop_count; i++, p += 8) mf_put64(p, hops[i]);
for (struct ETCP_LINK* lk = conn ? conn->links : NULL; lk && link_count; lk = lk->next, link_count--) {
*p++ = lk->is_tcp ? 1 : 0;
*p++ = lk->link_state;
*p++ = lk->link_status;
*p++ = (lk->is_tcp && lk->tcp_link && stcp_link_is_ready(lk->tcp_link)) ? 1 : 0;
*p++ = lk->is_server;
{ uint8_t lf = 0; uint8_t la[16] = {0}; uint16_t lp = 0;
if (lk->conn) {
const struct sockaddr_storage* lsa = lk->conn->interface_addr.ss_family ? &lk->conn->interface_addr : &lk->conn->local_addr;
if (lsa->ss_family == AF_INET) {
lf = 4; memcpy(la, &((const struct sockaddr_in*)lsa)->sin_addr, 4); lp = ntohs(((const struct sockaddr_in*)lsa)->sin_port);
} else if (lsa->ss_family == AF_INET6) {
lf = 6; memcpy(la, &((const struct sockaddr_in6*)lsa)->sin6_addr, 16); lp = ntohs(((const struct sockaddr_in6*)lsa)->sin6_port);
}
} else if (lk->is_tcp && lk->tcp_link) {
const struct sockaddr_storage* lsa = stcp_link_get_local_addr(lk->tcp_link);
if (lsa && lsa->ss_family == AF_INET) {
lf = 4; memcpy(la, &((const struct sockaddr_in*)lsa)->sin_addr, 4); lp = ntohs(((const struct sockaddr_in*)lsa)->sin_port);
} else if (lsa && lsa->ss_family == AF_INET6) {
lf = 6; memcpy(la, &((const struct sockaddr_in6*)lsa)->sin6_addr, 16); lp = ntohs(((const struct sockaddr_in6*)lsa)->sin6_port);
}
}
*p++ = lf; memcpy(p, la, 16); p += 16; *p++ = (uint8_t)lp; *p++ = (uint8_t)(lp >> 8);
}
{ uint8_t rf = 0; uint8_t ra[16] = {0}; uint16_t rp = 0;
const struct sockaddr_storage* sa = &lk->remote_addr;
if (sa->ss_family == AF_INET) {
rf = 4; memcpy(ra, &((const struct sockaddr_in*)sa)->sin_addr, 4); rp = ntohs(((const struct sockaddr_in*)sa)->sin_port);
} else if (sa->ss_family == AF_INET6) {
rf = 6; memcpy(ra, &((const struct sockaddr_in6*)sa)->sin6_addr, 16); rp = ntohs(((const struct sockaddr_in6*)sa)->sin6_port);
}
*p++ = rf; memcpy(p, ra, 16); p += 16; *p++ = (uint8_t)rp; *p++ = (uint8_t)(rp >> 8);
}
{ char sn[20] = {0};
if (lk->conn) strncpy(sn, lk->conn->name, 19);
memcpy(p, sn, 20); p += 20;
}
*p++ = lk->reality_set ? 1 : 0;
}
for (struct ETCP_SOCKET* s = inst->etcp_sockets; s && sock_count; s = s->next, sock_count--) {
uint32_t sid = s->sock_id;
uint16_t sp = 0;
const struct sockaddr_storage* sa = s->interface_addr.ss_family ? &s->interface_addr : &s->local_addr;
if (sa->ss_family == AF_INET) sp = ntohs(((const struct sockaddr_in*)sa)->sin_port);
else if (sa->ss_family == AF_INET6) sp = ntohs(((const struct sockaddr_in6*)sa)->sin6_port);
uint16_t lu = 0, ld = 0;
{ struct ll_entry* e = inst->connections ? inst->connections->head : NULL;
for (; e; e = e->next) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
if (!ce || !ce->conn) continue;
for (struct ETCP_LINK* lk = ce->conn->links; lk; lk = lk->next)
if (lk->conn == s) { if (lk->link_state == 3) lu++; else ld++; }
}
}
{ struct ll_entry* e = inst->tcp_connections ? inst->tcp_connections->head : NULL;
for (; e; e = e->next) {
struct tcp_conn_entry* te = (struct tcp_conn_entry*)e->data;
if (!te || !te->etcp_conn) continue;
for (struct ETCP_LINK* lk = te->etcp_conn->links; lk; lk = lk->next)
if (lk->conn == s) { if (lk->link_state == 3) lu++; else ld++; }
}
}
char ifname[38] = {0}; sock_ifname(s->name, ifname, sizeof(ifname));
for (int i = 0; i < 4; i++) *p++ = (uint8_t)(sid >> (8 * i));
*p++ = (uint8_t)sp; *p++ = (uint8_t)(sp >> 8);
*p++ = (uint8_t)lu; *p++ = (uint8_t)(lu >> 8);
*p++ = (uint8_t)ld; *p++ = (uint8_t)(ld >> 8);
memcpy(p, ifname, 38); p += 38;
}
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "member detail: request=%llu group=%016llx node=%016llx hops=%u links=%u socks=%u bytes=%zu",
(unsigned long long)req->request_id, (unsigned long long)req->group_id, (unsigned long long)node_id,
buf[28], buf[29], buf[30], (size_t)(p - buf));
chat_event_post(inst, CHAT_EVT_MEMBER_DETAIL, buf, (int)(p - buf));
}
void chat_core_collect_member_detail_trampoline(void* arg) {
struct chat_member_detail_req* req = arg;
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member detail: null request"); return; }
collect_member_detail(req);
u_free(req);
}