My Project 3.5.5
C++ Distributed Hash Table
Loading...
Searching...
No Matches
dht_proxy_server.h
1/*
2 * Copyright (C) 2014-2025 Savoir-faire Linux Inc.
3 * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
4 * Adrien Béraud <adrien.beraud@savoirfairelinux.com>
5 * Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#pragma once
22
23#include "callbacks.h"
24#include "def.h"
25#include "infohash.h"
26#include "proxy.h"
27#include "scheduler.h"
28#include "sockaddr.h"
29#include "value.h"
30#include "http.h"
31
32#include <restinio/all.hpp>
33#include <restinio/tls.hpp>
34#include <json/json.h>
35
36#include <memory>
37#include <mutex>
38
39namespace dht {
40enum class PushType {
41 None = 0,
42 Android,
43 iOS,
44 UnifiedPush
45};
46}
47MSGPACK_ADD_ENUM(dht::PushType)
48
49namespace Json {
50class Value;
51}
52
53namespace dht {
54
55namespace http {
56class Request;
57struct ListenerSession;
58}
59
60class DhtRunner;
61
62using RestRouter = restinio::router::express_router_t<>;
63using RequestStatus = restinio::request_handling_status_t;
64
65struct OPENDHT_PUBLIC ProxyServerConfig {
66 std::string address {};
67 in_port_t port {8000};
68 std::string pushServer {};
69 std::string persistStatePath {};
70 dht::crypto::Identity identity {};
71 std::string bundleId {};
72};
73
77class OPENDHT_PUBLIC DhtProxyServer
78{
79public:
88 DhtProxyServer(const std::shared_ptr<DhtRunner>& dht,
89 const ProxyServerConfig& config = {},
90 const std::shared_ptr<log::Logger>& logger = {});
91
92 virtual ~DhtProxyServer();
93
94 DhtProxyServer(const DhtProxyServer& other) = delete;
95 DhtProxyServer(DhtProxyServer&& other) = delete;
96 DhtProxyServer& operator=(const DhtProxyServer& other) = delete;
97 DhtProxyServer& operator=(DhtProxyServer&& other) = delete;
98
99 asio::io_context& io_context() const;
100
101 using clock = std::chrono::steady_clock;
102 using time_point = clock::time_point;
103
104 struct PushStats {
105 uint64_t highPriorityCount {0};
106 uint64_t normalPriorityCount {0};
107
108 void increment(bool highPriority) {
109 if (highPriority)
110 highPriorityCount++;
111 else
112 normalPriorityCount++;
113 }
114
115 Json::Value toJson() const {
116 Json::Value val;
117 val["highPriorityCount"] = static_cast<Json::UInt64>(highPriorityCount);
118 val["normalPriorityCount"] = static_cast<Json::UInt64>(normalPriorityCount);
119 return val;
120 }
121
122 std::string toString() const {
123 return fmt::format("{} high priority, {} normal priority", highPriorityCount, normalPriorityCount);
124 }
125 };
126
127 struct OPENDHT_PUBLIC ServerStats {
129 size_t listenCount {0};
131 size_t putCount {0};
136
138 time_point serverStartTime;
140 time_point lastUpdated;
144 PushStats iosPush;
145 PushStats unifiedPush;
146
148 double requestRate {0};
150 std::shared_ptr<NodeInfo> nodeInfo {};
151
152 std::string toString() const;
153
157 Json::Value toJson() const;
158 };
159
160 std::shared_ptr<ServerStats> stats() const { return stats_; }
161
162 std::shared_ptr<ServerStats> updateStats(std::shared_ptr<NodeInfo> info) const;
163
164 std::shared_ptr<DhtRunner> getNode() const { return dht_; }
165
166private:
167 class ConnectionListener;
168 struct RestRouterTraitsTls;
169 struct RestRouterTraits;
170
171 template <typename HttpResponse>
172 static HttpResponse initHttpResponse(HttpResponse response);
173 static restinio::request_handling_status_t serverError(restinio::request_t& request);
174
175 template< typename ServerSettings >
176 void addServerSettings(ServerSettings& serverSettings,
177 const unsigned int max_pipelined_requests = 16);
178
179 std::unique_ptr<RestRouter> createRestRouter();
180
181 void onConnectionClosed(restinio::connection_id_t);
182
190 RequestStatus getNodeInfo(restinio::request_handle_t request,
191 restinio::router::route_params_t params) const;
192
199 RequestStatus getStats(restinio::request_handle_t request,
200 restinio::router::route_params_t params);
201
212 RequestStatus get(restinio::request_handle_t request,
213 restinio::router::route_params_t params);
214
225 RequestStatus listen(restinio::request_handle_t request,
226 restinio::router::route_params_t params);
227
237 RequestStatus put(restinio::request_handle_t request,
238 restinio::router::route_params_t params);
239
240 void handleCancelPermamentPut(const asio::error_code &ec, const InfoHash& key, Value::Id vid);
241
242#ifdef OPENDHT_PROXY_SERVER_IDENTITY
252 RequestStatus putSigned(restinio::request_handle_t request,
253 restinio::router::route_params_t params) const;
254
264 RequestStatus putEncrypted(restinio::request_handle_t request,
265 restinio::router::route_params_t params);
266
267#endif // OPENDHT_PROXY_SERVER_IDENTITY
268
279 RequestStatus getFiltered(restinio::request_handle_t request,
280 restinio::router::route_params_t params);
281
289 RequestStatus options(restinio::request_handle_t request,
290 restinio::router::route_params_t params);
291
292 struct PushSessionContext {
293 std::mutex lock;
294 std::string sessionId;
295 PushSessionContext(const std::string& id) : sessionId(id) {}
296 };
297
298#ifdef OPENDHT_PUSH_NOTIFICATIONS
299 PushType getTypeFromString(const std::string& type);
300 std::string getDefaultTopic(PushType type);
301
302 RequestStatus pingPush(restinio::request_handle_t request,
303 restinio::router::route_params_t /*params*/);
313 RequestStatus subscribe(restinio::request_handle_t request,
314 restinio::router::route_params_t params);
315
323 RequestStatus unsubscribe(restinio::request_handle_t request,
324 restinio::router::route_params_t params);
325
331 void sendPushNotification(const std::string& key, Json::Value&& json, PushType type, bool highPriority, const std::string& topic);
332
341 void handleNotifyPushListenExpire(const asio::error_code &ec, const std::string pushToken,
342 std::function<Json::Value()> json, PushType type, const std::string& topic);
343
351 void handleCancelPushListen(const asio::error_code &ec, const std::string pushToken,
352 const InfoHash key, const std::string clientId);
353
367 bool handlePushListen(const InfoHash& infoHash, const std::string& pushToken,
368 PushType type, const std::string& clientId,
369 const std::shared_ptr<DhtProxyServer::PushSessionContext>& sessionCtx, const std::string& topic,
370 const std::vector<std::shared_ptr<Value>>& values, bool expired);
371
372#endif //OPENDHT_PUSH_NOTIFICATIONS
373
374 void handlePrintStats(const asio::error_code &ec);
375 void updateStats();
376
377 template <typename Os>
378 void saveState(Os& stream);
379
380 template <typename Is>
381 void loadState(Is& is, size_t size);
382
383 std::shared_ptr<asio::io_context> ioContext_;
384 std::shared_ptr<DhtRunner> dht_;
385 Json::StreamWriterBuilder jsonBuilder_;
386 Json::CharReaderBuilder jsonReaderBuilder_;
387 std::mt19937_64 rd {crypto::getSeededRandomEngine<std::mt19937_64>()};
388
389 std::string persistPath_;
390
391 // http server
392 std::thread serverThread_;
393 std::unique_ptr<restinio::http_server_t<RestRouterTraitsTls>> httpsServer_;
394 std::unique_ptr<restinio::http_server_t<RestRouterTraits>> httpServer_;
395
396 // http client
397 std::pair<std::string, std::string> pushHostPort_;
398
399 mutable std::mutex requestLock_;
400 std::map<unsigned int /*id*/, std::shared_ptr<http::Request>> requests_;
401
402 std::shared_ptr<log::Logger> logger_;
403
404 std::shared_ptr<ServerStats> stats_;
405 std::shared_ptr<NodeInfo> nodeInfo_ {};
406 std::unique_ptr<asio::steady_timer> printStatsTimer_;
407 const time_point serverStartTime_;
408 mutable std::mutex pushStatsMutex_;
409 PushStats androidPush_;
410 PushStats iosPush_;
411 PushStats unifiedPush_;
412
413 // Thread-safe access to listeners map.
414 std::mutex lockListener_;
415 // Shared with connection listener.
416 std::map<restinio::connection_id_t, http::ListenerSession> listeners_;
417 // Connection Listener observing conn state changes.
418 std::shared_ptr<ConnectionListener> connListener_;
419 struct PermanentPut {
420 time_point expiration;
421 std::string pushToken;
422 std::string clientId;
423 std::shared_ptr<PushSessionContext> sessionCtx;
424 std::unique_ptr<asio::steady_timer> expireTimer;
425 std::unique_ptr<asio::steady_timer> expireNotifyTimer;
426 Sp<Value> value;
427 PushType type;
428 std::string topic;
429
430 template <typename Packer>
431 void msgpack_pack(Packer& p) const
432 {
433 p.pack_map(2 + (sessionCtx ? 1 : 0) + (clientId.empty() ? 0 : 1) + (type == PushType::None ? 0 : 2) + (topic.empty() ? 0 : 1));
434 p.pack("value"); p.pack(value);
435 p.pack("exp"); p.pack(to_time_t(expiration));
436 if (not clientId.empty()) {
437 p.pack("cid"); p.pack(clientId);
438 }
439 if (sessionCtx) {
440 std::lock_guard<std::mutex> l(sessionCtx->lock);
441 p.pack("sid"); p.pack(sessionCtx->sessionId);
442 }
443 if (type != PushType::None) {
444 p.pack("t"); p.pack(type);
445 p.pack("token"); p.pack(pushToken);
446 }
447 if (not topic.empty()) {
448 p.pack("top"); p.pack(topic);
449 }
450 }
451
452 void msgpack_unpack(const msgpack::object& o);
453 };
454 struct SearchPuts {
455 std::map<dht::Value::Id, PermanentPut> puts;
456 MSGPACK_DEFINE_ARRAY(puts)
457 };
458 std::mutex lockSearchPuts_;
459 std::map<InfoHash, SearchPuts> puts_;
460
461 mutable std::atomic<size_t> requestNum_ {0};
462 mutable std::atomic<time_point> lastStatsReset_ {time_point::min()};
463
464 std::string pushServer_;
465 std::string bundleId_;
466
467#ifdef OPENDHT_PUSH_NOTIFICATIONS
468 struct Listener {
469 time_point expiration;
470 std::string clientId;
471 std::shared_ptr<PushSessionContext> sessionCtx;
472 std::future<size_t> internalToken;
473 std::unique_ptr<asio::steady_timer> expireTimer;
474 std::unique_ptr<asio::steady_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"); p.pack(clientId);
483 p.pack("exp"); p.pack(to_time_t(expiration));
484 if (sessionCtx) {
485 std::lock_guard<std::mutex> l(sessionCtx->lock);
486 p.pack("sid"); p.pack(sessionCtx->sessionId);
487 }
488 p.pack("t"); p.pack(type);
489 if (!topic.empty()) {
490 p.pack("top"); p.pack(topic);
491 }
492 }
493
494 void msgpack_unpack(const msgpack::object& o);
495 };
496 struct PushListener {
497 std::map<InfoHash, std::vector<Listener>> listeners;
498 MSGPACK_DEFINE_ARRAY(listeners)
499 };
500 std::map<std::string, PushListener> pushListeners_;
501#endif //OPENDHT_PUSH_NOTIFICATIONS
502};
503
504}
DhtProxyServer(const std::shared_ptr< DhtRunner > &dht, const ProxyServerConfig &config={}, const std::shared_ptr< log::Logger > &logger={})
std::shared_ptr< NodeInfo > nodeInfo