#include "member_sync.h" #include "topo_node_sqlite.h" #include "chat_core.h" #include "../../../src/utun_instance.h" #include "../../../lib/debug_config.h" #include "../../../lib/mem.h" #include #include #include #include #define MS_ID "member_sync" struct addr_item { uint8_t family; uint8_t addr[16]; uint16_t port; }; /* ── DB access ── */ static sqlite3* _db(struct UTUN_INSTANCE* inst) { return inst ? inst->topo_sqlite_db : NULL; } static void _peers_table(const char* ch_id, char* buf, size_t sz) { size_t i = 0; while (*ch_id && i < sz - 1) { char c = *ch_id++; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') buf[i++] = c; else buf[i++] = '_'; } buf[i] = '\0'; char tbl[128]; snprintf(tbl, sizeof(tbl), "peers_%s", buf); snprintf(buf, sz, "%s", tbl); } /* ── Member hash (identical to old _compute_member_hash) ── */ static int _addr_cmp(const void* a, const void* b) { const struct addr_item* ia = (const struct addr_item*)a; const struct addr_item* ib = (const struct addr_item*)b; if (ia->family != ib->family) return (int)ia->family - (int)ib->family; int r = memcmp(ia->addr, ib->addr, (size_t)(ia->family == 4 ? 4 : 16)); if (r) return r; return (int)ia->port - (int)ib->port; } static void _compute_member_hash(uint64_t node_id, const uint8_t* x25519, const uint8_t* ed25519, 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, uint8_t hash_out[MT_HASH_SIZE]) { EVP_MD_CTX* ctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); EVP_DigestUpdate(ctx, &node_id, 8); EVP_DigestUpdate(ctx, x25519, 32); EVP_DigestUpdate(ctx, ed25519, 32); EVP_DigestUpdate(ctx, join_sig, 64); EVP_DigestUpdate(ctx, &join_ts, 8); if (update_sig) EVP_DigestUpdate(ctx, update_sig, 64); else { static const uint8_t z[64]; EVP_DigestUpdate(ctx, z, 64); } EVP_DigestUpdate(ctx, &update_ts, 8); uint8_t ac = (uint8_t)addr_count; EVP_DigestUpdate(ctx, &ac, 1); if (addr_count > 0 && addrs_data) { struct addr_item items[256]; int n = 0; const uint8_t* p = addrs_data; for (int i = 0; i < addr_count && n < 256; i++) { uint8_t fam = *p++; items[n].family = fam; int ip_len = fam == 4 ? 4 : 16; memcpy(items[n].addr, p, (size_t)ip_len); p += ip_len; items[n].port = ((uint16_t)p[0] << 8) | p[1]; p += 2; n++; } qsort(items, (size_t)n, sizeof(struct addr_item), _addr_cmp); for (int i = 0; i < n; i++) { EVP_DigestUpdate(ctx, &items[i].family, 1); EVP_DigestUpdate(ctx, items[i].addr, (size_t)(items[i].family == 4 ? 4 : 16)); uint8_t port_be[2] = { (uint8_t)(items[i].port >> 8), (uint8_t)(items[i].port & 0xFF) }; EVP_DigestUpdate(ctx, port_be, 2); } } EVP_DigestFinal_ex(ctx, hash_out, NULL); EVP_MD_CTX_free(ctx); } /* ── merkle_sync_data_ops implementation ── */ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level, uint64_t prefix64, EVP_MD_CTX* sha_ctx) { struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)ctx; sqlite3* db = _db(inst); if (!db) return -1; if (level == 0) return 0; DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: bucket_hash ns=%s L%d/P%016llx", MS_ID, ns, level, (unsigned long long)prefix64); char peers_tbl[128]; _peers_table(ns, peers_tbl, sizeof(peers_tbl)); int mask_shift = 64 - (int)level * 5; uint64_t mask = (mask_shift >= 0 && mask_shift < 64) ? (~0ULL << mask_shift) : UINT64_MAX; char sql[512]; snprintf(sql, sizeof(sql), "SELECT node_id, x25519_pubkey, ed25519_pubkey," " join_sig, join_ts, update_sig, update_ts" " FROM \"%s\"" " WHERE (node_id & %lld) == %lld ORDER BY node_id ASC", peers_tbl, (long long)mask, (long long)prefix64); sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1; int count = 0; while (sqlite3_step(stmt) == SQLITE_ROW) { uint64_t nid = (uint64_t)sqlite3_column_int64(stmt, 0); const uint8_t* x25 = (const uint8_t*)sqlite3_column_blob(stmt, 1); const uint8_t* ed = (const uint8_t*)sqlite3_column_blob(stmt, 2); const uint8_t* sig = (const uint8_t*)sqlite3_column_blob(stmt, 3); 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); if (!x25 || !ed) continue; uint8_t mh[MT_HASH_SIZE]; _compute_member_hash(nid, x25, ed, sig, jts, usig, uts, NULL, 0, mh); EVP_DigestUpdate(sha_ctx, mh, MT_HASH_SIZE); count++; } sqlite3_finalize(stmt); return count; } static int _member_get_items(void* ctx, const char* ns, uint8_t level, uint64_t prefix, uint8_t prefix_bytes, uint8_t* buf, size_t* len) { struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)ctx; sqlite3* db = _db(inst); if (!db || !buf || !len) return -1; if (level == 0) { *len = 0; return 0; } DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: get_items ns=%s L%d/P%016llx", MS_ID, ns, level, (unsigned long long)prefix); char peers_tbl[128]; _peers_table(ns, peers_tbl, sizeof(peers_tbl)); int mask_shift = 64 - (int)level * 5; uint64_t mask = (mask_shift >= 0 && mask_shift < 64) ? (~0ULL << mask_shift) : UINT64_MAX; char sql[512]; snprintf(sql, sizeof(sql), "SELECT node_id, x25519_pubkey, ed25519_pubkey," " join_sig, join_ts, update_sig, update_ts, userinfo" " FROM \"%s\"" " WHERE (node_id & %lld) == %lld ORDER BY node_id ASC", peers_tbl, (long long)mask, (long long)prefix); sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1; size_t off = 0; if (off + 2 > *len) { sqlite3_finalize(stmt); return -2; } uint16_t* cnt = (uint16_t*)(buf + off); off += 2; *cnt = 0; while (sqlite3_step(stmt) == SQLITE_ROW) { uint64_t nid = (uint64_t)sqlite3_column_int64(stmt, 0); const uint8_t* x25 = (const uint8_t*)sqlite3_column_blob(stmt, 1); const uint8_t* ed = (const uint8_t*)sqlite3_column_blob(stmt, 2); const uint8_t* sig = (const uint8_t*)sqlite3_column_blob(stmt, 3); 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* nm = (const char*)sqlite3_column_text(stmt, 7); if (!x25 || !ed) continue; uint8_t nl = nm ? (uint8_t)strnlen(nm, 255) : 0; sqlite3_stmt* as = NULL; sqlite3_prepare_v2(db, "SELECT family, socket_id, address, port FROM node_addresses WHERE node_id=? AND addr_type=0" " ORDER BY family, address, port", -1, &as, NULL); uint8_t addrs[2048]; int addr_off = 0; int addr_count = 0; if (as) { sqlite3_bind_int64(as, 1, (sqlite3_int64)nid); while (sqlite3_step(as) == SQLITE_ROW && addr_off < (int)sizeof(addrs) - 8) { int fam = sqlite3_column_int(as, 0); int sid = sqlite3_column_int(as, 1); addrs[addr_off++] = (uint8_t)fam; addrs[addr_off++] = (uint8_t)sid; int ip_sz = fam == 4 ? 4 : 16; memcpy(addrs + addr_off, sqlite3_column_blob(as, 2), (size_t)ip_sz); addr_off += ip_sz; uint16_t p = (uint16_t)sqlite3_column_int(as, 3); addrs[addr_off++] = (uint8_t)(p >> 8); addrs[addr_off++] = (uint8_t)(p & 0xFF); addr_count++; } sqlite3_finalize(as); } 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; if (off + need > *len) { sqlite3_finalize(stmt); return -2; } memcpy(buf + off, &nid, 8); off += 8; memcpy(buf + off, x25, 32); off += 32; memcpy(buf + off, ed, 32); off += 32; buf[off++] = flags; if (flags & PEERS_FLAG_HAS_JOIN) { memcpy(buf + off, sig, 64); off += 64; memcpy(buf + off, &jts, 8); off += 8; } if (usig && uts) { memcpy(buf + off, usig, 64); off += 64; memcpy(buf + off, &uts, 8); off += 8; } else { memset(buf + off, 0, 64); off += 64; uint64_t z = 0; memcpy(buf + off, &z, 8); off += 8; } buf[off++] = nl; if (nl) { memcpy(buf + off, nm, nl); off += nl; } buf[off++] = (uint8_t)addr_count; memcpy(buf + off, addrs, (size_t)addr_off); off += (size_t)addr_off; (*cnt)++; } sqlite3_finalize(stmt); *len = off; DEBUG_DEBUG(DEBUG_CATEGORY_DB_SYNC, "%s: get_items ns=%s L%d/P%016llx mask=%016llx count=%u off=%zu", MS_ID, ns, level, (unsigned long long)prefix, (unsigned long long)mask, *cnt, off); return 0; } static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, const uint8_t* data, size_t len) { struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)ctx; if (len < 2) return -1; 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; for (uint16_t i = 0; i < count && mrem >= 148; i++) { uint64_t nid; memcpy(&nid, mp, 8); mp += 8; mrem -= 8; const uint8_t* x25 = mp; mp += 32; mrem -= 32; const uint8_t* ed = mp; mp += 32; mrem -= 32; uint8_t flags = *mp++; mrem--; const uint8_t* jsig = NULL; uint64_t jts = 0; if (flags & PEERS_FLAG_HAS_JOIN) { if (mrem < 72) break; jsig = mp; mp += 64; mrem -= 64; memcpy(&jts, mp, 8); mp += 8; mrem -= 8; } 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 (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; } uint8_t ac = *mp++; mrem--; const uint8_t* addrs = mp; int consumed = 0; for (int a = 0; a < (int)ac && mrem >= (size_t)(2 + consumed); a++) { uint8_t fam = mp[consumed]; consumed++; uint8_t sid = mp[consumed]; consumed++; int sz = fam == 4 ? 4 : 16; consumed += sz + 2; } /* verify update_sig if available */ if (usig && uts && jsig && ed) { uint8_t vmsg[256]; size_t vlen = 0; memcpy(vmsg + vlen, jsig, 64); vlen += 64; memcpy(vmsg + vlen, &uts, 8); vlen += 8; size_t nl2 = nm ? strlen(nm) : 0; memcpy(vmsg + vlen, nm ? nm : "", nl2 + 1); vlen += nl2 + 1; { EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ed, 32); if (pkey) { EVP_MD_CTX* ver = EVP_MD_CTX_new(); if (ver) { int ok = (EVP_DigestVerifyInit(ver, NULL, NULL, NULL, pkey) == 1) && (EVP_DigestVerify(ver, usig, 64, vmsg, vlen) == 1); if (!ok) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_sync invalid update_sig node=0x%016llx ns=%s", MS_ID, (unsigned long long)nid, ns); EVP_MD_CTX_free(ver); } EVP_PKEY_free(pkey); } } } 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); mp += consumed; mrem -= (size_t)consumed; } if (count > 0) merkle_sync_broadcast(inst, ns, from_peer, data, len); return 0; } static member_sync_node_updated_fn g_node_updated_cb = NULL; static int _ms_apply_update(void* ctx, const char* ns, uint64_t key, uint8_t type, const uint8_t* data, size_t len) { (void)ns; struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)ctx; if (type == 0x01 && len >= 1) { int online = data[0]; member_sync_set_online(inst, key, online); if (g_node_updated_cb) g_node_updated_cb(key, online); } return 0; } static const struct merkle_sync_data_ops g_member_ops = { .update_bucket_hash = _member_update_bucket_hash, .get_items = _member_get_items, .apply_items = _member_apply_items, .apply_update = _ms_apply_update, }; /* ── Public API ── */ static void _rebuild_all_trees(struct UTUN_INSTANCE* inst) { sqlite3* db = _db(inst); if (!db) return; sqlite3_stmt* st = NULL; if (sqlite3_prepare_v2(db, "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers_%'", -1, &st, NULL) != SQLITE_OK) return; while (sqlite3_step(st) == SQLITE_ROW) { const char* tbl = (const char*)sqlite3_column_text(st, 0); if (!tbl || strncmp(tbl, "peers_", 6) != 0) continue; const char* ch_id = tbl + 6; sqlite3_stmt* ns = NULL; char sql[256]; snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\"", tbl); if (sqlite3_prepare_v2(db, sql, -1, &ns, NULL) == SQLITE_OK) { while (sqlite3_step(ns) == SQLITE_ROW) { uint64_t nid = (uint64_t)sqlite3_column_int64(ns, 0); merkle_sync_recompute_path(inst, ch_id, nid); DEBUG_DEBUG(DEBUG_CATEGORY_DB_SYNC, "%s: rebuild tree ns=%s nid=%016llx", MS_ID, ch_id, (unsigned long long)nid); } sqlite3_finalize(ns); } } sqlite3_finalize(st); } int member_sync_init(struct UTUN_INSTANCE* inst) { if (!inst) return -1; DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: init", MS_ID); int rc = merkle_sync_init(inst, 0x31, &g_member_ops, inst); if (rc != 0) return rc; _rebuild_all_trees(inst); DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: initialized, merkle_rc=%d", MS_ID, rc); return 0; } void member_sync_destroy(struct UTUN_INSTANCE* inst) { if (!inst) return; merkle_sync_destroy(inst); } int member_sync_start(struct UTUN_INSTANCE* inst, uint64_t peer, const char* ch_id, merkle_sync_done_cb done_cb, void* arg) { return merkle_sync_start(inst, peer, ch_id, done_cb, arg); } void member_sync_cancel(struct UTUN_INSTANCE* inst, uint64_t peer, const char* ch_id) { merkle_sync_cancel(inst, peer, ch_id); } int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t member_id, const uint8_t* x25519, const uint8_t* ed25519, 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) { 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); 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); 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); return -1; } if (addrs_data && addr_count > 0) { DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] member_sync_put node=0x%016llx ch=%s addr_count=%d", MS_ID, (unsigned long long)member_id, ch_id, addr_count); sqlite3_exec(db, "BEGIN", NULL, NULL, NULL); sqlite3_stmt* ds = NULL; sqlite3_prepare_v2(db, "DELETE FROM node_addresses WHERE node_id=? AND addr_type=0", -1, &ds, NULL); if (ds) { sqlite3_bind_int64(ds, 1, (sqlite3_int64)member_id); sqlite3_step(ds); int deleted = sqlite3_changes(db); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] member_sync_put DELETE addr_type=0 node=0x%016llx: %d rows deleted", MS_ID, (unsigned long long)member_id, deleted); sqlite3_finalize(ds); } sqlite3_stmt* as = NULL; sqlite3_prepare_v2(db, "INSERT INTO node_addresses(node_id,family,protocol,address,port,addr_type,socket_id)" " VALUES(?,?,1,?,?,0,?)", -1, &as, NULL); if (as) { const uint8_t* p = addrs_data; int written = 0; for (int i = 0; i < addr_count; i++) { uint8_t fam = *p++; uint8_t sid = *p++; int ip_sz = fam == 4 ? 4 : 16; sqlite3_bind_int64(as, 1, (sqlite3_int64)member_id); sqlite3_bind_int(as, 2, fam); sqlite3_bind_blob(as, 3, p, ip_sz, SQLITE_STATIC); p += ip_sz; uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; sqlite3_bind_int(as, 4, port); sqlite3_bind_int(as, 5, (int)sid); sqlite3_step(as); sqlite3_reset(as); if (fam == 4) { const uint8_t* ip = p - 6; /* p advanced by ip_sz(4) + port(2) */ DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] member_sync_put INSERT node=0x%016llx sock=%d %d.%d.%d.%d:%d", MS_ID, (unsigned long long)member_id, sid, ip[0], ip[1], ip[2], ip[3], port); } else { DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] member_sync_put INSERT v6 node=0x%016llx sock=%d port=%d", MS_ID, (unsigned long long)member_id, sid, port); } written++; } sqlite3_finalize(as); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] member_sync_put DONE node=0x%016llx: %d addrs written", MS_ID, (unsigned long long)member_id, written); } sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); } else { if (!addrs_data) DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] member_sync_put node=0x%016llx ch=%s addrs_data=NULL — NO addresses (skipping)", MS_ID, (unsigned long long)member_id, ch_id); else DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] member_sync_put node=0x%016llx ch=%s addr_count=%d — empty, NO DELETE (skipping)", MS_ID, (unsigned long long)member_id, ch_id, addr_count); } merkle_sync_recompute_path(inst, ch_id, member_id); return 0; } int member_sync_del(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t member_id) { if (!inst || !ch_id) return -1; DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: del ch=%s nid=%016llx", MS_ID, ch_id, (unsigned long long)member_id); sqlite3* db = _db(inst); if (!db) return -1; topo_node_sqlite_member_del(db, ch_id, member_id); merkle_sync_recompute_path(inst, ch_id, member_id); return 0; } int member_sync_count(struct UTUN_INSTANCE* inst, const char* ch_id) { sqlite3* db = _db(inst); if (!db || !ch_id) return 0; char peers_tbl[128]; _peers_table(ch_id, peers_tbl, sizeof(peers_tbl)); char sql[256]; snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM \"%s\"", peers_tbl); sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return 0; int c = 0; if (sqlite3_step(stmt) == SQLITE_ROW) c = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); return c; } void member_sync_set_node_updated_cb(member_sync_node_updated_fn cb) { g_node_updated_cb = cb; } void member_sync_set_online(struct UTUN_INSTANCE* inst, uint64_t node_id, int online) { if (!inst) return; DEBUG_TRACE(DEBUG_CATEGORY_DB_SYNC, "%s: set_online nid=%016llx online=%d", MS_ID, (unsigned long long)node_id, online); sqlite3* db = _db(inst); if (!db) return; topo_node_sqlite_node_set_online(db, node_id, online); } const uint8_t* member_sync_get_hash(struct UTUN_INSTANCE* inst, const char* ch_id, uint8_t level, uint64_t prefix64) { return merkle_sync_get_hash(inst, ch_id, level, prefix64); }