diff --git a/src/chat/chat_core.h b/src/chat/chat_core.h index bd03647a..d39c70a5 100644 --- a/src/chat/chat_core.h +++ b/src/chat/chat_core.h @@ -66,13 +66,14 @@ struct chat_msg_submit { int height; /* видео: высота */ uint8_t* data; /* содержимое сообщения, заимствовано обычным submit */ uint32_t data_len; /* размер data в байтах */ - uint64_t timestamp; /* входное время; отправка назначает новый timestamp через db_sync */ + uint64_t timestamp; /* успешная отправка возвращает назначенное время */ + uint8_t message_id[32]; /* успешная обычная отправка возвращает общий ID */ uint64_t reply_to_ts; /* 0 = не ответ */ uint64_t reply_to_node_id; /* node_id автора исходного сообщения */ }; -/* Подписать, сохранить и разослать сообщение. req/data остаются у вызывающего. */ -void chat_core_submit_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req); +/* Подписать, сохранить и разослать сообщение. req/data остаются у вызывающего. 0/-1. */ +int chat_core_submit_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req); /* Отправка медиа-сообщения (вложение): src->dest (copy) или видео (transcode). */ void chat_core_submit_media_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req); diff --git a/src/chat/chat_msg.c b/src/chat/chat_msg.c index b531dd70..f5a5e8b3 100644 --- a/src/chat/chat_msg.c +++ b/src/chat/chat_msg.c @@ -6,6 +6,7 @@ #include "chat_core_priv.h" #include "chat_event.h" +#include "chat_feedback.h" #include "chat_setting.h" #include "chat_whisper.h" @@ -71,45 +72,53 @@ static int msg_get_prev_chain_hash(struct chat_core_ctx* cc, const char* tbl, ui sqlite3_finalize(st); return 0; } -void chat_core_submit_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req) { - struct chat_core_ctx* cc = CC(inst); - if (!cc || !cc->initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: submit before init", CC_ID); return; } - if (!req) return; - - DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: SUBMIT ch=%s ct=%s len=%u", - CC_ID, req->channel_id, req->content_type, req->data_len); - +int chat_core_submit_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req) { + struct chat_core_ctx* cc = inst ? CC(inst) : NULL; + if (!cc || !cc->initialized || !req || (req->data_len && !req->data) || req->data_len > 4000) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid message submission", CC_ID); return -1; + } + memset(req->message_id, 0, sizeof(req->message_id)); struct DB_SYNC_INSTANCE* si = si_find(inst, req->channel_id); - if (!si) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: no db_sync instance for ch=%s", CC_ID, req->channel_id); return; } + if (!si) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: no history for ch=%s", CC_ID, req->channel_id); return -1; } - char json[4096]; - int off = snprintf(json, sizeof(json), - "{\"n\":%llu,\"ch\":\"%s\",\"ct\":\"%s\"", - (unsigned long long)cc->my_node_id, req->channel_id, req->content_type); + /* SQLite формирует JSON без обрезки и с экранированием текста/Unicode. */ + const char* query = req->reply_to_ts + ? "SELECT json_object('n',?1,'ch',?2,'ct',?3,'d',?4,'rt',?5,'rn',?6)" + : "SELECT json_object('n',?1,'ch',?2,'ct',?3,'d',?4)"; + sqlite3_stmt* st = NULL; + if (sqlite3_prepare_v2(cc->db, query, -1, &st, NULL) != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: message JSON: %s", CC_ID, sqlite3_errmsg(cc->db)); return -1; + } + sqlite3_bind_int64(st, 1, (sqlite3_int64)cc->my_node_id); + sqlite3_bind_text(st, 2, req->channel_id, -1, SQLITE_STATIC); + sqlite3_bind_text(st, 3, req->content_type, -1, SQLITE_STATIC); + sqlite3_bind_text(st, 4, req->data ? (const char*)req->data : "", req->data_len, SQLITE_STATIC); if (req->reply_to_ts) { - off += snprintf(json + off, sizeof(json) - off, - ",\"rt\":%llu,\"rn\":%llu", - (unsigned long long)req->reply_to_ts, (unsigned long long)req->reply_to_node_id); + sqlite3_bind_int64(st, 5, (sqlite3_int64)req->reply_to_ts); + sqlite3_bind_int64(st, 6, (sqlite3_int64)req->reply_to_node_id); } - off += snprintf(json + off, sizeof(json) - off, - ",\"d\":\"%.*s\"}", - (int)req->data_len, (const char*)req->data); - + int rc = sqlite3_step(st), bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 0) : 0; + char json[4001]; + if (rc != SQLITE_ROW || bytes <= 0 || bytes >= (int)sizeof(json)) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: message JSON rejected ch=%s bytes=%d rc=%d: %s", + CC_ID, req->channel_id, bytes, rc, sqlite3_errmsg(cc->db)); sqlite3_finalize(st); return -1; + } + memcpy(json, sqlite3_column_text(st, 0), bytes); json[bytes] = 0; sqlite3_finalize(st); uint64_t ts = db_sync_next_timestamp(si); - uint8_t sig_msg[8192]; size_t soff = 0; - memcpy(sig_msg + soff, &ts, 8); soff += 8; - size_t jl = strlen(json); memcpy(sig_msg + soff, json, jl); soff += jl; - uint8_t sig[64]; - if (sc_ed25519_sign(inst->my_ed25519_privkey, sig_msg, soff, sig) != SC_OK) { - DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: Ed25519 sign failed", CC_ID); - return; + uint8_t signed_body[sizeof(json) + 8], signature[64], message_id[32]; + memcpy(signed_body, &ts, 8); memcpy(signed_body + 8, json, bytes); + if (sc_ed25519_sign(inst->my_ed25519_privkey, signed_body, bytes + 8, signature) != SC_OK + || chat_message_id(db_sync_instance_group_id(si), ts, signature, message_id) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: message signing failed ch=%s", CC_ID, req->channel_id); return -1; } - - int ret = db_sync_insert_signed(si, json, strlen(json), sig, 64, ts, NULL); - if (ret != 0) { - DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: db_sync_insert_signed failed ret=%d", CC_ID, ret); - return; + int result = db_sync_insert_signed(si, json, bytes, signature, 64, ts, NULL); + if (result != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: message insert failed ch=%s result=%d", CC_ID, req->channel_id, result); return -1; } + req->timestamp = ts; memcpy(req->message_id, message_id, sizeof(message_id)); + DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: message saved ch=%s ct=%s ts=%llu bytes=%d", + CC_ID, req->channel_id, req->content_type, (unsigned long long)ts, bytes); + return 0; } void chat_core_submit_trampoline(void* arg) {