My Project 4.4.0
C++ Distributed Hash Table
Loading...
Searching...
No Matches
dht_proxy_server.h
1// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
2// SPDX-License-Identifier: MIT
3#pragma once
4
5#include "callbacks.h"
6#include "def.h"
7#include "infohash.h"
8#include "proxy.h"
9#include "scheduler.h"
10#include "sockaddr.h"
11#include "value.h"
12#include "http.h"
13
14#include <restinio/all.hpp>
15#include <restinio/tls.hpp>
16#include <json/json.h>
17
18#include <memory>
19#include <mutex>
20
21namespace dht {
22enum class PushType { None = 0, Android, iOS, UnifiedPush };
23}
24MSGPACK_ADD_ENUM(dht::PushType)
25
26namespace Json {
27class Value;
28}
29
30namespace dht {
31
32namespace http {
33class Request;
34struct ListenerSession;
35} // namespace http
36
37class DhtRunner;
38
39using RestRouter = restinio::router::express_router_t<>;
40using RequestStatus = restinio::request_handling_status_t;
41
42struct OPENDHT_PUBLIC ProxyServerConfig
43{
44 std::string address {};
45 in_port_t port {8000};
46 std::string pushServer {};
47 std::string persistStatePath {};
48 dht::crypto::Identity identity {};
49 std::string bundleId {};
50};
51
55class OPENDHT_PUBLIC DhtProxyServer
56{
57public:
66 DhtProxyServer(const std::shared_ptr<DhtRunner>& dht,
67 const ProxyServerConfig& config = {},
68 const std::shared_ptr<log::Logger>& logger = {});
69
70 virtual ~DhtProxyServer();
71
72 DhtProxyServer(const DhtProxyServer& other) = delete;
73 DhtProxyServer(DhtProxyServer&& other) = delete;
74 DhtProxyServer& operator=(const DhtProxyServer& other) = delete;
75 DhtProxyServer& operator=(DhtProxyServer&& other) = delete;
76
77 asio::io_context& io_context() const;
78
79 // Monotonic clock for elapsed-time statistics (request rate, uptime).
80 // Must stay on steady_clock so NTP/wall-clock corrections don't skew durations.
81 using clock = std::chrono::steady_clock;
82 using time_point = clock::time_point;
83 // Wall clock for persisted subscribe/permanent-put lease expirations,
84 // so they map directly to epoch time and survive restarts.
85 using sys_clock = std::chrono::system_clock;
86 using sys_time_point = sys_clock::time_point;
87
88 struct PushStats
89 {
90 uint64_t highPriorityCount {0};
91 uint64_t normalPriorityCount {0};
92
93 void increment(bool highPriority)
94 {
95 if (highPriority)
96 highPriorityCount++;
97 else
98 normalPriorityCount++;
99 }
100
101 Json::Value toJson() const
102 {
103 Json::Value val;
104 val["highPriorityCount"] = static_cast<Json::UInt64>(highPriorityCount);
105 val["normalPriorityCount"] = static_cast<Json::UInt64>(normalPriorityCount);
106 return val;
107 }
108
109 std::string toString() const
110 {
111 return fmt::format("{} high priority, {} normal priority", highPriorityCount, normalPriorityCount);
112 }
113 };
114
115 struct OPENDHT_PUBLIC ServerStats
116 {
118 size_t listenCount {0};
120 size_t putCount {0};
125
127 time_point serverStartTime;
129 time_point lastUpdated;
133 PushStats iosPush;
134 PushStats unifiedPush;
135
137 double requestRate {0};
139 std::shared_ptr<NodeInfo> nodeInfo {};
140
141 std::string toString() const;
142
146 Json::Value toJson() const;
147 };
148
149 std::shared_ptr<ServerStats> stats() const { return stats_; }
150
151 std::shared_ptr<ServerStats> updateStats(std::shared_ptr<NodeInfo> info) const;
152
153 std::shared_ptr<DhtRunner> getNode() const { return dht_; }
154
155private:
156 class ConnectionListener;
157 struct RestRouterTraitsTls;
158 struct RestRouterTraits;
159
160 template<typename HttpResponse>
161 static HttpResponse initHttpResponse(HttpResponse response);
162 static restinio::request_handling_status_t serverError(restinio::request_t& request);
163
164 template<typename ServerSettings>
165 void addServerSettings(ServerSettings& serverSettings, const unsigned int max_pipelined_requests = 16);
166
167 std::unique_ptr<RestRouter> createRestRouter();
168
169 void onConnectionClosed(restinio::connection_id_t);
170
178 RequestStatus getNodeInfo(restinio::request_handle_t request, restinio::router::route_params_t params) const;
179
186 RequestStatus getStats(restinio::request_handle_t request, restinio::router::route_params_t params);
187
198 RequestStatus get(restinio::request_handle_t request, restinio::router::route_params_t params);
199
210 RequestStatus listen(restinio::request_handle_t request, restinio::router::route_params_t params);
211
221 RequestStatus put(restinio::request_handle_t request, restinio::router::route_params_t params);
222
223 void handleCancelPermamentPut(const asio::error_code& ec, const InfoHash& key, Value::Id vid);
224
225#ifdef OPENDHT_PROXY_SERVER_IDENTITY
235 RequestStatus putSigned(restinio::request_handle_t request, restinio::router::route_params_t params) const;
236
246 RequestStatus putEncrypted(restinio::request_handle_t request, restinio::router::route_params_t params);
247
248#endif // OPENDHT_PROXY_SERVER_IDENTITY
249
260 RequestStatus getFiltered(restinio::request_handle_t request, restinio::router::route_params_t params);
261
269 RequestStatus options(restinio::request_handle_t request, restinio::router::route_params_t params);
270
271 struct PushSessionContext
272 {
273 std::mutex lock;
274 std::string sessionId;
275 PushSessionContext(const std::string& id)
276 : sessionId(id)
277 {}
278 };
279
280#ifdef OPENDHT_PUSH_NOTIFICATIONS
281 PushType getTypeFromString(const std::string& type);
282 std::string getDefaultTopic(PushType type);
283
284 RequestStatus pingPush(restinio::request_handle_t request, restinio::router::route_params_t /*params*/);
294 RequestStatus subscribe(restinio::request_handle_t request, restinio::router::route_params_t params);
295
303 RequestStatus unsubscribe(restinio::request_handle_t request, restinio::router::route_params_t params);
304
310 void sendPushNotification(
311 const std::string& key, Json::Value&& json, PushType type, bool highPriority, const std::string& topic);
312
321 void handleNotifyPushListenExpire(const asio::error_code& ec,
322 const std::string pushToken,
323 std::function<Json::Value()> json,
324 PushType type,
325 const std::string& topic);
326
334 void handleCancelPushListen(const asio::error_code& ec,
335 const std::string pushToken,
336 const InfoHash key,
337 const std::string clientId);
338
352 bool handlePushListen(const InfoHash& infoHash,
353 const std::string& pushToken,
354 PushType type,
355 const std::string& clientId,
356 const std::shared_ptr<DhtProxyServer::PushSessionContext>& sessionCtx,
357 const std::string& topic,
358 const std::vector<std::shared_ptr<Value>>& values,
359 bool expired);
360
361#endif // OPENDHT_PUSH_NOTIFICATIONS
362
363 void handlePrintStats(const asio::error_code& ec);
364 void updateStats();
365
366 template<typename Os>
367 void saveState(Os& stream);
368
369 template<typename Is>
370 void loadState(Is& is, size_t size);
371
372 std::shared_ptr<asio::io_context> ioContext_;
373 std::shared_ptr<DhtRunner> dht_;
374 Json::StreamWriterBuilder jsonBuilder_;
375 Json::CharReaderBuilder jsonReaderBuilder_;
376 std::mt19937_64 rd {crypto::getSeededRandomEngine<std::mt19937_64>()};
377
378 std::string persistPath_;
379
380 // http server
381 std::thread serverThread_;
382 std::unique_ptr<restinio::http_server_t<RestRouterTraitsTls>> httpsServer_;
383 std::unique_ptr<restinio::http_server_t<RestRouterTraits>> httpServer_;
384
385 // http client
386 std::pair<std::string, std::string> pushHostPort_;
387
388 mutable std::mutex requestLock_;
389 std::map<unsigned int /*id*/, std::shared_ptr<http::Request>> requests_;
390
391 std::shared_ptr<log::Logger> logger_;
392
393 std::shared_ptr<ServerStats> stats_;
394 std::shared_ptr<NodeInfo> nodeInfo_ {};
395 std::unique_ptr<asio::steady_timer> printStatsTimer_;
396 const time_point serverStartTime_;
397 mutable std::mutex pushStatsMutex_;
398 PushStats androidPush_;
399 PushStats iosPush_;
400 PushStats unifiedPush_;
401
402 // Thread-safe access to listeners map.
403 std::mutex lockListener_;
404 // Shared with connection listener.
405 std::map<restinio::connection_id_t, http::ListenerSession> listeners_;
406 // Connection Listener observing conn state changes.
407 std::shared_ptr<ConnectionListener> connListener_;
408 struct PermanentPut
409 {
410 sys_time_point expiration;
411 std::string pushToken;
412 std::string clientId;
413 std::shared_ptr<PushSessionContext> sessionCtx;
414 std::unique_ptr<asio::system_timer> expireTimer;
415 std::unique_ptr<asio::system_timer> expireNotifyTimer;
416 Sp<Value> value;
417 PushType type;
418 std::string topic;
419
420 template<typename Packer>
421 void msgpack_pack(Packer& p) const
422 {
423 p.pack_map(2 + (sessionCtx ? 1 : 0) + (clientId.empty() ? 0 : 1) + (type == PushType::None ? 0 : 2)
424 + (topic.empty() ? 0 : 1));
425 p.pack("value");
426 p.pack(value);
427 p.pack("exp");
428 p.pack(sys_clock::to_time_t(expiration));
429 if (not clientId.empty()) {
430 p.pack("cid");
431 p.pack(clientId);
432 }
433 if (sessionCtx) {
434 std::lock_guard l(sessionCtx->lock);
435 p.pack("sid");
436 p.pack(sessionCtx->sessionId);
437 }
438 if (type != PushType::None) {
439 p.pack("t");
440 p.pack(type);
441 p.pack("token");
442 p.pack(pushToken);
443 }
444 if (not topic.empty()) {
445 p.pack("top");
446 p.pack(topic);
447 }
448 }
449
450 void msgpack_unpack(const msgpack::object& o);
451 };
452 struct SearchPuts
453 {
454 std::map<dht::Value::Id, PermanentPut> puts;
455 MSGPACK_DEFINE_ARRAY(puts)
456 };
457 std::mutex lockSearchPuts_;
458 std::map<InfoHash, SearchPuts> puts_;
459
460 mutable std::atomic<size_t> requestNum_ {0};
461 mutable std::atomic<time_point> lastStatsReset_ {time_point::min()};
462
463 std::string pushServer_;
464 std::string bundleId_;
465
466#ifdef OPENDHT_PUSH_NOTIFICATIONS
467 struct Listener
468 {
469 sys_time_point expiration;
470 std::string clientId;
471 std::shared_ptr<PushSessionContext> sessionCtx;
472 std::future<size_t> internalToken;
473 std::unique_ptr<asio::system_timer> expireTimer;
474 std::unique_ptr<asio::system_timer> expireNotifyTimer;
475 PushType type;
476 std::string topic;
477
478 template<typename Packer>
479 void msgpack_pack(Packer& p) const
480 {
481 p.pack_map(3 + (sessionCtx ? 1 : 0) + (topic.empty() ? 0 : 1));
482 p.pack("cid");
483 p.pack(clientId);
484 p.pack("exp");
485 p.pack(sys_clock::to_time_t(expiration));
486 if (sessionCtx) {
487 std::lock_guard l(sessionCtx->lock);
488 p.pack("sid");
489 p.pack(sessionCtx->sessionId);
490 }
491 p.pack("t");
492 p.pack(type);
493 if (!topic.empty()) {
494 p.pack("top");
495 p.pack(topic);
496 }
497 }
498
499 void msgpack_unpack(const msgpack::object& o);
500 };
501 struct PushListener
502 {
503 std::map<InfoHash, std::vector<Listener>> listeners;
504 MSGPACK_DEFINE_ARRAY(listeners)
505 };
506 std::map<std::string, PushListener> pushListeners_;
507#endif // OPENDHT_PUSH_NOTIFICATIONS
508};
509
510} // namespace dht
DhtProxyServer(const std::shared_ptr< DhtRunner > &dht, const ProxyServerConfig &config={}, const std::shared_ptr< log::Logger > &logger={})
std::shared_ptr< NodeInfo > nodeInfo