Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #pragma once
18 :
19 : #include "jamidht/channel_handler.h"
20 : #include "jamidht/jamiaccount.h"
21 : #include "jamidht/svc_protocol.h"
22 :
23 : #include <dhtnet/connectionmanager.h>
24 :
25 : #include <filesystem>
26 : #include <functional>
27 : #include <memory>
28 : #include <mutex>
29 : #include <string>
30 : #include <vector>
31 :
32 : namespace jami {
33 :
34 : /**
35 : * Channel handler for service-discovery requests.
36 : *
37 : * Server side: accepts any incoming `svcdisc://query` channel from a peer
38 : * with a valid Jami certificate and replies with a service_list filtered
39 : * through `ServiceManager::getVisibleServices`.
40 : *
41 : * Client side: `connect()` opens a `svcdisc://query` channel and sends a
42 : * SvcDiscQuery as soon as the channel is ready; the response is delivered
43 : * through the callback registered via `setOnResponse`.
44 : *
45 : * Cache: maintains a per-device service cache that is proactively refreshed
46 : * when a new device connects. queryPeerServices() reads from this cache.
47 : */
48 : class SvcDiscoveryChannelHandler : public ChannelHandlerInterface
49 : {
50 : public:
51 : /// Callback delivered to a client when a discovery response arrives.
52 : /// `services` is empty on error or version mismatch. `peerDeviceId` is
53 : /// the long device id of the responder, or empty if the response did
54 : /// not carry one (older peers / error paths).
55 : using ResponseCb = std::function<void(const std::string& peerAccountUri,
56 : const std::string& peerDeviceId,
57 : const std::vector<svc_protocol::SvcInfo>& services)>;
58 :
59 : SvcDiscoveryChannelHandler(const std::shared_ptr<JamiAccount>& acc,
60 : dhtnet::ConnectionManager& cm,
61 : std::filesystem::path cachePath);
62 : ~SvcDiscoveryChannelHandler() override;
63 :
64 : /// Register the handler invoked when a discovery response is received from
65 : /// any peer device. Replaces any previous registration.
66 : void setOnResponse(ResponseCb cb);
67 :
68 : /// Open a discovery channel to the given device. As soon as the channel
69 : /// is ready, a SvcDiscQuery is automatically sent.
70 : void connect(const DeviceId& deviceId,
71 : const std::string& name,
72 : ConnectCb&& cb,
73 : const std::string& connectionType = "",
74 : bool forceNewConnection = false) override;
75 :
76 : bool onRequest(const std::shared_ptr<dht::crypto::Certificate>& peer, const std::string& name) override;
77 :
78 : void onReady(const std::shared_ptr<dht::crypto::Certificate>& peer,
79 : const std::string& name,
80 : std::shared_ptr<dhtnet::ChannelSocket> channel) override;
81 :
82 : /// Build the SvcDiscResponse the host would send to the given peer,
83 : /// based on the current ServiceManager state. Exposed for testing.
84 : static svc_protocol::SvcDiscResponse buildResponse(JamiAccount& account, const std::string& peerAccountUri);
85 :
86 : // ---- Cache API ----
87 :
88 : /// Callback invoked when the service cache is updated for a peer device.
89 : using CacheUpdateCb = std::function<
90 : void(const std::string& peerUri, const DeviceId& deviceId, const std::vector<svc_protocol::SvcInfo>& services)>;
91 :
92 : /// Set a callback to be notified whenever the cache is updated.
93 : void onCacheUpdated(CacheUpdateCb cb);
94 :
95 : /// Proactively discover services on a newly-connected device and cache
96 : /// the result. Called from onNewDeviceConnection.
97 : void refreshDevice(const std::string& peerUri, const DeviceId& deviceId);
98 :
99 : /// Push the current service list to all connected peers over existing
100 : /// svcdisc channels. Each peer receives only the services they are
101 : /// authorized to see.
102 : void broadcastServiceUpdate();
103 :
104 : /// A service entry annotated with the device that offers it.
105 : struct CachedSvcInfo
106 : {
107 : DeviceId deviceId;
108 : svc_protocol::SvcInfo info;
109 : };
110 :
111 : /// Return the cached services for a peer (all devices aggregated).
112 : /// Each entry carries the device ID that advertised it.
113 : /// Returns an empty vector if nothing is cached yet.
114 : std::vector<CachedSvcInfo> getCachedServices(const std::string& peerUri) const;
115 :
116 : /// Remove all cached entries for a specific device.
117 : void removeDevice(const DeviceId& deviceId);
118 :
119 : private:
120 : /// State shared between the handler and per-channel read closures, so
121 : /// closures remain safe even if the handler is destroyed before the
122 : /// channel sockets it produced.
123 : struct State
124 : {
125 : std::mutex mtx;
126 : ResponseCb responseCb;
127 : CacheUpdateCb cacheUpdateCb;
128 : /// Strong references to channels we have onReady'd, indexed by peer
129 : /// account URI so we can push updates to specific peers.
130 : std::map<std::string, std::vector<std::shared_ptr<dhtnet::ChannelSocket>>> channels;
131 :
132 : // ---- Cache state ----
133 : struct CachedDeviceServices
134 : {
135 : std::string peerUri;
136 : std::vector<svc_protocol::SvcInfo> services;
137 9131 : MSGPACK_DEFINE_MAP(peerUri, services)
138 : };
139 : /// deviceId -> cached services
140 : std::map<DeviceId, CachedDeviceServices> cache;
141 : };
142 :
143 : void installReader(const std::shared_ptr<dhtnet::ChannelSocket>& channel, std::string peerAccountUri);
144 : void saveCache() const;
145 : void loadCache();
146 :
147 : std::weak_ptr<JamiAccount> account_;
148 : dhtnet::ConnectionManager& connectionManager_;
149 : std::shared_ptr<State> state_;
150 : std::filesystem::path cachePath_;
151 : };
152 :
153 : } // namespace jami
|