@ -300,7 +300,20 @@ void chat_core_set_sound_on_message_trampoline(void* arg) {
static void json_escape ( const char * src , char * dst , size_t dst_sz ) {
size_t i = 0 ; const char * s = src ;
while ( * s & & i < dst_sz - 2 ) {
if ( * s = = ' " ' | | * s = = ' \\ ' ) { if ( i + 1 < dst_sz - 2 ) dst [ i + + ] = ' \\ ' ; }
const char * escape = NULL ;
switch ( * s ) {
case ' " ' : escape = " \\ \" " ; break ;
case ' \\ ' : escape = " \\ \\ " ; break ;
case ' \n ' : escape = " \\ n " ; break ;
case ' \r ' : escape = " \\ r " ; break ;
case ' \t ' : escape = " \\ t " ; break ;
case ' \b ' : escape = " \\ b " ; break ;
case ' \f ' : escape = " \\ f " ; break ;
}
if ( escape ) {
if ( i + 2 > = dst_sz ) break ;
dst [ i + + ] = escape [ 0 ] ; dst [ i + + ] = escape [ 1 ] ; s + + ; continue ;
}
dst [ i + + ] = * s + + ;
}
dst [ i ] = ' \0 ' ;
@ -403,7 +416,8 @@ int chat_core_get_messages_json(struct UTUN_INSTANCE* inst, const char* ch_id, i
int chat_core_get_members_json ( struct UTUN_INSTANCE * inst , const char * ch_id , char * buf , size_t buf_size , size_t * out_len ) {
struct chat_core_ctx * cc = CC ( inst ) ;
if ( ! cc | | ! cc - > initialized | | ! ch_id | | ! buf | | ! out_len ) return - 1 ;
if ( ! cc | | ! cc - > initialized | | ! ch_id | | ! buf | | ! out_len | | buf_size < 3 ) return - 1 ;
* out_len = 0 ;
char peers_tbl [ 80 ] ; peers_table_name ( ch_id , peers_tbl , sizeof ( peers_tbl ) ) ;
char * w = buf ; char * end = buf + buf_size ;
* w + + = ' [ ' ; const char * sep = " " ;
@ -412,6 +426,12 @@ int chat_core_get_members_json(struct UTUN_INSTANCE* inst, const char* ch_id, ch
" SELECT p.node_id, n.name, n.x25519_pubkey, n.ed25519_pubkey, p.adm_tags "
" FROM \" %s \" p LEFT JOIN nodes n ON p.node_id=n.node_id " , peers_tbl ) ;
sqlite3_stmt * st = NULL ;
sqlite3_stmt * as = NULL ;
# define MEMBERS_APPEND(...) do { \
int written = snprintf ( w , ( size_t ) ( end - w ) , __VA_ARGS__ ) ; \
if ( written < 0 | | ( size_t ) written > = ( size_t ) ( end - w ) ) goto overflow ; \
w + = written ; \
} while ( 0 )
if ( sqlite3_prepare_v2 ( cc - > db , sql , - 1 , & st , NULL ) ! = SQLITE_OK ) { * out_len = 0 ; return - 1 ; }
while ( sqlite3_step ( st ) = = SQLITE_ROW ) {
uint64_t nid = ( uint64_t ) sqlite3_column_int64 ( st , 0 ) ;
@ -422,7 +442,7 @@ int chat_core_get_members_json(struct UTUN_INSTANCE* inst, const char* ch_id, ch
int x25519_len = sqlite3_column_bytes ( st , 2 ) ;
int ed25519_len = sqlite3_column_bytes ( st , 3 ) ;
const char * adm_tags = ( const char * ) sqlite3_column_text ( st , 4 ) ;
char esc_name [ 256 ] , x25519_hex [ 65 ] , ed25519_hex [ 65 ] , esc_tags [ 384 ] ;
char esc_name [ 256 ] , x25519_hex [ 65 ] , ed25519_hex [ 65 ] , esc_tags [ 6 * MS_ADM_TAGS_MAX + 1 ] ;
json_escape ( name ? name : " " , esc_name , sizeof ( esc_name ) ) ;
x25519_hex [ 0 ] = ed25519_hex [ 0 ] = ' \0 ' ;
if ( x25519 & & x25519_len > = 32 ) for ( int i = 0 ; i < 32 ; i + + ) snprintf ( x25519_hex + i * 2 , 3 , " %02x " , x25519 [ i ] ) ;
@ -437,14 +457,13 @@ int chat_core_get_members_json(struct UTUN_INSTANCE* inst, const char* ch_id, ch
json_escape ( adm_tags ? adm_tags : " " , esc_tags , sizeof ( esc_tags ) ) ;
w + = snprintf ( w , ( size_t ) ( end - w ) ,
MEMBERS_APPEND (
" %s{ \" node_id \" : \" 0x%016llx \" , \" name \" : \" %s \" , \" online \" :%d, \" connected \" :%d, "
" \" adm_tags \" : \" %s \" , \" x25519 \" : \" %s \" , \" ed25519 \" : \" %s \" , \" addrs \" :[ " ,
sep , ( unsigned long long ) nid , esc_name , online , connected , esc_tags , x25519_hex , ed25519_hex ) ;
sep = " , " ;
/* addresses */
sqlite3_stmt * as = NULL ;
char asql [ 200 ] ; snprintf ( asql , sizeof ( asql ) ,
" SELECT family, protocol, address, port, rtt FROM node_addresses WHERE node_id=? ORDER BY family, protocol " ) ;
if ( sqlite3_prepare_v2 ( cc - > db , asql , - 1 , & as , NULL ) = = SQLITE_OK ) {
@ -465,20 +484,24 @@ int chat_core_get_members_json(struct UTUN_INSTANCE* inst, const char* ch_id, ch
addr [ 0 ] , addr [ 1 ] , addr [ 2 ] , addr [ 3 ] , addr [ 4 ] , addr [ 5 ] , addr [ 6 ] , addr [ 7 ] ,
addr [ 8 ] , addr [ 9 ] , addr [ 10 ] , addr [ 11 ] , addr [ 12 ] , addr [ 13 ] , addr [ 14 ] , addr [ 15 ] ) ;
else { snprintf ( ip_str , sizeof ( ip_str ) , " ? " ) ; }
w + = snprintf ( w , ( size_t ) ( end - w ) ,
MEMBERS_APPEND (
" %s{ \" ip \" : \" %s \" , \" port \" :%d, \" proto \" : \" %s \" , \" rtt \" :%d} " ,
asep2 , ip_str , port , proto = = 1 ? " UDP " : proto = = 2 ? " TCP " : " ? " , rtt ) ;
asep2 = " , " ;
}
sqlite3_finalize ( as ) ;
sqlite3_finalize ( as ) ; as = NULL ;
}
w + = snprintf ( w , ( size_t ) ( end - w ) , " ]} " ) ;
MEMBERS_APPEND ( " ]} " ) ;
}
MEMBERS_APPEND ( " ] " ) ;
sqlite3_finalize ( st ) ;
* w + + = ' ] ' ;
* w = ' \0 ' ;
* out_len = ( size_t ) ( w - buf ) ;
return 0 ;
overflow :
sqlite3_finalize ( as ) ; sqlite3_finalize ( st ) ; buf [ 0 ] = 0 ;
DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: members JSON exceeds output buffer bytes=%zu " , CC_ID , buf_size ) ;
return - 1 ;
# undef MEMBERS_APPEND
}
int chat_core_get_node_name ( struct UTUN_INSTANCE * inst , uint64_t node_id , char * out , size_t sz ) {
@ -500,7 +523,7 @@ int chat_core_get_node_name(struct UTUN_INSTANCE* inst, uint64_t node_id, char*
struct chat_member_tags_key {
char key [ CHAT_MEMBER_TAGS_KEY_SZ ] ;
char val [ CHAT_MEMBER_TAGS_VAL_SZ ] ;
char * val ;
char * raw ; /* malloc'd: serialized append-only string (admin/moder) or NULL (= val is raw) */
} ;
@ -530,20 +553,19 @@ static int tags_is_append_key(const char* key) {
return strcmp ( key , " admin " ) = = 0 | | strcmp ( key , " moder " ) = = 0 ;
}
static void tags_set_effective_val ( struct chat_member_tags_key * k , const char * raw ) {
uint64_t ts = 0 ;
const char * eff = tags_append_effective ( raw , & ts ) ;
snprintf ( k - > val , CHAT_MEMBER_TAGS_VAL_SZ , " %s " , eff ) ;
( void ) ts ;
}
static int tags_json_cb ( const char * key , const char * json_val , void * arg ) {
struct chat_member_tags * t = ( struct chat_member_tags * ) arg ;
if ( t - > key_count > = CHAT_MEMBER_TAGS_MAX_KEYS ) return - 1 ;
if ( t - > key_count > = CHAT_MEMBER_TAGS_MAX_KEYS | | strlen ( key ) > = CHAT_MEMBER_TAGS_KEY_SZ ) {
DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags JSON key limit exceeded " , CC_ID ) ; return - 1 ;
}
struct chat_member_tags_key * k = & t - > keys [ t - > key_count ] ;
snprintf ( k - > key , CHAT_MEMBER_TAGS_KEY_SZ , " %s " , key ) ;
snprintf ( k - > val , CHAT_MEMBER_TAGS_VAL_SZ , " %s " , json_val ) ;
k - > val = u_strdup ( tags_is_append_key ( key ) ? tags_append_effective ( json_val , NULL ) : json_val ) ;
k - > raw = tags_is_append_key ( key ) ? u_strdup ( json_val ) : NULL ;
if ( ! k - > val | | ( tags_is_append_key ( key ) & & ! k - > raw ) ) {
u_free ( k - > val ) ; u_free ( k - > raw ) ; memset ( k , 0 , sizeof ( * k ) ) ;
DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags JSON allocation failed " , CC_ID ) ; return - 1 ;
}
t - > key_count + + ;
return 0 ;
}
@ -563,7 +585,9 @@ struct chat_member_tags* chat_member_tags_load(struct UTUN_INSTANCE* inst, const
sqlite3_bind_int64 ( st , 1 , ( sqlite3_int64 ) node_id ) ;
if ( sqlite3_step ( st ) = = SQLITE_ROW ) {
const char * tags = ( const char * ) sqlite3_column_text ( st , 0 ) ;
if ( tags & & tags [ 0 ] ) chat_member_tags_replace ( t , tags ) ;
if ( tags & & tags [ 0 ] & & chat_member_tags_replace ( t , tags ) < 0 ) {
sqlite3_finalize ( st ) ; chat_member_tags_free ( t ) ; return NULL ;
}
}
sqlite3_finalize ( st ) ;
}
@ -575,22 +599,24 @@ struct chat_member_tags* chat_member_tags_load(struct UTUN_INSTANCE* inst, const
int chat_member_tags_set ( struct chat_member_tags * t , const char * key , const char * val ) {
if ( ! t | | ! key ) return - 1 ;
if ( strlen ( key ) > = CHAT_MEMBER_TAGS_KEY_SZ ) { DEBUG_WARN ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_set — key too long " , CC_ID ) ; return - 1 ; }
if ( val & & strlen ( val ) > = CHAT_MEMBER_TAGS_VAL_SZ ) { DEBUG_WARN ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_set — val too long " , CC_ID ) ; return - 1 ; }
if ( val & & strlen ( val ) > MS_ADM_TAGS_MAX ) { DEBUG_WARN ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_set — val too long " , CC_ID ) ; return - 1 ; }
int idx = tags_find ( t , key ) ;
if ( ! val ) {
if ( idx > = 0 ) { u_free ( t - > keys [ idx ] . raw ) ; t - > keys [ idx ] = t - > keys [ - - t - > key_count ] ; }
if ( idx > = 0 ) { u_free ( t - > keys [ idx ] . raw ) ; u_free ( t - > keys [ idx ] . val ) ; t - > keys [ idx ] = t - > keys [ - - t - > key_count ] ; }
return 0 ;
}
char * copy = u_strdup ( val ) ;
if ( ! copy ) { DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_set allocation failed " , CC_ID ) ; return - 1 ; }
if ( idx > = 0 ) {
snprintf ( t - > keys [ idx ] . val , CHAT_MEMBER_TAGS_VAL_SZ , " %s " , val ) ;
u_free ( t - > keys [ idx ] . val ) ; t - > keys [ idx ] . val = copy ;
} else if ( t - > key_count < CHAT_MEMBER_TAGS_MAX_KEYS ) {
struct chat_member_tags_key * k = & t - > keys [ t - > key_count + + ] ;
snprintf ( k - > key , CHAT_MEMBER_TAGS_KEY_SZ , " %s " , key ) ;
snprintf ( k - > val , CHAT_MEMBER_TAGS_VAL_SZ , " %s " , val ) ;
k - > val = copy ;
k - > raw = NULL ;
} else {
DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_set — max keys reached " , CC_ID ) ; return - 1 ;
u_free ( copy ) ; DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_set — max keys reached " , CC_ID ) ; return - 1 ;
}
return 0 ;
}
@ -602,11 +628,17 @@ const char* chat_member_tags_get(struct chat_member_tags* t, const char* key) {
}
int chat_member_tags_replace ( struct chat_member_tags * t , const char * json ) {
if ( ! t | | ! json ) return - 1 ;
for ( int i = 0 ; i < t - > key_count ; i + + ) u_free ( t - > keys [ i ] . raw ) ;
t - > key_count = 0 ;
int rc = json_flat_parse ( json , tags_json_cb , t ) ;
if ( rc ! = 0 ) { DEBUG_WARN ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_replace — invalid JSON " , CC_ID ) ; return - 1 ; }
if ( ! t | | ! json | | strlen ( json ) > MS_ADM_TAGS_MAX ) {
DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_replace invalid input or size " , CC_ID ) ; return - 1 ;
}
struct chat_member_tags parsed = { 0 } ;
int rc = json_flat_parse ( json , tags_json_cb , & parsed ) ;
if ( rc ! = 0 ) {
for ( int i = 0 ; i < parsed . key_count ; i + + ) { u_free ( parsed . keys [ i ] . raw ) ; u_free ( parsed . keys [ i ] . val ) ; }
DEBUG_WARN ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_replace — invalid JSON " , CC_ID ) ; return - 1 ;
}
for ( int i = 0 ; i < t - > key_count ; i + + ) { u_free ( t - > keys [ i ] . raw ) ; u_free ( t - > keys [ i ] . val ) ; }
t - > key_count = parsed . key_count ; memcpy ( t - > keys , parsed . keys , sizeof ( t - > keys ) ) ;
DEBUG_DEBUG ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_replace ch=%s nid=0x%016llx keys=%d " ,
CC_ID , t - > ch_id , ( unsigned long long ) t - > node_id , t - > key_count ) ;
return 0 ;
@ -617,8 +649,14 @@ int chat_member_tags_commit(struct UTUN_INSTANCE* inst, struct chat_member_tags*
if ( ! t | | ! cc | | ! cc - > initialized | | ! cc - > inst | | ! cc - > db ) {
DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_commit — invalid state " , CC_ID ) ; return - 1 ;
}
char json [ 4096 ] ; int off = 0 , ver = 1 ;
# define TAGS_ADVANCE(n) do { if (off < (int)sizeof(json) - 10) off += (int)(n); else off = (int)sizeof(json); } while(0)
char json [ MS_ADM_TAGS_MAX + 1 ] ; int off = 0 , ver = 1 ;
# define TAGS_ADVANCE(expr) do { \
int written = ( expr ) ; \
if ( written < 0 | | ( size_t ) written > = sizeof ( json ) - ( size_t ) off ) { \
DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_commit JSON exceeds %d bytes " , CC_ID , MS_ADM_TAGS_MAX ) ; return - 1 ; \
} \
off + = written ; \
} while ( 0 )
int vi = tags_find ( t , " ver " ) ;
if ( vi > = 0 ) ver = atoi ( t - > keys [ vi ] . val ) + 1 ;
TAGS_ADVANCE ( snprintf ( json , sizeof ( json ) , " { \" ver \" : \" %d \" " , ver ) ) ;
@ -647,7 +685,13 @@ int chat_member_tags_commit(struct UTUN_INSTANCE* inst, struct chat_member_tags*
} else {
out_val = k - > val ;
}
TAGS_ADVANCE ( snprintf ( json + off , sizeof ( json ) - ( size_t ) off , " , \" %s \" : \" %s \" " , k - > key , out_val ) ) ;
char escaped_key [ 6 * CHAT_MEMBER_TAGS_KEY_SZ + 1 ] , escaped_value [ 6 * MS_ADM_TAGS_MAX + 1 ] ;
if ( strlen ( out_val ) > MS_ADM_TAGS_MAX ) {
DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_commit value exceeds limit " , CC_ID ) ; return - 1 ;
}
json_escape ( k - > key , escaped_key , sizeof ( escaped_key ) ) ;
json_escape ( out_val , escaped_value , sizeof ( escaped_value ) ) ;
TAGS_ADVANCE ( snprintf ( json + off , sizeof ( json ) - ( size_t ) off , " , \" %s \" : \" %s \" " , escaped_key , escaped_value ) ) ;
}
TAGS_ADVANCE ( snprintf ( json + off , sizeof ( json ) - ( size_t ) off , " } " ) ) ;
# undef TAGS_ADVANCE
@ -656,10 +700,9 @@ int chat_member_tags_commit(struct UTUN_INSTANCE* inst, struct chat_member_tags*
if ( topo_node_sqlite_channel_get_priv ( cc - > db , t - > ch_id , ch_ed_priv ) ! = 0 ) {
DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_commit — no channel privkey for ch=%s (not owner?) " , CC_ID , t - > ch_id ) ; return - 1 ;
}
uint8_t sign_msg [ 8192 ] ; size_t mlen = ( size_t ) off ;
if ( mlen + 8 > sizeof ( sign_msg ) ) { DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_commit — json too big " , CC_ID ) ; return - 1 ; }
memcpy ( sign_msg , json , mlen ) ;
memcpy ( sign_msg + mlen , & t - > node_id , 8 ) ; mlen + = 8 ;
uint8_t sign_msg [ MS_ADM_TAGS_MAX + 8 ] ;
int mlen = member_sync_build_owner_msg ( t - > node_id , json , sign_msg , sizeof ( sign_msg ) ) ;
if ( mlen < 0 ) return - 1 ;
uint8_t sig [ 64 ] ;
if ( sc_ed25519_sign ( ch_ed_priv , sign_msg , mlen , sig ) ! = SC_OK ) {
DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_commit — sign failed " , CC_ID ) ; return - 1 ;
@ -702,14 +745,14 @@ int chat_member_tags_commit(struct UTUN_INSTANCE* inst, struct chat_member_tags*
sqlite3_finalize ( st ) ;
if ( rc < 0 ) { DEBUG_ERROR ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_commit — member_sync_put failed rc=%d " , CC_ID , rc ) ; return - 1 ; }
DEBUG_INFO ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_commit ch=%s nid=0x%016llx ver=%d keys=%d json=%s " ,
CC_ID , t - > ch_id , ( unsigned long long ) t - > node_id , ver , t - > key_count , json ) ;
DEBUG_INFO ( DEBUG_CATEGORY_CHAT_SYNC , " %s: tags_commit ch=%s nid=0x%016llx ver=%d keys=%d bytes=%d " ,
CC_ID , t - > ch_id , ( unsigned long long ) t - > node_id , ver , t - > key_count , off ) ;
return 0 ;
}
void chat_member_tags_free ( struct chat_member_tags * t ) {
if ( ! t ) return ;
for ( int i = 0 ; i < t - > key_count ; i + + ) u_free ( t - > keys [ i ] . raw ) ;
for ( int i = 0 ; i < t - > key_count ; i + + ) { u_free ( t - > keys [ i ] . raw ) ; u_free ( t - > keys [ i ] . val ) ; }
u_free ( t ) ;
}