VLink  2.0.0
A high-performance communication middleware
subscriber.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 subscriber.h
26  * @brief Read-side primitive of the VLink event communication model.
27  *
28  * @details
29  * @c Subscriber<MsgT, SecT> attaches a callback to a VLink topic. Each
30  * frame delivered by the transport back-end is deserialised into a @c MsgT
31  * instance and forwarded to the user callback registered via @c listen().
32  * Unlike @c Getter, the subscriber retains no value history -- it simply
33  * forwards every event in delivery order.
34  *
35  * The class is a thin, header-only template wrapper around @c SubscriberImpl.
36  * Codec dispatch is fully resolved at compile time using the type detected
37  * by @c Serializer::get_type_of<MsgT>().
38  *
39  * @par Event-model Delivery Path
40  * @verbatim
41  * Transport Back-end Subscriber<MsgT>
42  * ------------------ -----------------
43  * | inbound frame |
44  * |--------------------------------------> |
45  * | | Serializer::deserialize
46  * | | (thread-local scratch)
47  * | |
48  * | | optional MessageLoop hop
49  * | |
50  * | v
51  * | user callback(const MsgT&)
52  * @endverbatim
53  *
54  * @par Supported Message Types
55  * | Category | Example C++ Type | @c Serializer::Type | Notes |
56  * | ----------------- | -------------------------------- | ------------------- | ------------------------------ |
57  * | Raw bytes | @c Bytes | @c kBytesType | Pass-through to callback. |
58  * | Protobuf value | @c MyProto | @c kProtoType | ParseFromArray path. |
59  * | Protobuf pointer | @c MyProto* | @c kProtoPtrType | Needs @c bind_proto_arena. |
60  * | FlatBuffers obj | @c MyTableT (NativeTable) | @c kFlatTableType | Object API. |
61  * | FlatBuffers ptr | @c MyTable* | @c kFlatPtrType | Zero-copy view of buffer. |
62  * | DDS CDR | type with @c deserialize(Cdr&) | @c kCdrType | DDS fast path. |
63  * | POD struct | trivial standard-layout type | @c kStandardType | @c sizeof(T) byte copy. |
64  * | UTF-8 text | @c std::string | @c kStringType | Length-prefixed. |
65  * | Custom | type with @c operator>>/<< | @c kCustomType | User-supplied codec. |
66  *
67  * @par Basic Listen Example
68  * @code
69  * vlink::Subscriber<MyMsg> sub("dds://vehicle/speed");
70  * sub.listen([](const MyMsg& msg) { handle(msg); });
71  * @endcode
72  *
73  * @par Callback Dispatch via MessageLoop
74  * @code
75  * vlink::MessageLoop loop;
76  * vlink::Subscriber<MyMsg> sub("dds://vehicle/speed", vlink::InitType::kWithoutInit);
77  * sub.attach(&loop);
78  * sub.init();
79  * sub.listen([](const MyMsg& m) { handle(m); });
80  * loop.run();
81  * @endcode
82  *
83  * @par Zero-copy Intra Transport
84  * When @c MsgT is a shared pointer whose @c element_type derives from
85  * @c IntraDataType (generated via @c VLINK_INTRA_DATA_DECLARE) and the URL
86  * scheme is @c intra://, the shared pointer is forwarded zero-copy to the
87  * callback without serialisation:
88  * @code
89  * VLINK_INTRA_DATA_DECLARE(MyProtoMsg, MyIntra)
90  * vlink::Subscriber<MyIntra> sub("intra://my_topic");
91  * sub.listen([](const MyIntra& data) { use(data); });
92  * @endcode
93  *
94  * @par Latency and Sample-loss Tracking
95  * @code
96  * vlink::Subscriber<MyMsg> sub("dds://my_topic");
97  * sub.set_latency_and_lost_enabled(true);
98  * auto latency_ns = sub.get_latency();
99  * auto stats = sub.get_lost();
100  * @endcode
101  *
102  * @warning The deserialised callback argument is backed by @c thread_local
103  * scratch storage and is overwritten on the next delivery. Copy
104  * the value before storing it outside the callback scope. This
105  * warning does not apply to @c Bytes or @c IntraData arguments,
106  * which are owned by value or by shared pointer.
107  *
108  * @note Calling @c listen() more than once is a fatal error. The subscriber
109  * must be initialised (either by @c InitType::kWithInit or by explicit
110  * @c init()) before @c listen() is called.
111  *
112  * @see publisher.h, node.h, serializer.h, base/message_loop.h
113  */
114 
115 #pragma once
116 
117 #include <memory>
118 #include <string>
119 #include <type_traits>
120 
121 #include "./base/functional.h"
122 #include "./impl/subscriber_impl.h"
123 #include "./node.h"
124 
125 namespace vlink {
126 
127 /**
128  * @class Subscriber
129  * @brief Type-safe topic listener for the VLink event communication model.
130  *
131  * @details
132  * Inherits the full @c Node API and adds receive-specific operations:
133  * @c listen() to register the user callback, manual-unloan control for
134  * zero-copy back-ends, and latency / sample-loss tracking. The transport
135  * implementation (@c SubscriberImpl) is selected by the URL scheme or by
136  * the typed configuration object supplied at construction time.
137  *
138  * @tparam MsgT C++ message type. Must satisfy @c Serializer::is_supported().
139  * @tparam SecT Security mode; defaults to @c SecurityType::kWithoutSecurity.
140  */
141 template <typename MsgT, SecurityType SecT = SecurityType::kWithoutSecurity>
142 class Subscriber : public Node<SubscriberImpl, SecT> {
143  public:
144  using UniquePtr = std::unique_ptr<Subscriber<MsgT, SecT>>; ///< Owning unique-pointer alias.
145  using SharedPtr = std::shared_ptr<Subscriber<MsgT, SecT>>; ///< Owning shared-pointer alias.
146  using MsgCallback = Function<void(const MsgT&)>; ///< User callback signature for received messages.
147 
148  static constexpr ImplType kImplType = kSubscriber; ///< Node role tag (@c kSubscriber).
149  static constexpr Serializer::Type kMsgType = Serializer::get_type_of<MsgT>(); ///< Codec resolved from @c MsgT.
150 
151  static_assert(Serializer::is_supported(kMsgType), "<MsgT> is not a supported Serializer type.");
152 
153  /**
154  * @brief Heap-allocates a @c Subscriber and wraps it in a @c std::unique_ptr.
155  *
156  * @param url_str Topic URL string.
157  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
158  * @return Owning @c UniquePtr to the new subscriber.
159  */
160  [[nodiscard]] static UniquePtr create_unique(const std::string& url_str, InitType type = InitType::kWithInit);
161 
162  /**
163  * @brief Heap-allocates a @c Subscriber and wraps it in a @c std::shared_ptr.
164  *
165  * @param url_str Topic URL string.
166  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
167  * @return Owning @c SharedPtr to the new subscriber.
168  */
169  [[nodiscard]] static SharedPtr create_shared(const std::string& url_str, InitType type = InitType::kWithInit);
170 
171  /**
172  * @brief Constructs a subscriber from a typed transport configuration object.
173  *
174  * @details
175  * Accepts any @c Conf-derived configuration. A compile-time check enforces
176  * that the configuration permits the subscriber role.
177  *
178  * @tparam ConfT Concrete configuration type derived from @c Conf.
179  * @param conf Populated configuration aggregate.
180  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
181  */
182  // NOLINTNEXTLINE(modernize-use-constraints)
183  template <typename ConfT, typename = std::enable_if_t<std::is_base_of_v<Conf, ConfT>>>
184  explicit Subscriber(const ConfT& conf, InitType type = InitType::kWithInit);
185 
186  /**
187  * @brief Constructs a subscriber from a URL string.
188  *
189  * @param url_str Topic URL such as @c "shm://vehicle/speed".
190  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
191  */
192  explicit Subscriber(const std::string& url_str, InitType type = InitType::kWithInit);
193 
194  /**
195  * @brief Installs the receive callback that runs for every inbound message.
196  *
197  * @details
198  * The callback is invoked on the transport delivery thread by default; if
199  * the subscriber is @c attach()ed to a @c MessageLoop the callback is
200  * posted onto that loop instead. For @c intra:// transports carrying an
201  * @c IntraDataType shared pointer the value is forwarded zero-copy and
202  * no deserialisation occurs.
203  *
204  * @warning The argument reference points at @c thread_local scratch
205  * memory that is reused across invocations. Copy the data before
206  * stashing it outside the callback. This restriction does not
207  * apply to @c Bytes or @c IntraData payloads.
208  *
209  * @note Calling @c listen() more than once is fatal. The subscriber must
210  * be initialised before the first call to @c listen().
211  *
212  * @param callback @c void(const MsgT&) invoked for each received message.
213  * @return @c true if the callback was installed successfully.
214  */
215  bool listen(MsgCallback&& callback);
216 
217  /**
218  * @brief Enables or disables manual-unloan mode for zero-copy receives.
219  *
220  * @details
221  * In manual-unloan mode the user must call @c return_loan() after consuming
222  * each delivered buffer. Only relevant on loan-capable transports
223  * (@c shm://, Zenoh with SHM, etc.).
224  *
225  * @param manual_unloan @c true to enable manual return; @c false for auto-return.
226  */
227  void set_manual_unloan(bool manual_unloan) override;
228 
229  /**
230  * @brief Toggles per-message latency and sample-loss measurement.
231  *
232  * @param enable @c true to begin tracking; @c false to stop.
233  */
234  void set_latency_and_lost_enabled(bool enable);
235 
236  /**
237  * @brief Reports whether latency and sample-loss tracking is currently active.
238  *
239  * @return @c true if @c set_latency_and_lost_enabled(true) was invoked.
240  */
241  [[nodiscard]] bool is_latency_and_lost_enabled() const;
242 
243  /**
244  * @brief Returns the most recent end-to-end latency measurement.
245  *
246  * @details
247  * Computed as receive-timestamp minus source-timestamp on the last
248  * delivered message. Returns @c 0 when tracking is not enabled.
249  *
250  * @return Latency in nanoseconds; @c 0 if disabled.
251  */
252  [[nodiscard]] int64_t get_latency() const;
253 
254  /**
255  * @brief Returns cumulative sample-delivery statistics.
256  *
257  * @return @c SampleLostInfo with total expected and total lost counts.
258  */
259  [[nodiscard]] SampleLostInfo get_lost() const;
260 
261  /**
262  * @brief Promotes this subscriber to behave as a @c Getter (field-reader) at the transport layer.
263  *
264  * @details
265  * Switches @c impl_type from @c kSubscriber to @c kGetter so that
266  * latest-value delivery semantics are activated. Reinitialises the
267  * transport extension if called post-@c init(). Used internally by
268  * @c Getter.
269  */
270  void mark_as_getter();
271 
272  private:
273  bool listen_bytes(NodeImpl::MsgCallback&& callback);
274 
275  bool listen_intra(MsgCallback&& callback);
276 };
277 
278 /**
279  * @class SecuritySubscriber
280  * @brief Convenience alias of @c Subscriber with per-message decryption enabled.
281  *
282  * @details
283  * Equivalent to @c Subscriber<MsgT, SecurityType::kWithSecurity>. Every
284  * inbound payload is decrypted with the configured @c Security::Config
285  * before the codec dispatcher is invoked.
286  *
287  * @note Security is not supported on @c intra:// or on @c dds:// CDR
288  * payloads.
289  *
290  * @tparam MsgT C++ message type to subscribe.
291  */
292 template <typename MsgT>
293 class SecuritySubscriber : public Subscriber<MsgT, SecurityType::kWithSecurity> {
294  public:
295  using UniquePtr = std::unique_ptr<SecuritySubscriber<MsgT>>; ///< Owning unique-pointer alias.
296  using SharedPtr = std::shared_ptr<SecuritySubscriber<MsgT>>; ///< Owning shared-pointer alias.
297 
298  /**
299  * @brief Heap-allocates a @c SecuritySubscriber and wraps it in a @c std::unique_ptr.
300  *
301  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
302  * @param url_str Topic URL string.
303  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
304  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
305  * @return Owning @c UniquePtr to the new secure subscriber.
306  */
307  // NOLINTNEXTLINE(modernize-use-constraints)
308  template <typename SecurityConfigT = Security::Config>
309  [[nodiscard]] static UniquePtr create_unique(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
311 
312  /**
313  * @brief Heap-allocates a @c SecuritySubscriber and wraps it in a @c std::shared_ptr.
314  *
315  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
316  * @param url_str Topic URL string.
317  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
318  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
319  * @return Owning @c SharedPtr to the new secure subscriber.
320  */
321  // NOLINTNEXTLINE(modernize-use-constraints)
322  template <typename SecurityConfigT = Security::Config>
323  [[nodiscard]] static SharedPtr create_shared(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
325 
326  /**
327  * @brief Constructs a @c SecuritySubscriber from a typed configuration object.
328  *
329  * @tparam ConfT Configuration type derived from @c Conf.
330  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
331  * @param conf Populated configuration aggregate.
332  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
333  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
334  */
335  // NOLINTNEXTLINE(modernize-use-constraints)
336  template <typename ConfT, typename SecurityConfigT = Security::Config,
337  typename = std::enable_if_t<std::is_base_of_v<Conf, ConfT>>>
338  explicit SecuritySubscriber(const ConfT& conf, SecurityConfigT&& sec_cfg = {}, InitType type = InitType::kWithInit);
339 
340  /**
341  * @brief Constructs a @c SecuritySubscriber from a URL string and installs the security configuration.
342  *
343  * @details
344  * Builds the base @c Subscriber in @c kWithoutInit mode, installs @p sec_cfg
345  * via @c enable_security(), then calls @c init() unless deferred. When
346  * @c enable_security() fails to produce a usable @c NodeImpl::security the
347  * subsequent @c init() will fail.
348  *
349  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
350  * @param url_str Topic URL string.
351  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
352  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
353  */
354  // NOLINTNEXTLINE(modernize-use-constraints)
355  template <typename SecurityConfigT = Security::Config>
356  explicit SecuritySubscriber(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
358 };
359 
360 } // namespace vlink
361 
Pool-backed type-erased callables: copyable vlink::Function and move-only vlink::MoveFunction.
Common CRTP base for every VLink communication primitive.
Transport-neutral base class for every event-model subscriber implementation.