@ -11,6 +11,7 @@
# include "../lib/socket_compat.h"
# include "../lib/socket_compat.h"
# include "merkle_sync.h"
# include "merkle_sync.h"
# include "../src/utun_instance.h"
# include "../src/utun_instance.h"
# include "../src/routing_layer/topo_group.h"
# include "../src/config_updater.h"
# include "../src/config_updater.h"
# include "../src/tun_if.h"
# include "../src/tun_if.h"
# include "etcp.h"
# include "etcp.h"
@ -19,6 +20,21 @@
# include <sqlite3.h>
# include <sqlite3.h>
# include <openssl/evp.h>
# include <openssl/evp.h>
/* numeric channel_id == group_id для CHAT-групп (recv требует существующую группу) */
# define MS_NS_TEST "1001"
# define MS_NS_RND "1002"
# define MS_NS_STRESS "1003"
static void _ms_ensure_groups ( struct UTUN_INSTANCE * inst ) {
if ( ! inst | | ! inst - > topo_groups ) return ;
const char * nss [ ] = { MS_NS_TEST , MS_NS_RND , MS_NS_STRESS } ;
for ( int i = 0 ; i < 3 ; i + + ) {
uint64_t gid = strtoull ( nss [ i ] , NULL , 10 ) ;
if ( ! topo_groups_find ( inst - > topo_groups , gid ) )
topo_groups_create_group ( inst - > topo_groups , gid , TOPO_GROUP_TYPE_CHAT , nss [ i ] ) ;
}
}
/* merkle_sync private struct — needed for Stage 2 unit tests.
/* merkle_sync private struct — needed for Stage 2 unit tests.
* Must match struct ms_session prefix in merkle_sync . c exactly . */
* Must match struct ms_session prefix in merkle_sync . c exactly . */
struct ms_session {
struct ms_session {
@ -320,8 +336,8 @@ static void test_tree_empty_ns(void) {
ms . inst = & inst ; ms . ops = & g_test_ops ; ms . data_ctx = & ctx ; ms . initialized = 1 ;
ms . inst = & inst ; ms . ops = & g_test_ops ; ms . data_ctx = & ctx ; ms . initialized = 1 ;
inst . msync = & ms ;
inst . msync = & ms ;
merkle_sync_recompute_path ( & inst , " test " , 0x1234ULL ) ;
merkle_sync_recompute_path ( & inst , MS_NS_TEST , 0x1234ULL ) ;
if ( _db_count_rows ( db , " test " ) = = 0 ) PASS ( ) ; else FAIL ( " got %d rows " , _db_count_rows ( db , " test " ) ) ;
if ( _db_count_rows ( db , MS_NS_TEST ) = = 0 ) PASS ( ) ; else FAIL ( " got %d rows " , _db_count_rows ( db , MS_NS_TEST ) ) ;
sqlite3_close ( db ) ;
sqlite3_close ( db ) ;
}
}
@ -342,15 +358,15 @@ static void test_tree_single_item(void) {
ms . inst = & inst ; ms . ops = & g_test_ops ; ms . data_ctx = & ctx ; ms . initialized = 1 ;
ms . inst = & inst ; ms . ops = & g_test_ops ; ms . data_ctx = & ctx ; ms . initialized = 1 ;
inst . msync = & ms ;
inst . msync = & ms ;
merkle_sync_recompute_path ( & inst , " test " , key ) ;
merkle_sync_recompute_path ( & inst , MS_NS_TEST , key ) ;
int rows = _db_count_rows ( db , " test " ) ;
int rows = _db_count_rows ( db , MS_NS_TEST ) ;
if ( rows ! = 5 ) { FAIL ( " expected 5 rows got %d " , rows ) ; sqlite3_close ( db ) ; return ; }
if ( rows ! = 5 ) { FAIL ( " expected 5 rows got %d " , rows ) ; sqlite3_close ( db ) ; return ; }
/* check hashes are non-zero */
/* check hashes are non-zero */
int ok = 1 ;
int ok = 1 ;
for ( uint8_t lv = 1 ; lv < = 5 & & ok ; lv + + ) {
for ( uint8_t lv = 1 ; lv < = 5 & & ok ; lv + + ) {
uint64_t pf = merkle_sync_level_prefix ( key , lv ) ;
uint64_t pf = merkle_sync_level_prefix ( key , lv ) ;
const uint8_t * h = merkle_sync_get_hash ( & inst , " test " , lv , pf ) ;
const uint8_t * h = merkle_sync_get_hash ( & inst , MS_NS_TEST , lv , pf ) ;
int zero = 1 ;
int zero = 1 ;
for ( int i = 0 ; i < MT_HASH_SIZE ; i + + ) if ( h [ i ] ! = 0 ) zero = 0 ;
for ( int i = 0 ; i < MT_HASH_SIZE ; i + + ) if ( h [ i ] ! = 0 ) zero = 0 ;
if ( zero ) { FAIL ( " L%d zero hash " , lv ) ; ok = 0 ; }
if ( zero ) { FAIL ( " L%d zero hash " , lv ) ; ok = 0 ; }
@ -378,8 +394,8 @@ static void test_tree_multi_item_same_bucket(void) {
inst . msync = & ms ;
inst . msync = & ms ;
/* recompute for both */
/* recompute for both */
merkle_sync_recompute_path ( & inst , " test " , k1 ) ;
merkle_sync_recompute_path ( & inst , MS_NS_TEST , k1 ) ;
merkle_sync_recompute_path ( & inst , " test " , k2 ) ;
merkle_sync_recompute_path ( & inst , MS_NS_TEST , k2 ) ;
uint64_t pf1 = merkle_sync_level_prefix ( k1 , 1 ) ;
uint64_t pf1 = merkle_sync_level_prefix ( k1 , 1 ) ;
uint64_t pf2 = merkle_sync_level_prefix ( k2 , 1 ) ;
uint64_t pf2 = merkle_sync_level_prefix ( k2 , 1 ) ;
@ -395,7 +411,7 @@ static void test_tree_multi_item_same_bucket(void) {
EVP_DigestFinal_ex ( ctx , expected_hash , NULL ) ;
EVP_DigestFinal_ex ( ctx , expected_hash , NULL ) ;
EVP_MD_CTX_free ( ctx ) ;
EVP_MD_CTX_free ( ctx ) ;
}
}
const uint8_t * stored = merkle_sync_get_hash ( & inst , " test " , 1 , pf1 ) ;
const uint8_t * stored = merkle_sync_get_hash ( & inst , MS_NS_TEST , 1 , pf1 ) ;
if ( memcmp ( expected_hash , stored , MT_HASH_SIZE ) = = 0 ) PASS ( ) ; else FAIL ( " hash mismatch " ) ;
if ( memcmp ( expected_hash , stored , MT_HASH_SIZE ) = = 0 ) PASS ( ) ; else FAIL ( " hash mismatch " ) ;
sqlite3_close ( db ) ;
sqlite3_close ( db ) ;
@ -449,16 +465,16 @@ static void test_tree_delete_empty_bucket(void) {
ms . inst = & inst ; ms . ops = & g_test_ops ; ms . data_ctx = & ctx ; ms . initialized = 1 ;
ms . inst = & inst ; ms . ops = & g_test_ops ; ms . data_ctx = & ctx ; ms . initialized = 1 ;
inst . msync = & ms ;
inst . msync = & ms ;
merkle_sync_recompute_path ( & inst , " test " , k ) ;
merkle_sync_recompute_path ( & inst , MS_NS_TEST , k ) ;
if ( _db_count_rows ( db , " test " ) ! = 5 ) { FAIL ( " expected 5 rows " ) ; sqlite3_close ( db ) ; return ; }
if ( _db_count_rows ( db , MS_NS_TEST ) ! = 5 ) { FAIL ( " expected 5 rows " ) ; sqlite3_close ( db ) ; return ; }
/* remove item and recompute */
/* remove item and recompute */
data . count = 0 ;
data . count = 0 ;
merkle_sync_recompute_path ( & inst , " test " , k ) ;
merkle_sync_recompute_path ( & inst , MS_NS_TEST , k ) ;
if ( _db_count_rows ( db , " test " ) ! = 0 ) { FAIL ( " expected 0 rows got %d " , _db_count_rows ( db , " test " ) ) ; sqlite3_close ( db ) ; return ; }
if ( _db_count_rows ( db , MS_NS_TEST ) ! = 0 ) { FAIL ( " expected 0 rows got %d " , _db_count_rows ( db , MS_NS_TEST ) ) ; sqlite3_close ( db ) ; return ; }
uint64_t pf = merkle_sync_level_prefix ( k , 3 ) ;
uint64_t pf = merkle_sync_level_prefix ( k , 3 ) ;
const uint8_t * h = merkle_sync_get_hash ( & inst , " test " , 3 , pf ) ;
const uint8_t * h = merkle_sync_get_hash ( & inst , MS_NS_TEST , 3 , pf ) ;
int zero = 1 ;
int zero = 1 ;
for ( int i = 0 ; i < MT_HASH_SIZE ; i + + ) if ( h [ i ] ! = 0 ) zero = 0 ;
for ( int i = 0 ; i < MT_HASH_SIZE ; i + + ) if ( h [ i ] ! = 0 ) zero = 0 ;
if ( zero ) PASS ( ) ; else FAIL ( " hash not zero after delete " ) ;
if ( zero ) PASS ( ) ; else FAIL ( " hash not zero after delete " ) ;
@ -694,6 +710,7 @@ static int _intg_init_two(void) {
_intg_setup_contexts ( ) ;
_intg_setup_contexts ( ) ;
ctx_a . inst = i_a ; ctx_b . inst = i_b ;
ctx_a . inst = i_a ; ctx_b . inst = i_b ;
_ms_ensure_groups ( i_a ) ; _ms_ensure_groups ( i_b ) ;
if ( merkle_sync_init ( i_a , MS_SVC_ID , & g_test_ops , & ctx_a ) ! = 0 | |
if ( merkle_sync_init ( i_a , MS_SVC_ID , & g_test_ops , & ctx_a ) ! = 0 | |
merkle_sync_init ( i_b , MS_SVC_ID , & g_test_ops , & ctx_b ) ! = 0 )
merkle_sync_init ( i_b , MS_SVC_ID , & g_test_ops , & ctx_b ) ! = 0 )
{ _intg_cleanup ( ) ; return - 1 ; }
{ _intg_cleanup ( ) ; return - 1 ; }
@ -709,7 +726,7 @@ static void _intg_ins_many(struct ms_data* d, struct UTUN_INSTANCE* inst, int ba
}
}
_data_sort ( d ) ;
_data_sort ( d ) ;
for ( int i = 0 ; i < d - > count ; i + + )
for ( int i = 0 ; i < d - > count ; i + + )
merkle_sync_recompute_path ( inst , " test " , d - > items [ i ] . key ) ;
merkle_sync_recompute_path ( inst , MS_NS_TEST , d - > items [ i ] . key ) ;
}
}
static int _intg_compare_data ( struct ms_data * a , struct ms_data * b ) {
static int _intg_compare_data ( struct ms_data * a , struct ms_data * b ) {
@ -736,14 +753,14 @@ static void test_peer_empty(void) {
if ( _intg_init_two ( ) ! = 0 ) { FAIL ( " setup failed " ) ; return ; }
if ( _intg_init_two ( ) ! = 0 ) { FAIL ( " setup failed " ) ; return ; }
_intg_ins_many ( & data_b , i_b , 0 , 5 ) ;
_intg_ins_many ( & data_b , i_b , 0 , 5 ) ;
done_sync = 0 ;
done_sync = 0 ;
merkle_sync_start ( i_a , i_b - > node_id , " test " , _on_sync_done , NULL ) ;
merkle_sync_start ( i_a , i_b - > node_id , MS_NS_TEST , _on_sync_done , NULL ) ;
if ( ! _wait_for ( " sync done " , _cond_done , PHASE_TIMEOUT_TB ) )
if ( ! _wait_for ( " sync done " , _cond_done , PHASE_TIMEOUT_TB ) )
{ FAIL ( " sync timeout " ) ; _intg_cleanup ( ) ; return ; }
{ FAIL ( " sync timeout " ) ; _intg_cleanup ( ) ; return ; }
printf ( " A data=%d B data=%d, A tree=%d B tree=%d \n " ,
printf ( " A data=%d B data=%d, A tree=%d B tree=%d \n " ,
data_a . count , data_b . count ,
data_a . count , data_b . count ,
_db_count_rows ( i_a - > topo_sqlite_db , " test " ) ,
_db_count_rows ( i_a - > topo_sqlite_db , MS_NS_TEST ) ,
_db_count_rows ( i_b - > topo_sqlite_db , " test " ) ) ;
_db_count_rows ( i_b - > topo_sqlite_db , MS_NS_TEST ) ) ;
if ( _intg_verify_all ( i_a , i_b , & data_a , & data_b , " test " ) ! = 0 )
if ( _intg_verify_all ( i_a , i_b , & data_a , & data_b , MS_NS_TEST ) ! = 0 )
{ FAIL ( " verify failed " ) ; _intg_cleanup ( ) ; return ; }
{ FAIL ( " verify failed " ) ; _intg_cleanup ( ) ; return ; }
PASS ( ) ;
PASS ( ) ;
_intg_cleanup ( ) ;
_intg_cleanup ( ) ;
@ -755,10 +772,10 @@ static void test_hash_match(void) {
_intg_ins_many ( & data_a , i_a , 0 , 5 ) ;
_intg_ins_many ( & data_a , i_a , 0 , 5 ) ;
_intg_ins_many ( & data_b , i_b , 0 , 5 ) ;
_intg_ins_many ( & data_b , i_b , 0 , 5 ) ;
done_sync = 0 ;
done_sync = 0 ;
merkle_sync_start ( i_a , i_b - > node_id , " test " , _on_sync_done , NULL ) ;
merkle_sync_start ( i_a , i_b - > node_id , MS_NS_TEST , _on_sync_done , NULL ) ;
for ( int i = 0 ; i < 200 & & ! done_sync & & i_phase = = 0 ; i + + ) uasync_poll ( i_ua , 1 ) ;
for ( int i = 0 ; i < 200 & & ! done_sync & & i_phase = = 0 ; i + + ) uasync_poll ( i_ua , 1 ) ;
if ( ! done_sync ) { FAIL ( " sync did not complete quickly " ) ; _intg_cleanup ( ) ; return ; }
if ( ! done_sync ) { FAIL ( " sync did not complete quickly " ) ; _intg_cleanup ( ) ; return ; }
if ( _intg_verify_all ( i_a , i_b , & data_a , & data_b , " test " ) ! = 0 )
if ( _intg_verify_all ( i_a , i_b , & data_a , & data_b , MS_NS_TEST ) ! = 0 )
{ FAIL ( " verify failed " ) ; _intg_cleanup ( ) ; return ; }
{ FAIL ( " verify failed " ) ; _intg_cleanup ( ) ; return ; }
PASS ( ) ;
PASS ( ) ;
_intg_cleanup ( ) ;
_intg_cleanup ( ) ;
@ -770,10 +787,10 @@ static void test_divergence_merge(void) {
_intg_ins_many ( & data_a , i_a , 0 , 3 ) ;
_intg_ins_many ( & data_a , i_a , 0 , 3 ) ;
_intg_ins_many ( & data_b , i_b , 10 , 2 ) ;
_intg_ins_many ( & data_b , i_b , 10 , 2 ) ;
done_sync = 0 ;
done_sync = 0 ;
merkle_sync_start ( i_a , i_b - > node_id , " test " , _on_sync_done , NULL ) ;
merkle_sync_start ( i_a , i_b - > node_id , MS_NS_TEST , _on_sync_done , NULL ) ;
if ( ! _wait_for ( " sync done " , _cond_done , PHASE_TIMEOUT_TB ) )
if ( ! _wait_for ( " sync done " , _cond_done , PHASE_TIMEOUT_TB ) )
{ FAIL ( " sync timeout " ) ; _intg_cleanup ( ) ; return ; }
{ FAIL ( " sync timeout " ) ; _intg_cleanup ( ) ; return ; }
if ( _intg_verify_all ( i_a , i_b , & data_a , & data_b , " test " ) ! = 0 )
if ( _intg_verify_all ( i_a , i_b , & data_a , & data_b , MS_NS_TEST ) ! = 0 )
{ FAIL ( " verify failed " ) ; _intg_cleanup ( ) ; return ; }
{ FAIL ( " verify failed " ) ; _intg_cleanup ( ) ; return ; }
PASS ( ) ;
PASS ( ) ;
_intg_cleanup ( ) ;
_intg_cleanup ( ) ;
@ -806,17 +823,17 @@ static void test_randomized_two(void) {
_data_insert ( & data_b , k , ( uint32_t ) rand ( ) ) ;
_data_insert ( & data_b , k , ( uint32_t ) rand ( ) ) ;
}
}
_data_sort ( & data_a ) ; _data_sort ( & data_b ) ;
_data_sort ( & data_a ) ; _data_sort ( & data_b ) ;
for ( int i = 0 ; i < data_a . count ; i + + ) merkle_sync_recompute_path ( i_a , " rnd " , data_a . items [ i ] . key ) ;
for ( int i = 0 ; i < data_a . count ; i + + ) merkle_sync_recompute_path ( i_a , MS_NS_RND , data_a . items [ i ] . key ) ;
for ( int i = 0 ; i < data_b . count ; i + + ) merkle_sync_recompute_path ( i_b , " rnd " , data_b . items [ i ] . key ) ;
for ( int i = 0 ; i < data_b . count ; i + + ) merkle_sync_recompute_path ( i_b , MS_NS_RND , data_b . items [ i ] . key ) ;
done_sync = 0 ;
done_sync = 0 ;
merkle_sync_start ( i_a , i_b - > node_id , " rnd " , _on_sync_done , NULL ) ;
merkle_sync_start ( i_a , i_b - > node_id , MS_NS_RND , _on_sync_done , NULL ) ;
if ( ! _wait_for ( " sync " , _cond_done , PHASE_TIMEOUT_TB ) ) { FAIL ( " timeout iter %d " , iter ) ; _intg_cleanup ( ) ; break ; }
if ( ! _wait_for ( " sync " , _cond_done , PHASE_TIMEOUT_TB ) ) { FAIL ( " timeout iter %d " , iter ) ; _intg_cleanup ( ) ; break ; }
if ( _intg_verify_all ( i_a , i_b , & data_a , & data_b , " rnd " ) ! = 0 ) {
if ( _intg_verify_all ( i_a , i_b , & data_a , & data_b , MS_NS_RND ) ! = 0 ) {
printf ( " A data=%d B data=%d A tree=%d B tree=%d \n " ,
printf ( " A data=%d B data=%d A tree=%d B tree=%d \n " ,
data_a . count , data_b . count ,
data_a . count , data_b . count ,
_db_count_rows ( i_a - > topo_sqlite_db , " rnd " ) ,
_db_count_rows ( i_a - > topo_sqlite_db , MS_NS_RND ) ,
_db_count_rows ( i_b - > topo_sqlite_db , " rnd " ) ) ;
_db_count_rows ( i_b - > topo_sqlite_db , MS_NS_RND ) ) ;
/* dump items only in A */
/* dump items only in A */
for ( int ai = 0 ; ai < data_a . count ; ai + + ) {
for ( int ai = 0 ; ai < data_a . count ; ai + + ) {
int found = 0 ;
int found = 0 ;
@ -839,16 +856,16 @@ static void test_randomized_two(void) {
sqlite3_prepare_v2 ( i_a - > topo_sqlite_db ,
sqlite3_prepare_v2 ( i_a - > topo_sqlite_db ,
" SELECT level,prefix64 FROM merkle_tree_hash WHERE namespace=? ORDER BY level,prefix64 " ,
" SELECT level,prefix64 FROM merkle_tree_hash WHERE namespace=? ORDER BY level,prefix64 " ,
- 1 , & st , NULL ) ;
- 1 , & st , NULL ) ;
sqlite3_bind_text ( st , 1 , " rnd " , - 1 , SQLITE_STATIC ) ;
sqlite3_bind_text ( st , 1 , MS_NS_RND , - 1 , SQLITE_STATIC ) ;
while ( sqlite3_step ( st ) = = SQLITE_ROW & & ok ) {
while ( sqlite3_step ( st ) = = SQLITE_ROW & & ok ) {
uint8_t lv = ( uint8_t ) sqlite3_column_int ( st , 0 ) ;
uint8_t lv = ( uint8_t ) sqlite3_column_int ( st , 0 ) ;
uint64_t pf = ( uint64_t ) sqlite3_column_int64 ( st , 1 ) ;
uint64_t pf = ( uint64_t ) sqlite3_column_int64 ( st , 1 ) ;
EVP_MD_CTX * c = EVP_MD_CTX_new ( ) ;
EVP_MD_CTX * c = EVP_MD_CTX_new ( ) ;
EVP_DigestInit_ex ( c , EVP_sha256 ( ) , NULL ) ;
EVP_DigestInit_ex ( c , EVP_sha256 ( ) , NULL ) ;
_bucket_hash ( & ctx_a , " rnd " , lv , pf , c ) ;
_bucket_hash ( & ctx_a , MS_NS_RND , lv , pf , c ) ;
uint8_t expected [ MT_HASH_SIZE ] ;
uint8_t expected [ MT_HASH_SIZE ] ;
EVP_DigestFinal_ex ( c , expected , NULL ) ; EVP_MD_CTX_free ( c ) ;
EVP_DigestFinal_ex ( c , expected , NULL ) ; EVP_MD_CTX_free ( c ) ;
const uint8_t * stored = merkle_sync_get_hash ( i_a , " rnd " , lv , pf ) ;
const uint8_t * stored = merkle_sync_get_hash ( i_a , MS_NS_RND , lv , pf ) ;
uint8_t scopy [ MT_HASH_SIZE ] ; memcpy ( scopy , stored , MT_HASH_SIZE ) ;
uint8_t scopy [ MT_HASH_SIZE ] ; memcpy ( scopy , stored , MT_HASH_SIZE ) ;
if ( memcmp ( expected , scopy , MT_HASH_SIZE ) ! = 0 ) { ok = 0 ; }
if ( memcmp ( expected , scopy , MT_HASH_SIZE ) ! = 0 ) { ok = 0 ; }
}
}
@ -879,6 +896,7 @@ static int _intg_init_three(void) {
for ( int i = 0 ; i < 20 ; i + + ) uasync_poll ( i_ua , 5 ) ;
for ( int i = 0 ; i < 20 ; i + + ) uasync_poll ( i_ua , 5 ) ;
_intg_setup_contexts ( ) ;
_intg_setup_contexts ( ) ;
ctx_a . inst = i_a ; ctx_b . inst = i_b ; ctx_c . inst = i_c ;
ctx_a . inst = i_a ; ctx_b . inst = i_b ; ctx_c . inst = i_c ;
_ms_ensure_groups ( i_a ) ; _ms_ensure_groups ( i_b ) ; _ms_ensure_groups ( i_c ) ;
if ( merkle_sync_init ( i_a , MS_SVC_ID , & g_test_ops , & ctx_a ) ! = 0 | |
if ( merkle_sync_init ( i_a , MS_SVC_ID , & g_test_ops , & ctx_a ) ! = 0 | |
merkle_sync_init ( i_b , MS_SVC_ID , & g_test_ops , & ctx_b ) ! = 0 | |
merkle_sync_init ( i_b , MS_SVC_ID , & g_test_ops , & ctx_b ) ! = 0 | |
merkle_sync_init ( i_c , MS_SVC_ID , & g_test_ops , & ctx_c ) ! = 0 )
merkle_sync_init ( i_c , MS_SVC_ID , & g_test_ops , & ctx_c ) ! = 0 )
@ -890,8 +908,8 @@ static int _intg_init_three(void) {
static int _cond_all_synced ( void ) {
static int _cond_all_synced ( void ) {
if ( data_a . count ! = data_b . count | | data_b . count ! = data_c . count ) return 0 ;
if ( data_a . count ! = data_b . count | | data_b . count ! = data_c . count ) return 0 ;
if ( _db_compare_trees ( i_a - > topo_sqlite_db , i_b - > topo_sqlite_db , " rnd " ) ! = 0 ) return 0 ;
if ( _db_compare_trees ( i_a - > topo_sqlite_db , i_b - > topo_sqlite_db , MS_NS_RND ) ! = 0 ) return 0 ;
if ( _db_compare_trees ( i_b - > topo_sqlite_db , i_c - > topo_sqlite_db , " rnd " ) ! = 0 ) return 0 ;
if ( _db_compare_trees ( i_b - > topo_sqlite_db , i_c - > topo_sqlite_db , MS_NS_RND ) ! = 0 ) return 0 ;
return 1 ;
return 1 ;
}
}
@ -905,24 +923,24 @@ static void test_randomized_three(void) {
for ( int i = 0 ; i < na ; i + + ) { uint64_t k = ( ( uint64_t ) rand ( ) < < 32 ) | ( uint64_t ) rand ( ) ; _data_insert ( & data_a , k , ( uint32_t ) rand ( ) ) ; }
for ( int i = 0 ; i < na ; i + + ) { uint64_t k = ( ( uint64_t ) rand ( ) < < 32 ) | ( uint64_t ) rand ( ) ; _data_insert ( & data_a , k , ( uint32_t ) rand ( ) ) ; }
for ( int i = 0 ; i < nb ; i + + ) { uint64_t k = ( ( uint64_t ) rand ( ) < < 32 ) | ( uint64_t ) rand ( ) ; _data_insert ( & data_b , k , ( uint32_t ) rand ( ) ) ; }
for ( int i = 0 ; i < nb ; i + + ) { uint64_t k = ( ( uint64_t ) rand ( ) < < 32 ) | ( uint64_t ) rand ( ) ; _data_insert ( & data_b , k , ( uint32_t ) rand ( ) ) ; }
for ( int i = 0 ; i < nc ; i + + ) { uint64_t k = ( ( uint64_t ) rand ( ) < < 32 ) | ( uint64_t ) rand ( ) ; _data_insert ( & data_c , k , ( uint32_t ) rand ( ) ) ; }
for ( int i = 0 ; i < nc ; i + + ) { uint64_t k = ( ( uint64_t ) rand ( ) < < 32 ) | ( uint64_t ) rand ( ) ; _data_insert ( & data_c , k , ( uint32_t ) rand ( ) ) ; }
_data_sort ( & data_a ) ; for ( int i = 0 ; i < data_a . count ; i + + ) merkle_sync_recompute_path ( i_a , " rnd " , data_a . items [ i ] . key ) ;
_data_sort ( & data_a ) ; for ( int i = 0 ; i < data_a . count ; i + + ) merkle_sync_recompute_path ( i_a , MS_NS_RND , data_a . items [ i ] . key ) ;
_data_sort ( & data_b ) ; for ( int i = 0 ; i < data_b . count ; i + + ) merkle_sync_recompute_path ( i_b , " rnd " , data_b . items [ i ] . key ) ;
_data_sort ( & data_b ) ; for ( int i = 0 ; i < data_b . count ; i + + ) merkle_sync_recompute_path ( i_b , MS_NS_RND , data_b . items [ i ] . key ) ;
_data_sort ( & data_c ) ; for ( int i = 0 ; i < data_c . count ; i + + ) merkle_sync_recompute_path ( i_c , " rnd " , data_c . items [ i ] . key ) ;
_data_sort ( & data_c ) ; for ( int i = 0 ; i < data_c . count ; i + + ) merkle_sync_recompute_path ( i_c , MS_NS_RND , data_c . items [ i ] . key ) ;
done_sync = 0 ; i_phase = 0 ;
done_sync = 0 ; i_phase = 0 ;
/* B→A: B syncs with A, gets A's data, B: SYNCED */
/* B→A: B syncs with A, gets A's data, B: SYNCED */
merkle_sync_start ( i_b , i_a - > node_id , " rnd " , _on_sync_done , NULL ) ;
merkle_sync_start ( i_b , i_a - > node_id , MS_NS_RND , _on_sync_done , NULL ) ;
if ( ! _wait_for ( " sync B " , _cond_done , PHASE_TIMEOUT_TB ) ) { FAIL ( " timeout B iter %d " , iter ) ; _intg_cleanup ( ) ; break ; }
if ( ! _wait_for ( " sync B " , _cond_done , PHASE_TIMEOUT_TB ) ) { FAIL ( " timeout B iter %d " , iter ) ; _intg_cleanup ( ) ; break ; }
done_sync = 0 ; i_phase = 0 ;
done_sync = 0 ; i_phase = 0 ;
/* C→A: C syncs with A, gets A∪B, A broadcasts C's new items to B (relay) */
/* C→A: C syncs with A, gets A∪B, A broadcasts C's new items to B (relay) */
merkle_sync_start ( i_c , i_a - > node_id , " rnd " , _on_sync_done , NULL ) ;
merkle_sync_start ( i_c , i_a - > node_id , MS_NS_RND , _on_sync_done , NULL ) ;
if ( ! _wait_for ( " sync C " , _cond_done , PHASE_TIMEOUT_TB ) ) { FAIL ( " timeout C iter %d " , iter ) ; _intg_cleanup ( ) ; break ; }
if ( ! _wait_for ( " sync C " , _cond_done , PHASE_TIMEOUT_TB ) ) { FAIL ( " timeout C iter %d " , iter ) ; _intg_cleanup ( ) ; break ; }
/* wait for broadcast relay */
/* wait for broadcast relay */
for ( int k = 0 ; k < 100 ; k + + ) uasync_poll ( i_ua , 10 ) ;
for ( int k = 0 ; k < 100 ; k + + ) uasync_poll ( i_ua , 10 ) ;
if ( _intg_compare_data ( & data_a , & data_b ) ! = 0 | | _intg_compare_data ( & data_b , & data_c ) ! = 0 )
if ( _intg_compare_data ( & data_a , & data_b ) ! = 0 | | _intg_compare_data ( & data_b , & data_c ) ! = 0 )
{ FAIL ( " data mismatch iter %d " , iter ) ; _intg_cleanup ( ) ; break ; }
{ FAIL ( " data mismatch iter %d " , iter ) ; _intg_cleanup ( ) ; break ; }
if ( _db_compare_trees ( i_a - > topo_sqlite_db , i_b - > topo_sqlite_db , " rnd " ) ! = 0 | |
if ( _db_compare_trees ( i_a - > topo_sqlite_db , i_b - > topo_sqlite_db , MS_NS_RND ) ! = 0 | |
_db_compare_trees ( i_b - > topo_sqlite_db , i_c - > topo_sqlite_db , " rnd " ) ! = 0 )
_db_compare_trees ( i_b - > topo_sqlite_db , i_c - > topo_sqlite_db , MS_NS_RND ) ! = 0 )
{ FAIL ( " tree mismatch iter %d " , iter ) ; _intg_cleanup ( ) ; break ; }
{ FAIL ( " tree mismatch iter %d " , iter ) ; _intg_cleanup ( ) ; break ; }
_intg_cleanup ( ) ;
_intg_cleanup ( ) ;
}
}
@ -936,9 +954,9 @@ static void test_cancel(void) {
if ( _intg_init_two ( ) ! = 0 ) { FAIL ( " setup failed " ) ; return ; }
if ( _intg_init_two ( ) ! = 0 ) { FAIL ( " setup failed " ) ; return ; }
_intg_ins_many ( & data_b , i_b , 0 , 5 ) ;
_intg_ins_many ( & data_b , i_b , 0 , 5 ) ;
done_sync = 0 ;
done_sync = 0 ;
merkle_sync_start ( i_a , i_b - > node_id , " test " , _on_sync_done , NULL ) ;
merkle_sync_start ( i_a , i_b - > node_id , MS_NS_TEST , _on_sync_done , NULL ) ;
uasync_poll ( i_ua , 1 ) ;
uasync_poll ( i_ua , 1 ) ;
merkle_sync_cancel ( i_a , i_b - > node_id , " test " ) ;
merkle_sync_cancel ( i_a , i_b - > node_id , MS_NS_TEST ) ;
for ( int i = 0 ; i < 100 & & i_phase = = 0 ; i + + ) uasync_poll ( i_ua , 10 ) ;
for ( int i = 0 ; i < 100 & & i_phase = = 0 ; i + + ) uasync_poll ( i_ua , 10 ) ;
if ( ! done_sync ) PASS ( ) ; else FAIL ( " done_cb was called " ) ;
if ( ! done_sync ) PASS ( ) ; else FAIL ( " done_cb was called " ) ;
_intg_cleanup ( ) ;
_intg_cleanup ( ) ;
@ -953,8 +971,8 @@ static void test_start_overwrite(void) {
if ( _intg_init_two ( ) ! = 0 ) { FAIL ( " setup failed " ) ; return ; }
if ( _intg_init_two ( ) ! = 0 ) { FAIL ( " setup failed " ) ; return ; }
_intg_ins_many ( & data_b , i_b , 0 , 3 ) ;
_intg_ins_many ( & data_b , i_b , 0 , 3 ) ;
sw_cb1_called = sw_cb2_called = 0 ;
sw_cb1_called = sw_cb2_called = 0 ;
merkle_sync_start ( i_a , i_b - > node_id , " test " , _sw_done1 , NULL ) ;
merkle_sync_start ( i_a , i_b - > node_id , MS_NS_TEST , _sw_done1 , NULL ) ;
merkle_sync_start ( i_a , i_b - > node_id , " test " , _sw_done2 , NULL ) ;
merkle_sync_start ( i_a , i_b - > node_id , MS_NS_TEST , _sw_done2 , NULL ) ;
for ( int i = 0 ; i < 500 & & ! sw_cb2_called & & i_phase = = 0 ; i + + ) uasync_poll ( i_ua , 10 ) ;
for ( int i = 0 ; i < 500 & & ! sw_cb2_called & & i_phase = = 0 ; i + + ) uasync_poll ( i_ua , 10 ) ;
if ( sw_cb2_called ) PASS ( ) ; else FAIL ( " cb1=%d cb2=%d " , sw_cb1_called , sw_cb2_called ) ;
if ( sw_cb2_called ) PASS ( ) ; else FAIL ( " cb1=%d cb2=%d " , sw_cb1_called , sw_cb2_called ) ;
_intg_cleanup ( ) ;
_intg_cleanup ( ) ;
@ -1043,8 +1061,8 @@ static void _str_spam_cb(void* arg) {
/* spammer: bump own member, sync to hub */
/* spammer: bump own member, sync to hub */
uint64_t key = ( ( uint64_t ) idx ) < < 60 ;
uint64_t key = ( ( uint64_t ) idx ) < < 60 ;
_data_insert ( & gs - > data [ idx ] , key , ( uint32_t ) ( gs - > data [ idx ] . count + 1 ) ) ;
_data_insert ( & gs - > data [ idx ] , key , ( uint32_t ) ( gs - > data [ idx ] . count + 1 ) ) ;
merkle_sync_recompute_path ( gs - > inst [ idx ] , " stress " , key ) ;
merkle_sync_recompute_path ( gs - > inst [ idx ] , MS_NS_STRESS , key ) ;
merkle_sync_start ( gs - > inst [ idx ] , gs - > inst [ STRESS_HUB ] - > node_id , " stress " , NULL , NULL ) ;
merkle_sync_start ( gs - > inst [ idx ] , gs - > inst [ STRESS_HUB ] - > node_id , MS_NS_STRESS , NULL , NULL ) ;
_str_spam_schedule ( gs , idx ) ;
_str_spam_schedule ( gs , idx ) ;
}
}
@ -1053,7 +1071,7 @@ static void _str_obs_spam_cb(void* arg) {
if ( ! gs | | ! gs - > spam_active ) return ;
if ( ! gs | | ! gs - > spam_active ) return ;
int idx = ( int ) ( intptr_t ) arg ;
int idx = ( int ) ( intptr_t ) arg ;
merkle_sync_start ( gs - > inst [ idx ] , gs - > inst [ STRESS_HUB ] - > node_id , " stress " , NULL , NULL ) ;
merkle_sync_start ( gs - > inst [ idx ] , gs - > inst [ STRESS_HUB ] - > node_id , MS_NS_STRESS , NULL , NULL ) ;
int delay_tb = ( ( rand ( ) % 10 ) + 1 ) * 10 ; /* 1-10ms */
int delay_tb = ( ( rand ( ) % 10 ) + 1 ) * 10 ; /* 1-10ms */
uasync_set_timeout ( gs - > ua , ( uint32_t ) delay_tb , arg , _str_obs_spam_cb , " obs_spam " ) ;
uasync_set_timeout ( gs - > ua , ( uint32_t ) delay_tb , arg , _str_obs_spam_cb , " obs_spam " ) ;
@ -1160,6 +1178,7 @@ static int _str_init(void) {
s - > ctx [ i ] . data = & s - > data [ i ] ;
s - > ctx [ i ] . data = & s - > data [ i ] ;
s - > ctx [ i ] . inst = s - > inst [ i ] ;
s - > ctx [ i ] . inst = s - > inst [ i ] ;
s - > ctx [ i ] . tag = ( char ) ( ' A ' + i ) ;
s - > ctx [ i ] . tag = ( char ) ( ' A ' + i ) ;
_ms_ensure_groups ( s - > inst [ i ] ) ;
if ( merkle_sync_init ( s - > inst [ i ] , MS_SVC_ID , & g_test_ops , & s - > ctx [ i ] ) ! = 0 ) {
if ( merkle_sync_init ( s - > inst [ i ] , MS_SVC_ID , & g_test_ops , & s - > ctx [ i ] ) ! = 0 ) {
printf ( " FAIL: merkle_sync_init %d \n " , i ) ; goto fail ;
printf ( " FAIL: merkle_sync_init %d \n " , i ) ; goto fail ;
}
}
@ -1170,12 +1189,12 @@ static int _str_init(void) {
for ( int i = 1 ; i < STRESS_N ; i + + ) {
for ( int i = 1 ; i < STRESS_N ; i + + ) {
uint64_t key = ( ( uint64_t ) i ) < < 60 ;
uint64_t key = ( ( uint64_t ) i ) < < 60 ;
_data_insert ( & s - > data [ i ] , key , ( uint32_t ) i ) ;
_data_insert ( & s - > data [ i ] , key , ( uint32_t ) i ) ;
merkle_sync_recompute_path ( s - > inst [ i ] , " stress " , key ) ;
merkle_sync_recompute_path ( s - > inst [ i ] , MS_NS_STRESS , key ) ;
}
}
/* initial sync: each spoke → hub */
/* initial sync: each spoke → hub */
s - > sync_pending = STRESS_N - 1 ;
s - > sync_pending = STRESS_N - 1 ;
for ( int i = 1 ; i < STRESS_N ; i + + )
for ( int i = 1 ; i < STRESS_N ; i + + )
merkle_sync_start ( s - > inst [ i ] , s - > inst [ STRESS_HUB ] - > node_id , " stress " , _str_sync_done , s ) ;
merkle_sync_start ( s - > inst [ i ] , s - > inst [ STRESS_HUB ] - > node_id , MS_NS_STRESS , _str_sync_done , s ) ;
_str_wait_for ( " initial sync " , _str_cond_sync_done , STRESS_SYNC_TB ) ;
_str_wait_for ( " initial sync " , _str_cond_sync_done , STRESS_SYNC_TB ) ;
/* observers: sync with hub too */
/* observers: sync with hub too */
@ -1236,7 +1255,7 @@ static void _str_start_final_round(void) {
s - > final_pending = STRESS_N - 1 ;
s - > final_pending = STRESS_N - 1 ;
printf ( " final round %d: syncing %d spokes -> hub \n " , s - > final_round , s - > final_pending ) ;
printf ( " final round %d: syncing %d spokes -> hub \n " , s - > final_round , s - > final_pending ) ;
for ( int i = 1 ; i < STRESS_N ; i + + )
for ( int i = 1 ; i < STRESS_N ; i + + )
merkle_sync_start ( s - > inst [ i ] , s - > inst [ STRESS_HUB ] - > node_id , " stress " , _str_final_done , NULL ) ;
merkle_sync_start ( s - > inst [ i ] , s - > inst [ STRESS_HUB ] - > node_id , MS_NS_STRESS , _str_final_done , NULL ) ;
}
}
static void _str_final_done ( uint64_t peer , const char * ns , int result , void * arg ) {
static void _str_final_done ( uint64_t peer , const char * ns , int result , void * arg ) {
@ -1251,17 +1270,17 @@ static void _str_verify(void) {
int ok = 1 ;
int ok = 1 ;
/* tree sizes match pairwise */
/* tree sizes match pairwise */
int na = _db_count_rows ( s - > inst [ STRESS_HUB ] - > topo_sqlite_db , " stress " ) ;
int na = _db_count_rows ( s - > inst [ STRESS_HUB ] - > topo_sqlite_db , MS_NS_STRESS ) ;
for ( int i = 1 ; i < STRESS_N & & ok ; i + + ) {
for ( int i = 1 ; i < STRESS_N & & ok ; i + + ) {
int nb = _db_count_rows ( s - > inst [ i ] - > topo_sqlite_db , " stress " ) ;
int nb = _db_count_rows ( s - > inst [ i ] - > topo_sqlite_db , MS_NS_STRESS ) ;
if ( na ! = nb ) { printf ( " tree size mismatch: hub=%d inst[%d]=%d \n " , na , i , nb ) ; ok = 0 ; }
if ( na ! = nb ) { printf ( " tree size mismatch: hub=%d inst[%d]=%d \n " , na , i , nb ) ; ok = 0 ; }
}
}
/* root (level=1, prefix=0) hashes identical */
/* root (level=1, prefix=0) hashes identical */
const uint8_t * root0 = merkle_sync_get_hash ( s - > inst [ STRESS_HUB ] , " stress " , 1 , 0 ) ;
const uint8_t * root0 = merkle_sync_get_hash ( s - > inst [ STRESS_HUB ] , MS_NS_STRESS , 1 , 0 ) ;
uint8_t root_copy [ MT_HASH_SIZE ] ; memcpy ( root_copy , root0 , MT_HASH_SIZE ) ;
uint8_t root_copy [ MT_HASH_SIZE ] ; memcpy ( root_copy , root0 , MT_HASH_SIZE ) ;
for ( int i = 1 ; i < STRESS_N & & ok ; i + + ) {
for ( int i = 1 ; i < STRESS_N & & ok ; i + + ) {
const uint8_t * ri = merkle_sync_get_hash ( s - > inst [ i ] , " stress " , 1 , 0 ) ;
const uint8_t * ri = merkle_sync_get_hash ( s - > inst [ i ] , MS_NS_STRESS , 1 , 0 ) ;
if ( memcmp ( root_copy , ri , MT_HASH_SIZE ) ! = 0 ) { printf ( " ROOT HASH MISMATCH inst[%d] \n " , i ) ; ok = 0 ; }
if ( memcmp ( root_copy , ri , MT_HASH_SIZE ) ! = 0 ) { printf ( " ROOT HASH MISMATCH inst[%d] \n " , i ) ; ok = 0 ; }
}
}
@ -1271,16 +1290,16 @@ static void _str_verify(void) {
sqlite3_prepare_v2 ( s - > inst [ i ] - > topo_sqlite_db ,
sqlite3_prepare_v2 ( s - > inst [ i ] - > topo_sqlite_db ,
" SELECT level,prefix64 FROM merkle_tree_hash WHERE namespace=? ORDER BY level,prefix64 " ,
" SELECT level,prefix64 FROM merkle_tree_hash WHERE namespace=? ORDER BY level,prefix64 " ,
- 1 , & st , NULL ) ;
- 1 , & st , NULL ) ;
sqlite3_bind_text ( st , 1 , " stress " , - 1 , SQLITE_STATIC ) ;
sqlite3_bind_text ( st , 1 , MS_NS_STRESS , - 1 , SQLITE_STATIC ) ;
while ( sqlite3_step ( st ) = = SQLITE_ROW & & ok ) {
while ( sqlite3_step ( st ) = = SQLITE_ROW & & ok ) {
uint8_t lv = ( uint8_t ) sqlite3_column_int ( st , 0 ) ;
uint8_t lv = ( uint8_t ) sqlite3_column_int ( st , 0 ) ;
uint64_t pf = ( uint64_t ) sqlite3_column_int64 ( st , 1 ) ;
uint64_t pf = ( uint64_t ) sqlite3_column_int64 ( st , 1 ) ;
EVP_MD_CTX * c = EVP_MD_CTX_new ( ) ;
EVP_MD_CTX * c = EVP_MD_CTX_new ( ) ;
EVP_DigestInit_ex ( c , EVP_sha256 ( ) , NULL ) ;
EVP_DigestInit_ex ( c , EVP_sha256 ( ) , NULL ) ;
_bucket_hash ( & s - > ctx [ i ] , " stress " , lv , pf , c ) ;
_bucket_hash ( & s - > ctx [ i ] , MS_NS_STRESS , lv , pf , c ) ;
uint8_t expected [ MT_HASH_SIZE ] ;
uint8_t expected [ MT_HASH_SIZE ] ;
EVP_DigestFinal_ex ( c , expected , NULL ) ; EVP_MD_CTX_free ( c ) ;
EVP_DigestFinal_ex ( c , expected , NULL ) ; EVP_MD_CTX_free ( c ) ;
const uint8_t * stored = merkle_sync_get_hash ( s - > inst [ i ] , " stress " , lv , pf ) ;
const uint8_t * stored = merkle_sync_get_hash ( s - > inst [ i ] , MS_NS_STRESS , lv , pf ) ;
uint8_t scopy [ MT_HASH_SIZE ] ; memcpy ( scopy , stored , MT_HASH_SIZE ) ;
uint8_t scopy [ MT_HASH_SIZE ] ; memcpy ( scopy , stored , MT_HASH_SIZE ) ;
if ( memcmp ( expected , scopy , MT_HASH_SIZE ) ! = 0 ) {
if ( memcmp ( expected , scopy , MT_HASH_SIZE ) ! = 0 ) {
printf ( " consistency fail inst[%d] L%d P%016llx \n " , i , lv , ( unsigned long long ) pf ) ;
printf ( " consistency fail inst[%d] L%d P%016llx \n " , i , lv , ( unsigned long long ) pf ) ;
@ -1301,12 +1320,12 @@ static void _str_verify(void) {
static void _str_check_roots ( void ) {
static void _str_check_roots ( void ) {
struct str_state * s = gs ;
struct str_state * s = gs ;
if ( ! s ) return ;
if ( ! s ) return ;
const uint8_t * root0 = merkle_sync_get_hash ( s - > inst [ STRESS_HUB ] , " stress " , 1 , 0 ) ;
const uint8_t * root0 = merkle_sync_get_hash ( s - > inst [ STRESS_HUB ] , MS_NS_STRESS , 1 , 0 ) ;
uint8_t root_copy [ MT_HASH_SIZE ] ;
uint8_t root_copy [ MT_HASH_SIZE ] ;
memcpy ( root_copy , root0 , MT_HASH_SIZE ) ;
memcpy ( root_copy , root0 , MT_HASH_SIZE ) ;
int converged = 1 ;
int converged = 1 ;
for ( int i = 1 ; i < STRESS_N ; i + + ) {
for ( int i = 1 ; i < STRESS_N ; i + + ) {
const uint8_t * ri = merkle_sync_get_hash ( s - > inst [ i ] , " stress " , 1 , 0 ) ;
const uint8_t * ri = merkle_sync_get_hash ( s - > inst [ i ] , MS_NS_STRESS , 1 , 0 ) ;
if ( memcmp ( root_copy , ri , MT_HASH_SIZE ) ! = 0 ) { converged = 0 ; break ; }
if ( memcmp ( root_copy , ri , MT_HASH_SIZE ) ! = 0 ) { converged = 0 ; break ; }
}
}
if ( converged ) {
if ( converged ) {