- Add last_msg_at column to channels table - Update last_msg_at on every message insert (send/receive) - Parameterize getChannels() with ChannelSortMode enum (Alphabetical/Recent) - Add getChannelLastMessage() for preview text in channel list - Add sorting combo to Profile settings page (stored in ui_state) - Reload channels on message receive when sort is recent
231 lines
8.9 KiB
C
231 lines
8.9 KiB
C
/*
|
|
* chat_msg.c — сообщения: отправка, DB-операции для chat_sync
|
|
*
|
|
* Вынесено из chat_core.c для уменьшения размера модуля.
|
|
*/
|
|
|
|
#include "chat_core_priv.h"
|
|
#include "gui_bridge.h"
|
|
|
|
#include "../../../src/utun_instance.h"
|
|
#include "secure_channel.h"
|
|
#include "../../../lib/mem.h"
|
|
#include "../../../lib/platform_compat.h"
|
|
|
|
#include <openssl/sha.h>
|
|
|
|
/* ─── отправка сообщения (GUI → uasync) ─── */
|
|
|
|
static int msg_get_prev_chain_hash(const char* tbl, uint8_t out[32]) {
|
|
char sql[128]; snprintf(sql, sizeof(sql),
|
|
"SELECT chain_hash FROM \"%s\" ORDER BY timestamp DESC LIMIT 1", tbl);
|
|
sqlite3_stmt* st = NULL;
|
|
if (sqlite3_prepare_v2(g_cc.db, sql, -1, &st, NULL) != SQLITE_OK) { memset(out,0,32); return -1; }
|
|
if (sqlite3_step(st) == SQLITE_ROW) {
|
|
const void* blob = sqlite3_column_blob(st, 0); int blen = sqlite3_column_bytes(st, 0);
|
|
if (blob && blen == 32) memcpy(out, blob, 32); else memset(out, 0, 32);
|
|
} else { memset(out, 0, 32); }
|
|
sqlite3_finalize(st); return 0;
|
|
}
|
|
|
|
void chat_core_submit_message(struct chat_msg_submit* req) {
|
|
if (!g_cc.initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: submit before init", CC_ID); return; }
|
|
if (!req) return;
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: SUBMIT ch=%s ct=%s len=%u",
|
|
CC_ID, req->channel_id, req->content_type, req->data_len);
|
|
|
|
struct DB_SYNC_INSTANCE* si = si_find(req->channel_id);
|
|
if (!si) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: no db_sync instance for ch=%s", CC_ID, req->channel_id); return; }
|
|
|
|
char json[4096];
|
|
snprintf(json, sizeof(json),
|
|
"{\"n\":%llu,\"ch\":\"%s\",\"ct\":\"%s\",\"d\":\"%.*s\"}",
|
|
(unsigned long long)g_cc.my_node_id, req->channel_id,
|
|
req->content_type, (int)req->data_len, (const char*)req->data);
|
|
|
|
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(g_cc.inst->my_ed25519_privkey, sig_msg, soff, sig) != SC_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: Ed25519 sign failed", CC_ID);
|
|
return;
|
|
}
|
|
|
|
int ret = db_sync_insert_signed(si, json, strlen(json), sig, 64, ts);
|
|
if (ret != 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: db_sync_insert_signed failed ret=%d", CC_ID, ret);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void chat_core_submit_trampoline(void* arg) { chat_core_submit_message((struct chat_msg_submit*)arg); u_free(arg); }
|
|
|
|
/* ─── DB-операции для chat_sync ─── */
|
|
|
|
uint32_t chat_core_count(const char* ch_id) {
|
|
if (!g_cc.initialized) return 0;
|
|
char tbl[80]; msg_table_name(ch_id, tbl, sizeof(tbl));
|
|
char sql[128]; snprintf(sql, sizeof(sql),
|
|
"SELECT COUNT(*) FROM \"%s\"", tbl);
|
|
sqlite3_stmt* stmt = NULL;
|
|
if (sqlite3_prepare_v2(g_cc.db, sql, -1, &stmt, NULL) != SQLITE_OK) return 0;
|
|
uint32_t cnt = 0;
|
|
if (sqlite3_step(stmt) == SQLITE_ROW)
|
|
cnt = (uint32_t)sqlite3_column_int64(stmt, 0);
|
|
sqlite3_finalize(stmt);
|
|
return cnt;
|
|
}
|
|
|
|
int chat_core_chain_hash_at(const char* ch_id, uint32_t pos, uint8_t* hash_out) {
|
|
if (!g_cc.initialized || !hash_out) return -1;
|
|
char tbl[80]; msg_table_name(ch_id, tbl, sizeof(tbl));
|
|
char sql[200]; snprintf(sql, sizeof(sql),
|
|
"SELECT chain_hash FROM \"%s\" ORDER BY timestamp, node_id ASC LIMIT 1 OFFSET ?",
|
|
tbl);
|
|
sqlite3_stmt* stmt = NULL;
|
|
if (sqlite3_prepare_v2(g_cc.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
|
memset(hash_out, 0, 32); return -1;
|
|
}
|
|
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)pos);
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
const void* blob = sqlite3_column_blob(stmt, 0);
|
|
int len = sqlite3_column_bytes(stmt, 0);
|
|
if (blob && len == 32) memcpy(hash_out, blob, 32);
|
|
else memset(hash_out, 0, 32);
|
|
} else {
|
|
memset(hash_out, 0, 32);
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
return 0;
|
|
}
|
|
|
|
int chat_core_list_channels(uint8_t* buf, size_t buf_size, size_t* out_len) {
|
|
if (!g_cc.initialized || !buf || !out_len) return -1;
|
|
sqlite3_stmt* stmt = NULL;
|
|
if (sqlite3_prepare_v2(g_cc.db,
|
|
"SELECT channel_id FROM channels ORDER BY created_at ASC",
|
|
-1, &stmt, NULL) != SQLITE_OK) return -1;
|
|
|
|
uint8_t* out = buf;
|
|
uint8_t* start = out;
|
|
out += 2;
|
|
|
|
uint16_t cnt = 0;
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
const char* ch_id = (const char*)sqlite3_column_text(stmt, 0);
|
|
int ch_len = sqlite3_column_bytes(stmt, 0);
|
|
if (!ch_id || ch_len <= 0 || ch_len > 63) continue;
|
|
size_t need = (size_t)(out - buf) + 1 + (size_t)ch_len;
|
|
if (need > buf_size) break;
|
|
*out++ = (uint8_t)ch_len;
|
|
memcpy(out, ch_id, (size_t)ch_len); out += ch_len;
|
|
cnt++;
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
|
|
memcpy(start, &cnt, 2);
|
|
*out_len = (size_t)(out - buf);
|
|
return 0;
|
|
}
|
|
|
|
int chat_core_list_peers(const char* ch_id, uint8_t* buf, size_t buf_size,
|
|
size_t* out_len) {
|
|
if (!g_cc.initialized || !buf || !out_len) return -1;
|
|
char tbl[80]; peers_table_name(ch_id, tbl, sizeof(tbl));
|
|
char sql[128]; snprintf(sql, sizeof(sql),
|
|
"SELECT node_id FROM \"%s\"", tbl);
|
|
sqlite3_stmt* stmt = NULL;
|
|
if (sqlite3_prepare_v2(g_cc.db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
|
uint16_t z = 0; memcpy(buf, &z, 2); *out_len = 2; return -1;
|
|
}
|
|
uint16_t cnt = 0;
|
|
uint8_t* out = buf + 2;
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
if ((size_t)(out - buf) + 8 > buf_size) break;
|
|
uint64_t nid = (uint64_t)sqlite3_column_int64(stmt, 0);
|
|
memcpy(out, &nid, 8); out += 8; cnt++;
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
memcpy(buf, &cnt, 2);
|
|
*out_len = (size_t)(out - buf);
|
|
return 0;
|
|
}
|
|
|
|
int chat_core_load_nodeinfo(uint64_t node_id, uint8_t* buf, size_t buf_size,
|
|
size_t* out_len) {
|
|
if (!g_cc.initialized || !buf || !out_len) return -1;
|
|
|
|
sqlite3_stmt* stmt = NULL;
|
|
if (sqlite3_prepare_v2(g_cc.db,
|
|
"SELECT x25519_pubkey, ed25519_pubkey FROM nodes WHERE node_id=?",
|
|
-1, &stmt, NULL) != SQLITE_OK) return -1;
|
|
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; }
|
|
const uint8_t* x25519 = (const uint8_t*)sqlite3_column_blob(stmt, 0);
|
|
int x25519_len = sqlite3_column_bytes(stmt, 0);
|
|
const uint8_t* ed25519 = (const uint8_t*)sqlite3_column_blob(stmt, 1);
|
|
int ed25519_len = sqlite3_column_bytes(stmt, 1);
|
|
|
|
uint8_t* out = buf;
|
|
if (x25519 && x25519_len == 32) memcpy(out, x25519, 32);
|
|
else memset(out, 0, 32);
|
|
out += 32;
|
|
if (ed25519 && ed25519_len == 32) memcpy(out, ed25519, 32);
|
|
else memset(out, 0, 32);
|
|
out += 32;
|
|
sqlite3_finalize(stmt);
|
|
|
|
if (sqlite3_prepare_v2(g_cc.db,
|
|
"SELECT family, protocol, address, port, rtt FROM node_addresses WHERE node_id=?",
|
|
-1, &stmt, NULL) != SQLITE_OK) {
|
|
*out++ = 0; *out_len = (size_t)(out - buf); return 0;
|
|
}
|
|
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
|
uint8_t* cnt_pos = out; *out++ = 0;
|
|
uint8_t addr_cnt = 0;
|
|
|
|
while (sqlite3_step(stmt) == SQLITE_ROW && addr_cnt < 255) {
|
|
int family = sqlite3_column_int(stmt, 0);
|
|
int proto = sqlite3_column_int(stmt, 1);
|
|
const uint8_t* addr = (const uint8_t*)sqlite3_column_blob(stmt, 2);
|
|
int addr_len = sqlite3_column_bytes(stmt, 2);
|
|
uint16_t port = (uint16_t)sqlite3_column_int(stmt, 3);
|
|
int16_t rtt = (int16_t)sqlite3_column_int(stmt, 4);
|
|
|
|
if ((size_t)(out - buf) + 7 + (size_t)addr_len > buf_size) break;
|
|
*out++ = (uint8_t)family;
|
|
*out++ = (uint8_t)proto;
|
|
*out++ = (uint8_t)addr_len;
|
|
if (addr_len > 0) { memcpy(out, addr, (size_t)addr_len); out += addr_len; }
|
|
memcpy(out, &port, 2); out += 2;
|
|
memcpy(out, &rtt, 2); out += 2;
|
|
addr_cnt++;
|
|
}
|
|
*cnt_pos = addr_cnt;
|
|
sqlite3_finalize(stmt);
|
|
|
|
*out_len = (size_t)(out - buf);
|
|
return 0;
|
|
}
|
|
|
|
/* ─── db_sync callback ─── */
|
|
|
|
void on_msg_inserted(struct DB_SYNC_INSTANCE* si, uint64_t record_ts, const char* data, size_t len, uint64_t author, void* arg) {
|
|
(void)si; (void)data; (void)len;
|
|
const char* ch_id = (const char*)arg;
|
|
uint8_t evt[73]; uint8_t cl = (uint8_t)strlen(ch_id); evt[0] = cl; memcpy(evt + 1, ch_id, cl);
|
|
memcpy(evt + 1 + cl, &author, 8);
|
|
gui_bridge_post(GUI_EVT_MSG_RECEIVED, evt, 1 + cl + 8);
|
|
|
|
sqlite3_stmt* st = NULL;
|
|
sqlite3_prepare_v2(g_cc.db, "UPDATE channels SET last_msg_at = ? WHERE channel_id = ?", -1, &st, NULL);
|
|
if (st) {
|
|
sqlite3_bind_int64(st, 1, (sqlite3_int64)record_ts);
|
|
sqlite3_bind_text(st, 2, ch_id, -1, SQLITE_STATIC);
|
|
sqlite3_step(st); sqlite3_finalize(st);
|
|
}
|
|
}
|