VLink  2.1.0
A high-performance communication middleware
bytes.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 bytes.h
26  * @brief Canonical 128-byte binary payload carrier with inline storage, multi-mode ownership and LZAV compression.
27  *
28  * @details
29  * Every binary buffer that crosses a VLink boundary (publish, subscribe, RPC argument, field value,
30  * proxy snapshot) flows through a @c vlink::Bytes object. The class fuses five orthogonal concerns
31  * into a single fixed-size 128-byte structure: small-buffer optimisation, ownership tagging,
32  * loaned-memory tracking, prefix-offset reservation and a compression / encoding utility surface.
33  *
34  * @par Ownership model
35  *
36  * | Factory | Owns memory | Frees on destroy | Aliases source pointer | Typical caller |
37  * | ----------------------------- | ----------- | ---------------- | ---------------------- | --------------------- |
38  * | @c Bytes::create | yes | yes | no | Fresh allocation |
39  * | @c Bytes::shallow_copy | no | no | yes | Zero-copy view |
40  * | @c Bytes::deep_copy | when sized | when sized | no | Detached owned copy |
41  * | @c Bytes::loan_internal | no (loaned) | no | yes | Iceoryx zero-copy |
42  * | @c Bytes::shallow_copy_ptr | no | no | yes | Opaque pointer wrap |
43  *
44  * @par Memory layout (logical)
45  *
46  * @verbatim
47  * +------------------------------------------------------------------+
48  * | Bytes (128 B) |
49  * +-------------------------+-------+----+------+-------+------------+
50  * | stack_data_ (96 B SBO) | owner | ln | off | size | capacity |
51  * +-------------------------+-------+----+------+-------+------------+
52  * |
53  * | (when size > 96 B)
54  * v
55  * +--------------------------------+
56  * | heap buffer from MemoryPool |
57  * | [ offset prefix ][ payload ] |
58  * +--------------------------------+
59  * @endverbatim
60  *
61  * @par Compression frame layout
62  *
63  * @verbatim
64  * byte: 0 1 2 3 4 5 6 7 8 N-4 N-3 N-2 N-1
65  * +---+---+---+---+---+---+---+---+---+ - - - - - - - - - +----+----+----+----+
66  * field: | header magic | original size BE | LZAV payload | footer magic |
67  * +---+---+---+---+---+---+---+---+---+ - - - - - - - - - +----+----+----+----+
68  * 17 49 B2 6F A7 05 ED 71
69  * @endverbatim
70  *
71  * Buffers below or equal to @c kStackSize (96 bytes) reside entirely in @c stack_data_; only larger
72  * payloads pull from @c MemoryPool::global_instance() through @c bytes_malloc. The total object
73  * footprint is fixed at 128 bytes regardless of payload size. An @c offset prefix reserves space
74  * at the head of the buffer so transport adapters can prepend protocol headers without realloc;
75  * @c data() returns @c real_data() @c + @c offset().
76  *
77  * @par Example
78  * @code
79  * vlink::Bytes a = vlink::Bytes::create(64); // SBO path, no heap allocation
80  * std::memcpy(a.data(), payload, a.size());
81  *
82  * vlink::Bytes view = vlink::Bytes::shallow_copy(ext, ext_size); // zero-copy alias
83  *
84  * auto packed = vlink::Bytes::compress_data(a.data(), a.size());
85  * if (vlink::Bytes::is_compress_data(packed.data(), packed.size())) {
86  * vlink::Bytes plain = vlink::Bytes::uncompress_data(packed.data(), packed.size());
87  * }
88  *
89  * const uint32_t crc = vlink::Bytes::get_crc_32(a);
90  * const std::string base64 = vlink::Bytes::encode_to_base64(a);
91  * vlink::Bytes round_trip = vlink::Bytes::decode_from_base64(base64);
92  * @endcode
93  */
94 
95 #pragma once
96 
97 #include <cstddef>
98 #include <cstdint>
99 #include <cstring>
100 #include <iostream>
101 #include <string>
102 #include <string_view>
103 #include <vector>
104 
105 #include "./macros.h"
106 
107 namespace vlink {
108 
109 /**
110  * @class Bytes
111  * @brief Fixed-size 128-byte buffer holder with SBO, five ownership modes and integrated codecs.
112  *
113  * @details
114  * Implements VLink's universal binary carrier. Small payloads live inside the embedded
115  * @c stack_data_ array; larger payloads spill to the global @c MemoryPool. Ownership is encoded
116  * in two flags (@c is_owner_, @c is_loaned_) so the destructor can route to the correct release
117  * primitive: heap free, iceoryx loan release, or no-op for shallow aliases. All public functions
118  * are @c noexcept; failure is reported via empty return values.
119  */
120 class VLINK_EXPORT Bytes final { // size == 128 bytes
121  public:
122  /**
123  * @brief Eagerly constructs the process-wide @c MemoryPool that backs heap allocations.
124  *
125  * @details
126  * @c Bytes::bytes_malloc routes through @c MemoryPool::global_instance(). Calling this once
127  * at program start front-loads the singleton construction cost and respects
128  * @c VLINK_MEMORY_LEVEL / @c VLINK_MEMORY_PREALLOC / @c VLINK_MEMORY_BATCH_SIZE. Subsequent
129  * calls are idempotent no-ops.
130  */
131  static void init_memory_pool() noexcept;
132 
133  /**
134  * @brief Releases every empty chunk currently cached by the @c Bytes memory pool.
135  *
136  * @details
137  * Forwards to @c MemoryPool::global_instance().trim(). Only chunks whose blocks are entirely
138  * on their tier's free list are returned to the system allocator; chunks still backing a live
139  * @c Bytes instance are preserved. Lifetime statistics and the geometric chunk-growth state
140  * are kept intact.
141  *
142  * @note Safe to invoke concurrently with allocations and frees; treat as a periodic maintenance
143  * call rather than a hot-path primitive.
144  */
145  static void release_memory_pool() noexcept;
146 
147  /**
148  * @brief Allocates a raw aligned byte buffer through the global memory pool.
149  *
150  * @param size Number of bytes requested.
151  * @return Pointer to the newly allocated buffer, or @c nullptr on upstream OOM.
152  * @note The same @p size value must be passed to the matching @c bytes_free call.
153  */
154  [[nodiscard]] static uint8_t* bytes_malloc(size_t size) noexcept;
155 
156  /**
157  * @brief Returns a buffer previously obtained from @c bytes_malloc to the pool.
158  *
159  * @param ptr Pointer returned by @c bytes_malloc. @c nullptr is a no-op.
160  * @param size Original size; must match the value passed to @c bytes_malloc.
161  */
162  static void bytes_free(uint8_t* ptr, size_t size) noexcept;
163 
164  /**
165  * @brief Allocates an owned buffer of the requested size with an optional header offset.
166  *
167  * @details
168  * Payloads up to @c kStackSize stay in @c stack_data_; larger payloads use the memory pool.
169  * The content is left uninitialised. When @p offset is non-zero the first @p offset bytes of
170  * the backing buffer are reserved so transport layers can prepend frame headers in place;
171  * @c data() then points past the reserved prefix.
172  *
173  * @param size Number of usable payload bytes after construction.
174  * @param offset Header bytes reserved before the payload region. Default: @c 0.
175  * @return New owning @c Bytes instance.
176  */
177  [[nodiscard]] static Bytes create(size_t size, uint8_t offset = 0) noexcept;
178 
179  /**
180  * @brief Wraps an external mutable buffer as a non-owning alias.
181  *
182  * @details
183  * Performs no allocation and no copy. The caller guarantees the lifetime of @p data exceeds
184  * the lifetime of the returned object. A null pointer with a non-zero @p size is rejected and
185  * produces an empty object.
186  *
187  * @param data External buffer to alias.
188  * @param size Length of the buffer in bytes.
189  * @return Non-owning @c Bytes pointing at @p data.
190  */
191  [[nodiscard]] static Bytes shallow_copy(uint8_t* data, size_t size) noexcept;
192 
193  /**
194  * @brief Wraps an external read-only buffer as a non-owning alias.
195  *
196  * @details
197  * Identical to the mutable overload; the @c const pointer is stored verbatim through a
198  * @c const_cast so the non-const @c data() accessor returns the same address. A null pointer
199  * with a non-zero @p size is rejected and produces an empty object.
200  *
201  * @param data External read-only buffer to alias.
202  * @param size Length of the buffer in bytes.
203  * @return Non-owning @c Bytes pointing at @p data.
204  */
205  [[nodiscard]] static Bytes shallow_copy(const uint8_t* data, size_t size) noexcept;
206 
207  /**
208  * @brief Wraps an opaque pointer as a zero-size pointer carrier.
209  *
210  * @details
211  * Sets @c size() and @c offset() to @c 0 so @c is_ptr() reports @c true. The wrapped pointer
212  * is retrieved through @c to_ptr<T>(); ownership stays with the caller.
213  *
214  * @param data Opaque pointer value to embed.
215  * @return Non-owning, zero-size @c Bytes carrying @p data.
216  */
217  [[nodiscard]] static Bytes shallow_copy_ptr(void* data) noexcept;
218 
219  /**
220  * @brief Produces an owned copy of an external mutable buffer.
221  *
222  * @details
223  * Allocates a fresh buffer and @c memcpy s @p size bytes from @p data into it. A null pointer
224  * with a non-zero @p size is rejected and produces an empty object. When @p size is zero and
225  * @p offset is @c 0 the result is empty and non-owning; with a non-zero @p offset only the prefix
226  * region is allocated.
227  *
228  * @param data Source buffer.
229  * @param size Number of bytes to copy.
230  * @param offset Header bytes reserved in the new buffer. Default: @c 0.
231  * @return Owning @c Bytes containing the copied payload.
232  */
233  [[nodiscard]] static Bytes deep_copy(uint8_t* data, size_t size, uint8_t offset = 0) noexcept;
234 
235  /**
236  * @brief Produces an owned copy of an external read-only buffer.
237  *
238  * @details
239  * Read-only overload of @c deep_copy(uint8_t*, size_t, uint8_t). Ownership rules for empty
240  * sources match the mutable overload exactly.
241  *
242  * @param data Source read-only buffer.
243  * @param size Number of bytes to copy.
244  * @param offset Header bytes reserved in the new buffer. Default: @c 0.
245  * @return Owning @c Bytes containing the copied payload.
246  */
247  [[nodiscard]] static Bytes deep_copy(const uint8_t* data, size_t size, uint8_t offset = 0) noexcept;
248 
249  /**
250  * @brief Wraps an iceoryx-loaned mutable payload as a non-owning, non-aliasing carrier.
251  *
252  * @details
253  * Marks @c is_loaned() as @c true so the destructor skips the free call -- the underlying
254  * memory is owned by RouDi. Used internally by the @c shm:// transport backend. A null pointer
255  * with a non-zero @p size is rejected and produces an empty object.
256  *
257  * @param data Pointer to the iceoryx chunk payload.
258  * @param size Length of the payload in bytes.
259  * @return Loaned @c Bytes instance.
260  */
261  [[nodiscard]] static Bytes loan_internal(uint8_t* data, size_t size) noexcept;
262 
263  /**
264  * @brief Wraps an iceoryx-loaned read-only payload as a non-owning, non-aliasing carrier.
265  *
266  * @details A null pointer with a non-zero @p size is rejected and produces an empty object.
267  *
268  * @param data Pointer to the read-only iceoryx chunk payload.
269  * @param size Length of the payload in bytes.
270  * @return Loaned @c Bytes instance.
271  */
272  [[nodiscard]] static Bytes loan_internal(const uint8_t* data, size_t size) noexcept;
273 
274  /**
275  * @brief Builds an owned @c Bytes from the bytes of a UTF-8 string.
276  *
277  * @param str Source string; copied byte-for-byte.
278  * @param offset Header bytes reserved before the payload. Default: @c 0.
279  * @return Owning @c Bytes containing @p str. Empty input with zero offset yields an empty result.
280  */
281  [[nodiscard]] static Bytes from_string(const std::string& str, uint8_t offset = 0) noexcept;
282 
283  /**
284  * @brief Parses a user-typed hex literal into a @c Bytes payload.
285  *
286  * @details
287  * Accepts space-separated byte tokens (@c "1A @c 2B"), a contiguous even-length hex run with
288  * or without a @c 0x / @c 0X prefix (@c "0x1A2B"), or mixed forms. Returns an empty result on
289  * parse failure and sets @p ok to @c false.
290  *
291  * @param str Source hex string.
292  * @param ok Optional pointer set to @c true on success and @c false on failure.
293  * @return Parsed @c Bytes, or an empty value on failure.
294  */
295  [[nodiscard]] static Bytes from_user_input(const std::string& str, bool* ok = nullptr) noexcept;
296 
297  /**
298  * @brief Renders a raw byte array as space-separated uppercase hex tokens.
299  *
300  * @param value Pointer to the source buffer.
301  * @param size Number of bytes to render.
302  * @return Hex string such as @c "1A B2 C3" for the input @c {0x1A, @c 0xB2, @c 0xC3}.
303  */
304  [[nodiscard]] static std::string convert_to_hex_str(const uint8_t* value, size_t size) noexcept;
305 
306  /**
307  * @brief Returns a new owned @c Bytes with the byte order of @p target reversed.
308  *
309  * @param target Source buffer to reverse.
310  * @return New owned buffer with reversed byte order.
311  */
312  [[nodiscard]] static Bytes reverse_order(const Bytes& target) noexcept;
313 
314  /**
315  * @brief Encodes a payload as a standard Base-64 ASCII string.
316  *
317  * @param target Source buffer.
318  * @return Base-64 string representation.
319  */
320  [[nodiscard]] static std::string encode_to_base64(const Bytes& target) noexcept;
321 
322  /**
323  * @brief Decodes a Base-64 ASCII string back into a binary payload.
324  *
325  * @param target Base-64 source string.
326  * @return Decoded @c Bytes, or an empty value on invalid input.
327  */
328  [[nodiscard]] static Bytes decode_from_base64(const std::string& target) noexcept;
329 
330  /**
331  * @brief Computes the CRC-32 (ISO-HDLC) checksum of @p target.
332  *
333  * @param target Source buffer.
334  * @return 32-bit CRC-32 value.
335  */
336  [[nodiscard]] static uint32_t get_crc_32(const Bytes& target) noexcept;
337 
338  /**
339  * @brief Computes the CRC-64 (ECMA-182) checksum of @p target.
340  *
341  * @param target Source buffer.
342  * @return 64-bit CRC-64 value.
343  */
344  [[nodiscard]] static uint64_t get_crc_64(const Bytes& target) noexcept;
345 
346  /**
347  * @brief Constructs an empty, non-owning carrier with no payload.
348  *
349  * @details
350  * @c data() is @c nullptr and @c size() is @c 0; both @c is_owner() and @c is_loaned() are
351  * @c false. The SBO region is zero-initialised.
352  */
353  Bytes() noexcept;
354 
355  /**
356  * @brief Copy constructor; converts any source into an owned deep copy.
357  *
358  * @details
359  * Allocates a fresh buffer through the memory pool and copies @p target's bytes into it when
360  * @p target carries data. Empty inputs yield an empty non-owning result. The copy is always
361  * an owner regardless of the source's ownership tags -- aliasing and loaned semantics are not
362  * preserved by this constructor.
363  *
364  * @param target Source buffer.
365  * @note To keep aliasing or loaned semantics use the explicit factory methods
366  * (@c shallow_copy / @c loan_internal) instead of the copy constructor.
367  */
368  Bytes(const Bytes& target) noexcept;
369 
370  /**
371  * @brief Move constructor; transfers payload, ownership flags and prefix from @p target.
372  *
373  * @param target Source buffer left in the empty state after the move.
374  */
375  Bytes(Bytes&& target) noexcept;
376 
377  /**
378  * @brief Constructs an owned buffer from an initialiser list of bytes.
379  *
380  * @param list Byte values to copy into the new buffer.
381  */
382  Bytes(const std::initializer_list<uint8_t>& list) noexcept;
383 
384  /**
385  * @brief Constructs an owned buffer from a @c std::vector<uint8_t> by deep copy.
386  *
387  * @param data Source vector; its contents are copied verbatim.
388  */
389  explicit Bytes(const std::vector<uint8_t>& data) noexcept;
390 
391  /**
392  * @brief Destructor; releases owned heap storage and ignores loaned / shallow buffers.
393  */
394  ~Bytes() noexcept;
395 
396  /**
397  * @brief Copy assignment; converts any source into an owned deep copy of @p target.
398  *
399  * @details
400  * Releases the current buffer first when this instance owns one. Empty sources produce an
401  * empty non-owning result.
402  *
403  * @param target Source buffer.
404  * @return Reference to @c *this.
405  */
406  Bytes& operator=(const Bytes& target) noexcept;
407 
408  /**
409  * @brief Move assignment; releases the current buffer and adopts @p target's state.
410  *
411  * @param target Source buffer left empty after the move.
412  * @return Reference to @c *this.
413  */
414  Bytes& operator=(Bytes&& target) noexcept;
415 
416  /**
417  * @brief Replaces the payload with a deep copy of @p data.
418  *
419  * @param data Source vector.
420  * @return Reference to @c *this.
421  */
422  Bytes& operator=(const std::vector<uint8_t>& data) noexcept;
423 
424  /**
425  * @brief Byte-wise equality comparison with another @c Bytes.
426  *
427  * @param target Right-hand operand.
428  * @return @c true when sizes and payload bytes match exactly.
429  */
430  [[nodiscard]] bool operator==(const Bytes& target) const noexcept;
431 
432  /**
433  * @brief Byte-wise inequality comparison with another @c Bytes.
434  *
435  * @param target Right-hand operand.
436  * @return @c true when either the sizes or the payload bytes differ.
437  */
438  [[nodiscard]] bool operator!=(const Bytes& target) const noexcept;
439 
440  /**
441  * @brief Byte-wise equality comparison with a @c std::vector<uint8_t>.
442  *
443  * @param data Right-hand operand.
444  * @return @c true when sizes and bytes match exactly.
445  */
446  [[nodiscard]] bool operator==(const std::vector<uint8_t>& data) const noexcept;
447 
448  /**
449  * @brief Byte-wise inequality comparison with a @c std::vector<uint8_t>.
450  *
451  * @param data Right-hand operand.
452  * @return @c true when either the sizes or the bytes differ.
453  */
454  [[nodiscard]] bool operator!=(const std::vector<uint8_t>& data) const noexcept;
455 
456  /**
457  * @brief Mutable indexed access into the payload region.
458  *
459  * @details
460  * Resolves to @c real_data()[offset() @c + @c index]. No bounds checking is performed; pass
461  * indices in @c [0, size()).
462  *
463  * @param index Zero-based logical offset within the payload.
464  * @return Reference to the byte at @p index.
465  */
466  [[nodiscard]] uint8_t& operator[](size_t index) noexcept;
467 
468  /**
469  * @brief Read-only indexed access into the payload region.
470  *
471  * @param index Zero-based logical offset within the payload.
472  * @return Const reference to the byte at @p index.
473  */
474  [[nodiscard]] const uint8_t& operator[](size_t index) const noexcept;
475 
476  /**
477  * @brief Returns a mutable pointer to the start of the payload (post-offset).
478  *
479  * @return Pointer to the first payload byte, or @c nullptr when empty.
480  */
481  [[nodiscard]] uint8_t* data() noexcept;
482 
483  /**
484  * @brief Returns a read-only pointer to the start of the payload (post-offset).
485  *
486  * @return Pointer to the first payload byte, or @c nullptr when empty.
487  */
488  [[nodiscard]] const uint8_t* data() const noexcept;
489 
490  /**
491  * @brief Returns a mutable pointer to the very beginning of the backing buffer.
492  *
493  * @details
494  * @c real_data() points at the prefix region; @c real_data() @c + @c offset() equals @c data().
495  *
496  * @return Pointer to the raw buffer origin, or @c nullptr when empty.
497  */
498  [[nodiscard]] uint8_t* real_data() noexcept;
499 
500  /**
501  * @brief Returns a read-only pointer to the very beginning of the backing buffer.
502  *
503  * @return Pointer to the raw buffer origin, or @c nullptr when empty.
504  */
505  [[nodiscard]] const uint8_t* real_data() const noexcept;
506 
507  /**
508  * @brief Returns the size of the payload region in bytes.
509  *
510  * @return Number of payload bytes (excluding the prefix offset).
511  */
512  [[nodiscard]] size_t size() const noexcept;
513 
514  /**
515  * @brief Returns the size of the used backing region (payload plus prefix offset).
516  *
517  * @return @c size() @c + @c offset().
518  */
519  [[nodiscard]] size_t real_size() const noexcept;
520 
521  /**
522  * @brief Returns the allocated capacity of the backing buffer.
523  *
524  * @details
525  * SBO buffers report @c kStackSize; pool-allocated buffers report the rounded allocation size.
526  *
527  * @return Capacity in bytes; always @c >= @c real_size().
528  */
529  [[nodiscard]] size_t capacity() const noexcept;
530 
531  /**
532  * @brief Returns the reserved header offset preceding the payload.
533  *
534  * @return Offset in bytes.
535  */
536  [[nodiscard]] uint8_t offset() const noexcept;
537 
538  /**
539  * @brief Reports whether this instance owns and will free its storage.
540  *
541  * @return @c true for objects produced by @c create / @c deep_copy and surviving copy/move
542  * assignments that produced an owned deep copy.
543  */
544  [[nodiscard]] bool is_owner() const noexcept;
545 
546  /**
547  * @brief Reports whether the buffer is an iceoryx loan that VLink must not free.
548  *
549  * @return @c true for objects produced by @c loan_internal.
550  */
551  [[nodiscard]] bool is_loaned() const noexcept;
552 
553  /**
554  * @brief Reports whether the buffer is logically empty.
555  *
556  * @return @c true when @c data() is @c nullptr and @c size() is @c 0.
557  */
558  [[nodiscard]] bool empty() const noexcept;
559 
560  /**
561  * @brief Returns a mutable iterator to the first payload byte.
562  *
563  * @return Pointer to the first payload byte, or @c nullptr when empty.
564  */
565  [[nodiscard]] uint8_t* begin() noexcept;
566 
567  /**
568  * @brief Returns a read-only iterator to the first payload byte.
569  *
570  * @return Pointer to the first payload byte, or @c nullptr when empty.
571  */
572  [[nodiscard]] const uint8_t* begin() const noexcept;
573 
574  /**
575  * @brief Returns a mutable iterator one past the last payload byte.
576  *
577  * @return End pointer, or @c nullptr when empty.
578  */
579  [[nodiscard]] uint8_t* end() noexcept;
580 
581  /**
582  * @brief Returns a read-only iterator one past the last payload byte.
583  *
584  * @return End pointer, or @c nullptr when empty.
585  */
586  [[nodiscard]] const uint8_t* end() const noexcept;
587 
588  /**
589  * @brief Returns a mutable iterator to the start of the raw backing region.
590  *
591  * @return Pointer equal to @c real_data().
592  */
593  [[nodiscard]] uint8_t* real_begin() noexcept;
594 
595  /**
596  * @brief Returns a read-only iterator to the start of the raw backing region.
597  *
598  * @return Pointer equal to @c real_data().
599  */
600  [[nodiscard]] const uint8_t* real_begin() const noexcept;
601 
602  /**
603  * @brief Returns a mutable iterator one past the prefix-plus-payload region.
604  *
605  * @return End pointer for the used backing region, or @c nullptr when empty.
606  */
607  [[nodiscard]] uint8_t* real_end() noexcept;
608 
609  /**
610  * @brief Returns a read-only iterator one past the prefix-plus-payload region.
611  *
612  * @return End pointer for the used backing region, or @c nullptr when empty.
613  */
614  [[nodiscard]] const uint8_t* real_end() const noexcept;
615 
616  /**
617  * @brief Reports whether this carrier merely wraps an opaque pointer.
618  *
619  * @details
620  * A pointer-only wrapper satisfies @c data_ @c != @c nullptr, @c size_ @c == @c 0,
621  * @c offset_ @c == @c 0 and @c is_owner_ @c == @c false. Retrieve the underlying pointer via
622  * @c to_ptr<T>().
623  *
624  * @return @c true when this is a pointer-only wrapper.
625  */
626  [[nodiscard]] bool is_ptr() const noexcept;
627 
628  /**
629  * @brief Copies the payload region into a new @c std::vector<uint8_t>.
630  *
631  * @return Vector mirroring @c data()[0, size()).
632  */
633  [[nodiscard]] std::vector<uint8_t> to_raw_data() const noexcept;
634 
635  /**
636  * @brief Materialises the payload region as a new @c std::string.
637  *
638  * @return Owning string with the payload bytes.
639  */
640  [[nodiscard]] std::string to_string() const noexcept;
641 
642  /**
643  * @brief Returns a non-owning @c std::string_view into the payload region.
644  *
645  * @details
646  * The view is valid until the next mutation of this @c Bytes instance or its destruction.
647  *
648  * @return View covering @c data()[0, size()).
649  */
650  [[nodiscard]] std::string_view to_string_view() const noexcept;
651 
652  /**
653  * @brief Reinterprets the backing pointer as @c T*.
654  *
655  * @details
656  * Equivalent to @c reinterpret_cast<T*>(real_data()). Caller is responsible for alignment.
657  *
658  * @tparam T Target pointee type. Defaults to @c void.
659  * @return Pointer to @c T, or @c nullptr when empty.
660  */
661  template <typename T = void>
662  [[nodiscard]] T* to_ptr() const noexcept;
663 
664  /**
665  * @brief Returns the SBO threshold below which payloads stay inline.
666  *
667  * @return @c kStackSize (@c 96).
668  */
669  [[nodiscard]] static constexpr uint8_t stack_size() noexcept;
670 
671  /**
672  * @brief Returns @c true at compile time when the platform is little-endian.
673  *
674  * @return @c true on x86 / arm-le / Windows; @c false on big-endian targets.
675  */
676  [[nodiscard]] static constexpr bool is_little_endian() noexcept;
677 
678  /**
679  * @brief Returns @c true at compile time when the platform is big-endian.
680  *
681  * @return Logical negation of @c is_little_endian().
682  */
683  [[nodiscard]] static constexpr bool is_big_endian() noexcept;
684 
685  /**
686  * @brief Detects whether a raw byte buffer matches the VLink LZAV compression frame layout.
687  *
688  * @details
689  * Validates the 4-byte header magic (@c 17 @c 49 @c B2 @c 6F), the 4-byte footer magic
690  * (@c A7 @c 05 @c ED @c 71) and that the buffer is at least 13 bytes long.
691  *
692  * @param data Pointer to the buffer to inspect.
693  * @param size Length of the buffer.
694  * @return @c true when both magics match and the size precondition holds.
695  */
696  [[nodiscard]] static bool is_compress_data(const uint8_t* data, size_t size) noexcept;
697 
698  /**
699  * @brief Compresses a payload using LZAV and wraps it in the VLink compression frame.
700  *
701  * @details
702  * Emits the layout shown in the file-level diagram. Inputs above the codec's @c 256 @c MiB decoded-size
703  * boundary are rejected and produce an empty result.
704  *
705  * @param data Source pointer.
706  * @param size Source length in bytes.
707  * @param high_ratio @c true to use LZAV's high-compression preset; default @c false.
708  * @return Compressed framed buffer, or an empty @c Bytes on failure.
709  */
710  [[nodiscard]] static Bytes compress_data(const uint8_t* data, size_t size, bool high_ratio = false) noexcept;
711 
712  /**
713  * @brief Decompresses an LZAV-framed buffer back into its original payload.
714  *
715  * @details
716  * Strips the header / size / footer fields and feeds the LZAV payload to @c lzav_decompress.
717  * When @p check_valid is @c true the magics are verified up front; invalid magics yield an
718  * empty result. Stored original sizes of @c 0 or above @c 256 @c MiB are also rejected.
719  *
720  * @param data Framed source pointer.
721  * @param size Framed source length in bytes.
722  * @param check_valid @c true to verify magics before decompressing. Default: @c true.
723  * @return Decompressed payload, or an empty @c Bytes on failure.
724  */
725  [[nodiscard]] static Bytes uncompress_data(const uint8_t* data, size_t size, bool check_valid = true) noexcept;
726 
727  /**
728  * @brief Releases owned storage and resets all metadata to the empty state.
729  *
730  * @details
731  * When @c is_owner() is @c true the backing buffer is returned to the pool; loaned and
732  * shallow carriers are simply forgotten. After the call @c empty() reports @c true.
733  */
734  void clear() noexcept;
735 
736  /**
737  * @brief Truncates the logical payload size in place without reallocating.
738  *
739  * @details
740  * Valid only for owned buffers. @p size must be @c <= current @c size(); the backing
741  * capacity is unchanged.
742  *
743  * @param size New logical size in bytes.
744  * @return @c true on success; @c false for non-owned buffers or oversized requests.
745  */
746  [[nodiscard]] bool shrink_to(size_t size) noexcept;
747 
748  /**
749  * @brief Ensures the backing capacity is at least @p new_capacity bytes.
750  *
751  * @details
752  * Valid only for owned buffers. When the current capacity already satisfies the request the
753  * call is a no-op; otherwise a fresh buffer is allocated and the existing payload copied.
754  *
755  * @param new_capacity Minimum required capacity.
756  * @return @c true on success; @c false for non-owned buffers or allocation failure.
757  */
758  [[nodiscard]] bool reserve(size_t new_capacity) noexcept;
759 
760  /**
761  * @brief Resizes the logical payload region to @p size bytes.
762  *
763  * @details
764  * Valid only for owned buffers. When @p size exceeds the current capacity @c reserve is
765  * invoked first. Newly exposed bytes are uninitialised unless the build defines
766  * @c VLINK_BYTES_MEM_RESET.
767  *
768  * @param size New payload size in bytes.
769  * @return @c true on success; @c false for non-owned buffers or reallocation failure.
770  */
771  [[nodiscard]] bool resize(size_t size) noexcept;
772 
773  /**
774  * @brief Replaces this instance with a non-owning alias of @p bytes.
775  *
776  * @details
777  * Releases any owned storage first. Copies @p bytes's raw pointer, size and offset without
778  * copying the payload. The result is non-owning regardless of @p bytes's loaned tag; use
779  * @c loan_internal directly to preserve loaned semantics.
780  *
781  * @param bytes Source carrier to alias.
782  * @return Reference to @c *this.
783  */
784  Bytes& shallow_copy(const Bytes& bytes) noexcept;
785 
786  /**
787  * @brief Replaces this instance with an owned deep copy of @p bytes.
788  *
789  * @details
790  * Releases any owned storage first, then allocates and populates a fresh buffer when
791  * @p bytes carries data. Empty sources leave this instance empty and non-owning.
792  *
793  * @param bytes Source carrier to copy.
794  * @return Reference to @c *this.
795  */
796  Bytes& deep_copy(const Bytes& bytes) noexcept;
797 
798  /**
799  * @brief Converts an existing non-owning carrier into an owning one in place.
800  *
801  * @details
802  * Owners are returned unchanged. Empty carriers are cleared. Otherwise a fresh buffer is
803  * allocated and the current payload copied into it.
804  *
805  * @return Reference to @c *this.
806  */
807  Bytes& deep_copy_self() noexcept;
808 
809  /**
810  * @brief Stream insertion operator; prints the payload as space-separated hex bytes.
811  *
812  * @param ostream Target output stream.
813  * @param target Buffer to print.
814  * @return Reference to @p ostream.
815  */
816  VLINK_EXPORT friend std::ostream& operator<<(std::ostream& ostream, const Bytes& target) noexcept;
817 
818  private:
819  enum Type : uint8_t {
820  kCreate = 0,
821  kShallowCopy = 1,
822  kDeepCopy = 2,
823  kMove = 3,
824  };
825 
826  Bytes(Type type, uint8_t* data, size_t size, uint8_t offset, bool loaned) noexcept;
827 
828  void process_type(Type type, uint8_t* data, size_t size, uint8_t offset, bool loaned, Bytes* tmp = nullptr) noexcept;
829 
830  static constexpr uint8_t kStackSize{96};
831  alignas(std::max_align_t) uint8_t stack_data_[kStackSize]{0};
832  bool is_owner_{false};
833  bool is_loaned_{false};
834  uint8_t offset_{0};
835  uint8_t* data_{nullptr};
836  size_t size_{0};
837  size_t capacity_{0};
838 };
839 
840 ////////////////////////////////////////////////////////////////
841 /// Details
842 ////////////////////////////////////////////////////////////////
843 
844 inline uint8_t& Bytes::operator[](size_t index) noexcept { return data_[offset_ + index]; }
845 
846 inline const uint8_t& Bytes::operator[](size_t index) const noexcept { return data_[offset_ + index]; }
847 
848 inline uint8_t* Bytes::data() noexcept { return data_ ? (data_ + offset_) : nullptr; }
849 
850 inline const uint8_t* Bytes::data() const noexcept { return data_ ? (data_ + offset_) : nullptr; }
851 
852 inline uint8_t* Bytes::real_data() noexcept { return data_; }
853 
854 inline const uint8_t* Bytes::real_data() const noexcept { return data_; }
855 
856 inline size_t Bytes::size() const noexcept { return size_; }
857 
858 inline size_t Bytes::real_size() const noexcept { return size_ + offset_; }
859 
860 inline size_t Bytes::capacity() const noexcept { return capacity_; }
861 
862 inline uint8_t Bytes::offset() const noexcept { return offset_; }
863 
864 inline bool Bytes::is_owner() const noexcept { return is_owner_; }
865 
866 inline bool Bytes::is_loaned() const noexcept { return is_loaned_; }
867 
868 inline bool Bytes::empty() const noexcept { return data_ == nullptr && size_ == 0; }
869 
870 inline uint8_t* Bytes::begin() noexcept { return data_ ? (data_ + offset_) : nullptr; }
871 
872 inline const uint8_t* Bytes::begin() const noexcept { return data_ ? (data_ + offset_) : nullptr; }
873 
874 inline uint8_t* Bytes::end() noexcept { return data_ ? (data_ + offset_ + size_) : nullptr; }
875 
876 inline const uint8_t* Bytes::end() const noexcept { return data_ ? (data_ + offset_ + size_) : nullptr; }
877 
878 inline uint8_t* Bytes::real_begin() noexcept { return data_; }
879 
880 inline const uint8_t* Bytes::real_begin() const noexcept { return data_; }
881 
882 inline uint8_t* Bytes::real_end() noexcept { return data_ ? (data_ + offset_ + size_) : nullptr; }
883 
884 inline const uint8_t* Bytes::real_end() const noexcept { return data_ ? (data_ + offset_ + size_) : nullptr; }
885 
886 inline bool Bytes::is_ptr() const noexcept { return data_ != nullptr && size_ == 0 && offset_ == 0 && !is_owner_; }
887 
888 template <typename T>
889 inline T* Bytes::to_ptr() const noexcept {
890  return reinterpret_cast<T*>(data_);
891 }
892 
893 inline constexpr uint8_t Bytes::stack_size() noexcept { return kStackSize; }
894 
895 inline constexpr bool Bytes::is_little_endian() noexcept {
896 #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || \
897  (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
898  return true;
899 #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
900  return false;
901 #else
902  return true;
903 #endif
904 }
905 
906 inline constexpr bool Bytes::is_big_endian() noexcept { return !is_little_endian(); }
907 
908 } // namespace vlink
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VLINK_EXPORT
Definition: macros.h:81