diff --git a/doc/tasks.md b/doc/tasks.md index ff120485..ef273047 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -13,6 +13,14 @@ канонические данные набора эмодзи и ответа, формат неизменяемого poll-сообщения (single/multiple, allow_custom_answer), проверку и подсчёт результатов при получении исходного сообщения; подключить headless, desktop и Android интерфейсы. + План продолжения: + 1. Сохранить индексируемый message_id в msg_, вернуть его через API сообщений. + 2. Добавить общий каталог эмодзи и канонические кодеки poll/answer/reactions; + single/multiple и allow_custom_answer проверяются ядром. + 3. Добавить снимок результатов, список голосовавших и собственные состояния; + неизвестное сообщение откладывает учёт ответа, получение сообщения обновляет представление. + 4. Подключить единый командный API к CLI, desktop и Android; команды выполняются в uasync. + 5. Прогнать проверки кодеков и сетевые сценарии, собрать интерфейсы и выполнить check.sh. [ ] **tcp_proxy_full: проверить аномальные значения elapsed в отчёте** — 06.10.2026: `/tmp/utun3-check.log` при успешной передаче показывает длительности вроде diff --git a/lib/strbuf.c b/lib/strbuf.c index 1a066dba..52c27137 100644 --- a/lib/strbuf.c +++ b/lib/strbuf.c @@ -3,6 +3,7 @@ */ #include "strbuf.h" #include "mem.h" +#include "debug_config.h" #include #include #include @@ -69,6 +70,29 @@ void strbuf_addc(struct strbuf *sb, char c) { sb->buf[sb->len] = '\0'; } +/* Экранирует ASCII-управляющие байты; UTF-8 сохраняется без преобразования. */ +int strbuf_add_json_string(struct strbuf* sb, const char* text) { + if (!sb || strbuf_addf(sb, "\"") < 0) goto failed; + const unsigned char* p = (const unsigned char*)(text ? text : ""); + while (*p) { + int rc; + if (*p == '"' || *p == '\\') rc = strbuf_addf(sb, "\\%c", *p); + else if (*p < 32) rc = strbuf_addf(sb, "\\u%04x", *p); + else { + const unsigned char* start = p; + while (*p >= 32 && *p != '"' && *p != '\\') p++; + rc = strbuf_addf(sb, "%.*s", (int)(p - start), start); + if (rc < 0) goto failed; + continue; + } + if (rc < 0) goto failed; + p++; + } + if (strbuf_addf(sb, "\"") >= 0) return 0; +failed: + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "strbuf: JSON string allocation failed"); return -1; +} + void strbuf_adds(struct strbuf *sb, const char *s) { if (!s) return; size_t slen = strlen(s); diff --git a/lib/strbuf.h b/lib/strbuf.h index 48946e99..99ba569e 100644 --- a/lib/strbuf.h +++ b/lib/strbuf.h @@ -41,6 +41,9 @@ void strbuf_reset(struct strbuf *sb); int strbuf_addf(struct strbuf *sb, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +/* Добавить JSON-строку с кавычками и полным экранированием; NULL означает пустую строку. 0/-1. */ +int strbuf_add_json_string(struct strbuf* sb, const char* text); + void strbuf_addc(struct strbuf *sb, char c); void strbuf_adds(struct strbuf *sb, const char *s); diff --git a/src/chat/db_sync.c b/src/chat/db_sync.c index 447936a0..a94e3980 100644 --- a/src/chat/db_sync.c +++ b/src/chat/db_sync.c @@ -7,6 +7,7 @@ #include "../routing_layer/topo_group.h" #include "../routing_layer/topo_node_sqlite.h" #include "member_sync.h" +#include "chat_feedback.h" #include "../transport_layer/etcp_dump.h" #include "../transport_layer/secure_channel.h" #include "../../lib/debug_config.h" @@ -562,6 +563,9 @@ static int db_record_insert(struct DB_SYNC_INSTANCE* si, sqlite3_stmt* stmt; int rc; + uint8_t message_id[32]; + if (chat_message_id(si->group_id, ts, author_sig, message_id) < 0) return -1; + // Verify author signature (mandatory) if (db_verify_author_sig(si, ts, author_node_id, json, jlen, author_sig) != 0) { sqlite3_stmt* del; @@ -598,8 +602,8 @@ static int db_record_insert(struct DB_SYNC_INSTANCE* si, if (si_prep(si, &stmt, "INSERT INTO \"%s\"" " (timestamp,node_id,id,chain_hash,flags,data," - " author_signature,local_attrs,delivered_peers,delivery_chain)" - " VALUES (?,?,?,?,0,?,?,?,0,'')") != SQLITE_OK) + " author_signature,local_attrs,delivered_peers,delivery_chain,message_id)" + " VALUES (?,?,?,?,0,?,?,?,0,'',?)") != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "db_record_insert INSERT prep: %s", sqlite3_errmsg(db)); sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL); return -1; } sqlite3_bind_int64(stmt, 1, (sqlite3_int64)ts); sqlite3_bind_int64(stmt, 2, (sqlite3_int64)author_node_id); @@ -610,6 +614,7 @@ static int db_record_insert(struct DB_SYNC_INSTANCE* si, sqlite3_bind_blob(stmt, 6, author_sig, DB_SIG_SIZE, SQLITE_STATIC); if (local_attrs && local_attrs[0]) sqlite3_bind_text(stmt, 7, local_attrs, -1, SQLITE_STATIC); else sqlite3_bind_text(stmt, 7, "", -1, SQLITE_STATIC); + sqlite3_bind_blob(stmt, 8, message_id, sizeof(message_id), SQLITE_STATIC); rc = sqlite3_step(stmt); sqlite3_finalize(stmt); if (rc != SQLITE_DONE) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "INSERT: %s", sqlite3_errmsg(db)); sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL); return -1; } @@ -1707,7 +1712,7 @@ struct DB_SYNC_INSTANCE* db_sync_instance_add(struct UTUN_INSTANCE* inst, const // Create table — PK is (timestamp, node_id), author_signature NOT NULL { - char sql[512]; + char sql[768]; snprintf(sql, sizeof(sql), "CREATE TABLE IF NOT EXISTS \"%s\" (" " timestamp INTEGER NOT NULL," @@ -1717,6 +1722,7 @@ struct DB_SYNC_INSTANCE* db_sync_instance_add(struct UTUN_INSTANCE* inst, const " flags INTEGER NOT NULL DEFAULT 0," " data BLOB," " author_signature BLOB NOT NULL," + " message_id BLOB NOT NULL UNIQUE CHECK(length(message_id)=32)," " local_attrs TEXT DEFAULT ''," " delivered_peers INTEGER NOT NULL DEFAULT 0," " delivery_chain TEXT NOT NULL DEFAULT '',"