VLink  2.0.0
A high-performance communication middleware
proxy_api.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file proxy_api.h
26  * @brief Client-side C++ API for connecting to a @c ProxyServer daemon.
27  *
28  * @details
29  * @c ProxyAPI is the consumer-facing half of the VLink proxy subsystem. It connects
30  * to a running @c ProxyServer over a security-authenticated DDS channel set and
31  * exposes asynchronous callbacks for connection state, error transitions, server
32  * heartbeats, per-topic statistics, and (when active) raw payload bytes. The class
33  * inherits @c MessageLoop, so posted tasks, timers, handshake retries, and async
34  * control publishes execute inside the inherited loop once @c run() or
35  * @c async_run() has been started. Incoming DDS/SHM callbacks may still arrive on
36  * transport-managed receive threads.
37  *
38  * @par Roles
39  * Each instance is constructed with exactly one role.
40  *
41  * | Role | Description |
42  * | ---------------- | ----------------------------------------------------------------------- |
43  * | @c kController | Drives the server: may call @c send_control() and @c send_data(). |
44  * | @c kListener | Passive observer only; @c send_control() / @c send_data() return false. |
45  *
46  * @par Inheritance Pattern
47  * Both @c ProxyAPI and @c ProxyServer derive from @c MessageLoop. Application code
48  * may either embed a @c ProxyAPI directly or subclass it to centralise callback
49  * wiring.
50  * @code
51  * MessageLoop
52  * ^
53  * |
54  * ProxyAPI <-- this class (publicly inherits MessageLoop)
55  * ^
56  * |
57  * MyMonitor : public ProxyAPI
58  * - register_connect_callback()
59  * - register_info_callback()
60  * - register_data_callback()
61  * @endcode
62  *
63  * @par Operation Modes
64  * Selectable by a @c kController instance through @c Control::mode.
65  *
66  * | Mode | Value | Description |
67  * | ----------------------- | ----- | -------------------------------------------------------- |
68  * | @c kOffline | 0 | Disconnected; server releases all subscriptions. |
69  * | @c kObserveOne | 1 | Observe a single selected topic. |
70  * | @c kObserveAll | 2 | Observe every discovered topic. |
71  * | @c kRecord | 3 | Record every topic in the subscription URL list. |
72  * | @c kPlay | 4 | Playback / injection, usually fed by recorded data. |
73  * | @c kEdit | 5 | Forward data injected by the controller. |
74  * | @c kAuto | 6 | Observe selected topics and accept controller injection. |
75  * | @c kAutoAndObserveAll | 7 | Observe every topic and accept controller injection. |
76  *
77  * @par Error Codes
78  *
79  * | Error | Value | Description |
80  * | ----------------------- | ----- | --------------------------------------------------------------- |
81  * | @c kNoError | 0 | No error. |
82  * | @c kModeError | 1 | Reserved legacy code for unsupported modes. |
83  * | @c kControlError | 2 | Control ID mismatch with the server. |
84  * | @c kReliableCompError | 3 | @c reliable setting mismatch between client and server. |
85  * | @c kTcpCompError | 4 | @c enable_tcp setting mismatch. |
86  * | @c kDirectCompError | 5 | @c direct setting mismatch. |
87  * | @c kMultiProxyError | 7 | Multiple proxy servers or @c Time identity mismatch. |
88  * | @c kVersionCompError | 8 | VLink version mismatch between client and server. |
89  * | @c kTokenError | 9 | Handshake refused/empty token, or same-identity token mismatch. |
90  * | @c kUnknownError | 10 | Unknown or unclassified error. |
91  *
92  * @par Connectivity, Handshake, and Heartbeat
93  * Internally the API subscribes to the 1-second @c Time heartbeat published by
94  * @c ProxyServer. When @c VLINK_PROXY_ENABLE_HANDSHAKE is non-zero (default), it
95  * also runs an RPC handshake against the server's security-authenticated handshake
96  * service before any @c Control may be published; the server replies with a
97  * per-process @em token that the API caches under a dedicated mutex and stamps onto
98  * every outgoing @c Control. A refused or empty token raises @c kTokenError, while
99  * handshake setup/connect/invoke timeouts are treated as channel-not-ready and
100  * retried silently. Every incoming heartbeat must come from the same server
101  * identity and carry the same token: identity mismatch raises @c kMultiProxyError,
102  * an identity-matching token mismatch raises @c kTokenError; both paths clear the
103  * cached token and retry the handshake. After five consecutive seconds without a
104  * heartbeat the connection is declared lost and @c ConnectCallback fires with
105  * @c connected=false. A @c kController instance caches and posts an initial
106  * @c kAuto @c Control at construction and re-sends the last control automatically
107  * once the server reconnects; actual publication waits for the loop, handshake, and
108  * transport handles to be ready. When the macro is @c 0, the handshake is bypassed
109  * and controls flow without any token field.
110  *
111  * @par Channel Configuration
112  *
113  * | @c direct | Data path | Control / Info / Time / Handshake path |
114  * | --------- | ------------------------------------------ | -------------------------------------- |
115  * | false | Server-relayed DDS data channel | DDS (security-enabled) |
116  * | true | Local direct SHM publishers / subscribers | DDS (security-enabled) |
117  *
118  * @par Version Matching
119  * When @c Config::match_version is @c true (default) the client cross-checks the
120  * server's @c VLINK_VERSION string at connection time and reports
121  * @c kVersionCompError on mismatch.
122  *
123  * @par Example (Controller)
124  * @code
125  * vlink::ProxyAPI::Config cfg;
126  * cfg.role = vlink::ProxyAPI::kController;
127  * cfg.dds_impl = "dds";
128  * cfg.domain_id = 0;
129  * cfg.reliable = false;
130  * cfg.match_version = true;
131  *
132  * vlink::ProxyAPI api(cfg);
133  * api.register_connect_callback([](bool connected) {
134  * if (connected) {
135  * // server online
136  * }
137  * });
138  * api.register_info_callback([](const std::vector<vlink::ProxyAPI::Info>& list) {
139  * for (const auto& info : list) {
140  * // info.url, info.freq, info.status, ...
141  * }
142  * });
143  * api.async_run(); // start the loop so handshake/timers can run
144  *
145  * vlink::ProxyAPI::Control ctrl;
146  * ctrl.mode = vlink::ProxyAPI::kObserveOne;
147  * ctrl.url_meta_list.push_back(
148  * {"dds://my/topic", "demo.proto.PointCloud", vlink::SchemaType::kProtobuf, vlink::kSubscriber});
149  * api.send_control(ctrl);
150  * @endcode
151  *
152  * @note
153  * - Only one @c ProxyServer should exist on a given DDS domain at a time; reaching
154  * two will trigger @c kMultiProxyError.
155  * - @c send_control() and @c send_data() return @c false immediately for
156  * @c kListener instances.
157  */
158 
159 #pragma once
160 
161 #undef VLINK_PROXY_API_EXPORT
162 #ifdef VLINK_PROXY_API_LIBRARY_STATIC
163 #define VLINK_PROXY_API_EXPORT
164 #elif defined(_WIN32) || defined(__CYGWIN__)
165 #ifdef VLINK_PROXY_API_LIBRARY
166 #define VLINK_PROXY_API_EXPORT __declspec(dllexport)
167 #else
168 #define VLINK_PROXY_API_EXPORT __declspec(dllimport)
169 #endif
170 #else
171 #define VLINK_PROXY_API_EXPORT __attribute__((visibility("default")))
172 #endif
173 
174 #include <cstdint>
175 #include <memory>
176 #include <string>
177 #include <unordered_set>
178 #include <vector>
179 
180 #include "../base/bytes.h"
181 #include "../base/functional.h"
182 #include "../base/message_loop.h"
183 #include "../impl/types.h"
184 
185 namespace vlink {
186 
187 /**
188  * @class ProxyAPI
189  * @brief Client-side proxy monitoring and control surface backed by a @c MessageLoop.
190  *
191  * @details
192  * Connects to a @c ProxyServer over security-authenticated DDS control channels and
193  * exposes registration entry points for connection state, error transitions,
194  * heartbeat timestamps, per-topic statistics, and raw data payloads. In direct mode
195  * data payloads ride on locally-created SHM channels instead of the server-relayed
196  * data channel. Inherits @c MessageLoop; start the loop with @c run() or
197  * @c async_run() so the handshake retries, heartbeat timer, handle reset, and async
198  * control posts can execute.
199  */
201  public:
202  /**
203  * @enum Mode
204  * @brief Proxy operation modes published via @c send_control().
205  *
206  * @details
207  * The mode governs what the @c ProxyServer observes or what the controller may
208  * inject. Only a @c kController instance may switch the mode.
209  */
210  enum Mode : uint8_t {
211  kOffline = 0, ///< Disconnected; server releases every subscription.
212  kObserveOne = 1, ///< Observe a single topic taken from the URL list.
213  kObserveAll = 2, ///< Observe every discovered topic on the network.
214  kRecord = 3, ///< Record data from topics in the URL list.
215  kPlay = 4, ///< Injection/playback mode, usually fed by recorded data.
216  kEdit = 5, ///< Forward data injected by the controller.
217  kAuto = 6, ///< Observe specified URLs and accept controller injection.
218  kAutoAndObserveAll = 7, ///< Observe every topic and accept controller injection.
219  };
220 
221  /**
222  * @enum Error
223  * @brief Compatibility and protocol error codes reported through @c ErrorCallback.
224  *
225  * @details
226  * Errors are produced when the client parses an incoming @c Time heartbeat or a
227  * handshake response; the callback fires only when the error state changes.
228  */
229  enum Error : uint8_t {
230  kNoError = 0, ///< No error; the connection is healthy.
231  kModeError = 1, ///< Reserved legacy code; unsupported modes are ignored server-side.
232  kControlError = 2, ///< Server responded with a different @c control_id.
233  kReliableCompError = 3, ///< @c reliable setting differs between client and server.
234  kTcpCompError = 4, ///< @c enable_tcp setting differs between client and server.
235  kDirectCompError = 5, ///< @c direct setting differs between client and server.
236  kMultiProxyError = 7, ///< Multiple proxy servers, or Time identity differs from the handshake.
237  kVersionCompError = 8, ///< @c VLINK_VERSION string differs between client and server.
238  kTokenError = 9, ///< Handshake refused/empty token or identity-matching token mismatch.
239  kUnknownError = 10, ///< Unknown or unclassified error.
240  };
241 
242  /**
243  * @enum Status
244  * @brief Per-topic activity status reported inside @c Info records.
245  */
246  enum Status : uint8_t {
247  kActive = 0, ///< Topic is actively receiving data.
248  kInActive = 1, ///< Topic exists but has not produced data recently.
249  kPending = 2, ///< Topic was just discovered; statistics are still accumulating.
250  kInvalid = 3, ///< Topic type does not support observation (e.g. subscriber-only).
251  };
252 
253  /**
254  * @enum Role
255  * @brief Role this @c ProxyAPI instance plays towards the @c ProxyServer.
256  *
257  * @details
258  * @c kController may call @c send_control() and @c send_data(). @c kListener is
259  * a passive observer; send calls return @c false immediately.
260  */
261  enum Role : uint8_t { kController = 0, kListener };
262 
263  /**
264  * @struct Process
265  * @brief Description of a VLink node process attached to a topic endpoint.
266  */
267  struct Process final {
268  uint32_t type{0}; ///< Node-type bitmask (kPublisher / kSubscriber / kServer / kClient / kSetter / kGetter).
269  std::string host; ///< Hostname of the machine running the process.
270  uint32_t pid{0}; ///< Operating-system process ID.
271  std::string name; ///< Human-readable process name.
272  std::string ip; ///< IP address of the network interface in use.
273  };
274 
275  /**
276  * @struct Info
277  * @brief Statistics and metadata describing one discovered topic endpoint.
278  *
279  * @details
280  * Delivered in batches through @c InfoCallback once per second. @c freq, @c rate,
281  * @c loss, and @c latency are weighted moving averages over the last two
282  * one-second collection intervals.
283  */
284  struct Info final {
285  uint32_t type{0}; ///< Node-type bitmask for this endpoint.
286  std::string url; ///< Full topic URL, e.g. @c "dds://my/topic".
287  std::string ser; ///< Serialisation type, e.g. @c "demo.proto.PointCloud".
288  SchemaType schema{SchemaType::kUnknown}; ///< Coarse schema family of the payload.
289  Status status{kInvalid}; ///< Current activity status of the topic.
290  float freq{0}; ///< Observed message rate in messages/s.
291  uint64_t rate{0}; ///< Observed throughput in bytes/s.
292  float loss{0}; ///< Sample loss ratio in the range [0, 1].
293  float latency{0}; ///< Latency in ms; @c -1 = no sample, @c -2 = invalid.
294  std::vector<Process> process_list; ///< Connected publisher/subscriber processes.
295  };
296 
297  /**
298  * @struct UrlMeta
299  * @brief Pairs a topic URL with its serialisation type and node role.
300  *
301  * @details
302  * Carried in @c Control::url_meta_list to tell the server which topics to
303  * subscribe to or publish on. @c type describes the proxy route direction; for
304  * direct field relays, setter/getter peers may be mapped to the matching field
305  * reader/writer semantics internally.
306  */
307  struct UrlMeta final {
308  std::string url; ///< Full topic URL.
309  std::string ser; ///< Required serialisation type on this proxy route.
310  SchemaType schema{SchemaType::kUnknown}; ///< Required coarse schema family on this proxy route.
311  ImplType type{kSubscriber}; ///< Whether the server should act as publisher or subscriber here.
312  };
313 
314  /**
315  * @struct Control
316  * @brief Control message sent from a @c kController instance to @c ProxyServer.
317  *
318  * @details
319  * Sets the server's operating mode and the list of topics to observe, record, or
320  * inject/play. @c filter_str is a space- or comma-separated set of substrings; entries
321  * must contain at least one substring to be included. When @c filter_by_process
322  * is @c true, the filter is matched against process names instead of URLs.
323  */
324  struct Control final {
325  Mode mode{kOffline}; ///< Target operation mode.
326  std::vector<UrlMeta> url_meta_list; ///< Topics to observe / record / inject (mode-dependent).
327  bool filter_by_process{false}; ///< When true, @c filter_str matches process names; otherwise URLs.
328  std::string filter_str; ///< Space- or comma-separated filter keywords (case-insensitive).
329  uint32_t filter_type{0}; ///< Type filter: 0=all, 1=pub+sub pair, 2=srv+cli pair, etc.
330  };
331 
332  /**
333  * @struct Data
334  * @brief Raw message payload delivered via @c DataCallback or sent via @c send_data().
335  *
336  * @details
337  * In non-direct observe/record modes the server relays raw serialised bytes plus
338  * routing metadata. In direct mode the callback is fed by the local direct
339  * subscriber created by @c ProxyAPI. When sending data, @c timestamp and @c seq
340  * are caller-defined and forwarded verbatim.
341  */
342  struct Data final {
343  std::string url; ///< Topic URL the data was captured on.
344  std::string ser; ///< Serialisation type of the payload.
345  SchemaType schema{SchemaType::kUnknown}; ///< Coarse schema family of the payload.
346  Bytes raw; ///< Raw serialised message bytes.
347  int64_t timestamp{-1}; ///< Sender/session timestamp in microseconds; @c -1 if unset.
348  int64_t seq{0}; ///< Sender or relay sequence number for the URL.
349  };
350 
351  /**
352  * @struct Config
353  * @brief Construction-time configuration aggregate for @c ProxyAPI.
354  *
355  * @details
356  * Every field must be set consistently with the @c ProxyServer::Config the
357  * instance connects to. A mismatch on @c reliable, @c enable_tcp, or @c direct
358  * raises the corresponding @c Error code on the first heartbeat received.
359  */
360  struct Config final {
361  Role role{kController}; ///< Role of this client instance.
362  int domain_id{0}; ///< DDS domain ID; must match the server's @c domain_id.
363  std::string dds_impl{"dds"}; ///< DDS implementation: "dds", "ddsc", "ddsr", etc.
364  std::string security_key; ///< Optional security key; empty selects the default slot.
365  bool native{false}; ///< When true, restrict all DDS traffic to 127.0.0.1.
366  bool reliable{false}; ///< Use reliable DDS QoS; must match the server.
367  bool direct{false}; ///< Use direct SHM channels for data; must match the server.
368  bool enable_tcp{false}; ///< Use TCP transport for data channels; must match the server.
369  bool match_version{true}; ///< Reject when the server's @c VLINK_VERSION differs from this client.
370  std::string allow_ip; ///< Bind DDS sockets to this IP (empty = any).
371  std::string peer_ip; ///< Unicast peer IP for DDS discovery (empty = multicast).
372  int buf_size{0}; ///< Socket send/receive buffer in bytes; 0 = default.
373  int mtu_size{0}; ///< DDS MTU size in bytes; 0 = default.
374  };
375 
376  /**
377  * @brief Callback fired when the connection state with @c ProxyServer changes.
378  *
379  * @details
380  * @c connected becomes @c true on the first valid heartbeat and @c false after
381  * five seconds of heartbeat silence or when the control channel reports
382  * disconnection.
383  */
384  using ConnectCallback = MoveFunction<void(bool connected)>;
385 
386  /**
387  * @brief Callback fired on each error-state transition.
388  *
389  * @details
390  * Only invoked when @c Error changes -- for instance from @c kNoError to
391  * @c kVersionCompError, or back to @c kNoError.
392  */
393  using ErrorCallback = MoveFunction<void(Error error)>;
394 
395  /**
396  * @brief Callback delivering the server's wall-clock and boot-time from each heartbeat.
397  *
398  * @param sys_time Server system time in microseconds since the Unix epoch.
399  * @param boot_time Server uptime in microseconds since boot.
400  */
401  using TimeCallback = MoveFunction<void(uint64_t sys_time, uint64_t boot_time)>;
402 
403  /**
404  * @brief Callback delivering the per-topic statistics list once per second.
405  *
406  * @param info_list Filtered discovery records relevant to the current mode,
407  * including their status fields.
408  */
409  using InfoCallback = MoveFunction<void(const std::vector<Info>& info_list)>;
410 
411  /**
412  * @brief Callback delivering raw message data from the active proxy data path.
413  *
414  * @details
415  * Fires for every message forwarded in observe, record, or auto modes. In
416  * non-direct mode the bytes are relayed by @c ProxyServer; in direct mode they
417  * come from local direct subscribers. @c Data::raw is shallow-borrowed -- copy
418  * it if you must retain it past the callback.
419  */
420  using DataCallback = MoveFunction<void(const Data& data)>;
421 
422  /**
423  * @brief Constructs a @c ProxyAPI using the supplied configuration.
424  *
425  * @details
426  * Stores @p config, starts the internal heartbeat timer, and derives a unique
427  * @c control_id from the CPU timestamp so the server can distinguish concurrent
428  * controllers. Transport handles are created by @c reset_handle(), which runs
429  * when the inherited @c MessageLoop starts and again whenever the reconnect logic
430  * resets the connection. A controller also caches and posts an initial @c kAuto
431  * @c Control, but it is only published once the loop, handshake, and transport
432  * handles are all ready.
433  *
434  * @param config Configuration for the proxy connection.
435  */
436  explicit ProxyAPI(const Config& config);
437 
438  /**
439  * @brief Destroys the @c ProxyAPI and releases every transport handle.
440  *
441  * @details
442  * Quits the inherited @c MessageLoop (if running), waits for it to stop, then
443  * releases DDS/SHM handles. For controller instances, normal @c MessageLoop
444  * shutdown publishes @c kOffline from @c on_end().
445  */
446  ~ProxyAPI() override;
447 
448  /**
449  * @brief Registers a callback for connection-state changes.
450  *
451  * @details
452  * If the API is already connected when the registration runs, the callback is
453  * invoked immediately with @c connected=true inside this call. Callbacks are
454  * replaced atomically; only one callback is active at a time.
455  *
456  * @param callback Callable with signature @c void(bool connected).
457  */
459 
460  /**
461  * @brief Registers a callback for error-state transitions.
462  *
463  * @details
464  * If a non-zero error is already active at registration time, the callback fires
465  * immediately with the current error inside this call.
466  *
467  * @param callback Callable with signature @c void(Error error).
468  */
470 
471  /**
472  * @brief Registers a callback for heartbeat timestamp delivery.
473  *
474  * @details
475  * If timestamps have already been received (timers active) the callback is
476  * invoked immediately with the latest extrapolated values inside this call.
477  *
478  * @param callback Callable with signature @c void(uint64_t sys_time, uint64_t boot_time).
479  */
481 
482  /**
483  * @brief Registers a callback for per-topic statistics updates.
484  *
485  * @details
486  * Invoked once per second with the full @c Info list from the server. There is
487  * no immediate invocation at registration; data arrives on the next server
488  * broadcast cycle.
489  *
490  * @param callback Callable with signature @c void(const std::vector<Info>& info_list).
491  */
493 
494  /**
495  * @brief Registers a callback for raw message data relayed by the server.
496  *
497  * @details
498  * There is no immediate invocation at registration time. Data arrives when the
499  * server is in a mode that forwards messages: @c kObserveOne, @c kObserveAll,
500  * @c kRecord, @c kAuto, or @c kAutoAndObserveAll.
501  *
502  * @param callback Callable with signature @c void(const Data& data).
503  */
505 
506  /**
507  * @brief Publishes a @c Control message to the @c ProxyServer.
508  *
509  * @details
510  * Valid only for the @c kController role; returns @c false immediately for
511  * @c kListener. The control is cached internally and automatically re-sent when
512  * the server reconnects after a dropout.
513  *
514  * When @p async is @c true (default) the DDS @c Control publish is posted to the
515  * @c MessageLoop thread and the return value reports only whether posting was
516  * accepted. When @p async is @c false the publish runs synchronously on the
517  * calling thread and the return value reports the publish result. In direct
518  * mode, direct-map synchronisation is still queued on the @c MessageLoop, so the
519  * return value also depends on whether that enqueue was accepted.
520  *
521  * Entries in @c control.url_meta_list are encoded using their supplied @c ser and
522  * @c schema verbatim. Proxy no longer back-fills missing routing metadata from
523  * discovery caches: entries with an empty @c ser or unknown @c schema are ignored
524  * by the direct-map sync path and cannot become usable server-side routes.
525  *
526  * In @c direct mode, matching local SHM publishers are created or destroyed to
527  * mirror the publisher entries. Direct subscribers are synchronised either from
528  * subscriber entries in @c url_meta_list or, for @c kObserveAll and
529  * @c kAutoAndObserveAll, from the latest @c Info list reported by the server.
530  * Setter endpoints are observed with getter semantics so field updates keep
531  * their last-value behaviour.
532  *
533  * @param control Control message to send.
534  * @param async @c true to post asynchronously (default); @c false to block.
535  * @return When @p async=false, @c true means the DDS publish succeeded and
536  * any direct-map sync was queued. When @p async=true, @c true
537  * means the publish task was accepted. @c false on listener role,
538  * shutdown, or enqueue/publish failure.
539  *
540  * @note Thread-safe. The control is also stored as the last-known control for
541  * automatic resend on reconnect.
542  */
543  bool send_control(const Control& control, bool async = true);
544 
545  /**
546  * @brief Injects raw message data into the proxy data path.
547  *
548  * @details
549  * Valid only for the @c kController role; returns @c false for @c kListener. In
550  * @c direct mode the data is published through the local SHM publisher matching
551  * @c data.url and @c ProxyServer is not on the data relay path. In non-direct
552  * mode the payload is wrapped in a @c ProxyData envelope and forwarded over the
553  * DDS data channel. Returns @c false if no subscribers are listening on the
554  * target channel.
555  *
556  * The caller must provide both @c data.ser and a known @c data.schema. Proxy no
557  * longer guesses the decode stack from cached discovery metadata.
558  *
559  * @param data Data to inject: URL, serialisation type, schema family, raw bytes,
560  * timestamp, and sequence number.
561  * @return In non-direct mode, @c true only if the underlying data publish
562  * succeeds. In direct mode, @c true means a matching local direct
563  * publisher and subscriber were found, metadata matched, and the
564  * publish attempt was made (backend result is not surfaced).
565  * @c false otherwise.
566  */
567  bool send_data(const Data& data);
568 
569  /**
570  * @brief Returns the configuration supplied at construction.
571  *
572  * @return Const reference to the internal @c Config aggregate.
573  */
574  [[nodiscard]] const Config& get_current_config() const;
575 
576  /**
577  * @brief Returns the most recently requested proxy operation mode.
578  *
579  * @details
580  * Reflects the mode most recently set through @c send_control(). Updated on the
581  * calling thread before any async DDS publish.
582  *
583  * @return Current @c Mode value.
584  */
585  [[nodiscard]] Mode get_current_mode() const;
586 
587  /**
588  * @brief Returns the current error state.
589  *
590  * @return Current @c Error value; @c kNoError when no error is active.
591  */
592  [[nodiscard]] Error get_current_error() const;
593 
594  /**
595  * @brief Returns the hostname of the connected @c ProxyServer.
596  *
597  * @details
598  * With handshake enabled, initialised from the successful handshake response and
599  * cross-checked against @c Time heartbeats. Without handshake, populated from
600  * the first valid heartbeat. Empty before any server identity is accepted and
601  * after disconnection.
602  *
603  * @return Server hostname, or empty when unavailable.
604  */
605  [[nodiscard]] std::string get_current_hostname() const;
606 
607  /**
608  * @brief Returns the machine ID of the connected @c ProxyServer.
609  *
610  * @details
611  * With handshake enabled, initialised from the successful handshake response and
612  * cross-checked against @c Time heartbeats. Without handshake, populated from
613  * the first valid heartbeat. Empty before any server identity is accepted and
614  * after disconnection.
615  *
616  * @return Server machine ID, or empty when unavailable.
617  */
618  [[nodiscard]] std::string get_current_machine_id() const;
619 
620  /**
621  * @brief Returns the best estimate of the server's current wall-clock time.
622  *
623  * @details
624  * Computed as the last received @c sys_time plus the elapsed microseconds since
625  * that heartbeat, extrapolated via an @c ElapsedTimer. Returns the raw
626  * @c sys_time field when the timer is not yet running.
627  *
628  * @return Estimated server system time in microseconds since the Unix epoch.
629  */
630  [[nodiscard]] uint64_t get_current_sys_time() const;
631 
632  /**
633  * @brief Returns the best estimate of the server's current boot time.
634  *
635  * @details
636  * Computed as the last received @c boot_time plus the elapsed microseconds since
637  * that heartbeat, extrapolated via an @c ElapsedTimer.
638  *
639  * @return Estimated server uptime in microseconds since boot.
640  */
641  [[nodiscard]] uint64_t get_current_boot_time() const;
642 
643  /**
644  * @brief Returns the server's most recently reported CPU utilisation.
645  *
646  * @return CPU usage percentage in the range [0, 100]; @c 0 when disconnected.
647  */
648  [[nodiscard]] double get_current_cpu_usage() const;
649 
650  /**
651  * @brief Returns the server's most recently reported memory utilisation.
652  *
653  * @return Memory usage percentage in the range [0, 100]; @c 0 when disconnected.
654  */
655  [[nodiscard]] double get_current_memory_usage() const;
656 
657  /**
658  * @brief Returns the latest backend-reported latency on the data channel.
659  *
660  * @details
661  * In direct (SHM) mode, or before the DDS data subscriber is initialised, this
662  * returns @c 0. Otherwise it delegates to the underlying data subscriber's
663  * latency tracker. Main backends report one-way/end-to-end data latency in
664  * nanoseconds.
665  *
666  * @return Latency in nanoseconds, or @c 0 when unavailable.
667  */
668  [[nodiscard]] int64_t get_latency() const;
669 
670  /**
671  * @brief Returns the sample-loss statistics on the data channel.
672  *
673  * @details
674  * In direct (SHM) mode, or before the DDS data subscriber is initialised, this
675  * returns a default-constructed (zero) @c SampleLostInfo. Otherwise it
676  * delegates to the underlying data subscriber.
677  *
678  * @return @c SampleLostInfo holding total and lost sample counters.
679  */
680  [[nodiscard]] SampleLostInfo get_lost() const;
681 
682  /**
683  * @brief Returns whether a valid connection to @c ProxyServer exists.
684  *
685  * @details
686  * @c true once the first valid heartbeat arrives; @c false after five seconds
687  * without a heartbeat or when the control channel reports disconnection.
688  *
689  * @return @c true if connected, @c false otherwise.
690  */
691  [[nodiscard]] bool is_connected() const;
692 
693  /**
694  * @brief Returns the @c VLINK_VERSION string reported by the server.
695  *
696  * @details
697  * With handshake enabled, initialised from the successful handshake response and
698  * subsequently refreshed from @c Time metadata after token/control checks.
699  * Without handshake, populated from the first valid heartbeat. Empty before any
700  * server version is accepted and after a handle reset.
701  *
702  * @return Server VLink version string, e.g. @c "2.0.0".
703  */
704  [[nodiscard]] std::string get_proxy_version() const;
705 
706  /**
707  * @brief Returns every server hostname observed during this session.
708  *
709  * @details
710  * Hostnames are accumulated for the lifetime of the connection. They are NOT
711  * cleared on disconnect, only when @c reset_handle() runs.
712  *
713  * @return Unordered set of observed hostnames.
714  */
715  [[nodiscard]] std::unordered_set<std::string> get_proxy_hostnames() const;
716 
717  /**
718  * @brief Returns every server machine ID observed during this session.
719  *
720  * @details
721  * Analogous to @c get_proxy_hostnames(); accumulated for the lifetime of the
722  * connection.
723  *
724  * @return Unordered set of observed machine IDs.
725  */
726  [[nodiscard]] std::unordered_set<std::string> get_proxy_machine_ids() const;
727 
728  /**
729  * @brief Returns @c true when the build includes SHM (Iceoryx) support.
730  *
731  * @details
732  * Decided at compile time by @c VLINK_SUPPORT_SHM. Useful to gate
733  * @c Config::direct.
734  *
735  * @return @c true when SHM support is compiled in.
736  */
737  [[nodiscard]] static bool is_support_shm();
738 
739  /**
740  * @brief Returns @c true when topic filtering support is compiled in.
741  *
742  * @details
743  * Decided at compile time by @c VLINK_PROXY_ENABLE_FILTER. When @c false the
744  * @c filter_str and @c filter_by_process fields in @c Control are ignored by the
745  * server.
746  *
747  * @return @c true when filtering is enabled.
748  */
749  [[nodiscard]] static bool is_enable_filter();
750 
751  /**
752  * @brief Formats a microsecond wall-clock timestamp as a human-readable string.
753  *
754  * @details
755  * Output format: @c "YYYY/MM/DD HH:MM:SS:mmm" where @c mmm is milliseconds.
756  * Uses @c localtime_r by default; @c gmtime_r when @p enable_utc is @c true.
757  *
758  * @param time Microseconds since the Unix epoch.
759  * @param enable_utc @c true for UTC; @c false for local time (default).
760  * @return Formatted timestamp string, or empty on conversion error.
761  */
762  [[nodiscard]] static std::string get_format_sys_time(uint64_t time, bool enable_utc = false);
763 
764  /**
765  * @brief Formats a microsecond boot-time duration as a human-readable string.
766  *
767  * @details
768  * Converts @p time (microseconds since boot) into a formatted elapsed string,
769  * e.g. @c "0d 01:23:45.678".
770  *
771  * @param time Microseconds since boot.
772  * @return Formatted elapsed-time string.
773  */
774  [[nodiscard]] static std::string get_format_boot_time(uint64_t time);
775 
776  protected:
777  size_t get_max_task_count() const override;
778 
779  uint32_t get_max_elapsed_time() const override;
780 
781  void on_begin() override;
782 
783  void on_end() override;
784 
785  private:
786  bool send_control_sync(const Control& control);
787 
788  void sync_direct_maps(const Control& control);
789 
790  bool do_handshake(Error& out_err);
791 
792  void reset_handle();
793 
794  void process_connected(bool connected);
795 
796  void process_time(uint64_t sys_time, uint64_t boot_time);
797 
798  void process_error(Error error);
799 
800  struct Impl;
801  std::unique_ptr<Impl> impl_;
802 
804 };
805 
806 } // namespace vlink
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174
#define VLINK_PROXY_API_EXPORT
Definition: proxy_api.h:171