docs(chat): specify complete member snapshots and committed local mute

This commit is contained in:
evgeny committed 2026-10-27 05:38:07 +01:00
1 parent 41b3503398
commit 618c68268c
3 files changed
+58 -40

No files matched your search

+3
View File
@@ -25,6 +25,9 @@ struct strbuf {
size_t len; /* current length (excluding '\0') */
};
/* Размер ограничен UINT32_MAX allocator-ом mem.h;превышение отклоняется до преобразования size_t.
* init предназначен для нового буфера;повторная инициализация живого буфера требует предварительного free. */
/* zero-init on stack: struct strbuf sb = {0}; */
#define strbuf_new() ((struct strbuf){NULL, 0, 0})
+35 -20
View File
@@ -4,6 +4,8 @@
* Используется chatgui (desktop), headless CLI, Android.
* Live-снимки для всех потребителей формирует chat_core_get_member_list() в uasync-потоке.
* Снимок содержит свойства участника и состояние маршрута именно его CHAT-группы.
* Все вызовы — в uasync-потоке. ch_id — канонический десятичный ID1..INT64_MAX.
* В список входят self,placeholders и deleted;deleted показывается offline,self обычно online.
*
* Формат на проводе (ручная сериализация, фиксированный размер 164 байта):
* [0-7] node_id uint64_t LE
@@ -48,29 +50,32 @@ extern "C" {
#define CHAT_MEMBER_DISPLAY_SIZE 164
/* Получить всех мемберов канала. out = u_malloc-массив, count = количество. */
/* Снять полный список:0 — успех,-1 — ошибка/более65535 записей. Обязательные out/count сбрасываются в NULL/0.
* out — u_malloc-массив count*CHAT_MEMBER_DISPLAY_SIZE байт,освобождает caller через u_free;пустой список — NULL/0.
* Порядок:online первыми,затем unsigned node_id по возрастанию. SQL/память не дают частичный успешный результат. */
int chat_core_get_member_list(struct UTUN_INSTANCE* inst, const char* ch_id, uint8_t** out, int* count);
/* Получить одного мембера по node_id. out = буфер размером CHAT_MEMBER_DISPLAY_SIZE. */
/* Снять одного участника:0/-1 (ошибка/нет строки). out — обязательный буфер CHAT_MEMBER_DISPLAY_SIZE байт;
* при отказе не меняется. Формирование полей совпадает с полным списком. */
int chat_core_get_single_member(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id, uint8_t* out);
/* Live-онлайн: маршрутизатор располагает живым путём в группе канала (self всегда онлайн).
Не читает nodes.online — статус не хранится в БД. */
Не читает nodes.online и не проверяет членство/deleted;это отдельная проверка транспорта,self всегда1. NULL/неверный ID дают0. */
int chat_core_member_online(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id);
/* Сериализовать из внутренней структуры в wire-формат */
/* Внутренний снимок:поля далее кодируются вручную,struct-layout в wire не передаётся. */
struct chat_member_display {
uint64_t node_id;
uint8_t online;
uint8_t is_self;
uint8_t flags;
uint16_t rtt;
uint8_t nat_type;
uint8_t storage;
char name[64];
uint64_t avatar_server;
char avatar_file[70];
uint8_t storage_role;
uint64_t node_id; /* ID участника. */
uint8_t online; /* Живой путь/self,сброшен для deleted. */
uint8_t is_self; /* Совпадение с node_id instance. */
uint8_t flags; /* CHAT_MEMBER_FLAG_*;offline не несёт phone known/busy. */
uint16_t rtt; /* Суммарный RTT выбранного пути,0.1мс;UINT16_MAX — неизвестен/насыщен. */
uint8_t nat_type; /* Агрегированный ADDR_TYPE_* адресов узла;255 — адресов нет. */
uint8_t storage; /* Локальный приоритет хранения,отдельно от подписанной роли. */
char name[64]; /* Local nick,имя nodes или0xNNNN;до63 байт+NUL. */
uint64_t avatar_server; /* Сервер файлового аватара,0 — ссылки нет. */
char avatar_file[70];/* Проверенный sha256.json+NUL. */
uint8_t storage_role; /* Полный действующий member с adm_tags.storage=yes и64-байтовой подписью owner-блока. */
uint8_t verified; /* административное подтверждение участника */
uint8_t node_type; /* классификация узла, не состояние маршрута */
uint8_t conn_presence; /* известные виды подключения NCONN_* */
@@ -78,21 +83,30 @@ struct chat_member_display {
uint16_t intermediaries; /* число посредников выбранного живого пути */
};
/* Копирование фиксированного wire164:src/dst обязательны,caller обеспечивает размеры.
* Serialize завершает/дополняет NUL поля name/avatar_file;NULL только логируется. */
void chat_core_serialize_member(const struct chat_member_display* src, uint8_t* dst);
/* Deserialize читает ровно164 доступных байта;0/-1 при NULL,длину/семантику полей проверяет caller. */
int chat_core_deserialize_member(const uint8_t* src, struct chat_member_display* dst);
/* Трамплин для gui_bridge: GUI запрашивает полный список мемберов канала.
* arg = struct chat_member_list_req*. Результат: CHAT_EVT_MEMBER_LIST. */
struct chat_member_list_req { struct UTUN_INSTANCE* inst; char ch_id[64]; };
* arg — u_malloc chat_member_list_req,забирается/освобождается;inst заимствован до выполнения.
* Результат:CHAT_EVT_MEMBER_LIST,только целиком после успешного чтения;ошибка логируется без события.
* Count batch — u16 в порядке байтов хоста,каждая запись — wire164 LE. */
struct chat_member_list_req { struct UTUN_INSTANCE* inst; char ch_id[64]; }; /* Заимствованный inst,канонический ID+NUL. */
void chat_core_request_member_list_trampoline(void* arg);
/* Трамплин для gui_bridge: GUI запрашивает RTT мемберов канала.
* arg = struct chat_member_list_req*. Результат: CHAT_EVT_MEMBER_UPDATED с RTT-обновлениями. */
* arg/владение как у list;это снимок актуального состояния без запуска новых сетевых измерений.
* Результат:по одному CHAT_EVT_MEMBER_UPDATED для каждого участника полученного списка. */
void chat_core_request_member_rtt_trampoline(void* arg);
/* Локальный переключатель рации; каждый запрос меняет актуальное состояние в uasync. */
/* Локальный переключатель рации другого участника:0 — committed,-1 — отказ,чужая транзакция запрещена.
* Снимок готовится до изменения;после radio commit обновлённый флаг приходит MEMBER_UPDATED без нового SQL-чтения.
* Ошибка сохраняет настройку/кэш/звук и не выдаёт успешное событие. */
int chat_core_toggle_member_radio_mute(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id);
struct chat_member_radio_mute_req { struct UTUN_INSTANCE* inst; char ch_id[64]; uint64_t node_id; };
/* Забирает u_malloc-запрос;inst заимствован. Ответ — RADIO_MUTE_RESULT(group,node,success),включая отказ. */
void chat_core_toggle_member_radio_mute_trampoline(void* arg);
/* Подписки свойств/маршрутов → MEMBER_UPDATED; вызываются в uasync при init/destroy чата. */
@@ -113,7 +127,8 @@ struct chat_member_detail_req {
* hop_count:1, link_count:1, sock_count:1; далее hops:8*, links:64*, socks:48*.
* transport_flags: bit0=conn_present, bit1=links_up, bit2=initialized; не доступность в группе.
* hops — список выбранного пути от назначения к первому посреднику; у offline/self пустой.
* Ошибка содержит только заголовок.
* Ошибка содержит только заголовок;INVALID допускает нулевые поля неверного исходного запроса.
* Счётчики own_tcp и ссылок сокета насыщаются на65535;списки ограничены16 элементами каждый.
* link: tcp:1,state:1,status:1,ready:1,server:1,local_family:1,local_addr:16,local_port:2,
* remote_family:1,remote_addr:16,remote_port:2,socket_name:20,reality:1.
* sock: id:4,port:2,links_up:2,links_down:2,ifname:38. Строки завершаются NUL. */
+20 -20
View File
@@ -74,32 +74,32 @@ static void chat_core_collect_status(struct UTUN_INSTANCE* inst) {
struct uasync_load load;
if (uasync_get_load(inst->ua, &load) == ERR_OK && load.valid) {
STATUS_APPEND( "uasync CPU: %.1f%%, average %.1f%% (window %.0f ms, age %.0f ms)\n",
STATUS_APPEND("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);
} else STATUS_APPEND( "uasync CPU: unavailable\n");
} else STATUS_APPEND("uasync CPU: unavailable\n");
struct NTP_TIME* ntp = &inst->ntp;
STATUS_APPEND( "=== NTP ===\n");
STATUS_APPEND( "Enabled: %s\n", ntp->enabled ? "yes" : "no");
STATUS_APPEND( "Synced: %s\n", ntp->synced ? "yes" : "no");
STATUS_APPEND( "Offset: %.1f s\n", ntp->offset_us / 1000000.0);
STATUS_APPEND("=== NTP ===\n");
STATUS_APPEND("Enabled: %s\n", ntp->enabled ? "yes" : "no");
STATUS_APPEND("Synced: %s\n", ntp->synced ? "yes" : "no");
STATUS_APPEND("Offset: %.1f s\n", ntp->offset_us / 1000000.0);
if (ntp->server_count > 0 && ntp->servers) {
STATUS_APPEND( "Servers: %d\n", ntp->server_count);
STATUS_APPEND("Servers: %d\n", ntp->server_count);
for (int i = 0; i < ntp->server_count && ntp->servers[i]; i++)
STATUS_APPEND( " [%d] %s%s\n", i, ntp->servers[i],
STATUS_APPEND(" [%d] %s%s\n", i, ntp->servers[i],
i == ntp->server_current ? " (current)" : "");
}
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;
STATUS_APPEND( "Last sync: %llu sec ago\n", (unsigned long long)ago_sec);
STATUS_APPEND("Last sync: %llu sec ago\n", (unsigned long long)ago_sec);
}
int conn_count = 0;
struct ll_entry* entry = inst->connections ? inst->connections->head : NULL;
while (entry) { conn_count++; entry = entry->next; }
STATUS_APPEND( "=== Connections (%d) ===\n", conn_count);
STATUS_APPEND("=== Connections (%d) ===\n", conn_count);
uint64_t my_id = inst->node_id;
entry = inst->connections ? inst->connections->head : NULL;
@@ -124,11 +124,11 @@ static void chat_core_collect_status(struct UTUN_INSTANCE* inst) {
chat_core_get_node_name(inst, pid, peername, sizeof(peername));
if (peername[0])
STATUS_APPEND( "[%s]→[%s] %s ETCP:%s%s(%dL)\n",
STATUS_APPEND("[%s]→[%s] %s ETCP:%s%s(%dL)\n",
myhex, peerhex, peername,
conn->links_up ? "UP" : "DOWN", init_str, link_count);
else
STATUS_APPEND( "[%s]→[%s] ETCP:%s%s(%dL)\n",
STATUS_APPEND("[%s]→[%s] ETCP:%s%s(%dL)\n",
myhex, peerhex,
conn->links_up ? "UP" : "DOWN", init_str, link_count);
@@ -137,7 +137,7 @@ static void chat_core_collect_status(struct UTUN_INSTANCE* inst) {
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);
STATUS_APPEND( " LINK#%d: %s %s %s NAT=%s%s\n",
STATUS_APPEND(" LINK#%d: %s %s %s NAT=%s%s\n",
link_idx,
link->is_tcp ? "TCP" : "UDP",
link->conn ? link->conn->name : "?",
@@ -273,18 +273,18 @@ static void collect_conn_metrics(struct UTUN_INSTANCE* inst, uint64_t peer_node_
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) STATUS_APPEND( "input_q: %uB/%dp ", (unsigned)queue_total_bytes(conn->input_queue), queue_entry_count(conn->input_queue));
if (conn->input_send_q) STATUS_APPEND( "send_q: %uB/%dp ", (unsigned)queue_total_bytes(conn->input_send_q), queue_entry_count(conn->input_send_q));
if (conn->input_wait_ack) STATUS_APPEND( "wait_ack: %uB/%dp\n", (unsigned)queue_total_bytes(conn->input_wait_ack), queue_entry_count(conn->input_wait_ack));
if (conn->input_queue) STATUS_APPEND("input_q: %uB/%dp ", (unsigned)queue_total_bytes(conn->input_queue), queue_entry_count(conn->input_queue));
if (conn->input_send_q) STATUS_APPEND("send_q: %uB/%dp ", (unsigned)queue_total_bytes(conn->input_send_q), queue_entry_count(conn->input_send_q));
if (conn->input_wait_ack) STATUS_APPEND("wait_ack: %uB/%dp\n", (unsigned)queue_total_bytes(conn->input_wait_ack), queue_entry_count(conn->input_wait_ack));
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);
}
STATUS_APPEND( "ack_q: %uB/%up ", ack_bytes, ack_packets);
if (conn->recv_q) STATUS_APPEND( "recv_q: %uB/%dp ", (unsigned)queue_total_bytes(conn->recv_q), queue_entry_count(conn->recv_q));
if (conn->output_queue) STATUS_APPEND( "out_q: %uB/%dp\n", (unsigned)queue_total_bytes(conn->output_queue), queue_entry_count(conn->output_queue));
STATUS_APPEND("ack_q: %uB/%up ", ack_bytes, ack_packets);
if (conn->recv_q) STATUS_APPEND("recv_q: %uB/%dp ", (unsigned)queue_total_bytes(conn->recv_q), queue_entry_count(conn->recv_q));
if (conn->output_queue) STATUS_APPEND("out_q: %uB/%dp\n", (unsigned)queue_total_bytes(conn->output_queue), queue_entry_count(conn->output_queue));
STATUS_APPEND( "\n--- ACK Debug ---\n"
STATUS_APPEND("\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);