Files
utun2/tests/test_call.c
T
evgeny 23368a5bbe Add Android boot startup, lazy settings, emoji and DM calls
Add battery optimization controls and configurable boot startup. Keep message sounds in the application and render general settings lazily to avoid ANRs. Add animated emoji controls and live BGP availability for direct-message calls on Android and desktop.
2026-10-02 17:18:24 +03:00

573 lines
22 KiB
C

// test_call.c — интеграционный тест P2P аудио-звонка (сигналинг).
//
// Однопоточный: 2 узла (A=caller, B=callee) в одном процессе, один UASYNC.
// Прямое соединение A←B, оба в CHAT-группе. Master state-machine на таймере.
//
// Сценарий:
// 1. PM BGP: живой/закрывающийся/исчезнувший путь, выбор общей группы и повторная проверка.
// 2. happy path из PM: A call_start → B INCOMING → B accept → обе ACTIVE (ACCEPTED)
// → A hangup → обе ENDED.
// 3. decline из PM: A call_start → B INCOMING → B decline → обе ENDED(DECLINE).
#include "chat_core.h"
#include "chat_sync.h"
#include "chat_event.h"
#include "member_sync.h"
#include "../call/call.h"
#include "../dm/dm_core.h"
#include "../dm/dm_crypto.h"
#include "../call/call_proto.h"
#include "../routing_layer/topo_node_sqlite.h"
#include "../routing_layer/topo_group.h"
#include "../routing_layer/topo_node.h"
#include "../routing_layer/conn_mgr_priv.h"
#include "../utun_instance.h"
#include "../transport_layer/etcp.h"
#include "../transport_layer/etcp_connections.h"
#include "../transport_layer/secure_channel.h"
#include "../ntp_time.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include "test_utils.h"
#include <sqlite3.h>
#define OPENSSL_API_COMPAT 0x10100000L
#include <openssl/evp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#define CH_ID "4242424242424242"
#define IDX_A 0
#define IDX_B 1
#define TICK_TB 100 /* 10 ms на такт */
#define MAX_TICKS 3000 /* глобальный таймаут ≈ 30 c */
enum call_phase {
P_WAIT_CONN,
P_SETUP,
P_WAIT_BGP,
P_CALL1_START, /* A: call_start */
P_WAIT_INCOMING1, /* B: INCOMING */
P_ACCEPT, /* B: call_accept */
P_WAIT_ACCEPTED, /* обе ACTIVE */
P_SEND_MEDIA, /* A: call_send_media (тестовый Opus-кадр) */
P_WAIT_MEDIA, /* B: media_recv_cb получил кадр */
P_HANGUP, /* A: call_hangup */
P_WAIT_ENDED1, /* обе ENDED */
P_CALL2_START, /* A: call_start (2-й звонок) */
P_WAIT_INCOMING2, /* B: INCOMING */
P_DECLINE, /* B: call_decline */
P_WAIT_ENDED2, /* обе ENDED + A DECLINED */
P_DONE,
};
struct call_ctx {
struct UASYNC* ua;
struct UTUN_INSTANCE* inst[2];
enum call_phase phase;
int result; /* 0=running, 1=ok, 2=fail */
int ticks;
void* timer;
uint64_t call_id; /* текущий call_id */
};
/* записи событий CHAT_EVT_CALL_* на узел */
struct call_evt {
int incoming, ringing, accepted, ended, declined, errors;
uint64_t route_peer, route_group;
uint64_t incoming_call_id;
uint64_t accepted_call_id;
uint64_t ended_call_id;
uint8_t ended_reason;
};
static struct call_evt g_ev[2]; /* [A], [B] */
/* записи принятых медиа-кадров (media_recv_cb) */
struct media_evt {
int frames;
uint64_t last_call_id;
int last_len;
uint8_t last[64];
};
static struct media_evt g_media[2];
static void media_recv_cb(struct UTUN_INSTANCE* inst, uint64_t call_id,
const uint8_t* opus, int len, void* arg) {
(void)inst;
struct media_evt* m = (struct media_evt*)arg;
if (!m) return;
m->frames++;
m->last_call_id = call_id;
m->last_len = len;
if (len > 0) memcpy(m->last, opus, (size_t)(len < 64 ? len : 64));
}
struct call_shared {
uint8_t ch_x_pub[32], ch_ed_pub[32], ch_ed_priv[32], ch_sig[64];
uint64_t nid[2];
uint8_t x_pub[2][32], x_priv[2][32], ed_pub[2][32];
int port[2];
};
static struct call_shared g_sh;
static int G_PASSED = 0, G_FAILED = 0, G_TOTAL = 0;
#define TEST(n) do { G_TOTAL++; printf(" %-58s", n); fflush(stdout); } while(0)
#define OK() do { G_PASSED++; printf("OK\n"); } while(0)
#define FAIL(f,...) do { G_FAILED++; printf("FAIL: " f "\n", ##__VA_ARGS__); } while(0)
/* Тип определяется по живым линкам, даже когда NCD остаётся UP при смене направления. */
static int test_transport_direction(void) {
struct ETCP_CONN* conn = u_calloc(1, sizeof(*conn));
struct ETCP_LINK incoming = {0}, outgoing = {0};
if (!conn) return 0;
conn->peer_node_id = 42; conn->links_up = 1; conn->links = &incoming;
incoming.initialized = incoming.link_status = incoming.is_server = 1;
outgoing.initialized = outgoing.link_status = 1; incoming.next = &outgoing;
int ok = conn_mgr_connection_type(conn, 42) == CONN_TYPE_DIRECT;
outgoing.link_status = 0;
ok &= conn_mgr_connection_type(conn, 42) == CONN_TYPE_REVERSE;
incoming.link_status = 0;
ok &= conn_mgr_connection_type(conn, 42) == CONN_TYPE_NONE;
incoming.link_status = 1;
ok &= conn_mgr_connection_type(conn, 43) == CONN_TYPE_INDIRECT;
conn->close_requested = 1;
ok &= conn_mgr_connection_type(conn, 43) == CONN_TYPE_NONE;
u_free(conn); return ok;
}
/* ── крипто-хелперы ── */
static void gen_x25519(uint8_t pub[32], uint8_t priv[32]) {
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL);
EVP_PKEY* pkey = NULL;
EVP_PKEY_keygen_init(ctx); EVP_PKEY_keygen(ctx, &pkey); EVP_PKEY_CTX_free(ctx);
size_t l = 32; EVP_PKEY_get_raw_public_key(pkey, pub, &l);
l = 32; EVP_PKEY_get_raw_private_key(pkey, priv, &l);
EVP_PKEY_free(pkey);
}
static void gen_ed25519(uint8_t pub[32], uint8_t priv[32]) {
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
EVP_PKEY* pkey = NULL;
EVP_PKEY_keygen_init(ctx); EVP_PKEY_keygen(ctx, &pkey); EVP_PKEY_CTX_free(ctx);
size_t l = 32; EVP_PKEY_get_raw_public_key(pkey, pub, &l);
l = 32; EVP_PKEY_get_raw_private_key(pkey, priv, &l);
EVP_PKEY_free(pkey);
}
static void to_hex(const uint8_t* bin, size_t n, char* out) {
for (size_t i = 0; i < n; i++) sprintf(out + i * 2, "%02x", bin[i]);
out[n * 2] = '\0';
}
static int wf(const char* p, const char* f, ...) {
va_list ap; FILE* fp = fopen(p, "w"); if (!fp) return -1;
va_start(ap, f); vfprintf(fp, f, ap); va_end(ap); fclose(fp); return 0;
}
/* ── генерация данных сценария + конфигов ── */
static void fill_shared(int base_port) {
memset(&g_sh, 0, sizeof(g_sh));
for (int i = 0; i < 2; i++) {
gen_x25519(g_sh.x_pub[i], g_sh.x_priv[i]);
sc_derive_ed25519_pubkey(g_sh.x_priv[i], g_sh.ed_pub[i]);
g_sh.nid[i] = sc_derive_node_id_from_pubkey(g_sh.x_pub[i]);
g_sh.port[i] = base_port + i * 1000;
}
{ uint8_t ch_x_priv[32]; gen_x25519(g_sh.ch_x_pub, ch_x_priv); }
gen_ed25519(g_sh.ch_ed_pub, g_sh.ch_ed_priv);
{
uint8_t msg[256]; size_t off = 0;
off += (size_t)snprintf((char*)msg + off, sizeof(msg) - off, "%s", CH_ID) + 1;
off += (size_t)snprintf((char*)msg + off, sizeof(msg) - off, "%s", "call-test") + 1;
memcpy(msg + off, &g_sh.nid[IDX_A], 8); off += 8;
memcpy(msg + off, g_sh.ch_x_pub, 32); off += 32;
memcpy(msg + off, g_sh.ch_ed_pub, 32); off += 32;
sc_ed25519_sign(g_sh.ch_ed_priv, msg, off, g_sh.ch_sig);
}
}
static void write_configs(const char* dir) {
for (int i = 0; i < 2; i++) {
char priv[65], pub[65], path[512], dbd[512];
to_hex(g_sh.x_priv[i], 32, priv);
to_hex(g_sh.x_pub[i], 32, pub);
snprintf(path, sizeof(path), "%s/%s.conf", dir, i == IDX_A ? "a" : "b");
snprintf(dbd, sizeof(dbd), "%s/db%s", dir, i == IDX_A ? "a" : "b");
utun_mkdir(dbd, 0755);
char client[512] = "";
if (i == IDX_B) {
/* B → A (клиент к A) */
char apub[65]; to_hex(g_sh.x_pub[IDX_A], 32, apub);
snprintf(client, sizeof(client),
"[client: to_a]\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n",
apub, g_sh.port[IDX_A]);
}
wf(path,
"[global]\ntun_ip=10.97.%d.1/24\ntun_ifname=tun%d0\ndb_path=%s/db%s\n"
"my_private_key=%s\nmy_public_key=%s\n"
"[server: s1]\naddr=127.0.0.1:%d\ntype=public\n"
"%s"
"[chatserver]\nstorage_autoload=0\n[allowed_keys]\nallow_all=1\n",
i, i, dir, i == IDX_A ? "a" : "b",
priv, pub, g_sh.port[i], client);
}
}
/* ── событийный обработчик ── */
static void call_evt_handler(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len) {
struct call_evt* ev = (struct call_evt*)inst->test_user_ptr;
if (!ev) return;
switch (type) {
case CHAT_EVT_CALL_ERROR:
ev->errors++;
break;
case CHAT_EVT_DM_CALL_ROUTE:
if (len >= 17 && len == 17 + data[0]) {
memcpy(&ev->route_peer, data + 1 + data[0], 8);
memcpy(&ev->route_group, data + 9 + data[0], 8);
}
break;
case CHAT_EVT_CALL_INCOMING: {
if (len < 1 + 8 + 8) return;
int ch_len = data[0];
if (len < 1 + ch_len + 8 + 8) return;
ev->incoming = 1;
memcpy(&ev->incoming_call_id, data + 1 + ch_len + 8, 8);
break;
}
case CHAT_EVT_CALL_RINGING:
ev->ringing = 1;
break;
case CHAT_EVT_CALL_ACCEPTED:
ev->accepted = 1;
if (len >= 8) memcpy(&ev->accepted_call_id, data, 8);
break;
case CHAT_EVT_CALL_ENDED:
ev->ended = 1;
if (len >= 9) { memcpy(&ev->ended_call_id, data, 8); ev->ended_reason = data[8]; }
break;
case CHAT_EVT_CALL_DECLINED:
ev->declined = 1;
break;
default:
break;
}
}
/* ── проверки ── */
static int conn_up(struct UTUN_INSTANCE* inst, uint64_t nid) {
struct ETCP_CONN* c = instance_find_conn(inst, nid);
return c && c->initialized && c->links_up;
}
static int bgp_has(struct UTUN_INSTANCE* inst, uint64_t nid) {
uint64_t gid = strtoull(CH_ID, NULL, 10);
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid);
if (!g) return 0;
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(g, nid);
return nq && nq->paths && nq->paths->head;
}
/* ── настройка канала и мемберов ── */
static void channel_put_shared(struct UTUN_INSTANCE* inst, int has_priv) {
topo_node_sqlite_channel_put(inst->topo_sqlite_db, CH_ID, "call-test", g_sh.nid[IDX_A],
g_sh.ch_x_pub, NULL, g_sh.ch_ed_pub, has_priv ? g_sh.ch_ed_priv : NULL, g_sh.ch_sig);
chat_core_ensure_channel_ready(inst, CH_ID);
}
static void member_put_self(struct UTUN_INSTANCE* inst, const char* name) {
uint64_t join_ts = (uint64_t)ntp_time_get_seconds(inst);
uint8_t jm[128];
int jl = member_sync_build_join_msg(g_sh.ch_x_pub, g_sh.ch_ed_pub, inst->node_id,
inst->my_keys.public_key, join_ts, jm, (int)sizeof(jm));
uint8_t join_sig[64]; sc_ed25519_sign(inst->my_ed25519_privkey, jm, (size_t)jl, join_sig);
char userinfo[256]; snprintf(userinfo, sizeof(userinfo), "{\"name\":\"%s\"}", name);
member_sync_put(inst, CH_ID, inst->node_id, inst->my_keys.public_key, inst->my_ed25519_pubkey,
join_sig, join_ts, NULL, 0, userinfo, NULL, NULL, 0, 0, NULL);
}
static void member_put_placeholder(struct UTUN_INSTANCE* inst, int idx, const char* name) {
char userinfo[256]; snprintf(userinfo, sizeof(userinfo), "{\"name\":\"%s\"}", name);
member_sync_put(inst, CH_ID, g_sh.nid[idx], g_sh.x_pub[idx], g_sh.ed_pub[idx],
NULL, 0, NULL, 0, userinfo, NULL, NULL, 0, 0, NULL);
}
static void do_setup(struct UTUN_INSTANCE* inst, int role) {
switch (role) {
case IDX_A:
channel_put_shared(inst, 1);
member_put_self(inst, "A");
member_put_placeholder(inst, IDX_B, "B");
member_sync_start(inst, g_sh.nid[IDX_B], CH_ID, NULL, NULL);
break;
case IDX_B:
channel_put_shared(inst, 0);
member_put_self(inst, "B");
member_put_placeholder(inst, IDX_A, "A");
member_sync_start(inst, g_sh.nid[IDX_A], CH_ID, NULL, NULL);
break;
}
}
/* Кнопка и запуск PM одинаково реагируют на потерю BGP, даже при живом ETCP. */
static int test_pm_route(struct UTUN_INSTANCE* inst, uint64_t peer, char target[64]) {
char conv[24];
snprintf(conv, sizeof(conv), "%llu", (unsigned long long)dm_derive_conv_id(inst->node_id, peer));
snprintf(target, 64, "dm:%s", conv);
if (dm_start(inst, peer, g_sh.x_pub[IDX_B], g_sh.ed_pub[IDX_B], "B", CH_ID)) return 0;
uint64_t actual_peer = 0, group = 0, expected = strtoull(CH_ID, NULL, 10);
if (!dm_call_route(inst, conv, &actual_peer, &group) || actual_peer != peer || group != expected) return 0;
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, expected);
struct TOPO_GROUP_NODE* node = topo_node_find_by_id(g, peer);
if (!node || !node->paths) return 0;
struct ETCP_CONN* route_conn = topo_node_best_path(node)->conn;
route_conn->close_requested = 1;
int closed_unavailable = !dm_call_route(inst, conv, &actual_peer, &group) && group == 0;
route_conn->close_requested = 0;
if (!closed_unavailable) return 0;
struct ll_queue* paths = node->paths;
node->paths = NULL;
int ok = !dm_call_route(inst, conv, &actual_peer, &group) && actual_peer == peer && group == 0;
struct dm_messages_req* req = u_calloc(1, sizeof(*req));
if (req) {
req->inst = inst; snprintf(req->conv_id, sizeof(req->conv_id), "%s", conv);
dm_call_route_trampoline(req);
ok &= g_ev[IDX_A].route_peer == peer && g_ev[IDX_A].route_group == 0;
} else ok = 0;
int before = g_ev[IDX_A].errors;
call_start(inst, target, peer, 123);
ok &= g_ev[IDX_A].errors == before + 1;
node->paths = paths;
ok &= dm_call_route(inst, conv, &actual_peer, &group) && group == expected;
/* Сохранённая область недоступна: выбирается другой общий канал с живым BGP. */
char sql[160];
snprintf(sql, sizeof(sql), "UPDATE dm_conversations SET group_id=1 WHERE conv_id='%s'", conv);
ok &= sqlite3_exec(inst->topo_sqlite_db, sql, NULL, NULL, NULL) == SQLITE_OK;
ok &= dm_call_route(inst, conv, &actual_peer, &group) && group == expected;
before = g_ev[IDX_A].errors;
call_start(inst, "dm:missing", peer, 124);
/* ID другого пира не должен позволять позвонить из чужой беседы. */
call_start(inst, target, peer ^ 1, 126);
ok &= g_ev[IDX_A].errors == before + 2;
return ok;
}
/* ── master state-machine ── */
static void call_tick(void* arg) {
struct call_ctx* t = arg;
t->timer = NULL;
if (t->result) return;
struct UTUN_INSTANCE* A = t->inst[IDX_A];
struct UTUN_INSTANCE* B = t->inst[IDX_B];
uint64_t na = g_sh.nid[IDX_A], nb = g_sh.nid[IDX_B];
struct call_evt* ea = &g_ev[IDX_A];
struct call_evt* eb = &g_ev[IDX_B];
switch (t->phase) {
case P_WAIT_CONN:
if (conn_up(A, nb) && conn_up(B, na)) t->phase = P_SETUP;
break;
case P_SETUP:
do_setup(A, IDX_A);
do_setup(B, IDX_B);
t->phase = P_WAIT_BGP;
break;
case P_WAIT_BGP:
if (bgp_has(A, nb) && bgp_has(B, na)) t->phase = P_CALL1_START;
break;
case P_CALL1_START: {
memset(ea, 0, sizeof(*ea));
memset(eb, 0, sizeof(*eb));
char target[64];
if (!test_pm_route(A, nb, target)) {
fprintf(stderr, "PM route availability/recheck failed\n"); t->result = 2; uasync_stop(t->ua); return;
}
t->call_id = ((uint64_t)1 << 40) | (uint64_t)(rand() & 0xFFFFF);
call_start(A, target, nb, t->call_id);
t->phase = P_WAIT_INCOMING1;
break;
}
case P_WAIT_INCOMING1:
if (eb->incoming && eb->incoming_call_id == t->call_id) t->phase = P_ACCEPT;
break;
case P_ACCEPT:
call_accept(B, t->call_id);
t->phase = P_WAIT_ACCEPTED;
break;
case P_WAIT_ACCEPTED:
if (ea->accepted && eb->accepted && ea->accepted_call_id == t->call_id) t->phase = P_SEND_MEDIA;
break;
case P_SEND_MEDIA: {
// Потеря CM-пути не завершает звонок; роутер продолжает доставку.
struct TOPO_GROUP* g = topo_groups_find(A->topo_groups, strtoull(CH_ID, NULL, 10));
struct CONN_MGR_ENTRY* ce = cm_find_entry(g->conn_mgr, nb);
if (ce) {
cm_deliver_event(ce, CONN_EVENT_DOWN);
cm_deliver_event(ce, CONN_EVENT_TIMEOUT);
}
struct ETCP_CONN* conn = instance_find_conn(A, nb);
if (conn) {
etcp_fire_conn_status(conn, ETCP_CONN_STATUS_DOWN);
etcp_fire_conn_status(conn, ETCP_CONN_STATUS_UP);
}
if (ea->ended || eb->ended) { fprintf(stderr, "temporary path loss ended call\n"); t->result = 2; break; }
/* тестовый «Opus-кадр» — релей произвольных байт, не валидируем кодек */
static const uint8_t opus[16] = { 0xf8, 0xff, 0xfe, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d };
if (call_send_media(A, t->call_id, opus, sizeof(opus)) != 0) {
fprintf(stderr, "A: call_send_media failed\n");
t->result = 2;
} else {
t->phase = P_WAIT_MEDIA;
}
break;
}
case P_WAIT_MEDIA:
if (g_media[IDX_B].frames > 0 && g_media[IDX_B].last_call_id == t->call_id
&& g_media[IDX_B].last_len == 16) t->phase = P_HANGUP;
break;
case P_HANGUP:
call_hangup(A, t->call_id);
t->phase = P_WAIT_ENDED1;
break;
case P_WAIT_ENDED1:
if (ea->ended && eb->ended && ea->ended_call_id == t->call_id) t->phase = P_CALL2_START;
break;
case P_CALL2_START: {
memset(ea, 0, sizeof(*ea));
memset(eb, 0, sizeof(*eb));
char target[64];
if (!test_pm_route(A, nb, target)) {
fprintf(stderr, "PM route availability/recheck failed\n"); t->result = 2; uasync_stop(t->ua); return;
}
t->call_id = ((uint64_t)2 << 40) | (uint64_t)(rand() & 0xFFFFF);
call_start(A, target, nb, t->call_id);
t->phase = P_WAIT_INCOMING2;
break;
}
case P_WAIT_INCOMING2:
if (eb->incoming && eb->incoming_call_id == t->call_id) t->phase = P_DECLINE;
break;
case P_DECLINE:
call_decline(B, t->call_id);
t->phase = P_WAIT_ENDED2;
break;
case P_WAIT_ENDED2:
if (ea->ended && eb->ended && ea->declined) t->phase = P_DONE;
break;
case P_DONE:
t->result = 1;
uasync_stop(t->ua);
return;
}
if (t->phase == P_DONE) { t->result = 1; uasync_stop(t->ua); return; }
if (++t->ticks > MAX_TICKS) {
fprintf(stderr, "test: timeout in phase %d (A_conn=%d B_conn=%d evA:inc=%d acc=%d end=%d dec=%d evB:inc=%d acc=%d end=%d dec=%d)\n",
(int)t->phase, conn_up(A, nb), conn_up(B, na),
ea->incoming, ea->accepted, ea->ended, ea->declined,
eb->incoming, eb->accepted, eb->ended, eb->declined);
t->result = 2;
uasync_stop(t->ua);
return;
}
t->timer = uasync_set_timeout(t->ua, TICK_TB, t, call_tick, "call_tick");
}
/* ── main ── */
int main(int argc, char** argv) {
(void)argc; (void)argv;
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
if (getenv("UTUN_TEST_DEBUG")) {
debug_set_category_level_by_name("call", "trace");
debug_set_category_level_by_name("bgp", "info");
debug_set_category_level_by_name("etcp_route", "info");
debug_set_category_level_by_name("member_sync", "info");
debug_set_category_level_by_name("chat_sync", "info");
}
utun_instance_set_tun_init_enabled(0);
srand((unsigned)time(NULL));
char dir[512]; snprintf(dir, sizeof(dir), "/tmp/utun_call_XXXXXX");
if (test_mkdtemp(dir) != 0) { fprintf(stderr, "mkdtemp failed\n"); return 1; }
test_setenv("UTUN_TEST_DIR", dir, 1);
fill_shared(56000 + (getpid() % 2000));
write_configs(dir);
printf("=== test_call ===\n"); fflush(stdout);
TEST("live transport: mixed links, reverse, none, intermediary");
if (test_transport_direction()) OK(); else FAIL("incorrect transport direction");
TEST("PM BGP availability/recheck + audio call + decline"); {
struct UASYNC* ua = uasync_create();
if (!ua) { FAIL("uasync_create failed"); return 1; }
struct call_ctx t;
memset(&t, 0, sizeof(t));
t.ua = ua;
t.phase = P_WAIT_CONN;
char cfgA[512], cfgB[512];
snprintf(cfgA, sizeof(cfgA), "%s/a.conf", dir);
snprintf(cfgB, sizeof(cfgB), "%s/b.conf", dir);
t.inst[IDX_A] = utun_instance_create(ua, cfgA);
t.inst[IDX_B] = utun_instance_create(ua, cfgB);
if (!t.inst[IDX_A] || !t.inst[IDX_B]) {
FAIL("instance create failed A=%p B=%p", (void*)t.inst[IDX_A], (void*)t.inst[IDX_B]);
uasync_destroy(ua, 0);
return 1;
}
utun_instance_init(t.inst[IDX_A]);
utun_instance_init(t.inst[IDX_B]);
t.inst[IDX_A]->test_user_ptr = &g_ev[IDX_A];
t.inst[IDX_B]->test_user_ptr = &g_ev[IDX_B];
chat_event_set_handler(t.inst[IDX_A], call_evt_handler);
chat_event_set_handler(t.inst[IDX_B], call_evt_handler);
call_set_media_recv_cb(t.inst[IDX_A], media_recv_cb, &g_media[IDX_A]);
call_set_media_recv_cb(t.inst[IDX_B], media_recv_cb, &g_media[IDX_B]);
t.timer = uasync_set_timeout(ua, TICK_TB, &t, call_tick, "call_tick");
uasync_mainloop(ua);
int ok = (t.result == 1);
if (t.inst[IDX_A]) utun_instance_destroy(t.inst[IDX_A]);
if (t.inst[IDX_B]) utun_instance_destroy(t.inst[IDX_B]);
uasync_poll(ua, 0);
uasync_destroy(ua, 0);
if (ok) OK(); else FAIL("result=%d phase=%d", t.result, (int)t.phase);
}
char cmd[512]; snprintf(cmd, sizeof(cmd), "rm -rf %s", dir); (void)!system(cmd);
printf("\n%d/%d passed, %d failed\n", G_PASSED, G_TOTAL, G_FAILED);
return G_FAILED > 0 ? 1 : 0;
}