fix(chat): return complete escaped JSON and propagate listing SQL failures
This commit is contained in:
1 parent
5fe66585b6
commit
d5070a5df6
1 file changed
+172
-150
+172
-150
@@ -25,6 +25,7 @@
|
||||
#include "../transport_layer/secure_channel.h"
|
||||
#include "../media_delivery/media_index.h"
|
||||
#include "../../lib/mem.h"
|
||||
#include "../../lib/strbuf.h"
|
||||
#include "../../lib/json_flat.h"
|
||||
|
||||
#include <string.h>
|
||||
@@ -349,62 +350,102 @@ static void json_escape(const char* src, char* dst, size_t dst_sz) {
|
||||
dst[i] = '\0';
|
||||
}
|
||||
|
||||
int chat_core_get_channels_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf_size, size_t* out_len) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
if (!cc || !cc->initialized || !buf || !out_len) return -1;
|
||||
char* w = buf; char* end = buf + buf_size;
|
||||
*w++ = '['; const char* sep = "";
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(cc->db,
|
||||
"SELECT c.channel_id, c.name, c.owner_node_id, "
|
||||
"(SELECT COUNT(*) FROM nodes WHERE node_id=c.owner_node_id AND name IS NOT NULL) AS has_owner_name, "
|
||||
"(SELECT name FROM nodes WHERE node_id=c.owner_node_id) AS owner_name "
|
||||
"FROM channels c ORDER BY c.created_at ASC",
|
||||
-1, &st, NULL) != SQLITE_OK) { *out_len = 0; return -1; }
|
||||
while (sqlite3_step(st) == SQLITE_ROW) {
|
||||
const char* ch_id = (const char*)sqlite3_column_text(st, 0);
|
||||
const char* ch_name = (const char*)sqlite3_column_text(st, 1);
|
||||
uint64_t owner_id = (uint64_t)sqlite3_column_int64(st, 2);
|
||||
const char* owner_name = (const char*)sqlite3_column_text(st, 4);
|
||||
if (!ch_id) continue;
|
||||
|
||||
uint32_t msg_count = chat_core_count(inst, ch_id);
|
||||
char peers_tbl[80]; peers_table_name(ch_id, peers_tbl, sizeof(peers_tbl));
|
||||
int peer_count = 0, online_count = 0;
|
||||
sqlite3_stmt* ps = NULL;
|
||||
char psql[200]; snprintf(psql, sizeof(psql), "SELECT node_id FROM \"%s\" WHERE deleted=0", peers_tbl);
|
||||
if (sqlite3_prepare_v2(cc->db, psql, -1, &ps, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(ps) == SQLITE_ROW) {
|
||||
uint64_t pid = (uint64_t)sqlite3_column_int64(ps, 0);
|
||||
peer_count++;
|
||||
if (chat_core_member_online(inst, ch_id, pid)) online_count++;
|
||||
}
|
||||
sqlite3_finalize(ps);
|
||||
}
|
||||
|
||||
char esc_name[256], esc_owner[256];
|
||||
json_escape(ch_name ? ch_name : "", esc_name, sizeof(esc_name));
|
||||
json_escape(owner_name ? owner_name : "", esc_owner, sizeof(esc_owner));
|
||||
|
||||
w += snprintf(w, (size_t)(end - w),
|
||||
"%s{\"id\":\"%s\",\"name\":\"%s\",\"owner_id\":\"0x%016llx\",\"owner_name\":\"%s\","
|
||||
"\"peers\":%d,\"msgs\":%u,\"online\":%d}",
|
||||
sep, ch_id, esc_name, (unsigned long long)owner_id, esc_owner, peer_count, msg_count, online_count);
|
||||
sep = ",";
|
||||
/* Результат чтения публикуется целиком;даже неверные аргументы не оставляют старый JSON. */
|
||||
static struct chat_core_ctx* json_context(struct UTUN_INSTANCE* inst, char* buf, size_t size, size_t* out_len) {
|
||||
if (out_len) *out_len = 0;
|
||||
if (buf && size) buf[0] = 0;
|
||||
struct chat_core_ctx* cc = inst ? CC(inst) : NULL;
|
||||
if (!cc || !cc->initialized || !cc->db || !buf || size < 3 || !out_len) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "chat JSON: invalid context/output capacity=%zu", size); return NULL;
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
*w++ = ']';
|
||||
*w = '\0';
|
||||
*out_len = (size_t)(w - buf);
|
||||
return 0;
|
||||
return cc;
|
||||
}
|
||||
|
||||
static int json_copy(const struct strbuf* json, char* buf, size_t capacity, size_t* out_len) {
|
||||
if (json->len >= capacity) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "chat JSON: output exceeds capacity bytes=%zu capacity=%zu", json->len, capacity); return -1;
|
||||
}
|
||||
memcpy(buf, strbuf_str(json), json->len + 1); *out_len = json->len; return 0;
|
||||
}
|
||||
|
||||
/* Сохранённый канал может ещё не иметь history-таблицы при отложенной runtime-подготовке. */
|
||||
static int json_message_count(sqlite3* db, const char* channel, sqlite3_int64* count) {
|
||||
char table[80], sql[128]; msg_table_name(channel, table, sizeof(table)); sqlite3_stmt* st = NULL;
|
||||
const char* stage = "history table lookup";
|
||||
int rc = sqlite3_prepare_v2(db, "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, table, -1, SQLITE_STATIC);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
||||
int found = rc == SQLITE_ROW;
|
||||
if (rc != SQLITE_ROW && rc != SQLITE_DONE) goto error;
|
||||
rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error;
|
||||
if (!found) {
|
||||
*count = 0; DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "chat JSON: history not prepared ch=%s", channel); return 0;
|
||||
}
|
||||
stage = "history count"; snprintf(sql, sizeof(sql), "SELECT count(*) FROM \"%s\"", table);
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
||||
if (rc != SQLITE_ROW) goto error;
|
||||
sqlite3_int64 result = sqlite3_column_int64(st, 0);
|
||||
rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error;
|
||||
*count = result; return 0;
|
||||
error:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "chat JSON: ch=%s stage=%s rc=%d: %s", channel, stage, rc, sqlite3_errmsg(db));
|
||||
rc = sqlite3_finalize(st);
|
||||
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "chat JSON: count cleanup finalize rc=%d", rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int chat_core_get_channels_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf_size, size_t* out_len) {
|
||||
struct chat_core_ctx* cc = json_context(inst, buf, buf_size, out_len); if (!cc) return -1;
|
||||
struct strbuf json = strbuf_new(); sqlite3_stmt *st = NULL, *peers = NULL; const char* stage = "prepare";
|
||||
int rc = sqlite3_prepare_v2(cc->db, "SELECT c.channel_id,c.name,c.owner_node_id,n.name FROM channels c"
|
||||
" LEFT JOIN nodes n ON n.node_id=c.owner_node_id ORDER BY c.created_at,c.channel_id", -1, &st, NULL);
|
||||
if (rc != SQLITE_OK) goto error;
|
||||
stage = "JSON"; rc = SQLITE_NOMEM; if (strbuf_addf(&json, "[") < 0) goto error;
|
||||
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
const char *channel = (const char*)sqlite3_column_text(st, 0), *name = (const char*)sqlite3_column_text(st, 1);
|
||||
const char* owner_name = (const char*)sqlite3_column_text(st, 3);
|
||||
uint64_t owner = (uint64_t)sqlite3_column_int64(st, 2);
|
||||
if (!chat_channel_id(channel)) { stage = "invalid stored channel ID"; rc = SQLITE_CORRUPT; goto error; }
|
||||
sqlite3_int64 messages, count = 0, online = 0;
|
||||
stage = "history count";
|
||||
if (json_message_count(cc->db, channel, &messages)) { rc = SQLITE_ERROR; goto error; }
|
||||
char table[80], sql[160]; peers_table_name(channel, table, sizeof(table));
|
||||
snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE deleted=0", table);
|
||||
stage = "members"; rc = sqlite3_prepare_v2(cc->db, sql, -1, &peers, NULL); if (rc != SQLITE_OK) goto error;
|
||||
while ((rc = sqlite3_step(peers)) == SQLITE_ROW) {
|
||||
count++; online += chat_core_member_online(inst, channel, (uint64_t)sqlite3_column_int64(peers, 0));
|
||||
}
|
||||
if (rc != SQLITE_DONE) goto error;
|
||||
stage = "members finalize"; rc = sqlite3_finalize(peers); peers = NULL; if (rc != SQLITE_OK) goto error;
|
||||
stage = "JSON"; rc = SQLITE_NOMEM;
|
||||
if (strbuf_addf(&json, "%s{\"id\":", json.len > 1 ? "," : "") < 0 || strbuf_add_json_string(&json, channel) < 0 ||
|
||||
strbuf_addf(&json, ",\"name\":") < 0 || strbuf_add_json_string(&json, name) < 0 ||
|
||||
strbuf_addf(&json, ",\"owner_id\":\"0x%016llx\",\"owner_name\":", (unsigned long long)owner) < 0 ||
|
||||
strbuf_add_json_string(&json, owner_name) < 0 ||
|
||||
strbuf_addf(&json, ",\"peers\":%lld,\"msgs\":%lld,\"online\":%lld}", (long long)count, (long long)messages, (long long)online) < 0) goto error;
|
||||
if (json.len >= buf_size) { rc = SQLITE_TOOBIG; goto error; }
|
||||
stage = "channels step";
|
||||
}
|
||||
if (rc != SQLITE_DONE) goto error;
|
||||
stage = "channels finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error;
|
||||
stage = "JSON"; rc = SQLITE_NOMEM; if (strbuf_addf(&json, "]") < 0) goto error;
|
||||
int result = json_copy(&json, buf, buf_size, out_len); strbuf_free(&json); return result;
|
||||
error:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "channels JSON: stage=%s rc=%d bytes=%zu capacity=%zu: %s",
|
||||
stage, rc, json.len, buf_size, sqlite3_errmsg(cc->db));
|
||||
int finish = sqlite3_finalize(peers);
|
||||
if (finish != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "channels JSON: members cleanup rc=%d", finish);
|
||||
finish = sqlite3_finalize(st);
|
||||
if (finish != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "channels JSON: cleanup rc=%d", finish);
|
||||
strbuf_free(&json); return -1;
|
||||
}
|
||||
|
||||
int chat_core_get_messages_json(struct UTUN_INSTANCE* inst, const char* ch_id, int count, int offset,
|
||||
char* buf, size_t buf_size, size_t* out_len) {
|
||||
struct chat_core_ctx* cc = inst ? CC(inst) : NULL;
|
||||
if (out_len) *out_len = 0;
|
||||
if (!cc || !cc->initialized || !ch_id || !buf || !out_len || buf_size < 3 || offset < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid message listing arguments", CC_ID); return -1;
|
||||
struct chat_core_ctx* cc = json_context(inst, buf, buf_size, out_len); if (!cc) return -1;
|
||||
if (!chat_channel_id(ch_id) || offset < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "messages JSON: invalid channel/offset=%d", offset); return -1;
|
||||
}
|
||||
char table[80], sql[1024]; msg_table_name(ch_id, table, sizeof(table));
|
||||
snprintf(sql, sizeof(sql),
|
||||
@@ -414,117 +455,98 @@ int chat_core_get_messages_json(struct UTUN_INSTANCE* inst, const char* ch_id, i
|
||||
"'data',coalesce(CAST(m.data AS TEXT),''),'local_attrs',coalesce(m.local_attrs,''),'ver',m.ver,'edited_at',m.edited_at)"
|
||||
" FROM \"%s\" m LEFT JOIN nodes n ON m.node_id=n.node_id"
|
||||
" ORDER BY m.timestamp DESC,m.message_id DESC LIMIT ? OFFSET ?", table);
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: messages query ch=%s: %s", CC_ID, ch_id, sqlite3_errmsg(cc->db)); return -1;
|
||||
}
|
||||
sqlite3_bind_int(st, 1, count > 0 ? count : 100); sqlite3_bind_int(st, 2, offset);
|
||||
size_t off = 1; buf[0] = '['; int step, result = 0;
|
||||
while ((step = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
sqlite3_stmt* st = NULL; const char* stage = "prepare";
|
||||
int rc = sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) { stage = "bind count"; rc = sqlite3_bind_int(st, 1, count > 0 ? count : 100); }
|
||||
if (rc == SQLITE_OK) { stage = "bind offset"; rc = sqlite3_bind_int(st, 2, offset); }
|
||||
if (rc != SQLITE_OK) goto error;
|
||||
size_t off = 1; buf[0] = '['; stage = "step";
|
||||
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
const char* row = (const char*)sqlite3_column_text(st, 0); size_t bytes = sqlite3_column_bytes(st, 0);
|
||||
if (!row || bytes + (off > 1) + 2 > buf_size - off) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: messages exceed output buffer ch=%s capacity=%zu", CC_ID, ch_id, buf_size);
|
||||
result = -1; break;
|
||||
size_t separator = off > 1, available = buf_size - off;
|
||||
if (!row || available < separator + 2 || bytes > available - separator - 2) {
|
||||
stage = "JSON capacity/read"; rc = row ? SQLITE_TOOBIG : SQLITE_NOMEM; goto error;
|
||||
}
|
||||
if (off > 1) buf[off++] = ',';
|
||||
if (separator) buf[off++] = ',';
|
||||
memcpy(buf + off, row, bytes); off += bytes;
|
||||
}
|
||||
if (result == 0 && step != SQLITE_DONE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: messages read ch=%s: %s", CC_ID, ch_id, sqlite3_errmsg(cc->db)); result = -1;
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
if (result < 0) { buf[0] = 0; return -1; }
|
||||
if (rc != SQLITE_DONE) goto error;
|
||||
stage = "finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error;
|
||||
buf[off++] = ']'; buf[off] = 0; *out_len = off; return 0;
|
||||
error:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "messages JSON: ch=%s stage=%s rc=%d capacity=%zu: %s", ch_id, stage, rc, buf_size, sqlite3_errmsg(cc->db));
|
||||
int finish = sqlite3_finalize(st);
|
||||
if (finish != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "messages JSON: cleanup rc=%d", finish);
|
||||
buf[0] = 0; return -1;
|
||||
}
|
||||
|
||||
int chat_core_get_members_json(struct UTUN_INSTANCE* inst, const char* ch_id, char* buf, size_t buf_size, size_t* out_len) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
if (!cc || !cc->initialized || !ch_id || !buf || !out_len || buf_size < 3) return -1;
|
||||
*out_len = 0;
|
||||
char peers_tbl[80]; peers_table_name(ch_id, peers_tbl, sizeof(peers_tbl));
|
||||
char* w = buf; char* end = buf + buf_size;
|
||||
*w++ = '['; const char* sep = "";
|
||||
char sql[256];
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT p.node_id, n.name, n.x25519_pubkey, n.ed25519_pubkey, p.adm_tags "
|
||||
"FROM \"%s\" p LEFT JOIN nodes n ON p.node_id=n.node_id", peers_tbl);
|
||||
sqlite3_stmt* st = NULL;
|
||||
sqlite3_stmt* as = NULL;
|
||||
#define MEMBERS_APPEND(...) do { \
|
||||
int written = snprintf(w, (size_t)(end - w), __VA_ARGS__); \
|
||||
if (written < 0 || (size_t)written >= (size_t)(end - w)) goto overflow; \
|
||||
w += written; \
|
||||
} while (0)
|
||||
if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) != SQLITE_OK) { *out_len = 0; return -1; }
|
||||
while (sqlite3_step(st) == SQLITE_ROW) {
|
||||
uint64_t nid = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
const char* name = (const char*)sqlite3_column_text(st, 1);
|
||||
int online = chat_core_member_online(inst, ch_id, nid);
|
||||
const uint8_t* x25519 = (const uint8_t*)sqlite3_column_blob(st, 2);
|
||||
const uint8_t* ed25519 = sqlite3_column_blob(st, 3);
|
||||
int x25519_len = sqlite3_column_bytes(st, 2);
|
||||
int ed25519_len = sqlite3_column_bytes(st, 3);
|
||||
const char* adm_tags = (const char*)sqlite3_column_text(st, 4);
|
||||
char esc_name[256], x25519_hex[65], ed25519_hex[65], esc_tags[6 * MS_ADM_TAGS_MAX + 1];
|
||||
json_escape(name ? name : "", esc_name, sizeof(esc_name));
|
||||
x25519_hex[0] = ed25519_hex[0] = '\0';
|
||||
if (x25519 && x25519_len >= 32) for (int i = 0; i < 32; i++) snprintf(x25519_hex + i*2, 3, "%02x", x25519[i]);
|
||||
if (ed25519 && ed25519_len >= 32) for (int i = 0; i < 32; i++) snprintf(ed25519_hex + i*2, 3, "%02x", ed25519[i]);
|
||||
|
||||
/* check connected */
|
||||
int connected = 0;
|
||||
if (inst && inst->connections) {
|
||||
struct ll_entry* e = queue_find_data_by_index(inst->connections, (const uint8_t*)&nid);
|
||||
if (e) connected = 1;
|
||||
struct chat_core_ctx* cc = json_context(inst, buf, buf_size, out_len); if (!cc) return -1;
|
||||
if (!chat_channel_id(ch_id)) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "members JSON: invalid channel"); return -1; }
|
||||
char table[80], sql[256]; peers_table_name(ch_id, table, sizeof(table));
|
||||
snprintf(sql, sizeof(sql), "SELECT p.node_id,n.name,n.x25519_pubkey,n.ed25519_pubkey,p.adm_tags,p.deleted"
|
||||
" FROM \"%s\" p LEFT JOIN nodes n ON p.node_id=n.node_id ORDER BY p.node_id", table);
|
||||
sqlite3_stmt *st = NULL, *addresses = NULL; struct strbuf json = strbuf_new(); const char* stage = "prepare";
|
||||
int rc = sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL); if (rc != SQLITE_OK) goto error;
|
||||
stage = "JSON"; rc = SQLITE_NOMEM; if (strbuf_addf(&json, "[") < 0) goto error;
|
||||
stage = "members step";
|
||||
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
uint64_t node = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
const char *name = (const char*)sqlite3_column_text(st, 1), *tags = (const char*)sqlite3_column_text(st, 4);
|
||||
char keys[2][65] = {{0}};
|
||||
for (int i = 0; i < 2; i++) if (sqlite3_column_type(st, i + 2) != SQLITE_NULL) {
|
||||
stage = "key size/type"; rc = SQLITE_CORRUPT;
|
||||
if (sqlite3_column_type(st, i + 2) != SQLITE_BLOB || sqlite3_column_bytes(st, i + 2) != 32) goto error;
|
||||
const uint8_t* key = sqlite3_column_blob(st, i + 2); if (!key) { rc = SQLITE_NOMEM; goto error; }
|
||||
for (int b = 0; b < 32; b++) snprintf(keys[i] + b * 2, 3, "%02x", key[b]);
|
||||
}
|
||||
|
||||
json_escape(adm_tags ? adm_tags : "", esc_tags, sizeof(esc_tags));
|
||||
|
||||
MEMBERS_APPEND(
|
||||
"%s{\"node_id\":\"0x%016llx\",\"name\":\"%s\",\"online\":%d,\"connected\":%d,"
|
||||
"\"adm_tags\":\"%s\",\"x25519\":\"%s\",\"ed25519\":\"%s\",\"addrs\":[",
|
||||
sep, (unsigned long long)nid, esc_name, online, connected, esc_tags, x25519_hex, ed25519_hex);
|
||||
sep = ",";
|
||||
|
||||
/* addresses */
|
||||
char asql[200]; snprintf(asql, sizeof(asql),
|
||||
"SELECT family, protocol, address, port, rtt FROM node_addresses WHERE node_id=? ORDER BY family, protocol");
|
||||
if (sqlite3_prepare_v2(cc->db, asql, -1, &as, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(as, 1, (sqlite3_int64)nid);
|
||||
const char* asep2 = "";
|
||||
while (sqlite3_step(as) == SQLITE_ROW) {
|
||||
int fam = sqlite3_column_int(as, 0);
|
||||
int proto = sqlite3_column_int(as, 1);
|
||||
const uint8_t* addr = (const uint8_t*)sqlite3_column_blob(as, 2);
|
||||
int addr_len = sqlite3_column_bytes(as, 2);
|
||||
int port = sqlite3_column_int(as, 3);
|
||||
int rtt = sqlite3_column_int(as, 4);
|
||||
char ip_str[64]; ip_str[0] = '\0';
|
||||
if (fam == 4 && addr && addr_len >= 4)
|
||||
snprintf(ip_str, sizeof(ip_str), "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]);
|
||||
else if (fam == 6 && addr && addr_len >= 16)
|
||||
snprintf(ip_str, sizeof(ip_str), "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
|
||||
addr[0],addr[1],addr[2],addr[3],addr[4],addr[5],addr[6],addr[7],
|
||||
addr[8],addr[9],addr[10],addr[11],addr[12],addr[13],addr[14],addr[15]);
|
||||
else { snprintf(ip_str, sizeof(ip_str), "?"); }
|
||||
MEMBERS_APPEND(
|
||||
"%s{\"ip\":\"%s\",\"port\":%d,\"proto\":\"%s\",\"rtt\":%d}",
|
||||
asep2, ip_str, port, proto == 1 ? "UDP" : proto == 2 ? "TCP" : "?", rtt);
|
||||
asep2 = ",";
|
||||
int online = !sqlite3_column_int(st, 5) && chat_core_member_online(inst, ch_id, node);
|
||||
int connected = inst->connections && queue_find_data_by_index(inst->connections, (const uint8_t*)&node);
|
||||
stage = "JSON"; rc = SQLITE_NOMEM;
|
||||
if (strbuf_addf(&json, "%s{\"node_id\":\"0x%016llx\",\"name\":", json.len > 1 ? "," : "", (unsigned long long)node) < 0 ||
|
||||
strbuf_add_json_string(&json, name) < 0 || strbuf_addf(&json, ",\"online\":%d,\"connected\":%d,\"adm_tags\":", online, connected) < 0 ||
|
||||
strbuf_add_json_string(&json, tags) < 0 ||
|
||||
strbuf_addf(&json, ",\"x25519\":\"%s\",\"ed25519\":\"%s\",\"addrs\":[", keys[0], keys[1]) < 0) goto error;
|
||||
stage = "addresses prepare";
|
||||
rc = sqlite3_prepare_v2(cc->db, "SELECT family,protocol,address,port,rtt FROM node_addresses WHERE node_id=? ORDER BY family,protocol",
|
||||
-1, &addresses, NULL);
|
||||
if (rc == SQLITE_OK) { stage = "addresses bind"; rc = sqlite3_bind_int64(addresses, 1, (sqlite3_int64)node); }
|
||||
if (rc != SQLITE_OK) goto error;
|
||||
const char* separator = ""; stage = "addresses step";
|
||||
while ((rc = sqlite3_step(addresses)) == SQLITE_ROW) {
|
||||
int family = sqlite3_column_int(addresses, 0), protocol = sqlite3_column_int(addresses, 1);
|
||||
int bytes = sqlite3_column_bytes(addresses, 2), port = sqlite3_column_int(addresses, 3);
|
||||
const void* address = sqlite3_column_blob(addresses, 2); char ip[64];
|
||||
if ((family != 4 && family != 6) || bytes != (family == 4 ? 4 : 16) || !address ||
|
||||
(protocol != 1 && protocol != 2 && protocol != 6) || port < 0 || port > UINT16_MAX ||
|
||||
!inet_ntop(family == 4 ? AF_INET : AF_INET6, address, ip, sizeof(ip))) {
|
||||
stage = "invalid stored address"; rc = SQLITE_CORRUPT; goto error;
|
||||
}
|
||||
sqlite3_finalize(as); as = NULL;
|
||||
stage = "JSON"; rc = SQLITE_NOMEM;
|
||||
if (strbuf_addf(&json, "%s{\"ip\":", separator) < 0 || strbuf_add_json_string(&json, ip) < 0 ||
|
||||
strbuf_addf(&json, ",\"port\":%d,\"proto\":\"%s\",\"rtt\":%lld}", port,
|
||||
protocol == 1 ? "UDP" : protocol == 2 ? "TCP" : "TCP+REALITY", (long long)sqlite3_column_int64(addresses, 4)) < 0) goto error;
|
||||
separator = ","; if (json.len >= buf_size) { rc = SQLITE_TOOBIG; goto error; }
|
||||
stage = "addresses step";
|
||||
}
|
||||
MEMBERS_APPEND("]}");
|
||||
if (rc != SQLITE_DONE) goto error;
|
||||
stage = "addresses finalize"; rc = sqlite3_finalize(addresses); addresses = NULL; if (rc != SQLITE_OK) goto error;
|
||||
stage = "JSON"; rc = SQLITE_NOMEM; if (strbuf_addf(&json, "]}") < 0) goto error;
|
||||
if (json.len >= buf_size) { rc = SQLITE_TOOBIG; goto error; }
|
||||
stage = "members step";
|
||||
}
|
||||
MEMBERS_APPEND("]");
|
||||
sqlite3_finalize(st);
|
||||
*out_len = (size_t)(w - buf);
|
||||
return 0;
|
||||
overflow:
|
||||
sqlite3_finalize(as); sqlite3_finalize(st); buf[0] = 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: members JSON exceeds output buffer bytes=%zu", CC_ID, buf_size);
|
||||
return -1;
|
||||
#undef MEMBERS_APPEND
|
||||
if (rc != SQLITE_DONE) goto error;
|
||||
stage = "members finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error;
|
||||
stage = "JSON"; rc = SQLITE_NOMEM; if (strbuf_addf(&json, "]") < 0) goto error;
|
||||
int result = json_copy(&json, buf, buf_size, out_len); strbuf_free(&json); return result;
|
||||
error:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "members JSON: ch=%s stage=%s rc=%d bytes=%zu capacity=%zu: %s",
|
||||
ch_id, stage, rc, json.len, buf_size, sqlite3_errmsg(cc->db));
|
||||
int finish = sqlite3_finalize(addresses);
|
||||
if (finish != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "members JSON: addresses cleanup rc=%d", finish);
|
||||
finish = sqlite3_finalize(st);
|
||||
if (finish != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "members JSON: cleanup rc=%d", finish);
|
||||
strbuf_free(&json); return -1;
|
||||
}
|
||||
|
||||
int chat_core_get_node_name(struct UTUN_INSTANCE* inst, uint64_t node_id, char* out, size_t sz) {
|
||||
|
||||
Reference in new issue
Block a user