VLink  2.0.0
A high-performance communication middleware
proxy_data.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_data.h
26  * @brief Routing envelope used by the VLink proxy / monitoring path.
27  *
28  * @details
29  * @c ProxyData bundles a serialised user payload with the metadata that the
30  * VLink proxy subsystem needs to forward the message across hosts: topic URL,
31  * serialisation type label, source hostname, plus control fields (control id,
32  * mode, microsecond timestamp, sequence number, coarse schema family). All
33  * variable-length regions share a single owned allocation so a freshly
34  * deserialised envelope can expose each region as a @c string_view or
35  * shallow @c Bytes without further allocation.
36  *
37  * | Region | Meaning |
38  * | --------- | ---------------------------------------------------- |
39  * | raw | Serialised user message bytes |
40  * | url | Topic URL (e.g. @c "dds://my/topic") |
41  * | ser | Serialisation type label (e.g. @c "proto.MyMsg") |
42  * | hostname | Source machine identifier (optional) |
43  *
44  * @par Wire format
45  * @c ProxyData is POD; @c memcpy serialises the struct. The @c sizeof value
46  * is locked by @c static_assert and forms a permanent contract: the
47  * @c vlink::zerocopy::* containers have NO forward or backward binary
48  * compatibility across releases. Pointer / ownership fields inside the wire
49  * snapshot must NEVER be interpreted by remote consumers.
50  * @code
51  * static_assert(sizeof(ProxyData) == 80, "Sizeof must be 80 bytes.");
52  * @endcode
53  *
54  * @par Memory layout
55  * @code
56  * Offset Size Field
57  * ------ ---- ---------------------------------
58  * 0 8 uint8_t* data_
59  * 8 8 size_t size_
60  * 16 4 uint32_t control_id_
61  * 20 4 uint32_t mode_
62  * 24 8 int64_t timestamp_
63  * 32 8 int64_t seq_
64  * 40 4 uint32_t data_pos_
65  * 44 4 uint32_t data_size_
66  * 48 4 uint32_t url_pos_
67  * 52 4 uint32_t url_size_
68  * 56 4 uint32_t ser_pos_
69  * 60 4 uint32_t ser_size_
70  * 64 4 uint32_t hostname_pos_
71  * 68 4 uint32_t hostname_size_
72  * 72 4 uint32_t schema_
73  * 76 1 bool is_owner_
74  * 77 1 uint8_t reserved_buf_
75  * 78 2 uint16_t reserved_buf2_
76  * ------ ---- ---------------------------------
77  * Total 80 bytes (alignas 8)
78  *
79  * Wire envelope:
80  * [ magic_begin (4) | version (4) | ProxyData struct (80) | raw | url | ser | hostname | magic_end (4) ]
81  * @endcode
82  *
83  * @par Reserved bytes
84  * The 3 bytes that follow @c is_owner_ are exposed as two explicit wire-locked
85  * fields: @c uint8_t @c reserved_buf_ (offset 77) and @c uint16_t @c reserved_buf2_
86  * (offset 78), reachable through @c get_reserved() / @c get_reserved2(). They
87  * travel through the wire format and are carried by the copy / move helpers, but
88  * MUST NOT be repurposed by application code: future library revisions may bind
89  * them to real fields.
90  *
91  * @par Example
92  * @code
93  * vlink::zerocopy::ProxyData envelope;
94  * envelope.set_control_id(42);
95  * envelope.set_timestamp(vlink::time_us());
96  * envelope.create(payload, "dds://lidar/top", "proto.PointCloud",
97  * static_cast<uint32_t>(SchemaType::kProtobuf), "host-a");
98  *
99  * vlink::Bytes wire;
100  * envelope >> wire;
101  *
102  * vlink::zerocopy::ProxyData rx;
103  * rx << wire;
104  * auto url = rx.url();
105  * @endcode
106  */
107 
108 #pragma once
109 
110 #include <cstdint>
111 #include <string_view>
112 
113 #include "../base/bytes.h"
114 
115 namespace vlink {
116 
117 namespace zerocopy {
118 
119 /**
120  * @struct ProxyData
121  * @brief 80-byte POD envelope packing payload, URL, serialisation type and host string.
122  *
123  * @details
124  * Used internally by the VLink proxy / monitoring layer. All four variable
125  * regions share one allocation laid out as @c [raw | url | ser | hostname]
126  * so deserialisation is zero-copy and string accessors point straight into
127  * the source wire buffer.
128  */
129 struct VLINK_EXPORT_AND_ALIGNED(8) ProxyData final {
130  /**
131  * @brief Default-constructs an empty envelope and asserts the 80-byte contract.
132  */
133  ProxyData() noexcept;
134 
135  /**
136  * @brief Frees the owned tail buffer when @c is_owner() is @c true.
137  */
138  ~ProxyData() noexcept;
139 
140  /**
141  * @brief Deep-copies @p target into a freshly allocated envelope.
142  *
143  * @param target Source envelope to clone.
144  */
145  ProxyData(const ProxyData& target) noexcept;
146 
147  /**
148  * @brief Steals @p target's allocation and metadata; @p target ends empty.
149  *
150  * @param target Source envelope moved from.
151  */
152  ProxyData(ProxyData&& target) noexcept;
153 
154  /**
155  * @brief Deep-copy-assigns @p target; self-assignment is a no-op.
156  *
157  * @param target Source envelope to clone.
158  * @return Reference to @c *this.
159  */
160  ProxyData& operator=(const ProxyData& target) noexcept;
161 
162  /**
163  * @brief Move-assigns @p target; self-assignment is a no-op.
164  *
165  * @param target Source envelope moved from.
166  * @return Reference to @c *this.
167  */
168  ProxyData& operator=(ProxyData&& target) noexcept;
169 
170  /**
171  * @brief Deserialises an envelope from @p bytes with zero-copy borrowing semantics.
172  *
173  * @param bytes Wire buffer previously produced by @c operator>>.
174  * @return @c true on success; @c false on magic mismatch or region inconsistency.
175  */
176  bool operator<<(const Bytes& bytes) noexcept;
177 
178  /**
179  * @brief Serialises the struct snapshot plus tail buffer into @p bytes.
180  *
181  * @param bytes Output buffer; reallocated automatically when undersized.
182  * @return Always @c true.
183  */
184  bool operator>>(Bytes& bytes) const noexcept;
185 
186  /**
187  * @brief Proxy control identifier set by @c set_control_id().
188  *
189  * @return Stored control id.
190  */
191  [[nodiscard]] uint32_t control_id() const noexcept;
192 
193  /**
194  * @brief Proxy operation mode set by @c set_mode().
195  *
196  * @return Stored mode value.
197  */
198  [[nodiscard]] uint32_t mode() const noexcept;
199 
200  /**
201  * @brief Microsecond-resolution timestamp set by @c set_timestamp().
202  *
203  * @return Timestamp.
204  */
205  [[nodiscard]] int64_t timestamp() const noexcept;
206 
207  /**
208  * @brief Sequence number set by @c set_seq().
209  *
210  * @return Sequence number.
211  */
212  [[nodiscard]] int64_t seq() const noexcept;
213 
214  /**
215  * @brief Coarse schema family tag set by @c set_schema().
216  *
217  * @return Numeric @c SchemaType value.
218  */
219  [[nodiscard]] uint32_t schema() const noexcept;
220 
221  /**
222  * @brief Shallow @c Bytes view of the raw payload region (no copy).
223  *
224  * @return @c Bytes pointing into the internal tail buffer; lifetime tracks @c *this.
225  */
226  [[nodiscard]] Bytes raw() const noexcept;
227 
228  /**
229  * @brief View of the URL region within the internal tail buffer.
230  *
231  * @return Non-owning view, or empty when no URL was provided.
232  */
233  [[nodiscard]] std::string_view url() const noexcept;
234 
235  /**
236  * @brief View of the serialisation-type region within the internal tail buffer.
237  *
238  * @return Non-owning view, or empty when no serialisation label was provided.
239  */
240  [[nodiscard]] std::string_view ser() const noexcept;
241 
242  /**
243  * @brief View of the optional source hostname region.
244  *
245  * @return Non-owning view, or empty when no hostname was provided.
246  */
247  [[nodiscard]] std::string_view hostname() const noexcept;
248 
249  /**
250  * @brief Stores the proxy control identifier.
251  *
252  * @param control_id Identifier value.
253  */
254  void set_control_id(uint32_t control_id) noexcept;
255 
256  /**
257  * @brief Stores the proxy operation mode.
258  *
259  * @param mode Mode value.
260  */
261  void set_mode(uint32_t mode) noexcept;
262 
263  /**
264  * @brief Stores the microsecond-resolution timestamp.
265  *
266  * @param timestamp Timestamp value.
267  */
268  void set_timestamp(int64_t timestamp) noexcept;
269 
270  /**
271  * @brief Stores the message sequence number.
272  *
273  * @param seq Sequence number.
274  */
275  void set_seq(int64_t seq) noexcept;
276 
277  /**
278  * @brief Stores the coarse schema family tag.
279  *
280  * @param schema Numeric @c SchemaType value.
281  */
282  void set_schema(uint32_t schema) noexcept;
283 
284  /**
285  * @brief Validates that @p bytes carries a well-formed @c ProxyData envelope.
286  *
287  * @param bytes Wire buffer to inspect.
288  * @return @c true on magic match and minimum-size check.
289  */
290  [[nodiscard]] static bool check_valid(const Bytes& bytes) noexcept;
291 
292  /**
293  * @brief Total bytes that @c operator>> writes for this envelope.
294  *
295  * @return @c sizeof(magic_begin) + @c sizeof(version) + @c sizeof(ProxyData) + @c size() + @c sizeof(magic_end).
296  */
297  [[nodiscard]] size_t get_serialized_size() const noexcept;
298 
299  /**
300  * @brief Validates that internal region positions and sizes are consistent.
301  *
302  * @details
303  * Confirms the four sub-regions are contiguous, sum to @c size(), and the
304  * data pointer is non-null. Useful after @c operator<< when forwarding
305  * untrusted input.
306  *
307  * @return @c true when the layout is internally consistent.
308  */
309  [[nodiscard]] bool is_valid() const noexcept;
310 
311  /**
312  * @brief Borrows @p target's tail buffer without copying.
313  *
314  * @param target Source envelope; its backing buffer must outlive @c *this.
315  * @return @c false on self-borrow, otherwise @c true.
316  */
317  bool shallow_copy(const ProxyData& target) noexcept;
318 
319  /**
320  * @brief Allocates (or reuses) an owned buffer and clones @p target's payload.
321  *
322  * @param target Source envelope to clone.
323  * @return @c false on self-copy, otherwise @c true.
324  */
325  bool deep_copy(const ProxyData& target) noexcept;
326 
327  /**
328  * @brief Transfers ownership from @p target; @p target ends empty.
329  *
330  * @param target Source envelope moved from.
331  * @return @c false on self-move, otherwise @c true.
332  */
333  bool move_copy(ProxyData& target) noexcept;
334 
335  /**
336  * @brief Builds the envelope by packing all four regions into a single allocation.
337  *
338  * @details
339  * Allocates @c raw.size() + @c url.size() + @c ser.size() + @c hostname.size()
340  * bytes and copies each region in order. If any region (or the total) would
341  * exceed @c UINT32_MAX the envelope is cleared. Callers passing dynamic
342  * input must verify success via @c is_valid() or @c size().
343  *
344  * @param raw Raw payload bytes.
345  * @param url Topic URL string.
346  * @param ser Serialisation type label.
347  * @param schema Optional coarse schema family tag.
348  * @param hostname Optional source hostname.
349  */
350  void create(const Bytes& raw, std::string_view url, std::string_view ser, uint32_t schema = 0,
351  std::string_view hostname = {}) noexcept;
352 
353  /**
354  * @brief Frees the owned buffer (if any) and zeroes every field.
355  */
356  void clear() noexcept;
357 
358  /**
359  * @brief Total size of the variable-length tail buffer.
360  *
361  * @return Byte count, or 0 when empty.
362  */
363  [[nodiscard]] size_t size() const noexcept;
364 
365  /**
366  * @brief Whether this envelope currently owns its tail buffer.
367  *
368  * @return @c true when the destructor would free the buffer.
369  */
370  [[nodiscard]] bool is_owner() const noexcept;
371 
372  /**
373  * @brief Mutable accessor for the first wire-locked reserved field (@c uint8_t).
374  *
375  * @return Reference to @c reserved_buf_.
376  */
377  [[nodiscard]] uint8_t& get_reserved() noexcept;
378 
379  /**
380  * @brief Mutable accessor for the second wire-locked reserved field (@c uint16_t).
381  *
382  * @return Reference to @c reserved_buf2_.
383  */
384  [[nodiscard]] uint16_t& get_reserved2() noexcept;
385 
386  static constexpr bool kZerocopyTypes{true}; ///< Marker probed by the VLink type-trait machinery.
387 
388  private:
389  uint8_t* data_{nullptr};
390  size_t size_{0};
391  uint32_t control_id_{0};
392  uint32_t mode_{0};
393  int64_t timestamp_{0};
394  int64_t seq_{0};
395  uint32_t data_pos_{0};
396  uint32_t data_size_{0};
397  uint32_t url_pos_{0};
398  uint32_t url_size_{0};
399  uint32_t ser_pos_{0};
400  uint32_t ser_size_{0};
401  uint32_t hostname_pos_{0};
402  uint32_t hostname_size_{0};
403  uint32_t schema_{0};
404  bool is_owner_{false};
405  uint8_t reserved_buf_{0};
406  uint16_t reserved_buf2_{0};
407 
408  static constexpr uint32_t kMagicNumberBegin{0x98B7F12A};
409  static constexpr uint32_t kMagicNumberEnd{0x98B7F12F};
410 };
411 
412 } // namespace zerocopy
413 
414 } // namespace vlink
80-byte POD envelope packing payload, URL, serialisation type and host string.