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