diff --git a/src/chat/chat_channel.c b/src/chat/chat_channel.c index a67d8462..0d53e8ac 100644 --- a/src/chat/chat_channel.c +++ b/src/chat/chat_channel.c @@ -36,6 +36,13 @@ #include #include +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; } diff --git a/src/chat/chat_core_priv.h b/src/chat/chat_core_priv.h index b5ebb6af..edfc97bb 100644 --- a/src/chat/chat_core_priv.h +++ b/src/chat/chat_core_priv.h @@ -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, diff --git a/src/chat/chat_member.c b/src/chat/chat_member.c index 151bc171..30f639fa 100644 --- a/src/chat/chat_member.c +++ b/src/chat/chat_member.c @@ -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) {