|
|
|
|
@ -26,6 +26,7 @@
|
|
|
|
|
#include "../../lib/ll_queue.h" |
|
|
|
|
#include "../../lib/debug_config.h" |
|
|
|
|
#include "../../lib/platform_compat.h" |
|
|
|
|
#include "../../lib/strbuf.h" |
|
|
|
|
|
|
|
|
|
#include <string.h> |
|
|
|
|
#include <stdlib.h> |
|
|
|
|
@ -563,6 +564,7 @@ int dm_accept_receipt(struct UTUN_INSTANCE* inst, const uint8_t receipt[DM_RECEI
|
|
|
|
|
if (rc != SQLITE_DONE) goto fail; |
|
|
|
|
sqlite3_finalize(st); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DM, "%s: delivery confirmed conv=%s seq=%llu", DM_ID, conv_id, (unsigned long long)seq); |
|
|
|
|
dm_media_notify(inst, conv); |
|
|
|
|
return 0; |
|
|
|
|
fail: |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: receipt database error rc=%d sql=%s", DM_ID, rc, sqlite3_errmsg(dm->db)); |
|
|
|
|
@ -892,64 +894,66 @@ int dm_list_conversations_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf
|
|
|
|
|
int dm_list_messages_json(struct UTUN_INSTANCE* inst, const char* conv_id, int count, int offset, |
|
|
|
|
char* buf, size_t buf_size, size_t* out_len) { |
|
|
|
|
struct dm_state* dm = dm_of(inst); |
|
|
|
|
if (!conv_id || !buf || !out_len || !dm || !dm->db) return -1; |
|
|
|
|
|
|
|
|
|
if (!conv_id || !buf || !out_len || !dm || !dm->db || offset < 0) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: invalid messages snapshot request", DM_ID); |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
*out_len = 0; |
|
|
|
|
struct dm_conv c; |
|
|
|
|
if (dm_conv_load(dm, conv_id, &c) != 0) return -1; |
|
|
|
|
|
|
|
|
|
char sql[256]; |
|
|
|
|
if (count > 0) |
|
|
|
|
snprintf(sql, sizeof(sql), |
|
|
|
|
"SELECT dir,seq,ts,author,ct,data FROM dm_messages WHERE conv_id=? ORDER BY ts, seq LIMIT %d OFFSET %d", |
|
|
|
|
count, offset); |
|
|
|
|
else |
|
|
|
|
snprintf(sql, sizeof(sql), |
|
|
|
|
"SELECT dir,seq,ts,author,ct,data FROM dm_messages WHERE conv_id=? ORDER BY ts, seq"); |
|
|
|
|
|
|
|
|
|
int off = 0; |
|
|
|
|
off += snprintf(buf + off, buf_size - (size_t)off, "["); |
|
|
|
|
/* Ограниченный снимок не блокирует uasync на длинной истории. */ |
|
|
|
|
if (count <= 0 || count > 200) count = 200; |
|
|
|
|
const char* sql = "SELECT * FROM (SELECT m.dir,m.seq,m.ts,m.author,m.ct,m.data,o.seq IS NOT NULL AS pending," |
|
|
|
|
"COALESCE(f.ready,0) AS ready,COALESCE(f.downloading,0) AS downloading,f.receipt," |
|
|
|
|
"COALESCE(f.local_path,'') AS path,COALESCE(f.plain_size,0) AS size" |
|
|
|
|
" FROM dm_messages m LEFT JOIN dm_outbox o ON m.dir=1 AND o.conv_id=m.conv_id AND o.seq=m.seq" |
|
|
|
|
" LEFT JOIN dm_files f ON f.conv_id=m.conv_id AND f.dir=m.dir AND f.seq=m.seq" |
|
|
|
|
" WHERE m.conv_id=? ORDER BY m.ts DESC,m.seq DESC,m.dir DESC LIMIT ? OFFSET ?) ORDER BY ts,seq,dir"; |
|
|
|
|
struct strbuf sb = strbuf_new(); |
|
|
|
|
uint64_t conv_num = (uint64_t)strtoull(conv_id, NULL, 10); |
|
|
|
|
sqlite3_stmt* st = NULL; |
|
|
|
|
if (sqlite3_prepare_v2(dm->db, sql, -1, &st, NULL) == SQLITE_OK) { |
|
|
|
|
sqlite3_bind_text(st, 1, conv_id, -1, SQLITE_STATIC); |
|
|
|
|
int first = 1; |
|
|
|
|
while (sqlite3_step(st) == SQLITE_ROW) { |
|
|
|
|
int dir = sqlite3_column_int(st, 0); |
|
|
|
|
uint64_t seq = (uint64_t)sqlite3_column_int64(st, 1); |
|
|
|
|
uint64_t ts = (uint64_t)sqlite3_column_int64(st, 2); |
|
|
|
|
uint64_t author = (uint64_t)sqlite3_column_int64(st, 3); |
|
|
|
|
const char* ct = (const char*)sqlite3_column_text(st, 4); |
|
|
|
|
const uint8_t* enc = (const uint8_t*)sqlite3_column_blob(st, 5); |
|
|
|
|
int enc_len = sqlite3_column_bytes(st, 5); |
|
|
|
|
/* расшифровать (nonce детерминирован по conv_id+author+seq) */ |
|
|
|
|
char text[DM_DATA_MAX + 1]; |
|
|
|
|
text[0] = '\0'; |
|
|
|
|
if (enc && enc_len >= (int)DM_TAG_SIZE) { |
|
|
|
|
uint8_t nonce[DM_NONCE_SIZE]; |
|
|
|
|
dm_build_nonce(conv_num, author, seq, nonce); |
|
|
|
|
uint8_t* plain = u_malloc((size_t)enc_len); |
|
|
|
|
size_t plen = 0; |
|
|
|
|
if (dm_decrypt(c.content_key, nonce, enc, (size_t)enc_len, plain, &plen) == 0) { |
|
|
|
|
if (plen >= sizeof(text)) plen = sizeof(text) - 1; |
|
|
|
|
memcpy(text, plain, plen); |
|
|
|
|
text[plen] = '\0'; |
|
|
|
|
} |
|
|
|
|
u_free(plain); |
|
|
|
|
} |
|
|
|
|
char esc_ct[64], esc_data[DM_DATA_MAX * 2 + 8]; |
|
|
|
|
json_escape(ct ? ct : "", esc_ct, sizeof(esc_ct)); |
|
|
|
|
json_escape(text, esc_data, sizeof(esc_data)); |
|
|
|
|
off += snprintf(buf + off, buf_size - (size_t)off, |
|
|
|
|
"%s{\"dir\":%d,\"seq\":%llu,\"ts\":%llu,\"author\":%llu,\"ct\":\"%s\",\"data\":\"%s\"}", |
|
|
|
|
first ? "" : ",", dir, (unsigned long long)seq, (unsigned long long)ts, |
|
|
|
|
(unsigned long long)author, esc_ct, esc_data); |
|
|
|
|
first = 0; |
|
|
|
|
} |
|
|
|
|
if (sqlite3_prepare_v2(dm->db, sql, -1, &st, NULL) != SQLITE_OK || strbuf_addf(&sb, "[") < 0) goto fail; |
|
|
|
|
sqlite3_bind_text(st, 1, conv_id, -1, SQLITE_STATIC); |
|
|
|
|
sqlite3_bind_int(st, 2, count); sqlite3_bind_int(st, 3, offset); |
|
|
|
|
int first = 1, rc; |
|
|
|
|
while ((rc = sqlite3_step(st)) == SQLITE_ROW) { |
|
|
|
|
int dir = sqlite3_column_int(st, 0); |
|
|
|
|
uint64_t seq = (uint64_t)sqlite3_column_int64(st, 1), ts = (uint64_t)sqlite3_column_int64(st, 2); |
|
|
|
|
uint64_t author = (uint64_t)sqlite3_column_int64(st, 3); |
|
|
|
|
const char* ct = (const char*)sqlite3_column_text(st, 4); |
|
|
|
|
const uint8_t* enc = sqlite3_column_blob(st, 5); |
|
|
|
|
int enc_len = sqlite3_column_bytes(st, 5); |
|
|
|
|
char text[DM_DATA_MAX + 1]; |
|
|
|
|
size_t plen = 0; |
|
|
|
|
uint8_t nonce[DM_NONCE_SIZE]; |
|
|
|
|
dm_build_nonce(conv_num, author, seq, nonce); |
|
|
|
|
if (enc_len < DM_TAG_SIZE || enc_len > DM_DATA_MAX + DM_TAG_SIZE || |
|
|
|
|
dm_decrypt(c.content_key, nonce, enc, (size_t)enc_len, (uint8_t*)text, &plen)) goto fail; |
|
|
|
|
text[plen] = '\0'; |
|
|
|
|
int ready = sqlite3_column_int(st, 7), downloading = sqlite3_column_int(st, 8); |
|
|
|
|
int delivered = sqlite3_column_bytes(st, 9) == DM_MEDIA_RECEIPT_SIZE; |
|
|
|
|
const char* path = ready == 1 ? (const char*)sqlite3_column_text(st, 10) : ""; |
|
|
|
|
const char* status = delivered && dir ? "delivered" : ready == 1 ? "ready" : downloading ? "downloading" : |
|
|
|
|
ready < 0 ? "expired" : "pending"; |
|
|
|
|
char esc_ct[512], esc_data[DM_DATA_MAX * 2 + 8], esc_path[2048]; |
|
|
|
|
json_escape(ct ? ct : "", esc_ct, sizeof(esc_ct)); json_escape(text, esc_data, sizeof(esc_data)); |
|
|
|
|
json_escape(path, esc_path, sizeof(esc_path)); |
|
|
|
|
if (strbuf_addf(&sb, "%s{\"dir\":%d,\"seq\":%llu,\"ts\":%llu,\"author\":\"%llu\",\"ct\":\"%s\",\"data\":\"%s\"," |
|
|
|
|
"\"pending\":%s,\"media_status\":\"%s\",\"media_path\":\"%s\",\"media_size\":%llu}", first ? "" : ",", dir, |
|
|
|
|
(unsigned long long)seq, (unsigned long long)ts, (unsigned long long)author, esc_ct, esc_data, |
|
|
|
|
sqlite3_column_int(st, 6) ? "true" : "false", status, esc_path, |
|
|
|
|
(unsigned long long)sqlite3_column_int64(st, 11)) < 0) goto fail; |
|
|
|
|
first = 0; |
|
|
|
|
} |
|
|
|
|
if (st) sqlite3_finalize(st); |
|
|
|
|
off += snprintf(buf + off, buf_size - (size_t)off, "]"); |
|
|
|
|
*out_len = (size_t)off; |
|
|
|
|
if (rc != SQLITE_DONE || strbuf_addf(&sb, "]") < 0 || sb.len + 1 > buf_size) goto fail; |
|
|
|
|
memcpy(buf, strbuf_str(&sb), sb.len + 1); *out_len = sb.len; |
|
|
|
|
sqlite3_finalize(st); strbuf_free(&sb); |
|
|
|
|
return 0; |
|
|
|
|
fail: |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: message snapshot failed conv=%s buffer=%zu required=%zu sql=%s", DM_ID, |
|
|
|
|
conv_id, buf_size, sb.len + 1, sqlite3_errmsg(dm->db)); |
|
|
|
|
sqlite3_finalize(st); strbuf_free(&sb); |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* ── трамплины для gui_bridge (GUI → uasync) ── */ |
|
|
|
|
|