@ -23,6 +23,7 @@
# include "topo_group.h"
# include "topo_group.h"
# include "../transport_layer/etcp_connect.h"
# include "../transport_layer/etcp_connect.h"
# include "route_ping.h"
# include "route_ping.h"
# include "../chat/chat_core.h"
/* ═══════ forward-декларации ═══════ */
/* ═══════ forward-декларации ═══════ */
@ -741,55 +742,254 @@ void conn_mgr_direct_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry
/* ═══════ invite обработчики ═══════ */
/* ═══════ invite обработчики ═══════ */
/* Формирует полный INVITE_INFO_RESP: если сервер участник канала — возвращает
* is_member = 1 + В С Е п о л я к а н а л а ( name , owner , к л ю ч и , ch_sig , join / update_sig ) .
* Е с л и н е у ч а с т н и к — is_member = 0. П о п у т н о с о з д а ё т и н ф р а с т р у к т у р у г р у п п ы ч е р е з
* chat_core_ensure_channel_ready , ч т о б ы г р у п п а б ы л а г о т о в а к м а р ш р у т и з а ц и и с р а з у . */
void cm_handle_invite_info_req ( struct CONN_MGR * mgr , struct ETCP_CONN * conn , const uint8_t * data , size_t len ) {
void cm_handle_invite_info_req ( struct CONN_MGR * mgr , struct ETCP_CONN * conn , const uint8_t * data , size_t len ) {
if ( len < sizeof ( struct CM_INVITE_REQ ) ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: INVITE_INFO_REQ too short (%zu) " , len ) ; return ; }
if ( len < sizeof ( struct CM_INVITE_REQ ) ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: INVITE_INFO_REQ too short (%zu) " , len ) ; return ; }
struct CM_INVITE_REQ * req = ( struct CM_INVITE_REQ * ) data ;
struct CM_INVITE_REQ * req = ( struct CM_INVITE_REQ * ) data ;
DEBUG_DEBUG ( DEBUG_CATEGORY_GENERAL , " invite: INFO_REQ from=%016llx group=%016llx " ,
( unsigned long long ) conn - > peer_node_id , ( unsigned long long ) req - > group_id ) ;
char ch_id [ 32 ] ; snprintf ( ch_id , sizeof ( ch_id ) , " %llu " , ( unsigned long long ) req - > group_id ) ;
/* данные узла-ответчика для ed_pubkey и node_name */
struct TOPO_GROUP * grp = topo_groups_find ( mgr - > instance - > topo_groups , req - > group_id ) ;
struct TOPO_GROUP * grp = topo_groups_find ( mgr - > instance - > topo_groups , req - > group_id ) ;
struct TOPO_GROUP * sg = grp ? grp : mgr - > group ;
struct TOPO_GROUP * sg = grp ? grp : mgr - > group ;
struct TOPO_NODE * ln = sg - > local_node ? topo_node_registry_find ( sg - > instance - > topo_groups , sg - > local_node - > node_id ) : NULL ;
struct TOPO_NODE * ln = sg - > local_node ? topo_node_registry_find ( sg - > instance - > topo_groups , sg - > local_node - > node_id ) : NULL ;
const char * nm = ln ? ln - > node_name : NULL ; size_t nl = nm ? strlen ( nm ) : 0 ; if ( nl > 255 ) nl = 255 ;
const char * nm = ln ? ln - > node_name : NULL ; size_t nl = nm ? strlen ( nm ) : 0 ; if ( nl > 255 ) nl = 255 ;
size_t rs = sizeof ( struct CM_INVITE_RESP ) + nl ;
/* ── ищем канал в SQLite ── */
sqlite3 * db = mgr - > instance - > topo_sqlite_db ;
char ch_name [ 128 ] = " " ; uint64_t owner = 0 ;
uint8_t ch_x25519 [ 32 ] = { 0 } , ch_ed_pub [ 32 ] = { 0 } , ch_sig [ 64 ] = { 0 } ;
int have_channel = db & & topo_node_sqlite_channel_get ( db , ch_id , ch_name , ( int ) sizeof ( ch_name ) ,
& owner , ch_x25519 , ch_ed_pub , ch_sig ) = = 0 ;
if ( ! have_channel ) {
size_t rs = CM_INVITE_RESP_HDR_SIZE + nl ;
struct CM_INVITE_RESP * resp = u_calloc ( 1 , rs ) ; if ( ! resp ) return ;
struct CM_INVITE_RESP * resp = u_calloc ( 1 , rs ) ; if ( ! resp ) return ;
resp - > cmd = ETCP_RT_ID_CONN_MGR ; resp - > subcmd = CM_SUBCMD_INVITE_INFO_RESP ; resp - > group_id = req - > group_id ; resp - > is_member = 1 ;
resp - > cmd = ETCP_RT_ID_CONN_MGR ; resp - > subcmd = CM_SUBCMD_INVITE_INFO_RESP ;
memcpy ( resp - > ed25519_pubkey , sg - > ed25519_public_key , SC_PUBKEY_SIZE ) ; resp - > node_name_len = ( uint8_t ) nl ;
resp - > group_id = req - > group_id ; resp - > is_member = 0 ;
if ( nl ) memcpy ( resp - > node_name , nm , nl ) ;
memcpy ( resp - > ed25519_pubkey , sg - > ed25519_public_key , SC_PUBKEY_SIZE ) ;
resp - > node_name_len = ( uint8_t ) nl ; if ( nl ) memcpy ( resp - > node_name , nm , nl ) ;
struct ll_entry * qe = queue_entry_new ( 0 ) ;
struct ll_entry * qe = queue_entry_new ( 0 ) ;
if ( qe ) {
if ( qe ) { size_t new_len = rs + 1 ; uint8_t * buf = u_malloc ( new_len ) ;
size_t new_len = rs + 1 ; /* +1 for duplicate cmd: dgram[0]=dispatch, dgram[1]=handler-skip, dgram[2]=subcmd, dgram[3..]=payload */
uint8_t * buf = u_malloc ( new_len ) ;
if ( buf ) { buf [ 0 ] = ETCP_RT_ID_CONN_MGR ; buf [ 1 ] = ETCP_RT_ID_CONN_MGR ; buf [ 2 ] = CM_SUBCMD_INVITE_INFO_RESP ;
if ( buf ) { buf [ 0 ] = ETCP_RT_ID_CONN_MGR ; buf [ 1 ] = ETCP_RT_ID_CONN_MGR ; buf [ 2 ] = CM_SUBCMD_INVITE_INFO_RESP ;
memcpy ( buf + 3 , ( uint8_t * ) resp + 2 , rs - 2 ) ; qe - > dgram = buf ; qe - > len = ( uint16_t ) new_len ; etcp_send ( conn , qe ) ; u_free ( resp ) ; }
memcpy ( buf + 3 , ( uint8_t * ) resp + 2 , rs - 2 ) ; qe - > dgram = buf ; qe - > len = ( uint16_t ) new_len ; etcp_send ( conn , qe ) ; }
else { queue_entry_free ( qe ) ; u_free ( resp ) ; }
else { queue_entry_free ( qe ) ; }
} else u_free ( resp ) ;
}
DEBUG_INFO ( DEBUG_CATEGORY_GENERAL , " invite: membership check RECEIVED from 0x%016llx — responding is_member=yes name= \" %.*s \" " ,
u_free ( resp ) ;
( unsigned long long ) conn - > peer_node_id , ( int ) nl , nm ? nm : " " ) ;
DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: group=%016llx NOT in DB — responding is_member=NO to 0x%016llx " ,
( unsigned long long ) req - > group_id , ( unsigned long long ) conn - > peer_node_id ) ;
return ;
}
/* ── участник: генерируем join_sig / update_sig ── */
uint64_t myid = mgr - > instance - > node_id ;
uint8_t my_join_sig [ 64 ] = { 0 } ; uint64_t my_join_ts = 0 ;
{
if ( db & & topo_node_sqlite_member_get_join ( db , ch_id , myid , my_join_sig , & my_join_ts ) ! = 0 ) {
uint8_t join_msg [ 256 ] ; size_t mlen = 0 ;
memcpy ( join_msg + mlen , ch_x25519 , 32 ) ; mlen + = 32 ;
memcpy ( join_msg + mlen , ch_ed_pub , 32 ) ; mlen + = 32 ;
memcpy ( join_msg + mlen , & myid , 8 ) ; mlen + = 8 ;
memcpy ( join_msg + mlen , mgr - > instance - > my_keys . public_key , 32 ) ; mlen + = 32 ;
my_join_ts = ( uint64_t ) ntp_time_get_seconds ( mgr - > instance ) ;
memcpy ( join_msg + mlen , & my_join_ts , 8 ) ; mlen + = 8 ;
if ( sc_ed25519_sign ( mgr - > instance - > my_ed25519_privkey , join_msg , mlen , my_join_sig ) ! = SC_OK )
DEBUG_ERROR ( DEBUG_CATEGORY_GENERAL , " invite: join_sig sign FAILED myid=%016llx ch=%s " , ( unsigned long long ) myid , ch_id ) ;
else
DEBUG_DEBUG ( DEBUG_CATEGORY_GENERAL , " invite: join_sig NEW myid=%016llx ts=%llu ch=%s " ,
( unsigned long long ) myid , ( unsigned long long ) my_join_ts , ch_id ) ;
} else {
DEBUG_DEBUG ( DEBUG_CATEGORY_GENERAL , " invite: join_sig REUSED myid=%016llx ts=%llu ch=%s " ,
( unsigned long long ) myid , ( unsigned long long ) my_join_ts , ch_id ) ;
}
}
uint8_t my_update_sig [ 64 ] = { 0 } ; uint64_t my_update_ts = 0 ;
{
char juser [ 256 ] ; snprintf ( juser , sizeof ( juser ) , " { \" name \" : \" %s \" } " ,
mgr - > instance - > name [ 0 ] ? mgr - > instance - > name : " " ) ;
uint8_t umsg [ 256 ] ; size_t ulen = 0 ;
memcpy ( umsg + ulen , my_join_sig , 64 ) ; ulen + = 64 ;
my_update_ts = ( uint64_t ) ntp_time_get_seconds ( mgr - > instance ) ;
memcpy ( umsg + ulen , & my_update_ts , 8 ) ; ulen + = 8 ;
size_t jl = strlen ( juser ) ; memcpy ( umsg + ulen , juser , jl ) ; umsg [ ulen + jl ] = ' \0 ' ; ulen + = jl + 1 ;
if ( sc_ed25519_sign ( mgr - > instance - > my_ed25519_privkey , umsg , ulen , my_update_sig ) ! = SC_OK )
DEBUG_ERROR ( DEBUG_CATEGORY_GENERAL , " invite: update_sig sign FAILED myid=%016llx " , ( unsigned long long ) myid ) ;
}
/* ── создаём инфраструктуру группы СРАЗУ (topo_group, db_sync, msg-таблицу) ── */
chat_core_ensure_channel_ready ( ch_id ) ;
/* ── сериализуем ответ ── */
size_t ch_nl = strlen ( ch_name ) ; if ( ch_nl > 255 ) ch_nl = 255 ;
uint8_t inv_flags = ( my_join_sig [ 0 ] | | my_join_ts ) ? PEERS_FLAG_HAS_JOIN : 0 ;
char userinfo [ 256 ] ; size_t userinfo_len = ( size_t ) snprintf ( userinfo , sizeof ( userinfo ) ,
" { \" name \" : \" %s \" } " , mgr - > instance - > name [ 0 ] ? mgr - > instance - > name : " " ) ;
size_t ch_info_len = 1 + ch_nl + 8 + 32 + 32 + 64 + 1
+ ( inv_flags & PEERS_FLAG_HAS_JOIN ? 72 : 0 )
+ 64 + 8 + 1 + userinfo_len ;
size_t rs = CM_INVITE_RESP_HDR_SIZE + nl + ch_info_len ;
uint8_t * rb = u_malloc ( rs ) ; if ( ! rb ) return ;
memset ( rb , 0 , rs ) ;
{ struct CM_INVITE_RESP * resp = ( struct CM_INVITE_RESP * ) rb ;
resp - > cmd = ETCP_RT_ID_CONN_MGR ; resp - > subcmd = CM_SUBCMD_INVITE_INFO_RESP ;
resp - > group_id = req - > group_id ; resp - > is_member = 1 ;
memcpy ( resp - > ed25519_pubkey , sg - > ed25519_public_key , SC_PUBKEY_SIZE ) ;
resp - > node_name_len = ( uint8_t ) nl ; if ( nl ) memcpy ( resp - > node_name , nm , nl ) ; }
uint8_t * ch = rb + CM_INVITE_RESP_HDR_SIZE + nl ;
* ch + + = ( uint8_t ) ch_nl ; memcpy ( ch , ch_name , ch_nl ) ; ch + = ch_nl ;
memcpy ( ch , & owner , 8 ) ; ch + = 8 ;
memcpy ( ch , ch_x25519 , 32 ) ; ch + = 32 ;
memcpy ( ch , ch_ed_pub , 32 ) ; ch + = 32 ;
memcpy ( ch , ch_sig , 64 ) ; ch + = 64 ;
* ch + + = inv_flags ;
if ( inv_flags & PEERS_FLAG_HAS_JOIN ) { memcpy ( ch , my_join_sig , 64 ) ; ch + = 64 ; memcpy ( ch , & my_join_ts , 8 ) ; ch + = 8 ; }
memcpy ( ch , my_update_sig , 64 ) ; ch + = 64 ;
memcpy ( ch , & my_update_ts , 8 ) ; ch + = 8 ;
* ch + + = ( uint8_t ) userinfo_len ; memcpy ( ch , userinfo , userinfo_len ) ;
DEBUG_INFO ( DEBUG_CATEGORY_GENERAL ,
" invite: membership CHECK OK from=0x%016llx group=%016llx ch_name=%.*s owner=%016llx "
" ch_x25519=%016llx... ch_ed=%016llx... inv_flags=%u join_ts=%llu upd_ts=%llu my_name=%s → is_member=YES full_ch_info (%zu+%zu=%zuB) " ,
( unsigned long long ) conn - > peer_node_id , ( unsigned long long ) req - > group_id ,
( int ) ch_nl , ch_name , ( unsigned long long ) owner ,
* ( const uint64_t * ) ch_x25519 , * ( const uint64_t * ) ch_ed_pub ,
( unsigned ) inv_flags , ( unsigned long long ) my_join_ts , ( unsigned long long ) my_update_ts ,
mgr - > instance - > name [ 0 ] ? mgr - > instance - > name : " ? " ,
CM_INVITE_RESP_HDR_SIZE + nl , ch_info_len , rs ) ;
{ struct ll_entry * qe = queue_entry_new ( 0 ) ;
if ( qe ) { size_t dgram_len = rs + 1 ; uint8_t * dgram = u_malloc ( dgram_len ) ;
if ( dgram ) { dgram [ 0 ] = ETCP_RT_ID_CONN_MGR ; dgram [ 1 ] = ETCP_RT_ID_CONN_MGR ; dgram [ 2 ] = CM_SUBCMD_INVITE_INFO_RESP ;
memcpy ( dgram + 3 , rb + 2 , rs - 2 ) ; qe - > dgram = dgram ; qe - > len = ( uint16_t ) dgram_len ; etcp_send ( conn , qe ) ; }
else { queue_entry_free ( qe ) ; }
}
}
u_free ( rb ) ;
}
}
void cm_handle_invite_info_resp ( struct CONN_MGR * mgr , struct ETCP_CONN * conn , const uint8_t * data , size_t len ) {
void cm_handle_invite_info_resp ( struct CONN_MGR * mgr , struct ETCP_CONN * conn , const uint8_t * data , size_t len ) {
if ( len < sizeof ( struct CM_INVITE_RESP ) ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: INVITE_INFO_RESP too short (%zu) " , len ) ; return ; }
if ( len < CM_INVITE_RESP_HDR_SIZE ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: INVITE_INFO_RESP too short (%zu < %zu ) " , len , CM_INVITE_RESP_HDR_SIZE ) ; return ; }
struct CM_INVITE_RESP * resp = ( struct CM_INVITE_RESP * ) data ;
struct CM_INVITE_RESP * resp = ( struct CM_INVITE_RESP * ) data ;
if ( len < sizeof ( struct CM_INVITE_RESP ) + ( size_t ) resp - > node_name_len ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: INVITE_INFO_RESP truncated " ) ; return ; }
uint8_t nnl = resp - > node_name_len ;
if ( len < CM_INVITE_RESP_HDR_SIZE + ( size_t ) nnl ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: INVITE_INFO_RESP truncated (need %zu, got %zu) " , CM_INVITE_RESP_HDR_SIZE + ( size_t ) nnl , len ) ; return ; }
/* ── ищем ожидающий invite ── */
struct cm_invite_pending * inv = NULL ;
struct cm_invite_pending * inv = NULL ;
{ struct TOPO_GROUP * grp = topo_groups_find ( mgr - > instance - > topo_groups , resp - > group_id ) ;
{ struct TOPO_GROUP * grp = topo_groups_find ( mgr - > instance - > topo_groups , resp - > group_id ) ;
if ( grp & & grp - > conn_mgr ) {
if ( grp & & grp - > conn_mgr )
for ( struct cm_invite_pending * p = grp - > conn_mgr - > invite_list ; p ; p = p - > next )
for ( struct cm_invite_pending * p = grp - > conn_mgr - > invite_list ; p ; p = p - > next )
if ( p - > state = = CM_INVITE_WAIT_INFO & & ( ! conn - > peer_node_id | | p - > node_id = = conn - > peer_node_id ) ) { inv = p ; break ; }
if ( p - > state = = CM_INVITE_WAIT_INFO & & ( ! conn - > peer_node_id | | p - > node_id = = conn - > peer_node_id ) ) { inv = p ; break ; }
}
}
}
if ( ! inv ) { for ( struct cm_invite_pending * p = mgr - > invite_list ; p ; p = p - > next ) if ( p - > state = = CM_INVITE_WAIT_INFO ) { inv = p ; break ; } }
if ( ! inv ) { for ( struct cm_invite_pending * p = mgr - > invite_list ; p ; p = p - > next ) if ( p - > state = = CM_INVITE_WAIT_INFO ) { inv = p ; break ; } }
if ( ! inv ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: INVITE_INFO_RESP — no pending invite in WAIT_INFO state " ) ; return ; }
if ( ! inv ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: INVITE_INFO_RESP — no pending invite in WAIT_INFO state " ) ; return ; }
if ( ! resp - > is_member ) {
if ( ! resp - > is_member ) {
DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: membership DENIED by 0x%016llx — not in channel " , ( unsigned long long ) inv - > node_id ) ;
DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: membership DENIED by 0x%016llx — not in channel " , ( unsigned long long ) inv - > node_id ) ;
cm_invite_fail ( inv ) ; return ;
cm_invite_fail ( inv ) ; return ;
}
}
/* ── сохраняем Ed25519 pubkey ответчика для NODEINFO-верификации ── */
if ( inv - > temp_nq ) {
if ( inv - > temp_nq ) {
struct TOPO_NODE * ni2 = topo_node_registry_find ( inv - > mgr - > instance - > topo_groups , inv - > temp_nq - > node_id ) ;
struct TOPO_NODE * ni2 = topo_node_registry_find ( inv - > mgr - > instance - > topo_groups , inv - > temp_nq - > node_id ) ;
if ( ni2 ) memcpy ( ni2 - > ed25519_public_key , resp - > ed25519_pubkey , SC_PUBKEY_SIZE ) ;
if ( ni2 ) memcpy ( ni2 - > ed25519_public_key , resp - > ed25519_pubkey , SC_PUBKEY_SIZE ) ;
if ( inv - > mgr - > instance - > topo_sqlite_db ) topo_node_sqlite_node_put ( inv - > mgr - > instance - > topo_sqlite_db , inv - > mgr - > instance - > topo_groups , inv - > temp_nq , ( time_t ) ( get_time_tb ( ) / 10000 ) ) ;
if ( inv - > mgr - > instance - > topo_sqlite_db )
topo_node_sqlite_node_put ( inv - > mgr - > instance - > topo_sqlite_db , inv - > mgr - > instance - > topo_groups , inv - > temp_nq , ( time_t ) ( get_time_tb ( ) / 10000 ) ) ;
}
}
/* ── парсим channel_info (новое) ── */
const uint8_t * ch = data + CM_INVITE_RESP_HDR_SIZE + nnl ;
size_t ch_remain = len - ( CM_INVITE_RESP_HDR_SIZE + nnl ) ;
if ( ch_remain < CM_INVITE_RESP_CH_MIN_SIZE ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: INVITE_INFO_RESP channel info too short (%zu < %zu) " , ch_remain , CM_INVITE_RESP_CH_MIN_SIZE ) ; cm_invite_fail ( inv ) ; return ; }
uint8_t c_nl = * ch + + ; ch_remain - - ;
if ( ch_remain < ( size_t ) c_nl ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: ch_name truncated " ) ; cm_invite_fail ( inv ) ; return ; }
char ch_name [ 128 ] ; size_t cn = c_nl > 127 ? 127 : c_nl ; memcpy ( ch_name , ch , cn ) ; ch_name [ cn ] = ' \0 ' ; ch + = c_nl ; ch_remain - = c_nl ;
uint64_t ch_owner ; memcpy ( & ch_owner , ch , 8 ) ; ch + = 8 ; ch_remain - = 8 ;
const uint8_t * ch_x25519 = ch ; ch + = 32 ; ch_remain - = 32 ;
const uint8_t * ch_ed_pub = ch ; ch + = 32 ; ch_remain - = 32 ;
const uint8_t * ch_sig = ch ; ch + = 64 ; ch_remain - = 64 ;
if ( ch_remain < 1 ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: ch_info truncated at inv_flags " ) ; cm_invite_fail ( inv ) ; return ; }
uint8_t inv_flags = * ch + + ; ch_remain - - ;
const uint8_t * join_sig = NULL ; uint64_t join_ts = 0 ;
if ( inv_flags & PEERS_FLAG_HAS_JOIN ) {
if ( ch_remain < 72 ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: ch_info truncated at join_sig " ) ; cm_invite_fail ( inv ) ; return ; }
join_sig = ch ; ch + = 64 ; memcpy ( & join_ts , ch , 8 ) ; ch + = 8 ; ch_remain - = 72 ;
}
if ( ch_remain < 73 ) { DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: ch_info truncated at update " ) ; cm_invite_fail ( inv ) ; return ; }
const uint8_t * upd_sig = ch ; ch + = 64 ; uint64_t upd_ts ; memcpy ( & upd_ts , ch , 8 ) ; ch + = 8 ; ch_remain - = 72 ;
uint8_t ui_len = * ch + + ; ch_remain - - ;
char ch_userinfo [ 256 ] = " " ;
if ( ui_len > 0 & & ch_remain > = ui_len ) { size_t ul = ui_len > 255 ? 255 : ui_len ; memcpy ( ch_userinfo , ch , ul ) ; ch_userinfo [ ul ] = ' \0 ' ; }
char ch_id [ 32 ] ; snprintf ( ch_id , sizeof ( ch_id ) , " %llu " , ( unsigned long long ) resp - > group_id ) ;
DEBUG_INFO ( DEBUG_CATEGORY_GENERAL ,
" invite: INFO_RESP from=0x%016llx group=%016llx node_name=%.*s "
" ch_name=%s ch_owner=%016llx ch_x25519=%016llx... ch_ed=%016llx... "
" inv_flags=%u join_ts=%llu upd_ts=%llu userinfo=%s " ,
( unsigned long long ) conn - > peer_node_id , ( unsigned long long ) resp - > group_id ,
( int ) nnl , nnl ? ( const char * ) resp - > node_name : " ? " ,
ch_name , ( unsigned long long ) ch_owner ,
* ( const uint64_t * ) ch_x25519 , * ( const uint64_t * ) ch_ed_pub ,
( unsigned ) inv_flags , ( unsigned long long ) join_ts , ( unsigned long long ) upd_ts ,
ch_userinfo [ 0 ] ? ch_userinfo : " ? " ) ;
/* ── верифицируем ch_sig ── */
{
uint8_t vmsg [ 1024 ] ; size_t vlen = 0 ;
vlen + = snprintf ( ( char * ) vmsg + vlen , sizeof ( vmsg ) - vlen , " %s " , ch_id ) + 1 ;
vlen + = ( size_t ) snprintf ( ( char * ) vmsg + vlen , sizeof ( vmsg ) - vlen , " %s " , ch_name ) + 1 ;
memcpy ( vmsg + vlen , & ch_owner , 8 ) ; vlen + = 8 ;
memcpy ( vmsg + vlen , ch_x25519 , 32 ) ; vlen + = 32 ;
memcpy ( vmsg + vlen , ch_ed_pub , 32 ) ; vlen + = 32 ;
if ( sc_ed25519_verify ( ch_ed_pub , vmsg , vlen , ch_sig ) ! = SC_OK ) {
DEBUG_ERROR ( DEBUG_CATEGORY_GENERAL , " invite: ch_sig VERIFY FAIL — rejecting group=%016llx from=0x%016llx " ,
( unsigned long long ) resp - > group_id , ( unsigned long long ) conn - > peer_node_id ) ;
cm_invite_fail ( inv ) ; return ;
}
DEBUG_DEBUG ( DEBUG_CATEGORY_GENERAL , " invite: ch_sig VERIFY OK " ) ;
}
/* ── сохраняем канал в БД и создаём ПОЛНОЦЕННУЮ группу (topo_group + db_sync + msg-таблицу) ── */
{
sqlite3 * ldb = inv - > mgr - > instance - > topo_sqlite_db ;
if ( ldb ) topo_node_sqlite_channel_put ( ldb , ch_id , ch_name , ch_owner ,
ch_x25519 , NULL , ch_ed_pub , NULL , ch_sig ) ;
}
chat_core_ensure_channel_ready ( ch_id ) ;
/* ── верифицируем update_sig приглашающего ── */
{
const uint8_t * inv_ed = conn - > peer_ed25519_pubkey ;
uint8_t ivmsg [ 256 ] ; size_t ilen = 0 ;
if ( join_sig & & ( inv_flags & PEERS_FLAG_HAS_JOIN ) ) { memcpy ( ivmsg + ilen , join_sig , 64 ) ; ilen + = 64 ; }
else { memset ( ivmsg + ilen , 0 , 64 ) ; ilen + = 64 ; }
memcpy ( ivmsg + ilen , & upd_ts , 8 ) ; ilen + = 8 ;
{ const char * nm2 = ch_userinfo [ 0 ] ? ch_userinfo : " " ;
size_t nl2 = strlen ( nm2 ) ; memcpy ( ivmsg + ilen , nm2 , nl2 + 1 ) ; ilen + = nl2 + 1 ; }
if ( sc_ed25519_verify ( inv_ed , ivmsg , ilen , upd_sig ) ! = SC_OK ) {
DEBUG_WARN ( DEBUG_CATEGORY_GENERAL , " invite: inviter_update_sig VERIFY FAIL node=0x%016llx " , ( unsigned long long ) conn - > peer_node_id ) ;
} else {
DEBUG_DEBUG ( DEBUG_CATEGORY_GENERAL , " invite: inviter_update_sig VERIFY OK " ) ;
}
}
if ( inv - > overall_timer ) { uasync_cancel_timeout ( inv - > mgr - > instance - > ua , inv - > overall_timer ) ; inv - > overall_timer = NULL ; }
if ( inv - > overall_timer ) { uasync_cancel_timeout ( inv - > mgr - > instance - > ua , inv - > overall_timer ) ; inv - > overall_timer = NULL ; }
DEBUG_INFO ( DEBUG_CATEGORY_GENERAL , " invite: membership CONFIRMED by 0x%016llx name= \" %.*s \" — joined to channel, handle registered " ,
DEBUG_INFO ( DEBUG_CATEGORY_GENERAL , " invite: membership CONFIRMED by 0x%016llx name= \" %.*s \" ch=%s — full channel ready " ,
( unsigned long long ) inv - > node_id , resp - > node_name_len , resp - > node_name_len ? ( const char * ) resp - > node_name : " " ) ;
( unsigned long long ) inv - > node_id , ( int ) nnl , nnl ? ( const char * ) resp - > node_name : " ? " , ch_id ) ;
if ( inv - > cb ) inv - > cb ( inv - > handle , inv - > node_id , inv - > mgr - > group - > group_id , CONN_EVENT_JOIN , inv - > cb_arg ) ;
if ( inv - > cb ) inv - > cb ( inv - > handle , inv - > node_id , inv - > mgr - > group - > group_id , CONN_EVENT_JOIN , inv - > cb_arg ) ;
if ( inv - > invite_conn ) {
if ( inv - > invite_conn ) {
node_conn_direct_open_node ( inv - > mgr - > instance , inv - > node_id , cm_ncd_callback , NULL , NULL , NULL , NULL ) ;
node_conn_direct_open_node ( inv - > mgr - > instance , inv - > node_id , cm_ncd_callback , NULL , NULL , NULL , NULL ) ;