@ -1,12 +1,15 @@
# include "chat_sync.h"
# include "chat_core.h"
# include "gui_bridge.h"
# include "topo_node_sqlite.h"
# include "../../../src/utun_instance.h"
# include "../../../src/etcp_router.h"
# include "../../../src/etcp_api.h"
# include "../../../src/etcp.h"
# include "../../../src/conn_mgr.h"
# include "../../../src/topo_group.h"
# include "../../../src/secure_channel.h"
# include "../../../lib/u_async.h"
# include "../../../lib/ll_queue.h"
# include "../../../lib/debug_config.h"
@ -14,6 +17,7 @@
# include "../../../lib/platform_compat.h"
# include <string.h>
# include <openssl/evp.h>
static struct chat_sync * g_cs = NULL ;
@ -33,6 +37,8 @@ struct chat_sync {
void * refresh_timer ;
void * ttl_timer ;
uint8_t initialized ;
uint64_t pending_invite_ch_id ;
uint64_t pending_invite_node_id ;
} ;
# define CS_ID "chat_sync"
@ -238,6 +244,23 @@ static void cs_handle_sync_done(struct chat_sync* cs, uint64_t peer,
if ( ch ) { ch - > msg_count = pc ; ch - > synced = CS_SYNC_DONE ; }
}
/* ── Forward declarations for new message handlers ── */
static void cs_propagate ( struct chat_sync * cs , const char * ch_id , uint64_t exclude_id ,
const uint8_t * payload , size_t len ) ;
static void cs_handle_channel_info_req ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) ;
static void cs_handle_channel_info_resp ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) ;
static void cs_handle_channel_join ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) ;
static void cs_handle_welcome ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) ;
static void cs_handle_peer_upsert ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) ;
static void cs_handle_peer_remove ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) ;
/* ── Recv dispatcher ── */
static void chat_sync_recv_cb ( struct ETCP_CONN * conn , struct ll_entry * entry ) {
@ -260,12 +283,18 @@ static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
size_t plen = dlen - 3 - ch_len ;
switch ( type ) {
case CS_MSG_INIT_SYNC : cs_handle_init_sync ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_INIT_RESP : cs_handle_init_resp ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_SEND_DATA : cs_handle_send_data ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_PUSH : cs_handle_push ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_ACK_PUSH : cs_handle_ack_push ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_SYNC_DONE : cs_handle_sync_done ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_INIT_SYNC : cs_handle_init_sync ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_INIT_RESP : cs_handle_init_resp ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_SEND_DATA : cs_handle_send_data ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_PUSH : cs_handle_push ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_ACK_PUSH : cs_handle_ack_push ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_SYNC_DONE : cs_handle_sync_done ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_CHANNEL_INFO_REQ : cs_handle_channel_info_req ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_CHANNEL_INFO_RESP : cs_handle_channel_info_resp ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_CHANNEL_JOIN : cs_handle_channel_join ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_WELCOME : cs_handle_welcome ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_PEER_UPSERT : cs_handle_peer_upsert ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_PEER_REMOVE : cs_handle_peer_remove ( g_cs , peer , ch_id , pl , plen ) ; break ;
default : break ;
}
u_free ( entry - > dgram ) ; queue_entry_free ( entry ) ;
@ -278,6 +307,18 @@ static void cs_on_conn_up(struct ETCP_CONN* conn, void* arg) {
if ( ! conn | | ! g_cs ) return ;
uint64_t peer = conn - > peer_node_id ;
if ( peer = = 0 | | peer = = g_cs - > inst - > node_id ) return ;
/* pending invite: send CHANNEL_INFO_REQ */
if ( g_cs - > pending_invite_node_id = = peer & & g_cs - > pending_invite_ch_id ! = 0 ) {
char ch_id_str [ 64 ] ;
snprintf ( ch_id_str , sizeof ( ch_id_str ) , " %llu " , ( unsigned long long ) g_cs - > pending_invite_ch_id ) ;
uint8_t req [ 1 ] = { CS_MSG_CHANNEL_INFO_REQ } ;
cs_send ( g_cs , ch_id_str , peer , req , 1 ) ;
g_cs - > pending_invite_node_id = 0 ;
g_cs - > pending_invite_ch_id = 0 ;
return ;
}
for ( int i = 0 ; i < g_cs - > channel_count ; i + + ) {
struct channel_cache * ch = & g_cs - > channels [ i ] ;
int found = 0 ;
@ -302,7 +343,12 @@ static void cs_on_conn_down(struct ETCP_CONN* conn, void* arg) {
int found = 0 ;
for ( int j = 0 ; j < g_cs - > channels [ i ] . peer_count ; j + + )
if ( g_cs - > channels [ i ] . peer_ids [ j ] = = peer ) { found = 1 ; break ; }
if ( found ) g_cs - > channels [ i ] . synced = CS_SYNC_NONE ;
if ( found ) {
g_cs - > channels [ i ] . synced = CS_SYNC_NONE ;
uint8_t rem [ 9 ] ; rem [ 0 ] = CS_MSG_PEER_REMOVE ;
memcpy ( rem + 1 , & peer , 8 ) ;
cs_propagate ( g_cs , g_cs - > channels [ i ] . channel_id , peer , rem , 9 ) ;
}
}
}
@ -479,9 +525,9 @@ void chat_sync_connect_node(struct UTUN_INSTANCE* inst, uint64_t node_id) {
}
}
void chat_sync_connect_from_invite ( uint64_t node_id , const uint8_t * pubkey_bin ,
const uint8_t * addrs_data , int addr_count ,
const uint8_t * channel_id , int ch_id_len ) {
void chat_sync_connect_from_invite ( uint64_t channel_id , uint64_t node_id ,
const uint8_t * pubkey_bin ,
const uint8_t * addrs_data , int addr_count ) {
if ( ! g_cs | | ! g_cs - > inst | | ! g_cs - > inst - > ua ) {
int r = - 7 ;
uint8_t err [ 12 ] ; memcpy ( err , & node_id , 8 ) ; memcpy ( err + 8 , & r , 4 ) ;
@ -492,6 +538,7 @@ void chat_sync_connect_from_invite(uint64_t node_id, const uint8_t* pubkey_bin,
struct chat_invite * inv = u_calloc ( 1 , sizeof ( struct chat_invite ) ) ;
if ( ! inv ) return ;
inv - > channel_id = channel_id ;
inv - > node_id = node_id ;
memcpy ( inv - > pubkey , pubkey_bin , 32 ) ;
@ -501,11 +548,387 @@ void chat_sync_connect_from_invite(uint64_t node_id, const uint8_t* pubkey_bin,
memcpy ( inv - > addrs_data , addrs_data , addrs_sz ) ;
inv - > addr_count = addr_count ;
inv - > ch_id = u_malloc ( ( size_t ) ch_id_len + 1 ) ;
if ( ! inv - > ch_id ) { u_free ( inv - > addrs_data ) ; u_free ( inv ) ; return ; }
memcpy ( inv - > ch_id , channel_id , ( size_t ) ch_id_len ) ;
inv - > ch_id_len = ch_id_len ;
g_cs - > pending_invite_ch_id = channel_id ;
g_cs - > pending_invite_node_id = node_id ;
gui_bridge_post_uasync_fn (
( void ( * ) ( void * ) ) chat_core_connect_from_invite , inv ) ;
}
/* ─── Ed25519 sign / verify helpers ─── */
static int cs_ed25519_sign ( const uint8_t * privkey , const uint8_t * msg , size_t msg_len ,
uint8_t * sig_out ) {
EVP_PKEY * pkey = EVP_PKEY_new_raw_private_key ( EVP_PKEY_ED25519 , NULL , privkey , SC_PRIVKEY_SIZE ) ;
if ( ! pkey ) { DEBUG_ERROR ( DEBUG_CATEGORY_DEBUG , " %s: EVP_PKEY_new failed " , CS_ID ) ; return - 1 ; }
EVP_MD_CTX * mdctx = EVP_MD_CTX_new ( ) ;
if ( ! mdctx ) { EVP_PKEY_free ( pkey ) ; return - 1 ; }
int ok = ( EVP_DigestSignInit ( mdctx , NULL , NULL , NULL , pkey ) = = 1 )
& & ( EVP_DigestSign ( mdctx , sig_out , & ( size_t ) { 64 } , msg , msg_len ) = = 1 ) ;
EVP_MD_CTX_free ( mdctx ) ;
EVP_PKEY_free ( pkey ) ;
return ok ? 0 : - 1 ;
}
static int cs_ed25519_verify ( const uint8_t * pubkey , const uint8_t * msg , size_t msg_len ,
const uint8_t * sig ) {
EVP_PKEY * pkey = EVP_PKEY_new_raw_public_key ( EVP_PKEY_ED25519 , NULL , pubkey , SC_PUBKEY_SIZE ) ;
if ( ! pkey ) { DEBUG_ERROR ( DEBUG_CATEGORY_DEBUG , " %s: EVP_PKEY_new pub failed " , CS_ID ) ; return - 1 ; }
EVP_MD_CTX * mdctx = EVP_MD_CTX_new ( ) ;
if ( ! mdctx ) { EVP_PKEY_free ( pkey ) ; return - 1 ; }
int rc = EVP_DigestVerifyInit ( mdctx , NULL , NULL , NULL , pkey )
& & EVP_DigestVerify ( mdctx , sig , 64 , msg , msg_len ) = = 1 ;
EVP_MD_CTX_free ( mdctx ) ;
EVP_PKEY_free ( pkey ) ;
return rc ? 0 : - 1 ;
}
/* ─── Propagation helper ─── */
static void cs_propagate ( struct chat_sync * cs , const char * ch_id , uint64_t exclude_id ,
const uint8_t * payload , size_t len ) {
struct channel_cache * ch = cs_find ( cs , ch_id ) ;
if ( ! ch ) return ;
uint64_t myid = cs - > inst - > node_id ;
for ( int i = 0 ; i < ch - > peer_count ; i + + ) {
uint64_t pid = ch - > peer_ids [ i ] ;
if ( pid = = exclude_id | | pid = = myid ) continue ;
if ( ! etcp_router_conn_get ( cs - > inst , pid , ETCP_RT_ID_CHAT_SYNC ) ) continue ;
cs_send ( cs , ch_id , pid , payload , len ) ;
}
}
/* ─── CHANNEL_INFO_REQ (0x09): joiner → inviter ─── */
static void cs_handle_channel_info_req ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
( void ) pl ; ( void ) len ;
char name [ 128 ] ; int is_dm ; uint64_t owner ; uint8_t x25519 [ 32 ] , ed_pub [ 32 ] , ch_sig [ 64 ] ;
if ( topo_node_sqlite_channel_get ( cs - > inst - > topo_groups - > topo_sqlite_db ,
ch_id , name , ( int ) sizeof ( name ) , & is_dm , & owner , x25519 , ed_pub , ch_sig ) ! = 0 ) {
DEBUG_INFO ( DEBUG_CATEGORY_DEBUG , " %s: CHANNEL_INFO_REQ unknown ch=%s " , CS_ID , ch_id ) ;
return ;
}
uint64_t myid = cs - > inst - > node_id ;
uint8_t my_join_sig [ 64 ] = { 0 } ;
uint8_t join_msg [ 256 ] ; size_t mlen = 0 ;
mlen + = snprintf ( ( char * ) join_msg + mlen , sizeof ( join_msg ) - mlen , " %s " , ch_id ) + 1 ;
memcpy ( join_msg + mlen , & myid , 8 ) ; mlen + = 8 ;
memcpy ( join_msg + mlen , cs - > inst - > my_keys . public_key , 32 ) ; mlen + = 32 ;
cs_ed25519_sign ( cs - > inst - > my_ed25519_privkey , join_msg , mlen , my_join_sig ) ;
uint8_t buf [ 1024 ] ; size_t boff = 0 ;
buf [ boff + + ] = CS_MSG_CHANNEL_INFO_RESP ;
uint8_t nl = ( uint8_t ) strlen ( name ) ;
buf [ boff + + ] = nl ; memcpy ( buf + boff , name , nl ) ; boff + = nl ;
memcpy ( buf + boff , & owner , 8 ) ; boff + = 8 ;
buf [ boff + + ] = ( uint8_t ) is_dm ;
memcpy ( buf + boff , x25519 , 32 ) ; boff + = 32 ;
memcpy ( buf + boff , ed_pub , 32 ) ; boff + = 32 ;
memcpy ( buf + boff , ch_sig , 64 ) ; boff + = 64 ;
memcpy ( buf + boff , my_join_sig , 64 ) ; boff + = 64 ;
cs_send ( cs , ch_id , peer , buf , boff ) ;
}
/* ─── CHANNEL_INFO_RESP (0x0A): inviter → joiner ─── */
static void cs_handle_channel_info_resp ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
if ( len < 1 ) return ;
uint8_t nl = pl [ 0 ] ; if ( 1 + nl + 8 + 1 + 32 + 32 + 64 + 64 > len ) return ;
const uint8_t * p = pl + 1 ;
char name [ 128 ] ; memcpy ( name , p , nl ) ; name [ nl ] = ' \0 ' ; p + = nl ;
uint64_t owner ; memcpy ( & owner , p , 8 ) ; p + = 8 ;
uint8_t is_dm = * p + + ;
const uint8_t * x25519 = p ; p + = 32 ;
const uint8_t * ed_pub = p ; p + = 32 ;
const uint8_t * ch_sig = p ; p + = 64 ;
const uint8_t * inviter_join_sig = p ;
/* verify channel signature */
uint8_t vmsg [ 1024 ] ; size_t vlen = 0 ;
vlen + = snprintf ( ( char * ) vmsg + vlen , sizeof ( vmsg ) - vlen , " %s " , ch_id ) + 1 ;
vlen + = snprintf ( ( char * ) vmsg + vlen , sizeof ( vmsg ) - vlen , " %s " , name ) + 1 ;
memcpy ( vmsg + vlen , & owner , 8 ) ; vlen + = 8 ;
memcpy ( vmsg + vlen , x25519 , 32 ) ; vlen + = 32 ;
memcpy ( vmsg + vlen , ed_pub , 32 ) ; vlen + = 32 ;
if ( cs_ed25519_verify ( ed_pub , vmsg , vlen , ch_sig ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_DEBUG , " %s: CHANNEL_INFO_RESP invalid ch_sig ch=%s " , CS_ID , ch_id ) ;
return ;
}
/* save channel to local DB */
topo_node_sqlite_channel_put ( cs - > inst - > topo_groups - > topo_sqlite_db ,
ch_id , name , ( int ) is_dm , owner , x25519 , NULL , ed_pub , NULL , ch_sig ) ;
/* save inviter as node and member */
topo_node_sqlite_member_put ( cs - > inst - > topo_groups - > topo_sqlite_db , ch_id , peer ,
inviter_join_sig , inviter_join_sig ) ;
/* generate our own join_sig */
uint64_t myid = cs - > inst - > node_id ;
uint8_t my_x25519 [ 32 ] ;
memcpy ( my_x25519 , cs - > inst - > my_keys . public_key , 32 ) ;
uint8_t join_sig [ 64 ] ;
{
uint8_t msg [ 256 ] ; size_t mlen = 0 ;
mlen + = snprintf ( ( char * ) msg + mlen , sizeof ( msg ) - mlen , " %s " , ch_id ) + 1 ;
memcpy ( msg + mlen , & myid , 8 ) ; mlen + = 8 ;
memcpy ( msg + mlen , my_x25519 , 32 ) ; mlen + = 32 ;
cs_ed25519_sign ( cs - > inst - > my_ed25519_privkey , msg , mlen , join_sig ) ;
}
/* send JOIN_CHANNEL */
uint8_t jbuf [ 512 ] ; size_t joff = 0 ;
jbuf [ joff + + ] = CS_MSG_CHANNEL_JOIN ;
memcpy ( jbuf + joff , & myid , 8 ) ; joff + = 8 ;
memcpy ( jbuf + joff , my_x25519 , 32 ) ; joff + = 32 ;
memcpy ( jbuf + joff , cs - > inst - > my_ed25519_pubkey , 32 ) ; joff + = 32 ;
memcpy ( jbuf + joff , join_sig , 64 ) ; joff + = 64 ;
/* add local addresses */
uint8_t addr_cnt = 0 ; size_t ac_pos = joff ;
jbuf [ joff + + ] = 0 ;
struct ETCP_SOCKET * sock = cs - > inst - > etcp_sockets ;
while ( sock & & addr_cnt < 255 ) {
struct sockaddr_storage * sa ;
if ( sock - > nat_addr . ss_family ) sa = & sock - > nat_addr ;
else if ( sock - > interface_addr . ss_family ) sa = & sock - > interface_addr ;
else { sock = sock - > next ; continue ; }
if ( sa - > ss_family = = AF_INET ) {
struct sockaddr_in * sin = ( struct sockaddr_in * ) sa ;
if ( joff + 7 > sizeof ( jbuf ) ) break ;
jbuf [ joff + + ] = 4 ;
memcpy ( jbuf + joff , & sin - > sin_addr , 4 ) ; joff + = 4 ;
uint16_t p = ntohs ( sin - > sin_port ) ;
jbuf [ joff + + ] = ( uint8_t ) ( ( p > > 8 ) & 0xFF ) ;
jbuf [ joff + + ] = ( uint8_t ) ( p & 0xFF ) ;
addr_cnt + + ;
} else if ( sa - > ss_family = = AF_INET6 ) {
struct sockaddr_in6 * sin6 = ( struct sockaddr_in6 * ) sa ;
if ( joff + 19 > sizeof ( jbuf ) ) break ;
jbuf [ joff + + ] = 6 ;
memcpy ( jbuf + joff , & sin6 - > sin6_addr , 16 ) ; joff + = 16 ;
uint16_t p = ntohs ( sin6 - > sin6_port ) ;
jbuf [ joff + + ] = ( uint8_t ) ( ( p > > 8 ) & 0xFF ) ;
jbuf [ joff + + ] = ( uint8_t ) ( p & 0xFF ) ;
addr_cnt + + ;
}
sock = sock - > next ;
}
jbuf [ ac_pos ] = addr_cnt ;
cs_send ( cs , ch_id , peer , jbuf , joff ) ;
}
/* ─── CHANNEL_JOIN (0x0B): joiner → inviter ─── */
static void cs_handle_channel_join ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
if ( len < 8 + 32 + 32 + 64 + 1 ) return ;
const uint8_t * p = pl ;
uint64_t node_id ; memcpy ( & node_id , p , 8 ) ; p + = 8 ;
const uint8_t * x25519 = p ; p + = 32 ;
const uint8_t * ed_pub = p ; p + = 32 ;
const uint8_t * join_sig = p ; p + = 64 ;
uint8_t addr_cnt = * p + + ;
/* verify join_sig */
uint8_t vmsg [ 256 ] ; size_t vlen = 0 ;
vlen + = snprintf ( ( char * ) vmsg + vlen , sizeof ( vmsg ) - vlen , " %s " , ch_id ) + 1 ;
memcpy ( vmsg + vlen , & node_id , 8 ) ; vlen + = 8 ;
memcpy ( vmsg + vlen , x25519 , 32 ) ; vlen + = 32 ;
if ( cs_ed25519_verify ( ed_pub , vmsg , vlen , join_sig ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_DEBUG , " %s: JOIN invalid sig node=0x%016llx ch=%s " , CS_ID ,
( unsigned long long ) node_id , ch_id ) ;
return ;
}
sqlite3 * db = cs - > inst - > topo_groups - > topo_sqlite_db ;
topo_node_sqlite_member_put ( db , ch_id , node_id , join_sig , NULL ) ;
/* save/update node addresses */
for ( uint8_t i = 0 ; i < addr_cnt & & p + 1 < = pl + len ; i + + ) {
uint8_t fm = * p + + ;
int ip_len = ( fm = = 4 ) ? 4 : 16 ;
if ( p + ip_len + 2 > pl + len ) break ;
const uint8_t * ip = p ; p + = ip_len ;
uint16_t port = ( ( uint16_t ) p [ 0 ] < < 8 ) | p [ 1 ] ; p + = 2 ;
char sql [ 256 ] ; snprintf ( sql , sizeof ( sql ) ,
" INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,is_nat) "
" VALUES(?,?,?,?,0) " ) ;
sqlite3_stmt * stmt = NULL ;
if ( sqlite3_prepare_v2 ( db , sql , - 1 , & stmt , NULL ) = = SQLITE_OK ) {
sqlite3_bind_int64 ( stmt , 1 , ( sqlite3_int64 ) node_id ) ;
sqlite3_bind_int ( stmt , 2 , fm ) ;
sqlite3_bind_blob ( stmt , 3 , ip , ip_len , SQLITE_STATIC ) ;
sqlite3_bind_int ( stmt , 4 , ( int ) port ) ;
sqlite3_step ( stmt ) ; sqlite3_finalize ( stmt ) ;
}
}
/* build WELCOME with all current peers */
uint8_t wbuf [ 8192 ] ;
{
uint8_t peers_data [ 8192 ] ; size_t peers_len = 0 ;
if ( topo_node_sqlite_channel_peers_all ( db , ch_id ,
peers_data , sizeof ( peers_data ) , & peers_len ) = = 0 ) {
size_t woff = 0 ;
wbuf [ woff + + ] = CS_MSG_WELCOME ;
if ( woff + peers_len < = sizeof ( wbuf ) )
{ memcpy ( wbuf + woff , peers_data , peers_len ) ; woff + = peers_len ; }
cs_send ( cs , ch_id , peer , wbuf , woff ) ;
}
}
/* propagate PEER_UPSERT to other channel members */
{
uint8_t ubuf [ 512 ] ; size_t uoff = 0 ;
ubuf [ uoff + + ] = CS_MSG_PEER_UPSERT ;
memcpy ( ubuf + uoff , & node_id , 8 ) ; uoff + = 8 ;
memcpy ( ubuf + uoff , x25519 , 32 ) ; uoff + = 32 ;
memcpy ( ubuf + uoff , ed_pub , 32 ) ; uoff + = 32 ;
memcpy ( ubuf + uoff , join_sig , 64 ) ; uoff + = 64 ;
ubuf [ uoff + + ] = addr_cnt ;
size_t addr_data_sz = ( size_t ) ( p - ( pl + 8 + 32 + 32 + 64 + 1 ) ) ;
if ( uoff + addr_data_sz < = sizeof ( ubuf ) ) {
memcpy ( ubuf + uoff , pl + 8 + 32 + 32 + 64 + 1 , addr_data_sz ) ;
uoff + = addr_data_sz ;
}
cs_propagate ( cs , ch_id , peer , ubuf , uoff ) ;
}
/* add to channel cache */
struct channel_cache * ch = cs_find ( cs , ch_id ) ;
if ( ! ch ) {
cs_refresh_channels ( cs ) ;
ch = cs_find ( cs , ch_id ) ;
}
DEBUG_INFO ( DEBUG_CATEGORY_DEBUG , " %s: JOIN accepted node=0x%016llx ch=%s addrs=%d " ,
CS_ID , ( unsigned long long ) node_id , ch_id , addr_cnt ) ;
}
/* ─── WELCOME (0x0C): inviter → joiner with full peer list ─── */
static void cs_handle_welcome ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
( void ) peer ;
if ( len < 2 ) return ;
sqlite3 * db = cs - > inst - > topo_groups - > topo_sqlite_db ;
const uint8_t * p = pl ;
uint16_t pc ; memcpy ( & pc , p , 2 ) ; p + = 2 ;
for ( uint16_t i = 0 ; i < pc ; i + + ) {
if ( ( size_t ) ( p - pl ) + 8 + 32 + 32 + 64 + 1 > len ) break ;
uint64_t node_id ; memcpy ( & node_id , p , 8 ) ; p + = 8 ;
const uint8_t * x25519 = p ; p + = 32 ;
const uint8_t * ed_pub = p ; p + = 32 ;
const uint8_t * join_sig = p ; p + = 64 ;
uint8_t ac = * p + + ;
topo_node_sqlite_member_put ( db , ch_id , node_id , join_sig , NULL ) ;
for ( uint8_t j = 0 ; j < ac ; j + + ) {
if ( p + 1 > pl + len ) break ;
uint8_t fm = * p + + ;
int ip_len = ( fm = = 4 ) ? 4 : 16 ;
if ( p + ip_len + 2 > pl + len ) break ;
const uint8_t * ip = p ; p + = ip_len ;
uint16_t port = ( ( uint16_t ) p [ 0 ] < < 8 ) | p [ 1 ] ; p + = 2 ;
char sql [ 256 ] ; snprintf ( sql , sizeof ( sql ) ,
" INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,is_nat) "
" VALUES(?,?,?,?,0) " ) ;
sqlite3_stmt * stmt = NULL ;
if ( sqlite3_prepare_v2 ( db , sql , - 1 , & stmt , NULL ) = = SQLITE_OK ) {
sqlite3_bind_int64 ( stmt , 1 , ( sqlite3_int64 ) node_id ) ;
sqlite3_bind_int ( stmt , 2 , fm ) ;
sqlite3_bind_blob ( stmt , 3 , ip , ip_len , SQLITE_STATIC ) ;
sqlite3_bind_int ( stmt , 4 , ( int ) port ) ;
sqlite3_step ( stmt ) ; sqlite3_finalize ( stmt ) ;
}
}
}
cs_refresh_channels ( cs ) ;
uint8_t evt [ 65 ] ; uint8_t ch_id_len = ( uint8_t ) strlen ( ch_id ) ;
evt [ 0 ] = ch_id_len ; memcpy ( evt + 1 , ch_id , ch_id_len ) ;
gui_bridge_post ( GUI_EVT_CHANNEL_UPDATED , evt , 1 + ch_id_len ) ;
DEBUG_INFO ( DEBUG_CATEGORY_DEBUG , " %s: WELCOME processed ch=%s peers=%d " ,
CS_ID , ch_id , pc ) ;
}
/* ─── PEER_UPSERT (0x0D): propagate new peer ─── */
static void cs_handle_peer_upsert ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
if ( len < 8 + 32 + 32 + 64 + 1 ) return ;
const uint8_t * p = pl ;
uint64_t node_id ; memcpy ( & node_id , p , 8 ) ; p + = 8 ;
const uint8_t * x25519 = p ; p + = 32 ;
const uint8_t * ed_pub = p ; p + = 32 ;
const uint8_t * join_sig = p ; p + = 64 ;
uint8_t ac = * p + + ;
/* verify join_sig */
uint8_t vmsg [ 256 ] ; size_t vlen = 0 ;
vlen + = snprintf ( ( char * ) vmsg + vlen , sizeof ( vmsg ) - vlen , " %s " , ch_id ) + 1 ;
memcpy ( vmsg + vlen , & node_id , 8 ) ; vlen + = 8 ;
memcpy ( vmsg + vlen , x25519 , 32 ) ; vlen + = 32 ;
if ( cs_ed25519_verify ( ed_pub , vmsg , vlen , join_sig ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_DEBUG , " %s: PEER_UPSERT invalid sig node=0x%016llx " , CS_ID ,
( unsigned long long ) node_id ) ;
return ;
}
sqlite3 * db = cs - > inst - > topo_groups - > topo_sqlite_db ;
topo_node_sqlite_member_put ( db , ch_id , node_id , join_sig , NULL ) ;
for ( uint8_t i = 0 ; i < ac ; i + + ) {
if ( p + 1 > pl + len ) break ;
uint8_t fm = * p + + ;
int ip_len = ( fm = = 4 ) ? 4 : 16 ;
if ( p + ip_len + 2 > pl + len ) break ;
const uint8_t * ip = p ; p + = ip_len ;
uint16_t port = ( ( uint16_t ) p [ 0 ] < < 8 ) | p [ 1 ] ; p + = 2 ;
char sql [ 256 ] ; snprintf ( sql , sizeof ( sql ) ,
" INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,is_nat) "
" VALUES(?,?,?,?,0) " ) ;
sqlite3_stmt * stmt = NULL ;
if ( sqlite3_prepare_v2 ( db , sql , - 1 , & stmt , NULL ) = = SQLITE_OK ) {
sqlite3_bind_int64 ( stmt , 1 , ( sqlite3_int64 ) node_id ) ;
sqlite3_bind_int ( stmt , 2 , fm ) ;
sqlite3_bind_blob ( stmt , 3 , ip , ip_len , SQLITE_STATIC ) ;
sqlite3_bind_int ( stmt , 4 , ( int ) port ) ;
sqlite3_step ( stmt ) ; sqlite3_finalize ( stmt ) ;
}
}
cs_refresh_channels ( cs ) ;
/* propagate to others (except sender and the subject node) */
cs_propagate ( cs , ch_id , peer , pl , len ) ;
DEBUG_INFO ( DEBUG_CATEGORY_DEBUG , " %s: PEER_UPSERT node=0x%016llx ch=%s " ,
CS_ID , ( unsigned long long ) node_id , ch_id ) ;
}
/* ─── PEER_REMOVE (0x0E) ─── */
static void cs_handle_peer_remove ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
if ( len < 8 ) return ;
uint64_t node_id ; memcpy ( & node_id , pl , 8 ) ;
sqlite3 * db = cs - > inst - > topo_groups - > topo_sqlite_db ;
topo_node_sqlite_member_del ( db , ch_id , node_id ) ;
cs_refresh_channels ( cs ) ;
cs_propagate ( cs , ch_id , peer , pl , len ) ;
DEBUG_INFO ( DEBUG_CATEGORY_DEBUG , " %s: PEER_REMOVE node=0x%016llx ch=%s " ,
CS_ID , ( unsigned long long ) node_id , ch_id ) ;
}