@ -51,7 +51,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 , const char * data , size_t len , uint64_t author , void * arg ) ;
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 ) ;
/* ─── утилиты ─── */
@ -77,40 +77,6 @@ static void peers_table_name(const char* ch_id, char* buf, size_t sz) {
snprintf ( buf , sz , " peers_%s " , san ) ;
}
static void compute_datahash ( const uint8_t * data , size_t len , uint64_t * dh ) {
uint8_t hash [ 32 ] ; SHA256 ( data , len , hash ) ;
memcpy ( dh , hash , 8 ) ;
}
static void compute_chain_hash ( const uint8_t * prev_chain , int64_t ts ,
uint64_t dh , uint8_t * out ) {
uint8_t buf [ 32 + 8 + 8 ] ;
memcpy ( buf , prev_chain , 32 ) ;
memcpy ( buf + 32 , & ts , 8 ) ;
memcpy ( buf + 40 , & dh , 8 ) ;
SHA256 ( buf , 48 , out ) ;
}
static int get_last_chain_hash ( const char * ch_id , uint8_t * out ) {
char tbl [ 80 ] ; msg_table_name ( ch_id , tbl , sizeof ( tbl ) ) ;
char sql [ 256 ] ; snprintf ( sql , sizeof ( sql ) ,
" SELECT chain_hash FROM \" %s \" ORDER BY timestamp, datahash DESC LIMIT 1 " , tbl ) ;
sqlite3_stmt * stmt = NULL ;
if ( sqlite3_prepare_v2 ( g_cc . db , sql , - 1 , & stmt , NULL ) ! = SQLITE_OK ) {
memset ( out , 0 , 32 ) ; return - 1 ;
}
if ( sqlite3_step ( stmt ) = = SQLITE_ROW ) {
const void * blob = sqlite3_column_blob ( stmt , 0 ) ;
int len = sqlite3_column_bytes ( stmt , 0 ) ;
if ( blob & & len = = 32 ) memcpy ( out , blob , 32 ) ;
else memset ( out , 0 , 32 ) ;
} else {
memset ( out , 0 , 32 ) ;
}
sqlite3_finalize ( stmt ) ;
return 0 ;
}
static int db_exec ( const char * sql ) {
char * err = NULL ;
int rc = sqlite3_exec ( g_cc . db , sql , NULL , NULL , & err ) ;
@ -359,25 +325,31 @@ void chat_core_submit_message(struct chat_msg_submit* req) {
" { \" n \" :%llu, \" ch \" : \" %s \" , \" ct \" : \" %s \" , \" d \" : \" %.*s \" } " ,
( unsigned long long ) g_cc . my_node_id , req - > channel_id ,
req - > content_type , ( int ) req - > data_len , ( const char * ) req - > data ) ;
/* FIXME: JSON escaping for d (quotes/backslashes in data) */
/* For now, naive format; will break on special chars. TODO: use base64. */
int ret = db_sync_insert_signed ( si , json , strlen ( json ) , NULL , 0 ) ;
/* Generate Ed25519 signature: ts || json */
uint64_t ts = db_sync_next_timestamp ( si ) ;
uint8_t sig_msg [ 8192 ] ; size_t soff = 0 ;
memcpy ( sig_msg + soff , & ts , 8 ) ; soff + = 8 ;
size_t jl = strlen ( json ) ; memcpy ( sig_msg + soff , json , jl ) ; soff + = jl ;
uint8_t sig [ 64 ] ;
if ( sc_ed25519_sign ( g_cc . inst - > my_ed25519_privkey , sig_msg , soff , sig ) ! = SC_OK ) {
DEBUG_ERROR ( DEBUG_CATEGORY_CONNECTIVITY , " %s: Ed25519 sign failed " , CC_ID ) ;
return ;
}
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) */
uint64_t dh ; compute_datahash ( ( const uint8_t * ) req - > data , req - > data_len , & dh ) ;
char tbl [ 80 ] ; msg_table_name ( req - > channel_id , tbl , sizeof ( tbl ) ) ;
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(?,?,?,?,?,?,?,1,1) " , tbl ) ;
char sql [ 512 ] ; snprintf ( sql , sizeof ( sql ) , " INSERT OR IGNORE INTO \" %s \" (node_id,content_type,data,timestamp,signature,is_outgoing,is_read) VALUES(?,?,?,?,?,1,1) " , 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 ) dh ) ;
static const uint8_t ch32 [ 32 ] = { 0 } ; sqlite3_bind_blob ( st , 6 , ch32 , 32 , SQLITE_STATIC ) ;
static const uint8_t sig64 [ 64 ] = { 0 } ; sqlite3_bind_blob ( st , 7 , sig64 , 64 , SQLITE_STATIC ) ;
sqlite3_bind_blob ( st , 5 , sig , 64 , SQLITE_STATIC ) ;
sqlite3_step ( st ) ; sqlite3_finalize ( st ) ;
}
/* notify GUI anyway */
@ -408,7 +380,7 @@ int chat_core_chain_hash_at(const char* ch_id, uint32_t pos, uint8_t* hash_out)
if ( ! g_cc . initialized | | ! hash_out ) return - 1 ;
char tbl [ 80 ] ; msg_table_name ( ch_id , tbl , sizeof ( tbl ) ) ;
char sql [ 200 ] ; snprintf ( sql , sizeof ( sql ) ,
" SELECT chain_hash FROM \" %s \" ORDER BY timestamp, datahash ASC LIMIT 1 OFFSET ? " ,
" SELECT chain_hash FROM \" %s \" ORDER BY timestamp, node_id ASC LIMIT 1 OFFSET ? " ,
tbl ) ;
sqlite3_stmt * stmt = NULL ;
if ( sqlite3_prepare_v2 ( g_cc . db , sql , - 1 , & stmt , NULL ) ! = SQLITE_OK ) {
@ -1005,16 +977,13 @@ void chat_core_ensure_channel_ready(const char* ch_id) {
" content_type TEXT NOT NULL, "
" data BLOB NOT NULL, "
" timestamp INTEGER NOT NULL, "
" datahash INTEGER NOT NULL, "
" chain_hash BLOB NOT NULL, "
" signature BLOB, "
" is_outgoing INTEGER DEFAULT 0, "
" is_read INTEGER DEFAULT 0, "
" sync_flags INTEGER DEFAULT 0, "
" UNIQUE(timestamp, datahash)) " , tbl_msg ) ;
" UNIQUE(timestamp, node_id)) " , tbl_msg ) ;
db_exec ( sql ) ;
snprintf ( sql , sizeof ( sql ) ,
" CREATE INDEX IF NOT EXISTS \" idx_%s_ts_dh \" ON \" %s \" (timestamp, datahash ) " ,
" CREATE INDEX IF NOT EXISTS \" idx_%s_ts_node \" ON \" %s \" (timestamp, node_id ) " ,
tbl_msg , tbl_msg ) ;
db_exec ( sql ) ;
@ -1134,7 +1103,7 @@ static void si_register(struct DB_SYNC_INSTANCE* si, const char* ch_id) {
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 , const char * data , size_t len , uint64_t author , void * arg ) {
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>"} */
@ -1153,37 +1122,31 @@ static void on_db_sync_insert(struct DB_SYNC_INSTANCE* si, const char* data, siz
}
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 ; }
uint64_t dh ; compute_datahash ( ( const uint8_t * ) jd_start , jd_len , & dh ) ;
uint64_t ts = db_sync_get_last_timestamp ( si ) ;
char tbl [ 80 ] ; msg_table_name ( ch_id , tbl , sizeof ( tbl ) ) ;
uint8_t prev_ch [ 32 ] ; get_last_chain_hash ( ch_id , prev_ch ) ;
uint8_t chain_h [ 32 ] ; compute_chain_hash ( prev_ch , ( int64_t ) ts , dh , chain_h ) ;
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,sync_flags ) "
" VALUES(?,?,?,?,?,?,?,?,?,? ) " , tbl ) ;
" INSERT OR IGNORE INTO \" %s \" (node_id,content_type,data,timestamp,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 ) ts ) ;
sqlite3_bind_int64 ( st , 5 , ( sqlite3_int64 ) dh ) ;
sqlite3_bind_blob ( st , 6 , chain_h , 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 ) ; sqlite3_bind_int ( st , 10 , 0 ) ;
sqlite3_bind_int64 ( st , 4 , ( sqlite3_int64 ) record_ts ) ;
{ static const uint8_t z64 [ 64 ] = { 0 } ; sqlite3_bind_blob ( st , 5 , z64 , 64 , SQLITE_STATIC ) ; }
sqlite3_bind_int ( st , 6 , ( uint64_t ) jn = = g_cc . my_node_id ? 1 : 0 ) ;
sqlite3_bind_int ( st , 7 , 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 dh=%016llx ct=%.*s " ,
CC_ID , insert_count , ch_id , ( unsigned long long ) jn , ( unsigned long long ) ts ,
( unsigned long long ) dh , ( int ) jct_len , jct ) ;
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 dh=0x%016llx " ,
CC_ID , ch_id , ( long long ) ts , ( unsigned long long ) dh ) ;
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 ) ;