|
|
|
@ -152,14 +152,15 @@ void utun_bridge_destroy(void) { |
|
|
|
bridge_log(BLEV_INFO, "bridge destroy"); |
|
|
|
bridge_log(BLEV_INFO, "bridge destroy"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void utun_bridge_start(const char* config_text) { |
|
|
|
int utun_bridge_start(const char* config_text) { |
|
|
|
bridge_log(BLEV_INFO, "bridge start: launching utun instance"); |
|
|
|
bridge_log(BLEV_INFO, "bridge start: launching utun instance"); |
|
|
|
int rc = instance_lite_start(config_text); |
|
|
|
int rc = instance_lite_start(config_text); |
|
|
|
if (rc != 0) { |
|
|
|
if (rc != 0) { |
|
|
|
bridge_log(BLEV_ERROR, "bridge start: instance_lite_start failed rc=%d", rc); |
|
|
|
bridge_log(BLEV_ERROR, "bridge start: instance_lite_start failed rc=%d", rc); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
bridge_log(BLEV_INFO, "bridge start: instance running, node_id ready"); |
|
|
|
bridge_log(BLEV_INFO, "bridge start: instance thread created"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return rc; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void utun_bridge_poll(void) { |
|
|
|
void utun_bridge_poll(void) { |
|
|
|
@ -210,21 +211,174 @@ void utun_bridge_connect_channel(const char* channel_id) { |
|
|
|
uasync_post(ua, chat_core_connect_channel_trampoline, cid); |
|
|
|
uasync_post(ua, chat_core_connect_channel_trampoline, cid); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "chat/chat_core_priv.h" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char* json_escape_alloc(const char* str) { |
|
|
|
|
|
|
|
if (!str) return u_strdup(""); |
|
|
|
|
|
|
|
size_t len = strlen(str), cap = len * 2 + 8; |
|
|
|
|
|
|
|
char* out = u_malloc(cap); |
|
|
|
|
|
|
|
if (!out) return u_strdup(""); |
|
|
|
|
|
|
|
size_t pos = 0; |
|
|
|
|
|
|
|
for (size_t i = 0; i < len && pos < cap - 2; i++) { |
|
|
|
|
|
|
|
char c = str[i]; |
|
|
|
|
|
|
|
if (c == '"' || c == '\\') { out[pos++] = '\\'; out[pos++] = c; } |
|
|
|
|
|
|
|
else if (c == '\n') { out[pos++] = '\\'; out[pos++] = 'n'; } |
|
|
|
|
|
|
|
else if (c == '\r') { out[pos++] = '\\'; out[pos++] = 'r'; } |
|
|
|
|
|
|
|
else if (c == '\t') { out[pos++] = '\\'; out[pos++] = 't'; } |
|
|
|
|
|
|
|
else if ((unsigned char)c < 32) { pos += snprintf(out + pos, cap - pos, "\\u%04x", (unsigned char)c); } |
|
|
|
|
|
|
|
else out[pos++] = c; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
out[pos] = '\0'; |
|
|
|
|
|
|
|
return out; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
char* utun_bridge_get_channels_json(void) { |
|
|
|
char* utun_bridge_get_channels_json(void) { |
|
|
|
return strdup("[]"); |
|
|
|
sqlite3* db = chat_core_get_db(); |
|
|
|
|
|
|
|
if (!chat_core_is_initialized() || !db) { |
|
|
|
|
|
|
|
bridge_log(BLEV_WARN, "get_channels: not initialized db=%p initialized=%d", |
|
|
|
|
|
|
|
(void*)db, chat_core_is_initialized()); |
|
|
|
|
|
|
|
return strdup("[]"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
size_t cap = 4096, pos = 1; |
|
|
|
|
|
|
|
char* json = u_malloc(cap); |
|
|
|
|
|
|
|
if (!json) return strdup("[]"); |
|
|
|
|
|
|
|
json[0] = '['; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sqlite3_stmt* st = NULL; |
|
|
|
|
|
|
|
int rc = sqlite3_prepare_v2(db, "SELECT channel_id, name, last_msg_at FROM channels ORDER BY last_msg_at DESC, name ASC", -1, &st, NULL); |
|
|
|
|
|
|
|
if (rc != SQLITE_OK) { u_free(json); return strdup("[]"); } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int first = 1; |
|
|
|
|
|
|
|
while (sqlite3_step(st) == SQLITE_ROW) { |
|
|
|
|
|
|
|
const char* ch_id = (const char*)sqlite3_column_text(st, 0); |
|
|
|
|
|
|
|
const char* name = (const char*)sqlite3_column_text(st, 1); |
|
|
|
|
|
|
|
int64_t last_msg_at = sqlite3_column_int64(st, 2); |
|
|
|
|
|
|
|
if (!ch_id) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char tbl_msg[80], tbl_peers[80], last_msg[128]; |
|
|
|
|
|
|
|
msg_table_name(ch_id, tbl_msg, sizeof(tbl_msg)); |
|
|
|
|
|
|
|
peers_table_name(ch_id, tbl_peers, sizeof(tbl_peers)); |
|
|
|
|
|
|
|
last_msg[0] = '\0'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sqlite3_stmt* lm = NULL; |
|
|
|
|
|
|
|
char sql[256]; |
|
|
|
|
|
|
|
snprintf(sql, sizeof(sql), "SELECT node_id, data FROM \"%s\" ORDER BY id DESC LIMIT 1", tbl_msg); |
|
|
|
|
|
|
|
if (sqlite3_prepare_v2(db, sql, -1, &lm, NULL) == SQLITE_OK) { |
|
|
|
|
|
|
|
if (sqlite3_step(lm) == SQLITE_ROW) { |
|
|
|
|
|
|
|
int64_t author = sqlite3_column_int64(lm, 0); |
|
|
|
|
|
|
|
const uint8_t* dptr = sqlite3_column_blob(lm, 1); |
|
|
|
|
|
|
|
int dlen = sqlite3_column_bytes(lm, 1); |
|
|
|
|
|
|
|
char txt[128] = ""; |
|
|
|
|
|
|
|
if (dptr && dlen > 0) { |
|
|
|
|
|
|
|
const char* dp = (const char*)dptr; |
|
|
|
|
|
|
|
const char* ds = strstr(dp, "\"d\":\""); |
|
|
|
|
|
|
|
if (ds) { ds += 5; char* de = strchr((char*)ds, '"'); if (de) { size_t tl = (size_t)(de - ds); if (tl > 120) tl = 120; memcpy(txt, ds, tl); txt[tl] = '\0'; } } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
snprintf(last_msg, sizeof(last_msg), "0x%016llx: %s", (unsigned long long)author, txt); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sqlite3_finalize(lm); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int online = 0; |
|
|
|
|
|
|
|
sqlite3_stmt* pc = NULL; |
|
|
|
|
|
|
|
snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM \"%s\"", tbl_peers); |
|
|
|
|
|
|
|
if (sqlite3_prepare_v2(db, sql, -1, &pc, NULL) == SQLITE_OK) { |
|
|
|
|
|
|
|
if (sqlite3_step(pc) == SQLITE_ROW) online = sqlite3_column_int(pc, 0); |
|
|
|
|
|
|
|
sqlite3_finalize(pc); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char* esc_name = json_escape_alloc(name ? name : ch_id); |
|
|
|
|
|
|
|
char* esc_msg = json_escape_alloc(last_msg); |
|
|
|
|
|
|
|
const char* sep = first ? "" : ","; |
|
|
|
|
|
|
|
first = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
size_t needed = snprintf(NULL, 0, "%s{\"id\":\"%s\",\"name\":\"%s\",\"lastMsgAt\":%lld,\"lastMsg\":\"%s\",\"peersOnline\":%d}", |
|
|
|
|
|
|
|
sep, ch_id, esc_name, (long long)last_msg_at, esc_msg, online); |
|
|
|
|
|
|
|
while (pos + needed + 2 > cap) { cap *= 2; char* tmp = u_realloc(json, cap); if (!tmp) break; json = tmp; } |
|
|
|
|
|
|
|
if (pos + needed + 2 <= cap) |
|
|
|
|
|
|
|
pos += (size_t)snprintf(json + pos, cap - pos, "%s{\"id\":\"%s\",\"name\":\"%s\",\"lastMsgAt\":%lld,\"lastMsg\":\"%s\",\"peersOnline\":%d}", |
|
|
|
|
|
|
|
sep, ch_id, esc_name, (long long)last_msg_at, esc_msg, online); |
|
|
|
|
|
|
|
u_free(esc_msg); |
|
|
|
|
|
|
|
u_free(esc_name); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sqlite3_finalize(st); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
json[pos++] = ']'; json[pos] = '\0'; |
|
|
|
|
|
|
|
return json; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
char* utun_bridge_get_messages_json(const char* channel_id, int limit) { |
|
|
|
char* utun_bridge_get_messages_json(const char* channel_id, int limit) { |
|
|
|
(void)limit; |
|
|
|
sqlite3* db = chat_core_get_db(); |
|
|
|
char buf[128]; |
|
|
|
if (!chat_core_is_initialized() || !db || !channel_id || !channel_id[0]) return strdup("[]"); |
|
|
|
snprintf(buf, sizeof(buf), "{\"channel\":\"%s\",\"messages\":[]}", channel_id ? channel_id : ""); |
|
|
|
uint64_t my_id = g_cc.my_node_id; |
|
|
|
return strdup(buf); |
|
|
|
|
|
|
|
|
|
|
|
char tbl_msg[80]; |
|
|
|
|
|
|
|
msg_table_name(channel_id, tbl_msg, sizeof(tbl_msg)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char sql[256]; |
|
|
|
|
|
|
|
snprintf(sql, sizeof(sql), "SELECT id, node_id, data, timestamp FROM \"%s\" ORDER BY timestamp ASC LIMIT %d", tbl_msg, limit); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sqlite3_stmt* st = NULL; |
|
|
|
|
|
|
|
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return strdup("[]"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
size_t cap = 4096, pos = 1; |
|
|
|
|
|
|
|
char* json = u_malloc(cap); |
|
|
|
|
|
|
|
if (!json) { sqlite3_finalize(st); return strdup("[]"); } |
|
|
|
|
|
|
|
json[0] = '['; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int first = 1; |
|
|
|
|
|
|
|
while (sqlite3_step(st) == SQLITE_ROW) { |
|
|
|
|
|
|
|
int64_t msg_id = sqlite3_column_int64(st, 0); |
|
|
|
|
|
|
|
int64_t node_id = sqlite3_column_int64(st, 1); |
|
|
|
|
|
|
|
int64_t ts = sqlite3_column_int64(st, 3); |
|
|
|
|
|
|
|
const uint8_t* dptr = sqlite3_column_blob(st, 2); |
|
|
|
|
|
|
|
int dlen = sqlite3_column_bytes(st, 2); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char txt[256] = ""; |
|
|
|
|
|
|
|
if (dptr && dlen > 0) { |
|
|
|
|
|
|
|
const char* ds = strstr((const char*)dptr, "\"d\":\""); |
|
|
|
|
|
|
|
if (ds) { ds += 5; char* de = strchr((char*)ds, '"'); if (de) { size_t tl = (size_t)(de - ds); if (tl > 250) tl = 250; memcpy(txt, ds, tl); txt[tl] = '\0'; } } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
char* esc_txt = json_escape_alloc(txt); |
|
|
|
|
|
|
|
int is_out = (node_id == (int64_t)my_id) ? 1 : 0; |
|
|
|
|
|
|
|
const char* sep = first ? "" : ","; |
|
|
|
|
|
|
|
first = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
size_t needed = snprintf(NULL, 0, "%s{\"id\":%lld,\"author\":\"0x%016llx\",\"text\":\"%s\",\"ts\":%lld,\"isOutgoing\":%s}", |
|
|
|
|
|
|
|
sep, (long long)msg_id, (unsigned long long)node_id, esc_txt, (long long)ts, is_out ? "true" : "false"); |
|
|
|
|
|
|
|
while (pos + needed + 2 > cap) { cap *= 2; char* tmp = u_realloc(json, cap); if (!tmp) break; json = tmp; } |
|
|
|
|
|
|
|
if (pos + needed + 2 <= cap) |
|
|
|
|
|
|
|
pos += (size_t)snprintf(json + pos, cap - pos, "%s{\"id\":%lld,\"author\":\"0x%016llx\",\"text\":\"%s\",\"ts\":%lld,\"isOutgoing\":%s}", |
|
|
|
|
|
|
|
sep, (long long)msg_id, (unsigned long long)node_id, esc_txt, (long long)ts, is_out ? "true" : "false"); |
|
|
|
|
|
|
|
u_free(esc_txt); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sqlite3_finalize(st); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
json[pos++] = ']'; json[pos] = '\0'; |
|
|
|
|
|
|
|
return json; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t utun_bridge_get_my_node_id(void) { |
|
|
|
|
|
|
|
return chat_core_is_initialized() ? g_cc.my_node_id : 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void utun_bridge_set_debug_level(const char* category, const char* level) { |
|
|
|
void utun_bridge_set_debug_level(const char* category, const char* level) { |
|
|
|
bridge_log(BLEV_INFO, "debug %s=%s", category ? category : "all", level ? level : "info"); |
|
|
|
bridge_log(BLEV_INFO, "debug %s=%s", category ? category : "all", level ? level : "info"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void utun_bridge_restart(const char* config_text) { |
|
|
|
|
|
|
|
bridge_log(BLEV_INFO, "bridge restart: posting to instance"); |
|
|
|
|
|
|
|
instance_lite_restart(config_text); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void utun_bridge_ping(void) { |
|
|
|
|
|
|
|
instance_lite_ping(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int utun_bridge_is_responsive(void) { |
|
|
|
|
|
|
|
return instance_lite_is_responsive(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* ──────────────────────────────────────────────────────────────────
|
|
|
|
/* ──────────────────────────────────────────────────────────────────
|
|
|
|
* JNI functions (compiled only for Android via NDK) |
|
|
|
* JNI functions (compiled only for Android via NDK) |
|
|
|
* ────────────────────────────────────────────────────────────────── */ |
|
|
|
* ────────────────────────────────────────────────────────────────── */ |
|
|
|
@ -346,10 +500,9 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeStart( |
|
|
|
/* Set up event forwarder (bridge config/log are set by nativeInit) */ |
|
|
|
/* Set up event forwarder (bridge config/log are set by nativeInit) */ |
|
|
|
instance_lite_set_event_handler(instance_event_handler); |
|
|
|
instance_lite_set_event_handler(instance_event_handler); |
|
|
|
|
|
|
|
|
|
|
|
utun_bridge_start(cfg); |
|
|
|
int rc = utun_bridge_start(cfg); |
|
|
|
(*env)->ReleaseStringUTFChars(env, configText, cfg); |
|
|
|
(*env)->ReleaseStringUTFChars(env, configText, cfg); |
|
|
|
|
|
|
|
return (rc >= 0) ? JNI_TRUE : JNI_FALSE; |
|
|
|
return instance_lite_is_running() ? JNI_TRUE : JNI_FALSE; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* ── nativeStop() ── */ |
|
|
|
/* ── nativeStop() ── */ |
|
|
|
@ -485,7 +638,7 @@ JNIEXPORT jstring JNICALL Java_com_utun_chat_data_NativeLib_nativeGetChannels( |
|
|
|
(void)thiz; |
|
|
|
(void)thiz; |
|
|
|
char* json = utun_bridge_get_channels_json(); |
|
|
|
char* json = utun_bridge_get_channels_json(); |
|
|
|
jstring result = (*env)->NewStringUTF(env, json); |
|
|
|
jstring result = (*env)->NewStringUTF(env, json); |
|
|
|
free(json); |
|
|
|
u_free(json); |
|
|
|
return result; |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -495,11 +648,40 @@ JNIEXPORT jstring JNICALL Java_com_utun_chat_data_NativeLib_nativeGetMessages( |
|
|
|
const char* ch = (*env)->GetStringUTFChars(env, channelId, NULL); |
|
|
|
const char* ch = (*env)->GetStringUTFChars(env, channelId, NULL); |
|
|
|
char* json = utun_bridge_get_messages_json(ch, (int)limit); |
|
|
|
char* json = utun_bridge_get_messages_json(ch, (int)limit); |
|
|
|
jstring result = (*env)->NewStringUTF(env, json); |
|
|
|
jstring result = (*env)->NewStringUTF(env, json); |
|
|
|
free(json); |
|
|
|
u_free(json); |
|
|
|
(*env)->ReleaseStringUTFChars(env, channelId, ch); |
|
|
|
(*env)->ReleaseStringUTFChars(env, channelId, ch); |
|
|
|
return result; |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeGetMyNodeId( |
|
|
|
|
|
|
|
JNIEnv* env, jobject thiz) { |
|
|
|
|
|
|
|
(void)env; (void)thiz; |
|
|
|
|
|
|
|
return (jlong)utun_bridge_get_my_node_id(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRestart( |
|
|
|
|
|
|
|
JNIEnv* env, jobject thiz, jstring configText) { |
|
|
|
|
|
|
|
(void)thiz; |
|
|
|
|
|
|
|
if (!configText) return JNI_FALSE; |
|
|
|
|
|
|
|
const char* cfg = (*env)->GetStringUTFChars(env, configText, NULL); |
|
|
|
|
|
|
|
if (!cfg) return JNI_FALSE; |
|
|
|
|
|
|
|
utun_bridge_restart(cfg); |
|
|
|
|
|
|
|
(*env)->ReleaseStringUTFChars(env, configText, cfg); |
|
|
|
|
|
|
|
return JNI_TRUE; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeIsResponsive( |
|
|
|
|
|
|
|
JNIEnv* env, jobject thiz) { |
|
|
|
|
|
|
|
(void)env; (void)thiz; |
|
|
|
|
|
|
|
return utun_bridge_is_responsive() ? JNI_TRUE : JNI_FALSE; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativePing( |
|
|
|
|
|
|
|
JNIEnv* env, jobject thiz) { |
|
|
|
|
|
|
|
(void)env; (void)thiz; |
|
|
|
|
|
|
|
utun_bridge_ping(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeConnectChannel( |
|
|
|
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeConnectChannel( |
|
|
|
JNIEnv* env, jobject thiz, jstring channelId) { |
|
|
|
JNIEnv* env, jobject thiz, jstring channelId) { |
|
|
|
(void)thiz; |
|
|
|
(void)thiz; |
|
|
|
|