Browse Source

topo_group_connect: infinite auto-connect cycle with pause + connecting nodes notification

- client_activity: desktop/server now ACTIVE by default, only mobile starts STANDBY
- topo_group_connect: endless Phase1→2→3→1 cycle until connected
- Pause between failed Phase2/3 attempts: 1s active, 30s standby
- on_down always triggers cycle_restart when active_conn_count=0
- re-init destroys previous state and starts fresh
- CHAT_EVT_CONNECTING_NODES event notifies GUI of current connecting nodes
- Android: ConfigProvider default mode='auto', PTT fix, UDP log timer fix
topo_upd
evgeny 2 months ago
parent
commit
54ad03185b
  1. 3
      src/chat/chat_event.c
  2. 1
      src/chat/chat_event.h
  3. 130
      src/routing_layer/topo_group_connect.c
  4. 19
      src/routing_layer/topo_group_connect.h
  5. 2
      src/utun_instance.c
  6. 2
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt
  7. 13
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/MessageBubble.kt
  8. 4
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt
  9. 1
      tools/chatgui-android/jni_bridge/android_udp_log.c
  10. 1
      tools/chatgui-android/libutun_lite/instance_lite.c

3
src/chat/chat_event.c

@ -37,8 +37,9 @@ void chat_event_post(int type, const uint8_t* data, int len) {
[20]="CONN_LIST", [21]="CONN_METRICS", [20]="CONN_LIST", [21]="CONN_METRICS",
[22]="MEMBER_DETAIL", [23]="MEMBER_LIST", [22]="MEMBER_DETAIL", [23]="MEMBER_LIST",
[24]="MEMBER_UPDATED", [25]="MEMBER_REMOVED", [24]="MEMBER_UPDATED", [25]="MEMBER_REMOVED",
[26]="BGP_INFO", [27]="CONNECTING_NODES",
}; };
const char* n = (type >= 1 && type <= 25) ? names[type] : "?"; const char* n = (type >= 1 && type <= 27) ? names[type] : "?";
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "chat_event: %s(%d) data=%d bytes", n, type, len); DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "chat_event: %s(%d) data=%d bytes", n, type, len);
if (data && len > 0) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CHAT, " ", data, len); if (data && len > 0) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CHAT, " ", data, len);
} }

1
src/chat/chat_event.h

@ -45,6 +45,7 @@ extern "C" {
#define CHAT_EVT_MEMBER_UPDATED 24 /* [ch_id_len:1][ch_id:var][count:1][member:79B] — обновление одного мембера */ #define CHAT_EVT_MEMBER_UPDATED 24 /* [ch_id_len:1][ch_id:var][count:1][member:79B] — обновление одного мембера */
#define CHAT_EVT_MEMBER_REMOVED 25 /* [ch_id_len:1][ch_id:var][node_id:8] — мембер удалён из канала */ #define CHAT_EVT_MEMBER_REMOVED 25 /* [ch_id_len:1][ch_id:var][node_id:8] — мембер удалён из канала */
#define CHAT_EVT_BGP_INFO 26 /* текст: "rtt:XXms hop:X via bgp: [XXXX] name1 [XXXX] name2" */ #define CHAT_EVT_BGP_INFO 26 /* текст: "rtt:XXms hop:X via bgp: [XXXX] name1 [XXXX] name2" */
#define CHAT_EVT_CONNECTING_NODES 27 /* [ch_id_len:1][ch_id:var][count:2][node_id:8]* */
typedef void (*chat_event_handler_fn)(int type, const uint8_t* data, int len); typedef void (*chat_event_handler_fn)(int type, const uint8_t* data, int len);

130
src/routing_layer/topo_group_connect.c

@ -1,19 +1,24 @@
/* /*
* topo_group_connect.c — авто-подключение к узлам группы при старте * topo_group_connect.c — авто-подключение к узлам группы (бесконечный цикл до успеха)
* *
* Phase 1: одновременный запуск conn_mgr_open_invite для пиров с connected=1. * Phase 1: одновременный запуск conn_mgr_open_invite для пиров с connected=1.
* Таймаут = TGC_DIRECT_TIMEOUT_MS. По таймауту закрываем незавершённые handle'ы * Таймаут = TGC_DIRECT_TIMEOUT_MS. Если хоть один подключился → done.
* и помечаем connected=0 в БД. Если хоть один подключился → done.
* *
* Phase 2: последовательный перебор пиров с прямыми/EIM адресами. * Phase 2: последовательный перебор (supernode, затем public/EIM адреса).
* После каждого TIMEOUT — пауза перед следующей попыткой.
* *
* Phase 3: последовательный перебор пиров с локальными/strict NAT. * Phase 3: последовательный перебор локальных/strict NAT адресов.
* После каждого TIMEOUT — пауза перед следующей попыткой.
*
* После исчерпания Phase 3 → пауза → cycle_restart → Phase 1 (бесконечно).
*
* Пауза: 1с когда client_activity == ACTIVE, 30с когда STANDBY (Android фон).
* При DOWN (active_conn_count → 0) — немедленный cycle_restart.
* При повторном topo_group_connect_init — destroy + fresh init.
* *
* Счётчик active_conn_count — число уникальных peer-узлов с реальными соединениями. * Счётчик active_conn_count — число уникальных peer-узлов с реальными соединениями.
* UP: инкремент только для первого соединения к peer_node_id. * UP: инкремент только для первого соединения к peer_node_id.
* DOWN: декремент только если нет других conn к peer_node_id И нет indirect-путей в nodeinfo. * DOWN: декремент только если нет других conn к peer_node_id И нет indirect-путей.
*
* restart: перезапуск авто-подключения при phase=done и active_conn_count==0.
*/ */
#include "topo_group_connect.h" #include "topo_group_connect.h"
@ -26,6 +31,7 @@
#include "../lib/u_async.h" #include "../lib/u_async.h"
#include "../lib/ll_queue.h" #include "../lib/ll_queue.h"
#include "etcp.h" #include "etcp.h"
#include "../chat/chat_event.h"
/* ─── внутренние константы ─── */ /* ─── внутренние константы ─── */
#define TGC_ID "topo_group_connect" #define TGC_ID "topo_group_connect"
@ -34,13 +40,16 @@
#define TGC_PHASE_TWO 1 #define TGC_PHASE_TWO 1
#define TGC_PHASE_THREE 2 #define TGC_PHASE_THREE 2
#define TGC_PHASE_DONE 3 #define TGC_PHASE_DONE 3
#define TGC_PHASE_PAUSE 4
#define TGC_MAX_HANDLES 128 #define TGC_MAX_HANDLES 128
#define TGC_PAUSE_ACTIVE_TB 10000 /* 1s пауза когда client_activity == ACTIVE */
#define TGC_PAUSE_IDLE_TB 300000 /* 30s пауза когда STANDBY (Android фон) */
struct TOPO_GROUP_CONNECT { struct TOPO_GROUP_CONNECT {
struct TOPO_GROUP* group; struct TOPO_GROUP* group;
void* phase_timer; void* phase_timer;
void* pause_timer;
uint8_t phase; uint8_t phase;
uint8_t active;
int pending; int pending;
int connected_count; int connected_count;
int active_conn_count; int active_conn_count;
@ -55,20 +64,39 @@ struct TOPO_GROUP_CONNECT {
static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc); static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc);
static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc); static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc);
static void tgc_phase1_timeout(void* arg); static void tgc_phase1_timeout(void* arg);
static void tgc_cycle_restart(struct TOPO_GROUP_CONNECT* gc);
static void tgc_start_pause(struct TOPO_GROUP_CONNECT* gc, void (*cb)(void*), const char* label);
static void tgc_callback(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t group_id, static void tgc_callback(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t group_id,
enum conn_mgr_event event, void* arg); enum conn_mgr_event event, void* arg);
/* ─── нотификация GUI о списке узлов в процессе подключения ─── */
static void tgc_notify_connecting(struct TOPO_GROUP_CONNECT* gc, const uint64_t* ids, int count) {
size_t cl = strlen(gc->group->channel_id); if (cl > 255) cl = 255;
size_t sz = 1 + cl + 2 + (size_t)count * 8;
uint8_t* buf = u_malloc(sz); if (!buf) return;
uint8_t* p = buf;
*p++ = (uint8_t)cl; memcpy(p, gc->group->channel_id, cl); p += cl;
uint16_t c = (uint16_t)count; memcpy(p, &c, 2); p += 2;
for (int i = 0; i < count; i++) { memcpy(p, &ids[i], 8); p += 8; }
chat_event_post(CHAT_EVT_CONNECTING_NODES, buf, (int)sz);
u_free(buf);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: notify_connecting ch=%s count=%d",
TGC_ID, gc->group->channel_id, count);
}
/* ═══════════════════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════════════════
* Жизненный цикл * Жизненный цикл
* ══════════════════════════════════════════════════════════════════════ */ * ══════════════════════════════════════════════════════════════════════ */
int topo_group_connect_init(struct TOPO_GROUP* group) { int topo_group_connect_init(struct TOPO_GROUP* group) {
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0] || group->connect) if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0])
return -1; return -1;
if (group->connect) topo_group_connect_destroy(group);
struct TOPO_GROUP_CONNECT* gc = u_calloc(1, sizeof(*gc)); struct TOPO_GROUP_CONNECT* gc = u_calloc(1, sizeof(*gc));
if (!gc) return -1; if (!gc) return -1;
gc->group = group; gc->active = 1; gc->tried_super = 0; gc->group = group; gc->tried_super = 0;
group->connect = gc; group->connect = gc;
uint64_t* ids = NULL; int count = 0; uint64_t* ids = NULL; int count = 0;
@ -89,6 +117,7 @@ int topo_group_connect_init(struct TOPO_GROUP* group) {
conn_mgr_open_invite(group->instance, group->group_id, NULL, ids[i], tgc_callback, gc, conn_mgr_open_invite(group->instance, group->group_id, NULL, ids[i], tgc_callback, gc,
&gc->handles[gc->handle_count++]); &gc->handles[gc->handle_count++]);
} }
tgc_notify_connecting(gc, ids, gc->handle_count);
u_free(ids); u_free(ids);
gc->phase_timer = uasync_set_timeout(group->instance->ua, gc->phase_timer = uasync_set_timeout(group->instance->ua,
@ -101,6 +130,7 @@ void topo_group_connect_destroy(struct TOPO_GROUP* group) {
if (!gc) return; if (!gc) return;
group->connect = NULL; group->connect = NULL;
if (gc->phase_timer) { uasync_cancel_timeout(group->instance->ua, gc->phase_timer); gc->phase_timer = NULL; } if (gc->phase_timer) { uasync_cancel_timeout(group->instance->ua, gc->phase_timer); gc->phase_timer = NULL; }
if (gc->pause_timer) { uasync_cancel_timeout(group->instance->ua, gc->pause_timer); gc->pause_timer = NULL; }
for (int i = 0; i < gc->handle_count; i++) conn_mgr_close(gc->handles[i]); for (int i = 0; i < gc->handle_count; i++) conn_mgr_close(gc->handles[i]);
u_free(gc->candidate_ids); u_free(gc->candidate_ids);
u_free(gc); u_free(gc);
@ -184,9 +214,9 @@ void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn
if (db) topo_node_sqlite_set_connected(db, group->channel_id, peer, 0); if (db) topo_node_sqlite_set_connected(db, group->channel_id, peer, 0);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: DOWN peer=0x%016llx ch=%s active=%d", DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: DOWN peer=0x%016llx ch=%s active=%d",
TGC_ID, (unsigned long long)peer, group->channel_id, gc->active_conn_count); TGC_ID, (unsigned long long)peer, group->channel_id, gc->active_conn_count);
if (gc->phase == TGC_PHASE_DONE && gc->active_conn_count == 0) { if (gc->active_conn_count == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: DOWN restart eligible ch=%s → restarting", TGC_ID, group->channel_id); DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: DOWN no connections — cycle restart ch=%s", TGC_ID, group->channel_id);
topo_group_connect_restart(group); tgc_cycle_restart(gc);
} }
} }
@ -220,23 +250,39 @@ static void tgc_callback(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t g
if (ok) { if (ok) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id); DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id);
gc->phase = TGC_PHASE_DONE; gc->phase = TGC_PHASE_DONE;
tgc_notify_connecting(gc, NULL, 0);
} else { } else {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 0x%016llx FAIL — try next", TGC_ID, (unsigned long long)node_id); DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 0x%016llx FAIL — pause then try next", TGC_ID, (unsigned long long)node_id);
tgc_phase2_try_next(gc); tgc_start_pause(gc, (void(*)(void*))tgc_phase2_try_next, "tgc_p2_try");
} }
break; break;
case TGC_PHASE_THREE: case TGC_PHASE_THREE:
if (ok) { if (ok) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id); DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id);
gc->phase = TGC_PHASE_DONE; gc->phase = TGC_PHASE_DONE;
tgc_notify_connecting(gc, NULL, 0);
} else { } else {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 0x%016llx FAIL — try next", TGC_ID, (unsigned long long)node_id); DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 0x%016llx FAIL — pause then try next", TGC_ID, (unsigned long long)node_id);
tgc_phase3_try_next(gc); tgc_start_pause(gc, (void(*)(void*))tgc_phase3_try_next, "tgc_p3_try");
} }
break; break;
} }
} }
/* ═══════════════════════════════════════════════════════════════════════
* Пауза: 1s ACTIVE / 30s STANDBY
* ══════════════════════════════════════════════════════════════════════ */
static void tgc_start_pause(struct TOPO_GROUP_CONNECT* gc, void (*cb)(void*), const char* label) {
gc->phase = TGC_PHASE_PAUSE;
int active = (gc->group->instance->client_activity == CLIENT_ACTIVITY_ACTIVE);
int delay_tb = active ? TGC_PAUSE_ACTIVE_TB : TGC_PAUSE_IDLE_TB;
gc->pause_timer = uasync_set_timeout(gc->group->instance->ua, delay_tb, gc, cb, label);
tgc_notify_connecting(gc, NULL, 0);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: pause %dms (activity=%s) for ch=%s",
TGC_ID, delay_tb / 10, active ? "ACTIVE" : "STANDBY", gc->group->channel_id);
}
/* ═══════════════════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════════════════
* Phase 1 timeout * Phase 1 timeout
* ══════════════════════════════════════════════════════════════════════ */ * ══════════════════════════════════════════════════════════════════════ */
@ -273,6 +319,7 @@ static void tgc_phase1_timeout(void* arg) {
if (gc->connected_count > 0) { if (gc->connected_count > 0) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase1 done — %d connected, skipping Phase 2", TGC_ID, gc->connected_count); DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase1 done — %d connected, skipping Phase 2", TGC_ID, gc->connected_count);
gc->phase = TGC_PHASE_DONE; gc->phase = TGC_PHASE_DONE;
tgc_notify_connecting(gc, NULL, 0);
return; return;
} }
gc->phase = TGC_PHASE_TWO; gc->phase = TGC_PHASE_TWO;
@ -284,6 +331,7 @@ static void tgc_phase1_timeout(void* arg) {
* ══════════════════════════════════════════════════════════════════════ */ * ══════════════════════════════════════════════════════════════════════ */
static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) { static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) {
if (gc->phase == TGC_PHASE_PAUSE) gc->phase = TGC_PHASE_TWO;
if (gc->phase != TGC_PHASE_TWO) return; if (gc->phase != TGC_PHASE_TWO) return;
while (1) { while (1) {
if (gc->candidate_count == 0) { if (gc->candidate_count == 0) {
@ -311,6 +359,7 @@ static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 trying 0x%016llx (%d/%d) super_round=%d", TGC_ID, DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 trying 0x%016llx (%d/%d) super_round=%d", TGC_ID,
(unsigned long long)nid, gc->cursor, gc->candidate_count, gc->tried_super); (unsigned long long)nid, gc->cursor, gc->candidate_count, gc->tried_super);
if (conn_mgr_open_invite(gc->group->instance, gc->group->group_id, NULL, nid, tgc_callback, gc, NULL) < 0) continue; if (conn_mgr_open_invite(gc->group->instance, gc->group->group_id, NULL, nid, tgc_callback, gc, NULL) < 0) continue;
tgc_notify_connecting(gc, &nid, 1);
return; return;
} }
if (gc->candidate_ids) { u_free(gc->candidate_ids); gc->candidate_ids = NULL; } if (gc->candidate_ids) { u_free(gc->candidate_ids); gc->candidate_ids = NULL; }
@ -323,6 +372,7 @@ static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) {
* ══════════════════════════════════════════════════════════════════════ */ * ══════════════════════════════════════════════════════════════════════ */
static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc) { static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc) {
if (gc->phase == TGC_PHASE_PAUSE) gc->phase = TGC_PHASE_THREE;
if (gc->phase != TGC_PHASE_THREE) return; if (gc->phase != TGC_PHASE_THREE) return;
if (gc->candidate_count == 0) { if (gc->candidate_count == 0) {
sqlite3* db = gc->group->instance->topo_sqlite_db; sqlite3* db = gc->group->instance->topo_sqlite_db;
@ -337,8 +387,48 @@ static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 trying 0x%016llx (%d/%d)", TGC_ID, DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 trying 0x%016llx (%d/%d)", TGC_ID,
(unsigned long long)nid, gc->cursor, gc->candidate_count); (unsigned long long)nid, gc->cursor, gc->candidate_count);
if (conn_mgr_open_invite(gc->group->instance, gc->group->group_id, NULL, nid, tgc_callback, gc, NULL) < 0) continue; if (conn_mgr_open_invite(gc->group->instance, gc->group->group_id, NULL, nid, tgc_callback, gc, NULL) < 0) continue;
tgc_notify_connecting(gc, &nid, 1);
return; return;
} }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 exhausted — no connections, ch=%s", TGC_ID, gc->group->channel_id); DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 exhausted — pause then cycle restart, ch=%s", TGC_ID, gc->group->channel_id);
gc->phase = TGC_PHASE_DONE; tgc_start_pause(gc, (void(*)(void*))tgc_cycle_restart, "tgc_p3_restart");
}
/* ═══════════════════════════════════════════════════════════════════════
* Полный перезапуск цикла с Phase 1 (без destroy/init)
* ══════════════════════════════════════════════════════════════════════ */
static void tgc_cycle_restart(struct TOPO_GROUP_CONNECT* gc) {
gc->pause_timer = NULL;
if (gc->phase_timer) { uasync_cancel_timeout(gc->group->instance->ua, gc->phase_timer); gc->phase_timer = NULL; }
for (int i = 0; i < gc->handle_count; i++) conn_mgr_close(gc->handles[i]);
gc->handle_count = 0; gc->pending = 0; gc->connected_count = 0;
gc->cursor = 0; gc->tried_super = 0;
u_free(gc->candidate_ids); gc->candidate_ids = NULL; gc->candidate_count = 0;
uint64_t* ids = NULL; int count = 0;
sqlite3* db = gc->group->instance->topo_sqlite_db;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: cycle_restart ch=%s active=%d",
TGC_ID, gc->group->channel_id, gc->active_conn_count);
if (!db || topo_node_sqlite_get_connected_peers(db, gc->group->channel_id, &ids, &count) != 0 || count == 0) {
if (ids) { u_free(ids); ids = NULL; }
gc->phase = TGC_PHASE_TWO;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: cycle_restart ch=%s no connected peers → Phase 2", TGC_ID, gc->group->channel_id);
tgc_phase2_try_next(gc);
return;
}
gc->phase = TGC_PHASE_ONE; gc->pending = count;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: cycle_restart Phase 1 launching %d connects ch=%s",
TGC_ID, count, gc->group->channel_id);
for (int i = 0; i < count && gc->handle_count < TGC_MAX_HANDLES; i++) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 connect to 0x%016llx (%d/%d)", TGC_ID, (unsigned long long)ids[i], i + 1, count);
conn_mgr_open_invite(gc->group->instance, gc->group->group_id, NULL, ids[i], tgc_callback, gc,
&gc->handles[gc->handle_count++]);
}
tgc_notify_connecting(gc, ids, gc->handle_count);
u_free(ids);
gc->phase_timer = uasync_set_timeout(gc->group->instance->ua,
TGC_DIRECT_TIMEOUT_MS * 10, gc, tgc_phase1_timeout, "tgc_phase1");
} }

19
src/routing_layer/topo_group_connect.h

@ -1,16 +1,21 @@
/** /**
* @file topo_group_connect.h * @file topo_group_connect.h
* @brief Авто-подключение к узлам группы при старте и отслеживание состояния connected в БД. * @brief Авто-подключение к узлам группы — бесконечный цикл до успеха.
* *
* При старте CHAT-группы: * При старте CHAT-группы (topo_group_connect_init):
* Phase 1 — одновременный запуск подключений ко всем пирам с connected=1. * Phase 1 — параллельный запуск ко всем пирам с connected=1 (таймаут 2с).
* Если хоть один подключился → done. * Если хоть один подключился → done.
* Phase 2 — последовательный перебор по пирам с публичными адресами. * Phase 2 — последовательный перебор (supernode, затем public/EIM).
* До первого успешного подключения. * После каждого TIMEOUT — пауза перед следующей попыткой.
* Phase 3 — последовательный перебор локальных/strict NAT.
* После каждого TIMEOUT — пауза перед следующей попыткой.
* После исчерпания Phase 3 → пауза → cycle_restart → Phase 1 (бесконечно).
*
* Пауза: 1с когда client_activity == ACTIVE, 30с когда STANDBY (Android фон).
* При DOWN (active_conn_count → 0) — немедленный cycle_restart из любой фазы.
* При повторном вызове topo_group_connect_init — destroy + fresh init.
* *
* Отслеживание: на UP → peers_<ch>.connected=1, на DOWN (последний conn) → 0.
* active_conn_count — количество уникальных peer-узлов с живыми соединениями. * active_conn_count — количество уникальных peer-узлов с живыми соединениями.
* restart — перезапуск авто-подключения (если phase=done и нет подключений).
*/ */
#ifndef TOPO_GROUP_CONNECT_H #ifndef TOPO_GROUP_CONNECT_H
#define TOPO_GROUP_CONNECT_H #define TOPO_GROUP_CONNECT_H

2
src/utun_instance.c

@ -102,7 +102,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
// Set client type // Set client type
instance->client_type = config->global.client_type; instance->client_type = config->global.client_type;
instance->keepalive_interval = (uint16_t)(config->global.keepalive_interval > 0 ? config->global.keepalive_interval : 200); instance->keepalive_interval = (uint16_t)(config->global.keepalive_interval > 0 ? config->global.keepalive_interval : 200);
instance->client_activity = (instance->client_type == CLIENT_TYPE_SERVER) ? CLIENT_ACTIVITY_ACTIVE : CLIENT_ACTIVITY_STANDBY; instance->client_activity = (instance->client_type == CLIENT_TYPE_MOBILE) ? CLIENT_ACTIVITY_STANDBY : CLIENT_ACTIVITY_ACTIVE;
instance->client_activity_timer = NULL; instance->client_activity_timer = NULL;
// Set my keys // Set my keys

2
tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt

@ -191,7 +191,7 @@ class ConfigProvider(private val context: Context) {
/* ── Connection mode ── */ /* ── Connection mode ── */
fun getConnectionMode(): String = runBlocking { fun getConnectionMode(): String = runBlocking {
context.dataStore.data.first()[connectionModeKey] ?: "manual" context.dataStore.data.first()[connectionModeKey] ?: "auto"
} }
suspend fun setConnectionMode(mode: String) { suspend fun setConnectionMode(mode: String) {

13
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/MessageBubble.kt

@ -378,7 +378,10 @@ fun InputBar(
modifier = Modifier modifier = Modifier
.size(48.dp) .size(48.dp)
.background(pttBg, RoundedCornerShape(4.dp)) .background(pttBg, RoundedCornerShape(4.dp))
.pointerInput(hasMicPerm) { .then(if (!hasMicPerm) {
Modifier.clickable { permLauncher.launch(Manifest.permission.RECORD_AUDIO) }
} else {
Modifier.pointerInput(Unit) {
awaitPointerEventScope { awaitPointerEventScope {
while (true) { while (true) {
val down = awaitPointerEvent() val down = awaitPointerEvent()
@ -386,11 +389,6 @@ fun InputBar(
if (!ch.pressed) continue if (!ch.pressed) continue
ch.consume() ch.consume()
if (!hasMicPerm) {
permLauncher.launch(Manifest.permission.RECORD_AUDIO)
continue
}
onPttPressed() onPttPressed()
var locked = false var locked = false
var totalDragY = 0f var totalDragY = 0f
@ -412,7 +410,8 @@ fun InputBar(
onPttReleased() onPttReleased()
} }
} }
}, }
}),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text("\uD83C\uDFA4", fontSize = 20.sp, color = if (isRecording) Color.White else Color.Unspecified) Text("\uD83C\uDFA4", fontSize = 20.sp, color = if (isRecording) Color.White else Color.Unspecified)

4
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt

@ -209,10 +209,10 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
/* ── Connection Mode ── */ /* ── Connection Mode ── */
Text("Network Interfaces", style = MaterialTheme.typography.titleMedium) Text("Network Interfaces", style = MaterialTheme.typography.titleMedium)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
FilterChip(selected = connectionMode == "manual", onClick = { if (connectionMode != "manual") switchMode("manual") },
label = { Text("Manual") })
FilterChip(selected = connectionMode == "auto", onClick = { if (connectionMode != "auto") switchMode("auto") }, FilterChip(selected = connectionMode == "auto", onClick = { if (connectionMode != "auto") switchMode("auto") },
label = { Text("Auto") }) label = { Text("Auto") })
FilterChip(selected = connectionMode == "manual", onClick = { if (connectionMode != "manual") switchMode("manual") },
label = { Text("Manual") })
} }
if (connectionMode == "auto") { if (connectionMode == "auto") {

1
tools/chatgui-android/jni_bridge/android_udp_log.c

@ -37,6 +37,7 @@ static int g_udp_send_errors = 0;
static void udp_reschedule_timer(void) { static void udp_reschedule_timer(void) {
if (g_udp_stopping) return; if (g_udp_stopping) return;
if (g_udp_timer) return;
struct UASYNC* ua = instance_lite_get_uasync(); struct UASYNC* ua = instance_lite_get_uasync();
if (ua) g_udp_timer = uasync_set_timeout(ua, UDP_FLUSH_TB, NULL, udp_trampoline, "udp_log_flush"); if (ua) g_udp_timer = uasync_set_timeout(ua, UDP_FLUSH_TB, NULL, udp_trampoline, "udp_log_flush");
} }

1
tools/chatgui-android/libutun_lite/instance_lite.c

@ -236,7 +236,6 @@ static void* instance_thread(void* arg) {
if (config->global.log_udp_ip[0] && config->global.log_udp_port > 0) { if (config->global.log_udp_ip[0] && config->global.log_udp_port > 0) {
udp_log_set_target(config->global.log_udp_ip, config->global.log_udp_port); udp_log_set_target(config->global.log_udp_ip, config->global.log_udp_port);
uasync_set_timeout(g_ua, 2000, NULL, udp_trampoline, "udp_log_flush");
} }
IL_LOGI("[EVT_DIAG] thread: about to set chat_event handler, g_event_handler=%p", (void*)g_event_handler); IL_LOGI("[EVT_DIAG] thread: about to set chat_event handler, g_event_handler=%p", (void*)g_event_handler);

Loading…
Cancel
Save