call: P2P аудиозвонки — сигналинг, релей медиа, аудио-контур, headless/desktop/android
- src/call/: сигналинг (call.c, call_proto.h, ETCP_RT_ID_CALL 0x36) + релей медиа + headless аудио-сокет (call_headless.c, call_audio_bind) + Opus/jitter/тоны - chat: события CHAT_EVT_CALL_* (34–40), CHAT_EVT_DM_MESSAGES (41), DM-трамплины - chatgui: call window, miniaudio I/O, audio_frame_ring, test_audio_jitter - chatgui-android: CallScreen/IncomingCallDialog, аудио-роутинг, JNI, рингтон - tests: test_call, test_call_headless
This commit is contained in:
1 parent
0157f7f96b
commit
b11e77e4c0
78 files changed
+7053
-70
No files matched your search
+155
-3
@@ -7,7 +7,7 @@
|
||||
// Сценарий:
|
||||
// фаза 1 (прямая): A,B,C online. A dm_start(B) → dm_send("hello1") → B получает напрямую.
|
||||
// фаза 2 (offline): B1 завершается (A теряет B). A dm_send("hello2") → недоступен →
|
||||
// кладёт в mailbox на C (dm_storage=yes). C видит блоб в dm_mail.
|
||||
// кладёт в mailbox на C (storage=yes). C видит блоб в dm_mail.
|
||||
// фаза 3 (reconnect): B2 переподключается к C → dm_on_conn_status → PULL mailbox → получает "hello2".
|
||||
|
||||
#include "chat_core.h"
|
||||
@@ -244,10 +244,10 @@ static void member_put_placeholder(struct UTUN_INSTANCE* inst, int idx, const ch
|
||||
NULL, 0, NULL, 0, userinfo, NULL, NULL, 0, 0, NULL);
|
||||
}
|
||||
|
||||
/* мембер C со флагом dm_storage=yes (adm_tags подписан канальным ключом) */
|
||||
/* мембер C со флагом storage=yes (adm_tags подписан канальным ключом) */
|
||||
static void member_put_storage(struct UTUN_INSTANCE* inst, int idx, const char* name) {
|
||||
char userinfo[256]; snprintf(userinfo, sizeof(userinfo), "{\"name\":\"%s\"}", name);
|
||||
char adm_tags[64]; snprintf(adm_tags, sizeof(adm_tags), "{\"dm_storage\":\"yes\",\"ver\":\"1\"}");
|
||||
char adm_tags[64]; snprintf(adm_tags, sizeof(adm_tags), "{\"storage\":\"yes\",\"ver\":\"1\"}");
|
||||
uint8_t ats[64];
|
||||
uint8_t amsg[256]; size_t off = strlen(adm_tags);
|
||||
memcpy(amsg, adm_tags, off);
|
||||
@@ -378,6 +378,108 @@ static void dm_tick(void* arg) {
|
||||
t->timer = uasync_set_timeout(t->ua, TICK_TB, t, dm_tick, "dm_tick");
|
||||
}
|
||||
|
||||
/* ── сценарий 2: первое сообщение в offline (у получателя нет беседы) ──
|
||||
* A,B,C online → B уходит offline → A шлёт ПЕРВОЕ сообщение "first" → mailbox →
|
||||
* B2 переподключается (без беседы) → тянет mailbox → получает "first". */
|
||||
enum dm2_phase {
|
||||
S2_WAIT_CONN,
|
||||
S2_SETUP,
|
||||
S2_WAIT_BGP,
|
||||
S2_B_OFFLINE,
|
||||
S2_WAIT_B_OFFLINE,
|
||||
S2_SEND, /* A: dm_start + dm_send "first" → mailbox (B offline, первое) */
|
||||
S2_WAIT_MAIL, /* C: блоб в dm_mail */
|
||||
S2_RECONNECT, /* create B2 (та же БД B, но без беседы) */
|
||||
S2_WAIT_B2, /* B2: получил "first" */
|
||||
S2_DONE,
|
||||
};
|
||||
|
||||
struct dm2_ctx {
|
||||
struct UASYNC* ua;
|
||||
struct UTUN_INSTANCE* inst[3];
|
||||
enum dm2_phase phase;
|
||||
int result;
|
||||
int ticks;
|
||||
void* timer;
|
||||
};
|
||||
|
||||
static void dm2_tick(void* arg) {
|
||||
struct dm2_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];
|
||||
struct UTUN_INSTANCE* C = t->inst[IDX_C];
|
||||
uint64_t na = g_sh.nid[IDX_A], nb = g_sh.nid[IDX_B], nc = g_sh.nid[IDX_C];
|
||||
|
||||
switch (t->phase) {
|
||||
case S2_WAIT_CONN:
|
||||
if (conn_up(A, nc) && conn_up(C, na) && conn_up(B, nc) && conn_up(C, nb))
|
||||
t->phase = S2_SETUP;
|
||||
break;
|
||||
case S2_SETUP:
|
||||
do_setup(A, IDX_A);
|
||||
do_setup(B, IDX_B);
|
||||
do_setup(C, IDX_C);
|
||||
t->phase = S2_WAIT_BGP;
|
||||
break;
|
||||
case S2_WAIT_BGP:
|
||||
if (bgp_has(A, nc) && bgp_has(A, nb) && bgp_has(C, na) && bgp_has(C, nb) && bgp_has(B, na))
|
||||
t->phase = S2_B_OFFLINE;
|
||||
break;
|
||||
case S2_B_OFFLINE:
|
||||
utun_instance_destroy(B);
|
||||
t->inst[IDX_B] = NULL;
|
||||
t->phase = S2_WAIT_B_OFFLINE;
|
||||
break;
|
||||
case S2_WAIT_B_OFFLINE:
|
||||
if (!bgp_has(A, nb)) t->phase = S2_SEND;
|
||||
break;
|
||||
case S2_SEND:
|
||||
if (dm_start(A, nb, g_sh.x_pub[IDX_B], g_sh.ed_pub[IDX_B], "B", CH_ID) != 0
|
||||
|| dm_send(A, g_sh.conv_id, "text", (const uint8_t*)"first", 5) != 0) {
|
||||
fprintf(stderr, "A: dm_start/send first failed\n");
|
||||
t->result = 2;
|
||||
} else {
|
||||
t->phase = S2_WAIT_MAIL;
|
||||
}
|
||||
break;
|
||||
case S2_WAIT_MAIL:
|
||||
if (dm_mail_count(C) > 0) t->phase = S2_RECONNECT;
|
||||
break;
|
||||
case S2_RECONNECT: {
|
||||
char cfg[512];
|
||||
snprintf(cfg, sizeof(cfg), "%s/b.conf", getenv("UTUN_TEST_DIR") ? getenv("UTUN_TEST_DIR") : ".");
|
||||
struct UTUN_INSTANCE* b2 = utun_instance_create(t->ua, cfg);
|
||||
if (!b2) { fprintf(stderr, "B2 create failed\n"); t->result = 2; break; }
|
||||
utun_instance_init(b2);
|
||||
t->inst[IDX_B] = b2;
|
||||
t->phase = S2_WAIT_B2;
|
||||
break;
|
||||
}
|
||||
case S2_WAIT_B2:
|
||||
if (dm_msg_has(B, "first") && dm_mail_count(C) == 0) t->phase = S2_DONE;
|
||||
break;
|
||||
case S2_DONE:
|
||||
t->result = 1;
|
||||
uasync_stop(t->ua);
|
||||
return;
|
||||
}
|
||||
|
||||
if (t->phase == S2_DONE) { t->result = 1; uasync_stop(t->ua); return; }
|
||||
|
||||
if (++t->ticks > MAX_TICKS) {
|
||||
fprintf(stderr, "s2: timeout in phase %d (A_conn=%d C_conn=%d B_conn=%d)\n",
|
||||
(int)t->phase,
|
||||
conn_up(A, nc), conn_up(C, na), B ? conn_up(B, nc) : -1);
|
||||
t->result = 2;
|
||||
uasync_stop(t->ua);
|
||||
return;
|
||||
}
|
||||
t->timer = uasync_set_timeout(t->ua, TICK_TB, t, dm2_tick, "dm2_tick");
|
||||
}
|
||||
|
||||
/* ── main ── */
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -447,6 +549,56 @@ int main(int argc, char** argv) {
|
||||
|
||||
char cmd[512]; snprintf(cmd, sizeof(cmd), "rm -rf %s", dir); (void)!system(cmd);
|
||||
|
||||
/* сценарий 2: отдельная топология (свежие ключи/БД) */
|
||||
char dir2[512]; snprintf(dir2, sizeof(dir2), "/tmp/utun_dm_e2e2_XXXXXX");
|
||||
if (!mkdtemp(dir2)) { fprintf(stderr, "mkdtemp dir2 failed\n"); return 1; }
|
||||
setenv("UTUN_TEST_DIR", dir2, 1);
|
||||
fill_shared(70000 + (getpid() % 2000));
|
||||
write_configs(dir2);
|
||||
|
||||
TEST("first message offline: mailbox delivery without prior conversation"); {
|
||||
struct UASYNC* ua = uasync_create();
|
||||
if (!ua) { FAIL("uasync_create failed"); return 1; }
|
||||
|
||||
struct dm2_ctx t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.ua = ua;
|
||||
t.phase = S2_WAIT_CONN;
|
||||
|
||||
char cfgA[512], cfgB[512], cfgC[512];
|
||||
snprintf(cfgA, sizeof(cfgA), "%s/a.conf", dir2);
|
||||
snprintf(cfgB, sizeof(cfgB), "%s/b.conf", dir2);
|
||||
snprintf(cfgC, sizeof(cfgC), "%s/c.conf", dir2);
|
||||
|
||||
t.inst[IDX_A] = utun_instance_create(ua, cfgA);
|
||||
t.inst[IDX_B] = utun_instance_create(ua, cfgB);
|
||||
t.inst[IDX_C] = utun_instance_create(ua, cfgC);
|
||||
|
||||
if (!t.inst[IDX_A] || !t.inst[IDX_B] || !t.inst[IDX_C]) {
|
||||
FAIL("instance create failed A=%p B=%p C=%p",
|
||||
(void*)t.inst[IDX_A], (void*)t.inst[IDX_B], (void*)t.inst[IDX_C]);
|
||||
uasync_destroy(ua, 0);
|
||||
return 1;
|
||||
}
|
||||
utun_instance_init(t.inst[IDX_A]);
|
||||
utun_instance_init(t.inst[IDX_C]);
|
||||
utun_instance_init(t.inst[IDX_B]);
|
||||
|
||||
t.timer = uasync_set_timeout(ua, TICK_TB, &t, dm2_tick, "dm2_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]);
|
||||
if (t.inst[IDX_C]) utun_instance_destroy(t.inst[IDX_C]);
|
||||
uasync_destroy(ua, 0);
|
||||
|
||||
if (ok) OK(); else FAIL("result=%d phase=%d", t.result, (int)t.phase);
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", dir2); (void)!system(cmd);
|
||||
|
||||
printf("\n%d/%d passed, %d failed\n", G_PASSED, G_TOTAL, G_FAILED);
|
||||
return G_FAILED > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user