Contain JNI config failures and validate audio result buffers

This commit is contained in:
evgeny committed 2026-10-29 13:35:41 +01:00
1 parent af776deed6
commit 20f506fc3c
3 files changed
+51 -14

No files matched your search

+1
View File
@@ -7,6 +7,7 @@
#include "../lib/debug_config.h"
#include "../src/utun_instance.h"
#include "../src/chat/chat_core.h"
#include "../src/chat/chat_core_priv.h"
#include "../src/chat/chat_event.h"
#include "../src/dm/dm_core.h"
#include "../src/dm/dm_crypto.h"
@@ -72,6 +72,10 @@ UTUN-сервис включается отдельно через `[gui] utun_e
Реестры команд освобождают также PCM/compressor и собственные cache-файлы при отмене до исполнения.
JNI строки преобразуются через обычный UTF-8 byte[] в обе стороны: имена, JSON и пути сохраняют emoji.
NUL во входной строке отклоняется, ошибки выделения возвращают NULL/отказ, а не усечённую строку.
SQL/память в channels/messages/members возвращает NULL, отдельно от подтверждённого пустого массива.
Массивы сокетов/линков строятся целиком с экранированием имён; при ошибке прежний UI-снимок сохраняется.
JNI event callback удерживает local ref на время вызова, независимо от замены global ref.
Ошибка копирования не доставляет повреждённый payload; исключение callback описывается/снимается для следующих событий.
Заимствованные inst/ua не удерживают ресурс; они пригодны только в потоке владельца.
`instance_lite_post` публикует команду под защитой времени жизни worker и заполняет её поле instance.
Аргумент принадлежит callback, а при отмене — discard; отдельный реестр переживает удаление posted_task.
@@ -176,6 +176,7 @@ int utun_bridge_destroy(void) {
voice_recorder_deinit();
utun_config_set_provider(NULL);
__atomic_store_n(&g_on_log, NULL, __ATOMIC_RELEASE);
g_on_event = NULL;
g_cfg_string = NULL;
g_cfg_int64 = NULL;
g_cfg_int = NULL;
@@ -1835,25 +1836,47 @@ void instance_event_handler(int type, const uint8_t* data, int len) {
static jstring jni_get_string_callback(JNIEnv* env, const char* key) {
if (!g_config_callback) return NULL;
jclass cls = (*env)->GetObjectClass(env, g_config_callback);
jmethodID mid = (*env)->GetMethodID(env, cls, "getString", "(Ljava/lang/String;)Ljava/lang/String;");
if (!mid) return NULL;
jstring jkey = jni_text(env, key);
jstring result = (jstring)(*env)->CallObjectMethod(env, g_config_callback, mid, jkey);
(*env)->DeleteLocalRef(env, jkey);
jmethodID mid = cls ? (*env)->GetMethodID(env, cls, "getString", "(Ljava/lang/String;)Ljava/lang/String;") : NULL;
jstring jkey = mid ? jni_text(env, key) : NULL;
jstring result = jkey ? (jstring)(*env)->CallObjectMethod(env, g_config_callback, mid, jkey) : NULL;
if (jkey) (*env)->DeleteLocalRef(env, jkey);
if (cls) (*env)->DeleteLocalRef(env, cls);
return result;
}
static const char* jni_cfg_get_string(const char* key) {
JNIEnv* env = NULL;
if (!g_jvm || (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6) != JNI_OK)
return NULL;
static __thread char buf[4096];
buf[0] = 0;
if (!g_jvm) { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: JVM unavailable"); return NULL; }
int attached = 0;
jint rc = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
if (rc == JNI_EDETACHED) {
rc = (*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL);
if (rc == JNI_OK) attached = 1;
}
if (rc != JNI_OK) { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: attach/GetEnv failed rc=%d", rc); return NULL; }
if ((*env)->ExceptionCheck(env)) {
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: preserving existing exception key=%s", key); goto finish;
}
jstring js = jni_get_string_callback(env, key);
if (!js) return NULL;
const char* result = jni_utf8(env, js);
static char buf[256];
snprintf(buf, sizeof(buf), "%s", result ? result : "");
if (result) u_free((void*)result);
(*env)->DeleteLocalRef(env, js);
if (js && !(*env)->ExceptionCheck(env)) {
char* result = jni_utf8(env, js);
if (result) {
size_t length = strlen(result);
if (length < sizeof(buf)) memcpy(buf, result, length + 1);
else __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: value too long key=%s bytes=%zu", key, length);
u_free(result);
}
}
if (js) (*env)->DeleteLocalRef(env, js);
if ((*env)->ExceptionCheck(env)) {
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: callback failed key=%s", key);
(*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env);
}
finish:
if (attached && (*g_jvm)->DetachCurrentThread(g_jvm) != JNI_OK)
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI config: detach failed key=%s", key);
return buf[0] ? buf : NULL;
}
@@ -2027,6 +2050,7 @@ JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeDeriveNodeId(
jsize pk_len = (*env)->GetArrayLength(env, pubkey);
if (pk_len < 32) return 0;
uint8_t* pk = (uint8_t*)(*env)->GetByteArrayElements(env, pubkey, NULL);
if (!pk) { bridge_log(BLEV_ERROR, "Node ID: public key JNI access failed"); return 0; }
uint64_t node_id = sc_derive_node_id_from_pubkey(pk);
(*env)->ReleaseByteArrayElements(env, pubkey, (jbyte*)pk, JNI_ABORT);
return (jlong)node_id;
@@ -2568,6 +2592,7 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeVoiceFeed(
if (!samples) return JNI_FALSE;
jsize len = (*env)->GetArrayLength(env, samples);
jshort* data = (*env)->GetShortArrayElements(env, samples, NULL);
if (!data) { bridge_log(BLEV_ERROR, "Voice capture: JNI PCM access failed"); return JNI_FALSE; }
int rc = utun_bridge_voice_feed((const int16_t*)data, (int)len);
(*env)->ReleaseShortArrayElements(env, samples, data, JNI_ABORT);
return (rc == 0) ? JNI_TRUE : JNI_FALSE;
@@ -2689,13 +2714,18 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativePhotoSend(
JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeVoiceDecodeOpen(
JNIEnv* env, jobject thiz, jstring filePath, jintArray outInfo) {
(void)thiz;
if (!filePath || !outInfo) return 0;
if (!filePath || !outInfo || (*env)->GetArrayLength(env, outInfo) < 3) {
bridge_log(BLEV_ERROR, "Voice decode: expected path and IntArray(3)"); return 0;
}
const char* fp = jni_utf8(env, filePath);
int sr = 0, ch = 0, total = 0;
void* h = utun_bridge_voice_decode_open(fp, &sr, &ch, &total);
if (h) {
jint info[3] = {sr, ch, total};
(*env)->SetIntArrayRegion(env, outInfo, 0, 3, info);
if ((*env)->ExceptionCheck(env)) {
bridge_log(BLEV_ERROR, "Voice decode: output metadata JNI copy failed"); utun_bridge_voice_decode_close(h); h = NULL;
}
}
if (fp) u_free((void*)fp);
return (jlong)(intptr_t)h;
@@ -2707,6 +2737,7 @@ JNIEXPORT jint JNICALL Java_com_utun_chat_data_NativeLib_nativeVoiceDecodeRead(
if (handle == 0 || !buf) return -1;
jsize max = (*env)->GetArrayLength(env, buf);
jshort* data = (*env)->GetShortArrayElements(env, buf, NULL);
if (!data) { bridge_log(BLEV_ERROR, "Voice decode: JNI PCM buffer access failed"); return -1; }
int read = utun_bridge_voice_decode_read((void*)(intptr_t)handle, (int16_t*)data, (int)max);
(*env)->ReleaseShortArrayElements(env, buf, data, 0);
return (jint)read;
@@ -2971,6 +3002,7 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeCallAudioFeed
}
jsize len = (*env)->GetArrayLength(env, samples);
jshort* data = (*env)->GetShortArrayElements(env, samples, NULL);
if (!data) { bridge_log(BLEV_ERROR, "Radio capture: JNI PCM access failed"); return JNI_FALSE; }
if (!data) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call capture: JNI PCM access failed");
return JNI_FALSE;