diff --git a/src/chat/chat_core.c b/src/chat/chat_core.c index d0faca8c..73c9a8b5 100644 --- a/src/chat/chat_core.c +++ b/src/chat/chat_core.c @@ -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 @@ -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) {