Files
utun2/tools/chatgui/tests/test_android_audio_lifecycle.c
T

602 lines
34 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 "chat/chat_setting.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 "../../chatgui-android/libutun_lite/utun_config_api.h"
#include "../../../tests/test_utils.h"
#include <pthread.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <openssl/sha.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, errors, checked;
static unsigned identity_failures;
static unsigned activity_checked;
static unsigned phone_checked, phone_applied, phone_post_entered, release_phone_post;
static int pause_poll, pause_phone_post, phone_post_result;
static __thread int phone_producer, fail_phone_alloc;
static int radio_mode, radio_post_result;
static unsigned radio_acked, radio_inspected, radio_post_entered, release_radio_post;
static __thread int radio_producer;
static int64_t radio_request = INT64_C(0x1234567800000000);
static int radio_success = 1;
static int radio_expected[INSTANCE_LITE_RADIO_SETTINGS_COUNT];
static const int radio_valid[] = {1, 2, -20, -60, 400, 65, 20};
static const int radio_defaults[] = {0, 1, -35, -45, 200, 50, 15};
static uint64_t first_node, current_node;
static struct UTUN_INSTANCE* last_detached;
static int stream_failure, voice_failure;
static const char* lifecycle_string(const char* key, const char* fallback) { return !strcmp(key, "lifecycle") ? "owned" : fallback; }
static int pause_loop, pause_instance, pause_attach;
static unsigned paused, release_worker, stop_requested, stop_entered, stop_done, joins, self_detaches, finished;
#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);
}
static void pause_worker(int enabled) {
if (!enabled) return;
pthread_mutex_lock(&observation_lock);
++paused; pthread_cond_broadcast(&changed);
while (!release_worker) pthread_cond_wait(&changed, &observation_lock);
pthread_mutex_unlock(&observation_lock);
}
struct UASYNC* __real_uasync_create(void);
struct UASYNC* __wrap_uasync_create(void) {
worker = pthread_self();
pause_worker(pause_loop);
return __real_uasync_create();
}
void __real_uasync_poll(struct UASYNC*, int);
void __wrap_uasync_poll(struct UASYNC* ua, int timeout) {
if (pause_poll) {
pause_poll = 0;
pause_worker(1); // Сервис готов, но очередь ещё не взята в исполняемую пачку.
return;
}
__real_uasync_poll(ua, timeout);
}
void __real_uasync_post_reserved(struct UASYNC*, struct posted_task*);
void __wrap_uasync_post_reserved(struct UASYNC* ua, struct posted_task* task) {
if (radio_producer && radio_mode == 4) {
pthread_mutex_lock(&observation_lock);
++radio_post_entered; pthread_cond_broadcast(&changed);
while (!release_radio_post) pthread_cond_wait(&changed, &observation_lock);
pthread_mutex_unlock(&observation_lock);
}
if (phone_producer && pause_phone_post) {
pthread_mutex_lock(&observation_lock);
++phone_post_entered; pthread_cond_broadcast(&changed);
while (!release_phone_post) pthread_cond_wait(&changed, &observation_lock);
pthread_mutex_unlock(&observation_lock);
}
__real_uasync_post_reserved(ua, task);
}
void* __real_u_malloc_impl(uint32_t, const char*);
void* __wrap_u_malloc_impl(uint32_t size, const char* location) {
if (fail_phone_alloc) { fail_phone_alloc = 0; return NULL; }
return __real_u_malloc_impl(size, location);
}
void __real_call_set_phone_state(struct UTUN_INSTANCE*, int, unsigned);
void __wrap_call_set_phone_state(struct UTUN_INSTANCE* inst, int state, unsigned policy) {
observe(pthread_equal(pthread_self(), worker), "phone state applied outside core worker");
__real_call_set_phone_state(inst, state, policy);
pthread_mutex_lock(&observation_lock); ++phone_applied; pthread_mutex_unlock(&observation_lock);
}
struct UTUN_INSTANCE* __real_utun_instance_create_from_config(struct UASYNC*, struct utun_config*);
struct UTUN_INSTANCE* __wrap_utun_instance_create_from_config(struct UASYNC* ua, struct utun_config* config) {
struct UTUN_INSTANCE* inst = __real_utun_instance_create_from_config(ua, config);
pause_worker(pause_instance);
return inst;
}
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");
pause_worker(pause_attach);
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(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(thread, worker)) {
pthread_mutex_lock(&observation_lock); ++self_detaches; pthread_mutex_unlock(&observation_lock);
}
return result;
}
int __real_pthread_join(pthread_t, void**);
int __wrap_pthread_join(pthread_t thread, void** result) {
int status = __real_pthread_join(thread, result);
if (pthread_equal(thread, worker)) {
pthread_mutex_lock(&observation_lock); ++joins; pthread_mutex_unlock(&observation_lock);
}
return status;
}
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");
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
current_node = inst->node_id;
if (!first_node) first_node = current_node;
pthread_mutex_lock(&observation_lock); ++started; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
static void log_event(int level, const char* category, const char* message) {
(void)level; (void)category;
if (strstr(message, "Android worker stop requested")) {
pthread_mutex_lock(&observation_lock); ++stop_requested; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
if (strstr(message, "Android worker finished")) {
worker = pthread_self();
pthread_mutex_lock(&observation_lock); ++finished; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
if (!strstr(message, "Android identity save failed") && !strstr(message, "Android identity key pair is invalid")) return;
pthread_mutex_lock(&observation_lock); ++identity_failures; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
static void* stop_worker(void* arg) {
(void)arg;
pthread_mutex_lock(&observation_lock); ++stop_entered; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
instance_lite_stop();
pthread_mutex_lock(&observation_lock); ++stop_done; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
return NULL;
}
static void* post_phone_state(void* arg) {
(void)arg;
phone_producer = 1;
phone_post_result = utun_bridge_set_phone_state(2, 31);
return NULL;
}
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_radio(void* arg) {
(void)arg;
static const char* keys[] = {"radio_auto_enabled", "radio_auto_mode", "radio_level_on_dbfs", "radio_level_off_dbfs",
"radio_auto_hangover_ms", "radio_vad_threshold", "radio_vad_compressor_max_gain_db"};
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
observe(inst && pthread_equal(pthread_self(), worker), "radio settings inspected outside core worker");
if (inst) for (unsigned i = 0; i < INSTANCE_LITE_RADIO_SETTINGS_COUNT; ++i)
observe(chat_setting_get_int(inst, keys[i], radio_defaults[i]) == radio_expected[i], "radio snapshot partially changed or misordered");
pthread_mutex_lock(&observation_lock); ++radio_inspected; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
static void radio_configured(int64_t request, int success) {
observe(request == radio_request && success == radio_success, "radio settings acknowledgement differs from request/result");
inspect_radio(NULL);
pthread_mutex_lock(&observation_lock); ++radio_acked; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
static void* post_radio_config(void* arg) {
(void)arg;
radio_producer = 1;
radio_post_result = instance_lite_configure_radio(radio_request, radio_valid, radio_configured);
return NULL;
}
static int submit_radio(const int* values, int success) {
unsigned target = radio_acked + 1;
++radio_request; radio_success = success;
if (success) memcpy(radio_expected, values, sizeof(radio_expected));
CHECK_ANDROID(!instance_lite_configure_radio(radio_request, values, radio_configured));
CHECK_ANDROID(wait_for(&radio_acked, target));
return 0;
}
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");
observe(instance_lite_set_active(1) == -1, "activity posting accepted from native callback");
observe(utun_bridge_set_phone_state(1, 31) == -1, "phone state posting accepted from native callback");
observe(instance_lite_configure_radio(42, radio_valid, radio_configured) == -1, "radio config posting accepted from native callback");
observe(!strcmp(cfg_get_string("lifecycle", "missing"), "owned"), "config provider unavailable before callback stop");
observe(utun_bridge_destroy() == -1 && instance_lite_is_running(), "bridge destroyed resources from core callback");
observe(!strcmp(cfg_get_string("lifecycle", "missing"), "owned"), "failed stop cleared config callbacks");
uint8_t public_key[SC_PUBKEY_SIZE], ed_public[SC_PUBKEY_SIZE], seed[SHA512_DIGEST_LENGTH];
observe(sc_compute_public_key_from_private(inst->my_keys.private_key, public_key) == SC_OK &&
!memcmp(public_key, inst->my_keys.public_key, sizeof(public_key)) &&
inst->node_id == sc_derive_node_id_from_pubkey(public_key) && inst->node_id == inst->config->global.my_node_id,
"X25519/node_id identity mismatch");
observe(sc_derive_ed25519_pubkey(inst->my_keys.private_key, ed_public) == SC_OK &&
!memcmp(ed_public, inst->my_ed25519_pubkey, sizeof(ed_public)), "Ed25519 public identity mismatch");
observe(SHA512(inst->my_keys.private_key, SC_PRIVKEY_SIZE, seed) &&
!memcmp(seed, inst->my_ed25519_privkey, SC_PRIVKEY_SIZE), "Ed25519 private identity mismatch");
pthread_mutex_lock(&observation_lock); ++checked; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
static void inspect_activity(void* arg) {
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
int expected = (int)(intptr_t)arg;
observe(inst && inst->client_activity_requested == expected && inst->client_activity == expected,
"activity command did not run before subsequent worker callback");
pthread_mutex_lock(&observation_lock); ++activity_checked; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
struct phone_expectation { int state; unsigned policy; };
static void inspect_phone(void* arg) {
const struct phone_expectation* expected = arg;
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
observe(inst && inst->phone_state == expected->state && inst->incoming_call_policy == expected->policy,
"phone state/policy differs from queued request");
pthread_mutex_lock(&observation_lock); ++phone_checked; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
/* Держим готовый worker перед poll: stop/restart должны отменить ожидающие команды без их применения и утечки. */
static int check_phone_shutdown(const char* config, int restart, int publication, size_t allocated) {
CHECK_ANDROID(wait_for(&started, 1) && wait_for(&paused, 1));
CHECK_ANDROID(utun_bridge_set_phone_state(-1, 0) == -1 && utun_bridge_set_phone_state(3, 0) == -1);
CHECK_ANDROID(utun_bridge_set_phone_state(0, 32) == -1 && utun_bridge_set_phone_state(0, (unsigned)-1) == -1);
pthread_t stopper, poster;
unsigned stopped_before_publication = 0;
if (publication) {
CHECK_ANDROID(!pthread_create(&poster, NULL, post_phone_state, NULL) && wait_for(&phone_post_entered, 1));
CHECK_ANDROID(!pthread_create(&stopper, NULL, stop_worker, NULL) && wait_for(&stop_entered, 1));
struct timespec deadline; clock_gettime(CLOCK_REALTIME, &deadline); deadline.tv_nsec += 100000000;
if (deadline.tv_nsec >= 1000000000) { ++deadline.tv_sec; deadline.tv_nsec -= 1000000000; }
pthread_mutex_lock(&observation_lock);
while (!stop_requested) if (pthread_cond_timedwait(&changed, &observation_lock, &deadline)) break;
stopped_before_publication = stop_requested;
release_phone_post = 1; pthread_cond_broadcast(&changed);
pthread_mutex_unlock(&observation_lock);
CHECK_ANDROID(!pthread_join(poster, NULL) && !phone_post_result && wait_for(&stop_requested, 1));
} else {
for (unsigned i = 0; i < 16; ++i) CHECK_ANDROID(!utun_bridge_set_phone_state(i % 3, i));
if (restart) instance_lite_restart(config);
else CHECK_ANDROID(!pthread_create(&stopper, NULL, stop_worker, NULL) && wait_for(&stop_requested, 1));
}
pthread_mutex_lock(&observation_lock); release_worker = 1; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
if (restart) {
CHECK_ANDROID(wait_for(&started, 2) && !phone_applied);
struct phone_expectation expected = { .state = 0, .policy = CALL_POLICY_PUBLISH_PHONE };
uasync_post(instance_lite_get_uasync(), inspect_phone, &expected);
CHECK_ANDROID(wait_for(&phone_checked, 1));
fail_phone_alloc = 1;
CHECK_ANDROID(utun_bridge_set_phone_state(2, 31) == -1 && !fail_phone_alloc);
for (unsigned i = 0; i < 6; ++i) {
expected.state = i % 3; expected.policy = i < 3 ? 31 : 1u << (i - 3);
CHECK_ANDROID(!utun_bridge_set_phone_state(expected.state, expected.policy));
uasync_post(instance_lite_get_uasync(), inspect_phone, &expected);
CHECK_ANDROID(wait_for(&phone_checked, i + 2));
}
CHECK_ANDROID(phone_applied == 6);
instance_lite_stop();
} else CHECK_ANDROID(!pthread_join(stopper, NULL) && !phone_applied);
CHECK_ANDROID(utun_bridge_set_phone_state(2, 31) == -1 && !instance_lite_is_running());
DEBUG_INFO(DEBUG_CATEGORY_CALL, "Android phone shutdown restart=%d publication=%d early_stop=%u applied=%u errors=%u allocations=%zu->%zu",
restart, publication, stopped_before_publication, phone_applied, errors, allocated, u_get_allocated_count());
CHECK_ANDROID(!stopped_before_publication && !errors && u_get_allocated_count() == allocated);
return 0;
}
/* Пакет проверок одной транзакции: копия входа, атомарный отказ, отмена и удержание loop во время публикации. */
static int check_radio_config(const char* config, size_t allocated) {
CHECK_ANDROID(wait_for(&started, 1) && wait_for(&paused, 1));
CHECK_ANDROID(instance_lite_configure_radio(42, NULL, radio_configured) == -1);
CHECK_ANDROID(instance_lite_configure_radio(42, radio_valid, NULL) == -1);
memcpy(radio_expected, radio_valid, sizeof(radio_expected));
pthread_t stopper, poster;
unsigned early_stop = 0;
if (radio_mode == 1) {
int input[INSTANCE_LITE_RADIO_SETTINGS_COUNT]; memcpy(input, radio_valid, sizeof(input));
CHECK_ANDROID(!instance_lite_configure_radio(radio_request, input, radio_configured));
memset(input, 0, sizeof(input)); // Задача обязана владеть копией до начала poll.
} else if (radio_mode == 4) {
CHECK_ANDROID(!pthread_create(&poster, NULL, post_radio_config, NULL) && wait_for(&radio_post_entered, 1));
CHECK_ANDROID(!pthread_create(&stopper, NULL, stop_worker, NULL) && wait_for(&stop_entered, 1));
struct timespec deadline; clock_gettime(CLOCK_REALTIME, &deadline); deadline.tv_nsec += 100000000;
if (deadline.tv_nsec >= 1000000000) { ++deadline.tv_sec; deadline.tv_nsec -= 1000000000; }
pthread_mutex_lock(&observation_lock);
while (!stop_requested) if (pthread_cond_timedwait(&changed, &observation_lock, &deadline)) break;
early_stop = stop_requested;
release_radio_post = 1; pthread_cond_broadcast(&changed);
pthread_mutex_unlock(&observation_lock);
CHECK_ANDROID(!pthread_join(poster, NULL) && !radio_post_result && wait_for(&stop_requested, 1));
} else {
for (unsigned i = 0; i < 16; ++i)
CHECK_ANDROID(!instance_lite_configure_radio(radio_request, radio_valid, radio_configured));
if (radio_mode == 3) instance_lite_restart(config);
else CHECK_ANDROID(!pthread_create(&stopper, NULL, stop_worker, NULL) && wait_for(&stop_requested, 1));
}
pthread_mutex_lock(&observation_lock); release_worker = 1; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
if (radio_mode == 1) {
CHECK_ANDROID(wait_for(&radio_acked, 1));
const int limits[][2] = {{-1, 2}, {0, 3}, {-91, 1}, {-91, 1}, {49, 2001}, {9, 96}, {0, 61}};
for (unsigned field = 0; field < INSTANCE_LITE_RADIO_SETTINGS_COUNT; ++field) for (unsigned bound = 0; bound < 2; ++bound) {
int invalid[] = {0, 1, -30, -70, 600, 75, 30}; invalid[field] = limits[field][bound];
CHECK_ANDROID(!submit_radio(invalid, 0));
}
int invalid_pair[] = {0, 1, -30, -30, 600, 75, 30};
CHECK_ANDROID(!submit_radio(invalid_pair, 0));
invalid_pair[3] = -20;
CHECK_ANDROID(!submit_radio(invalid_pair, 0));
const int extremes[] = {0, 1, 0, -90, 2000, 95, 60};
CHECK_ANDROID(!submit_radio(extremes, 1));
fail_phone_alloc = 1;
CHECK_ANDROID(instance_lite_configure_radio(42, radio_valid, radio_configured) == -1 && !fail_phone_alloc);
CHECK_ANDROID(!submit_radio(radio_valid, 1));
uasync_post(instance_lite_get_uasync(), inspect, NULL);
CHECK_ANDROID(wait_for(&checked, 1));
instance_lite_stop();
} else if (radio_mode == 3) {
CHECK_ANDROID(wait_for(&started, 2) && !radio_acked);
memcpy(radio_expected, radio_defaults, sizeof(radio_expected));
uasync_post(instance_lite_get_uasync(), inspect_radio, NULL);
CHECK_ANDROID(wait_for(&radio_inspected, 1));
CHECK_ANDROID(!submit_radio(radio_valid, 1));
instance_lite_stop();
} else CHECK_ANDROID(!pthread_join(stopper, NULL) && !radio_acked);
CHECK_ANDROID(instance_lite_configure_radio(42, radio_valid, radio_configured) == -1);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "Android radio config mode=%d early_stop=%u ack=%u errors=%u allocations=%zu->%zu",
radio_mode, early_stop, radio_acked, errors, allocated, u_get_allocated_count());
CHECK_ANDROID(!early_stop && !errors && u_get_allocated_count() == allocated);
return 0;
}
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");
int cancel_restart = !strcmp(argv[1], "cancel_restart");
int slow = !strcmp(argv[1], "cancel_slow_attach");
int parse_failure = !strcmp(argv[1], "parse_failure");
int phone_restart = !strcmp(argv[1], "phone_restart"), phone_publication = !strcmp(argv[1], "phone_publication_stop");
int phone = phone_restart || phone_publication || !strcmp(argv[1], "phone_stop");
radio_mode = !strcmp(argv[1], "radio_config") ? 1 : !strcmp(argv[1], "radio_config_stop") ? 2 :
!strcmp(argv[1], "radio_config_restart") ? 3 : !strcmp(argv[1], "radio_config_publication_stop") ? 4 : 0;
pause_poll = phone || radio_mode; pause_phone_post = phone_publication;
pause_loop = !strcmp(argv[1], "cancel_loop_create");
pause_instance = !strcmp(argv[1], "cancel_instance_create");
pause_attach = !strcmp(argv[1], "cancel_attach") || slow;
int cancel = pause_loop || pause_instance || pause_attach || cancel_restart;
CHECK_ANDROID(restart || normal || core_stop || stream_failure || voice_failure || cancel || parse_failure || phone || radio_mode);
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();
utun_config_provider_t provider = { .get_string = lifecycle_string };
utun_config_set_provider(&provider);
CHECK_ANDROID(utun_bridge_call_audio_start(42) == -1);
char directory[512], config[1200], public_key[65], private_key[65];
const char* temporary = getenv("TMPDIR");
int length = snprintf(directory, sizeof(directory), "%s/utun_android_audio_XXXXXX", temporary ? temporary : "/tmp");
CHECK_ANDROID(length > 0 && length < (int)sizeof(directory));
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);
debug_set_log_hook(log_event);
if (parse_failure) {
for (unsigned i = 1; i <= 2; ++i) {
CHECK_ANDROID(!instance_lite_start("[global]\nmy_private_key=invalid\n"));
CHECK_ANDROID(wait_for(&finished, i));
}
instance_lite_stop();
CHECK_ANDROID(joins == 2 && !self_detaches && !started && u_get_allocated_count() == allocated);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "Android worker repeated parse failure: joins=%u detach=%u PASS", joins, self_detaches);
return 0;
}
CHECK_ANDROID(!instance_lite_start(config));
if (phone) return check_phone_shutdown(config, phone_restart, phone_publication, allocated);
if (radio_mode) return check_radio_config(config, allocated);
if (cancel) {
if (cancel_restart) {
CHECK_ANDROID(wait_for(&started, 1));
pause_instance = 1;
instance_lite_restart(config);
}
CHECK_ANDROID(wait_for(&paused, 1));
CHECK_ANDROID(instance_lite_set_active(1) == -1);
CHECK_ANDROID(utun_bridge_set_phone_state(2, 31) == -1);
CHECK_ANDROID(instance_lite_configure_radio(42, radio_valid, radio_configured) == -1);
pthread_t stopper;
CHECK_ANDROID(!pthread_create(&stopper, NULL, stop_worker, NULL));
CHECK_ANDROID(wait_for(&stop_entered, 1));
struct timespec deadline; clock_gettime(CLOCK_REALTIME, &deadline); deadline.tv_sec += 3;
pthread_mutex_lock(&observation_lock);
while (!stop_requested && !stop_done) {
if (pthread_cond_timedwait(&changed, &observation_lock, &deadline)) break;
}
unsigned stopped_early = stop_done;
pthread_mutex_unlock(&observation_lock);
CHECK_ANDROID(stop_requested && !stopped_early);
CHECK_ANDROID(instance_lite_set_active(1) == -1);
CHECK_ANDROID(utun_bridge_set_phone_state(2, 31) == -1);
CHECK_ANDROID(instance_lite_configure_radio(42, radio_valid, radio_configured) == -1);
if (slow) {
clock_gettime(CLOCK_REALTIME, &deadline); deadline.tv_sec += 2; deadline.tv_nsec += 200000000;
if (deadline.tv_nsec >= 1000000000) { ++deadline.tv_sec; deadline.tv_nsec -= 1000000000; }
pthread_mutex_lock(&observation_lock);
while (!stop_done) {
if (pthread_cond_timedwait(&changed, &observation_lock, &deadline)) break;
}
stopped_early = stop_done;
pthread_mutex_unlock(&observation_lock);
CHECK_ANDROID(!stopped_early && !self_detaches);
}
pthread_mutex_lock(&observation_lock); release_worker = 1; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
CHECK_ANDROID(!pthread_join(stopper, NULL));
CHECK_ANDROID(stop_done == 1 && joins == 1 && !self_detaches && started == (unsigned)cancel_restart);
CHECK_ANDROID(!instance_lite_is_running() && !__real_instance_lite_get_instance() && !instance_lite_get_uasync());
CHECK_ANDROID(instance_lite_set_active(1) == -1);
CHECK_ANDROID(utun_bridge_set_phone_state(2, 31) == -1);
CHECK_ANDROID(instance_lite_configure_radio(42, radio_valid, radio_configured) == -1);
CHECK_ANDROID(!errors && u_get_allocated_count() == allocated);
/* Тот же singleton должен запуститься снова только после полного join. */
pause_loop = pause_instance = pause_attach = 0;
CHECK_ANDROID(!instance_lite_start(config) && wait_for(&started, 1u + cancel_restart));
instance_lite_stop();
CHECK_ANDROID(joins == 2 && !errors && u_get_allocated_count() == allocated);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "Android worker cancellation mode=%s joins=%u ready=%u detach=%u PASS",
argv[1], joins, started, self_detaches);
return 0;
}
if (stream_failure || voice_failure) {
CHECK_ANDROID(wait_for(&loops_destroyed, 1));
instance_lite_stop();
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(!instance_lite_set_active(1));
uasync_post(instance_lite_get_uasync(), inspect_activity, (void*)(intptr_t)1);
CHECK_ANDROID(wait_for(&activity_checked, 1));
CHECK_ANDROID(!instance_lite_set_active(0));
uasync_post(instance_lite_get_uasync(), inspect_activity, NULL);
CHECK_ANDROID(wait_for(&activity_checked, 2));
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();
char path[1024]; snprintf(path, sizeof(path), "%s/keys.conf", directory);
CHECK_ANDROID(!utun_mkdir(path, 0700));
CHECK_ANDROID(!instance_lite_regenerate_keys(config));
CHECK_ANDROID(wait_for(&identity_failures, 1));
CHECK_ANDROID(instance_lite_is_running() && started == 2 && attached == 2 && detached == 1 && current_node == first_node);
uasync_post(instance_lite_get_uasync(), inspect, NULL);
CHECK_ANDROID(wait_for(&checked, 3));
CHECK_ANDROID(!test_rmdir(path));
CHECK_ANDROID(!instance_lite_regenerate_keys(config));
CHECK_ANDROID(wait_for(&started, 3) && current_node != first_node && !utun_bridge_call_audio_start(44));
uasync_post(instance_lite_get_uasync(), inspect, NULL);
CHECK_ANDROID(wait_for(&checked, 4));
utun_bridge_call_audio_stop();
uint64_t regenerated_node = current_node;
/* Снимок INI был снят до смены ключей: обычный restart не должен возвращать прежнюю идентичность. */
instance_lite_restart(config);
CHECK_ANDROID(wait_for(&started, 4) && current_node == regenerated_node);
uasync_post(instance_lite_get_uasync(), inspect, NULL);
CHECK_ANDROID(wait_for(&checked, 5));
char backup[1024]; snprintf(backup, sizeof(backup), "%s/keys.backup", directory);
CHECK_ANDROID(!rename(path, backup));
FILE* invalid = fopen(path, "wb"); CHECK_ANDROID(invalid);
int written = fprintf(invalid, "public_key=%s\nprivate_key=%064d\n", public_key, 0);
int closed = fclose(invalid);
CHECK_ANDROID(written > 0 && !closed);
instance_lite_restart(config);
CHECK_ANDROID(wait_for(&identity_failures, 2));
CHECK_ANDROID(instance_lite_is_running() && started == 4 && current_node == regenerated_node && detached == 3);
uasync_post(instance_lite_get_uasync(), inspect, NULL);
CHECK_ANDROID(wait_for(&checked, 6));
CHECK_ANDROID(!test_unlink(path) && !rename(backup, path));
}
if (core_stop) {
uasync_post(instance_lite_get_uasync(), stop_core, NULL);
CHECK_ANDROID(wait_for(&loops_destroyed, 1u + 3u * restart));
}
instance_lite_stop();
CHECK_ANDROID(attached == 1u + 3u * 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_set_log_hook(NULL);
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;
}