VLink  2.1.0
A high-performance communication middleware
message_parser.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 message_parser.h
26  * @brief Unified runtime parser for built-in VLink zero-copy wire messages.
27  *
28  * @details
29  * @c MessageParser centralises type detection, deserialization, indexed field
30  * access, collection bounds, and exact integer representation for all eight
31  * built-in VLink zero-copy wire types. Root paths use names such as
32  * @c header.time_meas and @c width. Indexed payloads use @c data[N].field;
33  * tensors additionally expose @c shape[N] and @c strides[N].
34  *
35  * Integer fields remain @c int64_t or @c uint64_t in @c MessageParser::Value.
36  * Conversion through @c numeric reports values outside the exact IEEE-754
37  * integer range. Deserialized containers borrow the input wire storage; the
38  * input must therefore outlive the parser and any returned @c Bytes view.
39  *
40  * @par Parsing and access flow
41  * @verbatim
42  * serialized type + wire Bytes
43  * |
44  * v
45  * MessageParser::detect_type
46  * |
47  * v
48  * MessageParser::parse ---- failure ----> invalid / empty state
49  * |
50  * +----> exact field: value(path)
51  * |
52  * +----> collection: value(name, index, field)
53  * @endverbatim
54  *
55  * @par Path and value mapping
56  * | Payload kind | Example path | @c Value alternative |
57  * | ------------------ | ---------------------------- | -------------------- |
58  * | Header / metadata | @c header.time_meas | @c uint64_t |
59  * | Point record | @c data[4].intensity | Schema-dependent |
60  * | Object record | @c objects[2].track_id | @c uint64_t |
61  * | Grid / tensor cell | @c data[7] | Dtype-dependent |
62  * | Tensor dimension | @c shape[1] / @c strides[1] | @c uint64_t |
63  * | Raw payload | @c data / @c raw | Borrowed @c Bytes |
64  *
65  * @par Collection mapping
66  * | Message type | Indexed collections | Element field form |
67  * | ------------------ | --------------------------------------- | ------------------------ |
68  * | @c RawData | None | — |
69  * | @c CameraFrame | None | — |
70  * | @c PointCloud | @c data / @c points | Dynamic point key |
71  * | @c ProxyData | None | — |
72  * | @c OccupancyGrid | @c data / @c cells | Empty or @c value |
73  * | @c Tensor | @c data / @c elements, shape, strides | Empty or @c value |
74  * | @c ObjectArray | @c data / @c objects | Object member or alias |
75  * | @c AudioFrame | @c data | Empty or @c value |
76  *
77  * @par Example -- parse and read an exact integer field
78  * @code
79  * vlink::zerocopy::MessageParser parser;
80  * if (!parser.parse("vlink::zerocopy::ObjectArray", wire)) {
81  * return;
82  * }
83  *
84  * vlink::zerocopy::MessageParser::Value value;
85  * if (parser.value("data[0].track_id", value)) {
86  * consume(std::get<uint64_t>(value));
87  * }
88  * @endcode
89  */
90 
91 #pragma once
92 
93 #include <array>
94 #include <cstddef>
95 #include <cstdint>
96 #include <string>
97 #include <string_view>
98 #include <variant>
99 #include <vector>
100 
101 #include "../base/bytes.h"
102 #include "../base/macros.h"
103 #include "./audio_frame.h"
104 #include "./camera_frame.h"
105 #include "./object_array.h"
106 #include "./occupancy_grid.h"
107 #include "./point_cloud.h"
108 #include "./proxy_data.h"
109 #include "./raw_data.h"
110 #include "./tensor.h"
111 
112 namespace vlink {
113 
114 namespace zerocopy {
115 
116 /**
117  * @class MessageParser
118  * @brief Unified parser and field reader for built-in zero-copy messages.
119  *
120  * @details
121  * A successful parse retains the decoded message and exposes schema-neutral
122  * scalar access through @c value(), @c numeric(), and @c text(). Indexed
123  * payloads use paths such as @c data[4].x, @c shape[1], and
124  * @c objects[2].track_id. Integer values remain exact in @c Value; callers
125  * converting them to double can request a precision loss report. Decoded
126  * containers and @c Bytes values borrow payload storage from the input. The
127  * input must outlive the parser and every returned byte view; scalar and
128  * string values are copied into @c Value.
129  */
131  public:
132  /**
133  * @enum Type
134  * @brief Built-in zero-copy wire type retained by the parser.
135  */
136  enum Type : uint8_t {
137  kUnknown = 0, ///< No message is parsed, or the serialized type is unsupported.
138  kRawData = 1, ///< Opaque bytes with a common zero-copy header.
139  kCameraFrame = 2, ///< Image or encoded video frame.
140  kPointCloud = 3, ///< Dynamically described packed point records.
141  kProxyData = 4, ///< Proxy routing envelope and nested raw payload.
142  kOccupancyGrid = 5, ///< Typed two-dimensional occupancy or cost cells.
143  kTensor = 6, ///< Typed multidimensional tensor payload.
144  kObjectArray = 7, ///< Detection or tracking object records.
145  kAudioFrame = 8, ///< PCM or encoded audio frame.
146  };
147 
148  /**
149  * @enum ValueType
150  * @brief Exact C++ alternative stored by @c Value for a field.
151  */
152  enum ValueType : uint8_t {
153  kValueUnknown = 0, ///< Field has no supported value representation.
154  kInt64 = 1, ///< Signed integral value retained as @c int64_t.
155  kUInt64 = 2, ///< Unsigned integral value retained as @c uint64_t.
156  kDouble = 3, ///< Floating-point value represented as @c double.
157  kString = 4, ///< UTF-8 or protocol text represented as @c std::string.
158  kBytes = 5, ///< Opaque payload returned as a borrowed @c Bytes view.
159  };
160 
161  /**
162  * @enum EnumKind
163  * @brief Built-in enumeration a scalar field maps to, for symbolic-name rendering.
164  *
165  * @details
166  * Structural reflection alone cannot recover an integer field's symbolic
167  * enumerator name. @c EnumKind names the concrete built-in enum a field
168  * encodes so a presentation layer can resolve its label without re-hardcoding
169  * per-type knowledge. @c kEnumNone marks a plain numeric field.
170  */
171  enum EnumKind : uint8_t {
172  kEnumNone = 0, ///< Plain numeric field with no symbolic enumeration.
173  kEnumCameraFormat = 1, ///< @c CameraFrame::Format image or codec format.
174  kEnumCameraStream = 2, ///< @c CameraFrame::Stream stream role.
175  kEnumGridCellType = 3, ///< @c OccupancyGrid::CellType cell storage type.
176  kEnumTensorDataType = 4, ///< @c Tensor::DataType element data type.
177  kEnumTensorDevice = 5, ///< @c Tensor::Device residency device.
178  kEnumAudioFormat = 6, ///< @c AudioFrame::Format sample format.
179  kEnumAudioLayout = 7, ///< @c AudioFrame::Layout channel layout.
180  };
181 
182  /**
183  * @struct Field
184  * @brief Schema-neutral field descriptor returned by field enumeration.
185  */
186  struct Field final {
187  std::string name; ///< Canonical path or collection element field name.
188  ValueType type{ValueType::kValueUnknown}; ///< Exact @c Value alternative returned for the field.
189  uint16_t native_type{0}; ///< Message-specific schema type tag, or zero when not applicable.
190  uint16_t storage_size{0}; ///< Encoded field width in bytes, or zero for variable-width fields.
191  EnumKind enum_kind{EnumKind::kEnumNone}; ///< Built-in enumeration the field encodes, for symbolic rendering.
192  bool is_time{false}; ///< Field is a nanosecond timestamp eligible for date rendering.
193  bool is_bool{false}; ///< Field encodes a boolean and should render as @c true / @c false.
194  bool is_reserved{false}; ///< Field is a reserved slot a presentation layer may hide.
195  size_t byte_offset{0}; ///< Byte offset within an indexed packed record, when applicable.
196  size_t element_index{static_cast<size_t>(-1)}; ///< Declaration index within an indexed record, when applicable.
197  };
198 
199  /**
200  * @typedef Value
201  * @brief Exact schema-neutral field value without implicit integer-to-double conversion.
202  */
203  using Value = std::variant<int64_t, uint64_t, double, std::string, Bytes>;
204 
205  /**
206  * @brief Constructs an invalid parser with no retained message.
207  */
208  MessageParser() = default;
209 
210  /**
211  * @brief Releases the retained typed message.
212  */
213  ~MessageParser() = default;
214 
215  /**
216  * @brief Parsers are non-copyable because decoded containers may borrow wire storage.
217  *
218  * @param target Parser whose borrowed state would otherwise be copied.
219  */
220  MessageParser(const MessageParser&) = delete;
222 
223  /**
224  * @brief Move-constructs or move-assigns the retained parser state.
225  *
226  * @param target Parser whose retained state is transferred.
227  */
228  MessageParser(MessageParser&&) noexcept = default;
229  MessageParser& operator=(MessageParser&&) noexcept = default;
230 
231  /**
232  * @brief Detects @p serialized_type and parses @p bytes; failure clears the parser.
233  *
234  * @param serialized_type Exact type name or namespace-delimited built-in type suffix.
235  * @param bytes Zero-copy wire envelope to decode; its storage must outlive this parser.
236  * @return @c true on successful type detection and deserialization.
237  */
238  bool parse(std::string_view serialized_type, const Bytes& bytes);
239 
240  /**
241  * @brief Parses @p bytes as the explicitly selected type; failure clears the parser.
242  *
243  * @param type Built-in zero-copy type expected in @p bytes.
244  * @param bytes Zero-copy wire envelope to decode; its storage must outlive this parser.
245  * @return @c true when the envelope is valid for @p type.
246  */
247  bool parse(Type type, const Bytes& bytes);
248 
249  /**
250  * @brief Releases the current decoded message and returns to the invalid state.
251  */
252  void clear() noexcept;
253 
254  /**
255  * @brief Returns the retained built-in type, or @c kUnknown when invalid.
256  */
257  [[nodiscard]] Type type() const noexcept;
258 
259  /**
260  * @brief Returns whether the parser currently retains a successfully decoded message.
261  */
262  [[nodiscard]] bool valid() const noexcept;
263 
264  /**
265  * @brief Reads a root or indexed field without losing its integer representation.
266  *
267  * @param path Root path or indexed path such as @c data[3].x.
268  * @param out Exact field value written only when the path resolves.
269  * @return @c true when @p path exists and is readable.
270  */
271  bool value(std::string_view path, Value& out) const;
272 
273  /**
274  * @brief Reads @p field from element @p index of a named collection.
275  *
276  * @param collection Canonical collection or supported alias.
277  * @param index Zero-based element index.
278  * @param field Element field name, or @c value for scalar collections.
279  * @param out Exact field value written on success.
280  * @return @c false for an unknown collection, field, or out-of-range index.
281  */
282  bool value(std::string_view collection, size_t index, std::string_view field, Value& out) const;
283 
284  /**
285  * @brief Reads a collection element through a descriptor returned by @c element_fields().
286  *
287  * @details This overload avoids repeated name resolution in packed-record hot paths. The
288  * descriptor is validated against the parser's current schema, so a stale descriptor from
289  * an incompatible subsequent parse is rejected.
290  */
291  bool value(std::string_view collection, size_t index, const Field& field, Value& out) const;
292 
293  /**
294  * @brief Reads a numeric path and optionally reports integer-to-double precision loss.
295  *
296  * @param path Root or indexed numeric field path.
297  * @param out Numeric value converted to @c double.
298  * @param precision_loss Optional flag set when an integer exceeds the exact IEEE-754 range.
299  * @return @c true when @p path resolves to a numeric value.
300  */
301  bool numeric(std::string_view path, double& out, bool* precision_loss = nullptr) const;
302 
303  /**
304  * @brief Reads a numeric collection element and optionally reports precision loss.
305  *
306  * @param collection Canonical collection or supported alias.
307  * @param index Zero-based element index.
308  * @param field Element field name, or @c value for scalar collections.
309  * @param out Numeric value converted to @c double.
310  * @param precision_loss Optional flag set when integer conversion is inexact.
311  * @return @c true when the selected element resolves to a numeric value.
312  */
313  bool numeric(std::string_view collection, size_t index, std::string_view field, double& out,
314  bool* precision_loss = nullptr) const;
315 
316  /**
317  * @brief Reads a numeric collection element through a pre-resolved field descriptor.
318  */
319  bool numeric(std::string_view collection, size_t index, const Field& field, double& out,
320  bool* precision_loss = nullptr) const;
321 
322  /**
323  * @brief Reads a string-valued path.
324  *
325  * @param path Root or indexed string field path.
326  * @param out String copied from the decoded field on success.
327  * @return @c true when @p path resolves to a string value.
328  */
329  bool text(std::string_view path, std::string& out) const;
330 
331  /**
332  * @brief Reads a string-valued collection element.
333  *
334  * @param collection Canonical collection or supported alias.
335  * @param index Zero-based element index.
336  * @param field String field name.
337  * @param out String copied from the decoded field on success.
338  * @return @c true when the selected element resolves to a string value.
339  */
340  bool text(std::string_view collection, size_t index, std::string_view field, std::string& out) const;
341 
342  /**
343  * @brief Returns the validated accessible element count of @p collection.
344  *
345  * @param collection Canonical collection or supported alias.
346  * @return Number of elements safe to read from the decoded payload.
347  */
348  [[nodiscard]] size_t collection_size(std::string_view collection) const noexcept;
349 
350  /**
351  * @brief Enumerates root fields available for the parsed type.
352  *
353  * @return Stable declaration-order descriptors; empty when the parser is invalid.
354  */
355  [[nodiscard]] std::vector<Field> fields() const;
356 
357  /**
358  * @brief Enumerates fields of one element in @p collection.
359  *
360  * @param collection Canonical collection or supported alias.
361  * @return Stable element field descriptors, or an empty vector for an unknown collection.
362  */
363  [[nodiscard]] std::vector<Field> element_fields(std::string_view collection) const;
364 
365  /**
366  * @brief Deep-copies the retained message into a matching typed container.
367  *
368  * @tparam T One of the built-in zero-copy container types.
369  * @param out Destination container replaced only when @p T matches the parsed type.
370  * @return @c true when a matching retained message was copied.
371  *
372  * @note This compatibility operation is intended for typed container APIs. Dynamic
373  * readers should use @c value(), @c fields(), and collection access instead.
374  */
375  template <typename T>
376  bool copy_to(T& out) const;
377 
378  /**
379  * @brief Detects a built-in type using an exact name or namespace-delimited suffix.
380  *
381  * @param serialized_type Serialized type name supplied by schema metadata.
382  * @return Detected built-in type, or @c kUnknown when no exact suffix matches.
383  */
384  static Type detect_type(std::string_view serialized_type) noexcept;
385 
386  /**
387  * @brief Returns the canonical unqualified serialized name for @p type.
388  *
389  * @param type Built-in parser type.
390  * @return Stable unqualified name, or an empty view for @c kUnknown.
391  */
392  static std::string_view type_name(Type type) noexcept;
393 
394  private:
395  using Message = std::variant<std::monostate, RawData, CameraFrame, PointCloud, ProxyData, OccupancyGrid, Tensor,
397 
398  template <typename T>
399  [[nodiscard]] const T* get() const noexcept;
400 
401  /**
402  * @brief Resolves a non-indexed field from the retained message.
403  */
404  bool root_value(std::string_view path, Value& out) const;
405 
406  /**
407  * @brief Resolves one field from a validated collection element.
408  */
409  bool element_value(std::string_view collection, size_t index, std::string_view field, Value& out) const;
410 
411  /**
412  * @brief Reads a PointCloud field whose schema descriptor has already been validated.
413  */
414  bool point_value(size_t index, const Field& field, Value& out) const;
415 
416  Type type_{Type::kUnknown};
417  Message message_;
418  std::array<uint64_t, 5> reserved_{};
419  std::vector<Field> point_fields_;
420  std::vector<size_t> point_field_buckets_;
421  std::vector<size_t> point_field_next_;
422 };
423 
424 ////////////////////////////////////////////////////////////////
425 /// Details
426 ////////////////////////////////////////////////////////////////
427 
428 inline MessageParser::Type MessageParser::type() const noexcept { return type_; }
429 
430 inline bool MessageParser::valid() const noexcept { return type_ != Type::kUnknown; }
431 
432 template <typename T>
433 inline const T* MessageParser::get() const noexcept {
434  return std::get_if<T>(&message_);
435 }
436 
437 template <typename T>
438 inline bool MessageParser::copy_to(T& out) const {
439  const auto* message = get<T>();
440 
441  if VUNLIKELY (message == nullptr) {
442  return false;
443  }
444 
445  out = *message;
446  return true;
447 }
448 
449 /**
450  * @struct MessageFormatOptions
451  * @brief Presentation toggles for @c format_message.
452  */
453 struct MessageFormatOptions final {
454  bool hex{false}; ///< Render integer fields as hexadecimal instead of decimal.
455  bool date{false}; ///< Render nanosecond timestamp fields as calendar dates.
456  bool enum_name{false}; ///< Render enumeration fields as symbolic names instead of numbers.
457  bool expand_arrays{true}; ///< Expand indexed collections (e.g. PointCloud points) element by element.
458  size_t max_elements{10000}; ///< Upper bound on expanded collection elements.
459 };
460 
461 /**
462  * @brief Renders a parsed zero-copy message as canonical human-readable text.
463  *
464  * @details
465  * Walks @p parser purely through its field reflection (@c fields, @c element_fields,
466  * @c collection_size, @c value) and renders the canonical text form shared by
467  * @c vlink-parse, @c vlink-efbs and @c vlink-eproto. Per-type presentation -- header
468  * grouping, hidden reserved slots, symbolic enumerator names, nanosecond timestamps,
469  * boolean rendering, the PointCloud protocol block and Tensor shape line -- is driven
470  * by the @c MessageParser::Field metadata rather than by per-message branches.
471  *
472  * @param parser Valid parser retained for the duration of this call.
473  * @param options Presentation toggles.
474  * @param truncated Optional flag set when collection expansion hit @c max_elements.
475  * @return Rendered text, or an empty string when @p parser is invalid.
476  */
477 VLINK_EXPORT std::string format_message(const MessageParser& parser, const MessageFormatOptions& options,
478  bool* truncated = nullptr);
479 
480 } // namespace zerocopy
481 
482 } // namespace vlink
Zero-copy audio frame container carrying one PCM or codec-encoded packet.
Zero-copy container for a single image / video frame plus pixel-format metadata.
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLINK_EXPORT
Definition: macros.h:81
Zero-copy variable-length array of fixed-size 3-D detection / tracking records.
Zero-copy 2-D occupancy / cost-map grid container with typed cell storage.
Zero-copy, schema-aware 3-D point cloud container with per-field type protocol.
Routing envelope used by the VLink proxy / monitoring path.
Generic zero-copy byte-buffer container with a Header prefix.
128-byte POD container holding one audio packet with full sample-format metadata.
80-byte POD container holding one camera / video frame plus image-format metadata.
112-byte POD container holding a packed array of 144-byte Object records.
152-byte POD container holding a typed 2-D occupancy / cost grid plus pose metadata.
256-byte POD container holding a schema-described array of N-field point records.
80-byte POD envelope packing payload, URL, serialisation type and host string.
64-byte POD container that wraps an opaque byte payload with a Header prefix.
248-byte POD container holding a dense N-D tensor plus shape / dtype / device metadata.
Zero-copy N-dimensional dense tensor container with shape, strides, and dtype metadata.