fix(chat): publish complete member snapshots after checked SQL finalization
This commit is contained in:
1 parent
958feb4e75
commit
32a8c1c48b
3 files changed
+63
-58
No files matched your search
@@ -36,6 +36,13 @@
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
uint64_t chat_channel_id(const char* text) {
|
||||
if (!text) return 0;
|
||||
size_t length = strnlen(text, 20);
|
||||
if (!length || length == 20 || text[0] == '0' || strspn(text, "0123456789") != length) return 0;
|
||||
uint64_t id = strtoull(text, NULL, 10); return id <= INT64_MAX ? id : 0;
|
||||
}
|
||||
|
||||
/* Один формат описания для создания и проверки подписи входящего JOIN/invite. */
|
||||
int chat_channel_description_data(const char* channel, const char* name, uint64_t owner,
|
||||
const uint8_t x25519[32], const uint8_t ed25519[32], uint8_t* out, size_t capacity) {
|
||||
@@ -43,8 +50,7 @@ int chat_channel_description_data(const char* channel, const char* name, uint64_
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "channel description: missing argument"); return -1;
|
||||
}
|
||||
size_t channel_len = strnlen(channel, 20), name_len = strnlen(name, 256);
|
||||
if (!channel_len || channel_len == 20 || channel[0] == '0' || strspn(channel, "0123456789") != channel_len ||
|
||||
strtoull(channel, NULL, 10) > INT64_MAX || name_len == 256 || capacity < channel_len + name_len + 74) {
|
||||
if (!chat_channel_id(channel) || name_len == 256 || capacity < channel_len + name_len + 74) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "channel description: invalid channel/name lengths=%zu/%zu capacity=%zu",
|
||||
channel_len, name_len, capacity); return -1;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,10 @@ void sanitize_ch_id(const char* ch_id, char* out, size_t out_sz);
|
||||
void msg_table_name(const char* ch_id, char* buf, size_t sz);
|
||||
void peers_table_name(const char* ch_id, char* buf, size_t sz);
|
||||
|
||||
/* Тихая проверка канонического десятичного ID1..INT64_MAX;0 — отказ,NULL также недопустим.
|
||||
* Использующий модуль логирует ошибку с контекстом своей операции. */
|
||||
uint64_t chat_channel_id(const char* text);
|
||||
|
||||
/* Байты подписи описания:канонический ID+NUL,имя<=255байт+NUL,owner native-u64,X25519/Ed25519 public[32].
|
||||
* Возвращает длину/-1;аргументы обязательны,при отказе out не меняется. Общий формат create/JOIN/invite. */
|
||||
int chat_channel_description_data(const char* channel, const char* name, uint64_t owner,
|
||||
|
||||
+51
-56
@@ -25,8 +25,10 @@
|
||||
/* ─── live-онлайн: тот же живой путь, которым пользуется маршрутизатор ─── */
|
||||
|
||||
int chat_core_member_online(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
if (!cc || !cc->inst || !ch_id || !ch_id[0]) return 0;
|
||||
struct chat_core_ctx* cc = inst ? CC(inst) : NULL;
|
||||
if (!cc || !cc->inst || !chat_channel_id(ch_id)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "member online: inactive instance or invalid channel"); return 0;
|
||||
}
|
||||
if (node_id == cc->my_node_id) return 1; /* self всегда онлайн (local_node вне group->nodes) */
|
||||
if (!cc->inst->topo_groups) return 0;
|
||||
uint64_t group_id = strtoull(ch_id, NULL, 10);
|
||||
@@ -87,6 +89,7 @@ static uint8_t parse_adm_tags_flags(const char* adm_tags) {
|
||||
/* ─── сериализация (struct → wire) ─── */
|
||||
|
||||
void chat_core_serialize_member(const struct chat_member_display* src, uint8_t* dst) {
|
||||
if (!src || !dst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member serialization: missing argument"); return; }
|
||||
mf_put64(dst, src->node_id);
|
||||
dst[8] = src->online;
|
||||
dst[9] = src->is_self;
|
||||
@@ -98,7 +101,7 @@ void chat_core_serialize_member(const struct chat_member_display* src, uint8_t*
|
||||
strncpy((char*)dst + 15, src->name, sizeof(src->name) - 1);
|
||||
dst[78] = 0;
|
||||
mf_put64(dst + 79, src->avatar_server);
|
||||
memcpy(dst + 87, src->avatar_file, 70);
|
||||
strncpy((char*)dst + 87, src->avatar_file, 69); dst[156] = 0;
|
||||
dst[157] = src->storage_role;
|
||||
dst[158] = src->verified; dst[159] = src->node_type;
|
||||
dst[160] = src->conn_presence; dst[161] = src->conn_up;
|
||||
@@ -108,7 +111,7 @@ void chat_core_serialize_member(const struct chat_member_display* src, uint8_t*
|
||||
}
|
||||
|
||||
int chat_core_deserialize_member(const uint8_t* src, struct chat_member_display* dst) {
|
||||
if (!src || !dst) return -1;
|
||||
if (!src || !dst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member deserialization: missing argument"); return -1; }
|
||||
dst->node_id = mf_get64(src);
|
||||
dst->online = src[8];
|
||||
dst->is_self = src[9];
|
||||
@@ -186,11 +189,17 @@ static void member_from_row(struct UTUN_INSTANCE* inst, const char* ch_id, sqlit
|
||||
else snprintf(m->name, sizeof(m->name), "0x%04X", (unsigned)(m->node_id & 0xFFFF));
|
||||
}
|
||||
|
||||
static int member_compare(const void* left, const void* right) {
|
||||
const struct chat_member_display *a = left, *b = right;
|
||||
if (a->online != b->online) return a->online ? -1 : 1;
|
||||
return a->node_id < b->node_id ? -1 : a->node_id > b->node_id;
|
||||
}
|
||||
|
||||
static int member_query(struct UTUN_INSTANCE* inst, const char* ch_id, int single, uint64_t node_id,
|
||||
uint8_t** out, int* count) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
struct chat_core_ctx* cc = inst ? CC(inst) : NULL;
|
||||
*out = NULL; *count = 0;
|
||||
if (!cc || !cc->initialized || !ch_id || !ch_id[0] || !cc->db || !cc->inst) {
|
||||
if (!cc || !cc->initialized || !chat_channel_id(ch_id) || !cc->db || !cc->inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member snapshot: invalid instance/channel"); return -1;
|
||||
}
|
||||
char table[80]; peers_table_name(ch_id, table, sizeof(table));
|
||||
@@ -206,38 +215,34 @@ static int member_query(struct UTUN_INSTANCE* inst, const char* ch_id, int singl
|
||||
sqlite3_stmt* st = NULL;
|
||||
int rc = sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK && single) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member snapshot: query ch=%s rc=%d: %s", ch_id, rc, sqlite3_errmsg(cc->db));
|
||||
sqlite3_finalize(st); return -1;
|
||||
}
|
||||
struct chat_member_display* members = NULL;
|
||||
int cap = 0, cnt = 0;
|
||||
if (rc != SQLITE_OK) goto done;
|
||||
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
if (cnt == UINT16_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member snapshot: channel exceeds wire member limit ch=%s limit=%u", ch_id, UINT16_MAX);
|
||||
rc = SQLITE_TOOBIG; break;
|
||||
}
|
||||
if (cnt == cap) {
|
||||
cap = cap ? cap * 2 : (single ? 1 : 32);
|
||||
cap = cap ? (cap > UINT16_MAX / 2 ? UINT16_MAX : cap * 2) : (single ? 1 : 32);
|
||||
struct chat_member_display* tmp = u_realloc(members, (size_t)cap * sizeof(*members));
|
||||
if (!tmp) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member snapshot: allocation ch=%s count=%d", ch_id, cnt);
|
||||
u_free(members); sqlite3_finalize(st); return -1;
|
||||
rc = SQLITE_NOMEM; break;
|
||||
}
|
||||
members = tmp;
|
||||
}
|
||||
member_from_row(inst, ch_id, st, &members[cnt++]);
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member snapshot: read ch=%s rc=%d: %s", ch_id, rc, sqlite3_errmsg(cc->db));
|
||||
u_free(members); return -1;
|
||||
}
|
||||
for (int i = 1; i < cnt; i++) {
|
||||
struct chat_member_display key = members[i];
|
||||
int j = i - 1;
|
||||
while (j >= 0 && (members[j].online < key.online ||
|
||||
(members[j].online == key.online && members[j].node_id > key.node_id))) {
|
||||
members[j + 1] = members[j]; j--;
|
||||
done:
|
||||
{
|
||||
int final = sqlite3_finalize(st);
|
||||
if (rc != SQLITE_DONE || final != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member snapshot: read ch=%s rc=%d finalize=%d count=%d: %s",
|
||||
ch_id, rc, final, cnt, sqlite3_errmsg(cc->db)); u_free(members); return -1;
|
||||
}
|
||||
members[j + 1] = key;
|
||||
}
|
||||
if (cnt > 1) qsort(members, (size_t)cnt, sizeof(*members), member_compare);
|
||||
if (cnt) {
|
||||
*out = u_malloc((size_t)cnt * CHAT_MEMBER_DISPLAY_SIZE);
|
||||
if (!*out) {
|
||||
@@ -300,7 +305,9 @@ void chat_core_toggle_member_radio_mute_trampoline(void* arg) {
|
||||
|
||||
void chat_core_request_member_list_trampoline(void* arg) {
|
||||
struct chat_member_list_req* req = (struct chat_member_list_req*)arg;
|
||||
if (!req || !CC(req->inst) || !CC(req->inst)->initialized) { u_free(arg); return; }
|
||||
if (!req || !req->inst || !CC(req->inst) || !CC(req->inst)->initialized || !chat_channel_id(req->ch_id)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "member list: stopped chat/invalid request"); u_free(arg); return;
|
||||
}
|
||||
struct UTUN_INSTANCE* inst = req->inst;
|
||||
char ch_id[64]; strncpy(ch_id, req->ch_id, sizeof(ch_id) - 1); ch_id[sizeof(ch_id) - 1] = '\0';
|
||||
u_free(arg);
|
||||
@@ -335,7 +342,9 @@ void chat_core_request_member_list_trampoline(void* arg) {
|
||||
|
||||
void chat_core_request_member_rtt_trampoline(void* arg) {
|
||||
struct chat_member_list_req* req = (struct chat_member_list_req*)arg;
|
||||
if (!req || !CC(req->inst) || !CC(req->inst)->initialized) { u_free(arg); return; }
|
||||
if (!req || !req->inst || !CC(req->inst) || !CC(req->inst)->initialized || !chat_channel_id(req->ch_id)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "member RTT: stopped chat/invalid request"); u_free(arg); return;
|
||||
}
|
||||
struct UTUN_INSTANCE* inst = req->inst;
|
||||
char ch_id[64]; strncpy(ch_id, req->ch_id, sizeof(ch_id) - 1); ch_id[sizeof(ch_id) - 1] = '\0';
|
||||
u_free(arg);
|
||||
@@ -347,7 +356,9 @@ void chat_core_request_member_rtt_trampoline(void* arg) {
|
||||
size_t ch_len = strlen(ch_id);
|
||||
size_t evt_sz = 1 + ch_len + 1 + CHAT_MEMBER_DISPLAY_SIZE;
|
||||
uint8_t* evt = u_malloc(evt_sz);
|
||||
if (!evt) { u_free(members); return; }
|
||||
if (!evt) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member RTT: event allocation failed ch=%s count=%d", ch_id, count); u_free(members); return;
|
||||
}
|
||||
|
||||
/* шлём мемберов по одному (RTT мог измениться у каждого) */
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -366,36 +377,20 @@ void chat_core_request_member_rtt_trampoline(void* arg) {
|
||||
|
||||
static void on_member_props_changed(uint64_t node_id, const char* adm_tags, const char* channel_id, void* arg) {
|
||||
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)arg;
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
struct chat_core_ctx* cc = inst ? CC(inst) : NULL;
|
||||
(void)adm_tags;
|
||||
if (!cc || !cc->initialized || !cc->inst || !cc->db || !channel_id || !channel_id[0]) return;
|
||||
|
||||
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
||||
char sql[256]; snprintf(sql, sizeof(sql), "SELECT 1 FROM \"%s\" WHERE node_id=?", peers_tbl);
|
||||
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
sqlite3_finalize(st);
|
||||
size_t cl = strlen(channel_id);
|
||||
uint8_t evt[1 + 64 + 1 + CHAT_MEMBER_DISPLAY_SIZE];
|
||||
evt[0] = (uint8_t)cl;
|
||||
memcpy(evt + 1, channel_id, cl);
|
||||
evt[1 + cl] = 1;
|
||||
if (chat_core_get_single_member(inst, channel_id, node_id, evt + 1 + cl + 1) == 0) {
|
||||
const uint8_t* member = evt + 1 + cl + 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "chat_member: update ch=%s node=%016llx online=%u self=%u radio=%u",
|
||||
channel_id, (unsigned long long)node_id, member[8], member[9], !!(member[10] & CHAT_MEMBER_FLAG_RADIO));
|
||||
chat_event_post(inst, CHAT_EVT_MEMBER_UPDATED, evt, 1 + (int)cl + 1 + CHAT_MEMBER_DISPLAY_SIZE);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "chat_member: cannot build update ch=%s node=%016llx",
|
||||
channel_id, (unsigned long long)node_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
}
|
||||
if (!cc || !cc->initialized) return;
|
||||
if (!chat_channel_id(channel_id)) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "member update: invalid channel"); return; }
|
||||
uint8_t* wire = NULL; int count = 0;
|
||||
if (member_query(inst, channel_id, 1, node_id, &wire, &count) < 0) return;
|
||||
if (!count) { u_free(wire); return; }
|
||||
size_t cl = strlen(channel_id); uint8_t evt[1 + 64 + 1 + CHAT_MEMBER_DISPLAY_SIZE];
|
||||
evt[0] = (uint8_t)cl; memcpy(evt + 1, channel_id, cl); evt[1 + cl] = 1;
|
||||
memcpy(evt + cl + 2, wire, CHAT_MEMBER_DISPLAY_SIZE); u_free(wire);
|
||||
const uint8_t* member = evt + cl + 2;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "chat_member: update ch=%s node=%016llx online=%u self=%u radio=%u",
|
||||
channel_id, (unsigned long long)node_id, member[8], member[9], !!(member[10] & CHAT_MEMBER_FLAG_RADIO));
|
||||
chat_event_post(inst, CHAT_EVT_MEMBER_UPDATED, evt, (int)cl + 2 + CHAT_MEMBER_DISPLAY_SIZE);
|
||||
}
|
||||
|
||||
static void on_member_route_changed(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* node, void* arg) {
|
||||
|
||||
Reference in new issue
Block a user