@ -34,35 +34,54 @@ static void _peers_table(const char* ch_id, char* buf, size_t sz) {
snprintf ( buf , sz , " %s " , tbl ) ;
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 ,
static uint64_t member_read64 ( const uint8_t * p ) {
const uint8_t * ed25519 ,
uint64_t n = 0 ;
const uint8_t * join_sig , uint64_t join_ts ,
for ( int i = 0 ; i < 8 ; i + + ) n = ( n < < 8 ) | p [ i ] ;
const uint8_t * update_sig , uint64_t update_ts ,
return n ;
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 ] ) {
static int _compute_member_hash ( uint64_t node_id , const uint8_t * x25519 , const uint8_t * ed25519 ,
EVP_MD_CTX * ctx = EVP_MD_CTX_new ( ) ;
const uint8_t * join_sig , uint64_t join_ts , const uint8_t * update_sig , uint64_t update_ts ,
EVP_DigestInit_ex ( ctx , EVP_sha256 ( ) , NULL ) ;
const char * userinfo , const char * adm_tags , const uint8_t * adm_tags_sig ,
EVP_DigestUpdate ( ctx , & node_id , 8 ) ;
uint64_t signed_by , const uint8_t * signature , uint8_t hash_out [ MT_HASH_SIZE ] ) {
EVP_DigestUpdate ( ctx , x25519 , 32 ) ;
uint8_t data [ 1024 ] ;
EVP_DigestUpdate ( ctx , ed25519 , 32 ) ;
size_t off = 0 ;
EVP_DigestUpdate ( ctx , join_sig , 64 ) ;
member_write64 ( data + off , node_id ) ; off + = 8 ;
EVP_DigestUpdate ( ctx , & join_ts , 8 ) ;
memcpy ( data + off , x25519 , 32 ) ; off + = 32 ;
if ( update_sig ) EVP_DigestUpdate ( ctx , update_sig , 64 ) ; else { static const uint8_t z [ 64 ] ; EVP_DigestUpdate ( ctx , z , 64 ) ; }
memcpy ( data + off , ed25519 , 32 ) ; off + = 32 ;
EVP_DigestUpdate ( ctx , & update_ts , 8 ) ;
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 ;
uint8_t atl = adm_tags ? ( uint8_t ) strnlen ( adm_tags , 255 ) : 0 ;
if ( update_sig ) memcpy ( data + off , update_sig , 64 ) ; else memset ( data + off , 0 , 64 ) ;
EVP_DigestUpdate ( ctx , & atl , 1 ) ;
off + = 64 ; member_write64 ( data + off , update_ts ) ; off + = 8 ;
if ( atl ) EVP_DigestUpdate ( ctx , adm_tags , atl ) ;
size_t ul = userinfo ? strlen ( userinfo ) : 0 , atl = adm_tags ? strlen ( adm_tags ) : 0 ;
if ( adm_tags_sig ) EVP_DigestUpdate ( ctx , adm_tags_sig , 64 ) ; else { static const uint8_t z64 [ 64 ] ; EVP_DigestUpdate ( ctx , z64 , 64 ) ; }
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 ) ;
EVP_DigestUpdate ( ctx , & signed_by , 8 ) ;
return - 1 ;
if ( signature ) EVP_DigestUpdate ( ctx , signature , 64 ) ; else { static const uint8_t zsig [ 64 ] ; EVP_DigestUpdate ( ctx , zsig , 64 ) ; }
}
data [ off + + ] = ( uint8_t ) ul ;
EVP_DigestFinal_ex ( ctx , hash_out , NULL ) ;
if ( ul ) { memcpy ( data + off , userinfo , ul ) ; off + = ul ; }
EVP_MD_CTX_free ( ctx ) ;
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 ;
}
/* 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 ,
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 ) {
uint64_t prefix64 , EVP_MD_CTX * sha_ctx ) {
struct UTUN_INSTANCE * inst = ( struct UTUN_INSTANCE * ) 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 ; }
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 ) ;
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 ) ) ;
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 ) ,
char sql [ 512 ] ; snprintf ( sql , sizeof ( sql ) ,
" SELECT node_id, x25519_pubkey, ed25519_pubkey, "
" 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 \" "
" FROM \" %s \" "
" WHERE (node_id & %lld) == %lld ORDER BY node_id ASC " ,
" WHERE (node_id & %lld) == %lld ORDER BY node_id ASC " ,
peers_tbl , ( long long ) mask , ( long long ) prefix64 ) ;
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 ; }
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 ;
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 ) ;
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 * x25 = ( const uint8_t * ) sqlite3_column_blob ( stmt , 1 ) ;
const uint8_t * ed = ( const uint8_t * ) sqlite3_column_blob ( stmt , 2 ) ;
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 ) ;
uint64_t sb = ( uint64_t ) sqlite3_column_int64 ( stmt , 9 ) ;
const uint8_t * sgn = ( const uint8_t * ) sqlite3_column_blob ( stmt , 10 ) ;
const uint8_t * sgn = ( const uint8_t * ) sqlite3_column_blob ( stmt , 10 ) ;
int src = sqlite3_column_int ( stmt , 11 ) ;
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 ] ;
uint8_t mh [ MT_HASH_SIZE ] ;
_compute_member_hash ( nid , x25 , ed , sig , jts , usig , uts , atags , atsig , sb , sgn , mh ) ;
const char * userinfo = ( const char * ) sqlite3_column_text ( stmt , 12 ) ;
EVP_DigestUpdate ( sha_ctx , mh , MT_HASH_SIZE ) ;
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 + + ;
count + + ;
}
}
sqlite3_finalize ( stmt ) ;
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 ;
return count ;
}
}
static int _member_get_items ( void * ctx , const char * ns , uint8_t level ,
static int _member_get_page ( void * ctx , const char * ns , uint64_t prefix , int after_valid , uint64_t after ,
uint64_t prefix , uint8_t prefix_bytes ,
uint8_t * buf , size_t * len , uint64_t * next , int * more ) {
uint8_t * buf , size_t * len ) {
struct UTUN_INSTANCE * inst = ( struct UTUN_INSTANCE * ) ctx ;
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 ; }
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 ) ;
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 ) ) ;
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, "
" join_sig, join_ts, update_sig, update_ts, userinfo, adm_tags, adm_tags_sig, "
" signed_by, signature, source "
" signed_by, signature, source "
" FROM \" %s \" "
" 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 ) ;
peers_tbl , ( long long ) mask , ( long long ) prefix ) ;
sqlite3_stmt * stmt = NULL ;
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 ; }
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 ;
size_t off = 0 ;
if ( off + 2 > * len ) { sqlite3_finalize ( stmt ) ; return - 2 ; }
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 ) ;
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 * x25 = ( const uint8_t * ) sqlite3_column_blob ( stmt , 1 ) ;
const uint8_t * ed = ( const uint8_t * ) sqlite3_column_blob ( stmt , 2 ) ;
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 ) ;
uint64_t sb = ( uint64_t ) sqlite3_column_int64 ( stmt , 10 ) ;
const uint8_t * sgn = ( const uint8_t * ) sqlite3_column_blob ( stmt , 11 ) ;
const uint8_t * sgn = ( const uint8_t * ) sqlite3_column_blob ( stmt , 11 ) ;
int src = sqlite3_column_int ( stmt , 12 ) ;
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 nl = nm ? ( uint8_t ) strnlen ( nm , 255 ) : 0 ;
uint8_t atl = atags ? ( uint8_t ) strnlen ( atags , 255 ) : 0 ;
uint8_t atl = atags ? ( uint8_t ) strnlen ( atags , 255 ) : 0 ;
uint8_t flags = ( sig & & jts ) ? PEERS_FLAG_HAS_JOIN : 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 ;
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 ; }
if ( off + need > * len ) {
memcpy ( buf + off , & nid , 8 ) ; off + = 8 ;
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 , x25 , 32 ) ; off + = 32 ;
memcpy ( buf + off , ed , 32 ) ; off + = 32 ;
memcpy ( buf + off , ed , 32 ) ; off + = 32 ;
buf [ off + + ] = flags ;
buf [ off + + ] = flags ;
if ( flags & PEERS_FLAG_HAS_JOIN ) {
if ( flags & PEERS_FLAG_HAS_JOIN ) {
memcpy ( buf + off , sig , 64 ) ; off + = 64 ;
memcpy ( buf + off , sig , 64 ) ; off + = 64 ;
memcpy ( buf + off , & jts , 8 ) ; off + = 8 ;
member_write64 ( buf + off , jts ) ; off + = 8 ;
}
}
if ( usig & & uts ) {
if ( usig & & uts ) {
memcpy ( buf + off , usig , 64 ) ; off + = 64 ;
memcpy ( buf + off , usig , 64 ) ; off + = 64 ;
memcpy ( buf + off , & uts , 8 ) ; off + = 8 ;
member_write64 ( buf + off , uts ) ; off + = 8 ;
} else {
} else {
memset ( buf + off , 0 , 64 ) ; off + = 64 ;
memset ( buf + off , 0 , 64 ) ; off + = 64 ;
uint64_t z = 0 ; memcpy ( buf + off , & z , 8 ) ; off + = 8 ;
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 ( atl ) { memcpy ( buf + off , atags , atl ) ; off + = atl ; }
if ( atsig & & sqlite3_column_bytes ( stmt , 9 ) > = 64 ) { memcpy ( buf + off , atsig , 64 ) ; off + = 64 ; }
if ( atsig & & sqlite3_column_bytes ( stmt , 9 ) > = 64 ) { memcpy ( buf + off , atsig , 64 ) ; off + = 64 ; }
else { memset ( buf + off , 0 , 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 ; }
if ( sgn & & sqlite3_column_bytes ( stmt , 11 ) > = 64 ) { memcpy ( buf + off , sgn , 64 ) ; off + = 64 ; }
else { memset ( buf + off , 0 , 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 ) ;
sqlite3_finalize ( stmt ) ;
buf [ 0 ] = ( uint8_t ) ( cnt > > 8 ) ; buf [ 1 ] = ( uint8_t ) cnt ;
* len = off ;
* len = off ;
DEBUG_DEBUG ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: get_items ns=%s L%d/P%016llx mask=%016llx count=%u off=%zu " ,
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 ;
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 ) ;
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 ) {
uint64_t from_peer , const struct ms_member_rec * m ) {
if ( ! inst | | ! ch_id | | ! m | | ! m - > x25519 | | ! m - > ed25519 ) {
if ( ! inst | | ! ch_id | | ! m | | ! m - > x25519 | | ! m - > ed25519 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: apply_record — invalid args " , MS_ID ) ;
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 ;
sqlite3_stmt * st = NULL ;
char sql [ 512 ] ; snprintf ( sql , sizeof ( sql ) ,
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 ) ;
" 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 ) {
int rc = sqlite3_prepare_v2 ( db , sql , - 1 , & st , NULL ) ;
sqlite3_bind_int64 ( st , 1 , ( sqlite3_int64 ) m - > node_id ) ;
if ( rc = = SQLITE_OK ) rc = sqlite3_bind_int64 ( st , 1 , ( sqlite3_int64 ) m - > node_id ) ;
if ( sqlite3_step ( st ) = = SQLITE_ROW ) {
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 ;
has_local = 1 ;
const uint8_t * js = ( const uint8_t * ) sqlite3_column_blob ( st , 0 ) ;
const uint8_t * js = ( const uint8_t * ) sqlite3_column_blob ( st , 0 ) ;
if ( js ) memcpy ( local_join_sig , js , 64 ) ;
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 ) ;
if ( ls & & sqlite3_column_bytes ( st , 5 ) > = 64 ) memcpy ( local_signature , ls , 64 ) ;
local_src = sqlite3_column_int ( st , 6 ) ;
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 " ,
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 ) ;
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 ) {
if ( block_a_changed ) {
const uint8_t * jsig = m - > join_sig ? m - > join_sig : ( has_local ? local_join_sig : NULL ) ;
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) */
/* узел в nodes пишет только merkle-путь (verified identity) */
char node_name [ 128 ] = " " ;
char node_name [ 128 ] = " " ;
if ( m - > userinfo ) json_flat_get ( m - > userinfo , " name " , node_name , sizeof ( node_name ) ) ;
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 ,
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 ( 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 ) {
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 ;
if ( block_a_stale | | block_b_stale ) stale = 1 ;
/* пересчёт классификации (node_type/storage) — DB-хелпер, читает node_addresses (адреса BGP) */
/* пересчёт классификации (node_type/storage) — DB-хелпер, читает node_addresses (адреса BGP) */
if ( changed )
if ( changed & & topo_node_sqlite_nodeinfo_updated ( db , m - > node_id ) < 0 ) {
topo_node_sqlite_nodeinfo_updated ( db , m - > node_id ) ;
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 и подтвердить реальное изменение по хешу ── */
/* ── recompute и подтвердить реальное изменение по хешу ── */
if ( changed ) {
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 (данные идентичны) */
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 " ,
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 ) ;
MS_ID , ch_id , ( unsigned long long ) m - > node_id , changed , stale ) ;
return ( changed ? MS_APPLY_CHANGED : 0 ) | ( stale ? MS_APPLY_STALE : 0 ) ;
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 ) . О т с у т с т в и е п о д п и с и = « н е в е р и ф и ц и р о в а н о » , н е у д а л я е м . */
л и б о node_id ! = derive ( x25519 ) . О т с у т с т в и е п о д п и с и = « н е в е р и ф и ц и р о в а н о » , н е у д а л я е м . */
@ -789,76 +875,104 @@ void member_sync_verify_and_purge_all(struct UTUN_INSTANCE* inst) {
sqlite3_finalize ( st ) ;
sqlite3_finalize ( st ) ;
}
}
static int _member_apply_items ( void * ctx , const char * ns , uint64_t from_peer ,
struct member_wire {
const uint8_t * data , size_t len ) {
struct ms_member_rec rec ;
struct UTUN_INSTANCE * inst = ( struct UTUN_INSTANCE * ) ctx ;
char userinfo [ 256 ] , tags [ 256 ] ;
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 ; }
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 ;
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 ;
static int member_decode ( const uint8_t * p , size_t len , struct member_wire * w , size_t * used ) {
const uint8_t * x25 = mp ; mp + = 32 ; mrem - = 32 ;
memset ( w , 0 , sizeof ( * w ) ) ;
const uint8_t * ed = mp ; mp + = 32 ; mrem - = 32 ;
if ( len < 73 ) return - 1 ;
uint8_t flags = * mp + + ; mrem - - ;
struct ms_member_rec * m = & w - > rec ;
const uint8_t * jsig = NULL ; uint64_t jts = 0 ;
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 ( flags & PEERS_FLAG_HAS_JOIN ) {
if ( mrem < 72 ) break ; jsig = mp ; mp + = 64 ; mrem - = 64 ; memcpy ( & jts , mp , 8 ) ; mp + = 8 ; mrem - = 8 ;
if ( len - off < 72 ) return - 1 ;
} else {
m - > join_sig = p + off ; off + = 64 ; m - > join_ts = member_read64 ( p + off ) ; off + = 8 ;
/* lookup join from local DB */
}
uint8_t buf_jsig [ 64 ] ; uint64_t buf_jts ;
if ( len - off < 73 ) return - 1 ;
if ( db & & topo_node_sqlite_member_get_join ( db , ns , nid , buf_jsig , & buf_jts ) = = 0 ) { jsig = buf_jsig ; jts = buf_jts ; }
m - > update_sig = p + off ; off + = 64 ; m - > update_ts = member_read64 ( p + off ) ; off + = 8 ;
}
uint8_t n = p [ off + + ] ;
if ( mrem < 72 ) break ;
if ( len - off < ( size_t ) n + 1 | | memchr ( p + off , 0 , n ) ) return - 1 ;
const uint8_t * usig = mp ; mp + = 64 ; mrem - = 64 ; uint64_t uts ; memcpy ( & uts , mp , 8 ) ; mp + = 8 ; mrem - = 8 ;
memcpy ( w - > userinfo , p + off , n ) ; off + = n ;
uint8_t nl = * mp + + ; mrem - - ;
n = p [ off + + ] ;
char nm [ 256 ] = " " ;
if ( len - off < ( size_t ) n + 136 | | memchr ( p + off , 0 , n ) ) return - 1 ;
if ( nl & & mrem > = nl ) { memcpy ( nm , mp , nl ) ; nm [ nl ] = ' \0 ' ; mp + = nl ; mrem - = nl ; }
memcpy ( w - > tags , p + off , n ) ; off + = n ;
if ( mrem < 1 ) break ;
m - > adm_tags_sig = p + off ; off + = 64 ;
uint8_t atl = * mp + + ; mrem - - ;
m - > signed_by = member_read64 ( p + off ) ; off + = 8 ;
char atags [ 256 ] = " " ;
m - > signature = p + off ; off + = 64 ;
const uint8_t * atsig = NULL ;
m - > userinfo = w - > userinfo ; m - > adm_tags = w - > tags ;
if ( atl & & mrem > = atl ) { memcpy ( atags , mp , atl ) ; atags [ atl ] = ' \0 ' ; mp + = atl ; mrem - = atl ; }
* used = off ;
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 ) ;
}
}
if ( changed_count > 0 )
merkle_sync_broadcast ( inst , ns , from_peer , data , len ) ;
return 0 ;
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 ) ;
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 ;
}
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 ;
}
}
u_free ( changed ) ;
return rc ;
}
static int _member_validate_peer ( void * ctx , const char * ns , uint64_t peer ) {
static int _member_validate_peer ( void * ctx , const char * ns , uint64_t peer ) {
struct UTUN_INSTANCE * inst = ( struct UTUN_INSTANCE * ) ctx ;
struct UTUN_INSTANCE * inst = ( struct UTUN_INSTANCE * ) ctx ;
sqlite3 * db = _db ( inst ) ;
sqlite3 * db = _db ( inst ) ;
@ -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 ) ;
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 = {
static const struct merkle_sync_data_ops g_member_ops = {
. update_bucket_hash = _member_update_bucket_hash ,
. update_bucket_hash = _member_update_bucket_hash ,
. get_items = _member_get_items ,
. get_page = _member_get_page ,
. apply_items = _member_apply_items ,
. apply_items = _member_apply_items ,
. apply_update = NULL , /* online-статус больше не рассылается через merkle */
. validate_peer = _member_validate_peer ,
. validate_peer = _member_validate_peer ,
. finish = member_finish ,
} ;
} ;
/* ── Public API ── */
/* ── Public API ── */
static void _rebuild_all_trees ( struct UTUN_INSTANCE * inst ) {
static int _rebuild_all_trees ( struct UTUN_INSTANCE * inst ) {
sqlite3 * db = _db ( inst ) ; if ( ! db ) return ;
sqlite3 * db = _db ( inst ) ;
sqlite3_stmt * st = NULL ;
sqlite3_stmt * tables = NULL ;
if ( sqlite3_prepare_v2 ( db ,
int rc = sqlite3_prepare_v2 ( db , " SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers_%' " , - 1 , & tables , NULL ) ;
" SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers_%' " ,
if ( rc ! = SQLITE_OK ) goto fail ;
- 1 , & st , NULL ) ! = SQLITE_OK ) return ;
if ( sqlite3_exec ( db , " SAVEPOINT member_rebuild " , NULL , NULL , NULL ) ! = SQLITE_OK ) goto fail ;
while ( sqlite3_step ( st ) = = SQLITE_ROW ) {
while ( ( rc = sqlite3_step ( tables ) ) = = SQLITE_ROW ) {
const char * tbl = ( const char * ) sqlite3_column_text ( st , 0 ) ;
const char * table = ( const char * ) sqlite3_column_text ( tables , 0 ) ;
if ( ! tbl | | strncmp ( tbl , " peers_ " , 6 ) ! = 0 ) continue ;
if ( ! table | | strncmp ( table , " peers_ " , 6 ) ) continue ;
const char * ch_id = tbl + 6 ;
const char * ns = table + 6 ;
/* полный пересчёт: снести всё дерево канала, затем собрать заново из peers */
uint64_t mask = merkle_sync_level_prefix ( UINT64_MAX , MT_MAX_LEVEL ) ;
sqlite3_stmt * del = NULL ;
char sql [ 256 ] ;
if ( sqlite3_prepare_v2 ( db , " DELETE FROM merkle_tree_hash WHERE namespace=? " , - 1 , & del , NULL ) = = SQLITE_OK ) {
snprintf ( sql , sizeof ( sql ) , " SELECT DISTINCT (node_id & %lld) FROM \" %s \" WHERE source=0 " , ( long long ) mask , table ) ;
sqlite3_bind_text ( del , 1 , ch_id , - 1 , SQLITE_STATIC ) ;
sqlite3_stmt * leaves = NULL ;
sqlite3_step ( del ) ;
int lr = sqlite3_prepare_v2 ( db , sql , - 1 , & leaves , NULL ) ;
sqlite3_finalize ( del ) ;
if ( lr = = SQLITE_OK ) {
}
while ( ( lr = sqlite3_step ( leaves ) ) = = SQLITE_ROW ) {
sqlite3_stmt * ns = NULL ;
uint64_t key = ( uint64_t ) sqlite3_column_int64 ( leaves , 0 ) ;
char sql [ 256 ] ; snprintf ( sql , sizeof ( sql ) , " SELECT node_id FROM \" %s \" " , tbl ) ;
if ( merkle_sync_recompute_path ( inst , ns , key ) < 0 ) { lr = SQLITE_ERROR ; break ; }
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 ) ;
sqlite3_finalize ( leaves ) ;
merkle_sync_recompute_path ( inst , ch_id , nid ) ;
if ( lr ! = SQLITE_DONE ) { rc = lr ; break ; }
DEBUG_DEBUG ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: rebuild tree ns=%s nid=%016llx " , MS_ID , ch_id , ( unsigned long long ) nid ) ;
}
}
if ( rc ! = SQLITE_DONE ) {
sqlite3_finalize ( ns ) ;
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 ) ) ;
}
}
sqlite3_finalize ( st ) ;
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). */
/* Подписаться на 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 ;
if ( rc ! = 0 ) return rc ;
/* очистка битых записей во всех каналах перед построением дерева */
/* очистка битых записей во всех каналах перед построением дерева */
member_sync_verify_and_purge_all ( inst ) ;
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-групп */
/* подписка на BGP-события существующих chat-групп */
if ( inst - > topo_groups & & inst - > topo_groups - > group_list ) {
if ( inst - > topo_groups & & inst - > topo_groups - > group_list ) {
struct ll_entry * ge = inst - > topo_groups - > group_list - > head ;
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 ) ;
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 ,
int member_sync_put ( struct UTUN_INSTANCE * inst , const char * ch_id ,
uint64_t member_id , const uint8_t * x25519 ,
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 ) ;
sqlite3_finalize ( stmt ) ;
return c ;
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 ) ;
}