181 lines
8.9 KiB
C
181 lines
8.9 KiB
C
/* Реальный instance_lite worker и voice; wrappers наблюдают потоки/порядок и отказы attach. */
|
|
#include "utun_instance.h"
|
|
#include "call/call.h"
|
|
#include "call/call_audio.h"
|
|
#include "chat/chat_event.h"
|
|
#include "transport_layer/secure_channel.h"
|
|
#include "debug_config.h"
|
|
#include "mem.h"
|
|
#include "u_async.h"
|
|
#include "../../chatgui-android/libutun_lite/instance_lite.h"
|
|
#include "../../chatgui-android/jni_bridge/android_jni_bridge.h"
|
|
#include "../../../tests/test_utils.h"
|
|
#include <pthread.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
static pthread_mutex_t observation_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t changed = PTHREAD_COND_INITIALIZER;
|
|
static pthread_t ui, worker;
|
|
static unsigned attached, detached, destroyed, loops_destroyed, started, exited, errors, checked;
|
|
static struct UTUN_INSTANCE* last_detached;
|
|
static int stream_failure, voice_failure;
|
|
|
|
#define CHECK_ANDROID(value) do { if (!(value)) { \
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "Android audio lifecycle line=%d: %s", __LINE__, #value); return 1; \
|
|
} } while (0)
|
|
|
|
static void observe(int condition, const char* message) {
|
|
pthread_mutex_lock(&observation_lock);
|
|
if (!condition) { ++errors; DEBUG_ERROR(DEBUG_CATEGORY_CALL, "Android audio lifecycle: %s", message); }
|
|
pthread_cond_broadcast(&changed);
|
|
pthread_mutex_unlock(&observation_lock);
|
|
}
|
|
|
|
int __real_android_stream_attach(struct UTUN_INSTANCE*);
|
|
int __wrap_android_stream_attach(struct UTUN_INSTANCE* inst) {
|
|
worker = pthread_self();
|
|
debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_CALL, DEBUG_LEVEL_INFO);
|
|
observe(!pthread_equal(worker, ui), "media attach executed on UI thread");
|
|
if (stream_failure) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "Android audio lifecycle: injected stream attach failure"); return -1;
|
|
}
|
|
return __real_android_stream_attach(inst);
|
|
}
|
|
|
|
int __real_call_audio_init(struct UTUN_INSTANCE*);
|
|
int __wrap_call_audio_init(struct UTUN_INSTANCE* inst) {
|
|
observe(pthread_equal(pthread_self(), worker) && !pthread_equal(pthread_self(), ui), "voice init outside core worker");
|
|
int result = __real_call_audio_init(inst);
|
|
observe(!result && inst->call_audio_ops, "voice registration missing");
|
|
pthread_mutex_lock(&observation_lock); ++attached; pthread_mutex_unlock(&observation_lock);
|
|
if (voice_failure) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "Android audio lifecycle: injected failure after voice registration"); return -1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void __real_call_audio_destroy(struct UTUN_INSTANCE*);
|
|
void __wrap_call_audio_destroy(struct UTUN_INSTANCE* inst) {
|
|
observe(!pthread_equal(pthread_self(), ui), "voice detach executed on UI thread");
|
|
__real_call_audio_destroy(inst);
|
|
observe(!inst->call_audio_ops, "voice registration retained after detach");
|
|
pthread_mutex_lock(&observation_lock); ++detached; last_detached = inst; pthread_mutex_unlock(&observation_lock);
|
|
}
|
|
|
|
void __real_utun_instance_destroy(struct UTUN_INSTANCE*);
|
|
void __wrap_utun_instance_destroy(struct UTUN_INSTANCE* inst) {
|
|
observe(inst == last_detached && !inst->call_audio_ops, "core destroyed before voice detach");
|
|
__real_utun_instance_destroy(inst);
|
|
pthread_mutex_lock(&observation_lock); ++destroyed; pthread_mutex_unlock(&observation_lock);
|
|
}
|
|
|
|
void __real_uasync_destroy(struct UASYNC*, int);
|
|
void __wrap_uasync_destroy(struct UASYNC* ua, int flags) {
|
|
observe(destroyed && detached >= destroyed, "loop destroyed before core detach/destroy");
|
|
__real_uasync_destroy(ua, flags);
|
|
pthread_mutex_lock(&observation_lock); ++loops_destroyed; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
|
|
}
|
|
|
|
int __real_pthread_detach(pthread_t);
|
|
int __wrap_pthread_detach(pthread_t thread) {
|
|
int result = __real_pthread_detach(thread);
|
|
if (pthread_equal(pthread_self(), worker) && pthread_equal(thread, worker)) {
|
|
pthread_mutex_lock(&observation_lock); ++exited; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct UTUN_INSTANCE* __real_instance_lite_get_instance(void);
|
|
struct UTUN_INSTANCE* __wrap_instance_lite_get_instance(void) {
|
|
observe(!pthread_equal(pthread_self(), ui), "UI bridge reads raw instance");
|
|
return __real_instance_lite_get_instance();
|
|
}
|
|
|
|
char* __wrap_utun_bridge_get_local_sockets_json(void) { return u_strdup("[]"); }
|
|
|
|
static void event(int type, const uint8_t* data, int length) {
|
|
(void)data; (void)length;
|
|
if (type != CHAT_EVT_SERVICE_STARTED) return;
|
|
observe(pthread_equal(pthread_self(), worker), "service ready event outside core worker");
|
|
pthread_mutex_lock(&observation_lock); ++started; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
|
|
}
|
|
|
|
static int wait_for(const unsigned* counter, unsigned expected) {
|
|
struct timespec deadline; clock_gettime(CLOCK_REALTIME, &deadline); deadline.tv_sec += 10;
|
|
pthread_mutex_lock(&observation_lock);
|
|
int result = 0;
|
|
while (*counter < expected && !result) result = pthread_cond_timedwait(&changed, &observation_lock, &deadline);
|
|
int ready = *counter >= expected;
|
|
pthread_mutex_unlock(&observation_lock);
|
|
if (!ready) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "Android audio lifecycle timed out expected=%u", expected);
|
|
return ready;
|
|
}
|
|
|
|
static void inspect(void* arg) {
|
|
(void)arg;
|
|
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
|
|
observe(inst && inst->call_audio_ops && inst->call_audio_ops->on_media, "ready core without audio provider");
|
|
pthread_mutex_lock(&observation_lock); ++checked; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
|
|
}
|
|
|
|
static void stop_core(void* arg) {
|
|
(void)arg;
|
|
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
|
|
observe(inst && inst->running && inst->call_audio_ops, "core stop without live voice owner");
|
|
utun_instance_stop(inst);
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
CHECK_ANDROID(argc == 2);
|
|
int core_stop = !strcmp(argv[1], "core_stop") || !strcmp(argv[1], "restart_core_stop");
|
|
int restart = !strcmp(argv[1], "restart") || !strcmp(argv[1], "restart_core_stop"), normal = !strcmp(argv[1], "normal");
|
|
stream_failure = !strcmp(argv[1], "stream_failure"); voice_failure = !strcmp(argv[1], "voice_failure");
|
|
CHECK_ANDROID(restart || normal || core_stop || stream_failure || voice_failure);
|
|
ui = pthread_self(); worker = ui;
|
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_CALL, DEBUG_LEVEL_INFO);
|
|
utun_instance_set_tun_init_enabled(0);
|
|
size_t allocated = u_get_allocated_count();
|
|
CHECK_ANDROID(utun_bridge_call_audio_start(42) == -1);
|
|
char directory[512] = "/tmp/utun_android_audio_XXXXXX", config[1200], public_key[65], private_key[65];
|
|
CHECK_ANDROID(!test_mkdtemp(directory));
|
|
struct SC_MYKEYS keys;
|
|
CHECK_ANDROID(sc_generate_keypair(&keys) == SC_OK);
|
|
for (unsigned i = 0; i < SC_PUBKEY_SIZE; ++i) {
|
|
snprintf(public_key + i * 2, 3, "%02x", keys.public_key[i]);
|
|
snprintf(private_key + i * 2, 3, "%02x", keys.private_key[i]);
|
|
}
|
|
snprintf(config, sizeof(config), "[global]\ndb_path=%s\nauto_sockets=no\nmy_public_key=%s\nmy_private_key=%s\n"
|
|
"[server: s1]\naddr=127.0.0.1:0\ntype=public\n[chatserver]\nstorage_autoload=0\n", directory, public_key, private_key);
|
|
instance_lite_set_event_handler(event);
|
|
CHECK_ANDROID(!instance_lite_start(config));
|
|
if (stream_failure || voice_failure) {
|
|
CHECK_ANDROID(wait_for(&exited, 1));
|
|
CHECK_ANDROID(!started && attached == (unsigned)voice_failure && detached == 1 && destroyed == 1 && loops_destroyed == 1);
|
|
} else {
|
|
CHECK_ANDROID(wait_for(&started, 1) && instance_lite_is_running());
|
|
CHECK_ANDROID(!utun_bridge_call_audio_start(42));
|
|
uasync_post(instance_lite_get_uasync(), inspect, NULL);
|
|
CHECK_ANDROID(wait_for(&checked, 1));
|
|
utun_bridge_call_audio_stop();
|
|
if (restart) {
|
|
instance_lite_restart(config);
|
|
CHECK_ANDROID(wait_for(&started, 2) && !utun_bridge_call_audio_start(43));
|
|
uasync_post(instance_lite_get_uasync(), inspect, NULL);
|
|
CHECK_ANDROID(wait_for(&checked, 2));
|
|
utun_bridge_call_audio_stop();
|
|
}
|
|
if (core_stop) {
|
|
uasync_post(instance_lite_get_uasync(), stop_core, NULL);
|
|
CHECK_ANDROID(wait_for(&loops_destroyed, 1u + restart));
|
|
}
|
|
instance_lite_stop();
|
|
CHECK_ANDROID(attached == 1u + restart && detached == attached && destroyed == attached && loops_destroyed == attached);
|
|
}
|
|
CHECK_ANDROID(!errors && !instance_lite_is_running() && utun_bridge_call_audio_start(44) == -1);
|
|
CHECK_ANDROID(u_get_allocated_count() == allocated);
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "Android audio lifecycle mode=%s attach=%u detach=%u destroyed=%u loops=%u allocations=%zu->%zu PASS",
|
|
argv[1], attached, detached, destroyed, loops_destroyed, allocated, u_get_allocated_count());
|
|
return 0;
|
|
}
|