VLink  2.0.0
A high-performance communication middleware
node.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 node.h
26  * @brief Common CRTP base for every VLink communication primitive.
27  *
28  * @details
29  * @c Node<ImplT, SecT> is the polymorphic base class shared by all six
30  * VLink communication primitives. It owns the transport-specific
31  * implementation pointer (@c impl_), drives the node lifecycle, and exposes
32  * the cross-cutting services that every primitive needs: zero-copy loans,
33  * peer-discovery toggling, message-loop attachment, recording, security
34  * installation, profiling, and SSL/TLS configuration.
35  *
36  * @par Class Hierarchy
37  * @verbatim
38  * +--------------------------------------------------------------+
39  * | User-facing primitives |
40  * | Publisher<T> Subscriber<T> Setter<T> Getter<T> |
41  * | Client<Req,Resp> Server<Req,Resp> |
42  * +-----------------------------+--------------------------------+
43  * | inherits
44  * +-----------------------------v--------------------------------+
45  * | Node<ImplT, SecT> |
46  * | lifecycle | loans | properties | profiler | ssl | security |
47  * +-----------------------------+--------------------------------+
48  * | owns std::unique_ptr<ImplT>
49  * +-----------------------------v--------------------------------+
50  * | ImplT (PublisherImpl, SubscriberImpl, SetterImpl, ...) |
51  * +-----------------------------+--------------------------------+
52  * | dispatches to transport
53  * +-----------------------------v--------------------------------+
54  * | Transport back-ends |
55  * | intra | shm | shm2 | dds | ddsc | zenoh | someip | ... |
56  * +--------------------------------------------------------------+
57  * @endverbatim
58  *
59  * @par Lifecycle State Diagram
60  * @verbatim
61  * +------------+ constructor +------------+ init() +------------+
62  * | (uninited) |------------------>| parsed |------------->| active |
63  * | | parse URL / Conf | (impl_) | init_ext | (using API)|
64  * +------------+ +------------+ +------------+
65  * ^ |
66  * | init() | interrupt()
67  * | v
68  * +------+------+ +-------------+
69  * | deinited |<-----------| blocking |
70  * | | deinit() | wait aborts|
71  * +-------------+ +-------------+
72  * |
73  * | destructor (calls deinit if active)
74  * v
75  * destroyed
76  * @endverbatim
77  *
78  * | Step | Method | Notes |
79  * | ----------------- | ----------------------- | ------------------------------------------- |
80  * | Construction | constructor | Parses URL, creates impl via Conf factory. |
81  * | Initialisation | @c init() | Runs impl init + init_ext, samples loans. |
82  * | Active | publish / listen / ... | Normal operation. |
83  * | Interrupt | @c interrupt() | Aborts blocking waits immediately. |
84  * | Deinitialisation | @c deinit() | interrupt() then impl deinit + deinit_ext. |
85  * | Destruction | destructor | Auto-deinits if still active. |
86  *
87  * @par ImplType Roles
88  * | @c ImplType | Primitive that uses it | Direction |
89  * | ------------------- | --------------------------- | -------------- |
90  * | @c kPublisher | @c Publisher<T> | Event write |
91  * | @c kSubscriber | @c Subscriber<T> | Event read |
92  * | @c kClient | @c Client<Req,Resp> | RPC caller |
93  * | @c kServer | @c Server<Req,Resp> | RPC handler |
94  * | @c kSetter | @c Setter<T> | Field write |
95  * | @c kGetter | @c Getter<T> | Field read |
96  *
97  * @par Deferred Initialisation
98  * @code
99  * vlink::Publisher<MyMsg> pub("dds://topic", vlink::InitType::kWithoutInit);
100  * pub.set_ser_type("my.custom.Type");
101  * pub.set_discovery_enabled(false);
102  * pub.init();
103  * @endcode
104  *
105  * @par Security
106  * Use the @c Security* primitive aliases to enable per-message encryption.
107  * The @c Security::Config aggregate is passed as the second constructor
108  * argument; omitting it (or passing an empty aggregate) uses the built-in
109  * default symmetric slot:
110  * @code
111  * vlink::Security::Config cfg;
112  * cfg.key = "my-secret";
113  * vlink::SecurityPublisher<MyMsg> pub("shm://topic", cfg);
114  * @endcode
115  *
116  * @note @c intra:// and @c dds:// CDR payloads do not support per-message
117  * security; constructing a @c Security* primitive there is a fatal.
118  *
119  * @par Zero-copy Loans
120  * On loan-capable transports the loan API avoids extra copies:
121  * @code
122  * vlink::Publisher<vlink::Bytes> pub("shm://topic");
123  * if (pub.is_support_loan()) {
124  * vlink::Bytes buf = pub.loan(payload_size);
125  * write_into(buf);
126  * pub.publish(buf); // loan is returned automatically
127  * }
128  * @endcode
129  *
130  * @tparam ImplT Concrete transport implementation derived from @c NodeImpl.
131  * @tparam SecT Security mode: @c kWithoutSecurity (default) or @c kWithSecurity.
132  *
133  * @see publisher.h, subscriber.h, client.h, server.h, getter.h, setter.h,
134  * extension/security.h, extension/ssl_options.h
135  */
136 
137 #pragma once
138 
139 #include <atomic>
140 #include <memory>
141 #include <mutex>
142 #include <optional>
143 #include <string>
144 
145 #include "./extension/security.h"
146 #include "./impl/node_impl.h"
147 
148 namespace vlink {
149 
150 /**
151  * @class Node
152  * @brief Transport-agnostic CRTP base for all VLink communication primitives.
153  *
154  * @details
155  * Provides the lifecycle, loan, security, property, profiler, message-loop,
156  * recording, discovery, and TLS APIs shared by every primitive in the
157  * library. Subclasses fill in the role-specific operations (publish,
158  * listen, invoke, set, get, etc.).
159  *
160  * @tparam ImplT Concrete implementation class (e.g. @c PublisherImpl).
161  * @tparam SecT Security mode (@c kWithoutSecurity or @c kWithSecurity).
162  */
163 template <typename ImplT, SecurityType SecT>
164 class Node {
165  public:
166  using StatusCallback = NodeImpl::StatusCallback; ///< Handler signature for status-change notifications.
167 
168  /**
169  * @brief Initialises the node and its transport back-end.
170  *
171  * @details
172  * Uses an atomic compare-exchange to guard against double-initialisation.
173  * On success the method runs @c impl_->init() then @c impl_->init_ext()
174  * and finally samples the transport's loan capability flag. Calling
175  * @c init() on an already-initialised node is a no-op.
176  *
177  * @return @c true on first successful initialisation; @c false otherwise.
178  */
179  virtual bool init();
180 
181  /**
182  * @brief Tears the node down and releases all transport resources.
183  *
184  * @details
185  * Atomically guards against double-deinit, then runs @c interrupt(),
186  * @c impl_->deinit(), and @c impl_->deinit_ext(). When safe-quit mode is
187  * active the sequence runs under the safe-quit mutex. The destructor
188  * calls this automatically so explicit calls are only required for early
189  * shutdown.
190  *
191  * @return @c true on first successful deinit; @c false if not initialised.
192  */
193  virtual bool deinit();
194 
195  /**
196  * @brief Aborts any blocking wait on this node.
197  *
198  * @details
199  * Signals the internal interrupted flag and notifies the condition
200  * variable so that @c wait_for_subscribers(), @c wait_for_connected(), and
201  * @c wait_for_value() return immediately with @c false. @c Getter
202  * overrides this to additionally wake its own condition variable used by
203  * @c wait_for_value().
204  */
205  virtual void interrupt();
206 
207  /**
208  * @brief Reports whether @c init() has been successfully called.
209  *
210  * @return @c true when the node is currently in the initialised state.
211  */
212  [[nodiscard]] bool has_inited() const;
213 
214  /**
215  * @brief Reports whether the transport supports zero-copy loaned buffers.
216  *
217  * @details
218  * Delegates to the transport implementation. When loans are supported,
219  * @c publish() / @c set() / @c reply() automatically use them to avoid an
220  * extra memory copy.
221  *
222  * @return @c true if @c loan() / @c return_loan() are meaningful for this transport.
223  */
224  [[nodiscard]] bool is_support_loan() const;
225 
226  /**
227  * @brief Allocates a loaned buffer from the transport memory pool.
228  *
229  * @details
230  * Returns a @c Bytes backed by transport-managed memory of @p size bytes.
231  * The caller must either pass it to a publish/write call (which returns
232  * the loan automatically) or call @c return_loan() explicitly. Returns
233  * an empty @c Bytes on failure or when the transport has no loan pool.
234  *
235  * @param size Requested byte count; @c 0 is valid for empty messages.
236  * @return Loaned @c Bytes, or an empty @c Bytes on failure.
237  */
238  [[nodiscard]] Bytes loan(int64_t size);
239 
240  /**
241  * @brief Returns a previously loaned buffer to the transport pool.
242  *
243  * @details
244  * Must be called whenever a loan obtained via @c loan() is not consumed by
245  * a publish/write call; failing to return loans can exhaust the shared
246  * memory pool.
247  *
248  * @param bytes The loaned @c Bytes to return.
249  * @return @c true on success; @c false if the buffer is not a valid loan.
250  */
251  bool return_loan(const Bytes& bytes);
252 
253  /**
254  * @brief Toggles manual-unloan mode for zero-copy receives.
255  *
256  * @details
257  * In manual mode the user must call @c return_loan() after consuming each
258  * received buffer. The base implementation logs a warning; only
259  * @c Subscriber and @c Getter provide a meaningful override.
260  *
261  * @param manual_unloan @c true to enable; @c false for automatic (default).
262  */
263  virtual void set_manual_unloan(bool manual_unloan);
264 
265  /**
266  * @brief Reports whether manual-unloan mode is currently active.
267  *
268  * @return @c true if @c set_manual_unloan(true) was invoked.
269  */
270  [[nodiscard]] virtual bool is_manual_unloan() const;
271 
272  /**
273  * @brief Suspends message delivery on this node.
274  *
275  * @details
276  * Transport-dependent behaviour: some back-ends buffer incoming messages
277  * while suspended, others drop them. Pair with @c resume().
278  *
279  * @return @c true if suspension succeeded.
280  */
281  bool suspend();
282 
283  /**
284  * @brief Resumes message delivery after a prior @c suspend().
285  *
286  * @return @c true if resumption succeeded.
287  */
288  bool resume();
289 
290  /**
291  * @brief Reports whether the node is currently suspended.
292  *
293  * @return @c true while @c suspend() is in effect.
294  */
295  [[nodiscard]] bool is_suspend() const;
296 
297  /**
298  * @brief Attaches the node to a @c MessageLoop for callback dispatch.
299  *
300  * @details
301  * After attachment, inbound callbacks are posted onto @p message_loop
302  * rather than invoked on the transport delivery thread. This serialises
303  * dispatch onto the loop's thread, which is convenient for
304  * single-threaded user code.
305  *
306  * @param message_loop Pointer to the target @c MessageLoop.
307  * @return @c true on success; @c false if a loop is already attached.
308  */
309  bool attach(class MessageLoop* message_loop);
310 
311  /**
312  * @brief Detaches the node from its current @c MessageLoop.
313  *
314  * @details
315  * After detachment the callback returns to running on the transport
316  * delivery thread.
317  *
318  * @return @c true on success; @c false if no loop was attached.
319  */
320  bool detach();
321 
322  /**
323  * @brief Returns the @c MessageLoop this node is currently attached to.
324  *
325  * @return Pointer to the attached @c MessageLoop, or @c nullptr.
326  */
327  [[nodiscard]] class MessageLoop* get_message_loop() const;
328 
329  /**
330  * @brief Returns the abstract-graph handle for runtime topology inspection.
331  *
332  * @details
333  * The @c AbstractNode pointer is usable with @c AbstractFactory and the
334  * proxy monitoring API to enumerate peer nodes in the same transport
335  * graph.
336  *
337  * @return Non-owning pointer to the @c AbstractNode, or @c nullptr if the
338  * transport does not expose one.
339  */
340  [[nodiscard]] const AbstractNode* get_abstract_node() const;
341 
342  /**
343  * @brief Retrieves the current status object for the requested category.
344  *
345  * @details
346  * Returns a polymorphic shared pointer. The concrete type and set of
347  * supported categories depend on the active transport; an unsupported
348  * @p type yields a @c Status::Unknown instance and logs a warning.
349  *
350  * @param type Status category to retrieve.
351  * @return Shared pointer to status data, or @c Status::Unknown when unsupported.
352  */
353  [[nodiscard]] Status::BasePtr get_status(Status::Type type) const;
354 
355  /**
356  * @brief Registers a handler invoked whenever the node's status changes.
357  *
358  * @details
359  * Only one handler can be registered at a time; subsequent calls replace
360  * the previous handler. The handler receives a @c Status::BasePtr
361  * describing the new state (connected, disconnected, error, etc.).
362  *
363  * @param callback @c void(const Status::BasePtr&) handler.
364  */
365  void register_status_handler(StatusCallback&& callback);
366 
367  /**
368  * @brief Sets a transport-specific string-keyed property.
369  *
370  * @details
371  * Extensibility mechanism for back-end-specific tuning knobs that do not
372  * have a dedicated method. Recognised keys depend on the active
373  * transport.
374  *
375  * @param prop Property key string.
376  * @param value Property value string.
377  */
378  void set_property(const std::string& prop, const std::string& value);
379 
380  /**
381  * @brief Retrieves a previously set transport-specific property value.
382  *
383  * @param prop Property key string.
384  * @return Property value string; empty if the key is unknown.
385  */
386  [[nodiscard]] std::string get_property(const std::string& prop) const;
387 
388  /**
389  * @brief Returns the @c TransportType this node is bound to.
390  *
391  * @return Enumerator such as @c kDds, @c kShm, @c kIntra, etc.
392  */
393  [[nodiscard]] TransportType get_transport_type() const;
394 
395  /**
396  * @brief Returns the URL string used to construct this node.
397  *
398  * @details
399  * Non-empty only when the node was constructed via a URL string or @c Url
400  * object; typed @c ConfT-based construction leaves this empty.
401  *
402  * @return Const reference to the URL string.
403  */
404  [[nodiscard]] const std::string& get_url() const;
405 
406  /**
407  * @brief Enables recording of inbound or outbound messages to a bag file.
408  *
409  * @details
410  * Not supported on @c intra:// or @c dds:// CDR nodes (triggers a fatal log).
411  * Supported file suffixes are @c .vdb, @c .vdbx, @c .vcap, and @c .vcapx;
412  * an unknown suffix logs an error and disables recording.
413  *
414  * @param path Bag file path on disk.
415  */
416  void set_record_path(const std::string& path);
417 
418  /**
419  * @brief Overrides the runtime wire-metadata identifiers for this node.
420  *
421  * @details
422  * @p ser_type holds the concrete runtime type identifier; @p schema_type
423  * holds the coarse decoder family used by discovery, proxy, and bag
424  * metadata. When @p schema_type is @c SchemaType::kUnknown (the default)
425  * the family is inferred from @p ser_type: a @c "vlink::zerocopy::" prefix
426  * implies @c kZeroCopy, while values such as @c "raw", @c "string",
427  * @c "std::string", @c "text", or @c "json" imply @c kRaw. If no family
428  * can be inferred, an existing @c kRaw or @c kZeroCopy family is reset to
429  * @c kUnknown; any other existing family is preserved. Passing an empty
430  * @p ser_type clears both fields.
431  *
432  * If invoked post-@c init() the transport extension is restarted so that
433  * external metadata stays in sync.
434  *
435  * @param ser_type Concrete runtime type identifier, or empty to clear.
436  * @param schema_type Optional explicit schema family; default preserves the current family.
437  */
438  void set_ser_type(const std::string& ser_type, SchemaType schema_type = SchemaType::kUnknown);
439 
440  /**
441  * @brief Returns the current concrete runtime type identifier.
442  *
443  * @return Const reference to the type identifier string.
444  */
445  [[nodiscard]] const std::string& get_ser_type() const;
446 
447  /**
448  * @brief Returns the current coarse schema family.
449  *
450  * @return The @c SchemaType stored on the implementation.
451  */
452  [[nodiscard]] SchemaType get_schema_type() const;
453 
454  /**
455  * @brief Toggles peer-discovery on this node.
456  *
457  * @details
458  * Disabling discovery reduces CPU and network overhead for nodes that
459  * never need to locate peers. Reinitialises the transport extension if
460  * invoked post-@c init() so the change takes effect immediately.
461  *
462  * @param enable @c true (default) to enable discovery; @c false to disable.
463  */
464  void set_discovery_enabled(bool enable);
465 
466  /**
467  * @brief Reports whether peer-discovery is currently enabled.
468  *
469  * @return @c true if discovery is active.
470  */
471  [[nodiscard]] bool get_discovery_enabled() const;
472 
473  /**
474  * @brief Binds a Protobuf Arena for arena-allocated message objects.
475  *
476  * @details
477  * Required when @c MsgT is a raw Protobuf pointer type (e.g. @c MyProto*).
478  * The arena must outlive this node. Forgetting to bind an arena before
479  * the first received message triggers a fatal log.
480  *
481  * @param proto_arena Pointer to a @c google::protobuf::Arena instance (typed as @c void*).
482  */
483  void bind_proto_arena(void* proto_arena);
484 
485  /**
486  * @brief Returns the cumulative CPU-usage ratio sampled by the profiler.
487  *
488  * @details
489  * Reports the percentage of wall-clock time this node has spent in active
490  * publish or receive code since the profiler was started. Available only
491  * when the CPU profiler is built in (@c VLINK_DISABLE_PROFILER not
492  * defined) and global profiling is enabled via the @c VLINK_PROFILER_ENABLE
493  * environment variable. Returns @c -1.0 if no profiler is attached.
494  *
495  * @return CPU usage percentage (may exceed @c 100.0 on multi-core systems);
496  * @c -1.0 if unavailable.
497  */
498  [[nodiscard]] double get_cpu_usage() const;
499 
500  /**
501  * @brief Reports whether safe-quit mode is currently active.
502  *
503  * @details
504  * Safe-quit mode holds a @c std::mutex around user callbacks and around
505  * @c deinit() to prevent use-after-free races when a node is destroyed
506  * while a callback is in flight.
507  *
508  * @return @c true if the safe-quit mutex is engaged.
509  */
510  [[nodiscard]] bool get_safety_quit() const;
511 
512  /**
513  * @brief Enables or disables safe-quit mode.
514  *
515  * @details
516  * When enabled, an internal @c std::mutex is allocated and locked around
517  * every callback invocation and around @c deinit(). Enable when the
518  * node's lifetime is shorter than the callback scope. There is a small
519  * synchronisation overhead; avoid enabling it on hot paths.
520  *
521  * @param safety_quit @c true to enable; @c false to disable (default).
522  */
523  void set_safety_quit(bool safety_quit);
524 
525  /**
526  * @brief Configures transport-layer SSL/TLS encryption for this node.
527  *
528  * @details
529  * Merges the fields of @p options into the node's internal property map
530  * via @c SslOptions::parse_to(). The transport reads the resulting
531  * @c ssl.* properties during @c init() to set up the TLS connection, so
532  * this method must be called before @c init() for the settings to take
533  * effect.
534  *
535  * SSL is considered enabled when @c SslOptions::is_valid() returns
536  * @c true (i.e. at least @c ca_file or @c cert_file is non-empty). Not
537  * all back-ends support TLS; see @c SslOptions for the per-backend
538  * compatibility table. Thread-safe -- the property map is updated under
539  * a mutex.
540  *
541  * @par Example
542  * @code
543  * vlink::Publisher<MyMsg> pub("mqtt://sensor/data", vlink::InitType::kWithoutInit);
544  * vlink::SslOptions ssl;
545  * ssl.ca_file = "/etc/certs/ca.pem";
546  * ssl.cert_file = "/etc/certs/client.pem";
547  * ssl.key_file = "/etc/certs/client-key.pem";
548  * pub.set_ssl_options(ssl);
549  * pub.init();
550  * @endcode
551  *
552  * @param options SSL/TLS configuration to apply.
553  *
554  * @see SslOptions, set_property()
555  */
556  void set_ssl_options(const SslOptions& options);
557 
558  protected:
559  Node();
560 
561  virtual ~Node();
562 
563  /**
564  * @brief Installs a @c Security configuration before transport initialisation.
565  *
566  * @details
567  * Internal helper used by the @c Security* primitive constructors after
568  * @c impl_ has been created but before @c init(). Delegates default
569  * handling, validation, and storage to @c NodeImpl::enable_security().
570  *
571  * @param cfg Security configuration aggregate.
572  * @return @c true when @p cfg (including the default empty case) is usable for this role / transport.
573  */
574  bool enable_security(const Security::Config& cfg);
575 
576  /**
577  * @brief Move overload for construction-time security installation.
578  *
579  * @details
580  * Used when an internal caller owns the config and can forward it without
581  * an extra copy.
582  *
583  * @param cfg Security configuration aggregate to consume.
584  * @return @c true when @p cfg (including the default empty case) is usable for this role / transport.
585  */
586  bool enable_security(Security::Config&& cfg);
587 
588  template <typename CallbackT, typename... ArgsT>
589  void invoke_callback(const CallbackT& callback, ArgsT&&... args);
590 
591  template <typename TypeT>
592  TypeT get_default_value();
593 
594  void* proto_arena_{nullptr};
595  bool is_support_loan_{false};
596  bool is_manual_unloan_{false};
597 
598  std::atomic_bool has_inited_{false};
599  std::optional<std::mutex> quit_mtx_;
600 
601  std::unique_ptr<ImplT> impl_;
602 
603  private:
605 };
606 
607 } // namespace vlink
608 
609 #include "./internal/node-inl.h"
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174
Foundational base classes shared by every transport-backed VLink node.
Application-layer authenticated encryption with symmetric, hybrid asymmetric, and pluggable backends.