|
|
|
|
@ -797,6 +797,21 @@ char* utun_bridge_get_channel_members_json(const char* channel_id) {
|
|
|
|
|
return json; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Не продолжаем сборку JSON после усечения строки: pos должен оставаться внутри буфера. */ |
|
|
|
|
static int member_detail_append(char* buf, size_t size, size_t* pos, const char* fmt, ...) { |
|
|
|
|
va_list ap; |
|
|
|
|
va_start(ap, fmt); |
|
|
|
|
int written = vsnprintf(buf + *pos, size - *pos, fmt, ap); |
|
|
|
|
va_end(ap); |
|
|
|
|
if (written < 0 || (size_t)written >= size - *pos) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "member detail: JSON append failed used=%zu capacity=%zu required=%d", |
|
|
|
|
*pos, size, written); |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
*pos += (size_t)written; |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
char* utun_bridge_get_member_detail_json(const char* channel_id, uint64_t node_id) { |
|
|
|
|
struct UTUN_INSTANCE* inst = instance_lite_get_instance(); |
|
|
|
|
sqlite3* db = chat_core_get_db(inst); |
|
|
|
|
@ -817,16 +832,50 @@ char* utun_bridge_get_member_detail_json(const char* channel_id, uint64_t node_i
|
|
|
|
|
int64_t last_seen = sqlite3_column_int64(st, 1); |
|
|
|
|
int64_t created = sqlite3_column_int64(st, 2); |
|
|
|
|
char* esc_name = json_escape_alloc(name && name[0] ? name : ""); |
|
|
|
|
pos += (size_t)snprintf(buf + pos, sizeof(buf) - pos, |
|
|
|
|
int rc = member_detail_append(buf, sizeof(buf), &pos, |
|
|
|
|
",\"n\":\"%s\",\"on\":0,\"ls\":%lld,\"cr\":%lld", |
|
|
|
|
esc_name, (long long)last_seen, (long long)created); |
|
|
|
|
u_free(esc_name); |
|
|
|
|
if (rc < 0) { sqlite3_finalize(st); return u_strdup("{}"); } |
|
|
|
|
} |
|
|
|
|
sqlite3_finalize(st); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); |
|
|
|
|
snprintf(buf + pos, sizeof(buf) - pos, ",\"adr\":["); pos += strlen(buf + pos); |
|
|
|
|
/* Родитель дерева приглашений относится к каналу; без подписи это ещё неизвестная связь. */ |
|
|
|
|
char sql[512]; |
|
|
|
|
snprintf(sql, sizeof(sql), |
|
|
|
|
"SELECT p.signed_by, length(p.signature), p.source, COALESCE(NULLIF(i.local_nick,''),n.name,'')" |
|
|
|
|
" FROM \"%s\" p LEFT JOIN \"%s\" i ON i.node_id=p.signed_by" |
|
|
|
|
" LEFT JOIN nodes n ON n.node_id=p.signed_by WHERE p.node_id=?", peers_tbl, peers_tbl); |
|
|
|
|
int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL); |
|
|
|
|
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id); |
|
|
|
|
if (rc == SQLITE_OK) rc = sqlite3_step(st); |
|
|
|
|
if (rc == SQLITE_ROW) { |
|
|
|
|
int known = sqlite3_column_type(st, 0) != SQLITE_NULL && sqlite3_column_int(st, 1) == 64 && |
|
|
|
|
sqlite3_column_int(st, 2) == 0; |
|
|
|
|
uint64_t inviter_id = (uint64_t)sqlite3_column_int64(st, 0); |
|
|
|
|
const char* inviter_name = known && inviter_id ? (const char*)sqlite3_column_text(st, 3) : ""; |
|
|
|
|
char* escaped = json_escape_alloc(inviter_name); |
|
|
|
|
if (!escaped) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "member invitation: name allocation failed ch=%s node=%016llx", |
|
|
|
|
channel_id, (unsigned long long)node_id); |
|
|
|
|
sqlite3_finalize(st); return u_strdup("{}"); |
|
|
|
|
} |
|
|
|
|
char inviter_json[32] = "null"; |
|
|
|
|
if (known) snprintf(inviter_json, sizeof(inviter_json), "%llu", (unsigned long long)inviter_id); |
|
|
|
|
int append_rc = member_detail_append(buf, sizeof(buf), &pos, |
|
|
|
|
",\"inviter_id\":%s,\"inviter_name\":\"%s\"", inviter_json, escaped); |
|
|
|
|
u_free(escaped); |
|
|
|
|
if (append_rc < 0) { sqlite3_finalize(st); return u_strdup("{}"); } |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "member invitation: ch=%s node=%016llx known=%d inviter=%016llx name=%s", |
|
|
|
|
channel_id, (unsigned long long)node_id, known, (unsigned long long)inviter_id, inviter_name); |
|
|
|
|
} else { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "member invitation: read failed ch=%s node=%016llx rc=%d: %s", |
|
|
|
|
channel_id, (unsigned long long)node_id, rc, sqlite3_errmsg(db)); |
|
|
|
|
} |
|
|
|
|
sqlite3_finalize(st); st = NULL; |
|
|
|
|
if (member_detail_append(buf, sizeof(buf), &pos, ",\"adr\":[") < 0) return u_strdup("{}"); |
|
|
|
|
|
|
|
|
|
if (sqlite3_prepare_v2(db, |
|
|
|
|
"SELECT family, protocol, address, port, socket_id, addr_type, COALESCE(rtt,0)" |
|
|
|
|
@ -857,15 +906,17 @@ char* utun_bridge_get_member_detail_json(const char* channel_id, uint64_t node_i
|
|
|
|
|
const char* pn; |
|
|
|
|
if (proto & 0x04) pn = "TCP+REALITY"; |
|
|
|
|
else switch (proto) { case 1: pn = "UDP"; break; case 2: pn = "TCP"; break; default: pn = "UDP/TCP"; break; } |
|
|
|
|
pos += (size_t)snprintf(buf + pos, sizeof(buf) - pos, |
|
|
|
|
if (member_detail_append(buf, sizeof(buf), &pos, |
|
|
|
|
"%s{\"fm\":%d,\"pt\":\"%s\",\"ad\":\"%s\",\"po\":%d,\"si\":%d,\"at\":%d,\"rt\":%d}", |
|
|
|
|
af ? "" : ",", family, pn, addr_str[0] ? addr_str : "?", port, sid, at, rtt); |
|
|
|
|
af ? "" : ",", family, pn, addr_str[0] ? addr_str : "?", port, sid, at, rtt) < 0) { |
|
|
|
|
sqlite3_finalize(st); return u_strdup("{}"); |
|
|
|
|
} |
|
|
|
|
af = 0; |
|
|
|
|
} |
|
|
|
|
sqlite3_finalize(st); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pos += (size_t)snprintf(buf + pos, sizeof(buf) - pos, "]}"); |
|
|
|
|
if (member_detail_append(buf, sizeof(buf), &pos, "]}") < 0) return u_strdup("{}"); |
|
|
|
|
return u_strdup(buf); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|