Refactor Merkle sync into bounded bidirectional rounds with atomic member updates
This commit is contained in:
1 parent
1b92b4885b
commit
3e25bcef02
21 files changed
+1550
-1859
No files matched your search
+310
-242
@@ -34,35 +34,54 @@ static void _peers_table(const char* ch_id, char* buf, size_t sz) {
|
||||
snprintf(buf, sz, "%s", tbl);
|
||||
}
|
||||
|
||||
/* ── Member hash (identical to old _compute_member_hash) ── */
|
||||
/* Канонические целые для Merkle и wire; формат подписываемых сообщений задаётся отдельно. */
|
||||
static void member_write64(uint8_t* p, uint64_t n) {
|
||||
for (int i = 7; i >= 0; i--) { p[i] = (uint8_t)n; n >>= 8; }
|
||||
}
|
||||
|
||||
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 char* adm_tags, const uint8_t* adm_tags_sig,
|
||||
uint64_t signed_by, const uint8_t* signature,
|
||||
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);
|
||||
static uint64_t member_read64(const uint8_t* p) {
|
||||
uint64_t n = 0;
|
||||
for (int i = 0; i < 8; i++) n = (n << 8) | p[i];
|
||||
return n;
|
||||
}
|
||||
|
||||
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); }
|
||||
static int _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 char* userinfo, const char* adm_tags, const uint8_t* adm_tags_sig,
|
||||
uint64_t signed_by, const uint8_t* signature, uint8_t hash_out[MT_HASH_SIZE]) {
|
||||
uint8_t data[1024];
|
||||
size_t off = 0;
|
||||
member_write64(data + off, node_id); off += 8;
|
||||
memcpy(data + off, x25519, 32); off += 32;
|
||||
memcpy(data + off, ed25519, 32); off += 32;
|
||||
if (join_sig) memcpy(data + off, join_sig, 64); else memset(data + off, 0, 64);
|
||||
off += 64; member_write64(data + off, join_ts); off += 8;
|
||||
if (update_sig) memcpy(data + off, update_sig, 64); else memset(data + off, 0, 64);
|
||||
off += 64; member_write64(data + off, update_ts); off += 8;
|
||||
size_t ul = userinfo ? strlen(userinfo) : 0, atl = adm_tags ? strlen(adm_tags) : 0;
|
||||
if (ul > 255 || atl > 255) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member strings exceed wire limits node=%016llx", MS_ID, (unsigned long long)node_id);
|
||||
return -1;
|
||||
}
|
||||
data[off++] = (uint8_t)ul;
|
||||
if (ul) { memcpy(data + off, userinfo, ul); off += ul; }
|
||||
data[off++] = (uint8_t)atl;
|
||||
if (atl) { memcpy(data + off, adm_tags, atl); off += atl; }
|
||||
if (adm_tags_sig) memcpy(data + off, adm_tags_sig, 64); else memset(data + off, 0, 64);
|
||||
off += 64; member_write64(data + off, signed_by); off += 8;
|
||||
if (signature) memcpy(data + off, signature, 64); else memset(data + off, 0, 64);
|
||||
off += 64;
|
||||
if (EVP_Digest(data, off, hash_out, NULL, EVP_sha256(), NULL) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member hash failed node=%016llx", MS_ID, (unsigned long long)node_id);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
EVP_DigestUpdate(ctx, &signed_by, 8);
|
||||
if (signature) EVP_DigestUpdate(ctx, signature, 64); else { static const uint8_t zsig[64]; EVP_DigestUpdate(ctx, zsig, 64); }
|
||||
|
||||
EVP_DigestFinal_ex(ctx, hash_out, NULL);
|
||||
EVP_MD_CTX_free(ctx);
|
||||
/* SQLite BLOB pointers are only safe after checking their exact length. */
|
||||
static int member_blob(sqlite3_stmt* st, int col, int size, int optional) {
|
||||
int bytes = sqlite3_column_bytes(st, col);
|
||||
return bytes == size || (optional && bytes == 0);
|
||||
}
|
||||
|
||||
int member_sync_build_update_msg(const uint8_t* join_sig, uint64_t update_ts,
|
||||
@@ -114,7 +133,9 @@ 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) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: bucket_hash — db is NULL", MS_ID); return -1; }
|
||||
if (level == 0) return 0;
|
||||
if (level != MT_MAX_LEVEL) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: expected leaf, level=%u", MS_ID, level); return -1;
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_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));
|
||||
|
||||
@@ -123,7 +144,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, adm_tags, adm_tags_sig, signed_by, signature, source"
|
||||
" join_sig, join_ts, update_sig, update_ts, adm_tags, adm_tags_sig, signed_by, signature, source, userinfo"
|
||||
" FROM \"%s\""
|
||||
" WHERE (node_id & %lld) == %lld ORDER BY node_id ASC",
|
||||
peers_tbl, (long long)mask, (long long)prefix64);
|
||||
@@ -132,7 +153,8 @@ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level,
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: bucket_hash prepare failed ns=%s", MS_ID, ns); return -1; }
|
||||
|
||||
int count = 0;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int step;
|
||||
while ((step = 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);
|
||||
@@ -145,23 +167,35 @@ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level,
|
||||
uint64_t sb = (uint64_t)sqlite3_column_int64(stmt, 9);
|
||||
const uint8_t* sgn = (const uint8_t*)sqlite3_column_blob(stmt, 10);
|
||||
int src = sqlite3_column_int(stmt, 11);
|
||||
if (!x25 || !ed || src != 0) continue;
|
||||
if (src != 0) continue;
|
||||
if (!member_blob(stmt, 1, 32, 0) || !member_blob(stmt, 2, 32, 0) || !member_blob(stmt, 3, 64, 1)
|
||||
|| !member_blob(stmt, 5, 64, 1) || !member_blob(stmt, 8, 64, 1) || !member_blob(stmt, 10, 64, 1)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: invalid hash record ns=%s node=%016llx", MS_ID, ns, (unsigned long long)nid);
|
||||
sqlite3_finalize(stmt); return -1;
|
||||
}
|
||||
|
||||
uint8_t mh[MT_HASH_SIZE];
|
||||
_compute_member_hash(nid, x25, ed, sig, jts, usig, uts, atags, atsig, sb, sgn, mh);
|
||||
EVP_DigestUpdate(sha_ctx, mh, MT_HASH_SIZE);
|
||||
const char* userinfo = (const char*)sqlite3_column_text(stmt, 12);
|
||||
if (_compute_member_hash(nid, x25, ed, sig, jts, usig, uts, userinfo, atags, atsig, sb, sgn, mh) < 0
|
||||
|| EVP_DigestUpdate(sha_ctx, mh, MT_HASH_SIZE) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: leaf digest failed ns=%s", MS_ID, ns);
|
||||
sqlite3_finalize(stmt); return -1;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
if (step != SQLITE_DONE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: leaf read failed ns=%s: %s", MS_ID, ns, sqlite3_errmsg(db)); return -1;
|
||||
}
|
||||
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) {
|
||||
static int _member_get_page(void* ctx, const char* ns, uint64_t prefix, int after_valid, uint64_t after,
|
||||
uint8_t* buf, size_t* len, uint64_t* next, int* more) {
|
||||
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)ctx;
|
||||
sqlite3* db = _db(inst); if (!db || !buf || !len) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: get_items — invalid args db=%p buf=%p len=%p", MS_ID, (void*)db, (void*)buf, (void*)len); return -1; }
|
||||
if (level == 0) { *len = 0; return 0; }
|
||||
const uint8_t level = MT_MAX_LEVEL;
|
||||
*next = 0; *more = 0;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_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));
|
||||
|
||||
@@ -173,17 +207,20 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level,
|
||||
" join_sig, join_ts, update_sig, update_ts, userinfo, adm_tags, adm_tags_sig,"
|
||||
" signed_by, signature, source"
|
||||
" FROM \"%s\""
|
||||
" WHERE (node_id & %lld) == %lld ORDER BY node_id ASC",
|
||||
" WHERE (node_id & %lld) == %lld AND (?=0 OR node_id>?) 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) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: get_items prepare failed ns=%s", MS_ID, ns); return -1; }
|
||||
sqlite3_bind_int(stmt, 1, after_valid);
|
||||
sqlite3_bind_int64(stmt, 2, (sqlite3_int64)after);
|
||||
|
||||
size_t off = 0;
|
||||
if (off + 2 > *len) { sqlite3_finalize(stmt); return -2; }
|
||||
uint16_t* cnt = (uint16_t*)(buf + off); off += 2; *cnt = 0;
|
||||
uint16_t cnt = 0; off += 2;
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int step;
|
||||
while ((step = 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);
|
||||
@@ -197,24 +234,34 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level,
|
||||
uint64_t sb = (uint64_t)sqlite3_column_int64(stmt, 10);
|
||||
const uint8_t* sgn = (const uint8_t*)sqlite3_column_blob(stmt, 11);
|
||||
int src = sqlite3_column_int(stmt, 12);
|
||||
if (!x25 || !ed || src != 0) continue;
|
||||
if (src != 0) continue;
|
||||
if (!member_blob(stmt, 1, 32, 0) || !member_blob(stmt, 2, 32, 0) || !member_blob(stmt, 3, 64, 1)
|
||||
|| !member_blob(stmt, 5, 64, 1) || !member_blob(stmt, 9, 64, 1) || !member_blob(stmt, 11, 64, 1)
|
||||
|| (nm && strlen(nm) > 255) || (atags && strlen(atags) > 255)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: invalid page record ns=%s node=%016llx", MS_ID, ns, (unsigned long long)nid);
|
||||
sqlite3_finalize(stmt); return -1;
|
||||
}
|
||||
uint8_t nl = nm ? (uint8_t)strnlen(nm, 255) : 0;
|
||||
uint8_t atl = atags ? (uint8_t)strnlen(atags, 255) : 0;
|
||||
|
||||
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)atl + 64 + 8 + 64;
|
||||
if (off + need > *len) { sqlite3_finalize(stmt); return -2; }
|
||||
memcpy(buf + off, &nid, 8); off += 8;
|
||||
if (off + need > *len) {
|
||||
if (!cnt) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: record exceeds page capacity ns=%s", MS_ID, ns); sqlite3_finalize(stmt); return -1; }
|
||||
*more = 1;
|
||||
break;
|
||||
}
|
||||
member_write64(buf + off, nid); 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;
|
||||
member_write64(buf + off, jts); off += 8;
|
||||
}
|
||||
if (usig && uts) {
|
||||
memcpy(buf + off, usig, 64); off += 64;
|
||||
memcpy(buf + off, &uts, 8); off += 8;
|
||||
member_write64(buf + off, uts); off += 8;
|
||||
} else {
|
||||
memset(buf + off, 0, 64); off += 64;
|
||||
uint64_t z = 0; memcpy(buf + off, &z, 8); off += 8;
|
||||
@@ -225,15 +272,18 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level,
|
||||
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; }
|
||||
memcpy(buf + off, &sb, 8); off += 8;
|
||||
member_write64(buf + off, sb); off += 8;
|
||||
if (sgn && sqlite3_column_bytes(stmt, 11) >= 64) { memcpy(buf + off, sgn, 64); off += 64; }
|
||||
else { memset(buf + off, 0, 64); off += 64; }
|
||||
(*cnt)++;
|
||||
cnt++;
|
||||
*next = nid;
|
||||
}
|
||||
if (step != SQLITE_DONE && !*more) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: page read failed ns=%s: %s", MS_ID, ns, sqlite3_errmsg(db)); sqlite3_finalize(stmt); return -1; }
|
||||
sqlite3_finalize(stmt);
|
||||
buf[0] = (uint8_t)(cnt >> 8); buf[1] = (uint8_t)cnt;
|
||||
*len = off;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_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);
|
||||
MS_ID, ns, level, (unsigned long long)prefix, (unsigned long long)mask, cnt, off);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -416,7 +466,7 @@ int member_sync_validate_pubkey(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
return topo_node_sqlite_member_in_channel(db, ch_id, node_id);
|
||||
}
|
||||
|
||||
int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
uint64_t from_peer, const struct ms_member_rec* m) {
|
||||
if (!inst || !ch_id || !m || !m->x25519 || !m->ed25519) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record — invalid args", MS_ID);
|
||||
@@ -443,9 +493,12 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
sqlite3_stmt* st = NULL;
|
||||
char sql[512]; snprintf(sql, sizeof(sql),
|
||||
"SELECT join_sig, join_ts, update_ts, adm_tags, signed_by, signature, source FROM \"%s\" WHERE node_id=?", peers_tbl);
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)m->node_id);
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)m->node_id);
|
||||
if (rc == SQLITE_OK) {
|
||||
rc = sqlite3_step(st);
|
||||
if (rc == SQLITE_ROW && (!member_blob(st, 0, 64, 1) || !member_blob(st, 5, 64, 1))) rc = SQLITE_CORRUPT;
|
||||
if (rc == SQLITE_ROW) {
|
||||
has_local = 1;
|
||||
const uint8_t* js = (const uint8_t*)sqlite3_column_blob(st, 0);
|
||||
if (js) memcpy(local_join_sig, js, 64);
|
||||
@@ -458,7 +511,11 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
if (ls && sqlite3_column_bytes(st, 5) >= 64) memcpy(local_signature, ls, 64);
|
||||
local_src = sqlite3_column_int(st, 6);
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: local record read failed ns=%s node=%016llx rc=%d: %s",
|
||||
MS_ID, ch_id, (unsigned long long)m->node_id, rc, sqlite3_errmsg(db)); return MT_ERR_IO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,6 +635,12 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: adm_tags change on placeholder member (source=%d) — ignored by merkle, won't propagate nid=0x%016llx ns=%s",
|
||||
MS_ID, local_src, (unsigned long long)m->node_id, ch_id);
|
||||
|
||||
if (!identity_ok || !block_a_ok || (!is_local && m->adm_tags && m->adm_tags[0] && !block_b_ok)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: rejected record ns=%s node=%016llx identity=%d member=%d owner=%d",
|
||||
MS_ID, ch_id, (unsigned long long)m->node_id, identity_ok, block_a_ok, block_b_ok);
|
||||
return MT_ERR_DATA;
|
||||
}
|
||||
|
||||
/* ── запись изменённых блоков ── */
|
||||
if (block_a_changed) {
|
||||
const uint8_t* jsig = m->join_sig ? m->join_sig : (has_local ? local_join_sig : NULL);
|
||||
@@ -594,9 +657,12 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
/* узел в nodes пишет только merkle-путь (verified identity) */
|
||||
char node_name[128] = "";
|
||||
if (m->userinfo) json_flat_get(m->userinfo, "name", node_name, sizeof(node_name));
|
||||
topo_node_sqlite_node_update_verified(db, m->node_id, node_name, m->x25519, m->ed25519,
|
||||
if (topo_node_sqlite_node_update_verified(db, m->node_id, node_name, m->x25519, m->ed25519,
|
||||
m->update_ts ? m->update_ts : m->join_ts,
|
||||
ntp_time_get_seconds(inst));
|
||||
ntp_time_get_seconds(inst)) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: node update failed ns=%s node=%016llx", MS_ID, ch_id,
|
||||
(unsigned long long)m->node_id); return MT_ERR_IO;
|
||||
}
|
||||
}
|
||||
if (block_b_changed) {
|
||||
if (topo_node_sqlite_member_owner_put(db, ch_id, m->node_id, m->adm_tags, m->adm_tags_sig, adm_storage) != 0) {
|
||||
@@ -609,8 +675,10 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
if (block_a_stale || block_b_stale) stale = 1;
|
||||
|
||||
/* пересчёт классификации (node_type/storage) — DB-хелпер, читает node_addresses (адреса BGP) */
|
||||
if (changed)
|
||||
topo_node_sqlite_nodeinfo_updated(db, m->node_id);
|
||||
if (changed && topo_node_sqlite_nodeinfo_updated(db, m->node_id) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: classification update failed ns=%s node=%016llx", MS_ID, ch_id,
|
||||
(unsigned long long)m->node_id); return MT_ERR_IO;
|
||||
}
|
||||
|
||||
/* ── recompute и подтвердить реальное изменение по хешу ── */
|
||||
if (changed) {
|
||||
@@ -619,21 +687,39 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
if (rc == 0) changed = 0; /* запись была no-op (данные идентичны) */
|
||||
}
|
||||
|
||||
/* ── пассивное добавление: если узел уже подключён напрямую — добавить в CHAT-группу ── */
|
||||
if (changed && inst->topo_groups) {
|
||||
uint64_t gid = strtoull(ch_id, NULL, 10);
|
||||
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid);
|
||||
if (g && g->group_type == TOPO_GROUP_TYPE_CHAT) {
|
||||
struct ETCP_CONN* c = instance_find_conn(inst, m->node_id);
|
||||
if (c) topo_group_new_conn(g, c);
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record ch=%s nid=0x%016llx → changed=%d stale=%d",
|
||||
MS_ID, ch_id, (unsigned long long)m->node_id, changed, stale);
|
||||
return (changed ? MS_APPLY_CHANGED : 0) | (stale ? MS_APPLY_STALE : 0);
|
||||
}
|
||||
|
||||
static void member_committed(struct UTUN_INSTANCE* inst, const char* ns, uint64_t node_id) {
|
||||
merkle_sync_changed(inst, ns);
|
||||
struct TOPO_GROUP* g = inst->topo_groups ? topo_groups_find(inst->topo_groups, strtoull(ns, NULL, 10)) : NULL;
|
||||
struct ETCP_CONN* conn = instance_find_conn(inst, node_id);
|
||||
if (g && g->group_type == TOPO_GROUP_TYPE_CHAT && conn) topo_group_new_conn(g, conn);
|
||||
}
|
||||
|
||||
int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
uint64_t from_peer, const struct ms_member_rec* m) {
|
||||
sqlite3* db = _db(inst);
|
||||
if (!db || !ch_id || !m) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: invalid apply args", MS_ID); return -1; }
|
||||
int own = sqlite3_get_autocommit(db);
|
||||
if (sqlite3_exec(db, "SAVEPOINT member_record", NULL, NULL, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: record begin: %s", MS_ID, sqlite3_errmsg(db)); return -1;
|
||||
}
|
||||
int rc = member_apply_record(inst, ch_id, from_peer, m);
|
||||
if (rc < 0 && sqlite3_exec(db, "ROLLBACK TO member_record", NULL, NULL, NULL) != SQLITE_OK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: record rollback: %s", MS_ID, sqlite3_errmsg(db));
|
||||
if (sqlite3_exec(db, "RELEASE member_record", NULL, NULL, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: record commit: %s", MS_ID, sqlite3_errmsg(db));
|
||||
if (sqlite3_exec(db, "ROLLBACK TO member_record; RELEASE member_record", NULL, NULL, NULL) != SQLITE_OK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: record cleanup: %s", MS_ID, sqlite3_errmsg(db));
|
||||
return -1;
|
||||
}
|
||||
if (own && rc >= 0 && (rc & MS_APPLY_CHANGED)) member_committed(inst, ch_id, m->node_id);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ── Верификация локальной записи при чтении из БД ──
|
||||
Единственный сценарий удаления мембера: подпись присутствует и невалидна,
|
||||
либо node_id != derive(x25519). Отсутствие подписи = «не верифицировано», не удаляем. */
|
||||
@@ -789,74 +875,102 @@ void member_sync_verify_and_purge_all(struct UTUN_INSTANCE* inst) {
|
||||
sqlite3_finalize(st);
|
||||
}
|
||||
|
||||
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) { DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_items — no items from peer=%016llx ns=%.*s (empty, sync complete)", MS_ID, (unsigned long long)from_peer, (int)strnlen(ns, 32), ns); return 0; }
|
||||
struct member_wire {
|
||||
struct ms_member_rec rec;
|
||||
char userinfo[256], tags[256];
|
||||
uint8_t local_join[64];
|
||||
};
|
||||
|
||||
uint16_t count; memcpy(&count, data, 2);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_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;
|
||||
/* Одна строгая раскладка для проверки и применения. Нельзя молча принимать усечённую запись. */
|
||||
static int member_decode(const uint8_t* p, size_t len, struct member_wire* w, size_t* used) {
|
||||
memset(w, 0, sizeof(*w));
|
||||
if (len < 73) return -1;
|
||||
struct ms_member_rec* m = &w->rec;
|
||||
size_t off = 0;
|
||||
m->node_id = member_read64(p + off); off += 8;
|
||||
m->x25519 = p + off; off += 32; m->ed25519 = p + off; off += 32;
|
||||
uint8_t flags = p[off++];
|
||||
if (flags & ~PEERS_FLAG_HAS_JOIN) return -1;
|
||||
if (flags & PEERS_FLAG_HAS_JOIN) {
|
||||
if (len - off < 72) return -1;
|
||||
m->join_sig = p + off; off += 64; m->join_ts = member_read64(p + off); off += 8;
|
||||
}
|
||||
if (len - off < 73) return -1;
|
||||
m->update_sig = p + off; off += 64; m->update_ts = member_read64(p + off); off += 8;
|
||||
uint8_t n = p[off++];
|
||||
if (len - off < (size_t)n + 1 || memchr(p + off, 0, n)) return -1;
|
||||
memcpy(w->userinfo, p + off, n); off += n;
|
||||
n = p[off++];
|
||||
if (len - off < (size_t)n + 136 || memchr(p + off, 0, n)) return -1;
|
||||
memcpy(w->tags, p + off, n); off += n;
|
||||
m->adm_tags_sig = p + off; off += 64;
|
||||
m->signed_by = member_read64(p + off); off += 8;
|
||||
m->signature = p + off; off += 64;
|
||||
m->userinfo = w->userinfo; m->adm_tags = w->tags;
|
||||
*used = 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 = ctx;
|
||||
sqlite3* db = _db(inst);
|
||||
|
||||
int changed_count = 0;
|
||||
|
||||
for (uint16_t i = 0; i < count && mrem >= 73; 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 (db && topo_node_sqlite_member_get_join(db, ns, nid, buf_jsig, &buf_jts) == 0) { jsig = buf_jsig; jts = buf_jts; }
|
||||
if (!db || len < 2) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: invalid member page ns=%s bytes=%zu", MS_ID, ns, len); return MT_ERR_DATA;
|
||||
}
|
||||
uint16_t count = ((uint16_t)data[0] << 8) | data[1];
|
||||
if (count > (len - 2) / 283) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: impossible page count=%u bytes=%zu", MS_ID, count, len); return MT_ERR_DATA;
|
||||
}
|
||||
size_t off = 2;
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
struct member_wire w; size_t used;
|
||||
if (member_decode(data + off, len - off, &w, &used) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: malformed member page ns=%s record=%u offset=%zu", MS_ID, ns, i, off);
|
||||
return MT_ERR_DATA;
|
||||
}
|
||||
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; }
|
||||
uint64_t sb = 0; const uint8_t* sgn = NULL;
|
||||
if (mrem >= 72) { memcpy(&sb, mp, 8); mp += 8; mrem -= 8; sgn = mp; mp += 64; mrem -= 64; }
|
||||
|
||||
struct ms_member_rec m;
|
||||
memset(&m, 0, sizeof(m));
|
||||
m.node_id = nid;
|
||||
m.x25519 = x25; m.ed25519 = ed;
|
||||
m.join_sig = jsig; m.join_ts = jts;
|
||||
m.update_sig = usig; m.update_ts = uts;
|
||||
m.userinfo = nm;
|
||||
m.adm_tags = atags[0] ? atags : NULL;
|
||||
m.adm_tags_sig = atsig;
|
||||
m.signed_by = sb; m.signature = sgn;
|
||||
|
||||
int r = member_sync_apply_record(inst, ns, from_peer, &m);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member_sync recv ns=%s nid=0x%016llx r=%d", MS_ID, ns, (unsigned long long)nid, r);
|
||||
if (r < 0) continue;
|
||||
if (r & MS_APPLY_CHANGED) {
|
||||
changed_count++;
|
||||
/* fire node_props_changed callbacks */
|
||||
_fire_props_changed(inst, nid, atags[0] ? atags : NULL, ns);
|
||||
/* fire apply callbacks (ready-хендлинг join на connection-узле) */
|
||||
_fire_apply_cbk(inst, ns, nid, x25, sb, sgn);
|
||||
}
|
||||
if (r & MS_APPLY_STALE) {
|
||||
/* у нас версия новее — отправить наш полный рекорд автору */
|
||||
member_sync_send_to(inst, ns, nid, from_peer);
|
||||
off += used;
|
||||
}
|
||||
if (off != len) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: trailing page bytes ns=%s bytes=%zu", MS_ID, ns, len - off); return MT_ERR_DATA; }
|
||||
if (!count) return 0;
|
||||
uint8_t* changed = u_calloc(count, 1);
|
||||
if (!changed) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: page allocation failed", MS_ID); return MT_ERR_IO; }
|
||||
if (sqlite3_exec(db, "SAVEPOINT member_page", NULL, NULL, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: page begin: %s", MS_ID, sqlite3_errmsg(db)); u_free(changed); return MT_ERR_IO;
|
||||
}
|
||||
int rc = 0;
|
||||
off = 2;
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
struct member_wire w; size_t used;
|
||||
if (member_decode(data + off, len - off, &w, &used) < 0) { rc = MT_ERR_DATA; break; }
|
||||
if (!w.rec.join_sig && topo_node_sqlite_member_get_join(db, ns, w.rec.node_id, w.local_join, &w.rec.join_ts) == 0)
|
||||
w.rec.join_sig = w.local_join;
|
||||
int r = member_sync_apply_record(inst, ns, from_peer, &w.rec);
|
||||
if (r < 0) { rc = r; break; }
|
||||
changed[i] = (r & MS_APPLY_CHANGED) != 0;
|
||||
off += used;
|
||||
}
|
||||
if (rc < 0 && sqlite3_exec(db, "ROLLBACK TO member_page", NULL, NULL, NULL) != SQLITE_OK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: page rollback: %s", MS_ID, sqlite3_errmsg(db));
|
||||
if (sqlite3_exec(db, "RELEASE member_page", NULL, NULL, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: page commit: %s", MS_ID, sqlite3_errmsg(db)); rc = MT_ERR_IO;
|
||||
if (sqlite3_exec(db, "ROLLBACK TO member_page; RELEASE member_page", NULL, NULL, NULL) != SQLITE_OK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: page cleanup: %s", MS_ID, sqlite3_errmsg(db));
|
||||
}
|
||||
if (rc == 0) {
|
||||
off = 2;
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
struct member_wire w; size_t used;
|
||||
if (member_decode(data + off, len - off, &w, &used) < 0) { rc = MT_ERR_DATA; break; }
|
||||
if (changed[i]) {
|
||||
member_committed(inst, ns, w.rec.node_id);
|
||||
_fire_props_changed(inst, w.rec.node_id, w.tags[0] ? w.tags : NULL, ns);
|
||||
_fire_apply_cbk(inst, ns, w.rec.node_id, w.rec.x25519, w.rec.signed_by, w.rec.signature);
|
||||
}
|
||||
off += used;
|
||||
}
|
||||
}
|
||||
if (changed_count > 0)
|
||||
merkle_sync_broadcast(inst, ns, from_peer, data, len);
|
||||
return 0;
|
||||
u_free(changed);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int _member_validate_peer(void* ctx, const char* ns, uint64_t peer) {
|
||||
@@ -866,45 +980,83 @@ static int _member_validate_peer(void* ctx, const char* ns, uint64_t peer) {
|
||||
return topo_node_sqlite_member_in_channel(db, ns, peer);
|
||||
}
|
||||
|
||||
static int member_finish(void* ctx, const char* ns) {
|
||||
sqlite3* db = _db(ctx);
|
||||
char table[128], sql[320]; _peers_table(ns, table, sizeof(table));
|
||||
snprintf(sql, sizeof(sql), "SELECT node_id,x25519_pubkey,signed_by,signature FROM \"%s\" WHERE source=0", table);
|
||||
sqlite3_stmt* st = NULL;
|
||||
int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: finish query ns=%s: %s", MS_ID, ns, sqlite3_errmsg(db)); return -1;
|
||||
}
|
||||
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
struct ms_member_rec m = {0};
|
||||
m.node_id = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
m.x25519 = sqlite3_column_blob(st, 1);
|
||||
m.signed_by = (uint64_t)sqlite3_column_int64(st, 2);
|
||||
m.signature = sqlite3_column_blob(st, 3);
|
||||
if (!m.signature || sqlite3_column_bytes(st, 3) == 0) continue;
|
||||
if (sqlite3_column_bytes(st, 1) != 32 || sqlite3_column_bytes(st, 3) != 64) { rc = SQLITE_CORRUPT; break; }
|
||||
if (_sig_is_zero64(m.signature)) continue;
|
||||
int verified = member_sync_verify_signature(db, ns, &m);
|
||||
if (verified != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: cannot confirm ns=%s node=%016llx signer=%016llx verification=%d",
|
||||
MS_ID, ns, (unsigned long long)m.node_id, (unsigned long long)m.signed_by, verified);
|
||||
rc = SQLITE_CONSTRAINT; break;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
if (rc != SQLITE_DONE) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: finish failed ns=%s rc=%d", MS_ID, ns, rc); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct merkle_sync_data_ops g_member_ops = {
|
||||
.update_bucket_hash = _member_update_bucket_hash,
|
||||
.get_items = _member_get_items,
|
||||
.get_page = _member_get_page,
|
||||
.apply_items = _member_apply_items,
|
||||
.apply_update = NULL, /* online-статус больше не рассылается через merkle */
|
||||
.validate_peer = _member_validate_peer,
|
||||
.finish = member_finish,
|
||||
};
|
||||
|
||||
/* ── 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;
|
||||
/* полный пересчёт: снести всё дерево канала, затем собрать заново из peers */
|
||||
sqlite3_stmt* del = NULL;
|
||||
if (sqlite3_prepare_v2(db, "DELETE FROM merkle_tree_hash WHERE namespace=?", -1, &del, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(del, 1, ch_id, -1, SQLITE_STATIC);
|
||||
sqlite3_step(del);
|
||||
sqlite3_finalize(del);
|
||||
}
|
||||
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_MEMBER_SYNC, "%s: rebuild tree ns=%s nid=%016llx", MS_ID, ch_id, (unsigned long long)nid);
|
||||
static int _rebuild_all_trees(struct UTUN_INSTANCE* inst) {
|
||||
sqlite3* db = _db(inst);
|
||||
sqlite3_stmt* tables = NULL;
|
||||
int rc = sqlite3_prepare_v2(db, "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers_%'", -1, &tables, NULL);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
if (sqlite3_exec(db, "SAVEPOINT member_rebuild", NULL, NULL, NULL) != SQLITE_OK) goto fail;
|
||||
while ((rc = sqlite3_step(tables)) == SQLITE_ROW) {
|
||||
const char* table = (const char*)sqlite3_column_text(tables, 0);
|
||||
if (!table || strncmp(table, "peers_", 6)) continue;
|
||||
const char* ns = table + 6;
|
||||
uint64_t mask = merkle_sync_level_prefix(UINT64_MAX, MT_MAX_LEVEL);
|
||||
char sql[256];
|
||||
snprintf(sql, sizeof(sql), "SELECT DISTINCT (node_id & %lld) FROM \"%s\" WHERE source=0", (long long)mask, table);
|
||||
sqlite3_stmt* leaves = NULL;
|
||||
int lr = sqlite3_prepare_v2(db, sql, -1, &leaves, NULL);
|
||||
if (lr == SQLITE_OK) {
|
||||
while ((lr = sqlite3_step(leaves)) == SQLITE_ROW) {
|
||||
uint64_t key = (uint64_t)sqlite3_column_int64(leaves, 0);
|
||||
if (merkle_sync_recompute_path(inst, ns, key) < 0) { lr = SQLITE_ERROR; break; }
|
||||
}
|
||||
sqlite3_finalize(ns);
|
||||
}
|
||||
sqlite3_finalize(leaves);
|
||||
if (lr != SQLITE_DONE) { rc = lr; break; }
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
if (rc != SQLITE_DONE) {
|
||||
if (sqlite3_exec(db, "ROLLBACK TO member_rebuild", NULL, NULL, NULL) != SQLITE_OK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: rebuild rollback failed: %s", MS_ID, sqlite3_errmsg(db));
|
||||
}
|
||||
int release = sqlite3_exec(db, "RELEASE member_rebuild", NULL, NULL, NULL);
|
||||
sqlite3_finalize(tables); tables = NULL;
|
||||
if (rc != SQLITE_DONE || release != SQLITE_OK) goto fail;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: trees rebuilt from distinct leaves", MS_ID);
|
||||
return 0;
|
||||
fail:
|
||||
sqlite3_finalize(tables);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: tree rebuild failed: %s", MS_ID, db ? sqlite3_errmsg(db) : "no DB");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Подписаться на BGP node-события chat-группы (BGP → member_sync → node_props_changed). */
|
||||
@@ -920,7 +1072,7 @@ int member_sync_init(struct UTUN_INSTANCE* inst) {
|
||||
if (rc != 0) return rc;
|
||||
/* очистка битых записей во всех каналах перед построением дерева */
|
||||
member_sync_verify_and_purge_all(inst);
|
||||
_rebuild_all_trees(inst);
|
||||
if (_rebuild_all_trees(inst) < 0) { merkle_sync_destroy(inst); return -1; }
|
||||
/* подписка на BGP-события существующих chat-групп */
|
||||
if (inst->topo_groups && inst->topo_groups->group_list) {
|
||||
struct ll_entry* ge = inst->topo_groups->group_list->head;
|
||||
@@ -972,85 +1124,6 @@ void member_sync_cancel_channel(struct UTUN_INSTANCE* inst, const char* ch_id) {
|
||||
merkle_sync_cancel_ns(inst, ch_id);
|
||||
}
|
||||
|
||||
/* Сериализует одного мембера в wire-формат [count:2][member...].
|
||||
Возвращает 0 и *out_len, или -1 если мембер не найден/нет БД. */
|
||||
static int _serialize_member(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t member_id,
|
||||
uint8_t* buf, size_t buf_sz, size_t* out_len) {
|
||||
if (!inst || !ch_id || !buf || !out_len) return -1;
|
||||
sqlite3* db = _db(inst); if (!db) return -1;
|
||||
char peers_tbl[128]; _peers_table(ch_id, peers_tbl, sizeof(peers_tbl));
|
||||
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
char sql[512]; snprintf(sql, sizeof(sql),
|
||||
"SELECT node_id, x25519_pubkey, ed25519_pubkey,"
|
||||
" join_sig, join_ts, update_sig, update_ts, userinfo, adm_tags, adm_tags_sig,"
|
||||
" signed_by, signature"
|
||||
" FROM \"%s\" WHERE node_id=?", peers_tbl);
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: serialize_member — query failed ch=%s nid=0x%016llx", MS_ID, ch_id, (unsigned long long)member_id);
|
||||
return -1;
|
||||
}
|
||||
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)member_id);
|
||||
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; }
|
||||
|
||||
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);
|
||||
const char* atags = (const char*)sqlite3_column_text(stmt, 8);
|
||||
const uint8_t* atsig = (const uint8_t*)sqlite3_column_blob(stmt, 9);
|
||||
uint64_t sb = (uint64_t)sqlite3_column_int64(stmt, 10);
|
||||
const uint8_t* sgn = (const uint8_t*)sqlite3_column_blob(stmt, 11);
|
||||
if (!x25 || !ed) { sqlite3_finalize(stmt); return -1; }
|
||||
uint8_t nl = nm ? (uint8_t)strnlen(nm, 255) : 0;
|
||||
uint8_t atl = atags ? (uint8_t)strnlen(atags, 255) : 0;
|
||||
|
||||
uint8_t flags = (sig && jts) ? PEERS_FLAG_HAS_JOIN : 0;
|
||||
size_t need = 2 + 8 + 32 + 32 + 1 + (flags & PEERS_FLAG_HAS_JOIN ? 72 : 0)
|
||||
+ 72 + 1 + (size_t)nl + 1 + (size_t)atl + 64 + 8 + 64;
|
||||
if (need > buf_sz) { sqlite3_finalize(stmt); return -1; }
|
||||
size_t off = 0;
|
||||
uint16_t wcnt = 1; memcpy(buf, &wcnt, 2); off += 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, 72); off += 72; }
|
||||
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); }
|
||||
else { memset(buf + off, 0, 64); }
|
||||
off += 64;
|
||||
memcpy(buf + off, &sb, 8); off += 8;
|
||||
if (sgn && sqlite3_column_bytes(stmt, 11) >= 64) { memcpy(buf + off, sgn, 64); }
|
||||
else { memset(buf + off, 0, 64); }
|
||||
off += 64;
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
*out_len = off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void member_sync_broadcast_one(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t member_id) {
|
||||
uint8_t buf[8192]; size_t off = 0;
|
||||
if (_serialize_member(inst, ch_id, member_id, buf, sizeof(buf), &off) != 0) return;
|
||||
merkle_sync_broadcast(inst, ch_id, inst->node_id, buf, off);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: broadcast_one ch=%s nid=0x%016llx size=%zu",
|
||||
MS_ID, ch_id, (unsigned long long)member_id, off);
|
||||
}
|
||||
|
||||
int member_sync_send_to(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
uint64_t member_id, uint64_t target_peer) {
|
||||
uint8_t buf[8192]; size_t off = 0;
|
||||
if (_serialize_member(inst, ch_id, member_id, buf, sizeof(buf), &off) != 0) return -1;
|
||||
return merkle_sync_send_to(inst, ch_id, target_peer, buf, off);
|
||||
}
|
||||
|
||||
int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id,
|
||||
uint64_t member_id, const uint8_t* x25519,
|
||||
@@ -1088,8 +1161,3 @@ int member_sync_count(struct UTUN_INSTANCE* inst, const char* ch_id) {
|
||||
sqlite3_finalize(stmt);
|
||||
return c;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in new issue
Block a user