@ -5,6 +5,7 @@
# include "chat_setting.h"
# include "chat_event.h"
# include "chat_admin.h"
# include "chat_join.h"
# include "../routing_layer/topo_node_sqlite.h"
# include "../routing_layer/topo_group.h"
# include "../routing_layer/topo_group_invite.h"
@ -45,6 +46,13 @@ struct channel_cache {
uint8_t synced ;
} ;
struct join_pending {
uint64_t node_id ; /* joiner node_id */
char ch_id [ 64 ] ;
struct ETCP_CONN * conn ; /* прямое соединение к джойнеру */
struct join_pending * next ;
} ;
struct chat_sync {
struct UTUN_INSTANCE * inst ;
struct channel_cache * channels ;
@ -59,8 +67,10 @@ struct chat_sync {
uint8_t sync_scheduled ;
uint64_t pending_invite_ch_id ;
uint64_t pending_invite_node_id ;
uint8_t pending_invite_is_inviter ; /* 1 = inviter sends CHANNEL_INVITE on conn_up, 0 = joiner sends CHANNEL_INFO_REQ */
char pending_password [ 128 ] ; /* пароль для CHANNEL_INFO_REQ */
uint8_t pending_invite_is_inviter ; /* 1 = inviter sends CHANNEL_INVITE on conn_up, 0 = joiner sends JOIN_INFO_REQ */
char pending_password [ 128 ] ; /* пароль для JOIN_INFO_REQ */
uint64_t pending_join_key ; /* join-ключ из invite-ссылки (джойнер) */
struct join_pending * join_pending ; /* connection-узел: ожидающие ready джойнеры */
/* Pending incoming invites (для ask/deny) */
uint64_t pending_inv_in_ch_id [ 4 ] ;
uint64_t pending_inv_in_node_id [ 4 ] ;
@ -81,13 +91,12 @@ static const char* cs_msg_name(uint8_t type) {
case CS_MSG_PUSH : return " PUSH " ;
case CS_MSG_ACK_PUSH : return " ACK_PUSH " ;
case CS_MSG_SYNC_DONE : return " SYNC_DONE " ;
case CS_MSG_CHANNEL_INFO_REQ : return " CHANNEL_INFO_REQ " ;
case CS_MSG_CHANNEL_INFO_RESP : return " CHANNEL_INFO_RESP " ;
case CS_MSG_CHANNEL_JOIN : return " CHANNEL_JOIN " ;
case CS_MSG_WELCOME : return " WELCOME " ;
case CS_MSG_PEER_UPSERT : return " PEER_UPSERT " ;
case CS_MSG_ERROR : return " ERROR " ;
case CS_MSG_CHANNEL_INVITE : return " CHANNEL_INVITE " ;
case CS_MSG_JOIN_INFO_REQ : return " JOIN_INFO_REQ " ;
case CS_MSG_JOIN_INFO_RESP : return " JOIN_INFO_RESP " ;
case CS_MSG_JOIN_REQUEST : return " JOIN_REQUEST " ;
case CS_MSG_JOIN_READY : return " JOIN_READY " ;
default : return " ??? " ;
}
}
@ -156,15 +165,13 @@ static struct channel_cache* cs_find(struct chat_sync* cs, const char* ch_id) {
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 ,
static void cs_handle_join _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 ,
static void cs_handle_join _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 ,
static void cs_handle_join_request ( 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 ,
static void cs_handle_join_ready ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) ;
static void cs_handle_channel_invite ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) ;
@ -173,6 +180,10 @@ static int cs_send_channel_invite(struct chat_sync* cs, struct UTUN_INSTANCE* in
const char * ch_name , uint64_t owner ,
const uint8_t * ch_x25519 , const uint8_t * ch_ed ,
const uint8_t * ch_sig ) ;
static void cs_on_member_applied ( const char * ch_id , uint64_t node_id ,
const uint8_t * x25519 , uint64_t signed_by ,
const uint8_t * signature , void * arg ) ;
static void cs_pending_free_all ( struct chat_sync * cs ) ;
/* ── Recv dispatcher ── */
static void chat_sync_recv_cb ( struct ETCP_CONN * conn , struct ll_entry * entry ) {
@ -193,9 +204,9 @@ static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
uint8_t type = d [ 9 ] ;
char ch_id [ 64 ] ;
if ( cs_group_id_to_ch_id ( g_cs - > inst , group_id , ch_id , sizeof ( ch_id ) ) ! = 0 ) {
/* CHANNEL_INVITE/INFO_REQ приходят для ещё не созданного у нас канала:
/* CHANNEL_INVITE/JOIN_ INFO_REQ приходят для ещё не созданного у нас канала:
group_id = = channel_id ( numeric ) , п о э т о м у в ы в о д и м ch_id н а п р я м у ю . */
if ( type = = CS_MSG_CHANNEL_INVITE | | type = = CS_MSG_CHANNEL_INFO_REQ ) {
if ( type = = CS_MSG_CHANNEL_INVITE | | type = = CS_MSG_JOIN_INFO_REQ | | type = = CS_MSG_JOIN_REQUEST ) {
snprintf ( ch_id , sizeof ( ch_id ) , " %llu " , ( unsigned long long ) group_id ) ;
} else {
u_free ( entry - > dgram ) ; queue_entry_free ( entry ) ; return ;
@ -210,11 +221,10 @@ static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
CS_ID , cs_msg_name ( type ) , ( unsigned long long ) peer , ch_id , plen ) ;
switch ( type ) {
case CS_MSG_CHANNEL_INFO_REQ : DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → handle INFO_REQ " , CS_ID ) ; cs_handle_channel_info_req ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_CHANNEL_INFO_RESP : DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → handle INFO_RESP " , CS_ID ) ; cs_handle_channel_info_resp ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_CHANNEL_JOIN : DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → handle JOIN " , CS_ID ) ; cs_handle_channel_join ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_WELCOME : DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → handle WELCOME " , CS_ID ) ; cs_handle_welcome ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_PEER_UPSERT : DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → handle PEER_UPSERT " , CS_ID ) ; cs_handle_peer_upsert ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_JOIN_INFO_REQ : DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → handle JOIN_INFO_REQ " , CS_ID ) ; cs_handle_join_info_req ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_JOIN_INFO_RESP : DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → handle JOIN_INFO_RESP " , CS_ID ) ; cs_handle_join_info_resp ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_JOIN_REQUEST : DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → handle JOIN_REQUEST " , CS_ID ) ; cs_handle_join_request ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_JOIN_READY : DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → handle JOIN_READY " , CS_ID ) ; cs_handle_join_ready ( g_cs , peer , ch_id , pl , plen ) ; break ;
case CS_MSG_ERROR : {
DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: RECV ERROR from=%016llx ch=%s " , CS_ID , ( unsigned long long ) peer , ch_id ) ;
if ( g_cs - > info_req_timer ) { uasync_cancel_timeout ( g_cs - > inst - > ua , g_cs - > info_req_timer ) ; g_cs - > info_req_timer = NULL ; }
@ -448,22 +458,25 @@ static void _on_invite_sync_done(uint64_t peer, const char* ns, int result, void
u_free ( sa ) ;
}
/* ── send CHANNEL _INFO_REQ to start join handshake ── */
/* ── send JOIN _INFO_REQ to start join handshake ── */
static void cs_start_channel_join ( struct chat_sync * cs , uint64_t peer ) {
if ( ! cs | | cs - > info_req_timer ) return ;
char ch_id_str [ 64 ] ;
snprintf ( ch_id_str , sizeof ( ch_id_str ) , " %llu " , ( unsigned long long ) cs - > pending_invite_ch_id ) ;
/* payload: [join_key:8][pass_len:1][password] */
size_t plen = strlen ( cs - > pending_password ) ;
uint8_t req [ 130 ] ;
req [ 0 ] = CS_MSG_CHANNEL_INFO_REQ ;
req [ 1 ] = ( uint8_t ) ( plen > 127 ? 127 : plen ) ;
if ( plen > 0 ) memcpy ( req + 2 , cs - > pending_password , plen > 127 ? 127 : plen ) ;
cs_send ( cs , ch_id_str , peer , req , 2 + ( plen > 127 ? 127 : ( int ) plen ) ) ;
uint8_t req [ 140 ] ;
req [ 0 ] = CS_MSG_JOIN_INFO_REQ ;
memcpy ( req + 1 , & cs - > pending_join_key , 8 ) ;
req [ 9 ] = ( uint8_t ) ( plen > 127 ? 127 : plen ) ;
if ( plen > 0 ) memcpy ( req + 10 , cs - > pending_password , plen > 127 ? 127 : plen ) ;
cs_send ( cs , ch_id_str , peer , req , 10 + ( plen > 127 ? 127 : ( int ) plen ) ) ;
cs - > info_req_timer = uasync_set_timeout ( cs - > inst - > ua ,
CS_INFO_REQ_TIMEOUT_MS * 10 , cs , cs_info_req_timeout_cb , " cs_info_req " ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: sent CHANNEL_INFO_REQ ch=%llu peer=0x%016llx pass_len=%zu " ,
CS_ID , ( unsigned long long ) cs - > pending_invite_ch_id , ( unsigned long long ) peer , plen ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: sent JOIN_INFO_REQ ch=%llu peer=0x%016llx key=%016llx pass_len=%zu " ,
CS_ID , ( unsigned long long ) cs - > pending_invite_ch_id , ( unsigned long long ) peer ,
( unsigned long long ) cs - > pending_join_key , plen ) ;
}
/* ── Connection callbacks ── */
@ -633,7 +646,9 @@ int chat_sync_init(struct UTUN_INSTANCE* inst) {
cs - > join_timer = NULL ;
member_sync_init ( inst ) ;
member_sync_add_apply_cbk ( cs_on_member_applied , cs ) ;
chat_admin_init ( inst ) ;
chat_join_init ( inst ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: initialized " , CS_ID ) ;
return 0 ;
@ -646,8 +661,11 @@ void chat_sync_destroy(struct UTUN_INSTANCE* inst) {
if ( cs - > sync_timer ) { uasync_cancel_timeout ( inst - > ua , cs - > sync_timer ) ; cs - > sync_timer = NULL ; }
if ( cs - > invite_send_timer ) { uasync_cancel_timeout ( inst - > ua , cs - > invite_send_timer ) ; cs - > invite_send_timer = NULL ; }
if ( cs - > sync_scheduled ) { cs - > sync_scheduled = 0 ; cs_flush_sync ( cs ) ; }
member_sync_remove_apply_cbk ( cs_on_member_applied , cs ) ;
cs_pending_free_all ( cs ) ;
member_sync_destroy ( inst ) ;
chat_admin_destroy ( inst ) ;
chat_join_destroy ( inst ) ;
cs - > initialized = 0 ; g_cs = NULL ;
etcp_unbind ( inst , ETCP_RT_ID_CHAT_SYNC ) ;
@ -704,20 +722,6 @@ struct chat_invite {
int addr_count ;
} ;
/* коллбэк invite-подключения (topo_group_invite): nq->handle уже установлен модулем,
з д е с ь т о л ь к о л о г р е з у л ь т а т а . Р е а л ь н ы й join - п р о т о к о л з а п у с к а е т с я ч е р е з cs_on_conn_up
( г л о б а л ь н ы й callback ) , с о х р а н е н и е в Б Д — ч е р е з cs_handle_channel_info_resp . */
static void cs_tgi_cb ( uint64_t node_id , uint64_t group_id , int event , void * arg ) {
( void ) arg ; ( void ) group_id ;
if ( event = = TGI_EVENT_JOIN ) {
DEBUG_INFO ( DEBUG_CATEGORY_GENERAL , " channel invite: JOINED — connected to 0x%016llx, online now " ,
( unsigned long long ) node_id ) ;
} else {
DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " channel invite: TIMEOUT — could not connect to 0x%016llx " ,
( unsigned long long ) node_id ) ;
}
}
struct cm_invite_wrap { struct chat_invite inv ; } ;
static void cm_invite_trampoline ( void * arg ) {
@ -777,13 +781,13 @@ static void cm_invite_trampoline(void* arg) {
u_free ( ni ) ; u_free ( w ) ; return ;
}
/* topo_group_invite_join сам создаст TOPO_GROUP, найдёт/создаст соединение (ncd),
з а п у с т и т INVITE_INFO . DB channel + sync с о з д а ю т с я п о з ж е в cs_handle_channel_info_resp п о с л е в е р и ф и к а ц и и */
/* topo_group_invite_to_channel: прямое ncd-подключение к connection-узлу БЕЗ INVITE_INFO.
Р е а л ь н ы й join - п р о т о к о л ( JOIN_INFO_REQ → … → JOIN_READY ) з а п у с к а е т с я п о cs_on_conn_up . */
DEBUG_INFO ( DEBUG_CATEGORY_GENERAL , " channel invite: connecting to 0x%016llx via %d addresses " ,
( unsigned long long ) node_id , inv - > addr_count ) ;
int r = topo_group_invite_join ( inst , channel_id , ni , node_id , cs_tgi_cb , NULL ) ;
int r = topo_group_invite_to_channel ( inst , channel_id , ni , node_id ) ;
if ( r < 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_GENERAL , " channel invite: FAILED — topo_group_invite_join error=%d " , r ) ;
DEBUG_ERROR ( DEBUG_CATEGORY_GENERAL , " channel invite: FAILED — topo_group_invite_to_channel error=%d " , r ) ;
}
/* free caller-owned ni */
@ -797,7 +801,7 @@ 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 ,
int addrs_data_len ,
const char * password ) {
const char * password , uint64_t join_key ) {
if ( ! g_cs | | ! g_cs - > inst | | ! g_cs - > inst - > ua ) {
int r = - 7 ;
# ifdef __ANDROID__
@ -826,6 +830,7 @@ void chat_sync_connect_from_invite(uint64_t channel_id, uint64_t node_id,
g_cs - > pending_invite_ch_id = channel_id ;
g_cs - > pending_invite_node_id = node_id ;
g_cs - > pending_join_key = join_key ;
if ( password & & password [ 0 ] ) {
size_t pl = strlen ( password ) ;
if ( pl > = sizeof ( g_cs - > pending_password ) ) pl = sizeof ( g_cs - > pending_password ) - 1 ;
@ -933,479 +938,361 @@ static int _get_node_name(sqlite3* db, uint64_t node_id, char* out, size_t sz) {
return rc ;
}
/* ─── 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 ) {
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: INFO_REQ ch=%s from=%016llx len=%zu " , CS_ID , ch_id , ( unsigned long long ) peer , len ) ;
/* ─── JOIN_INFO_REQ (0x10): joiner → connection-узел ─── */
/* joiner responded to our CHANNEL_INVITE — cancel invite send timer */
if ( cs - > invite_send_timer ) { uasync_cancel_timeout ( cs - > inst - > ua , cs - > invite_send_timer ) ; cs - > invite_send_timer = NULL ; }
static void cs_handle_join_info_req ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
/* payload: [join_key:8][pass_len:1][password] */
if ( len < 9 ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_INFO_REQ too short len=%zu peer=%016llx " , CS_ID , len , ( unsigned long long ) peer ) ; return ; }
uint64_t join_key ; memcpy ( & join_key , pl , 8 ) ;
uint64_t channel_id = strtoull ( ch_id , NULL , 10 ) ;
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_INFO_REQ ch=%s from=%016llx key=%016llx len=%zu " ,
CS_ID , ch_id , ( unsigned long long ) peer , ( unsigned long long ) join_key , len ) ;
/* 1. валидация join-ключа (зарегистрирован инвайтером на connection-узле, TTL 10 мин) */
uint64_t inviter = chat_join_lookup_inviter ( cs - > inst , channel_id , join_key ) ;
if ( inviter = = 0 ) {
DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_INFO_REQ invalid/expired join key ch=%s from=%016llx key=%016llx " ,
CS_ID , ch_id , ( unsigned long long ) peer , ( unsigned long long ) join_key ) ;
uint8_t err [ 1 ] = { CS_MSG_ERROR } ; cs_send ( cs , ch_id , peer , err , 1 ) ;
return ;
}
/* Parse password from request (v2 format: type + pass_len + optional_password) */
/* 2. пароль (если настроен на сервере ) */
char req_password [ 128 ] = " " ;
if ( len > = 1 ) {
uint8_t plen2 = pl [ 0 ] ;
if ( plen2 > 0 & & plen2 < = len - 1 ) {
if ( len > = 10 ) {
uint8_t plen2 = pl [ 9 ] ;
if ( plen2 > 0 & & plen2 < = len - 10 ) {
if ( plen2 > 127 ) plen2 = 127 ;
memcpy ( req_password , pl + 1 , plen2 ) ; req_password [ plen2 ] = ' \0 ' ;
memcpy ( req_password , pl + 10 , plen2 ) ; req_password [ plen2 ] = ' \0 ' ;
}
}
/* Check password if server has one configured */
{
struct global_config * gcfg = & cs - > inst - > config - > global ;
if ( gcfg - > chatserver_enabled & & gcfg - > chatserver_join_password [ 0 ] ) {
if ( strcmp ( req_password , gcfg - > chatserver_join_password ) ! = 0 ) {
DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL_INFO_REQ wrong password ch=%s from=%016llx (got '%s', expected '%s') " ,
CS_ID , ch_id , ( unsigned long long ) peer , req_password , gcfg - > chatserver_join_password ) ;
uint8_t err [ 2 ] = { CS_MSG_ERROR , 0x01 } ; /* 0x01 = wrong password */
DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_INFO_REQ wrong password ch=%s from=%016llx " , CS_ID , ch_id , ( unsigned long long ) peer ) ;
uint8_t err [ 2 ] = { CS_MSG_ERROR , 0x01 } ;
cs_send ( cs , ch_id , peer , err , 2 ) ;
return ;
}
}
}
char name [ 128 ] ; uint64_t owner ; uint8_t x25519 [ 32 ] , ed_pub [ 32 ] , ch_sig [ 64 ] ;
/* 3. инфо канала */
char name [ 128 ] ; uint64_t owner ; uint8_t ch_x25519 [ 32 ] , ch_ed [ 32 ] , ch_sig [ 64 ] ;
if ( topo_node_sqlite_channel_get ( cs - > inst - > topo_sqlite_db ,
ch_id , name , ( int ) sizeof ( name ) , & owner , x25519 , ed_pub , ch_sig ) ! = 0 ) {
DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL _INFO_REQ unknown ch=%s from=%016llx " , CS_ID , ch_id , ( unsigned long long ) peer ) ;
ch_id , name , ( int ) sizeof ( name ) , & owner , ch_ x25519, ch_ ed, ch_sig ) ! = 0 ) {
DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN _INFO_REQ unknown ch=%s from=%016llx " , CS_ID , ch_id , ( unsigned long long ) peer ) ;
uint8_t err [ 1 ] = { CS_MSG_ERROR } ; cs_send ( cs , ch_id , peer , err , 1 ) ;
return ;
}
/* 4. мой мембер-рекорд (join_sig/update_sig + дерево приглашений) */
uint64_t myid = cs - > inst - > node_id ;
if ( memcmp ( cs - > inst - > my_keys . public_key , x25519 , 32 ) ! = 0 )
DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL_INFO_RESP pubkey MISMATCH: my=%016llx ch=%016llx — channel was created with DIFFERENT keys! " ,
CS_ID , * ( const uint64_t * ) cs - > inst - > my_keys . public_key , * ( const uint64_t * ) x25519 ) ;
/* load or create join_sig */
uint8_t my_join_sig [ 64 ] = { 0 } ; uint64_t my_join_ts = 0 ;
{
sqlite3 * vdb = cs - > inst - > topo_sqlite_db ;
if ( topo_node_sqlite_member_get_join ( vdb , ch_id , myid , my_join_sig , & my_join_ts ) ! = 0 ) {
uint8_t join_msg [ 128 ] ;
my_join_ts = ( uint64_t ) ntp_time_get_seconds ( cs - > inst ) ;
int jmlen = member_sync_build_join_msg ( x25519 , ed_pub , myid ,
cs - > inst - > my_keys . public_key , my_join_ts ,
int jmlen = member_sync_build_join_msg ( ch_x25519 , ch_ed , myid , cs - > inst - > my_keys . public_key , my_join_ts ,
join_msg , ( int ) sizeof ( join_msg ) ) ;
if ( jmlen > = 0 ) cs_ed25519_sign ( cs - > inst - > my_ed25519_privkey , join_msg , ( size_t ) jmlen , my_join_sig ) ;
DEBUG_DEBUG ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL_INFO_RESP created NEW join_sig: myid=%016llx ts=%llu " ,
CS_ID , ( unsigned long long ) myid , ( unsigned long long ) my_join_ts ) ;
}
}
/* generate update_sig */
uint8_t my_update_sig [ 64 ] = { 0 } ; uint64_t my_update_ts ;
{
char juser [ 256 ] ; snprintf ( juser , sizeof ( juser ) , " { \" name \" : \" %s \" } " ,
cs - > inst - > name [ 0 ] ? cs - > inst - > name : " " ) ;
char juser [ 256 ] ; snprintf ( juser , sizeof ( juser ) , " { \" name \" : \" %s \" } " , cs - > inst - > name [ 0 ] ? cs - > inst - > name : " " ) ;
my_update_ts = ( uint64_t ) ntp_time_get_seconds ( cs - > inst ) ;
uint8_t umsg [ 256 ] ;
int ulen = member_sync_build_update_msg ( my_join_sig , my_update_ts , juser ,
umsg , ( int ) sizeof ( umsg ) ) ;
int ulen = member_sync_build_update_msg ( my_join_sig , my_update_ts , juser , umsg , ( int ) sizeof ( umsg ) ) ;
if ( ulen > = 0 ) cs_ed25519_sign ( cs - > inst - > my_ed25519_privkey , umsg , ( size_t ) ulen , my_update_sig ) ;
}
uint64_t my_signed_by = 0 ; uint8_t my_signature [ 64 ] = { 0 } ;
topo_node_sqlite_member_get_sign ( cs - > inst - > topo_sqlite_db , ch_id , myid , & my_signed_by , my_signature ) ;
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 ;
/* 5. JOIN_INFO_RESP: инфо канала + мой мембер-рекорд */
char juser [ 256 ] ; snprintf ( juser , sizeof ( juser ) , " { \" name \" : \" %s \" } " , cs - > inst - > name [ 0 ] ? cs - > inst - > name : " " ) ;
uint8_t il = ( uint8_t ) strlen ( juser ) ;
uint8_t buf [ 1536 ] ; size_t boff = 0 ;
buf [ boff + + ] = CS_MSG_JOIN_INFO_RESP ;
uint8_t cnl = ( uint8_t ) strlen ( name ) ; buf [ boff + + ] = cnl ; memcpy ( buf + boff , name , cnl ) ; boff + = cnl ;
memcpy ( buf + boff , & owner , 8 ) ; boff + = 8 ;
memcpy ( buf + boff , x25519 , 32 ) ; boff + = 32 ;
memcpy ( buf + boff , ed_pub , 32 ) ; boff + = 32 ;
memcpy ( buf + boff , ch_ x25519, 32 ) ; boff + = 32 ;
memcpy ( buf + boff , ch_ ed, 32 ) ; boff + = 32 ;
memcpy ( buf + boff , ch_sig , 64 ) ; boff + = 64 ;
uint8_t inv_flags = ( my_join_sig [ 0 ] | | my_join_ts ) ? PEERS_FLAG_HAS_JOIN : 0 ;
buf [ boff + + ] = inv_flags ;
if ( inv_flags & PEERS_FLAG_HAS_JOIN ) {
memcpy ( buf + boff , my_join_sig , 64 ) ; boff + = 64 ;
memcpy ( buf + boff , & my_join_ts , 8 ) ; boff + = 8 ;
}
memcpy ( buf + boff , & myid , 8 ) ; boff + = 8 ;
memcpy ( buf + boff , cs - > inst - > my_keys . public_key , 32 ) ; boff + = 32 ;
memcpy ( buf + boff , cs - > inst - > my_ed25519_pubkey , 32 ) ; boff + = 32 ;
memcpy ( buf + boff , my_join_sig , 64 ) ; boff + = 64 ;
memcpy ( buf + boff , & my_join_ts , 8 ) ; boff + = 8 ;
memcpy ( buf + boff , my_update_sig , 64 ) ; boff + = 64 ;
memcpy ( buf + boff , & my_update_ts , 8 ) ; boff + = 8 ;
char juser [ 256 ] ; snprintf ( juser , sizeof ( juser ) , " { \" name \" : \" %s \" } " ,
cs - > inst - > name [ 0 ] ? cs - > inst - > name : " " ) ;
uint8_t il = ( uint8_t ) strlen ( juser ) ;
buf [ boff + + ] = il ; memcpy ( buf + boff , juser , il ) ; boff + = il ;
memcpy ( buf + boff , & my_signed_by , 8 ) ; boff + = 8 ;
memcpy ( buf + boff , my_signature , 64 ) ; boff + = 64 ;
cs_send ( cs , ch_id , peer , buf , boff ) ;
}
/* ─── CHANNEL_INFO_RESP (0x0A): inviter → joiner ─── */
/* ─── JOIN_INFO_RESP (0x11): connection-узел → 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 ) {
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: INFO_RESP ch=%s from=%016llx len=%zu " , CS_ID , ch_id , ( unsigned long long ) peer , len ) ;
static void cs_handle_join _info_resp ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_ INFO_RESP ch=%s from=%016llx len=%zu " , CS_ID , ch_id , ( unsigned long long ) peer , len ) ;
if ( cs - > info_req_timer ) { uasync_cancel_timeout ( cs - > inst - > ua , cs - > info_req_timer ) ; cs - > info_req_timer = NULL ; }
if ( len < 1 ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL_INFO_RESP too short len=%zu peer=%016llx " , CS_ID , len , ( unsigned long long ) peer ) ; return ; }
uint8_t nl = pl [ 0 ] ;
if ( 1 + nl + 8 + 32 + 32 + 64 + 1 + 64 + 8 + 1 > len ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL_INFO_RESP truncated len=%zu " , CS_ID , len ) ; return ; }
const uint8_t * p = pl + 1 ;
char name [ 128 ] ; memcpy ( name , p , nl ) ; name [ nl ] = ' \0 ' ; p + = nl ;
if ( len < 1 ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_INFO_RESP too short len=%zu " , CS_ID , len ) ; return ; }
const uint8_t * p = pl ;
uint8_t cnl = * p + + ;
if ( cnl + 8 + 32 + 32 + 64 + 8 + 32 + 32 + 64 + 8 + 64 + 8 + 1 + 8 + 64 > len ) {
DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_INFO_RESP truncated len=%zu " , CS_ID , len ) ; return ;
}
char name [ 128 ] ; size_t cn = cnl > 127 ? 127 : cnl ; memcpy ( name , p , cn ) ; name [ cn ] = ' \0 ' ; p + = cnl ;
uint64_t owner ; memcpy ( & owner , p , 8 ) ; p + = 8 ;
const uint8_t * x25519 = p ; p + = 32 ;
const uint8_t * ed_pub = p ; p + = 32 ;
const uint8_t * ch_ x25519 = p ; p + = 32 ;
const uint8_t * ch_ ed = p ; p + = 32 ;
const uint8_t * ch_sig = p ; p + = 64 ;
uint8_t inv_flags = * p + + ;
const uint8_t * inviter_join_sig = NULL ; uint64_t inviter_join_ts = 0 ;
if ( inv_flags & PEERS_FLAG_HAS_JOIN ) {
if ( p + 72 > pl + len ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL_INFO_RESP truncated at inviter_join_sig peer=%016llx " , CS_ID , ( unsigned long long ) peer ) ; return ; }
inviter_join_sig = p ; p + = 64 ; memcpy ( & inviter_join_ts , p , 8 ) ; p + = 8 ;
}
const uint8_t * inviter_update_sig = p ; p + = 64 ; uint64_t inviter_update_ts ;
memcpy ( & inviter_update_ts , p , 8 ) ; p + = 8 ;
uint8_t inv_userinfo_len = * p + + ;
char inv_userinfo [ 128 ] = " " ;
if ( p + inv_userinfo_len < = pl + len ) { memcpy ( inv_userinfo , p , inv_userinfo_len ) ; inv_userinfo [ inv_userinfo_len ] = ' \0 ' ; }
DEBUG_DEBUG ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL_INFO_RESP from peer=%016llx owner=%016llx ch_x25519=%016llx ch_ed=%016llx name=%s " ,
uint64_t c_node_id ; memcpy ( & c_node_id , p , 8 ) ; p + = 8 ;
const uint8_t * c_x25519 = p ; p + = 32 ;
const uint8_t * c_ed = p ; p + = 32 ;
const uint8_t * c_join_sig = p ; p + = 64 ;
uint64_t c_join_ts ; memcpy ( & c_join_ts , p , 8 ) ; p + = 8 ;
const uint8_t * c_update_sig = p ; p + = 64 ;
uint64_t c_update_ts ; memcpy ( & c_update_ts , p , 8 ) ; p + = 8 ;
uint8_t c_uil = * p + + ;
char c_userinfo [ 256 ] = " " ;
if ( c_uil & & p + c_uil < = pl + len ) { size_t u = c_uil > 255 ? 255 : c_uil ; memcpy ( c_userinfo , p , u ) ; c_userinfo [ u ] = ' \0 ' ; p + = c_uil ; }
uint64_t c_signed_by ; memcpy ( & c_signed_by , p , 8 ) ; p + = 8 ;
const uint8_t * c_signature = p ; p + = 64 ;
DEBUG_DEBUG ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_INFO_RESP from=%016llx owner=%016llx ch_x=%016llx ch_ed=%016llx name=%s conn_node=%016llx " ,
CS_ID , ( unsigned long long ) peer , ( unsigned long long ) owner ,
* ( const uint64_t * ) x25519 , * ( const uint64_t * ) ed_pub , name ) ;
* ( const uint64_t * ) ch_ x25519, * ( const uint64_t * ) ch_ed , name , ( unsigned long long ) c_node_id ) ;
/* 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_MEMBER_SYNC , " %s: CHANNEL_INFO_RESP invalid ch_sig ch=%s " , CS_ID , ch_id ) ;
memcpy ( vmsg + vlen , ch_ x25519, 32 ) ; vlen + = 32 ;
memcpy ( vmsg + vlen , ch_ ed, 32 ) ; vlen + = 32 ;
if ( cs_ed25519_verify ( ch_ ed, vmsg , vlen , ch_sig ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN _INFO_RESP invalid ch_sig ch=%s " , CS_ID , ch_id ) ;
return ;
}
/* save channel to local DB */
int cprc = topo_node_sqlite_channel_put ( cs - > inst - > topo_sqlite_db ,
ch_id , name , owner , x25519 , NULL , ed_pub , NULL , ch_sig ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: INFO_RESP channel_put rc=%d ch=%s owner=%016llx ch_x25519=%016llx " ,
CS_ID , cprc , ch_id , ( unsigned long long ) owner , * ( const uint64_t * ) x25519 ) ;
/* save channel */
topo_node_sqlite_channel_put ( cs - > inst - > topo_sqlite_db , ch_id , name , owner , ch_x25519 , NULL , ch_ed , NULL , ch_sig ) ;
chat_core_ensure_channel_ready ( ch_id ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: channel ready for sync ch=%s, db_sync will pick up via periodic check or active conn " ,
CS_ID , ch_id ) ;
/* verify inviter's join_sig AND save node info using ETCP-authenticated keys */
{ struct ETCP_CONN * inv_conn = cs_find_conn_for_node ( cs - > inst , peer ) ;
if ( ! inv_conn ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL_INFO_RESP no ETCP conn for inviter peer=%016llx " , CS_ID , ( unsigned long long ) peer ) ;
return ;
}
const uint8_t * inv_x25519 = inv_conn - > crypto_ctx . peer_public_key ;
const uint8_t * inv_ed = inv_conn - > peer_ed25519_pubkey ;
static const uint8_t zero_sig [ 64 ] = { 0 } ;
const uint8_t * jsig = inviter_join_sig ? inviter_join_sig : zero_sig ;
uint8_t ivmsg [ 256 ] ;
int ilen = member_sync_build_update_msg ( jsig , inviter_update_ts ,
inv_userinfo [ 0 ] ? inv_userinfo : " " ,
ivmsg , ( int ) sizeof ( ivmsg ) ) ;
if ( ilen < 0 | | cs_ed25519_verify ( inv_ed , ivmsg , ( size_t ) ilen , inviter_update_sig ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL_INFO_RESP invalid inviter_update_sig peer=%016llx ts=%llu " ,
CS_ID , ( unsigned long long ) peer , ( unsigned long long ) inviter_update_ts ) ;
}
sqlite3 * vdb = cs - > inst - > topo_sqlite_db ;
int rc = topo_node_sqlite_member_block_put ( vdb , ch_id , peer , inviter_join_sig , inviter_join_ts , inviter_update_sig , inviter_update_ts , inv_x25519 , inv_ed , inv_userinfo , inviter_join_sig ) ;
if ( rc ! = 0 ) DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: member_put(inviter) FAILED ch=%s peer=%016llx rc=%d " , CS_ID , ch_id , ( unsigned long long ) peer , rc ) ;
char inv_name [ 128 ] ; json_flat_get ( inv_userinfo , " name " , inv_name , sizeof ( inv_name ) ) ;
topo_node_sqlite_node_update_verified ( vdb , peer , inv_name , inv_x25519 , inv_ed , inviter_join_ts , ntp_time_get_seconds ( cs - > inst ) ) ;
}
/* добавить connection-узел в мемберы (дерево приглашений из INFO_RESP) */
sqlite3 * vdb = cs - > inst - > topo_sqlite_db ;
topo_node_sqlite_member_block_put ( vdb , ch_id , c_node_id , c_join_sig , c_join_ts ,
c_update_sig , c_update_ts , c_x25519 , c_ed ,
c_userinfo , NULL , c_signed_by , c_signature ) ;
{ char cn_name [ 128 ] ; json_flat_get ( c_userinfo , " name " , cn_name , sizeof ( cn_name ) ) ;
topo_node_sqlite_node_update_verified ( vdb , c_node_id , cn_name , c_x25519 , c_ed , c_join_ts , ntp_time_get_seconds ( cs - > inst ) ) ; }
/* generate our own join_sig */
/* построить собственный join_sig */
uint64_t myid = cs - > inst - > node_id ;
uint8_t my_x25519 [ 32 ] ;
memcpy ( my_x25519 , cs - > inst - > my_keys . public_key , 32 ) ;
const uint8_t * my_x25519 = cs - > inst - > my_keys . public_key ;
uint8_t join_sig [ 64 ] ; uint64_t join_ts ;
{
uint8_t msg [ 128 ] ;
join_ts = ( uint64_t ) ntp_time_get_seconds ( cs - > inst ) ;
int mlen = member_sync_build_join_msg ( x25519 , ed_pub , myid , my_x25519 , join_ts ,
msg , ( int ) sizeof ( msg ) ) ;
if ( mlen > = 0 ) cs_ed25519_sign ( cs - > inst - > my_ed25519_privkey , msg , ( size_t ) 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 ;
memcpy ( jbuf + joff , & join_ts , 8 ) ; joff + = 8 ;
char juser2 [ 256 ] ; snprintf ( juser2 , sizeof ( juser2 ) , " { \" name \" : \" %s \" } " ,
cs - > inst - > name [ 0 ] ? cs - > inst - > name : " " ) ;
uint8_t nml = ( uint8_t ) strlen ( juser2 ) ;
jbuf [ joff + + ] = nml ; memcpy ( jbuf + joff , juser2 , nml ) ; joff + = nml ;
int jrc = cs_send ( cs , ch_id , peer , jbuf , joff ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN sent myid=%016llx x25519=%016llx join_ts=%llu ch=%s to=0x%016llx rc=%d " ,
CS_ID , ( unsigned long long ) myid , * ( const uint64_t * ) my_x25519 , ( unsigned long long ) join_ts , ch_id , ( unsigned long long ) peer , jrc ) ;
cs - > join_timer = uasync_set_timeout ( cs - > inst - > ua ,
CS_JOIN_TIMEOUT_MS * 10 , cs , cs_join_timeout_cb , " cs_join " ) ;
}
int mlen = member_sync_build_join_msg ( ch_x25519 , ch_ed , myid , my_x25519 , join_ts , msg , ( int ) sizeof ( msg ) ) ;
if ( mlen < 0 ) { memset ( join_sig , 0 , 64 ) ; DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_INFO_RESP build_join_msg failed " , CS_ID ) ; return ; }
cs_ed25519_sign ( cs - > inst - > my_ed25519_privkey , msg , ( size_t ) mlen , join_sig ) ;
}
uint8_t my_update_sig [ 64 ] = { 0 } ; uint64_t my_update_ts ;
{
char juser [ 256 ] ; snprintf ( juser , sizeof ( juser ) , " { \" name \" : \" %s \" } " , cs - > inst - > name [ 0 ] ? cs - > inst - > name : " " ) ;
my_update_ts = ( uint64_t ) ntp_time_get_seconds ( cs - > inst ) ;
uint8_t umsg [ 256 ] ;
int ulen = member_sync_build_update_msg ( join_sig , my_update_ts , juser , umsg , ( int ) sizeof ( umsg ) ) ;
if ( ulen > = 0 ) cs_ed25519_sign ( cs - > inst - > my_ed25519_privkey , umsg , ( size_t ) ulen , my_update_sig ) ;
}
/* ─── CHANNEL_JOIN (0x0B): joiner → inviter ─── */
/* send JOIN_REQUEST: [join_key:8][member...] */
struct join_member_data jm ;
memset ( & jm , 0 , sizeof ( jm ) ) ;
jm . node_id = myid ; jm . x25519 = my_x25519 ; jm . ed25519 = cs - > inst - > my_ed25519_pubkey ;
jm . join_sig = join_sig ; jm . join_ts = join_ts ;
jm . update_sig = my_update_sig ; jm . update_ts = my_update_ts ;
snprintf ( jm . userinfo , sizeof ( jm . userinfo ) , " { \" name \" : \" %s \" } " , cs - > inst - > name [ 0 ] ? cs - > inst - > name : " " ) ;
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 + 8 + 1 ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: CHANNEL_JOIN too short len=%zu peer=%016llx " , CS_ID , len , ( unsigned long long ) peer ) ; return ; }
const uint8_t * p = pl ;
uint64_t node_id ; memcpy ( & node_id , p , 8 ) ; p + = 8 ;
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN ch=%s node=%016llx from=%016llx " , CS_ID , ch_id , ( unsigned long long ) node_id , ( unsigned long long ) peer ) ;
const uint8_t * x25519 = p ; p + = 32 ;
const uint8_t * ed_pub = p ; p + = 32 ;
const uint8_t * join_sig = p ; p + = 64 ;
uint64_t join_ts ; memcpy ( & join_ts , p , 8 ) ; p + = 8 ;
uint8_t userinfo_len = * p + + ;
char joiner_userinfo [ 128 ] = " " ;
if ( p + userinfo_len < = pl + len ) { memcpy ( joiner_userinfo , p , userinfo_len ) ; joiner_userinfo [ userinfo_len ] = ' \0 ' ; p + = userinfo_len ; }
/* verify join_sig */
uint8_t ch_x25519 [ 32 ] , ch_ed25519 [ 32 ] ;
if ( topo_node_sqlite_channel_get ( cs - > inst - > topo_sqlite_db , ch_id ,
NULL , 0 , NULL , ch_x25519 , ch_ed25519 , NULL ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN channel not in DB ch=%s " , CS_ID , ch_id ) ;
return ;
}
uint8_t vmsg [ 128 ] ;
int vlen = member_sync_build_join_msg ( ch_x25519 , ch_ed25519 , node_id , x25519 , join_ts ,
vmsg , ( int ) sizeof ( vmsg ) ) ;
if ( vlen < 0 | | cs_ed25519_verify ( ed_pub , vmsg , ( size_t ) vlen , join_sig ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN invalid sig node=0x%016llx ch=%s — signed(node=%016llx x25519=%016llx ed=%016llx ts=%llu) " , CS_ID ,
( unsigned long long ) node_id , ch_id ,
( unsigned long long ) node_id , * ( const uint64_t * ) x25519 , * ( const uint64_t * ) ed_pub , ( unsigned long long ) join_ts ) ;
uint8_t jm_buf [ 512 ] ; size_t jm_len = 0 ;
if ( chat_join_serialize_member ( & jm , jm_buf , sizeof ( jm_buf ) , & jm_len ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_REQUEST serialize failed " , CS_ID ) ;
return ;
}
uint8_t jbuf [ 520 ] ; size_t joff = 0 ;
jbuf [ joff + + ] = CS_MSG_JOIN_REQUEST ;
memcpy ( jbuf + joff , & cs - > pending_join_key , 8 ) ; joff + = 8 ;
memcpy ( jbuf + joff , jm_buf , jm_len ) ; joff + = jm_len ;
int jrc = cs_send ( cs , ch_id , peer , jbuf , joff ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_REQUEST sent myid=%016llx key=%016llx ch=%s to=0x%016llx rc=%d " ,
CS_ID , ( unsigned long long ) myid , ( unsigned long long ) cs - > pending_join_key , ch_id , ( unsigned long long ) peer , jrc ) ;
cs - > join_timer = uasync_set_timeout ( cs - > inst - > ua , CS_JOIN_TIMEOUT_MS * 10 , cs , cs_join_timeout_cb , " cs_join " ) ;
}
sqlite3 * db = cs - > inst - > topo_sqlite_db ;
int rc = topo_node_sqlite_member_block_put ( db , ch_id , node_id , join_sig , join_ts , NULL , 0 , x25519 , ed_pub , joiner_userinfo , NULL ) ;
if ( rc ! = 0 ) DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: member_put(joiner) FAILED ch=%s node=0x%016llx rc=%d " , CS_ID , ch_id , ( unsigned long long ) node_id , rc ) ;
{ char j_name [ 128 ] ; json_flat_get ( joiner_userinfo , " name " , j_name , sizeof ( j_name ) ) ;
topo_node_sqlite_node_update_verified ( db , node_id , j_name , x25519 , ed_pub , join_ts , ntp_time_get_seconds ( cs - > inst ) ) ; }
/* ─── pending-джойнеры (connection-узел): node_id → прямое соединение ─── */
/* 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 ) ;
static struct join_pending * cs_pending_find ( struct chat_sync * cs , uint64_t node_id , const char * ch_id ) {
for ( struct join_pending * jp = cs - > join_pending ; jp ; jp = jp - > next )
if ( jp - > node_id = = node_id & & strcmp ( jp - > ch_id , ch_id ) = = 0 ) return jp ;
return NULL ;
}
static void cs_pending_add ( struct chat_sync * cs , uint64_t node_id , const char * ch_id , struct ETCP_CONN * conn ) {
if ( cs_pending_find ( cs , node_id , ch_id ) ) return ;
struct join_pending * jp = u_malloc ( sizeof ( * jp ) ) ;
if ( ! jp ) return ;
jp - > node_id = node_id ; snprintf ( jp - > ch_id , sizeof ( jp - > ch_id ) , " %s " , ch_id ) ; jp - > conn = conn ;
jp - > next = cs - > join_pending ; cs - > join_pending = jp ;
DEBUG_DEBUG ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: pending join added node=0x%016llx ch=%s " , CS_ID , ( unsigned long long ) node_id , ch_id ) ;
}
static void cs_pending_remove ( struct chat_sync * cs , uint64_t node_id , const char * ch_id ) {
struct join_pending * * p = & cs - > join_pending ;
while ( * p ) {
if ( ( * p ) - > node_id = = node_id & & strcmp ( ( * p ) - > ch_id , ch_id ) = = 0 ) {
struct join_pending * r = * p ; * p = r - > next ; u_free ( r ) ;
return ;
}
p = & ( * p ) - > next ;
}
}
/* 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 ;
memcpy ( ubuf + uoff , & join_ts , 8 ) ; uoff + = 8 ;
ubuf [ uoff + + ] = userinfo_len ;
memcpy ( ubuf + uoff , joiner_userinfo , userinfo_len ) ; uoff + = userinfo_len ;
cs_propagate ( cs , ch_id , peer , ubuf , uoff ) ;
}
/* add to channel cache — always refresh to include new peer */
cs_refresh_channels ( cs ) ;
static void cs_pending_free_all ( struct chat_sync * cs ) {
struct join_pending * jp = cs - > join_pending ;
while ( jp ) { struct join_pending * n = jp - > next ; u_free ( jp ) ; jp = n ; }
cs - > join_pending = NULL ;
}
{ uint8_t mevt [ 1 + 64 + 1 + CHAT_MEMBER_DISPLAY_SIZE ] ; uint8_t cl = ( uint8_t ) strlen ( ch_id ) ;
mevt [ 0 ] = cl ; memcpy ( mevt + 1 , ch_id , cl ) ; mevt [ 1 + cl ] = 1 ;
if ( chat_core_get_single_member ( ch_id , node_id , mevt + 1 + cl + 1 ) = = 0 )
chat_event_post ( CHAT_EVT_MEMBER_UPDATED , mevt , 1 + cl + 1 + CHAT_MEMBER_DISPLAY_SIZE ) ; }
/* ─── отправка JOIN_READY джойнеру + снятие pending (connection-узел) ─── */
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN calling cs_post_channel_online ch=%s " , CS_ID , ch_id ) ;
cs_post_channel_online ( cs , ch_id ) ;
static void cs_send_join_ready ( struct chat_sync * cs , const char * ch_id , uint64_t node_id ) {
uint64_t group_id = strtoull ( ch_id , NULL , 10 ) ;
uint8_t rbuf [ 9 ] ;
rbuf [ 0 ] = CS_MSG_JOIN_READY ;
memcpy ( rbuf + 1 , & group_id , 8 ) ;
cs_send ( cs , ch_id , node_id , rbuf , 9 ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_READY sent to 0x%016llx ch=%s " ,
CS_ID , ( unsigned long long ) node_id , ch_id ) ;
cs_pending_remove ( cs , node_id , ch_id ) ;
}
/* initiate member sync with the joiner (server side) */
struct invite_sync_arg * sa = u_malloc ( sizeof ( struct invite_sync_arg ) ) ;
if ( sa ) { sa - > node_id = node_id ;
strncpy ( sa - > ch_id , ch_id , sizeof ( sa - > ch_id ) - 1 ) ; sa - > ch_id [ sizeof ( sa - > ch_id ) - 1 ] = ' \0 ' ;
DEBUG_DEBUG ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN alloc sa=%p node=0x%016llx ch=%s " , CS_ID , ( void * ) sa , ( unsigned long long ) sa - > node_id , sa - > ch_id ) ;
member_sync_start ( cs - > inst , node_id , ch_id , _on_invite_sync_done , sa ) ; }
else member_sync_start ( cs - > inst , node_id , ch_id , NULL , NULL ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN starting member_sync with joiner node=0x%016llx ch=%s done_cb=%s " ,
CS_ID , ( unsigned long long ) node_id , ch_id , sa ? " yes " : " no " ) ;
/* if we're the inviter — joiner joined successfully, notify GUI */
if ( cs - > pending_invite_ch_id ! = 0 & & cs - > pending_invite_is_inviter ) {
uint64_t ch_id_num = strtoull ( ch_id , NULL , 10 ) ;
uint8_t evt [ 20 ] ; memcpy ( evt , & node_id , 8 ) ; int r = 0 ; memcpy ( evt + 8 , & r , 4 ) ;
memcpy ( evt + 12 , & ch_id_num , 8 ) ;
chat_event_post ( CHAT_EVT_CONNECT_RESULT , evt , 20 ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN posted CHAT_EVT_CONNECT_RESULT to GUI node=0x%016llx ch=%s " ,
CS_ID , ( unsigned long long ) node_id , ch_id ) ;
cs - > pending_invite_node_id = 0 ; cs - > pending_invite_ch_id = 0 ;
cs - > pending_invite_is_inviter = 0 ;
if ( cs - > invite_send_timer ) { uasync_cancel_timeout ( cs - > inst - > ua , cs - > invite_send_timer ) ; cs - > invite_send_timer = NULL ; }
}
/* ─── коллбэк member_sync: пришёл рекорд мембера → отправить JOIN_READY джойнеру ─── */
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN accepted node=0x%016llx ch=%s " ,
CS_ID , ( unsigned long long ) node_id , ch_id ) ;
static void cs_on_member_applied ( const char * ch_id , uint64_t node_id ,
const uint8_t * x25519 , uint64_t signed_by ,
const uint8_t * signature , void * arg ) {
( void ) x25519 ; ( void ) signed_by ; ( void ) signature ;
struct chat_sync * cs = ( struct chat_sync * ) arg ;
if ( ! cs | | ! cs - > initialized ) return ;
struct join_pending * jp = cs_pending_find ( cs , node_id , ch_id ) ;
if ( ! jp | | ! jp - > conn ) return ;
cs_send_join_ready ( cs , ch_id , node_id ) ;
}
/* ─── WELCOME (0x0C): inviter → joiner with full peer list ─── */
/* ─── JOIN_REQUEST (0x12): joiner → connection-узел ─── */
static void cs_handle_welcome ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
if ( cs - > join_timer ) { uasync_cancel_timeout ( cs - > inst - > ua , cs - > join_timer ) ; cs - > join_timer = NULL ; }
( void ) peer ;
if ( len < 2 ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME too short len=%zu peer=%016llx " , CS_ID , len , ( unsigned long long ) peer ) ; return ; }
sqlite3 * db = cs - > inst - > topo_sqlite_db ;
static void cs_handle_join_request ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
/* payload: [join_key:8][member...] */
if ( len < 8 + 1 ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_REQUEST too short len=%zu peer=%016llx " , CS_ID , len , ( unsigned long long ) peer ) ; return ; }
uint64_t join_key ; memcpy ( & join_key , pl , 8 ) ;
uint64_t channel_id = strtoull ( ch_id , NULL , 10 ) ;
const uint8_t * member_data = pl + 8 ; size_t member_len = len - 8 ;
const uint8_t * p = pl ;
uint16_t pc ; memcpy ( & pc , p , 2 ) ; p + = 2 ;
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME ch=%s from=%016llx peers=%u " , CS_ID , ch_id , ( unsigned long long ) peer , pc ) ;
for ( uint16_t i = 0 ; i < pc ; i + + ) {
if ( ( size_t ) ( p - pl ) + 8 + 32 + 32 + 1 + 64 + 8 + 1 + 1 + 64 > len ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME peer[%u] truncated at %td len=%zu " , CS_ID , i , p - pl , len ) ; break ; } /* min: id+x25519+ed+flags+update_sig(64+8)+nl+atl+adm_tags_sig(64) */
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 ;
uint8_t flags = * p + + ;
const uint8_t * join_sig = NULL ; uint64_t join_ts = 0 ;
if ( flags & PEERS_FLAG_HAS_JOIN ) {
if ( p + 64 + 8 > pl + len ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME join_sig truncated node=0x%016llx i=%u " , CS_ID , ( unsigned long long ) node_id , i ) ; break ; }
join_sig = p ; p + = 64 ;
memcpy ( & join_ts , p , 8 ) ; p + = 8 ;
}
const uint8_t * update_sig = p ; p + = 64 ;
uint64_t update_ts ; memcpy ( & update_ts , p , 8 ) ; p + = 8 ;
( void ) update_sig ; ( void ) update_ts ;
uint8_t nl = * p + + ;
char peer_userinfo [ 256 ] = " " ;
if ( nl & & p + nl < = pl + len ) { memcpy ( peer_userinfo , p , nl ) ; peer_userinfo [ nl ] = ' \0 ' ; p + = nl ; }
uint8_t atl = * p + + ;
if ( atl > 0 & & p + atl < = pl + len ) p + = atl ;
p + = 64 ; /* adm_tags_sig */
/* verify join_sig if present */
if ( join_sig ) {
uint8_t ch_x2 [ 32 ] , ch_ed2 [ 32 ] ;
if ( topo_node_sqlite_channel_get ( db , ch_id ,
NULL , 0 , NULL , ch_x2 , ch_ed2 , NULL ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME channel not in DB ch=%s " , CS_ID , ch_id ) ;
continue ;
}
uint8_t vmsg [ 128 ] ;
int vlen = member_sync_build_join_msg ( ch_x2 , ch_ed2 , node_id , x25519 , join_ts ,
vmsg , ( int ) sizeof ( vmsg ) ) ;
if ( vlen < 0 | | cs_ed25519_verify ( ed_pub , vmsg , ( size_t ) vlen , join_sig ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME invalid join_sig node=0x%016llx — skipping " , CS_ID , ( unsigned long long ) node_id ) ;
continue ;
}
}
struct join_member_data m ;
if ( chat_join_parse_member ( member_data , member_len , & m ) ! = 0 ) {
DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_REQUEST parse failed ch=%s from=%016llx " , CS_ID , ch_id , ( unsigned long long ) peer ) ;
return ;
}
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_REQUEST ch=%s joiner=%016llx from=%016llx key=%016llx " ,
CS_ID , ch_id , ( unsigned long long ) m . node_id , ( unsigned long long ) peer , ( unsigned long long ) join_key ) ;
/* привязка идентичности: pubkey из ETCP-соединения == заявленный x25519 */
struct ETCP_CONN * jconn = cs_find_conn_for_node ( cs - > inst , peer ) ;
if ( ! jconn | | memcmp ( jconn - > crypto_ctx . peer_public_key , m . x25519 , 32 ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_REQUEST identity mismatch joiner=0x%016llx peer=0x%016llx — drop " ,
CS_ID , ( unsigned long long ) m . node_id , ( unsigned long long ) peer ) ;
return ;
}
int rc = topo_node_sqlite_member_block_put ( db , ch_id , node_id , join_sig , join_ts , NULL , 0 , x25519 , ed_pub , peer_userinfo , NULL ) ;
if ( rc ! = 0 ) DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: member_put(welcome) FAILED ch=%s node=0x%016llx rc=%d " , CS_ID , ch_id , ( unsigned long long ) node_id , rc ) ;
{ char pn [ 256 ] ; json_flat_get ( peer_userinfo , " name " , pn , sizeof ( pn ) ) ;
topo_node_sqlite_node_update_verified ( db , node_id , pn , x25519 , ed_pub , join_ts , ntp_time_get_seconds ( cs - > inst ) ) ; }
/* валидация ключа */
uint64_t inviter = chat_join_lookup_inviter ( cs - > inst , channel_id , join_key ) ;
if ( inviter = = 0 ) {
DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_REQUEST invalid/expired join key ch=%s joiner=0x%016llx " ,
CS_ID , ch_id , ( unsigned long long ) m . node_id ) ;
uint8_t err [ 1 ] = { CS_MSG_ERROR } ; cs_send ( cs , ch_id , peer , err , 1 ) ;
return ;
}
member_sync_cancel ( g_cs - > inst , node_id , ch_id ) ;
/* валидный ключ → сразу добавляем узел в группу для BGP sync */
{
uint64_t gid = strtoull ( ch_id , NULL , 10 ) ;
struct TOPO_GROUP * g = topo_groups_find ( cs - > inst - > topo_groups , gid ) ;
if ( g & & g - > group_type = = TOPO_GROUP_TYPE_CHAT )
topo_group_new_conn ( g , jconn ) ;
}
/* добавить в pending (ждут JOIN_READY по приходу рекорда через merkle) */
cs_pending_add ( cs , m . node_id , ch_id , jconn ) ;
/* форвард инвайтеру (или локальная обработка, если инвайтер == мы) */
if ( inviter = = cs - > inst - > node_id ) {
chat_join_process_request ( cs - > inst , channel_id , peer , join_key , member_data , member_len ) ;
/* мы и есть connection-узел + инвайтер: рекорд добавлен локально,
merkle - apply - к о л л б э к ( cs_on_member_applied ) з д е с ь н е с р а б о т а е т —
о т п р а в л я е м JOIN_READY н а п р я м у ю , е с л и м е м б е р р е а л ь н о д о б а в л е н . */
if ( topo_node_sqlite_member_in_channel ( cs - > inst - > topo_sqlite_db , ch_id , m . node_id ) )
cs_send_join_ready ( cs , ch_id , m . node_id ) ;
} else {
chat_join_forward_request ( cs - > inst , channel_id , inviter , join_key , & m ) ;
}
cs_refresh_channels ( cs ) ;
cs_post_channel_online ( cs , ch_id ) ;
}
/* add self to local peers table (server added joiner to its DB; client must do the same) */
/* ─── JOIN_READY (0x13): connection-узел → joiner (можно запускать BGP) ─── */
static void cs_handle_join_ready ( struct chat_sync * cs , uint64_t peer ,
const char * ch_id , const uint8_t * pl , size_t len ) {
if ( cs - > join_timer ) { uasync_cancel_timeout ( cs - > inst - > ua , cs - > join_timer ) ; cs - > join_timer = NULL ; }
if ( len < 8 ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_READY too short len=%zu peer=%016llx " , CS_ID , len , ( unsigned long long ) peer ) ; return ; }
uint64_t group_id ; memcpy ( & group_id , pl , 8 ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_READY ch=%s group=%016llx from=%016llx — joining complete, starting BGP " ,
CS_ID , ch_id , ( unsigned long long ) group_id , ( unsigned long long ) peer ) ;
/* запуск BGP + полноценное участие в канале */
{
uint64_t myid = cs - > inst - > node_id ;
const uint8_t * my_x25 = cs - > inst - > my_keys . public_key ;
const uint8_t * my_ed = cs - > inst - > my_ed25519_pubkey ;
const char * my_name = cs - > inst - > name [ 0 ] ? cs - > inst - > name : " " ;
int src = topo_node_sqlite_member_block_put ( db , ch_id , myid , NULL , 0 , NULL , 0 , my_x25 , my_ed , my_name , NULL ) ;
if ( src ! = 0 )
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: member_put(self) FAILED in WELCOME ch=%s rc=%d " , CS_ID , ch_id , src ) ;
else
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME added self to peers ch=%s node=0x%016llx x25519=%016llx name=%s " ,
CS_ID , ch_id , ( unsigned long long ) myid , * ( const uint64_t * ) my_x25 , my_name ) ;
struct TOPO_GROUP * g = topo_groups_find ( cs - > inst - > topo_groups , group_id ) ;
if ( g & & g - > group_type = = TOPO_GROUP_TYPE_CHAT ) {
struct ETCP_CONN * c = cs_find_conn_for_node ( cs - > inst , peer ) ;
if ( c ) topo_group_new_conn ( g , c ) ;
}
}
chat_core_ensure_channel_ready ( ch_id ) ;
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 ) ;
chat_event_post ( CHAT_EVT_CHANNEL_UPDATED , evt , 1 + ch_id_len ) ;
/* full member list for the newly joined channel */
{ uint8_t * members = NULL ; int mc = 0 ;
if ( chat_core_get_member_list ( ch_id , & members , & mc ) = = 0 & & members ) {
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME member_list count=%d ch=%s " , CS_ID , mc , ch_id ) ;
size_t esz = 1 + ch_id_len + 2 + ( size_t ) mc * CHAT_MEMBER_DISPLAY_SIZE ;
uint8_t * mevt = u_malloc ( esz ) ;
if ( mevt ) {
uint16_t u16 = ( uint16_t ) mc ;
mevt [ 0 ] = ch_id_len ; memcpy ( mevt + 1 , ch_id , ch_id_len ) ;
memcpy ( mevt + 1 + ch_id_len , & u16 , 2 ) ;
memcpy ( mevt + 1 + ch_id_len + 2 , members , ( size_t ) mc * CHAT_MEMBER_DISPLAY_SIZE ) ;
chat_event_post ( CHAT_EVT_MEMBER_LIST , mevt , ( int ) esz ) ;
u_free ( mevt ) ;
}
u_free ( members ) ;
}
}
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME calling cs_post_channel_online ch=%s " , CS_ID , ch_id ) ;
cs_post_channel_online ( cs , ch_id ) ;
cs_resume_db_sync ( ch_id ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME processed ch=%s peers=%d — message sync triggered via db_sync (active conn or peer_check timer every 5s) " ,
CS_ID , ch_id , pc ) ;
if ( cs - > pending_invite_ch_id ! = 0 ) {
uint64_t ch_id_num = strtoull ( ch_id , NULL , 10 ) ;
uint8_t cevt [ 20 ] ; memcpy ( cevt , & peer , 8 ) ; int r = 0 ; memcpy ( cevt + 8 , & r , 4 ) ; memcpy ( cevt + 12 , & ch_id_num , 8 ) ;
chat_event_post ( CHAT_EVT_CONNECT_RESULT , cevt , 20 ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WELCOME invite success ch=%s peer=%016llx — posted CHAT_EVT_CONNECT_RESULT " ,
CS_ID , ch_id , ( unsigned long long ) peer ) ;
cs - > pending_invite_node_id = 0 ; cs - > pending_invite_ch_id = 0 ; if ( cs - > join_timer ) { uasync_cancel_timeout ( cs - > inst - > ua , cs - > join_timer ) ; cs - > join_timer = NULL ; }
}
}
/* ─── 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 + 8 + 1 ) { DEBUG_WARN ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: PEER_UPSERT too short len=%zu " , CS_ID , len ) ; 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 ;
uint64_t join_ts ; memcpy ( & join_ts , p , 8 ) ; p + = 8 ;
uint8_t userinfo_len = * p + + ;
char peer_userinfo [ 128 ] = " " ;
if ( p + userinfo_len < = pl + len ) { memcpy ( peer_userinfo , p , userinfo_len ) ; peer_userinfo [ userinfo_len ] = ' \0 ' ; p + = userinfo_len ; }
/* verify join_sig */
uint8_t ch_x3 [ 32 ] , ch_ed3 [ 32 ] ;
if ( topo_node_sqlite_channel_get ( cs - > inst - > topo_sqlite_db , ch_id ,
NULL , 0 , NULL , ch_x3 , ch_ed3 , NULL ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: PEER_UPSERT channel not in DB ch=%s " , CS_ID , ch_id ) ;
return ;
}
uint8_t vmsg [ 128 ] ;
int vlen = member_sync_build_join_msg ( ch_x3 , ch_ed3 , node_id , x25519 , join_ts ,
vmsg , ( int ) sizeof ( vmsg ) ) ;
if ( vlen < 0 | | cs_ed25519_verify ( ed_pub , vmsg , ( size_t ) vlen , join_sig ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: PEER_UPSERT invalid sig node=0x%016llx " , CS_ID ,
( unsigned long long ) node_id ) ;
return ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: JOIN_READY invite success ch=%s peer=%016llx " , CS_ID , ch_id , ( unsigned long long ) peer ) ;
cs - > pending_invite_node_id = 0 ; cs - > pending_invite_ch_id = 0 ; cs - > pending_join_key = 0 ;
}
sqlite3 * db = cs - > inst - > topo_sqlite_db ;
int rc = topo_node_sqlite_member_block_put ( db , ch_id , node_id , join_sig , join_ts , NULL , 0 , x25519 , ed_pub , peer_userinfo , NULL ) ;
if ( rc ! = 0 ) DEBUG_ERROR ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: member_put(peer_upsert) FAILED ch=%s node=0x%016llx rc=%d " , CS_ID , ch_id , ( unsigned long long ) node_id , rc ) ;
{ char pn2 [ 128 ] ; json_flat_get ( peer_userinfo , " name " , pn2 , sizeof ( pn2 ) ) ;
topo_node_sqlite_node_update_verified ( db , node_id , pn2 , x25519 , ed_pub , join_ts , ntp_time_get_seconds ( cs - > inst ) ) ; }
cs_refresh_channels ( cs ) ;
{ uint8_t mevt [ 1 + 64 + 1 + CHAT_MEMBER_DISPLAY_SIZE ] ; uint8_t ml = ( uint8_t ) strlen ( ch_id ) ;
mevt [ 0 ] = ml ; memcpy ( mevt + 1 , ch_id , ml ) ; mevt [ 1 + ml ] = 1 ;
if ( chat_core_get_single_member ( ch_id , node_id , mevt + 1 + ml + 1 ) = = 0 )
chat_event_post ( CHAT_EVT_MEMBER_UPDATED , mevt , 1 + ml + 1 + CHAT_MEMBER_DISPLAY_SIZE ) ; }
/* propagate to others (except sender and the subject node) */
cs_propagate ( cs , ch_id , peer , pl , len ) ;
DEBUG_INFO ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: PEER_UPSERT node=0x%016llx ch=%s " ,
CS_ID , ( unsigned long long ) node_id , ch_id ) ;
}
/* ─── CHANNEL_INVITE (0x0F): inviter → joiner (push-приглашение в канал) ─── */