@@ -267,6 +267,106 @@ namespace vix::p2p_http
267267#endif
268268 }
269269
270+ // POST /p2p/connect (connect to a peer endpoint)
271+ if (opt.enable_peers )
272+ {
273+ const std::string path = join_prefix (base, " /connect" );
274+
275+ app.post (path, [&runtime](vix::vhttp::Request &req, vix::vhttp::ResponseWrapper &res)
276+ {
277+ auto node = runtime.node ();
278+ if (!node)
279+ {
280+ res.status (503 ).json (vix::json::o (
281+ " ok" , false ,
282+ " error" , " p2p_node_unavailable"
283+ ));
284+ return ;
285+ }
286+
287+ // Expect JSON: { "host": "127.0.0.1", "port": 9002, "scheme": "tcp" }
288+ vix::json::Json body;
289+ try
290+ {
291+ body = req.json ();
292+ }
293+ catch (...)
294+ {
295+ res.status (400 ).json (vix::json::o (
296+ " ok" , false ,
297+ " error" , " invalid_json"
298+ ));
299+ return ;
300+ }
301+
302+ auto get_str = [&](const char *key, const std::string &fallback = " " ) -> std::string
303+ {
304+ if (const auto *v = vix::json::jget (body, key))
305+ {
306+ if (v->is_string ())
307+ return v->get <std::string>();
308+
309+ if (v->is_number_integer ())
310+ return std::to_string (v->get <long long >());
311+ }
312+ return fallback;
313+ };
314+
315+ auto get_ll = [&](const char *key, long long fallback = 0 ) -> long long
316+ {
317+ if (const auto *v = vix::json::jget (body, key))
318+ {
319+ if (v->is_number_integer ())
320+ return v->get <long long >();
321+
322+ if (v->is_string ())
323+ {
324+ try { return std::stoll (v->get <std::string>()); }
325+ catch (...) {}
326+ }
327+ }
328+ return fallback;
329+ };
330+
331+ const std::string host = get_str (" host" , " " );
332+ const long long port_ll = get_ll (" port" , 0 );
333+ std::string scheme = get_str (" scheme" , " tcp" );
334+ if (scheme.empty ())
335+ scheme = " tcp" ;
336+
337+ if (host.empty () || port_ll <= 0 || port_ll > 65535 )
338+ {
339+ res.status (400 ).json (vix::json::o (
340+ " ok" , false ,
341+ " error" , " invalid_endpoint" ,
342+ " hint" , " expected {host, port, scheme?}"
343+ ));
344+ return ;
345+ }
346+
347+ vix::p2p::PeerEndpoint ep;
348+ ep.host = host;
349+ ep.port = static_cast <std::uint16_t >(port_ll);
350+ ep.scheme = scheme;
351+
352+ const bool started = node->connect (ep);
353+
354+ res.json (vix::json::o (
355+ " ok" , true ,
356+ " started" , started,
357+ " endpoint" , (scheme + " ://" + host + " :" + std::to_string ((int )ep.port ))
358+ )); });
359+
360+ #if defined(VIX_P2P_HTTP_WITH_MIDDLEWARE)
361+ {
362+ vix::p2p_http::RouteOptions ro;
363+ ro.heavy = true ;
364+ ro.require_auth = false ; // ou true si tu veux protéger
365+ install_route_middlewares (app, path, ro, opt);
366+ }
367+ #endif
368+ }
369+
270370 // GET /p2p/status
271371 if (opt.enable_status )
272372 {
@@ -375,6 +475,31 @@ namespace vix::p2p_http
375475 std::vector<J::token> peers_arr;
376476 peers_arr.reserve (items.size ());
377477
478+ auto hex2 = [](std::uint8_t b) -> char {
479+ b &= 0x0F ;
480+ return (b < 10 ) ? char (' 0' + b) : char (' a' + (b - 10 ));
481+ };
482+
483+ auto short_fp_bytes = [hex2](const std::vector<std::uint8_t > &v) -> std::string {
484+ if (v.empty ()) return " " ;
485+
486+ const std::size_t n = v.size ();
487+ const std::size_t take = std::min<std::size_t >(4 , n);
488+
489+ std::string out;
490+ out.reserve (take * 2 + 10 );
491+
492+ for (std::size_t i = 0 ; i < take; ++i)
493+ {
494+ const std::uint8_t b = v[i];
495+ out.push_back (hex2 (std::uint8_t (b >> 4 )));
496+ out.push_back (hex2 (b));
497+ }
498+
499+ out += " ..(" + std::to_string (n) + " )" ;
500+ return out;
501+ };
502+
378503 for (const auto &[peer_id, p] : items)
379504 {
380505 const std::string ep_str = endpoint_to_string (p.endpoint );
@@ -388,8 +513,8 @@ namespace vix::p2p_http
388513 }
389514
390515 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 ( );
516+ const std::string pub_fp = short_fp_bytes ( p.meta .public_key );
517+ const std::string sess_fp = short_fp_bytes ( p.meta .session_key_32 );
393518 const long long capabilities_count = (long long )p.meta .capabilities .size ();
394519
395520 // Handshake block (optional)
@@ -435,20 +560,21 @@ namespace vix::p2p_http
435560
436561 " secure" , secure,
437562 " capabilities_count" , capabilities_count,
438- " public_key_len" , public_key_len,
439- " session_key_len" , session_key_len,
563+ " public_key_len" , pub_fp,
564+ " public_key_fp" , pub_fp,
565+ " session_key_len" , sess_fp,
440566
441567 " last_seen_ms_ago" , (long long )last_seen_ms_ago,
442568
443569 " has_handshake" , has_hs,
444570 " handshake_stage" , hs_stage,
445571 " handshake_age_ms" , (long long )hs_age_ms,
446572
447- // debug-friendly (safe, no secrets)
448573 " nonce_a" , (long long )hs_nonce_a,
449574 " nonce_b" , (long long )hs_nonce_b,
450575 " ts_ms" , (long long )hs_ts_ms
451576 }));
577+
452578 }
453579
454580 res.json (J::obj ({
0 commit comments