Browse Source

ffmpeg: add build script and CMake integration

topo_upd
Evgeny 3 months ago
parent
commit
2d53b3120f
  1. 2
      src/chat/chat_channel.c
  2. 4
      src/chat/chat_profile.c
  3. 10
      src/chat/chat_sync.c
  4. 100
      src/chat/member_sync.c
  5. 3
      src/chat/member_sync.h
  6. 2
      src/routing_layer/topo_group.c
  7. 55
      src/routing_layer/topo_node_sqlite.c
  8. 3
      src/routing_layer/topo_node_sqlite.h
  9. 7
      tools/chatgui/CMakeLists.txt
  10. 1
      tools/chatgui/ffmpeg/.gitignore
  11. 269
      tools/chatgui/ffmpeg/build_ffmpeg.sh
  12. 28
      tools/chatgui/src/memberpropsdialog.cpp
  13. 2
      tools/chatgui/src/memberpropsdialog.h

2
src/chat/chat_channel.c

@ -146,7 +146,7 @@ void chat_core_create_channel(struct chat_channel_create* req) {
g_cc.inst->name[0] ? g_cc.inst->name : "");
int mrc = member_sync_put(g_cc.inst, req->channel_id, myid,
g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey,
join_sig, join_ts, NULL, 0, juser3, my_addrs, my_addr_cnt);
join_sig, join_ts, NULL, 0, juser3, my_addrs, my_addr_cnt, NULL, NULL, 0);
if (mrc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_sync_put(self) FAILED ch=%s rc=%d",
CC_ID, req->channel_id, mrc);

4
src/chat/chat_profile.c

@ -67,10 +67,10 @@ void chat_core_update_my_name(const char* name) {
}
char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", name);
int r1 = topo_node_sqlite_member_put(g_cc.db, ch, myid, new_sig, join_ts, NULL, 0,
g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey, juser, NULL);
g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey, juser, NULL, NULL, NULL, 0);
if (r1 != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(my_name) FAILED ch=%s rc=%d", CC_ID, ch, r1);
int r2 = member_sync_put(g_cc.inst, ch, myid, g_cc.inst->my_keys.public_key,
g_cc.inst->my_ed25519_pubkey, new_sig, join_ts, NULL, 0, juser, NULL, 0);
g_cc.inst->my_ed25519_pubkey, new_sig, join_ts, NULL, 0, juser, NULL, 0, NULL, NULL, 0);
if (r2 != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_sync_put(my_name) FAILED ch=%s rc=%d", CC_ID, ch, r2);
uint8_t evt[65]; uint8_t cl = (uint8_t)strlen(ch);
evt[0] = cl; memcpy(evt + 1, ch, cl);

10
src/chat/chat_sync.c

@ -928,7 +928,7 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer,
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] INFO_RESP DONE peer=%016llx: %d links, %d written",
CS_ID, (unsigned long long)peer, lk_count, lk_written);
}
int rc = topo_node_sqlite_member_put(vdb, ch_id, peer, inviter_join_sig, inviter_join_ts, inviter_update_sig, inviter_update_ts, inv_x25519, inv_ed, inv_userinfo, inviter_join_sig);
int rc = topo_node_sqlite_member_put(vdb, ch_id, peer, inviter_join_sig, inviter_join_ts, inviter_update_sig, inviter_update_ts, inv_x25519, inv_ed, inv_userinfo, inviter_join_sig, NULL, NULL, 0);
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(inviter) FAILED ch=%s peer=%016llx rc=%d", CS_ID, ch_id, (unsigned long long)peer, rc);
char inv_name[128]; json_flat_get(inv_userinfo, "name", inv_name, sizeof(inv_name));
topo_node_sqlite_node_update_verified(vdb, peer, inv_name, inv_x25519, inv_ed, inviter_join_ts, ntp_time_get_seconds(cs->inst));
@ -1038,7 +1038,7 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer,
}
sqlite3* db = cs->inst->topo_sqlite_db;
int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, joiner_userinfo, NULL);
int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, joiner_userinfo, NULL, NULL, NULL, 0);
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(joiner) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc);
{ char j_name[128]; json_flat_get(joiner_userinfo, "name", j_name, sizeof(j_name));
topo_node_sqlite_node_update_verified(db, node_id, j_name, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); }
@ -1193,7 +1193,7 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer,
}
}
int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL);
int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL, NULL, NULL, 0);
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(welcome) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc);
{ char pn[256]; json_flat_get(peer_userinfo, "name", pn, sizeof(pn));
topo_node_sqlite_node_update_verified(db, node_id, pn, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); }
@ -1240,7 +1240,7 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer,
const uint8_t* my_x25 = cs->inst->my_keys.public_key;
const uint8_t* my_ed = cs->inst->my_ed25519_pubkey;
const char* my_name = cs->inst->name[0] ? cs->inst->name : "";
int src = topo_node_sqlite_member_put(db, ch_id, myid, NULL, 0, NULL, 0, my_x25, my_ed, my_name, NULL);
int src = topo_node_sqlite_member_put(db, ch_id, myid, NULL, 0, NULL, 0, my_x25, my_ed, my_name, NULL, NULL, NULL, 0);
if (src != 0)
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(self) FAILED in WELCOME ch=%s rc=%d", CS_ID, ch_id, src);
else
@ -1306,7 +1306,7 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer,
sqlite3* db = cs->inst->topo_sqlite_db;
int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL);
int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL, NULL, NULL, 0);
if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(peer_upsert) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc);
{ char pn2[128]; json_flat_get(peer_userinfo, "name", pn2, sizeof(pn2));
topo_node_sqlite_node_update_verified(db, node_id, pn2, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); }

100
src/chat/member_sync.c

@ -49,6 +49,7 @@ static void _compute_member_hash(uint64_t node_id, const uint8_t* x25519,
const uint8_t* join_sig, uint64_t join_ts,
const uint8_t* update_sig, uint64_t update_ts,
const uint8_t* addrs_data, int addr_count,
const char* adm_tags, const uint8_t* adm_tags_sig,
uint8_t hash_out[MT_HASH_SIZE]) {
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
@ -81,6 +82,11 @@ static void _compute_member_hash(uint64_t node_id, const uint8_t* x25519,
}
}
uint8_t atl = adm_tags ? (uint8_t)strnlen(adm_tags, 255) : 0;
EVP_DigestUpdate(ctx, &atl, 1);
if (atl) EVP_DigestUpdate(ctx, adm_tags, atl);
if (adm_tags_sig) EVP_DigestUpdate(ctx, adm_tags_sig, 64); else { static const uint8_t z64[64]; EVP_DigestUpdate(ctx, z64, 64); }
EVP_DigestFinal_ex(ctx, hash_out, NULL);
EVP_MD_CTX_free(ctx);
}
@ -100,7 +106,7 @@ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level,
char sql[512]; snprintf(sql, sizeof(sql),
"SELECT node_id, x25519_pubkey, ed25519_pubkey,"
" join_sig, join_ts, update_sig, update_ts"
" join_sig, join_ts, update_sig, update_ts, adm_tags, adm_tags_sig"
" FROM \"%s\""
" WHERE (node_id & %lld) == %lld ORDER BY node_id ASC",
peers_tbl, (long long)mask, (long long)prefix64);
@ -117,9 +123,11 @@ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level,
uint64_t jts = (uint64_t)sqlite3_column_int64(stmt, 4);
const uint8_t* usig = (const uint8_t*)sqlite3_column_blob(stmt, 5);
uint64_t uts = (uint64_t)sqlite3_column_int64(stmt, 6);
const char* atags = (const char*)sqlite3_column_text(stmt, 7);
const uint8_t* atsig = (const uint8_t*)sqlite3_column_blob(stmt, 8);
if (!x25 || !ed) continue;
uint8_t mh[MT_HASH_SIZE];
_compute_member_hash(nid, x25, ed, sig, jts, usig, uts, NULL, 0, mh);
_compute_member_hash(nid, x25, ed, sig, jts, usig, uts, NULL, 0, atags, atsig, mh);
EVP_DigestUpdate(sha_ctx, mh, MT_HASH_SIZE);
count++;
}
@ -141,7 +149,7 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level,
char sql[512]; snprintf(sql, sizeof(sql),
"SELECT node_id, x25519_pubkey, ed25519_pubkey,"
" join_sig, join_ts, update_sig, update_ts, userinfo"
" join_sig, join_ts, update_sig, update_ts, userinfo, adm_tags, adm_tags_sig"
" FROM \"%s\""
" WHERE (node_id & %lld) == %lld ORDER BY node_id ASC",
peers_tbl, (long long)mask, (long long)prefix);
@ -162,8 +170,11 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level,
const uint8_t* usig = (const uint8_t*)sqlite3_column_blob(stmt, 5);
uint64_t uts = (uint64_t)sqlite3_column_int64(stmt, 6);
const char* nm = (const char*)sqlite3_column_text(stmt, 7);
const char* atags = (const char*)sqlite3_column_text(stmt, 8);
const uint8_t* atsig = (const uint8_t*)sqlite3_column_blob(stmt, 9);
if (!x25 || !ed) continue;
uint8_t nl = nm ? (uint8_t)strnlen(nm, 255) : 0;
uint8_t atl = atags ? (uint8_t)strnlen(atags, 255) : 0;
sqlite3_stmt* as = NULL;
sqlite3_prepare_v2(db,
@ -189,7 +200,7 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level,
}
uint8_t flags = (sig && jts) ? PEERS_FLAG_HAS_JOIN : 0;
size_t need = 8 + 32 + 32 + 1 + (flags ? 72ULL : 0ULL) + 64 + 8 + 1 + (size_t)nl + 1 + (size_t)addr_off;
size_t need = 8 + 32 + 32 + 1 + (flags ? 72ULL : 0ULL) + 64 + 8 + 1 + (size_t)nl + 1 + (size_t)atl + 64 + 1 + (size_t)addr_off;
if (off + need > *len) { sqlite3_finalize(stmt); return -2; }
memcpy(buf + off, &nid, 8); off += 8;
memcpy(buf + off, x25, 32); off += 32;
@ -208,6 +219,10 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level,
}
buf[off++] = nl;
if (nl) { memcpy(buf + off, nm, nl); off += nl; }
buf[off++] = atl;
if (atl) { memcpy(buf + off, atags, atl); off += atl; }
if (atsig && sqlite3_column_bytes(stmt, 9) >= 64) { memcpy(buf + off, atsig, 64); off += 64; }
else { memset(buf + off, 0, 64); off += 64; }
buf[off++] = (uint8_t)addr_count;
memcpy(buf + off, addrs, (size_t)addr_off); off += (size_t)addr_off;
(*cnt)++;
@ -227,6 +242,7 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer,
uint16_t count; memcpy(&count, data, 2);
DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: apply_items ns=%s count=%u from=%016llx", MS_ID, ns, count, (unsigned long long)from_peer);
const uint8_t* mp = data + 2; size_t mrem = len - 2;
sqlite3* db = _db(inst);
for (uint16_t i = 0; i < count && mrem >= 148; i++) {
uint64_t nid; memcpy(&nid, mp, 8); mp += 8; mrem -= 8;
@ -239,13 +255,19 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer,
} else {
/* lookup join from local DB */
uint8_t buf_jsig[64]; uint64_t buf_jts;
if (topo_node_sqlite_member_get_join(_db(inst), ns, nid, buf_jsig, &buf_jts) == 0) { jsig = buf_jsig; jts = buf_jts; }
if (db && topo_node_sqlite_member_get_join(db, ns, nid, buf_jsig, &buf_jts) == 0) { jsig = buf_jsig; jts = buf_jts; }
}
if (mrem < 72) break;
const uint8_t* usig = mp; mp += 64; mrem -= 64; uint64_t uts; memcpy(&uts, mp, 8); mp += 8; mrem -= 8;
uint8_t nl = *mp++; mrem--;
char nm[256] = "";
if (nl && mrem >= nl) { memcpy(nm, mp, nl); nm[nl] = '\0'; mp += nl; mrem -= nl; }
if (mrem < 1) break;
uint8_t atl = *mp++; mrem--;
char atags[256] = "";
const uint8_t* atsig = NULL;
if (atl && mrem >= atl) { memcpy(atags, mp, atl); atags[atl] = '\0'; mp += atl; mrem -= atl; }
if (mrem >= 64) { atsig = mp; mp += 64; mrem -= 64; }
uint8_t ac = *mp++; mrem--;
const uint8_t* addrs = mp;
int consumed = 0;
@ -276,8 +298,64 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer,
}
}
}
member_sync_put(inst, ns, nid, x25, ed, jsig, jts, usig, uts, nm, addrs, (int)ac);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] member_sync recv ns=%s nid=0x%016llx ac=%d", MS_ID, ns, (unsigned long long)nid, ac);
/* verify adm_tags_sig and version check */
int adm_ver = 0, adm_storage = 0;
if (atags[0] && atsig) {
const char* ver_s = strstr(atags, "ver=");
if (ver_s) adm_ver = atoi(ver_s + 4);
adm_storage = strstr(atags, "storage=yes") ? 1 : 0;
uint8_t ch_ed_pub[32] = {0};
if (db && topo_node_sqlite_channel_get(db, ns, NULL, 0, NULL, NULL, ch_ed_pub, NULL) == 0) {
uint8_t amsg[264]; size_t aoff = 0;
size_t atl2 = strlen(atags);
if (atl2 > 191) atl2 = 191;
memcpy(amsg + aoff, atags, atl2); aoff += atl2;
memcpy(amsg + aoff, &nid, 8); aoff += 8;
EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ch_ed_pub, 32);
if (pkey) {
EVP_MD_CTX* vctx = EVP_MD_CTX_new();
if (vctx) {
int ok = (EVP_DigestVerifyInit(vctx, NULL, NULL, NULL, pkey) == 1)
&& (EVP_DigestVerify(vctx, atsig, 64, amsg, aoff) == 1);
if (!ok) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_sync invalid adm_tags_sig node=0x%016llx ns=%s tags=%s",
MS_ID, (unsigned long long)nid, ns, atags);
adm_ver = -1; /* skip this record */
}
EVP_MD_CTX_free(vctx);
}
EVP_PKEY_free(pkey);
}
} else {
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: cannot verify adm_tags_sig — no channel key for ns=%s", MS_ID, ns);
adm_ver = -1;
}
/* check version is strictly greater than local */
if (adm_ver > 0 && db) {
char peers_tbl[128]; _peers_table(ns, peers_tbl, sizeof(peers_tbl));
sqlite3_stmt* av = NULL;
char asql[256]; snprintf(asql, sizeof(asql), "SELECT adm_tags FROM \"%s\" WHERE node_id=?", peers_tbl);
if (sqlite3_prepare_v2(db, asql, -1, &av, NULL) == SQLITE_OK) {
sqlite3_bind_int64(av, 1, (sqlite3_int64)nid);
if (sqlite3_step(av) == SQLITE_ROW) {
const char* local_tags = (const char*)sqlite3_column_text(av, 0);
if (local_tags) {
int local_ver = 0; const char* lv = strstr(local_tags, "ver=");
if (lv) local_ver = atoi(lv + 4);
if (adm_ver <= local_ver) {
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: stale adm_tags node=0x%016llx ns=%s ver=%d <= local=%d — skipping",
MS_ID, (unsigned long long)nid, ns, adm_ver, local_ver);
adm_ver = -1;
}
}
}
sqlite3_finalize(av);
}
}
}
if (adm_ver < 0) { mp += consumed; mrem -= (size_t)consumed; continue; }
member_sync_put(inst, ns, nid, x25, ed, jsig, jts, usig, uts, nm, addrs, (int)ac, atags[0] ? atags : NULL, atsig, adm_storage);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] member_sync recv ns=%s nid=0x%016llx ac=%d adm_ver=%d storage=%d", MS_ID, ns, (unsigned long long)nid, ac, adm_ver, adm_storage);
mp += consumed; mrem -= (size_t)consumed;
}
if (count > 0)
@ -362,14 +440,16 @@ int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id,
const uint8_t* join_sig, uint64_t join_ts,
const uint8_t* update_sig, uint64_t update_ts,
const char* userinfo,
const uint8_t* addrs_data, int addr_count) {
const uint8_t* addrs_data, int addr_count,
const char* adm_tags, const uint8_t* adm_tags_sig, int storage) {
if (!inst || !ch_id) return -1;
DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: put ch=%s nid=%016llx userinfo=%s ac=%d", MS_ID, ch_id, (unsigned long long)member_id, userinfo ? userinfo : "", addr_count);
DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: put ch=%s nid=%016llx userinfo=%s ac=%d adm_tags=%s storage=%d", MS_ID, ch_id, (unsigned long long)member_id, userinfo ? userinfo : "", addr_count, adm_tags ? adm_tags : "", storage);
sqlite3* db = _db(inst); if (!db) return -1;
int rc = topo_node_sqlite_member_put(db, ch_id, member_id,
join_sig, join_ts, update_sig, update_ts,
x25519, ed25519, userinfo, NULL);
x25519, ed25519, userinfo, NULL,
adm_tags, adm_tags_sig, storage);
if (rc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put FAILED ch=%s id=0x%016llx userinfo=%s rc=%d",
MS_ID, ch_id, (unsigned long long)member_id, userinfo ? userinfo : "", rc);

3
src/chat/member_sync.h

@ -102,7 +102,8 @@ int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id,
const uint8_t* join_sig, uint64_t join_ts,
const uint8_t* update_sig, uint64_t update_ts,
const char* userinfo,
const uint8_t* addrs_data, int addr_count);
const uint8_t* addrs_data, int addr_count,
const char* adm_tags, const uint8_t* adm_tags_sig, int storage);
/* Удалить мембера из канала и пересчитать дерево. */
int member_sync_del(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t member_id);

2
src/routing_layer/topo_group.c

@ -669,7 +669,7 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from
if (sdb) {
topo_node_sqlite_node_put(sdb, nodeinfo1, ntp_time_get_seconds(group->instance));
if (group->group_type == TOPO_GROUP_TYPE_CHAT && group->channel_id[0]) {
topo_node_sqlite_member_put(sdb, group->channel_id, node_id, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL);
topo_node_sqlite_member_put(sdb, group->channel_id, node_id, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 0);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "SQLite member_put: ch=%s node=%016llx", group->channel_id, (unsigned long long)node_id);
}
topo_node_sqlite_nodeinfo_updated(sdb, node_id);

55
src/routing_layer/topo_node_sqlite.c

@ -280,6 +280,7 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
2=EIM NAT 3=(не исп.) 4=суперузел(DIRECT+"supernode=yes") */
" node_RTT INTEGER," /* RTT до узла в мс, часть индекса (node_type, node_RTT) */
" connected INTEGER NOT NULL DEFAULT 0," /* 1 = было активное подключение к узлу */
" storage INTEGER NOT NULL DEFAULT 0," /* 1 = приоритетный хранитель аттачей */
" local_nick TEXT" /* локальный ник (GUI only, не синхронизируется) */
")";
@ -303,6 +304,16 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
u_free(idx_sql);
}
int sidx_sz = snprintf(NULL, 0,
"CREATE INDEX IF NOT EXISTS \"idx_%s_storage\" ON \"%s\" (storage)", peers_tbl, peers_tbl);
char* sidx_sql = u_malloc(sidx_sz + 1);
if (sidx_sql) {
snprintf(sidx_sql, sidx_sz + 1,
"CREATE INDEX IF NOT EXISTS \"idx_%s_storage\" ON \"%s\" (storage)", peers_tbl, peers_tbl);
sqlite3_exec(db, sidx_sql, NULL, NULL, NULL);
u_free(sidx_sql);
}
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"INSERT OR REPLACE INTO channels(channel_id, name, owner_node_id,"
@ -326,7 +337,8 @@ int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t no
const uint8_t* join_sig, uint64_t join_ts,
const uint8_t* update_sig, uint64_t update_ts,
const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey, const char* userinfo,
const uint8_t* creator_sig) {
const uint8_t* creator_sig,
const char* adm_tags, const uint8_t* adm_tags_sig, int storage) {
if (!db || !channel_id || !x25519_pubkey || !ed25519_pubkey) return -1;
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
char sql[256];
@ -349,7 +361,23 @@ int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t no
if (creator_sig) sqlite3_bind_blob(stmt, 9, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
else sqlite3_bind_null(stmt, 9);
int rc = sqlite3_step(stmt); sqlite3_finalize(stmt);
return rc == SQLITE_DONE ? 0 : -1;
if (rc != SQLITE_DONE) return -1;
if (adm_tags || adm_tags_sig || storage > 0) {
snprintf(sql, sizeof(sql),
"UPDATE \"%s\" SET adm_tags=?, adm_tags_sig=?, storage=? WHERE node_id=?", peers_tbl);
sqlite3_stmt* u = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &u, NULL) == SQLITE_OK) {
if (adm_tags && adm_tags[0]) sqlite3_bind_text(u, 1, adm_tags, -1, SQLITE_STATIC);
else sqlite3_bind_null(u, 1);
if (adm_tags_sig) sqlite3_bind_blob(u, 2, adm_tags_sig, 64, SQLITE_STATIC);
else sqlite3_bind_null(u, 2);
sqlite3_bind_int(u, 3, storage);
sqlite3_bind_int64(u, 4, (sqlite3_int64)node_id);
sqlite3_step(u); sqlite3_finalize(u);
}
}
return 0;
}
int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id,
@ -440,7 +468,7 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
char sql[512];
snprintf(sql, sizeof(sql),
"SELECT node_id, x25519_pubkey, ed25519_pubkey,"
" join_sig, join_ts, update_sig, update_ts, userinfo"
" join_sig, join_ts, update_sig, update_ts, userinfo, adm_tags, adm_tags_sig"
" FROM \"%s\" ORDER BY node_id ASC", peers_tbl);
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
@ -458,8 +486,11 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
const uint8_t* update_sig = (const uint8_t*)sqlite3_column_blob(stmt, 5);
uint64_t update_ts = (uint64_t)sqlite3_column_int64(stmt, 6);
const char* puserinfo = (const char*)sqlite3_column_text(stmt, 7);
const char* adm_tags_s = (const char*)sqlite3_column_text(stmt, 8);
const uint8_t* adm_tags_sig_b = (const uint8_t*)sqlite3_column_blob(stmt, 9);
if (!x25519 || !ed_pub) continue;
uint8_t nl = puserinfo ? (uint8_t)strnlen(puserinfo, 255) : 0;
uint8_t atl = adm_tags_s ? (uint8_t)strnlen(adm_tags_s, 255) : 0;
sqlite3_stmt* ast = NULL;
snprintf(sql, sizeof(sql),
@ -488,7 +519,7 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
sqlite3_finalize(ast);
uint8_t flags = (join_sig && join_ts) ? PEERS_FLAG_HAS_JOIN : 0;
size_t peer_needed = 8 + 32 + 32 + 1 + (flags & PEERS_FLAG_HAS_JOIN ? 64+8 : 0) + 64 + 8 + 1 + (size_t)nl + 1;
size_t peer_needed = 8 + 32 + 32 + 1 + (flags & PEERS_FLAG_HAS_JOIN ? 64+8 : 0) + 64 + 8 + 1 + (size_t)nl + 1 + (size_t)atl + 64 + 1;
size_t addr_sz = off - addr_start;
if (addr_cnt > 0) {
if (off + peer_needed > buf_sz) { sqlite3_finalize(stmt); return -2; }
@ -516,6 +547,10 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
buf[off++] = nl;
if (nl) { memcpy(buf + off, puserinfo, nl); off += nl; }
buf[off++] = addr_cnt;
buf[off++] = atl;
if (atl) { memcpy(buf + off, adm_tags_s, atl); off += atl; }
if (adm_tags_sig_b && sqlite3_column_bytes(stmt, 9) >= 64) { memcpy(buf + off, adm_tags_sig_b, 64); off += 64; }
else { memset(buf + off, 0, 64); off += 64; }
off += addr_sz;
(*cnt_ptr)++;
}
@ -593,7 +628,7 @@ int topo_node_sqlite_nodeinfo_updated(sqlite3* db, uint64_t node_id) {
while (sqlite3_step(s) == SQLITE_ROW) {
const char* tbl = (const char*)sqlite3_column_text(s, 0);
int node_type = base_type;
int node_type = base_type; int is_storage = 0;
if (best_at == ADDR_TYPE_DIRECT && base_type == 1) {
snprintf(sql, sizeof(sql),
"SELECT adm_tags FROM \"%s\" WHERE node_id=?", tbl);
@ -602,18 +637,22 @@ int topo_node_sqlite_nodeinfo_updated(sqlite3* db, uint64_t node_id) {
sqlite3_bind_int64(a, 1, (sqlite3_int64)node_id);
if (sqlite3_step(a) == SQLITE_ROW) {
const char* tags = (const char*)sqlite3_column_text(a, 0);
if (tags && strstr(tags, "supernode=yes")) node_type = 4;
if (tags) {
if (strstr(tags, "supernode=yes")) node_type = 4;
if (strstr(tags, "storage=yes")) is_storage = 1;
}
}
sqlite3_finalize(a);
}
}
snprintf(sql, sizeof(sql),
"UPDATE \"%s\" SET node_type=? WHERE node_id=?", tbl);
"UPDATE \"%s\" SET node_type=?, storage=? WHERE node_id=?", tbl);
sqlite3_stmt* u = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &u, NULL) == SQLITE_OK) {
sqlite3_bind_int(u, 1, node_type);
sqlite3_bind_int64(u, 2, (sqlite3_int64)node_id);
sqlite3_bind_int(u, 2, is_storage);
sqlite3_bind_int64(u, 3, (sqlite3_int64)node_id);
sqlite3_step(u); sqlite3_finalize(u);
}
}

3
src/routing_layer/topo_node_sqlite.h

@ -30,7 +30,8 @@ int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t no
const uint8_t* join_sig, uint64_t join_ts,
const uint8_t* update_sig, uint64_t update_ts,
const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey, const char* userinfo,
const uint8_t* creator_sig);
const uint8_t* creator_sig,
const char* adm_tags, const uint8_t* adm_tags_sig, int storage);
int topo_node_sqlite_member_get_join(sqlite3* db, const char* channel_id, uint64_t node_id,
uint8_t* join_sig_out, uint64_t* join_ts_out);

7
tools/chatgui/CMakeLists.txt

@ -70,9 +70,12 @@ if(NOT FFMPEG_FOUND)
endif()
endif()
if(NOT FFMPEG_FOUND)
message(FATAL_ERROR "FFmpeg not found at ${FFMPEG_INSTALL_DIR}. Run: cd ffmpeg && ./build_ffmpeg.sh")
message(WARNING "FFmpeg not found at ${FFMPEG_INSTALL_DIR}. Video transcoding disabled. Run ./ffmpeg/build_ffmpeg.sh to enable.")
set(FFMPEG_TARGET "")
set(FFMPEG_LIBRARIES "")
set(FFMPEG_INCLUDE_DIRS "")
endif()
if(UNIX AND NOT APPLE)
if(UNIX AND NOT APPLE AND FFMPEG_FOUND)
set(CMAKE_BUILD_RPATH "${FFMPEG_INSTALL_DIR}/lib")
set(CMAKE_INSTALL_RPATH "${FFMPEG_INSTALL_DIR}/lib")
endif()

1
tools/chatgui/ffmpeg/.gitignore vendored

@ -0,0 +1 @@
/ffmpeg_build/

269
tools/chatgui/ffmpeg/build_ffmpeg.sh

@ -0,0 +1,269 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BUILD_DIR="$SCRIPT_DIR/ffmpeg_build"
SRC_DIR="$BUILD_DIR/src"
INSTALL_DIR="$BUILD_DIR/install"
JOBS=$(nproc 2>/dev/null || echo 4)
case "$(uname -s)" in
Linux) PLATFORM=linux ;;
MINGW*|MSYS*|CYGWIN*) PLATFORM=windows ;;
*) echo "Unsupported platform: $(uname -s)"; exit 1 ;;
esac
log() { echo "[FFmpeg] $1"; }
die() { echo "[FFmpeg] ERROR: $1" >&2; exit 1; }
check_cmd() { command -v "$1" >/dev/null 2>&1 || die "$1 not found. Install it first."; }
do_clone() {
local name="$1" url="$2" tag="$3"
if [ ! -d "$SRC_DIR/$name" ]; then
log "Clone $name..."
for i in 1 2 3; do
git clone --depth 1 --branch "$tag" "$url" "$SRC_DIR/$name" 2>&1 && break
log "Clone attempt $i failed, retrying..."
rm -rf "$SRC_DIR/$name"
sleep 2
done
[ -d "$SRC_DIR/$name/.git" ] || die "Failed to clone $name after 3 attempts"
else
log "Skip clone $name: already exists"
fi
}
# ============================================================================
# Step 0: System dependencies
# ============================================================================
if [ "$PLATFORM" = "linux" ]; then
MISSING=""
for pkg in build-essential cmake nasm pkg-config meson ninja-build git; do
dpkg -s "$pkg" >/dev/null 2>&1 || MISSING="$MISSING $pkg"
done
if [ -n "$MISSING" ]; then
log "Installing build tools:$MISSING"
sudo apt update -qq && sudo apt install -y $MISSING
fi
HW_DEPS="libva-dev libvdpau-dev"
for pkg in $HW_DEPS; do
dpkg -s "$pkg" >/dev/null 2>&1 || { log "Installing $pkg (HW accel)"; sudo apt install -y $pkg; }
done
else
check_cmd pacman
for pkg in mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja mingw-w64-ucrt-x86_64-nasm \
mingw-w64-ucrt-x86_64-pkg-config mingw-w64-ucrt-x86_64-meson; do
pacman -Qi "$pkg" >/dev/null 2>&1 || { log "Installing $pkg"; pacman -S --noconfirm "$pkg"; }
done
fi
check_cmd cmake
check_cmd nasm
check_cmd pkg-config
check_cmd meson
check_cmd git
# ============================================================================
# Step 1: Codec libraries (static .a, built with -fPIC)
# ============================================================================
mkdir -p "$SRC_DIR" "$INSTALL_DIR/lib/pkgconfig"
export PKG_CONFIG_PATH="$INSTALL_DIR/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
# --- x264 (H.264 encoder) ---
if [ ! -f "$INSTALL_DIR/lib/pkgconfig/x264.pc" ]; then
do_clone x264 https://code.videolan.org/videolan/x264.git stable
cd "$SRC_DIR/x264"
log "Configure x264..."
./configure --prefix="$INSTALL_DIR" --enable-static --disable-shared \
--disable-opencl --disable-avs --disable-swscale --disable-lavf \
--disable-ffms --disable-gpac --disable-lsmash \
--extra-cflags="-fPIC" --disable-cli
log "Build x264..."
make -j"$JOBS"
make install
log "x264 OK"
else
log "Skip x264: already built"
fi
# --- x265 (H.265/HEVC encoder) ---
if [ ! -f "$INSTALL_DIR/lib/pkgconfig/x265.pc" ]; then
do_clone x265 https://bitbucket.org/multicoreware/x265_git.git master
cd "$SRC_DIR/x265"
mkdir -p build && cd build
log "Configure x265..."
cmake ../source -G "Unix Makefiles" \
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DENABLE_CLI=OFF -DENABLE_SHARED=OFF \
-DHIGH_BIT_DEPTH=OFF -DENABLE_HDR10_PLUS=OFF
log "Build x265..."
cmake --build . -j"$JOBS"
cmake --install .
cat > "$INSTALL_DIR/lib/pkgconfig/x265.pc" <<EOF
prefix=$INSTALL_DIR
exec_prefix=\${prefix}
libdir=\${exec_prefix}/lib
includedir=\${prefix}/include
Name: x265
Description: H.265/HEVC video encoder
Version: 4.1
Libs: -L\${libdir} -lx265
Libs.private: -lstdc++ -lm -lpthread
Cflags: -I\${includedir}
EOF
log "x265 OK"
else
log "Skip x265: already built"
fi
# --- libvpx (VP8/VP9 encoder) ---
if [ ! -f "$INSTALL_DIR/lib/pkgconfig/vpx.pc" ]; then
do_clone libvpx https://chromium.googlesource.com/webm/libvpx main
cd "$SRC_DIR/libvpx"
log "Configure libvpx..."
./configure --prefix="$INSTALL_DIR" --enable-static --disable-shared \
--enable-pic --enable-vp8 --enable-vp9 --enable-vp9-highbitdepth \
--disable-examples --disable-tools --disable-docs --disable-unit-tests
log "Build libvpx..."
make -j"$JOBS"
make install
log "libvpx OK"
else
log "Skip libvpx: already built"
fi
# --- SVT-AV1 (fast AV1 encoder) ---
if [ ! -f "$INSTALL_DIR/lib/pkgconfig/SvtAv1Enc.pc" ]; then
do_clone SVT-AV1 https://gitlab.com/AOMediaCodec/SVT-AV1.git v2.3.0
cd "$SRC_DIR/SVT-AV1"
mkdir -p build && cd build
log "Configure SVT-AV1..."
cmake .. -G "Unix Makefiles" \
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" \
-DBUILD_SHARED_LIBS=OFF -DBUILD_APPS=OFF \
-DBUILD_TESTING=OFF -DBUILD_DEC=OFF
log "Build SVT-AV1..."
cmake --build . -j"$JOBS"
cmake --install .
log "SVT-AV1 OK"
else
log "Skip SVT-AV1: already built"
fi
# --- libaom (reference AV1 encoder) ---
if [ ! -f "$INSTALL_DIR/lib/pkgconfig/aom.pc" ]; then
do_clone libaom https://aomedia.googlesource.com/aom v3.12.0
cd "$SRC_DIR/libaom"
mkdir -p build && cd build
log "Configure libaom..."
cmake .. -G "Unix Makefiles" \
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" \
-DBUILD_SHARED_LIBS=OFF -DENABLE_TESTS=OFF \
-DENABLE_TOOLS=OFF -DENABLE_DOCS=OFF \
-DENABLE_NASM=ON
log "Build libaom..."
cmake --build . -j"$JOBS"
cmake --install .
log "libaom OK"
else
log "Skip libaom: already built"
fi
# --- dav1d (fast AV1 decoder) ---
if [ ! -f "$INSTALL_DIR/lib/pkgconfig/dav1d.pc" ]; then
do_clone dav1d https://code.videolan.org/videolan/dav1d.git 1.5.1
cd "$SRC_DIR/dav1d"
log "Configure dav1d..."
meson setup build --prefix="$INSTALL_DIR" --buildtype release --libdir lib \
-Ddefault_library=static -Denable_tools=false -Denable_tests=false -Denable_docs=false
log "Build dav1d..."
meson compile -C build -j"$JOBS"
meson install -C build
log "dav1d OK"
else
log "Skip dav1d: already built"
fi
# --- nv-codec-headers (NVIDIA NVENC/NVDEC headers, no build needed) ---
if [ ! -f "$INSTALL_DIR/include/ffnvcodec/nvEncodeAPI.h" ]; then
do_clone nv-codec-headers https://github.com/FFmpeg/nv-codec-headers.git master
cd "$SRC_DIR/nv-codec-headers"
log "Install nv-codec-headers..."
make install PREFIX="$INSTALL_DIR"
log "nv-codec-headers OK"
else
log "Skip nv-codec-headers: already installed"
fi
# ============================================================================
# Step 2: FFmpeg
# ============================================================================
if [ -f "$INSTALL_DIR/lib/pkgconfig/libavcodec.pc" ]; then
log "FFmpeg already built, skipping."
log "Done: $INSTALL_DIR"
exit 0
fi
FFMPEG_SRC="$SRC_DIR/ffmpeg"
if [ ! -d "$FFMPEG_SRC" ]; then
log "Download FFmpeg 7.1.2..."
FFMPEG_TARBALL="$SRC_DIR/ffmpeg-7.1.2.tar.xz"
if [ ! -f "$FFMPEG_TARBALL" ]; then
for url in \
"https://ffmpeg.org/releases/ffmpeg-7.1.2.tar.xz" \
"https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.1.2.tar.gz"; do
curl -L --retry 3 -o "$FFMPEG_TARBALL" "$url" 2>&1 && break
log "Download from $url failed, trying next..."
rm -f "$FFMPEG_TARBALL"
done
[ -f "$FFMPEG_TARBALL" ] || die "Failed to download FFmpeg tarball"
log "Download OK ($(du -h "$FFMPEG_TARBALL" | cut -f1))"
fi
log "Extract FFmpeg..."
mkdir -p "$FFMPEG_SRC"
tar -xf "$FFMPEG_TARBALL" -C "$FFMPEG_SRC" --strip-components=1
rm -f "$FFMPEG_TARBALL"
fi
cd "$SRC_DIR/ffmpeg"
log "Configure FFmpeg..."
CONFIGURE_ARGS=(
--prefix="$INSTALL_DIR"
--enable-shared --disable-static
--enable-gpl --enable-version3
--pkg-config-flags=--static
--extra-cflags="-I$INSTALL_DIR/include"
--extra-ldflags="-L$INSTALL_DIR/lib"
--extra-libs="-lpthread -lm -ldl"
--enable-libx264 --enable-libx265
--enable-libvpx --enable-libsvtav1 --enable-libaom
--enable-libdav1d
--enable-nvenc --enable-cuda --enable-cuvid
--enable-vaapi --enable-vdpau
--disable-programs --disable-doc --disable-avdevice
--disable-ffprobe --disable-ffplay --disable-ffmpeg
--disable-debug --enable-optimizations
)
if [ "$PLATFORM" = "windows" ]; then
CONFIGURE_ARGS+=(
--enable-d3d11va --enable-dxva2
)
fi
"./configure" "${CONFIGURE_ARGS[@]}"
log "Build FFmpeg..."
make -j"$JOBS"
make install
log "==============================================================="
log "FFmpeg build SUCCESS"
log "Install prefix: $INSTALL_DIR"
log "Libraries: $(ls "$INSTALL_DIR/lib/"*.so* "$INSTALL_DIR/lib/"*.dll.a "$INSTALL_DIR/bin/"*.dll 2>/dev/null | tr '\n' ' ')"
log "==============================================================="

28
tools/chatgui/src/memberpropsdialog.cpp

@ -31,8 +31,9 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
bool haveKey = (m_edPrivkey.size() == 32);
bool isOwn = (m_nodeId == m_db->myNodeId());
bool isAdmin = false, isSuper = false;
bool isAdmin = false, isSuper = false, isStorage = false;
QString curNick;
m_currentVer = 0;
if (m_db->m_db) {
QString tbl = m_db->peersTableName(channelId);
QByteArray sql = QString(
@ -44,8 +45,11 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
if (sqlite3_step(st) == SQLITE_ROW) {
const char* tags = (const char*)sqlite3_column_text(st, 0);
if (tags) {
const char* vp = strstr(tags, "ver=");
if (vp) m_currentVer = atoi(vp + 4);
if (strstr(tags, "admin=yes")) isAdmin = true;
if (strstr(tags, "supernode=yes")) isSuper = true;
if (strstr(tags, "storage=yes")) isStorage = true;
}
curNick = DbManager::colText(st, 1);
}
@ -88,6 +92,11 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
m_superCb->setEnabled(haveKey);
layout->addWidget(m_superCb);
m_storageCb = new QCheckBox("Storage (priority attachment keeper)", this);
m_storageCb->setChecked(isStorage);
m_storageCb->setEnabled(haveKey);
layout->addWidget(m_storageCb);
if (!haveKey) {
auto* hint = new QLabel("(Admin/Super require channel private key)", this);
hint->setStyleSheet("color: gray; font-size: 11px;");
@ -144,12 +153,11 @@ void MemberPropsDialog::onSave() {
/* signed fields — only if key available */
if (m_edPrivkey.size() != 32) { accept(); return; }
QString tags;
if (m_adminCb->isChecked()) tags += "admin=yes";
if (m_superCb->isChecked()) {
if (!tags.isEmpty()) tags += ",";
tags += "supernode=yes";
}
int ver = m_currentVer + 1;
QString tags = QString("ver=%1").arg(ver);
if (m_adminCb->isChecked()) tags += ",admin=yes";
if (m_superCb->isChecked()) tags += ",supernode=yes";
if (m_storageCb->isChecked()) tags += ",storage=yes";
QByteArray msg = tags.toUtf8();
uint64_t le = htole64(m_nodeId);
@ -170,7 +178,7 @@ void MemberPropsDialog::onSave() {
}
QByteArray sql = QString(
"UPDATE \"%1\" SET adm_tags=?, adm_tags_sig=?, node_type=? WHERE node_id=?"
"UPDATE \"%1\" SET adm_tags=?, adm_tags_sig=?, node_type=?, storage=? WHERE node_id=?"
).arg(tbl).toUtf8();
sqlite3_stmt* st = nullptr;
if (sqlite3_prepare_v2(m_db->m_db, sql.constData(), -1, &st, nullptr) == SQLITE_OK) {
@ -179,7 +187,9 @@ void MemberPropsDialog::onSave() {
sqlite3_bind_blob(st, 2, sig, 64, SQLITE_TRANSIENT);
int nt = m_superCb->isChecked() ? 4 : 1;
sqlite3_bind_int(st, 3, nt);
sqlite3_bind_int64(st, 4, (sqlite3_int64)m_nodeId);
int stg = m_storageCb->isChecked() ? 1 : 0;
sqlite3_bind_int(st, 4, stg);
sqlite3_bind_int64(st, 5, (sqlite3_int64)m_nodeId);
sqlite3_step(st);
sqlite3_finalize(st);
}

2
tools/chatgui/src/memberpropsdialog.h

@ -22,8 +22,10 @@ private:
QString m_channelId;
quint64 m_nodeId;
QByteArray m_edPrivkey;
int m_currentVer;
QLineEdit* m_nickEdit;
QCheckBox* m_adminCb;
QCheckBox* m_superCb;
QCheckBox* m_storageCb;
};

Loading…
Cancel
Save