From 86f0ac7625c5a8b807b2cd55d9caab83196ae2f5 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 11:57:50 +0100 Subject: [PATCH] Route remaining JNI producers through the core task owner --- tools/chatgui-android/doc/ARCHITECTURE.md | 11 +- .../jni_bridge/android_jni_bridge.c | 202 +++++++++--------- .../jni_bridge/android_jni_bridge.h | 4 +- 3 files changed, 105 insertions(+), 112 deletions(-) diff --git a/tools/chatgui-android/doc/ARCHITECTURE.md b/tools/chatgui-android/doc/ARCHITECTURE.md index e920d527c..3ef9e9960 100644 --- a/tools/chatgui-android/doc/ARCHITECTURE.md +++ b/tools/chatgui-android/doc/ARCHITECTURE.md @@ -66,11 +66,12 @@ UTUN-сервис включается отдельно через `[gui] utun_e существующий запуск; он не подтверждает готовность сервисов. Готовность проверяется по событию и `instance_lite_is_running()`. -Команды UI обычно копируют аргументы и ставят работу в uasync. При этом C JSON-getters -и некоторые настройки читают SQLite/живые структуры синхронно в вызывающем потоке; -C `utun_bridge_set_member_nick()` пишет локальный nickname напрямую. C setter chat_setting -также синхронный, тогда как JNI nativeSetChatSetting использует trampoline. -Нельзя считать весь bridge автоматически выполняющимся в потоке ядра. +Команды UI копируют аргументы и передают их владельцу worker. JNI JSON-getters и короткие проверки +читают SQLite/живые структуры через `instance_lite_call`; C getters и `utun_bridge_set_member_nick()` +исполняются только в потоке владельца. JNI nativeSetChatSetting использует отдельный trampoline. +Реестры команд освобождают также PCM/compressor и собственные cache-файлы при отмене до исполнения. +JNI строки преобразуются через обычный UTF-8 byte[] в обе стороны: имена, JSON и пути сохраняют emoji. +NUL во входной строке отклоняется, ошибки выделения возвращают NULL/отказ, а не усечённую строку. Заимствованные inst/ua не удерживают ресурс; они пригодны только в потоке владельца. `instance_lite_post` публикует команду под защитой времени жизни worker и заполняет её поле instance. Аргумент принадлежит callback, а при отмене — discard; отдельный реестр переживает удаление posted_task. diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index 0bf2231d2..58605a0b9 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -1351,13 +1351,19 @@ char* utun_bridge_get_local_sockets_json(void) { return json; } -void utun_bridge_on_network_change(void) { +static void bridge_network_change_run(void* unused) { + (void)unused; struct UTUN_INSTANCE* inst = instance_lite_get_instance(); if (!inst) return; auto_socket_on_network_change(inst); bridge_log(BLEV_DEBUG, "network change notified to auto_socket"); } +void utun_bridge_on_network_change(void) { + if (instance_lite_post(bridge_network_change_run, NULL, NULL, NULL)) + bridge_log(BLEV_DEBUG, "Network change notification rejected: core not ready"); +} + int utun_bridge_set_active(int active) { return instance_lite_set_active(active); } @@ -1739,12 +1745,13 @@ static __thread int g_log_callback_active; /* Callback не может реку static jobject g_event_callback = NULL; static jmethodID g_event_mid = NULL; -enum bridge_query_kind { QUERY_CHANNELS, QUERY_MESSAGES, QUERY_MEMBERS, QUERY_DETAIL, QUERY_LINKS, QUERY_NODE, +enum bridge_query_kind { QUERY_CHANNELS, QUERY_MESSAGES, QUERY_MEMBERS, QUERY_DETAIL, QUERY_LINKS, QUERY_NODE, QUERY_SET_NICK, QUERY_OWNER, QUERY_MEMBERSHIP, QUERY_SETTING, QUERY_SOUND, QUERY_RADIO_SUBSCRIBERS, QUERY_RADIO_START }; /* Строки/ID заимствованы до возврата instance_lite_call; JSON становится собственностью JNI caller. */ struct bridge_query { enum bridge_query_kind kind; const char* channel; + const char* value; const uint8_t* anchor; uint64_t node; int limit, exact; @@ -1760,6 +1767,7 @@ static void bridge_query_run(void* arg) { case QUERY_DETAIL: q->json = utun_bridge_get_member_detail_json(q->channel, q->node); break; case QUERY_LINKS: q->json = utun_bridge_get_member_links_json(q->node); break; case QUERY_NODE: q->number = (int64_t)utun_bridge_get_my_node_id(); break; + case QUERY_SET_NICK: utun_bridge_set_member_nick(q->channel, q->node, q->value); break; case QUERY_OWNER: q->number = utun_bridge_is_channel_owner(q->channel); break; case QUERY_MEMBERSHIP: q->number = topo_node_sqlite_member_in_channel(instance_lite_get_instance()->topo_sqlite_db, q->channel, q->node); break; @@ -1774,6 +1782,11 @@ static int bridge_query_call(struct bridge_query* query) { bridge_log(BLEV_WARN, "Core query unavailable/cancelled kind=%d", query->kind); return -1; } +static uint64_t bridge_my_node_id(void) { + struct bridge_query query = {.kind = QUERY_NODE}; + bridge_query_call(&query); return (uint64_t)query.number; +} + JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { (void)reserved; g_jvm = vm; @@ -2172,35 +2185,30 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeRequestInviteLink /* ── nativeSetEventCallback(eventCb) ── */ +static void local_devices_run(void* unused) { + (void)unused; chat_local_peers_trampoline(instance_lite_get_instance()); +} JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRequestLocalDevices(JNIEnv* env, jobject thiz) { (void)env; (void)thiz; - struct UASYNC* ua = instance_lite_get_uasync(); - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - if (!inst || !uasync_is_running(ua)) { - DEBUG_WARN(DEBUG_CATEGORY_DISCOVERY, "device list requested while core is stopped"); return JNI_FALSE; - } - uasync_post(ua, chat_local_peers_trampoline, inst); - return JNI_TRUE; + return instance_lite_post(local_devices_run, NULL, NULL, NULL) == 0 ? JNI_TRUE : JNI_FALSE; } JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeSendLocalInvite( JNIEnv* env, jobject thiz, jlong peer, jlong request, jbyteArray link) { (void)thiz; - struct UASYNC* ua = instance_lite_get_uasync(); - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); jsize len = link ? (*env)->GetArrayLength(env, link) : 0; - if (!inst || !uasync_is_running(ua) || !peer || !request || len <= 0 || len > LOCAL_TRANSPORT_DATA_MAX) { + if (!instance_lite_is_running() || !peer || !request || len <= 0 || len > LOCAL_TRANSPORT_DATA_MAX) { DEBUG_WARN(DEBUG_CATEGORY_DISCOVERY, "invalid/stopped local send request bytes=%d", (int)len); return JNI_FALSE; } struct chat_local_send_request* req = u_calloc(1, sizeof(*req)); if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_DISCOVERY, "local send request allocation failed"); return JNI_FALSE; } - req->inst = inst; req->peer_id = (uint64_t)peer; req->request_id = (uint64_t)request; + req->peer_id = (uint64_t)peer; req->request_id = (uint64_t)request; (*env)->GetByteArrayRegion(env, link, 0, len, (jbyte*)req->link); if ((*env)->ExceptionCheck(env)) { DEBUG_ERROR(DEBUG_CATEGORY_DISCOVERY, "copying local invitation from JNI failed"); u_free(req); return JNI_FALSE; } - uasync_post(ua, chat_local_send_trampoline, req); - return JNI_TRUE; + int posted = bridge_post_request(chat_local_send_trampoline, req, &req->inst); + return posted == 0 ? JNI_TRUE : JNI_FALSE; } JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetEventCallback( @@ -2266,30 +2274,29 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeSendMessage( JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeEditMessage( JNIEnv* env, jobject thiz, jstring channelId, jbyteArray messageId, jlong version, jbyteArray text, jlong requestId) { (void)thiz; - struct UASYNC* ua = instance_lite_get_uasync(); uint8_t id[32]; - if (!uasync_is_running(ua) || !text || !channelId || version <= 0 || !messageId || + uint8_t id[32]; + if (!instance_lite_is_running() || !text || !channelId || version <= 0 || !messageId || (*env)->GetArrayLength(env, messageId) != 32 || jni_message_id(env, messageId, id) < 0) { bridge_log(BLEV_ERROR, "edit: unavailable core/invalid target"); return JNI_FALSE; } jsize length = (*env)->GetArrayLength(env, text); if (length <= 0 || length > DB_SYNC_MESSAGE_MAX) { bridge_log(BLEV_ERROR, "edit: invalid bytes=%d", length); return JNI_FALSE; } struct chat_msg_submit* req = u_calloc(1, sizeof(*req) + length); - struct posted_task* post = u_calloc(1, sizeof(*post)); - if (!req || !post) { bridge_log(BLEV_ERROR, "edit: allocation failed"); u_free(req); u_free(post); return JNI_FALSE; } - req->inst = instance_lite_get_instance(); req->data = (uint8_t*)(req + 1); req->data_len = length; + if (!req) { bridge_log(BLEV_ERROR, "edit: allocation failed"); u_free(req); return JNI_FALSE; } + req->data = (uint8_t*)(req + 1); req->data_len = length; req->expected_ver = (uint64_t)version; req->request_id = (uint64_t)requestId; memcpy(req->message_id, id, 32); (*env)->GetByteArrayRegion(env, text, 0, length, (jbyte*)req->data); const char* channel = jni_utf8(env, channelId); if (!channel || (*env)->ExceptionCheck(env) || strlen(channel) >= sizeof(req->channel_id)) { bridge_log(BLEV_ERROR, "edit: JNI copy failed"); if (channel) u_free((void*)channel); - u_free(req); u_free(post); return JNI_FALSE; + u_free(req); return JNI_FALSE; } snprintf(req->channel_id, sizeof(req->channel_id), "%s", channel); u_free((void*)channel); - post->callback = chat_core_submit_trampoline; post->arg = req; uasync_post_reserved(ua, post); + int posted = bridge_post_request(chat_core_submit_trampoline, req, &req->inst); bridge_log(BLEV_INFO, "edit queued ver=%llu request=%llu", (unsigned long long)version, (unsigned long long)requestId); - return JNI_TRUE; + return posted == 0 ? JNI_TRUE : JNI_FALSE; } JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeCreateChannel( @@ -2343,9 +2350,7 @@ JNIEXPORT jstring JNICALL Java_com_utun_chat_data_NativeLib_nativeGetMessages( JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeFeedbackCommand( JNIEnv* env, jobject thiz, jbyteArray json, jlong request_id) { (void)thiz; - struct UASYNC* ua = instance_lite_get_uasync(); - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - if (!json || !inst || !uasync_is_running(ua)) { + if (!json || !instance_lite_is_running()) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "bridge: feedback core unavailable"); return JNI_FALSE; } jsize len = (*env)->GetArrayLength(env, json); @@ -2354,10 +2359,10 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeFeedbackComma } struct chat_feedback_request* req = u_malloc(sizeof(*req) + len + 1); if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "bridge: feedback allocation failed"); return JNI_FALSE; } - req->inst = inst; req->request_id = (uint64_t)request_id; + req->request_id = (uint64_t)request_id; (*env)->GetByteArrayRegion(env, json, 0, len, (jbyte*)(req + 1)); if ((*env)->ExceptionCheck(env)) { u_free(req); DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "bridge: feedback JNI copy failed"); return JNI_FALSE; } - ((char*)(req + 1))[len] = 0; uasync_post(ua, chat_feedback_command_trampoline, req); return JNI_TRUE; + ((char*)(req + 1))[len] = 0; int posted = bridge_post_request(chat_feedback_command_trampoline, req, &req->inst); return posted == 0 ? JNI_TRUE : JNI_FALSE; } JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeDmStart( @@ -2392,31 +2397,27 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeDmSend( JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeDmDelete( JNIEnv* env, jobject thiz, jstring conversation, jlong generation, jlong request) { (void)thiz; - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua) || generation <= 0 || request <= 0) { + if (!instance_lite_is_running() || generation <= 0 || request <= 0) { bridge_log(BLEV_WARN, "DM delete: stopped core or invalid generation/request"); return JNI_FALSE; } const char* conv = jni_utf8(env, conversation); struct dm_delete_req* req = u_calloc(1, sizeof(*req)); - struct posted_task* post = u_calloc(1, sizeof(*post)); - if (!conv || !req || !post || !conv[0] || strlen(conv) >= sizeof(req->conv_id) || strspn(conv, "0123456789") != strlen(conv)) { + if (!conv || !req || !conv[0] || strlen(conv) >= sizeof(req->conv_id) || strspn(conv, "0123456789") != strlen(conv)) { bridge_log(BLEV_ERROR, "DM delete: invalid conversation or allocation failed"); if (conv) u_free((void*)conv); - u_free(req); u_free(post); return JNI_FALSE; + u_free(req); return JNI_FALSE; } - req->inst = instance_lite_get_instance(); req->generation = (uint64_t)generation; req->request_id = (uint64_t)request; + req->generation = (uint64_t)generation; req->request_id = (uint64_t)request; snprintf(req->conv_id, sizeof(req->conv_id), "%s", conv); u_free((void*)conv); - post->callback = dm_delete_trampoline; post->arg = req; uasync_post_reserved(ua, post); - return JNI_TRUE; + int posted = bridge_post_request(dm_delete_trampoline, req, &req->inst); + return posted == 0 ? JNI_TRUE : JNI_FALSE; } JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeDmInvite( JNIEnv* env, jobject thiz, jlong request_id, jlong peer, jstring source, jlong channel, jstring name, jboolean cancel) { (void)thiz; - struct UASYNC* ua = instance_lite_get_uasync(); - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - if (!uasync_is_running(ua) || !inst) { bridge_log(BLEV_ERROR, "invite PM: core unavailable"); return JNI_FALSE; } + if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "invite PM: core unavailable"); return JNI_FALSE; } struct dm_invite_req* req = u_calloc(1, sizeof(*req)); if (!req) { bridge_log(BLEV_ERROR, "invite PM: request allocation failed"); return JNI_FALSE; } const char* src = jni_utf8(env, source); @@ -2425,13 +2426,13 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeDmInvite( if (src) u_free((void*)src); bridge_log(BLEV_ERROR, "invite PM: string conversion failed"); u_free(req); return JNI_FALSE; } - req->inst = inst; req->request_id = (uint64_t)request_id; req->peer_node_id = (uint64_t)peer; + req->request_id = (uint64_t)request_id; req->peer_node_id = (uint64_t)peer; req->channel_id = (uint64_t)channel; req->cancel = cancel ? 1 : 0; snprintf(req->source_ch_id, sizeof(req->source_ch_id), "%s", src); snprintf(req->peer_name, sizeof(req->peer_name), "%s", nm); u_free((void*)src); u_free((void*)nm); - uasync_post(ua, dm_invite_trampoline, req); - return JNI_TRUE; + int posted = bridge_post_request(dm_invite_trampoline, req, &req->inst); + return posted == 0 ? JNI_TRUE : JNI_FALSE; } /* Чтение членства для выбора групп и открытия уже присоединённого канала. */ @@ -2456,15 +2457,14 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeDmRequestConversa JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeDmRequestCallRoute( JNIEnv* env, jobject thiz, jstring convId) { (void)thiz; - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) { bridge_log(BLEV_WARN, "PM call route: core stopped"); return; } + if (!instance_lite_is_running()) { bridge_log(BLEV_WARN, "PM call route: core stopped"); return; } const char* conv = jni_utf8(env, convId); if (!conv) return; struct dm_messages_req* req = u_calloc(1, sizeof(*req)); if (req) { - req->inst = instance_lite_get_instance(); + snprintf(req->conv_id, sizeof(req->conv_id), "%s", conv); - uasync_post(ua, dm_call_route_trampoline, req); + bridge_post_request(dm_call_route_trampoline, req, &req->inst); } else bridge_log(BLEV_ERROR, "PM call route: allocation failed"); u_free((void*)conv); } @@ -2540,17 +2540,13 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativePing( utun_bridge_ping(); } +static void cpu_load_run(void* unused) { + (void)unused; chat_core_collect_cpu_load_trampoline(instance_lite_get_instance()); +} /* GUI запрашивает готовый снимок асинхронно, CPU-часы читает только поток ядра. */ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRequestCpuLoad(JNIEnv* env, jobject thiz) { (void)env; (void)thiz; - struct UASYNC* ua = instance_lite_get_uasync(); - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - if (!ua || !inst || !instance_lite_is_running()) return JNI_FALSE; - struct posted_task* task = u_calloc(1, sizeof(*task)); - if (!task) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "CPU load request allocation failed"); return JNI_FALSE; } - task->callback = chat_core_collect_cpu_load_trampoline; task->arg = inst; - uasync_post_reserved(ua, task); - return JNI_TRUE; + return instance_lite_post(cpu_load_run, NULL, NULL, NULL) == 0 ? JNI_TRUE : JNI_FALSE; } JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeConnectChannel( @@ -2600,8 +2596,16 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetChatSetting( (void)thiz; const char* n = jni_utf8(env, name); const char* v = jni_utf8(env, value); - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - if (inst) chat_core_set_setting(inst, n, v); + if (n && v) { + struct chat_setting_arg* req = u_calloc(1, sizeof(*req)); + size_t name_size = strlen(n) + 1, value_size = strlen(v) + 1; + if (!req || name_size + value_size > sizeof(req->data)) { + bridge_log(BLEV_ERROR, "Chat setting rejected: allocation/size name=%s", n); u_free(req); + } else { + memcpy(req->data, n, name_size); memcpy(req->data + name_size, v, value_size); + bridge_post_request(chat_core_set_setting_trampoline, req, &req->inst); + } + } if (v) u_free((void*)v); if (n) u_free((void*)n); } @@ -2846,9 +2850,7 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeVoiceDecodeClose( JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRequestMemberList( JNIEnv* env, jobject thiz, jstring channelId) { (void)thiz; - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - struct UASYNC* ua = instance_lite_get_uasync(); - if (!inst || !ua || !channelId) { + if (!instance_lite_is_running() || !channelId) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "request_member_list: missing instance/uasync/channel"); return JNI_FALSE; } const char* ch = jni_utf8(env, channelId); @@ -2858,12 +2860,11 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRequestMember DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "request_member_list: allocation/invalid channel ch=%s", ch); u_free(req); u_free((void*)ch); return JNI_FALSE; } - req->inst = inst; - memcpy(req->ch_id, ch, strlen(ch) + 1); + memcpy(req->ch_id, ch, strlen(ch) + 1); DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "request_member_list: queued ch=%s", ch); u_free((void*)ch); - uasync_post(ua, chat_core_request_member_list_trampoline, req); - return JNI_TRUE; + int posted = bridge_post_request(chat_core_request_member_list_trampoline, req, &req->inst); + return posted == 0 ? JNI_TRUE : JNI_FALSE; } JNIEXPORT jstring JNICALL Java_com_utun_chat_data_NativeLib_nativeGetChannelMembers( @@ -2908,7 +2909,10 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetMemberNick( (void)thiz; const char* ch = jni_utf8(env, channelId); const char* nk = nick ? jni_utf8(env, nick) : NULL; - utun_bridge_set_member_nick(ch, (uint64_t)nodeId, nk); + if (ch && (!nick || nk)) { + struct bridge_query query = {.kind = QUERY_SET_NICK, .channel = ch, .node = (uint64_t)nodeId, .value = nk}; + bridge_query_call(&query); + } if (nk) u_free((void*)nk); u_free((void*)ch); } @@ -2976,9 +2980,8 @@ static void local_sockets_trampoline(void* arg) { JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeRequestLocalSockets( JNIEnv* env, jobject thiz) { (void)env; (void)thiz; - struct UASYNC* ua = instance_lite_get_uasync(); - if (!uasync_is_running(ua)) { bridge_log(BLEV_DEBUG, "local sockets request: core stopped"); return; } - uasync_post(ua, local_sockets_trampoline, NULL); + if (instance_lite_post(local_sockets_trampoline, NULL, NULL, NULL)) + bridge_log(BLEV_DEBUG, "local sockets request: core not ready"); } /* ── Network change notification ── */ @@ -3040,21 +3043,17 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeCallAccept( JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRadioCall( JNIEnv* env, jobject thiz, jlong id, jlong group, jlong peer, jint action) { (void)env; (void)thiz; - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - struct UASYNC* ua = instance_lite_get_uasync(); - if (!inst || !ua || !uasync_is_running(ua) || !id || action < RADIO_CALL_START || action > RADIO_CALL_CONTINUE) { - DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio call JNI rejected id=%lld action=%d core=%p", (long long)id, action, (void*)inst); + if (!instance_lite_is_running() || !id || action < RADIO_CALL_START || action > RADIO_CALL_CONTINUE) { + DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio call JNI rejected id=%lld action=%d", (long long)id, action); return JNI_FALSE; } struct radio_call_request* req = u_calloc(1, sizeof(*req)); - struct posted_task* task = u_calloc(1, sizeof(*task)); - if (!req || !task) { - DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio call JNI allocation failed"); u_free(req); u_free(task); return JNI_FALSE; + if (!req) { + DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio call JNI allocation failed"); u_free(req); return JNI_FALSE; } - req->inst = inst; req->id = (uint64_t)id; req->group = (uint64_t)group; req->peer = (uint64_t)peer; req->action = action; - task->callback = radio_call_trampoline; task->arg = req; - uasync_post_reserved(ua, task); - return JNI_TRUE; + req->id = (uint64_t)id; req->group = (uint64_t)group; req->peer = (uint64_t)peer; req->action = action; + int posted = bridge_post_request(radio_call_trampoline, req, &req->inst); + return posted == 0 ? JNI_TRUE : JNI_FALSE; } JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeCallDecline( @@ -3369,15 +3368,14 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeAvatarOperati JNIEnv* env, jobject thiz, jint op, jlong id, jlong group, jlong server, jlong owner, jstring remote, jstring local, jbyteArray data) { (void)thiz; - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - struct UASYNC* ua = instance_lite_get_uasync(); - if (!inst || !ua || !uasync_is_running(ua) || !id || (!group && op != 7) || op < 0 || op > 8) { + if (!instance_lite_is_running() || !id || (!group && op != 7) || op < 0 || op > 8) { DEBUG_WARN(DEBUG_CATEGORY_CHAT, "Android avatar: unavailable/invalid operation=%d id=%lld group=%llx", op, (long long)id, (unsigned long long)group); return JNI_FALSE; } const char* path = remote ? jni_utf8(env, remote) : NULL; const char* file = local ? jni_utf8(env, local) : NULL; void* req = NULL; uasync_post_callback_t callback = NULL; + struct UTUN_INSTANCE** instance_field = NULL; if ((remote && !path) || (local && !file)) goto invalid; if (((op >= 1 && op <= 4) || op == 8) && (!server || !owner || !path || (op == 3 || op == 8 ? strcmp(path, "avatars") != 0 : @@ -3385,7 +3383,7 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeAvatarOperati if (op == 0 || op == 6) { struct group_avatar_gui_request* r = u_calloc(1, sizeof(*r)); req = r; if (!r) goto invalid; - r->inst = inst; r->id = id; r->group = group; + instance_field = &r->inst; r->id = id; r->group = group; if (op == 6) { jsize size = data ? (*env)->GetArrayLength(env, data) : 0; if (size > GROUP_AVATAR_MAX) goto invalid; @@ -3398,7 +3396,7 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeAvatarOperati if (!file || !file[0] || strlen(file) > MF_PATH_MAX) goto invalid; struct mf_transfer_request* r = u_calloc(1, sizeof(*r)); req = r; if (!r) goto invalid; - r->inst = inst; r->id = id; r->group = group; r->server = server; r->owner = owner; + instance_field = &r->inst; r->id = id; r->group = group; r->server = server; r->owner = owner; uint8_t limit[8]; if (!data || (*env)->GetArrayLength(env, data) != 8) goto invalid; (*env)->GetByteArrayRegion(env, data, 0, 8, (jbyte*)limit); @@ -3410,7 +3408,7 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeAvatarOperati } else if (op == 3 || op == 4 || op == 8) { struct mf_gui_request* r = u_calloc(1, sizeof(*r)); req = r; if (!r) goto invalid; - r->inst = inst; r->group = group; r->server = server; + instance_field = &r->inst; r->group = group; r->server = server; r->message.kind = MF_REQUEST; r->message.command = op == 3 ? MF_MKDIR : op == 8 ? MF_LIST : MF_STAT; r->message.request_id = id; r->message.owner = owner; if (op == 8) { @@ -3425,19 +3423,19 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeAvatarOperati if (server && (!path || !mf_avatar_manifest_valid(path))) goto invalid; struct chat_avatar_req* r = u_calloc(1, sizeof(*r)); req = r; if (!r) goto invalid; - r->inst = inst; r->id = id; r->group = group; r->server = server; + instance_field = &r->inst; r->id = id; r->group = group; r->server = server; if (server) memcpy(r->file, path, strlen(path) + 1); callback = chat_core_set_my_avatar_trampoline; } else { struct mf_cancel_request* r = u_calloc(1, sizeof(*r)); req = r; if (!r) goto invalid; - r->inst = inst; r->id = id; callback = member_files_gui_cancel; + instance_field = &r->inst; r->id = id; callback = member_files_gui_cancel; } if (path) u_free((void*)path); if (file) u_free((void*)file); DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "Android avatar: queued operation=%d id=%lld group=%016llx server=%016llx owner=%016llx", op, (long long)id, (unsigned long long)group, (unsigned long long)server, (unsigned long long)owner); - uasync_post(ua, callback, req); return JNI_TRUE; + return bridge_post_request(callback, req, instance_field) == 0 ? JNI_TRUE : JNI_FALSE; invalid: DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "Android avatar: invalid arguments/allocation operation=%d id=%lld group=%016llx", op, (long long)id, (unsigned long long)group); @@ -3450,11 +3448,9 @@ invalid: JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeFileRequest( JNIEnv* env, jobject thiz, jlong group, jlong server, jbyteArray wire) { (void)thiz; - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - struct UASYNC* ua = instance_lite_get_uasync(); jsize size = wire ? (*env)->GetArrayLength(env, wire) : 0; struct mf_gui_request* q = NULL; - if (!inst || !ua || !uasync_is_running(ua) || !group || !server || size < MF_WIRE_HEADER || size > MF_WIRE_MAX) goto invalid; + if (!instance_lite_is_running() || !group || !server || size < MF_WIRE_HEADER || size > MF_WIRE_MAX) goto invalid; uint8_t bytes[MF_WIRE_MAX]; (*env)->GetByteArrayRegion(env, wire, 0, size, (jbyte*)bytes); if ((*env)->ExceptionCheck(env)) goto invalid; @@ -3466,17 +3462,17 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeFileRequest( if (m->kind != MF_REQUEST || (!read && !write) || m->data_len || m->offset || m->size || m->more || !mf_path_valid(m->path, read) || (!m->owner && (m->command != MF_LIST || m->path[0])) || (m->command == MF_RENAME ? !mf_path_valid(m->target, 0) : m->target[0] != 0)) goto invalid; - if (write && (m->owner != inst->node_id || + if (write && (m->owner != bridge_my_node_id() || (!strncasecmp(m->path, "avatars", 7) && (!m->path[7] || m->path[7] == '/')))) goto invalid; if (m->command == MF_RENAME && !strncasecmp(m->target, "avatars", 7) && (!m->target[7] || m->target[7] == '/')) goto invalid; - q->inst = inst; q->group = (uint64_t)group; q->server = (uint64_t)server; + q->group = (uint64_t)group; q->server = (uint64_t)server; DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "Android files: request id=%llu cmd=%s group=%llu server=%llu owner=%llu path=%s", (unsigned long long)m->request_id, mf_command_text(m->command), (unsigned long long)group, (unsigned long long)server, (unsigned long long)m->owner, m->path); - uasync_post(ua, member_files_gui_request, q); return JNI_TRUE; + int posted = bridge_post_request(member_files_gui_request, q, &q->inst); return posted == 0 ? JNI_TRUE : JNI_FALSE; invalid: DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "Android files: request rejected group=%llu server=%llu bytes=%d ready=%d", - (unsigned long long)group, (unsigned long long)server, (int)size, inst && ua && uasync_is_running(ua)); + (unsigned long long)group, (unsigned long long)server, (int)size, instance_lite_is_running()); u_free(q); return JNI_FALSE; } @@ -3494,22 +3490,20 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeFileTransfer( JNIEnv* env, jobject thiz, jlong id, jlong group, jlong server, jlong owner, jbyteArray remote, jbyteArray local, jboolean upload, jbyteArray version) { (void)thiz; - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - struct UASYNC* ua = instance_lite_get_uasync(); struct mf_transfer_request* q = NULL; - if (!inst || !ua || !uasync_is_running(ua) || !id || !group || !server || !owner || + if (!instance_lite_is_running() || !id || !group || !server || !owner || !version || (*env)->GetArrayLength(env, version) != 16) goto invalid; q = u_calloc(1, sizeof(*q)); if (!q || android_file_path(env, remote, q->remote) || android_file_path(env, local, q->local) || !mf_path_valid(q->remote, 0) || q->local[0] != '/') goto invalid; (*env)->GetByteArrayRegion(env, version, 0, 16, (jbyte*)q->version); if ((*env)->ExceptionCheck(env) || (!upload && mf_zero(q->version, 16))) goto invalid; - if (upload && (owner != (jlong)inst->node_id || + if (upload && (owner != (jlong)bridge_my_node_id() || (!strncasecmp(q->remote, "avatars", 7) && (!q->remote[7] || q->remote[7] == '/')))) goto invalid; - q->inst = inst; q->id = id; q->group = group; q->server = server; q->owner = owner; q->upload = upload != JNI_FALSE; + q->id = id; q->group = group; q->server = server; q->owner = owner; q->upload = upload != JNI_FALSE; DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "Android files: transfer queued id=%llu group=%llu server=%llu owner=%llu upload=%d path=%s", (unsigned long long)id, (unsigned long long)group, (unsigned long long)server, (unsigned long long)owner, q->upload, q->remote); - uasync_post(ua, member_files_gui_transfer, q); return JNI_TRUE; + int posted = bridge_post_request(member_files_gui_transfer, q, &q->inst); return posted == 0 ? JNI_TRUE : JNI_FALSE; invalid: DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "Android files: transfer rejected id=%llu group=%llu server=%llu owner=%llu upload=%d", (unsigned long long)id, (unsigned long long)group, (unsigned long long)server, (unsigned long long)owner, upload); @@ -3519,16 +3513,14 @@ invalid: /* Отмена по уникальному ID не затрагивает передачи других владельцев. */ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeFileCancel(JNIEnv* env, jobject thiz, jlong id) { (void)env; (void)thiz; - struct UTUN_INSTANCE* inst = instance_lite_get_instance(); - struct UASYNC* ua = instance_lite_get_uasync(); - if (!id || !inst || !ua || !uasync_is_running(ua)) { + if (!id || !instance_lite_is_running()) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "Android files: cancel unavailable id=%llu", (unsigned long long)id); return JNI_FALSE; } struct mf_cancel_request* q = u_calloc(1, sizeof(*q)); if (!q) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "Android files: cancel allocation failed"); return JNI_FALSE; } - q->inst = inst; q->id = id; + q->id = id; DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "Android files: cancel queued id=%llu", (unsigned long long)id); - uasync_post(ua, member_files_gui_cancel, q); return JNI_TRUE; + int posted = bridge_post_request(member_files_gui_cancel, q, &q->inst); return posted == 0 ? JNI_TRUE : JNI_FALSE; } #endif /* __ANDROID__ */ diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.h b/tools/chatgui-android/jni_bridge/android_jni_bridge.h index ab617a5a7..dbd694877 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.h +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.h @@ -174,7 +174,7 @@ int utun_bridge_call_hangup(uint64_t call_id); /* Аудио-движок звонка (call_audio.c, единый голосовой стек): PCM из AudioRecord → * Opus → peer (feed); принятый Opus → джиттер+декод+stretch → PCM (pull). */ -/* GUI-поток: начать DSP после готовности ядра; call_audio уже привязан его worker. 0 = успех, -1 = ошибка. */ +/* Начать DSP после готовности ядра; call_audio уже привязан его worker. 0 = успех, -1 = ошибка. */ int utun_bridge_call_audio_start(uint64_t call_id); /* Capture worker: настоящий raw PCM, ровно 960 mono-сэмплов @48кГц; muted!=0 применяется после AEC/AGC. * capture_pts_us — монотонная метка первого сэмпла либо INT64_MIN. 0 = поставлено в uasync, -1 = отказ/ошибка. */ @@ -188,7 +188,7 @@ void utun_bridge_call_audio_stop(void); void utun_bridge_radio_set_active(uint64_t group_id, int on); int utun_bridge_radio_subscribers(uint64_t group_id); -/* Создать/привязать DSP и начать рацию: capture_channels=0 только RX, 1/2 RX+TX. 0/-1. +/* В потоке ядра создать/привязать DSP и начать рацию: capture_channels=0 только RX, 1/2 RX+TX. 0/-1. * Один TX writer для feed/talk_begin/end и отдельный RX writer для pull; lifecycle сериализует frontend. */ int utun_bridge_radio_audio_start(uint64_t group_id, int capture_channels); /* PCM 48kHz int16 interleaved, count — число int16, кратное числу каналов; длина произвольная. См. radio_audio.h. */