diff --git a/tools/chatgui/transport/chat_core.c b/tools/chatgui/transport/chat_core.c index 86407860..8489e979 100644 --- a/tools/chatgui/transport/chat_core.c +++ b/tools/chatgui/transport/chat_core.c @@ -891,7 +891,8 @@ struct ca_state { struct UTUN_INSTANCE* inst; int addr_count; int pending_count; - int completed; /* 0=pending, 1=delivered */ + int delivered; /* 0=pending, 1=result already delivered */ + int cancelled; /* 1=externally cancelled, do not deliver result */ uint64_t node_id; struct ETCP_CONN** conns; void** timers; @@ -910,8 +911,8 @@ static void ca_cleanup(struct ca_state* st) { static void ca_ready_cb(struct ETCP_CONN* conn, void* arg) { struct ca_ctx* ctx = (struct ca_ctx*)arg; struct ca_state* st = ctx->state; - if (st->completed) { u_free(ctx); return; } - st->completed = 1; + if (st->delivered || st->cancelled) { u_free(ctx); return; } + st->delivered = 1; DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: auto_connect SUCCESS idx=%d peer=0x%016llx", CC_ID, ctx->addr_index, (unsigned long long)st->node_id); for (int i = 0; i < st->addr_count; i++) { @@ -926,24 +927,37 @@ static void ca_ready_cb(struct ETCP_CONN* conn, void* arg) { static void ca_timeout_cb(void* arg) { struct ca_ctx* ctx = (struct ca_ctx*)arg; struct ca_state* st = ctx->state; - if (st->completed) { u_free(ctx); return; } - if (st->conns[ctx->addr_index]) { etcp_connection_close(st->conns[ctx->addr_index]); st->conns[ctx->addr_index] = NULL; } + if (st->delivered || st->cancelled) { u_free(ctx); return; } st->timers[ctx->addr_index] = NULL; st->pending_count--; DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: auto_connect TIMEOUT idx=%d pending=%d/%d peer=0x%016llx", CC_ID, ctx->addr_index, st->pending_count, st->addr_count, (unsigned long long)st->node_id); - if (st->pending_count <= 0 && !st->completed) { - st->completed = 1; + if (st->pending_count <= 0 && !st->delivered) { + st->delivered = 1; st->result_cb(CONN_MGR_ERR_TIMEOUT, st->node_id, st->result_arg); - ca_cleanup(st); } u_free(ctx); } +void chat_core_connect_auto_cancel(void* state) { + if (!state) return; + struct ca_state* st = (struct ca_state*)state; + st->cancelled = 1; + DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: auto_connect CANCELLED peer=0x%016llx", + CC_ID, (unsigned long long)st->node_id); + for (int i = 0; i < st->addr_count; i++) { + if (st->timers[i]) { uasync_cancel_timeout(st->inst->ua, st->timers[i]); st->timers[i] = NULL; } + if (st->conns[i]) { etcp_connection_close(st->conns[i]); st->conns[i] = NULL; } + } + ca_cleanup(st); +} + void chat_core_connect_auto(uint64_t node_id, void (*cb)(int result, uint64_t node_id, void* arg), - void* arg) { + void* arg, + void** out_state) { if (!g_cc.initialized || !g_cc.inst || !cb) return; + if (out_state) *out_state = NULL; struct ETCP_SOCKET* best_socket = g_cc.inst->etcp_sockets; while (best_socket && best_socket->local_addr.ss_family != AF_INET) best_socket = best_socket->next; @@ -1048,8 +1062,11 @@ void chat_core_connect_auto(uint64_t node_id, addrs[i].port); } - if (pst->pending_count <= 0 && !pst->completed) { + if (out_state) *out_state = pst; + + if (pst->pending_count <= 0 && !pst->delivered) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: auto_connect all %d attempts failed to start", CC_ID, addr_count); + if (out_state) *out_state = NULL; cb(CONN_MGR_ERR_UNREACHABLE, node_id, arg); ca_cleanup(pst); } diff --git a/tools/chatgui/transport/chat_core.h b/tools/chatgui/transport/chat_core.h index 7ffa24cf..686dc900 100644 --- a/tools/chatgui/transport/chat_core.h +++ b/tools/chatgui/transport/chat_core.h @@ -55,10 +55,16 @@ struct chat_invite { void chat_core_connect_from_invite(struct chat_invite* inv); /* Прямое ETCP-подключение к узлу без BGP: pubkey+адреса из SQLite, - * коллбэк вызывается с CONN_MGR_OK / CONN_MGR_ERR_TIMEOUT / CONN_MGR_ERR_NO_ADDRESSES */ + * коллбэк вызывается один раз с CONN_MGR_OK / CONN_MGR_ERR_*. + * При out_state != NULL — возвращает opaque handle для отмены. */ void chat_core_connect_auto(uint64_t node_id, void (*cb)(int result, uint64_t node_id, void* arg), - void* arg); + void* arg, + void** out_state); + +/* Отмена активного auto-connect (закрывает ETCP-соединения, освобождает память). + * Коллбэк chat_core_connect_auto после cancel не вызывается. */ +void chat_core_connect_auto_cancel(void* state); /* ── Создание канала (GUI → uasync) ── */ diff --git a/tools/chatgui/transport/chat_sync.c b/tools/chatgui/transport/chat_sync.c index 723277bc..26fe06b4 100644 --- a/tools/chatgui/transport/chat_sync.c +++ b/tools/chatgui/transport/chat_sync.c @@ -24,219 +24,194 @@ static struct chat_sync* g_cs = NULL; /* ═══════════════════════════════════════════════════════════════════════ - * Auto-connect: parallel connect to SQLite peers via direct ETCP (no BGP) + * Auto-connect: cursor-based peer iteration with GC every 1s * ══════════════════════════════════════════════════════════════════════ */ -#define AC_MAX_PARALLEL 10 -#define AC_TARGET_SUCCESS 3 -#define AC_RETRY_INTERVAL_MS 10000 +#define AC_MAX_FLIGHTS 10 +#define AC_RETRY_MS 1000 +#define AC_GC_TIMEOUT_MS 3000 #define AC_ID "auto_connect" -struct auto_connect; - struct ac_flight { - struct auto_connect* ac; uint64_t node_id; + void* ca_state; /* opaque, owned by chat_core_connect_auto */ + uint64_t created_tb; /* get_time_tb() when launched */ }; struct auto_connect { struct UTUN_INSTANCE* inst; - uint64_t* node_ids; - int node_count; - int next_index; - int in_flight; - int success_count; - int total_tried; void* retry_timer; uint8_t active; + + char** channel_ids; /* loaded once, refreshed on cursor wrap */ + int channel_count; + int ch_cursor; /* current channel index */ + int peer_cursor; /* current peer index within channel */ + + struct ac_flight flights[AC_MAX_FLIGHTS]; }; static struct auto_connect* g_ac = NULL; -static void ac_result_cb(int result, uint64_t node_id, void* arg); static void ac_retry_timer_cb(void* arg); -static int ac_connect_one(struct auto_connect* ac, uint64_t nid); -static void ac_launch_batch(struct auto_connect* ac); -static int ac_collect_nodes(struct UTUN_INSTANCE* inst, uint64_t** out, int* out_count); +static void ac_result_cb(int result, uint64_t node_id, void* arg); -/* ── helper: count connected nodes (excl. self) ── */ +/* ── load channel IDs into auto_connect ── */ -static int ac_count_connected(struct UTUN_INSTANCE* inst) { - int cnt = 0; - struct ETCP_CONN* c = inst->connections; - while (c) { - if (c->peer_node_id && c->peer_node_id != inst->node_id) { - struct ETCP_LINK* l = c->links; - while (l) { if (l->initialized && l->link_status) { cnt++; break; } l = l->next; } - } - c = c->next; +static int ac_load_channels(struct auto_connect* ac) { + if (ac->channel_ids) { + for (int i = 0; i < ac->channel_count; i++) u_free(ac->channel_ids[i]); + u_free(ac->channel_ids); + ac->channel_ids = NULL; + } + ac->channel_count = 0; + uint8_t buf[4096]; size_t buf_len; + if (chat_core_list_channels(buf, sizeof(buf), &buf_len) != 0 || buf_len < 2) return 0; + uint16_t cnt; memcpy(&cnt, buf, 2); + const uint8_t* p = buf + 2; size_t rem = buf_len - 2; + ac->channel_ids = u_calloc(cnt, sizeof(char*)); + if (!ac->channel_ids) return 0; + for (uint16_t i = 0; i < cnt && rem >= 1; i++) { + uint8_t id_len = *p++; rem--; + if (rem < id_len) break; + ac->channel_ids[i] = u_malloc(id_len + 1); + if (ac->channel_ids[i]) { memcpy(ac->channel_ids[i], p, id_len); ac->channel_ids[i][id_len] = '\0'; ac->channel_count++; } + p += id_len; rem -= id_len; } - return cnt; + return ac->channel_count; } -/* ── helper: check if node already has active ETCP connection ── */ - -static int ac_node_busy(struct UTUN_INSTANCE* inst, uint64_t nid) { - struct ETCP_CONN* c = inst->connections; - while (c) { - if (c->peer_node_id == nid) { - struct ETCP_LINK* l = c->links; - while (l) { if (l->initialized && l->link_status) return 1; l = l->next; } - return 1; /* conn exists even without link_status yet */ +/* ── GC: close expired flights (no link_status after AC_GC_TIMEOUT_MS) ── */ + +static void ac_gc(struct auto_connect* ac) { + uint64_t now = get_time_tb(); + uint64_t deadline = (uint64_t)AC_GC_TIMEOUT_MS * 10; + for (int i = 0; i < AC_MAX_FLIGHTS; i++) { + if (!ac->flights[i].ca_state) continue; + if (now - ac->flights[i].created_tb < deadline) continue; + uint64_t nid = ac->flights[i].node_id; + /* check if link already UP — if so, just free slot (already connected) */ + struct ETCP_CONN* c = ac->inst->connections; + int found_up = 0; + while (c) { + if (c->peer_node_id == nid) { + struct ETCP_LINK* l = c->links; + while (l) { if (l->initialized && l->link_status) { found_up = 1; break; } l = l->next; } + if (found_up) break; + } + c = c->next; } - c = c->next; + if (found_up) { + chat_core_connect_auto_cancel(ac->flights[i].ca_state); + memset(&ac->flights[i], 0, sizeof(ac->flights[i])); + continue; + } + /* expired and not up — cancel */ + DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: GC closing flight node=0x%016llx (expired)", AC_ID, (unsigned long long)nid); + chat_core_connect_auto_cancel(ac->flights[i].ca_state); + memset(&ac->flights[i], 0, sizeof(ac->flights[i])); } - return 0; } -/* ── sort: read RTT from node_addresses, sort nodes ascending ── */ +/* ── count occupied flight slots ── */ -static void ac_sort_by_rtt(sqlite3* db, uint64_t* ids, int count) { - if (!db || count < 2) return; - struct ac_rtt_item { uint64_t id; int32_t rtt; }; - struct ac_rtt_item* items = u_malloc((size_t)count * sizeof(*items)); - if (!items) return; - for (int i = 0; i < count; i++) { - items[i].id = ids[i]; items[i].rtt = INT32_MAX; - } - sqlite3_stmt* st = NULL; - if (sqlite3_prepare_v2(db, - "SELECT node_id, rtt FROM node_addresses WHERE rtt IS NOT NULL ORDER BY rtt ASC", - -1, &st, NULL) == SQLITE_OK) { - while (sqlite3_step(st) == SQLITE_ROW) { - uint64_t nid = (uint64_t)sqlite3_column_int64(st, 0); - int32_t rtt = sqlite3_column_int(st, 1); - for (int i = 0; i < count; i++) if (items[i].id == nid) { items[i].rtt = rtt; break; } - } - sqlite3_finalize(st); - } - for (int i = 1; i < count; i++) { - struct ac_rtt_item key = items[i]; int j = i - 1; - while (j >= 0 && items[j].rtt > key.rtt) { items[j+1] = items[j]; j--; } - items[j+1] = key; - } - for (int i = 0; i < count; i++) ids[i] = items[i].id; - u_free(items); +static int ac_flight_count(struct auto_connect* ac) { + int n = 0; + for (int i = 0; i < AC_MAX_FLIGHTS; i++) if (ac->flights[i].ca_state) n++; + return n; } -/* ── collect + sort nodes from SQLite peers (all channels) ── */ +/* ── find first free flight slot ── */ -static int ac_collect_nodes(struct UTUN_INSTANCE* inst, uint64_t** out, int* out_count) { - int cap = 32, cnt = 0; - uint64_t* ids = u_malloc((size_t)cap * sizeof(uint64_t)); - if (!ids) return -1; +static int ac_find_free_slot(struct auto_connect* ac) { + for (int i = 0; i < AC_MAX_FLIGHTS; i++) if (!ac->flights[i].ca_state) return i; + return -1; +} - uint8_t ch_buf[4096]; size_t ch_len; - if (chat_core_list_channels(ch_buf, sizeof(ch_buf), &ch_len) != 0 || ch_len < 2) - { if (cnt == 0) { u_free(ids); *out = NULL; *out_count = 0; return 0; } } +/* ── check if node already has active ETCP connection ── */ - uint16_t ch_cnt; memcpy(&ch_cnt, ch_buf, 2); - const uint8_t* p = ch_buf + 2; size_t rem = ch_len - 2; - for (uint16_t ci = 0; ci < ch_cnt && rem >= 1; ci++) { - uint8_t id_len = *p++; rem--; - if (rem < id_len) break; - char cid[64]; memcpy(cid, p, id_len); cid[id_len] = '\0'; p += id_len; rem -= id_len; +static int ac_node_has_conn(struct UTUN_INSTANCE* inst, uint64_t nid) { + struct ETCP_CONN* c = inst->connections; + while (c) { + if (c->peer_node_id == nid) return 1; + c = c->next; + } + return 0; +} +/* ── fill up to AC_MAX_FLIGHTS by advancing cursor ── */ + +static void ac_fill(struct auto_connect* ac) { + if (ac->channel_count == 0) return; + /* try all channels up to 2 full cycles without finding a candidate → give up */ + int max_iter = ac->channel_count * 2 + 10; + while (ac_flight_count(ac) < AC_MAX_FLIGHTS && max_iter-- > 0) { + int slot = ac_find_free_slot(ac); + if (slot < 0) break; + char* ch_id = ac->channel_ids[ac->ch_cursor]; uint8_t pb[2048]; size_t plen; - if (chat_core_list_peers(cid, pb, sizeof(pb), &plen) != 0 || plen < 2) continue; + if (chat_core_list_peers(ch_id, pb, sizeof(pb), &plen) != 0 || plen < 2) { + /* channel has no peers or error — skip to next */ + ac->ch_cursor++; ac->peer_cursor = 0; + if (ac->ch_cursor >= ac->channel_count) { ac->ch_cursor = 0; ac_load_channels(ac); } + continue; + } uint16_t pc; memcpy(&pc, pb, 2); - for (uint16_t pj = 0; pj < pc && (size_t)(2 + (pj+1)*8) <= plen; pj++) { - uint64_t nid; memcpy(&nid, pb + 2 + pj*8, 8); - if (nid == 0 || nid == inst->node_id) continue; - if (ac_node_busy(inst, nid)) continue; - int dup = 0; - for (int j = 0; j < cnt; j++) if (ids[j] == nid) { dup = 1; break; } - if (dup) continue; - if (cnt >= cap) { cap *= 2; ids = u_realloc(ids, (size_t)cap * sizeof(uint64_t)); if (!ids) return -1; } - ids[cnt++] = nid; + if (ac->peer_cursor >= (int)pc) { + ac->ch_cursor++; ac->peer_cursor = 0; + if (ac->ch_cursor >= ac->channel_count) { ac->ch_cursor = 0; ac_load_channels(ac); } + continue; } + /* get next peer */ + uint64_t nid; memcpy(&nid, pb + 2 + ac->peer_cursor * 8, 8); + ac->peer_cursor++; + if (nid == 0 || nid == ac->inst->node_id) continue; + + /* already connected? */ + if (ac_node_has_conn(ac->inst, nid)) continue; + + /* already in a flight slot? */ + int dup = 0; + for (int i = 0; i < AC_MAX_FLIGHTS; i++) + if (ac->flights[i].ca_state && ac->flights[i].node_id == nid) { dup = 1; break; } + if (dup) continue; + + /* launch */ + struct ac_flight* f = &ac->flights[slot]; + f->node_id = nid; + f->created_tb = get_time_tb(); + chat_core_connect_auto(nid, ac_result_cb, f, &f->ca_state); + if (!f->ca_state) { + f->node_id = 0; f->created_tb = 0; + continue; + } + DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: flight[%d] launched node=0x%016llx", AC_ID, slot, (unsigned long long)nid); + uint8_t evt[7]; evt[0] = 0; uint16_t t = 0, n = (uint16_t)ac->channel_count, s = 0; + memcpy(evt + 1, &t, 2); memcpy(evt + 3, &n, 2); memcpy(evt + 5, &s, 2); + gui_bridge_post(GUI_EVT_AUTO_CONNECT_STATUS, evt, 7); } - - if (cnt > 0) { - sqlite3* db = inst->topo_groups ? inst->topo_groups->topo_sqlite_db : NULL; - if (db) ac_sort_by_rtt(db, ids, cnt); - } - *out = ids; *out_count = cnt; - return cnt; -} - -/* ── connect to node via direct ETCP (timeout handled by chat_core_connect_auto) ── */ - -static int ac_connect_one(struct auto_connect* ac, uint64_t nid) { - struct ac_flight* f = u_calloc(1, sizeof(*f)); - if (!f) return -1; - f->ac = ac; f->node_id = nid; - ac->in_flight++; - chat_core_connect_auto(nid, ac_result_cb, f); - DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: connecting to node 0x%016llx (in_flight=%d)", - AC_ID, (unsigned long long)nid, ac->in_flight); - return 0; } /* ── result callback (fired by chat_core_connect_auto) ── */ static void ac_result_cb(int result, uint64_t node_id, void* arg) { struct ac_flight* f = (struct ac_flight*)arg; - struct auto_connect* ac = f->ac; - if (!ac->active) { u_free(f); return; } - ac->in_flight--; ac->total_tried++; - if (result == CONN_MGR_OK) ac->success_count++; - DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: result node=0x%016llx result=%d succ=%d in_flight=%d total=%d", - AC_ID, (unsigned long long)node_id, result, ac->success_count, ac->in_flight, ac->total_tried); - uint8_t evt[7]; evt[0] = ac->success_count >= AC_TARGET_SUCCESS ? 1 : 0; - uint16_t t = (uint16_t)ac->total_tried, n = (uint16_t)ac->node_count, s = (uint16_t)ac->success_count; - memcpy(evt + 1, &t, 2); memcpy(evt + 3, &n, 2); memcpy(evt + 5, &s, 2); - gui_bridge_post(GUI_EVT_AUTO_CONNECT_STATUS, evt, 7); - u_free(f); - if (!ac->active) return; - while (ac->in_flight < AC_MAX_PARALLEL && ac->next_index < ac->node_count) { - uint64_t nid = ac->node_ids[ac->next_index++]; - ac_connect_one(ac, nid); - } - if (ac->in_flight == 0 && ac->success_count < AC_TARGET_SUCCESS) - DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: all tries done, %d/%d connected, waiting retry", - AC_ID, ac->success_count, ac->total_tried); -} - -/* ── launch initial batch ── */ - -static void ac_launch_batch(struct auto_connect* ac) { - while (ac->in_flight < AC_MAX_PARALLEL && ac->next_index < ac->node_count) { - uint64_t nid = ac->node_ids[ac->next_index++]; - ac_connect_one(ac, nid); - } + if (!g_ac || !g_ac->active) return; + const char* rs = result == CONN_MGR_OK ? "OK" : "FAIL"; + DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: result node=0x%016llx %s", AC_ID, (unsigned long long)node_id, rs); + /* DON'T clear ca_state — GC will close connection and free slot after AC_GC_TIMEOUT_MS */ } -/* ── retry timer callback ── */ +/* ── retry timer callback (every AC_RETRY_MS) ── */ static void ac_retry_timer_cb(void* arg) { struct auto_connect* ac = (struct auto_connect*)arg; ac->retry_timer = NULL; if (!ac->active) return; - int connected = ac_count_connected(ac->inst); - if (connected >= AC_TARGET_SUCCESS) { - DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: %d connected, target reached, stopping", - AC_ID, connected); - chat_sync_auto_connect_stop(); - return; - } - if (ac->in_flight > 0) { - DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: retry, %d in_flight, waiting", AC_ID, ac->in_flight); - } else { - if (ac->node_ids) { u_free(ac->node_ids); ac->node_ids = NULL; } - ac->node_count = 0; ac->next_index = 0; - ac_collect_nodes(ac->inst, &ac->node_ids, &ac->node_count); - DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: retry, %d nodes to try, %d connected", - AC_ID, ac->node_count, connected); - if (ac->node_count > 0) { - uint8_t evt[7]; evt[0] = 0; - uint16_t t = 0, n = (uint16_t)ac->node_count, s = (uint16_t)ac->success_count; - memcpy(evt + 1, &t, 2); memcpy(evt + 3, &n, 2); memcpy(evt + 5, &s, 2); - gui_bridge_post(GUI_EVT_AUTO_CONNECT_STATUS, evt, 7); - ac_launch_batch(ac); - } - } - ac->retry_timer = uasync_set_timeout(ac->inst->ua, AC_RETRY_INTERVAL_MS * 10, ac, ac_retry_timer_cb, "ac_retry"); + if (ac->channel_count == 0) ac_load_channels(ac); + ac_gc(ac); + ac_fill(ac); + ac->retry_timer = uasync_set_timeout(ac->inst->ua, AC_RETRY_MS * 10, ac, ac_retry_timer_cb, "ac_retry"); } /* ═══════════════════════════════════════════════════════════════════════ @@ -251,22 +226,11 @@ void chat_sync_auto_connect_start(struct UTUN_INSTANCE* inst) { ac->inst = inst; ac->active = 1; g_ac = ac; - int connected = ac_count_connected(inst); - DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: start, %d already connected", AC_ID, connected); - if (connected >= AC_TARGET_SUCCESS) { - DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: already have %d connections, idle", AC_ID, connected); - ac->retry_timer = uasync_set_timeout(inst->ua, AC_RETRY_INTERVAL_MS * 10, ac, ac_retry_timer_cb, "ac_retry"); - return; - } - ac_collect_nodes(inst, &ac->node_ids, &ac->node_count); - DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: collected %d candidate nodes", AC_ID, ac->node_count); - if (ac->node_count > 0) { - uint8_t evt[7]; evt[0] = 0; uint16_t z = 0, n = (uint16_t)ac->node_count; - memcpy(evt + 1, &z, 2); memcpy(evt + 3, &n, 2); memcpy(evt + 5, &z, 2); - gui_bridge_post(GUI_EVT_AUTO_CONNECT_STATUS, evt, 7); - ac_launch_batch(ac); - } - ac->retry_timer = uasync_set_timeout(inst->ua, AC_RETRY_INTERVAL_MS * 10, ac, ac_retry_timer_cb, "ac_retry"); + ac_load_channels(ac); + DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: start, %d channels loaded", AC_ID, ac->channel_count); + ac_gc(ac); + ac_fill(ac); + ac->retry_timer = uasync_set_timeout(inst->ua, AC_RETRY_MS * 10, ac, ac_retry_timer_cb, "ac_retry"); } void chat_sync_auto_connect_stop(void) { @@ -274,15 +238,18 @@ void chat_sync_auto_connect_stop(void) { if (!ac) return; ac->active = 0; g_ac = NULL; if (ac->retry_timer) { uasync_cancel_timeout(ac->inst->ua, ac->retry_timer); ac->retry_timer = NULL; } - uint8_t evt[7]; evt[0] = 2; - uint16_t z = 0; memcpy(evt + 1, &z, 2); memcpy(evt + 3, &z, 2); memcpy(evt + 5, &z, 2); + for (int i = 0; i < AC_MAX_FLIGHTS; i++) + if (ac->flights[i].ca_state) { chat_core_connect_auto_cancel(ac->flights[i].ca_state); memset(&ac->flights[i], 0, sizeof(ac->flights[i])); } + if (ac->channel_ids) { for (int i = 0; i < ac->channel_count; i++) u_free(ac->channel_ids[i]); u_free(ac->channel_ids); } + uint8_t evt[7]; evt[0] = 2; uint16_t z = 0; + memcpy(evt + 1, &z, 2); memcpy(evt + 3, &z, 2); memcpy(evt + 5, &z, 2); gui_bridge_post(GUI_EVT_AUTO_CONNECT_STATUS, evt, 7); - /* DON'T free ac — in-flight callbacks still reference it via ac_flight */ + u_free(ac); DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: stopped", AC_ID); } void chat_sync_auto_connect_switch_group(struct UTUN_INSTANCE* inst, uint64_t new_group_id) { - DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: switching to group 0x%016llx", AC_ID, (unsigned long long)new_group_id); + DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: switch group 0x%016llx", AC_ID, (unsigned long long)new_group_id); chat_sync_auto_connect_stop(); chat_sync_auto_connect_start(inst); }