#include "gui_bridge.h" #include #include #include #include extern "C" { #include "../../../lib/u_async.h" #include "../../../lib/mem.h" #include "../../../lib/debug_config.h" #include "../../../src/routing_layer/topo_node.h" #include "../../../src/routing_layer/topo_group.h" } /* ── Внутренний объект-приёмник в GUI-потоке ── */ class GuiBridgeReceiver : public QObject { Q_OBJECT public: void* ua = nullptr; GuiBridgeReceiver(QObject* parent = nullptr) : QObject(parent) {} Q_INVOKABLE void processPost(int eventType, QByteArray data); }; static GuiBridgeReceiver* g_receiver = nullptr; static gui_connect_result_fn g_connect_result_cb = nullptr; static gui_msg_received_fn g_msg_received_cb = nullptr; static gui_channel_updated_fn g_channel_updated_cb = nullptr; static gui_my_node_id_fn g_my_node_id_cb = nullptr; static gui_auto_connect_status_fn g_auto_connect_status_cb = nullptr; static gui_channel_peers_online_fn g_channel_peers_online_cb = nullptr; static gui_db_ready_fn g_db_ready_cb = nullptr; static gui_status_refresh_fn g_status_refresh_cb = nullptr; static gui_attachment_downloaded_fn g_attachment_downloaded_cb = nullptr; static gui_download_progress_fn g_download_progress_cb = nullptr; static gui_nodeinfo_update_fn g_nodeinfo_update_cb = nullptr; static gui_invite_received_fn g_invite_received_cb = nullptr; static gui_conn_list_fn g_conn_list_cb = nullptr; static gui_conn_metrics_fn g_conn_metrics_cb = nullptr; static gui_member_detail_fn g_member_detail_cb = nullptr; static gui_member_list_fn g_member_list_cb = nullptr; static gui_member_updated_fn g_member_updated_cb = nullptr; static gui_member_removed_fn g_member_removed_cb = nullptr; static gui_bgp_info_fn g_bgp_info_cb = nullptr; static gui_admin_key_received_fn g_admin_key_received_cb = nullptr; static gui_invite_link_ready_fn g_invite_link_ready_cb = nullptr; static gui_invite_candidates_fn g_invite_candidates_cb = nullptr; static gui_call_incoming_fn g_call_incoming_cb = nullptr; static gui_call_id_fn g_call_ringing_cb = nullptr; static gui_call_id_fn g_call_accepted_cb = nullptr; static gui_call_ended_fn g_call_ended_cb = nullptr; static gui_call_ended_fn g_call_declined_cb = nullptr; static gui_call_error_fn g_call_error_cb = nullptr; static gui_call_path_fn g_call_path_cb = nullptr; static gui_radio_talk_fn g_radio_talk_cb = nullptr; static gui_dm_msg_received_fn g_dm_msg_received_cb = nullptr; static gui_dm_conv_updated_fn g_dm_conv_updated_cb = nullptr; static gui_dm_messages_fn g_dm_messages_cb = nullptr; static struct UASYNC* g_ua = nullptr; static struct UTUN_INSTANCE* g_inst = nullptr; /* ── GuiBridgeReceiver implementation ── */ void GuiBridgeReceiver::processPost(int eventType, QByteArray data) { const uint8_t* d = (const uint8_t*)data.constData(); int dlen = data.size(); switch (eventType) { case GUI_EVT_MSG_RECEIVED: if (dlen >= 2) { uint8_t chLen = d[0]; if (dlen >= 1 + chLen + 8) { uint64_t author = 0; memcpy(&author, d + 1 + chLen, 8); QString chId = QString::fromUtf8((const char*)d + 1, chLen); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: MSG_RECEIVED ch=%s author=%llu", qPrintable(chId), (unsigned long long)author); if (g_msg_received_cb) g_msg_received_cb((const char*)d + 1, chLen, author); } else { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: MSG_RECEIVED invalid data length %d", dlen); } } else { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: MSG_RECEIVED data too short %d", dlen); } break; case GUI_EVT_CONNECT_RESULT: if (dlen >= 20) { uint64_t nodeId, channelId; int result; memcpy(&nodeId, d, 8); memcpy(&result, d + 8, 4); memcpy(&channelId, d + 12, 8); DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "gui_bridge: CONNECT_RESULT node=0x%016llx ch=%llu result=%d", (unsigned long long)nodeId, (unsigned long long)channelId, result); if (g_connect_result_cb) g_connect_result_cb(nodeId, channelId, result); } else { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: CONNECT_RESULT data too short %d", dlen); } break; case GUI_EVT_NEW_PEER: if (dlen >= 8) { uint64_t nodeId; memcpy(&nodeId, d, 8); DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "gui_bridge: NEW_PEER node=0x%016llx", (unsigned long long)nodeId); } else { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: NEW_PEER data too short %d", dlen); } break; case GUI_EVT_CHANNEL_UPDATED: if (dlen >= 2) { uint8_t chLen = d[0]; if (dlen >= 1 + chLen) { QString chId = QString::fromUtf8((const char*)d + 1, chLen); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: CHANNEL_UPDATED ch=%s", qPrintable(chId)); if (g_channel_updated_cb) g_channel_updated_cb((const char*)d + 1, chLen); } } else { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: CHANNEL_UPDATED data too short %d", dlen); } break; case GUI_EVT_MY_NODE_ID: if (dlen >= 8) { uint64_t nid; memcpy(&nid, d, 8); if (g_my_node_id_cb) g_my_node_id_cb(nid); } break; case GUI_EVT_AUTO_CONNECT_STATUS: if (dlen >= 7) { uint8_t status = d[0]; uint16_t total_tried; memcpy(&total_tried, d + 1, 2); uint16_t node_count; memcpy(&node_count, d + 3, 2); uint16_t connected; memcpy(&connected, d + 5, 2); const char* st_str = status == 0 ? "in_progress" : status == 1 ? "done" : "stopped"; if (status != 0) DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "gui_bridge: AUTO_CONNECT %s tried=%u/%u connected=%u", st_str, total_tried, node_count, connected); if (g_auto_connect_status_cb) g_auto_connect_status_cb(status, total_tried, node_count, connected); } break; case GUI_EVT_CHANNEL_PEERS_ONLINE: if (dlen >= 3) { uint8_t chLen = d[0]; if (dlen >= 3 + chLen) { uint16_t online; memcpy(&online, d + 1 + chLen, 2); static char lastChId[256] = {0}; static uint16_t lastOnline = 0xFFFF; if (chLen != (uint8_t)strlen(lastChId) || memcmp(lastChId, d + 1, chLen) != 0 || online != lastOnline) { memcpy(lastChId, d + 1, chLen); lastChId[chLen] = '\0'; lastOnline = online; DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: CHANNEL_PEERS_ONLINE ch=%.*s online=%u", chLen, (const char*)d + 1, online); } if (g_channel_peers_online_cb) g_channel_peers_online_cb((const char*)d + 1, chLen, online); } } break; case GUI_EVT_DB_READY: DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: DB_READY (chat DB tables created)"); if (g_db_ready_cb) g_db_ready_cb(); break; case GUI_EVT_STATUS_REFRESH: if (g_status_refresh_cb) g_status_refresh_cb((const char*)d, dlen); break; case GUI_EVT_NODE_CHANGED: if (dlen >= 8) { uint64_t nodeId; memcpy(&nodeId, d, 8); DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "gui_bridge: NODE_CHANGED node=0x%016llx — pubkey changed, connection closed", (unsigned long long)nodeId); } else { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: NODE_CHANGED data too short %d", dlen); } break; case GUI_EVT_ATTACHMENT_DOWNLOADED: if (dlen >= 2) { uint8_t chLen = d[0]; if (dlen >= 1 + chLen + 8) { int64_t msgId = 0; memcpy(&msgId, d + 1 + chLen, 8); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "gui_bridge: ATTACHMENT_DOWNLOADED ch=%.*s msgId=%lld", chLen, (const char*)d + 1, (long long)msgId); if (g_attachment_downloaded_cb) g_attachment_downloaded_cb((const char*)d + 1, chLen, msgId); } } break; case GUI_EVT_DOWNLOAD_PROGRESS: if (dlen >= 2) { uint8_t chLen = d[0]; if (dlen >= 1 + chLen + 16) { int64_t msgId = 0; memcpy(&msgId, d + 1 + chLen, 8); int32_t bd = 0, nb = 0; memcpy(&bd, d + 1 + chLen + 8, 4); memcpy(&nb, d + 1 + chLen + 12, 4); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "gui_bridge: DOWNLOAD_PROGRESS ch=%.*s msgId=%lld %d/%d", chLen, (const char*)d + 1, (long long)msgId, bd, nb); if (g_download_progress_cb) g_download_progress_cb((const char*)d + 1, chLen, msgId, bd, nb); } } break; case GUI_EVT_NODEINFO_UPDATE: if (g_nodeinfo_update_cb) g_nodeinfo_update_cb(d, dlen); break; case 18: /* CHAT_EVT_INVITE_RECEIVED */ if (g_invite_received_cb) g_invite_received_cb(d, dlen); break; case GUI_EVT_CONN_LIST: if (g_conn_list_cb) g_conn_list_cb(d, dlen); break; case GUI_EVT_CONN_METRICS: if (g_conn_metrics_cb) g_conn_metrics_cb(d, dlen); break; case GUI_EVT_MEMBER_DETAIL: if (g_member_detail_cb) g_member_detail_cb(d, dlen); break; case GUI_EVT_MEMBER_LIST: if (g_member_list_cb) g_member_list_cb(d, dlen); break; case GUI_EVT_MEMBER_UPDATED: if (g_member_updated_cb) g_member_updated_cb(d, dlen); break; case GUI_EVT_MEMBER_REMOVED: if (g_member_removed_cb) g_member_removed_cb(d, dlen); break; case GUI_EVT_BGP_INFO: if (g_bgp_info_cb) g_bgp_info_cb(d, dlen); break; case GUI_EVT_ADMIN_KEY_RECEIVED: if (dlen >= 2) { uint8_t chLen = d[0]; if (dlen >= 1 + chLen + 8) { uint64_t from; memcpy(&from, d + 1 + chLen, 8); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: ADMIN_KEY_RECEIVED ch=%.*s from=0x%016llx", chLen, (const char*)d + 1, (unsigned long long)from); if (g_admin_key_received_cb) g_admin_key_received_cb(d, dlen); } } break; case GUI_EVT_INVITE_LINK_READY: if (dlen >= 11) { uint8_t chLen = d[0]; if (dlen < 1 + chLen + 10) break; uint64_t requestId; memcpy(&requestId, d + 1 + chLen, 8); uint16_t linkLen; memcpy(&linkLen, d + 1 + chLen + 8, 2); if (dlen >= 1 + chLen + 10 + linkLen) { QByteArray link((const char*)d + 1 + chLen + 10, linkLen); if (g_invite_link_ready_cb) g_invite_link_ready_cb((const char*)d + 1, chLen, requestId, link.constData()); } } break; case GUI_EVT_INVITE_CANDIDATES: if (dlen >= 2) { uint8_t chLen = d[0]; if (dlen >= 1 + chLen + 10) { if (g_invite_candidates_cb) g_invite_candidates_cb((const char*)d + 1, chLen, d + 1 + chLen, dlen - 1 - chLen); } } break; case GUI_EVT_DM_MSG_RECEIVED: if (dlen >= 2) { uint8_t cl = d[0]; if (dlen >= 1 + cl + 8) { uint64_t author; memcpy(&author, d + 1 + cl, 8); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: DM_MSG_RECEIVED conv=%.*s author=%llu", cl, (const char*)d + 1, (unsigned long long)author); if (g_dm_msg_received_cb) g_dm_msg_received_cb((const char*)d + 1, cl, author); } } break; case GUI_EVT_DM_CONV_UPDATED: if (dlen >= 2) { uint8_t cl = d[0]; if (dlen >= 1 + cl) { DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: DM_CONV_UPDATED conv=%.*s", cl, (const char*)d + 1); if (g_dm_conv_updated_cb) g_dm_conv_updated_cb((const char*)d + 1, cl); } } break; case GUI_EVT_DM_MESSAGES: if (g_dm_messages_cb) g_dm_messages_cb(d, dlen); break; case GUI_EVT_CALL_INCOMING: if (dlen >= 1 + 8 + 8) { uint8_t chLen = d[0]; if (dlen >= 1 + chLen + 8 + 8) { uint64_t caller, callId; memcpy(&caller, d + 1 + chLen, 8); memcpy(&callId, d + 1 + chLen + 8, 8); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: CALL_INCOMING ch=%.*s caller=0x%016llx id=0x%016llx", chLen, (const char*)d + 1, (unsigned long long)caller, (unsigned long long)callId); if (g_call_incoming_cb) g_call_incoming_cb((const char*)d + 1, chLen, caller, callId); } } break; case GUI_EVT_CALL_RINGING: if (dlen >= 8) { uint64_t callId; memcpy(&callId, d, 8); if (g_call_ringing_cb) g_call_ringing_cb(callId); } break; case GUI_EVT_CALL_ACCEPTED: if (dlen >= 8) { uint64_t callId; memcpy(&callId, d, 8); if (g_call_accepted_cb) g_call_accepted_cb(callId); } break; case GUI_EVT_CALL_ENDED: if (dlen >= 9) { uint64_t callId; memcpy(&callId, d, 8); if (g_call_ended_cb) g_call_ended_cb(callId, d[8]); } break; case GUI_EVT_CALL_DECLINED: if (dlen >= 9) { uint64_t callId; memcpy(&callId, d, 8); if (g_call_declined_cb) g_call_declined_cb(callId, d[8]); } break; case GUI_EVT_CALL_ERROR: if (dlen >= 9) { uint64_t callId; memcpy(&callId, d, 8); uint8_t err = d[8]; DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: CALL_ERROR id=0x%016llx err=%u text=%.*s", (unsigned long long)callId, (unsigned)err, dlen - 9, (const char*)d + 9); if (g_call_error_cb) g_call_error_cb(callId, err, (const char*)d + 9, dlen - 9); } break; case GUI_EVT_CALL_PATH: if (dlen >= 9) { uint64_t callId; memcpy(&callId, d, 8); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: CALL_PATH id=0x%016llx text=%.*s", (unsigned long long)callId, dlen - 8, (const char*)d + 8); if (g_call_path_cb) g_call_path_cb(callId, (const char*)d + 8, dlen - 8); } break; case GUI_EVT_CALL_STATS: if (dlen >= 14) { uint64_t callId; memcpy(&callId, d, 8); uint16_t rtt = (uint16_t)((d[8] << 8) | d[9]); uint16_t buf = (uint16_t)((d[10] << 8) | d[11]); uint16_t tempo = (uint16_t)((d[12] << 8) | d[13]); uint16_t dropped = 0, underruns = 0; if (dlen >= 18) { dropped = (uint16_t)((d[14] << 8) | d[15]); underruns = (uint16_t)((d[16] << 8) | d[17]); } DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "gui_bridge: CALL_STATS id=0x%016llx rtt=%ums buffer=%ums tempo=%u.%02ux dropped=%u underruns=%u", (unsigned long long)callId, (unsigned)rtt, (unsigned)buf, (unsigned)(tempo / 100), (unsigned)(tempo % 100), (unsigned)dropped, (unsigned)underruns); } break; case GUI_EVT_RADIO_TALK: if (dlen >= 19) { uint64_t groupId, src; memcpy(&groupId, d, 8); memcpy(&src, d + 8, 8); uint8_t on = d[18]; if (g_radio_talk_cb) g_radio_talk_cb(groupId, src, on); } break; default: DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: unknown event type %d", eventType); break; } } /* ── C API ── */ extern "C" { void gui_bridge_init(void) { g_receiver = new GuiBridgeReceiver(); g_receiver->ua = g_ua; } void gui_bridge_set_uasync(struct UASYNC* ua) { g_ua = ua; if (g_receiver) g_receiver->ua = ua; } void gui_bridge_set_inst(struct UTUN_INSTANCE* inst) { g_inst = inst; } struct UTUN_INSTANCE* gui_bridge_get_inst(void) { return g_inst; } void gui_bridge_post(int event_type, const uint8_t* data, int data_len) { if (!g_receiver) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge_post: g_receiver=NULL, dropping event %d", event_type); return; } QByteArray d; if (data_len > 0) d = QByteArray((const char*)data, data_len); QMetaObject::invokeMethod(g_receiver, "processPost", Qt::QueuedConnection, Q_ARG(int, event_type), Q_ARG(QByteArray, d)); } void gui_bridge_post_uasync_fn(void (*fn)(void*), void* arg) { if (!g_ua) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge_post_uasync_fn: g_ua=NULL, post dropped"); return; } if (!fn) return; uasync_post(g_ua, fn, arg); } int gui_bridge_is_uasync_ready(void) { return g_ua != NULL ? 1 : 0; } void gui_bridge_set_connect_result_cb(gui_connect_result_fn cb) { g_connect_result_cb = cb; } void gui_bridge_set_msg_received_cb(gui_msg_received_fn cb) { g_msg_received_cb = cb; } void gui_bridge_set_channel_updated_cb(gui_channel_updated_fn cb) { g_channel_updated_cb = cb; } void gui_bridge_set_my_node_id_cb(gui_my_node_id_fn cb) { g_my_node_id_cb = cb; } void gui_bridge_set_auto_connect_status_cb(gui_auto_connect_status_fn cb) { g_auto_connect_status_cb = cb; } void gui_bridge_set_channel_peers_online_cb(gui_channel_peers_online_fn cb) { g_channel_peers_online_cb = cb; } void gui_bridge_set_db_ready_cb(gui_db_ready_fn cb) { g_db_ready_cb = cb; } void gui_bridge_set_status_refresh_cb(gui_status_refresh_fn cb) { g_status_refresh_cb = cb; } void gui_bridge_set_attachment_downloaded_cb(gui_attachment_downloaded_fn cb) { g_attachment_downloaded_cb = cb; } void gui_bridge_set_download_progress_cb(gui_download_progress_fn cb) { g_download_progress_cb = cb; } void gui_bridge_set_nodeinfo_update_cb(gui_nodeinfo_update_fn cb) { g_nodeinfo_update_cb = cb; } void gui_bridge_set_invite_received_cb(gui_invite_received_fn cb) { g_invite_received_cb = cb; } void gui_bridge_set_conn_list_cb(gui_conn_list_fn cb) { g_conn_list_cb = cb; } void gui_bridge_set_conn_metrics_cb(gui_conn_metrics_fn cb) { g_conn_metrics_cb = cb; } void gui_bridge_set_member_detail_cb(gui_member_detail_fn cb) { g_member_detail_cb = cb; } void gui_bridge_set_member_list_cb(gui_member_list_fn cb) { g_member_list_cb = cb; } void gui_bridge_set_member_updated_cb(gui_member_updated_fn cb) { g_member_updated_cb = cb; } void gui_bridge_set_member_removed_cb(gui_member_removed_fn cb) { g_member_removed_cb = cb; } void gui_bridge_set_bgp_info_cb(gui_bgp_info_fn cb) { g_bgp_info_cb = cb; } void gui_bridge_set_admin_key_received_cb(gui_admin_key_received_fn cb) { g_admin_key_received_cb = cb; } void gui_bridge_set_invite_link_ready_cb(gui_invite_link_ready_fn cb) { g_invite_link_ready_cb = cb; } void gui_bridge_set_invite_candidates_cb(gui_invite_candidates_fn cb) { g_invite_candidates_cb = cb; } void gui_bridge_set_call_incoming_cb(gui_call_incoming_fn cb) { g_call_incoming_cb = cb; } void gui_bridge_set_call_ringing_cb(gui_call_id_fn cb) { g_call_ringing_cb = cb; } void gui_bridge_set_call_accepted_cb(gui_call_id_fn cb) { g_call_accepted_cb = cb; } void gui_bridge_set_call_ended_cb(gui_call_ended_fn cb) { g_call_ended_cb = cb; } void gui_bridge_set_call_declined_cb(gui_call_ended_fn cb) { g_call_declined_cb = cb; } void gui_bridge_set_call_error_cb(gui_call_error_fn cb) { g_call_error_cb = cb; } void gui_bridge_set_call_path_cb(gui_call_path_fn cb) { g_call_path_cb = cb; } void gui_bridge_set_radio_talk_cb(gui_radio_talk_fn cb) { g_radio_talk_cb = cb; } void gui_bridge_set_dm_msg_received_cb(gui_dm_msg_received_fn cb) { g_dm_msg_received_cb = cb; } void gui_bridge_set_dm_conv_updated_cb(gui_dm_conv_updated_fn cb) { g_dm_conv_updated_cb = cb; } void gui_bridge_set_dm_messages_cb(gui_dm_messages_fn cb) { g_dm_messages_cb = cb; } } /* extern "C" */ /* ── uTun nodeinfo callback (called from uasync thread) ── */ extern "C" void gui_nodeinfo_cb_impl(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq, void* arg) { (void)arg; if (!nq) return; uint16_t best_rtt = topo_get_chain_rtt(nq); uint8_t hop_count = 0; topo_node_best_hop_list(nq, &hop_count, NULL); uint8_t data[16]; memcpy(data, &nq->node_id, 8); data[8] = nq->conn_presence; data[9] = nq->conn_up; memcpy(data + 10, &best_rtt, 2); data[12] = hop_count; data[13] = 0; uint16_t bgp_nodes = group ? (uint16_t)queue_entry_count(group->nodes) : 0; memcpy(data + 14, &bgp_nodes, 2); gui_bridge_post(GUI_EVT_NODEINFO_UPDATE, data, sizeof(data)); } #include "gui_bridge_impl.moc"