VLink  2.0.0
A high-performance communication middleware
dynamic_data.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 dynamic_data.h
26  * @brief Self-describing serialised payload tagged with an inline type name.
27  *
28  * @details
29  * @c DynamicData carries a serialised VLink-compatible message together with a short
30  * type-name tag inside a single @c Bytes buffer. The first @c kOffset = 20 bytes of the
31  * buffer hold the type name (NUL-padded), and the remainder holds the payload as produced
32  * by @c Serializer. This layout lets transports route, log and inspect a payload without
33  * knowing its concrete C++ type at compile time -- the recipient picks the matching
34  * deserialiser at run time using the embedded tag.
35  *
36  * Supported dynamic categories (chosen automatically by @c Serializer):
37  *
38  * | Category | Examples | Notes |
39  * | ---------- | --------------------------------- | ------------------------------------------ |
40  * | Protobuf | @c MyProto, @c std::shared_ptr<P> | Encoded with the message's wire format |
41  * | FlatBuffer | @c MyTable, @c FlatPtr<T> | Encoded via @c FlatBuilder |
42  * | POD/Std | @c int, @c struct Foo (trivial) | Standard layout copy |
43  * | Bytes | @c vlink::Bytes | Stored verbatim |
44  * | String | @c std::string | Length-prefixed |
45  * | Chars | @c char[N], @c const char* | NUL-terminated |
46  * | Custom | Types with explicit @c Serializer | User-provided serialise/deserialise hooks |
47  *
48  * Two categories are forbidden and rejected by @c static_assert: nested @c DynamicData
49  * (avoids recursive type-name layout) and CDR types (their wire format does not allow
50  * the in-place prefix used here). The type-name literal (including its trailing NUL)
51  * must fit in fewer than @c kOffset bytes.
52  *
53  * Buffer layout:
54  *
55  * @verbatim
56  * +----------------------------+-----------------------------------------+
57  * | type name (20 bytes max) | serialised payload |
58  * +----------------------------+-----------------------------------------+
59  * ^ ^
60  * data_.real_data() data_.real_data() + kOffset
61  * @endverbatim
62  *
63  * @par Example
64  * @code
65  * vlink::DynamicData dd;
66  * MyProtoMsg msg;
67  * msg.set_value(42);
68  * dd.load("MyProtoMsg", msg); // serialise + tag
69  *
70  * // Send over any transport that handles vlink::Bytes:
71  * vlink::Bytes wire;
72  * dd >> wire;
73  *
74  * vlink::DynamicData received;
75  * received << wire; // recover the tagged blob
76  * if (received.get_type() == "MyProtoMsg") {
77  * MyProtoMsg out = received.as<MyProtoMsg>(); // deserialise
78  * (void)out;
79  * }
80  * @endcode
81  */
82 
83 #pragma once
84 
85 #include <memory>
86 #include <string_view>
87 #include <utility>
88 
89 #include "../serializer.h"
90 
91 namespace vlink {
92 
93 /**
94  * @class DynamicData
95  * @brief Type-erased wrapper that pairs an inline type name with a serialised payload.
96  *
97  * @details
98  * Stores its payload in an internal @c Bytes buffer; the first @c kOffset bytes are
99  * reserved for the type name and exposed through @c get_type() as a @c string_view.
100  * Copy and move operations rebind the view to the local buffer to avoid dangling.
101  */
103  public:
104  /**
105  * @brief Builds an empty container with no buffer and no type tag.
106  */
108 
109  /**
110  * @brief Copies the buffer and rebinds the local type view to point into the new buffer.
111  */
112  DynamicData(const DynamicData& target);
113 
114  /**
115  * @brief Adopts the source buffer and rebinds the type view to point at this object's storage.
116  *
117  * @details
118  * The moved-from object is left in an empty state with no payload and no type tag.
119  */
120  DynamicData(DynamicData&& target) noexcept;
121 
122  /**
123  * @brief Copy-assignment that rebinds the local type view to point into the new buffer.
124  */
126 
127  /**
128  * @brief Move-assignment that rebinds the local type view and empties the source.
129  */
130  DynamicData& operator=(DynamicData&& target) noexcept;
131 
132  /**
133  * @brief Serialises @p t under the type tag @p type into the internal buffer.
134  *
135  * @details
136  * The type literal (including its trailing NUL) must be shorter than @c kOffset, which
137  * is checked at compile time. On serialiser failure the container is reset to empty
138  * and an error is logged.
139  *
140  * @tparam SizeT Length of the type-name string literal including the NUL terminator.
141  * @tparam T Source message type (must be supported by @c Serializer).
142  * @param type String literal containing the type-name tag.
143  * @param t Message instance to serialise.
144  * @return Reference to @c *this for chaining.
145  */
146  template <uint8_t SizeT, typename T>
147  DynamicData& load(const char (&type)[SizeT], const T& t);
148 
149  /**
150  * @brief Deserialises the stored payload into @p t.
151  *
152  * @details
153  * Fails when the container is empty or the @c Serializer cannot decode the buffer
154  * (for example because @p T is incompatible with the embedded payload).
155  *
156  * @tparam T Destination type.
157  * @param t Output reference populated on success.
158  * @return @c true on success; @c false otherwise.
159  */
160  template <typename T>
161  bool convert(T& t) const;
162 
163  /**
164  * @brief Default-constructs a @p T (or @c shared_ptr<T>) and decodes the payload into it.
165  *
166  * @tparam T Destination type; specialised for @c std::shared_ptr to allocate the inner object.
167  * @return Decoded instance, or a default-constructed @p T when decoding fails.
168  */
169  template <typename T>
170  [[nodiscard]] T as() const;
171 
172  /**
173  * @brief Returns the underlying @c Bytes buffer, including the embedded type name.
174  */
175  [[nodiscard]] const Bytes& get_data() const;
176 
177  /**
178  * @brief Returns the embedded type name as a view into the internal buffer.
179  *
180  * @details
181  * Only valid for the lifetime of this @c DynamicData instance.
182  */
183  [[nodiscard]] const std::string_view& get_type() const;
184 
185  /**
186  * @brief Returns whether the internal buffer is empty.
187  */
188  [[nodiscard]] bool is_empty() const;
189 
190  /**
191  * @brief Byte-equality comparison.
192  *
193  * @return @c true when both buffers hold identical bytes.
194  */
195  [[nodiscard]] bool operator==(const DynamicData& target) const;
196 
197  /**
198  * @brief Byte-inequality comparison.
199  */
200  [[nodiscard]] bool operator!=(const DynamicData& target) const;
201 
202  /**
203  * @brief Compile-time trait used by @c Serializer to detect @c DynamicData specialisations.
204  *
205  * @return Always @c true.
206  */
207  [[nodiscard]] static constexpr bool is_vlink_dynamic_data();
208 
209  /**
210  * @brief Returns the byte offset at which the serialised payload begins (= @c kOffset).
211  */
212  [[nodiscard]] static constexpr uint8_t get_offset();
213 
214  /**
215  * @brief Reconstructs the container from a wire-format @c Bytes blob.
216  *
217  * @param bytes Wire blob previously produced by @c operator>>.
218  * @return @c true on success.
219  */
220  bool operator<<(const Bytes& bytes) noexcept;
221 
222  /**
223  * @brief Emits the container as a wire-format @c Bytes blob.
224  *
225  * @param bytes Destination buffer.
226  * @return @c true on success; @c false when the internal buffer is empty, non-owning,
227  * or does not carry the reserved prefix.
228  */
229  bool operator>>(Bytes& bytes) const noexcept;
230 
231  private:
232  void refresh_type_view(size_t type_size) noexcept;
233 
234  static constexpr uint8_t kOffset{20};
235  Bytes data_;
236  std::string_view type_;
237 };
238 
239 ////////////////////////////////////////////////////////////////
240 /// Details
241 ////////////////////////////////////////////////////////////////
242 
243 template <uint8_t SizeT, typename T>
244 inline DynamicData& DynamicData::load(const char (&type)[SizeT], const T& t) {
245  static_assert(SizeT < get_offset(), "DynamicData name out of size.");
246  static_assert(!std::is_same_v<DynamicData, T>, "DynamicData can not serialize self.");
247  static_assert(!Serializer::is_cdr_type<T>(), "DynamicData can not serialize cdr data.");
248 
249  Bytes next_data;
250  bool ret = Serializer::serialize<Serializer::get_type_of<T>()>(t, next_data, TransportType::kUnknown, get_offset());
251 
252  if VUNLIKELY (!ret || next_data.real_data() == nullptr) {
253  data_ = Bytes();
254  type_ = std::string_view();
255  VLOG_F("DynamicData serialize failed.");
256  return *this;
257  }
258 
259  data_ = std::move(next_data);
260 
261  if constexpr (SizeT > 0) {
262  std::memcpy(data_.real_data(), type, SizeT);
263 
264  if constexpr (SizeT < kOffset) {
265  std::memset(data_.real_data() + SizeT, 0, kOffset - SizeT);
266  }
267 
268  size_t name_len = SizeT;
269 
270  if (name_len > 0 && type[SizeT - 1] == '\0') {
271  name_len -= 1;
272  }
273 
274  type_ = std::string_view(reinterpret_cast<const char*>(data_.real_data()), name_len);
275  } else {
276  std::memset(data_.real_data(), 0, kOffset);
277  type_ = std::string_view();
278  }
279 
280  return *this;
281 }
282 
283 template <typename T>
284 inline bool DynamicData::convert(T& t) const {
285  static_assert(!std::is_same_v<DynamicData, T>, "DynamicData can not deserialize self.");
286  static_assert(!Serializer::is_cdr_type<T>(), "DynamicData can not deserialize cdr data.");
287 
288  if VUNLIKELY (data_.empty()) {
289  return false;
290  }
291 
292  return Serializer::deserialize<Serializer::get_type_of<T>()>(data_, t, TransportType::kUnknown);
293 }
294 
295 template <typename T>
296 inline T DynamicData::as() const {
297  T t;
298 
299  if constexpr (Traits::IsSharedPtr<T>()) {
300  t = std::make_shared<typename T::element_type>();
301  }
302 
303  if VUNLIKELY (!convert(t)) {
304  VLOG_F("DynamicData deserialize failed.");
305  return T{};
306  }
307 
308  return t;
309 }
310 
311 inline constexpr bool DynamicData::is_vlink_dynamic_data() { return true; }
312 
313 inline constexpr uint8_t DynamicData::get_offset() { return kOffset; }
314 
315 } // namespace vlink
#define VLOG_F(...)
Definition: logger.h:791
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLINK_EXPORT
Definition: macros.h:81