Browse Source

member detail: structured display with addresses, connections, BGP summary and socket names

- Extend MEMBER_DETAIL_LINK_SIZE to 63B with 20B sock_name per link
- Replace link_count with links_up/links_down (two uint16) in sock snap
- Add BGP hop list collection via topo_node_best_hop_list, post as CHAT_EVT_BGP_INFO
- Add direct RTT summary text via same event
- Rework AccountList detail panel: header + QTextEdit with monospace layout
- Display protocol (TCP/UDP), socket name line, colored dots for connections
- Show [up/down] link counts for self listen sockets
- Remove * prefix from link count display in Android
topo_upd
evgeny 2 months ago
parent
commit
fec3ea38a7
  1. 1
      src/chat/chat_event.h
  2. 70
      src/chat/chat_status.c
  3. 2
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt
  4. 2
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt
  5. 160
      tools/chatgui/src/accountlist.cpp
  6. 12
      tools/chatgui/src/accountlist.h
  7. 10
      tools/chatgui/transport/gui_bridge.h
  8. 13
      tools/chatgui/transport/gui_bridge_impl.cpp

1
src/chat/chat_event.h

@ -44,6 +44,7 @@ extern "C" {
#define CHAT_EVT_MEMBER_LIST 23 /* [ch_id_len:1][ch_id:var][count:2][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_BGP_INFO 26 /* текст: "rtt:XXms hop:X via bgp: [XXXX] name1 [XXXX] name2" */
typedef void (*chat_event_handler_fn)(int type, const uint8_t* data, int len);

70
src/chat/chat_status.c

@ -16,6 +16,8 @@
#include "../transport_layer/etcp_connections.h"
#include "../transport_layer/stcp_link.h"
#include "../transport_layer/pkt_normalizer.h"
#include "../routing_layer/topo_node.h"
#include "../routing_layer/topo_group.h"
#include <string.h>
static const char* nat_type_str(uint8_t t) {
@ -303,17 +305,18 @@ void chat_core_collect_conn_metrics_trampoline(void* arg) {
}
/* ── Live-снапшот для панели деталей участника ──
* packet: [node_id:8][flags:1][own_tcp_active:2][link_count:1](link:43B)*[sock_count:1](sock:48B)*
* packet: [node_id:8][flags:1][own_tcp_active:2][link_count:1](link:63B)*[sock_count:1](sock:48B)*
* flags: bit0=conn_present bit1=conn_up(links_up) bit2=conn_initialized
* link: [is_tcp:1][link_state:1][link_status:1][tcp_ready:1][is_server:1]
* [local_family:1][local_addr:16][local_port:2]
* [remote_family:1][remote_addr:16][remote_port:2]
* sock: [sock_id:4][port:2][link_count:4][ifname:38]
* [sock_name:20]
* sock: [sock_id:4][port:2][links_up:2][links_down:2][ifname:38]
* Собирается в worker-потоке — читает живые структуры UTUN_INSTANCE безопасно. */
#define MEMBER_DETAIL_MAX_LINKS 16
#define MEMBER_DETAIL_MAX_SOCKS 16
#define MEMBER_DETAIL_LINK_SIZE 43
#define MEMBER_DETAIL_LINK_SIZE 63
#define MEMBER_DETAIL_SOCK_SIZE 48
/* Повторяет Qt: name.lastIndexOf("_v") → name.mid(3, idx - 3) */
@ -416,6 +419,11 @@ static void collect_member_detail(uint64_t node_id) {
}
*p++ = rf; memcpy(p, ra, 16); p += 16; memcpy(p, &rp, 2); p += 2;
}
{ char sn[20] = {0};
if (lk->conn) strncpy(sn, lk->conn->name, 19);
memcpy(p, sn, 20); p += 20;
}
}
*p++ = sock_count;
@ -425,16 +433,68 @@ static void collect_member_detail(uint64_t node_id) {
const struct sockaddr_storage* sa = s->interface_addr.ss_family ? &s->interface_addr : &s->local_addr;
if (sa->ss_family == AF_INET) sp = ntohs(((const struct sockaddr_in*)sa)->sin_port);
else if (sa->ss_family == AF_INET6) sp = ntohs(((const struct sockaddr_in6*)sa)->sin6_port);
uint32_t lc = s->is_tcp ? 0 : (s->links_queue ? (uint32_t)queue_entry_count(s->links_queue) : 0);
uint16_t lu = 0, ld = 0;
{ struct ll_entry* e = inst->connections ? inst->connections->head : NULL;
for (; e; e = e->next) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
if (!ce || !ce->conn) continue;
for (struct ETCP_LINK* lk = ce->conn->links; lk; lk = lk->next)
if (lk->conn == s) { if (lk->link_state == 3) lu++; else ld++; }
}
}
{ struct ll_entry* e = inst->tcp_connections ? inst->tcp_connections->head : NULL;
for (; e; e = e->next) {
struct tcp_conn_entry* te = (struct tcp_conn_entry*)e->data;
if (!te || !te->etcp_conn) continue;
for (struct ETCP_LINK* lk = te->etcp_conn->links; lk; lk = lk->next)
if (lk->conn == s) { if (lk->link_state == 3) lu++; else ld++; }
}
}
char ifname[38] = {0}; sock_ifname(s->name, ifname, sizeof(ifname));
memcpy(p, &sid, 4); p += 4;
memcpy(p, &sp, 2); p += 2;
memcpy(p, &lc, 4); p += 4;
memcpy(p, &lu, 2); p += 2;
memcpy(p, &ld, 2); p += 2;
memcpy(p, ifname, 38); p += 38;
}
chat_event_post(CHAT_EVT_MEMBER_DETAIL, buf, (int)(p - buf));
u_free(buf);
/* summary text: direct RTT or BGP */
if (conn && conn->links_up) {
uint16_t rtt = conn->links ? conn->links->rtt_last : 0;
if (rtt > 0) {
char tbuf[32];
int off = snprintf(tbuf, sizeof(tbuf), "direct, rtt:%ums", rtt / 10);
chat_event_post(CHAT_EVT_BGP_INFO, (const uint8_t*)tbuf, off);
}
} else if (conn && !conn->links_up && node_id != inst->node_id) {
struct ll_queue* gl = inst->topo_groups ? inst->topo_groups->group_list : NULL;
for (struct ll_entry* ge = gl ? gl->head : NULL; ge; ge = ge->next) {
struct TOPO_GROUP* group = (struct TOPO_GROUP*)ge->data;
if (!group) continue;
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, node_id);
if (!nq) continue;
uint8_t hc = 0;
uint64_t* hops = topo_node_best_hop_list(nq, &hc, NULL);
if (hc > 0 && hops) {
char tbuf[512] = {0};
uint16_t cr = topo_get_chain_rtt(nq);
int off = snprintf(tbuf, sizeof(tbuf), "rtt:%ums hop:%u via bgp:", cr / 10, hc);
for (uint8_t hi = 0; hi < hc && off < (int)sizeof(tbuf) - 24; hi++) {
char hn[32] = {0};
get_node_name(hops[hi], hn, sizeof(hn));
uint16_t hid = (uint16_t)(hops[hi] & 0xFFFF);
off += snprintf(tbuf + off, sizeof(tbuf) - (size_t)off,
" [%04X]%s", hid, hn[0] ? " " : "");
if (hn[0]) off += snprintf(tbuf + off, sizeof(tbuf) - (size_t)off, "%s", hn);
}
chat_event_post(CHAT_EVT_BGP_INFO, (const uint8_t*)tbuf, off);
}
break;
}
}
}
void chat_core_collect_member_detail_trampoline(void* arg) {

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

@ -269,7 +269,7 @@ private fun MemberDetailCard(
Box(Modifier.size(8.dp).clip(CircleShape).background(dot))
Spacer(Modifier.width(6.dp))
Text(
text = "*[${sk.linksUp}/${sk.linksDown}] ${sk.protocol} ${sk.ifName} ${sk.bindIp}:${sk.port}",
text = "[${sk.linksUp}/${sk.linksDown}] ${sk.protocol} ${sk.ifName} ${sk.bindIp}:${sk.port}",
fontFamily = FontFamily.Monospace,
fontSize = 13.sp,
color = Color.DarkGray

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

@ -235,7 +235,7 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
Text(s.protocol, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
if (s.linksUp + s.linksDown > 0) {
Spacer(Modifier.width(4.dp))
Text("*[${s.linksUp}/${s.linksDown}]", style = MaterialTheme.typography.labelSmall,
Text("[${s.linksUp}/${s.linksDown}]", style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant)
}
}

160
tools/chatgui/src/accountlist.cpp

@ -64,7 +64,7 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
, m_listView(new QListView(this))
, m_model(new MemberListModel(this))
, m_db(db)
, m_detailText(nullptr)
, m_detailPanel(nullptr)
{
auto *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
@ -83,17 +83,25 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
m_listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
m_listView->setContextMenuPolicy(Qt::CustomContextMenu);
m_detailText = new QTextEdit(this);
m_detailPanel = new QWidget(this);
auto* detailLayout = new QVBoxLayout(m_detailPanel);
detailLayout->setContentsMargins(0, 0, 0, 0);
detailLayout->setSpacing(0);
m_detailHeader = new QLabel("Select a member", m_detailPanel);
m_detailHeader->setWordWrap(true);
m_detailHeader->setStyleSheet("font-weight: bold; padding: 4px 8px; background: palette(window);");
detailLayout->addWidget(m_detailHeader);
m_detailText = new QTextEdit(m_detailPanel);
m_detailText->setReadOnly(true);
m_detailText->setFont(QFont("monospace", 10));
m_detailText->setStyleSheet(
"QTextEdit { border-top: 1px solid palette(mid); border-bottom: none;"
" border-left: none; border-right: none; padding: 6px; background: palette(base); }");
m_detailText->setPlaceholderText("Select a member");
m_detailText->setFont(QFont("monospace", 9));
m_detailText->setStyleSheet("QTextEdit { border: none; padding: 4px; background: palette(base); }");
detailLayout->addWidget(m_detailText, 1);
auto *memberSplitter = new QSplitter(Qt::Vertical, this);
memberSplitter->addWidget(m_listView);
memberSplitter->addWidget(m_detailText);
memberSplitter->addWidget(m_detailPanel);
memberSplitter->setStretchFactor(0, 3);
memberSplitter->setStretchFactor(1, 2);
memberSplitter->setSizes({360, 240});
@ -119,6 +127,19 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
g_detailTarget = this;
gui_bridge_set_member_detail_cb(memberDetailCb);
static bool nodeinfoRegistered = false;
if (!nodeinfoRegistered) {
gui_bridge_set_nodeinfo_update_cb([](const uint8_t* data, int len) {
if (g_detailTarget) g_detailTarget->onNodeinfoUpdate(data, len);
});
gui_bridge_set_bgp_info_cb([](const uint8_t* data, int len) {
if (!g_detailTarget) return;
g_detailTarget->m_bgpText = QString::fromUtf8((const char*)data, len);
g_detailTarget->updateDetailDisplay();
});
nodeinfoRegistered = true;
}
}
AccountList::~AccountList() {
@ -131,6 +152,7 @@ void AccountList::setChannel(const QString& channelId) {
m_detailTimer->stop();
m_linkTimer->stop();
m_detailNodeId = 0;
m_detailHeader->setText("Select a member");
m_detailText->clear();
}
@ -157,6 +179,10 @@ void AccountList::showMemberDetail(quint64 nodeId) {
m_snapConnUp = false;
m_snapConnInit = false;
m_snapOwnTcpActive = 0;
m_snapChainRtt = 0;
m_snapHopCount = 0;
m_snapBgpNodes = 0;
m_bgpText.clear();
m_snapLinks.clear();
m_snapSocks.clear();
@ -266,6 +292,7 @@ void AccountList::onMemberDetailData(uint64_t nodeId, const uint8_t* data, int l
lk.family = p[24];
lk.addr = QByteArray((const char*)p + 25, 16);
memcpy(&lk.port, p + 41, 2);
memcpy(lk.sockName, p + 43, 20); lk.sockName[19] = '\0';
p += MEMBER_DETAIL_LINK_SIZE;
m_snapLinks.append(lk);
}
@ -278,7 +305,8 @@ void AccountList::onMemberDetailData(uint64_t nodeId, const uint8_t* data, int l
MemberSockSnap s;
memcpy(&s.sockId, p, 4);
memcpy(&s.port, p + 4, 2);
memcpy(&s.linkCount, p + 6, 4);
uint16_t lu = 0, ld = 0; memcpy(&lu, p + 6, 2); memcpy(&ld, p + 8, 2);
s.linksUp = lu; s.linksDown = ld;
s.ifname = QString::fromUtf8((const char*)p + 10).trimmed();
p += MEMBER_DETAIL_SOCK_SIZE;
m_snapSocks.append(s);
@ -302,68 +330,92 @@ void AccountList::onMemberDetailData(uint64_t nodeId, const uint8_t* data, int l
}
void AccountList::updateDetailDisplay() {
QStringList lines;
lines.append(QString("ID:0x%1").arg(m_detailNodeId, 16, 16, QChar('0')));
bool isSelf = m_db ? (m_detailNodeId == m_db->myNodeId()) : false;
QString nodeHex = QString("0x%1").arg(m_detailNodeId, 16, 16, QChar('0'));
/* ── Header ── */
QString dotColor = m_detailOnline ? "#4CAF50" : "#9E9E9E";
lines.append(QString("<span style='color:%1;'>●</span> %2")
.arg(dotColor, m_detailName.toHtmlEscaped()));
QStringList hdr;
hdr.append(QString("<span style='color:%1;'>●</span> %2 %3")
.arg(dotColor, m_detailName.toHtmlEscaped(), nodeHex));
if (m_detailLastSeen > 0)
lines.append(QString("⌛ %1").arg(relativeTimeStr(m_detailLastSeen)));
hdr.append(QString("seen %1").arg(relativeTimeStr(m_detailLastSeen)));
if (m_detailCreated > 0)
lines.append(QString("📅 %1").arg(relativeTimeStr(m_detailCreated)));
hdr.append(QString("created %1").arg(relativeTimeStr(m_detailCreated)));
m_detailHeader->setText(hdr.join(" "));
bool isSelf = m_db ? (m_detailNodeId == m_db->myNodeId()) : false;
/* ── Body ── */
QStringList lines;
if (isSelf) {
if (!m_detailAddrs.isEmpty()) {
lines.append("── Addresses ──");
for (const auto& a : m_detailAddrs) {
QString proto;
switch (a.protocol) { case 1: proto = "UDP"; break; case 2: proto = "TCP"; break; default: proto = "UDP/TCP"; break; }
int lc = 0;
if (a.protocol == 1) { for (const auto& s : m_snapSocks) { if (s.port == a.port) { lc = s.linkCount; break; } } }
else if (a.protocol == 2) lc = m_snapOwnTcpActive;
QString prefix = lc > 0 ? QString("[%1] ").arg(lc) : "";
if (!a.ifname.isEmpty())
lines.append(QString("%1● %2 %3").arg(prefix, proto, a.ifname));
else
lines.append(QString("%1● %2:%3:%4").arg(prefix, proto, addrStr(a.addr.constData(), a.family)).arg(a.port));
switch (a.protocol) { case 1: proto = "UDP"; break; case 2: proto = "TCP"; break; default: proto = "?"; break; }
lines.append(QString(" %1 %2 %3:%4")
.arg(proto)
.arg(a.ifname)
.arg(addrStr(a.addr.constData(), a.family))
.arg(a.port));
}
}
if (isSelf) {
lines.append("── Listen ──");
for (const auto& s : m_snapSocks) {
const DetailAddress* a = nullptr;
for (const auto& da : m_detailAddrs) {
if (da.socket_id == s.sockId) { a = &da; break; }
}
QString proto = a ? (a->protocol == 2 ? "TCP" : "UDP") : "?";
QString ip = a ? addrStr(a->addr.constData(), a->family) : "?";
lines.append(QString(" [%1/%2] %3:%4 %5")
.arg(s.linksUp).arg(s.linksDown)
.arg(ip).arg(s.port)
.arg(proto));
}
} else {
/* all live links with arrows */
if (!m_snapLinks.isEmpty()) {
lines.append("── Connections ──");
for (const auto& lk : m_snapLinks) {
QString proto = lk.isTcp ? "TCP" : "UDP";
QString lc = (lk.linkState == 3 && lk.linkStatus == 1) ? "#4CAF50" : "#E53935";
QString left = QString("%1:%2").arg(addrStr(lk.localAddr.constData(), lk.localFamily)).arg(lk.localPort);
QString right = QString("%1:%2").arg(addrStr(lk.addr.constData(), lk.family)).arg(lk.port);
const char* arrow = lk.isServer
? "<b style='color:#FF9800;font-size:13pt;'>◀</b>"
: "<b style='color:#2196F3;font-size:13pt;'>▶</b>";
if (lk.localFamily == 6 || lk.family == 6) {
lines.append(QString("<span style='color:%1;'>●</span> %2 %3:%4").arg(lc, proto, addrStr(lk.localAddr.constData(), lk.localFamily)).arg(lk.localPort));
lines.append(QString("&nbsp;%1 %2:%3").arg(arrow, addrStr(lk.addr.constData(), lk.family)).arg(lk.port));
} else {
lines.append(QString("<span style='color:%1;'>●</span> %2 %3 %4 %5")
.arg(lc, proto, left, arrow, right));
bool up = (lk.linkState == 3 && lk.linkStatus == 1);
QString c = up ? "#4CAF50" : "#E53935";
lines.append(QString("<span style='color:%1;'>●</span> %2 %3:%4 %5 %6:%7")
.arg(c)
.arg(lk.isTcp ? "TCP" : "UDP")
.arg(addrStr(lk.localAddr.constData(), lk.localFamily)).arg(lk.localPort)
.arg(lk.isServer ? "\u25c0" : "\u25b6")
.arg(addrStr(lk.addr.constData(), lk.family)).arg(lk.port));
if (lk.sockName[0])
lines.append(QString(" %1").arg(lk.sockName));
}
}
/* DB addresses without live link */
for (const auto& a : m_detailAddrs) {
bool matched = false;
for (const auto& lk : m_snapLinks) {
if (lk.family == a.family && lk.port == a.port && lk.addr == a.addr) { matched = true; break; }
/* summary */
if (!m_bgpText.isEmpty()) {
lines.append(m_bgpText);
} else if (m_snapConnUp) {
lines.append("UP");
}
if (matched) continue;
QString proto;
switch (a.protocol) { case 1: proto = "UDP"; break; case 2: proto = "TCP"; break; default: proto = "UDP/TCP"; break; }
lines.append(QString("<span style='color:#9E9E9E;'>●</span> %1 %2:%3")
.arg(proto, addrStr(a.addr.constData(), a.family)).arg(a.port));
}
if (lines.isEmpty())
m_detailText->clear();
else
m_detailText->setHtml(QString("<pre style='margin:0;'>") + lines.join("\n") + "</pre>");
}
m_detailText->setHtml(lines.join("<br>"));
void AccountList::onNodeinfoUpdate(const uint8_t* data, int len) {
if (len < 16 || m_detailNodeId == 0) return;
uint64_t nodeId; memcpy(&nodeId, data, 8);
if (nodeId != m_detailNodeId) return;
m_snapConnPresent = (data[8] & 1) != 0;
m_snapConnUp = (data[9] & 1) != 0;
memcpy(&m_snapChainRtt, data + 10, 2);
m_snapHopCount = data[12];
memcpy(&m_snapBgpNodes, data + 14, 2);
/* data[13] is reserved/unused */
if (m_snapConnPresent)
updateDetailDisplay();
}
void AccountList::onDetailTimerTick() {

12
tools/chatgui/src/accountlist.h

@ -3,6 +3,7 @@
#include <QWidget>
#include <QListView>
#include <QTextEdit>
#include <QLabel>
#include <QTimer>
#include <QMenu>
#include <QVector>
@ -33,12 +34,14 @@ struct MemberLinkSnap {
int family;
QByteArray addr;
uint16_t port;
char sockName[20];
};
struct MemberSockSnap {
int sockId;
uint16_t port;
int linkCount;
int linksUp;
int linksDown;
QString ifname;
};
@ -51,6 +54,7 @@ public:
void setChannel(const QString& channelId);
void refresh();
void onMemberDetailData(uint64_t nodeId, const uint8_t* data, int len);
void onNodeinfoUpdate(const uint8_t* data, int len);
/* Bridge callbacks (called from GUI thread) */
void onMemberList(const QString& chId, int count, const uint8_t* data, int dataLen);
@ -78,6 +82,8 @@ private:
QListView *m_listView;
MemberListModel *m_model;
DbManager *m_db;
QWidget *m_detailPanel;
QLabel *m_detailHeader;
QTextEdit *m_detailText;
QTimer *m_detailTimer;
QTimer *m_linkTimer;
@ -94,6 +100,10 @@ private:
bool m_snapConnUp = false;
bool m_snapConnInit = false;
int m_snapOwnTcpActive = 0;
uint16_t m_snapChainRtt = 0;
uint8_t m_snapHopCount = 0;
uint16_t m_snapBgpNodes = 0;
QString m_bgpText;
QVector<MemberLinkSnap> m_snapLinks;
QVector<MemberSockSnap> m_snapSocks;
};

10
tools/chatgui/transport/gui_bridge.h

@ -34,12 +34,13 @@ struct TOPO_GROUP_NODE;
#define GUI_EVT_MEMBER_LIST 23 /* data: [ch_id_len:1][ch_id:var][count:2][member:79B]* — полный список мемберов канала */
#define GUI_EVT_MEMBER_UPDATED 24 /* data: [ch_id_len:1][ch_id:var][count:1][member:79B] — обновление одного мембера */
#define GUI_EVT_MEMBER_REMOVED 25 /* data: [ch_id_len:1][ch_id:var][node_id:8] — мембер удалён из канала */
#define GUI_EVT_BGP_INFO 26 /* data: text — BGP hop summary for member detail */
/* Размеры элементов пакета GUI_EVT_MEMBER_DETAIL (должны совпадать с chat_status.c) */
#define MEMBER_DETAIL_LINK_SIZE 43
#define MEMBER_DETAIL_SOCK_SIZE 48
#define MEMBER_DETAIL_LINK_SIZE 63
#define MEMBER_DETAIL_MAX_LINKS 16
#define MEMBER_DETAIL_MAX_SOCKS 16
#define MEMBER_DETAIL_SOCK_SIZE 48
/* ── API ── */
@ -136,6 +137,11 @@ void gui_bridge_set_member_updated_cb(gui_member_updated_fn cb);
typedef void (*gui_member_removed_fn)(const uint8_t* data, int len);
void gui_bridge_set_member_removed_cb(gui_member_removed_fn cb);
/* Callback для BGP hop info текста (вызывается из GUI-потока)
data: текстовая строка формата "rtt:12ms hop:3 via bgp: [ABCD] name1 [EF12] name2" */
typedef void (*gui_bgp_info_fn)(const uint8_t* data, int len);
void gui_bridge_set_bgp_info_cb(gui_bgp_info_fn cb);
/* uTun nodeinfo callback — registered via utun_add_nodeinfo_cbk, called from uasync thread */
void gui_nodeinfo_cb_impl(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq, void* arg);

13
tools/chatgui/transport/gui_bridge_impl.cpp

@ -44,6 +44,7 @@ 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 struct UASYNC* g_ua = nullptr;
/* ── GuiBridgeReceiver implementation ── */
@ -194,6 +195,9 @@ void GuiBridgeReceiver::processPost(int eventType, QByteArray data) {
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;
default:
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: unknown event type %d", eventType);
break;
@ -311,6 +315,10 @@ 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;
}
} /* extern "C" */
/* ── uTun nodeinfo callback (called from uasync thread) ── */
@ -319,13 +327,16 @@ extern "C" void gui_nodeinfo_cb_impl(struct TOPO_GROUP* group, struct TOPO_GROUP
(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 + 12, &bgp_nodes, 2);
memcpy(data + 14, &bgp_nodes, 2);
gui_bridge_post(GUI_EVT_NODEINFO_UPDATE, data, sizeof(data));
}

Loading…
Cancel
Save