VLink  2.0.0
A high-performance communication middleware
node_impl.h
Go to the documentation of this file.
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 node_impl.h
26  * @brief Foundational base classes shared by every transport-backed VLink node.
27  *
28  * @details
29  * This is an internal implementation header used by the public node templates
30  * (@c Publisher, @c Subscriber, @c Client, @c Server, @c Setter, @c Getter)
31  * and by every transport backend; it is not part of the public API surface.
32  * The header introduces two cooperating base classes:
33  *
34  * - @c AbstractNode -- exposes a single @c get_native_handle() escape hatch so
35  * advanced users can reach the transport-native object behind a node.
36  * - @c NodeImpl -- the heavyweight base that every @c PublisherImpl,
37  * @c SubscriberImpl, @c ServerImpl, @c ClientImpl, @c SetterImpl and
38  * @c GetterImpl ultimately derive from. It centralises lifecycle hooks,
39  * property storage, message-loop attachment, interruption signalling and the
40  * ancillary observability paths used by discovery, recording and proxying.
41  *
42  * @par NodeImpl interface contract
43  * | Concern | API surface |
44  * | ---------------------- | ------------------------------------------------------------- |
45  * | Transport lifecycle | @c init() / @c deinit() |
46  * | Zero-copy loans | @c loan() / @c return_loan() / @c set_manual_unloan |
47  * | Suspend / resume | @c suspend() / @c resume() / @c is_suspend() |
48  * | Interrupt | @c interrupt() / @c reset_interrupted() / @c is_interrupted |
49  * | Property store | @c set_property() / @c get_property() / @c get_all_properties |
50  * | Message loop binding | @c attach() / @c detach() / @c get_message_loop() |
51  * | Status callbacks | @c register_status_handler() / @c call_status() |
52  * | Recording | @c set_record_path() / @c try_record() |
53  * | Extension / security | @c init_ext() / @c deinit_ext() / @c enable_security() |
54  * | Version probe | @c check_version() |
55  * | Process bootstrap | static @c global_init() |
56  *
57  * @par Lifecycle
58  * @code
59  * construct -> set_property -> enable_security -> init -> init_ext
60  * ^ ^
61  * | |
62  * attach(loop) ---+ | active
63  * |
64  * interrupt -> reset_interrupted (optional)
65  * |
66  * v
67  * deinit_ext -> deinit -> destroy
68  * @endcode
69  *
70  * @par ImplType / SecurityType / InitType
71  * - @c ImplType (defined in @c types.h) is captured at construction time and
72  * exposes the node role to discovery / proxy.
73  * - @c SecurityType is the template parameter on the public node templates that
74  * selects whether @c enable_security() runs.
75  * - @c InitType (also from @c types.h) controls whether the public template
76  * calls @c init() immediately or defers it until the user invokes @c init()
77  * manually.
78  *
79  * @par Callback signatures
80  * | Alias | Signature | Used by |
81  * | -------------------- | ---------------------------------------- | -------------------------------- |
82  * | @c ConnectCallback | @c void(bool) | Publisher/Client/Server peers |
83  * | @c StatusCallback | @c void(const Status::BasePtr&) | DDS-family status reporting |
84  * | @c SyncCallback | @c void() | SetterImpl late-getter sync |
85  * | @c ReqRespCallback | @c void(uint64_t, const Bytes&, Bytes*) | ServerImpl request handling |
86  * | @c MsgCallback | @c void(const Bytes&) | SubscriberImpl / GetterImpl |
87  * | @c IntraMsgCallback | @c void(const IntraData&) | intra:// subscriber |
88  */
89 
90 #pragma once
91 
92 #include <any>
93 #include <atomic>
94 #include <memory>
95 #include <string>
96 
97 #include "../base/bytes.h"
98 #include "../base/cpu_profiler.h"
99 #include "../base/functional.h"
100 #include "../extension/security.h"
101 #include "../extension/status.h"
102 #include "../impl/conf.h"
103 #include "../impl/types.h"
104 #include "./intra_data.h"
105 #include "./ssl_options.h"
106 
107 namespace vlink {
108 
109 /**
110  * @class AbstractNode
111  * @brief Tiny mix-in that exposes the backend-native handle behind a node.
112  *
113  * @details
114  * Transport backends commonly inherit from both @c AbstractNode and the matching
115  * @c NodeImpl subclass. @c get_native_handle() returns an @c std::any wrapping
116  * the native object (for instance a DDS @c DataWriter pointer) so advanced users
117  * can interact with backend internals without leaking transport types through
118  * the rest of the VLink API.
119  *
120  * @note The base implementation returns an @c std::any holding @c nullptr; the
121  * caller must @c std::any_cast to the expected backend type.
122  */
124  public:
125  /**
126  * @brief Returns the backend-native handle wrapped in an @c std::any.
127  *
128  * @details
129  * Backends override this method to expose, for example, a DDS @c DataWriter
130  * or @c DataReader pointer. Callers must @c std::any_cast the result.
131  *
132  * @return Backend handle, or @c nullptr-bearing @c std::any in the base.
133  */
134  virtual std::any get_native_handle() const;
135 
136  protected:
138  virtual ~AbstractNode();
139 
140  private:
142 };
143 
144 /**
145  * @class NodeImpl
146  * @brief Backbone of every transport-specific VLink node implementation.
147  *
148  * @details
149  * Every concrete transport backend ultimately derives from @c NodeImpl. The
150  * class centralises lifecycle, instrumentation and observability so that
151  * subclass authors only need to focus on transport mechanics. The contract
152  * table at file scope summarises which knobs the base owns and which require
153  * a transport override.
154  *
155  * @note @c suspend() and @c resume() are optional capabilities; the base
156  * implementation logs a warning and returns @c false so that backends
157  * lacking the operation can be detected gracefully.
158  *
159  * @note @c register_status_handler() and @c call_status() are only meaningful
160  * on DDS-family transports (@c kDds, @c kDdsc, @c kDdsr, @c kDdst);
161  * other backends log a warning and treat them as no-ops.
162  */
164  public:
165  /**
166  * @brief Callback fired when peer presence changes.
167  *
168  * The boolean argument is @c true when the peer becomes reachable and
169  * @c false when it disappears.
170  */
171  using ConnectCallback = Function<void(bool)>;
172 
173  /**
174  * @brief Callback fired on DDS-family status notifications (e.g. deadline missed).
175  *
176  * @param ptr Polymorphic status object; downcast to the concrete subtype.
177  */
178  using StatusCallback = Function<void(const Status::BasePtr& ptr)>;
179 
180  /**
181  * @brief Callback fired when a @c SetterImpl completes a late-getter sync.
182  */
183  using SyncCallback = Function<void()>;
184 
185  /**
186  * @brief Request / response handler installed by @c ServerImpl::listen().
187  *
188  * @details
189  * Parameters are @c (req_id, request_bytes, response_bytes_ptr). The handler
190  * writes the response into @c *response_bytes_ptr; when the pointer is
191  * @c nullptr the server is running in fire-and-forget mode.
192  */
193  using ReqRespCallback = Function<void(uint64_t, const Bytes&, Bytes*)>;
194 
195  /**
196  * @brief Callback delivering a serialised payload to a subscriber or getter.
197  *
198  * @param bytes Payload bytes; lifetime is scoped to the callback.
199  */
200  using MsgCallback = Function<void(const Bytes&)>;
201 
202  /**
203  * @brief Callback delivering an in-process payload to an intra:// subscriber.
204  *
205  * @details
206  * @c IntraData is a @c shared_ptr to the payload type, so no copy occurs as
207  * the callback is dispatched.
208  */
209  using IntraMsgCallback = Function<void(const IntraData&)>;
210 
211  /**
212  * @brief Brings the underlying transport channel online.
213  *
214  * @details
215  * Called by the public Node<> template after properties have been wired in.
216  * Backends create their DDS entities, shared-memory channels or any other
217  * resources here.
218  */
219  virtual void init() = 0;
220 
221  /**
222  * @brief Tears the underlying transport channel down.
223  *
224  * @details
225  * Called by the Node<> template destructor; releases all transport-owned
226  * resources.
227  */
228  virtual void deinit() = 0;
229 
230  /**
231  * @brief Pauses message delivery without releasing the channel.
232  *
233  * @details
234  * The base implementation logs a warning and returns @c false. Only a few
235  * backends implement true suspension; others should leave the default.
236  *
237  * @return @c true on success; @c false when the operation is unsupported.
238  */
239  virtual bool suspend();
240 
241  /**
242  * @brief Resumes message delivery after @c suspend().
243  *
244  * @details
245  * Default no-op returns @c false. Override in backends that support
246  * suspension.
247  *
248  * @return @c true on success; @c false otherwise.
249  */
250  virtual bool resume();
251 
252  /**
253  * @brief Reports whether the node is currently suspended.
254  *
255  * @details
256  * Default implementation logs a warning and returns @c false. Override
257  * alongside @c suspend() / @c resume() when relevant.
258  *
259  * @return @c true when the node is paused.
260  */
261  [[nodiscard]] virtual bool is_suspend() const;
262 
263  /**
264  * @brief Marks the node interrupted and wakes blocking operations.
265  *
266  * @details
267  * Sets the internal interrupted flag and is overridden by subclasses to
268  * signal additional condition variables (e.g. @c wait_for_subscribers()).
269  * Pair with @c reset_interrupted() before reusing blocking helpers.
270  */
271  virtual void interrupt();
272 
273  /**
274  * @brief Indicates whether zero-copy loaning is available on this backend.
275  *
276  * @return @c true when @c loan() / @c return_loan() can be used.
277  */
278  [[nodiscard]] virtual bool is_support_loan() const;
279 
280  /**
281  * @brief Borrows a write buffer of @p size bytes from the transport.
282  *
283  * @details
284  * Backends that support zero-copy override this method to return a @c Bytes
285  * view onto pre-allocated transport memory. The default returns an empty
286  * @c Bytes.
287  *
288  * @param size Requested buffer size in bytes.
289  * @return Borrowed @c Bytes; empty when loaning is unsupported or failed.
290  */
291  [[nodiscard]] virtual Bytes loan(int64_t size);
292 
293  /**
294  * @brief Returns a previously loaned buffer to the transport.
295  *
296  * @details
297  * Must be called whenever @c loan() succeeded but the caller decided not to
298  * publish (for example because serialisation failed).
299  *
300  * @param bytes Buffer previously obtained via @c loan().
301  * @return @c true on success; @c false when loaning is unsupported.
302  */
303  virtual bool return_loan(const Bytes& bytes);
304 
305  /**
306  * @brief Toggles automatic versus manual release of received loaned buffers.
307  *
308  * @details
309  * With @p manual_unloan set to @c true the backend does not release loaned
310  * buffers after callback dispatch; the application must call @c return_loan()
311  * itself.
312  *
313  * @param manual_unloan @c true for manual release; @c false for automatic.
314  */
315  virtual void set_manual_unloan(bool manual_unloan);
316 
317  /**
318  * @brief Returns the associated transport @c Conf, or @c nullptr in the base.
319  *
320  * @details
321  * Concrete backends override the method to expose their typed @c Conf so the
322  * public node templates can resolve transport-specific options.
323  *
324  * @return Pointer to the owning @c Conf.
325  */
326  [[nodiscard]] virtual const struct Conf* get_conf() const;
327 
328  /**
329  * @brief Returns the companion @c AbstractNode, when the impl is split.
330  *
331  * @return Pointer to the abstract node, or @c nullptr.
332  */
333  [[nodiscard]] virtual const AbstractNode* get_abstract_node() const;
334 
335  /**
336  * @brief Retrieves a transport-specific status object.
337  *
338  * @details
339  * Only meaningful on DDS-family transports; the base logs a warning and
340  * returns @c Status::Unknown.
341  *
342  * @param type Requested status category.
343  * @return Polymorphic status object; never @c nullptr.
344  */
345  [[nodiscard]] virtual Status::BasePtr get_status(Status::Type type) const;
346 
347  /**
348  * @brief Compares @p version with the runtime VLink library version.
349  *
350  * @details
351  * Logs a warning (once per process) on the first mismatch. Version checks
352  * are advisory and do not block node creation.
353  *
354  * @param version Compile-time version constants embedded by the caller.
355  * @return @c true when the versions agree.
356  */
357  virtual bool check_version(const Version& version);
358 
359  /**
360  * @brief Attaches the node to a @c MessageLoop for callback dispatch.
361  *
362  * @details
363  * Records @p message_loop atomically; subsequent @c call_status() posts onto
364  * the loop thread. When another non-null loop is already attached the call
365  * is rejected. Passing @c nullptr does not establish a usable binding even
366  * though it may succeed when the node was already detached.
367  *
368  * @param message_loop Loop to bind to.
369  * @return @c true when the pointer was stored; @c false on conflict.
370  */
371  virtual bool attach(class MessageLoop* message_loop);
372 
373  /**
374  * @brief Detaches the node from its @c MessageLoop.
375  *
376  * @details
377  * Clears the cached pointer and, when called from a thread different from
378  * the loop, blocks until the previous loop becomes idle so no callback is
379  * still in flight on return.
380  *
381  * @return @c true on success; @c false if no loop was attached.
382  */
383  virtual bool detach();
384 
385  /**
386  * @brief Returns the currently attached @c MessageLoop.
387  *
388  * @return Loop pointer or @c nullptr when none is bound.
389  */
390  [[nodiscard]] class MessageLoop* get_message_loop() const;
391 
392  /**
393  * @brief Convenience downcast wrapper around @c get_conf().
394  *
395  * @tparam T Expected concrete @c Conf subclass.
396  * @return Typed pointer; @c nullptr when @c get_conf() returns @c nullptr.
397  */
398  template <typename T>
399  [[nodiscard]] const T* get_target_conf() const;
400 
401  /**
402  * @brief Installs a DDS-family status handler.
403  *
404  * @details
405  * No-op on non-DDS transports. When a @c MessageLoop is attached the
406  * callback is invoked on the loop thread.
407  *
408  * @param callback Handler invoked for each status notification.
409  */
411 
412  /**
413  * @brief Reports whether a status handler has been registered.
414  *
415  * @return @c true when a non-null status handler exists.
416  */
417  [[nodiscard]] bool has_register_status() const;
418 
419  /**
420  * @brief Delivers a status notification to the registered handler.
421  *
422  * @details
423  * Posts the call onto the attached @c MessageLoop when one exists; otherwise
424  * invokes the handler synchronously.
425  *
426  * @param ptr Status object to forward.
427  */
429 
430  /**
431  * @brief Sets a named property on this node.
432  *
433  * @details
434  * Properties are key / value strings consumed during @c init(). The
435  * underlying map is guarded by a shared mutex for thread safety.
436  *
437  * @param prop Property key.
438  * @param value Property value.
439  */
440  void set_property(const std::string& prop, const std::string& value);
441 
442  /**
443  * @brief Retrieves the value of a named property.
444  *
445  * @param prop Property key.
446  * @return Value, or empty string when no entry exists.
447  */
448  [[nodiscard]] std::string get_property(const std::string& prop) const;
449 
450  /**
451  * @brief Returns a copy of every property currently set on this node.
452  *
453  * @return Snapshot of the internal @c PropertiesMap.
454  */
456 
457  /**
458  * @brief Toggles whether this node is reported to the discovery layer.
459  *
460  * @details
461  * Must be called before @c init_ext(). Disabling discovery hides the node
462  * from @c DiscoveryReporter / @c DiscoveryViewer; the proxy implementation
463  * uses this to keep its internal channels off topology views.
464  *
465  * @param enable @c true to report to discovery (default); @c false to hide.
466  */
467  void set_discovery_enabled(bool enable);
468 
469  /**
470  * @brief Returns the current discovery-reporting flag.
471  *
472  * @return @c true when the node will be visible to discovery.
473  */
474  [[nodiscard]] bool get_discovery_enabled() const;
475 
476  /**
477  * @brief Configures per-node message recording.
478  *
479  * @details
480  * A non-empty @p path acquires (or shares) a @c BagWriter so that subsequent
481  * @c try_record() calls capture frames. An empty path turns recording off.
482  *
483  * @param path File path; empty or unsupported suffix disables recording.
484  */
485  void set_record_path(const std::string& path);
486 
487  /**
488  * @brief Installs application-layer security from a const-reference config.
489  *
490  * @details
491  * Performs the wire-metadata validation and AAD-context plumbing, then
492  * forwards a fully prepared @c Security::Config into a @c Security instance
493  * stored in @c security. Unsupported transports return @c false without
494  * installing anything.
495  *
496  * @param cfg Security configuration supplied by the public node wrapper.
497  * @return @c true once a usable @c Security instance is installed.
498  */
500 
501  /**
502  * @brief Installs application-layer security by consuming @p cfg.
503  *
504  * @details
505  * Same validation path as the const-reference overload, but avoids copying
506  * callback targets and key material when the caller owns the config. The
507  * AAD context may be filled before the config is moved into @c Security.
508  *
509  * @param cfg Security configuration to consume.
510  * @return @c true once a usable @c Security instance is installed.
511  */
513 
514  /**
515  * @brief Merges SSL options into the node property map.
516  *
517  * @details
518  * Acquires the helper mutex and delegates to @c SslOptions::parse_to() so
519  * the transport factory reads the resulting @c ssl.* entries during
520  * connection setup.
521  *
522  * @param options SSL / TLS configuration to merge.
523  *
524  * @see SslOptions::parse_to(), Node::set_ssl_options()
525  */
526  void set_ssl_options(const SslOptions& options);
527 
528  /**
529  * @brief Records a message to the global and / or per-node bag writers.
530  *
531  * @details
532  * Skips DDS CDR payloads and (when the ignore-intra filter is on) intra
533  * messages. Used by every node role to feed @c BagWriter pipelines.
534  *
535  * @param action_type Logical role under which the message is being recorded.
536  * @param data Raw serialised payload bytes.
537  */
538  void try_record(ActionType action_type, const Bytes& data);
539 
540  /**
541  * @brief Clears the interrupted flag set by @c interrupt().
542  *
543  * @details
544  * Required before re-running a blocking helper such as
545  * @c wait_for_subscribers() after a prior interruption.
546  */
548 
549  /**
550  * @brief Reports whether @c interrupt() has been called and not yet reset.
551  *
552  * @return @c true when the interrupted flag is set.
553  */
554  [[nodiscard]] bool is_interrupted() const;
555 
556  /**
557  * @brief Registers the node with the runtime-owned discovery reporter.
558  *
559  * @details
560  * Invoked at the end of @c init() by every public Node<> template. Also
561  * starts the per-node @c CpuProfiler when global profiling is enabled.
562  * Skips registration for CDR, security, and (by default) intra nodes.
563  */
564  void init_ext();
565 
566  /**
567  * @brief Deregisters the node from the runtime-owned discovery reporter.
568  *
569  * @details
570  * Mirrors @c init_ext(); called after @c deinit() to release the discovery
571  * entry and restart the global profiler if it had been paused.
572  */
573  void deinit_ext();
574 
575  /**
576  * @brief Initialises process-wide VLink singletons.
577  *
578  * @details
579  * Brings the logger, memory pool, and global bag writer online, and prepares the
580  * runtime-owned discovery reporter holder without starting the reporter until a
581  * discoverable node is registered. Safe to call multiple times; only the first
582  * call has an effect. The base constructor invokes this automatically.
583  */
584  static void global_init();
585 
586  std::string url; ///< Full URL string of the node, e.g. @c "dds://my/topic".
587  std::string ser_type; ///< Concrete serialisation type tag (e.g. @c "demo.proto.PointCloud").
588  ImplType impl_type{kUnknownImplType}; ///< Role of this implementation node.
589  SchemaType schema_type{SchemaType::kUnknown}; ///< Coarse schema family reported to discovery and bag / proxy paths.
590  TransportType transport_type{TransportType::kUnknown}; ///< Transport backend identifier for this node.
591  bool is_cdr_type{false}; ///< @c true when DDS native CDR serialisation is in use.
592  bool is_security_type{false}; ///< @c true when an authenticated transport is enabled.
593  bool is_discovery_enabled{true}; ///< Whether the node is reported to the discovery layer.
594  std::atomic_bool has_suspend{false}; ///< Atomic suspend flag (currently unused by the default impls).
595  std::unique_ptr<CpuProfiler> profiler; ///< Optional per-node CPU profiler activated under global profiling.
596  std::unique_ptr<Security> security; ///< Installed per-node message-security context, or @c nullptr.
597 
598  protected:
599  explicit NodeImpl(ImplType type);
600 
601  virtual ~NodeImpl();
602 
603  private:
604  std::unique_ptr<struct NodeImplHelper> helper_;
605 
607 };
608 
609 ////////////////////////////////////////////////////////////////
610 /// Details
611 ////////////////////////////////////////////////////////////////
612 
613 template <typename T>
614 inline const T* NodeImpl::get_target_conf() const {
615  return static_cast<const T*>(get_conf());
616 }
617 
618 } // namespace vlink
Reference-counted in-process payload type used by the intra:// backend.
#define VLINK_EXPORT
Definition: macros.h:81
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174
Backend-neutral SSL / TLS settings shared by all VLink transports that support encryption.