You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

523 lines
29 KiB

/*
* topo_group_connect.c — авто-подключение к узлам группы (бесконечный цикл до успеха)
*
* Phase 1: одновременный запуск node_conn_direct_open для пиров с connected=1.
* Таймаут = TGC_DIRECT_TIMEOUT_MS. Если хоть один подключился → done.
*
* Phase 2: последовательный перебор (supernode, затем public/EIM адреса).
* После каждого TIMEOUT — пауза перед следующей попыткой.
*
* Phase 3: последовательный перебор локальных/strict NAT адресов.
* После каждого TIMEOUT — пауза перед следующей попыткой.
*
* После исчерпания Phase 3 → пауза → cycle_restart → Phase 1 (бесконечно).
*
* Пауза: активный режим — 1с; фоновый (Android standby) — standby_wait
* (burst → сразу, sleep → до следующего burst).
* При DOWN (active_conn_count → 0) — немедленный cycle_restart.
* При повторном topo_group_connect_init — destroy + fresh init.
*
* Счётчик active_conn_count — число уникальных peer-узлов с реальными соединениями.
* UP: инкремент только для первого соединения к peer_node_id.
* DOWN: декремент только если нет других conn к peer_node_id И нет indirect-путей.
*/
#include "topo_group_connect.h"
#include "topo_group.h"
#include "topo_node.h"
#include "topo_node_sqlite.h"
#include "../transport_layer/node_conn_direct.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "etcp.h"
#include "../chat/chat_event.h"
#ifdef UTUN_HAVE_STANDBY
#include "standby.h"
#endif
/* ─── внутренние константы ─── */
#define TGC_ID "topo_group_connect"
#define TGC_DIRECT_TIMEOUT_MS 2000
#define TGC_PHASE_ONE 0
#define TGC_PHASE_TWO 1
#define TGC_PHASE_THREE 2
#define TGC_PHASE_DONE 3
#define TGC_PHASE_PAUSE 4
#define TGC_MAX_HANDLES 128
#define TGC_PAUSE_ACTIVE_TB 10000 /* 1s пауза в активном режиме */
struct TOPO_GROUP_CONNECT {
struct TOPO_GROUP* group;
void* phase_timer;
void* pause_timer; /* uasync timeout handle (активный режим) */
void* pause_wait; /* standby_wait handle (фоновый режим, Android) */
uint8_t phase;
int pending;
int connected_count;
int active_conn_count;
int cursor;
int tried_super;
uint64_t* candidate_ids;
int candidate_count;
struct NODE_CONN_DIRECT* handles[TGC_MAX_HANDLES];
int handle_count;
struct NODE_CONN_DIRECT* cur_handle; /* handle текущей попытки Phase 2/3 (для закрытия на TIMEOUT) */
};
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_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 NODE_CONN_DIRECT* h, enum ncd_event event, void* arg);
/* ─── нотификация GUI о списке узлов в процессе подключения ─── */
/**
* Отправляет в GUI событие CHAT_EVT_CONNECTING_NODES — список узлов, к которым
* сейчас идёт попытка подключения. GUI по нему рисует жёлтый кружок у этих узлов.
* Пустой список (count=0) означает «сейчас ни к кому не подключаемся» —
* все жёлтые кружки снимаются.
*/
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_INFO(DEBUG_CATEGORY_BGP, "%s: notify_connecting ch=%s count=%d",
TGC_ID, gc->group->channel_id, count);
}
/* ═══════════════════════════════════════════════════════════════════════
* Жизненный цикл
* ══════════════════════════════════════════════════════════════════════ */
/**
* Запуск авто-подключения к узлам CHAT-группы.
* Если модуль уже запущен — останавливает его и стартует заново с нуля.
* Начинает с Phase 1: параллельные попытки ко всем пирам, что были connected
* в прошлой сессии (из БД). Если таких нет — сразу переходит к Phase 2.
*/
int topo_group_connect_init(struct TOPO_GROUP* group) {
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0])
return -1;
if (group->connect) topo_group_connect_destroy(group);
struct TOPO_GROUP_CONNECT* gc = u_calloc(1, sizeof(*gc));
if (!gc) return -1;
gc->group = group; gc->tried_super = 0;
group->connect = gc;
uint64_t* ids = NULL; int count = 0;
sqlite3* db = group->instance->topo_sqlite_db;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: init ch=%s db=%p", TGC_ID, group->channel_id, (void*)db);
if (!db || topo_node_sqlite_get_connected_peers(db, 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: ch=%s no connected peers → Phase 2", TGC_ID, group->channel_id);
tgc_phase2_try_next(gc);
return 0;
}
gc->phase = TGC_PHASE_ONE; gc->pending = count;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase 1 launching %d connects ch=%s grp=%016llx", TGC_ID, count, group->channel_id, (unsigned long long)group->group_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);
node_conn_direct_open(group->instance, ids[i], tgc_callback, gc,
&gc->handles[gc->handle_count++], NULL);
}
tgc_notify_connecting(gc, ids, gc->handle_count);
u_free(ids);
gc->phase_timer = uasync_set_timeout(group->instance->ua,
TGC_DIRECT_TIMEOUT_MS * 10, gc, tgc_phase1_timeout, "tgc_phase1");
return 0;
}
/**
* Полная остановка авто-подключения: отменяет все таймеры, закрывает все
* открытые соединения и освобождает память. Вызывается при удалении группы
* и при перезапуске (init/restart).
*/
void topo_group_connect_destroy(struct TOPO_GROUP* group) {
struct TOPO_GROUP_CONNECT* gc = group->connect;
if (!gc) return;
group->connect = 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; }
#ifdef UTUN_HAVE_STANDBY
if (gc->pause_wait) { standby_wait_cancel(gc->pause_wait); gc->pause_wait = NULL; }
#endif
for (int i = 0; i < gc->handle_count; i++) node_conn_direct_close(gc->handles[i]);
if (gc->cur_handle) { node_conn_direct_close(gc->cur_handle); gc->cur_handle = NULL; }
u_free(gc->candidate_ids);
u_free(gc);
}
/**
* Внешний перезапуск авто-подключения (destroy + init). Используется, например,
* при смене сети, когда надо начать поиск заново. Не трогает модуль, если уже
* есть живые соединения.
*/
void topo_group_connect_restart(struct TOPO_GROUP* group) {
struct TOPO_GROUP_CONNECT* gc = group->connect;
if (!gc) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: restart ch=%s no gc → init", TGC_ID, group->channel_id); topo_group_connect_init(group); return; }
if (gc->active_conn_count > 0) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: restart ch=%s skip active=%d", TGC_ID, group->channel_id, gc->active_conn_count); return; }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: restart ch=%s phase=%d", TGC_ID, group->channel_id, gc->phase);
topo_group_connect_destroy(group);
topo_group_connect_init(group);
}
/**
* Сколько сейчас живых соединений с пирами группы. Нужно внешней логике
* (например, chat_sync) для решения, запускать ли переподключение.
*/
int topo_group_connect_active_count(struct TOPO_GROUP* group) {
struct TOPO_GROUP_CONNECT* gc = group->connect;
return gc ? gc->active_conn_count : 0;
}
/* ═══════════════════════════════════════════════════════════════════════
* ON UP / DOWN
* ══════════════════════════════════════════════════════════════════════ */
/**
* Обработка появления соединения с пиром. Увеличивает счётчик активных и
* помечает connected=1 в БД (чтобы при следующем старте узел попал в Phase 1).
* Пропускает, если пир уже подключён через другое соединение.
*/
void topo_group_connect_on_up(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0] || !conn) return;
uint64_t peer = conn->peer_node_id;
struct TOPO_GROUP_CONNECT* gc = group->connect;
if (!gc) return;
struct ll_entry* e = group->senders_list ? group->senders_list->head : NULL;
while (e) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data;
if (item->conn && item->conn != conn && item->conn->peer_node_id == peer) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: UP peer=0x%016llx already connected via other conn, skip count",
TGC_ID, (unsigned long long)peer);
return;
}
e = e->next;
}
gc->active_conn_count++;
sqlite3* db = group->instance->topo_sqlite_db;
if (db) topo_node_sqlite_set_connected(db, group->channel_id, peer, 1);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: UP peer=0x%016llx ch=%s grp=%016llx active=%d",
TGC_ID, (unsigned long long)peer, group->channel_id, (unsigned long long)group->group_id, gc->active_conn_count);
}
/**
* Обработка обрыва соединения. Уменьшает счётчик активных и помечает
* connected=0 в БД. Если живых соединений не осталось — немедленно
* перезапускает цикл поиска с Phase 1.
*/
void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0] || !conn) return;
uint64_t peer = conn->peer_node_id;
struct TOPO_GROUP_CONNECT* gc = group->connect;
if (!gc) return;
struct ll_entry* e = group->senders_list ? group->senders_list->head : NULL;
while (e) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data;
if (item->conn && item->conn != conn && item->conn->peer_node_id == peer) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: DOWN peer=0x%016llx still has other direct conn, skip",
TGC_ID, (unsigned long long)peer);
return;
}
e = e->next;
}
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, peer);
if (nq && nq->paths) {
struct ll_entry* pe = nq->paths->head;
while (pe) {
struct TOPO_NODEPATH* path = (struct TOPO_NODEPATH*)pe;
if (path->conn && path->conn != conn) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: DOWN peer=0x%016llx has indirect path, skip",
TGC_ID, (unsigned long long)peer);
return;
}
pe = pe->next;
}
}
gc->active_conn_count--;
sqlite3* db = group->instance->topo_sqlite_db;
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",
TGC_ID, (unsigned long long)peer, group->channel_id, gc->active_conn_count);
if (gc->active_conn_count == 0) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: DOWN no connections — cycle restart ch=%s", TGC_ID, group->channel_id);
tgc_cycle_restart(gc);
}
}
/* ═══════════════════════════════════════════════════════════════════════
* Единый коллбэк для всех фаз
* ══════════════════════════════════════════════════════════════════════ */
/**
* Единый коллбэк на результат каждой попытки подключения (успех/таймаут/обрыв).
* В Phase 1 — просто считает результаты (итог подводит tgc_phase1_timeout).
* В Phase 2/3 — успех завершает цикл; провал закрывает handle (чтобы не оставить
* залипшее состояние) и ставит паузу перед следующей попыткой.
*/
static void tgc_callback(struct NODE_CONN_DIRECT* h, enum ncd_event event, void* arg) {
struct TOPO_GROUP_CONNECT* gc = (struct TOPO_GROUP_CONNECT*)arg;
struct ETCP_CONN* conn = node_conn_direct_get_conn(h);
uint64_t node_id = conn ? conn->peer_node_id : 0;
int ok = (event == NCD_EVENT_UP);
if (ok) {
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(gc->group, node_id);
if (nq) nq->handle = h;
} else if (event == NCD_EVENT_TIMEOUT) {
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(gc->group, node_id);
if (nq) nq->handle = NULL;
}
switch (gc->phase) {
case TGC_PHASE_ONE:
gc->pending--;
if (ok) gc->connected_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 result node=0x%016llx %s pending=%d connected=%d",
TGC_ID, (unsigned long long)node_id, ok ? "OK" : "FAIL", gc->pending, gc->connected_count);
break;
case TGC_PHASE_TWO:
if (ok) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id);
gc->phase = TGC_PHASE_DONE;
tgc_notify_connecting(gc, NULL, 0);
} else {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 0x%016llx FAIL — pause then try next", TGC_ID, (unsigned long long)node_id);
if (gc->cur_handle) { node_conn_direct_close(gc->cur_handle); gc->cur_handle = NULL; }
tgc_start_pause(gc, (void(*)(void*))tgc_phase2_try_next, "tgc_p2_try");
}
break;
case TGC_PHASE_THREE:
if (ok) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id);
gc->phase = TGC_PHASE_DONE;
tgc_notify_connecting(gc, NULL, 0);
} else {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 0x%016llx FAIL — pause then try next", TGC_ID, (unsigned long long)node_id);
if (gc->cur_handle) { node_conn_direct_close(gc->cur_handle); gc->cur_handle = NULL; }
tgc_start_pause(gc, (void(*)(void*))tgc_phase3_try_next, "tgc_p3_try");
}
break;
}
}
/* ═══════════════════════════════════════════════════════════════════════
* Пауза: 1s ACTIVE / 30s STANDBY
* ══════════════════════════════════════════════════════════════════════ */
/**
* Пауза между попытками подключения. Длительность зависит от активности
* приложения: 1 секунда когда приложение активно, 30 секунд когда в фоне
* (экран выключен). После паузы вызывает cb (следующая попытка или перезапуск
* цикла). На время паузы уведомляет GUI пустым списком (жёлтые кружки гаснут).
*/
static void tgc_start_pause(struct TOPO_GROUP_CONNECT* gc, void (*cb)(void*), const char* label) {
gc->phase = TGC_PHASE_PAUSE;
tgc_notify_connecting(gc, NULL, 0);
#ifdef UTUN_HAVE_STANDBY
if (standby_is_enabled()) {
/* фоновый режим: burst → standby_wait будит сразу, sleep → до следующего burst */
gc->pause_wait = standby_wait(gc, cb);
gc->pause_timer = NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: pause (standby) for ch=%s", TGC_ID, gc->group->channel_id);
return;
}
#endif
gc->pause_timer = uasync_set_timeout(gc->group->instance->ua, TGC_PAUSE_ACTIVE_TB, gc, cb, label);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: pause %dms (active) for ch=%s",
TGC_ID, TGC_PAUSE_ACTIVE_TB / 10, gc->group->channel_id);
}
/* ═══════════════════════════════════════════════════════════════════════
* Phase 1 timeout
* ══════════════════════════════════════════════════════════════════════ */
/**
* Таймаут параллельных попыток Phase 1 (2 секунды). Закрывает неудачные
* handles и сбрасывает connected в БД. Если хоть один узел подключился —
* цикл завершён; иначе переходим к Phase 2.
*/
static void tgc_phase1_timeout(void* arg) {
struct TOPO_GROUP_CONNECT* gc = (struct TOPO_GROUP_CONNECT*)arg;
gc->phase_timer = NULL;
if (gc->phase != TGC_PHASE_ONE) return;
sqlite3* db = gc->group->instance->topo_sqlite_db;
uint64_t* ids = NULL; int count = 0;
topo_node_sqlite_get_connected_peers(db, gc->group->channel_id, &ids, &count);
for (int i = 0; i < count; i++) {
int connected = 0;
for (int j = 0; j < gc->handle_count; j++) {
struct NODE_CONN_DIRECT* h = gc->handles[j];
if (h && node_conn_direct_get_conn(h) != NULL) { connected = 1; break; }
}
if (!connected) topo_node_sqlite_set_connected(db, gc->group->channel_id, ids[i], 0);
}
u_free(ids);
int remaining = 0;
for (int i = 0; i < gc->handle_count; i++) {
struct NODE_CONN_DIRECT* h = gc->handles[i];
if (h && node_conn_direct_get_conn(h) != NULL) {
gc->handles[remaining++] = h;
} else {
if (h) node_conn_direct_close(h);
}
}
gc->handle_count = remaining;
if (gc->connected_count > 0) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase1 done — %d connected, skipping Phase 2", TGC_ID, gc->connected_count);
gc->phase = TGC_PHASE_DONE;
tgc_notify_connecting(gc, NULL, 0);
return;
}
gc->phase = TGC_PHASE_TWO;
tgc_phase2_try_next(gc);
}
/* ═══════════════════════════════════════════════════════════════════════
* Phase 2
* ══════════════════════════════════════════════════════════════════════ */
/**
* Последовательный перебор узлов с публичными/EIM адресами (сначала supernode,
* затем обычные публичные). Пробует следующий узел и ставит ему жёлтый кружок.
* Когда все перебраны — переходит к Phase 3.
*/
static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) {
gc->pause_timer = NULL; gc->pause_wait = NULL;
if (gc->phase == TGC_PHASE_PAUSE) gc->phase = TGC_PHASE_TWO;
if (gc->phase != TGC_PHASE_TWO) return;
while (1) {
if (gc->candidate_count == 0) {
sqlite3* db = gc->group->instance->topo_sqlite_db;
if (gc->tried_super == 0) {
topo_node_sqlite_get_supernode_peers(db, gc->group->channel_id, &gc->candidate_ids, &gc->candidate_count);
gc->tried_super = 1; gc->cursor = 0;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 supernode round — loaded %d for ch=%s",
TGC_ID, gc->candidate_count, gc->group->channel_id);
} else if (gc->tried_super == 1) {
topo_node_sqlite_get_public_peers(db, gc->group->channel_id, &gc->candidate_ids, &gc->candidate_count,
gc->group->instance->node_id);
gc->tried_super = 2; gc->cursor = 0;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 public round — loaded %d for ch=%s",
TGC_ID, gc->candidate_count, gc->group->channel_id);
} else {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 exhausted — no connections, ch=%s", TGC_ID, gc->group->channel_id);
gc->phase = TGC_PHASE_THREE; gc->candidate_count = 0;
tgc_phase3_try_next(gc);
return;
}
}
if (gc->cursor < gc->candidate_count) {
uint64_t nid = gc->candidate_ids[gc->cursor++];
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);
if (node_conn_direct_open(gc->group->instance, nid, tgc_callback, gc, &gc->cur_handle, NULL) < 0) continue;
tgc_notify_connecting(gc, &nid, 1);
return;
}
if (gc->candidate_ids) { u_free(gc->candidate_ids); gc->candidate_ids = NULL; }
gc->candidate_count = 0;
}
}
/* ═══════════════════════════════════════════════════════════════════════
* Phase 3 — локальные/strict NAT адреса
* ══════════════════════════════════════════════════════════════════════ */
/**
* Последовательный перебор узлов с локальными/strict NAT адресами (как Phase 2,
* но для узлов, доступных только в локальной сети). Когда все перебраны —
* пауза и полный перезапуск цикла с Phase 1 (бесконечно, пока не подключимся).
*/
static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc) {
gc->pause_timer = NULL; gc->pause_wait = NULL;
if (gc->phase == TGC_PHASE_PAUSE) gc->phase = TGC_PHASE_THREE;
if (gc->phase != TGC_PHASE_THREE) return;
if (gc->candidate_count == 0) {
sqlite3* db = gc->group->instance->topo_sqlite_db;
topo_node_sqlite_get_local_peers(db, gc->group->channel_id, &gc->candidate_ids, &gc->candidate_count,
gc->group->instance->node_id);
gc->cursor = 0;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 loaded %d local peers for ch=%s",
TGC_ID, gc->candidate_count, gc->group->channel_id);
}
while (gc->cursor < gc->candidate_count) {
uint64_t nid = gc->candidate_ids[gc->cursor++];
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 trying 0x%016llx (%d/%d)", TGC_ID,
(unsigned long long)nid, gc->cursor, gc->candidate_count);
if (node_conn_direct_open(gc->group->instance, nid, tgc_callback, gc, &gc->cur_handle, NULL) < 0) continue;
tgc_notify_connecting(gc, &nid, 1);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 exhausted — pause then cycle restart, ch=%s", TGC_ID, gc->group->channel_id);
tgc_start_pause(gc, (void(*)(void*))tgc_cycle_restart, "tgc_p3_restart");
}
/* ═══════════════════════════════════════════════════════════════════════
* Полный перезапуск цикла с Phase 1 (без destroy/init)
* ══════════════════════════════════════════════════════════════════════ */
/**
* Полный перезапуск цикла с Phase 1 без уничтожения модуля. Закрывает все
* открытые соединения, сбрасывает состояние и заново запускает параллельные
* попытки ко всем connected-пирам из БД. Вызывается при обрыве всех соединений
* и после исчерпания Phase 3.
*/
static void tgc_cycle_restart(struct TOPO_GROUP_CONNECT* gc) {
gc->pause_timer = NULL; gc->pause_wait = 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++) node_conn_direct_close(gc->handles[i]);
if (gc->cur_handle) { node_conn_direct_close(gc->cur_handle); gc->cur_handle = NULL; }
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);
node_conn_direct_open(gc->group->instance, ids[i], tgc_callback, gc,
&gc->handles[gc->handle_count++], NULL);
}
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");
}