@ -49,7 +49,7 @@ static struct chat_core_ctx {
static struct DB_SYNC_INSTANCE * si_find ( const char * ch_id ) ;
static void si_register ( struct DB_SYNC_INSTANCE * si , const char * ch_id ) ;
static void on_db_sync_insert ( struct DB_SYNC_INSTANCE * si , uint64_t record_ts , const char * data , size_t len , uint64_t author , void * arg ) ;
static void on_msg_inserted ( struct DB_SYNC_INSTANCE * si , uint64_t record_ts , const char * data , size_t len , uint64_t author , void * arg ) ;
/* ─── утилиты ─── */
@ -399,34 +399,11 @@ void chat_core_submit_message(struct chat_msg_submit* req) {
int ret = db_sync_insert_signed ( si , json , strlen ( json ) , sig , 64 , ts ) ;
if ( ret ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_CONNECTIVITY , " %s: db_sync_insert_signed failed ret=%d " , CC_ID , ret ) ;
/* Insert directly to msg_ table as fallback (dual-write not triggered by callback) */
char tbl [ 80 ] ; msg_table_name ( req - > channel_id , tbl , sizeof ( tbl ) ) ;
/* compute datahash and chain_hash */
uint8_t dh_buf2 [ 32 ] ; SHA256 ( req - > data , req - > data_len , dh_buf2 ) ;
uint64_t datahash2 ; memcpy ( & datahash2 , dh_buf2 , 8 ) ;
uint8_t prev_ch2 [ 32 ] ; msg_get_prev_chain_hash ( tbl , prev_ch2 ) ;
uint8_t ch_buf2 [ 48 ] ; memcpy ( ch_buf2 , prev_ch2 , 32 ) ; memcpy ( ch_buf2 + 32 , & req - > timestamp , 8 ) ; memcpy ( ch_buf2 + 40 , & datahash2 , 8 ) ;
uint8_t ch_hash2 [ 32 ] ; SHA256 ( ch_buf2 , 48 , ch_hash2 ) ;
char sql [ 512 ] ; snprintf ( sql , sizeof ( sql ) , " INSERT OR IGNORE INTO \" %s \" (node_id,content_type,data,timestamp,datahash,chain_hash,signature,is_outgoing,is_read) VALUES(?,?,?,?,?,?,?,?,?) " , tbl ) ;
sqlite3_stmt * st = NULL ;
if ( sqlite3_prepare_v2 ( g_cc . db , sql , - 1 , & st , NULL ) = = SQLITE_OK ) {
sqlite3_bind_int64 ( st , 1 , ( sqlite3_int64 ) g_cc . my_node_id ) ;
sqlite3_bind_text ( st , 2 , req - > content_type , - 1 , SQLITE_STATIC ) ;
sqlite3_bind_blob ( st , 3 , req - > data , ( int ) req - > data_len , SQLITE_STATIC ) ;
sqlite3_bind_int64 ( st , 4 , ( sqlite3_int64 ) req - > timestamp ) ;
sqlite3_bind_int64 ( st , 5 , ( sqlite3_int64 ) datahash2 ) ;
sqlite3_bind_blob ( st , 6 , ch_hash2 , 32 , SQLITE_STATIC ) ;
sqlite3_bind_blob ( st , 7 , sig , 64 , SQLITE_STATIC ) ;
sqlite3_bind_int ( st , 8 , 1 ) ;
sqlite3_bind_int ( st , 9 , 1 ) ;
sqlite3_step ( st ) ; sqlite3_finalize ( st ) ;
}
/* notify GUI anyway */
uint8_t evt [ 65 ] ; uint8_t cl = ( uint8_t ) strlen ( req - > channel_id ) ; evt [ 0 ] = cl ; memcpy ( evt + 1 , req - > channel_id , cl ) ;
gui_bridge_post ( GUI_EVT_MSG_RECEIVED , evt , 1 + cl ) ;
return ;
}
/* notify GUI */
{ uint8_t evt [ 65 ] ; uint8_t cl = ( uint8_t ) strlen ( req - > channel_id ) ; evt [ 0 ] = cl ; memcpy ( evt + 1 , req - > channel_id , cl ) ;
gui_bridge_post ( GUI_EVT_MSG_RECEIVED , evt , 1 + cl ) ; }
}
void chat_core_submit_trampoline ( void * arg ) { chat_core_submit_message ( ( struct chat_msg_submit * ) arg ) ; u_free ( arg ) ; }
@ -877,40 +854,16 @@ void chat_core_connect_auto(uint64_t node_id,
}
}
/* ─── подготовка инфраструктуры канала (msg-таблица + db_sync instance) ─── */
/* ─── подготовка инфраструктуры канала (db_sync instance) ─── */
void chat_core_ensure_channel_ready ( const char * ch_id ) {
if ( ! g_cc . initialized | | ! ch_id | | ! ch_id [ 0 ] ) return ;
if ( si_find ( ch_id ) ) return ;
char tbl_msg [ 80 ] ; msg_table_name ( ch_id , tbl_msg , sizeof ( tbl_msg ) ) ;
char sql [ 512 ] ;
snprintf ( sql , sizeof ( sql ) ,
" CREATE TABLE IF NOT EXISTS \" %s \" ( "
" id INTEGER PRIMARY KEY AUTOINCREMENT, "
" node_id INTEGER NOT NULL, "
" content_type TEXT NOT NULL, "
" data BLOB NOT NULL, "
" timestamp INTEGER NOT NULL, "
" datahash INTEGER DEFAULT 0, "
" chain_hash BLOB, "
" signature BLOB, "
" is_outgoing INTEGER DEFAULT 0, "
" is_read INTEGER DEFAULT 0, "
" UNIQUE(timestamp, node_id)) " , tbl_msg ) ;
db_exec ( sql ) ;
/* migration for existing tables without datahash/chain_hash columns */
snprintf ( sql , sizeof ( sql ) , " ALTER TABLE \" %s \" ADD COLUMN datahash INTEGER DEFAULT 0 " , tbl_msg ) ;
sqlite3_exec ( g_cc . db , sql , NULL , NULL , NULL ) ;
snprintf ( sql , sizeof ( sql ) , " ALTER TABLE \" %s \" ADD COLUMN chain_hash BLOB " , tbl_msg ) ;
sqlite3_exec ( g_cc . db , sql , NULL , NULL , NULL ) ;
snprintf ( sql , sizeof ( sql ) ,
" CREATE INDEX IF NOT EXISTS \" idx_%s_ts_node \" ON \" %s \" (timestamp, node_id) " ,
tbl_msg , tbl_msg ) ;
db_exec ( sql ) ;
char tbl_peers [ 80 ] ; peers_table_name ( ch_id , tbl_peers , sizeof ( tbl_peers ) ) ;
char sql [ 512 ] ;
snprintf ( sql , sizeof ( sql ) ,
" CREATE TABLE IF NOT EXISTS \" %s \" ( "
" node_id INTEGER PRIMARY KEY, "
@ -929,12 +882,12 @@ void chat_core_ensure_channel_ready(const char* ch_id) {
uint64_t ch_hash = 0 ;
{ const uint8_t * chd = ( const uint8_t * ) ch_id ; size_t chl = strlen ( ch_id ) ; uint8_t sh [ 32 ] ; SHA256 ( chd , chl , sh ) ; memcpy ( & ch_hash , sh , 8 ) ; }
struct DB_SYNC_INSTANCE * si = db_sync_instance_add ( g_cc . inst , " chats " , ch_hash ) ;
struct DB_SYNC_INSTANCE * si = db_sync_instance_add ( g_cc . inst , tbl_msg , ch_hash ) ;
if ( si ) {
si_register ( si , ch_id ) ;
db_sync_set_insert_cb ( si , on_db_sync_insert , u_strdup ( ch_id ) ) ;
DEBUG_INFO ( DEBUG_CATEGORY_CONNECTIVITY , " %s: channel ready ch=%s hash=0x%016llx " ,
CC_ID , ch_id , ( unsigned long long ) ch_hash ) ;
db_sync_set_insert_cb ( si , on_msg_inserted , u_strdup ( ch_id ) ) ;
DEBUG_INFO ( DEBUG_CATEGORY_CONNECTIVITY , " %s: channel ready ch=%s tbl=%s hash=0x%016llx " ,
CC_ID , ch_id , tbl_msg , ( unsigned long long ) ch_hash ) ;
} else {
DEBUG_ERROR ( DEBUG_CATEGORY_CONNECTIVITY , " %s: db_sync_instance_add failed for ch=%s " ,
CC_ID , ch_id ) ;
@ -1043,6 +996,13 @@ void chat_core_create_channel_trampoline(void* arg) {
/* ─── db_sync helpers ─── */
static void on_msg_inserted ( struct DB_SYNC_INSTANCE * si , uint64_t record_ts , const char * data , size_t len , uint64_t author , void * arg ) {
( void ) si ; ( void ) record_ts ; ( void ) data ; ( void ) len ; ( void ) author ;
const char * ch_id = ( const char * ) arg ;
uint8_t evt [ 65 ] ; uint8_t cl = ( uint8_t ) strlen ( ch_id ) ; evt [ 0 ] = cl ; memcpy ( evt + 1 , ch_id , cl ) ;
gui_bridge_post ( GUI_EVT_MSG_RECEIVED , evt , 1 + cl ) ;
}
static struct DB_SYNC_INSTANCE * si_find ( const char * ch_id ) {
for ( int i = 0 ; i < g_cc . si_count ; i + + ) if ( strcmp ( g_cc . si_ch_id [ i ] , ch_id ) = = 0 ) return g_cc . si [ i ] ;
return NULL ;
@ -1050,67 +1010,9 @@ static struct DB_SYNC_INSTANCE* si_find(const char* ch_id) {
static void si_register ( struct DB_SYNC_INSTANCE * si , const char * ch_id ) {
if ( g_cc . si_count > = g_cc . si_capacity ) { int nc = g_cc . si_capacity ? g_cc . si_capacity * 2 : 8 ; g_cc . si = u_realloc ( g_cc . si , nc * sizeof ( void * ) ) ; g_cc . si_ch_id = u_realloc ( g_cc . si_ch_id , nc * sizeof ( char * ) ) ; g_cc . si_capacity = nc ; }
g_cc . si [ g_cc . si_count ] = si ; g_cc . si_ch_id [ g_cc . si_count ] = u_strdup ( ch_id ) ; g_cc . si_count + + ;
}
static void on_db_sync_insert ( struct DB_SYNC_INSTANCE * si , uint64_t record_ts , const char * data , size_t len , uint64_t author , void * arg ) {
const char * ch_id = ( const char * ) arg ;
static int insert_count = 0 ;
/* parse JSON: {"n":<uint64>,"ch":"<str>","ct":"<str>","d":"<str>"} */
const char * p = data ; const char * end = data + len ; uint64_t jn = 0 ;
{ /* "n" */
const char * n = strstr ( p , " \" n \" : " ) ; if ( ! n ) { DEBUG_ERROR ( DEBUG_CATEGORY_CONNECTIVITY , " %s: on_db_sync_insert JSON parse fail: no ' \" n \" ' field ch=%s " , CC_ID , ch_id ) ; return ; }
n + = 4 ; jn = strtoull ( n , NULL , 0 ) ; p = n ;
}
const char * jct = " text/plain " ; size_t jct_len = 10 ;
{ /* "ct" */
const char * ct = strstr ( p , " \" ct \" : \" " ) ; if ( ct ) { ct + = 6 ; const char * ce = strchr ( ct , ' " ' ) ; if ( ce ) { jct = ct ; jct_len = ( size_t ) ( ce - ct ) ; p = ce ; } }
}
const char * jd_start = NULL ; size_t jd_len = 0 ;
{ /* "d" */
const char * d = strstr ( p , " \" d \" : \" " ) ; if ( d ) { d + = 5 ; const char * de = d ; while ( de < end ) { if ( * de = = ' " ' & & ( de = = d | | * ( de - 1 ) ! = ' \\ ' ) ) break ; de + + ; } if ( de < end ) { jd_start = d ; jd_len = ( size_t ) ( de - d ) ; } }
}
if ( ! jd_start ) { DEBUG_ERROR ( DEBUG_CATEGORY_CONNECTIVITY , " %s: on_db_sync_insert JSON parse fail: no ' \" d \" ' field ch=%s " , CC_ID , ch_id ) ; return ; }
char tbl [ 80 ] ; msg_table_name ( ch_id , tbl , sizeof ( tbl ) ) ;
/* compute datahash and chain_hash */
uint8_t dh_buf [ 32 ] ; SHA256 ( ( const uint8_t * ) jd_start , jd_len , dh_buf ) ;
uint64_t datahash ; memcpy ( & datahash , dh_buf , 8 ) ;
uint8_t prev_ch [ 32 ] ; msg_get_prev_chain_hash ( tbl , prev_ch ) ;
uint8_t ch_buf [ 48 ] ; memcpy ( ch_buf , prev_ch , 32 ) ; memcpy ( ch_buf + 32 , & record_ts , 8 ) ; memcpy ( ch_buf + 40 , & datahash , 8 ) ;
uint8_t ch_hash [ 32 ] ; SHA256 ( ch_buf , 48 , ch_hash ) ;
char sql [ 512 ] ; snprintf ( sql , sizeof ( sql ) ,
" INSERT OR IGNORE INTO \" %s \" (node_id,content_type,data,timestamp,datahash,chain_hash,signature,is_outgoing,is_read) "
" VALUES(?,?,?,?,?,?,?,?,?) " , tbl ) ;
sqlite3_stmt * st = NULL ;
if ( sqlite3_prepare_v2 ( g_cc . db , sql , - 1 , & st , NULL ) ! = SQLITE_OK ) { DEBUG_ERROR ( DEBUG_CATEGORY_CONNECTIVITY , " %s: on_db_sync_insert prepare failed ch=%s " , CC_ID , ch_id ) ; return ; }
sqlite3_bind_int64 ( st , 1 , ( sqlite3_int64 ) jn ) ;
sqlite3_bind_text ( st , 2 , jct , ( int ) jct_len , SQLITE_STATIC ) ;
sqlite3_bind_blob ( st , 3 , jd_start , ( int ) jd_len , SQLITE_STATIC ) ;
sqlite3_bind_int64 ( st , 4 , ( sqlite3_int64 ) record_ts ) ;
sqlite3_bind_int64 ( st , 5 , ( sqlite3_int64 ) datahash ) ;
sqlite3_bind_blob ( st , 6 , ch_hash , 32 , SQLITE_STATIC ) ;
{ static const uint8_t z64 [ 64 ] = { 0 } ; sqlite3_bind_blob ( st , 7 , z64 , 64 , SQLITE_STATIC ) ; }
sqlite3_bind_int ( st , 8 , ( uint64_t ) jn = = g_cc . my_node_id ? 1 : 0 ) ;
sqlite3_bind_int ( st , 9 , 1 ) ;
int rc = sqlite3_step ( st ) ; sqlite3_finalize ( st ) ;
if ( rc = = SQLITE_DONE ) {
insert_count + + ;
if ( insert_count < = 3 | | insert_count % 10 = = 0 )
DEBUG_INFO ( DEBUG_CATEGORY_CONNECTIVITY , " %s: on_db_sync_insert #%d ch=%s n=%llu ts=%llu ct=%.*s " ,
CC_ID , insert_count , ch_id , ( unsigned long long ) jn , ( unsigned long long ) record_ts ,
( int ) jct_len , jct ) ;
uint8_t evt [ 65 ] ; uint8_t cl = ( uint8_t ) strlen ( ch_id ) ; evt [ 0 ] = cl ; memcpy ( evt + 1 , ch_id , cl ) ;
gui_bridge_post ( GUI_EVT_MSG_RECEIVED , evt , 1 + cl ) ;
} else if ( rc = = SQLITE_CONSTRAINT ) {
DEBUG_WARN ( DEBUG_CATEGORY_CONNECTIVITY , " %s: on_db_sync_insert duplicate ch=%s ts=%lld " ,
CC_ID , ch_id , ( long long ) record_ts ) ;
} else {
DEBUG_ERROR ( DEBUG_CATEGORY_CONNECTIVITY , " %s: on_db_sync_insert step failed rc=%d ch=%s " ,
CC_ID , rc , ch_id ) ;
}
uint32_t msg_cnt = db_sync_count ( si ) ;
DEBUG_INFO ( DEBUG_CATEGORY_DEBUG , " %s: si_register ch=%s mc=%u " , CC_ID , ch_id , msg_cnt ) ;
}
/* ─── сбор статуса (NTP + connections) и отправка в GUI ─── */