Skip to content

Commit 460d0ec

Browse files
committed
feat(p2p_http): add /p2p/peers endpoint for multi-peer dashboard
1 parent 6de68df commit 460d0ec

2 files changed

Lines changed: 167 additions & 0 deletions

File tree

include/vix/p2p_http/P2PHttpOptions.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ namespace vix::p2p_http
3636

3737
bool enable_live_logs = true;
3838
int stats_every_ms = 1000;
39+
bool enable_peers{true};
3940

4041
AuthHookCtx auth_ctx = nullptr;
4142
AuthHookLegacy auth_legacy = nullptr;

src/P2PHttp.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,172 @@ namespace vix::p2p_http
292292
"tracked_endpoints", (long long)st.connect.tracked_endpoints
293293
})); });
294294

295+
#if defined(VIX_P2P_HTTP_WITH_MIDDLEWARE)
296+
{
297+
vix::p2p_http::RouteOptions ro;
298+
ro.heavy = false;
299+
ro.require_auth = false;
300+
install_route_middlewares(app, path, ro, opt);
301+
}
302+
#endif
303+
}
304+
305+
// GET /p2p/peers (multi-peer view for dashboard)
306+
if (opt.enable_peers)
307+
{
308+
const std::string path = join_prefix(base, "/peers");
309+
310+
app.get(path, [&runtime](vix::vhttp::Request &, vix::vhttp::ResponseWrapper &res)
311+
{
312+
auto node = runtime.node();
313+
if (!node)
314+
{
315+
res.status(503).json(J::obj({
316+
"ok", false,
317+
"error", "p2p_node_unavailable"
318+
}));
319+
return;
320+
}
321+
322+
const auto snap = node->peers_snapshot();
323+
324+
// Make output stable: sort by peer_id
325+
std::vector<std::pair<vix::p2p::PeerId, vix::p2p::Peer>> items;
326+
items.reserve(snap.size());
327+
for (const auto &kv : snap)
328+
items.push_back(kv);
329+
330+
std::sort(items.begin(), items.end(),
331+
[](const auto &a, const auto &b)
332+
{
333+
return a.first < b.first;
334+
});
335+
336+
auto state_to_string = [](vix::p2p::PeerState s) -> const char *
337+
{
338+
switch (s)
339+
{
340+
case vix::p2p::PeerState::Disconnected: return "disconnected";
341+
case vix::p2p::PeerState::Connecting: return "connecting";
342+
case vix::p2p::PeerState::Handshaking: return "handshaking";
343+
case vix::p2p::PeerState::Connected: return "connected";
344+
case vix::p2p::PeerState::Stale: return "stale";
345+
case vix::p2p::PeerState::Closed: return "closed";
346+
default: return "unknown";
347+
}
348+
};
349+
350+
auto hs_stage_to_string = [](vix::p2p::HandshakeState::Stage s) -> const char *
351+
{
352+
switch (s)
353+
{
354+
case vix::p2p::HandshakeState::Stage::None: return "none";
355+
case vix::p2p::HandshakeState::Stage::HelloSent: return "hello_sent";
356+
case vix::p2p::HandshakeState::Stage::HelloReceived: return "hello_received";
357+
case vix::p2p::HandshakeState::Stage::AckSent: return "ack_sent";
358+
case vix::p2p::HandshakeState::Stage::AckReceived: return "ack_received";
359+
case vix::p2p::HandshakeState::Stage::Finished: return "finished";
360+
default: return "unknown";
361+
}
362+
};
363+
364+
auto endpoint_to_string = [](const std::optional<vix::p2p::PeerEndpoint> &ep) -> std::string
365+
{
366+
if (!ep)
367+
return "";
368+
369+
const std::string scheme = (ep->scheme.empty() ? "tcp" : ep->scheme);
370+
return scheme + "://" + ep->host + ":" + std::to_string(ep->port);
371+
};
372+
373+
const auto now = std::chrono::steady_clock::now();
374+
375+
std::vector<J::token> peers_arr;
376+
peers_arr.reserve(items.size());
377+
378+
for (const auto &[peer_id, p] : items)
379+
{
380+
const std::string ep_str = endpoint_to_string(p.endpoint);
381+
382+
long long last_seen_ms_ago = -1;
383+
if (p.meta.last_seen.time_since_epoch().count() != 0)
384+
{
385+
const auto diff = now - p.meta.last_seen;
386+
last_seen_ms_ago =
387+
(long long)std::chrono::duration_cast<std::chrono::milliseconds>(diff).count();
388+
}
389+
390+
const bool secure = p.meta.secure;
391+
const long long public_key_len = (long long)p.meta.public_key.size();
392+
const long long session_key_len = (long long)p.meta.session_key_32.size();
393+
const long long capabilities_count = (long long)p.meta.capabilities.size();
394+
395+
// Handshake block (optional)
396+
const bool has_hs = p.handshake.has_value();
397+
const char *hs_stage = "none";
398+
long long hs_age_ms = -1;
399+
long long hs_nonce_a = 0;
400+
long long hs_nonce_b = 0;
401+
long long hs_ts_ms = 0;
402+
403+
if (has_hs)
404+
{
405+
hs_stage = hs_stage_to_string(p.handshake->stage);
406+
407+
if (p.handshake->started_at.time_since_epoch().count() != 0)
408+
{
409+
const auto diff = now - p.handshake->started_at;
410+
hs_age_ms =
411+
(long long)std::chrono::duration_cast<std::chrono::milliseconds>(diff).count();
412+
}
413+
414+
hs_nonce_a = (long long)p.handshake->nonce_a;
415+
hs_nonce_b = (long long)p.handshake->nonce_b;
416+
hs_ts_ms = (long long)p.handshake->ts_ms;
417+
}
418+
419+
// Endpoint split (optional)
420+
const bool has_ep = p.endpoint.has_value();
421+
const std::string ep_scheme = (has_ep ? (p.endpoint->scheme.empty() ? "tcp" : p.endpoint->scheme) : "");
422+
const std::string ep_host = (has_ep ? p.endpoint->host : "");
423+
const long long ep_port = (has_ep ? (long long)p.endpoint->port : 0);
424+
425+
// Final peer object
426+
peers_arr.push_back(J::obj({
427+
"peer_id", peer_id,
428+
"state", state_to_string(p.state),
429+
430+
"endpoint", ep_str,
431+
"has_endpoint", has_ep,
432+
"scheme", ep_scheme,
433+
"host", ep_host,
434+
"port", ep_port,
435+
436+
"secure", secure,
437+
"capabilities_count", capabilities_count,
438+
"public_key_len", public_key_len,
439+
"session_key_len", session_key_len,
440+
441+
"last_seen_ms_ago", (long long)last_seen_ms_ago,
442+
443+
"has_handshake", has_hs,
444+
"handshake_stage", hs_stage,
445+
"handshake_age_ms", (long long)hs_age_ms,
446+
447+
// debug-friendly (safe, no secrets)
448+
"nonce_a", (long long)hs_nonce_a,
449+
"nonce_b", (long long)hs_nonce_b,
450+
"ts_ms", (long long)hs_ts_ms
451+
}));
452+
}
453+
454+
res.json(J::obj({
455+
"ok", true,
456+
"module", "p2p_http",
457+
"total", (long long)peers_arr.size(),
458+
"peers", J::array(std::move(peers_arr))
459+
})); });
460+
295461
#if defined(VIX_P2P_HTTP_WITH_MIDDLEWARE)
296462
{
297463
vix::p2p_http::RouteOptions ro;

0 commit comments

Comments
 (0)