Route remaining JNI producers through the core task owner

This commit is contained in:
evgeny committed 2026-10-29 13:35:41 +01:00
1 parent 95f0e89567
commit 86f0ac7625
3 files changed
+105 -112

No files matched your search

+6 -5
View File
@@ -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.
@@ -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__ */
@@ -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. */