VLink 2.0.0
A high-performance communication middleware
Loading...
Searching...
No Matches
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 Versatile byte buffer with small-buffer optimisation, ownership semantics and compression.
27 *
28 * @details
29 * @c Bytes is the primary binary data carrier in VLink. Every message serialised or received by
30 * a publisher/subscriber is wrapped in a @c Bytes object. The class is designed to minimise
31 * heap allocations on the hot path:
32 *
33 * - Buffers up to @c kStackSize (96) bytes are stored entirely within the object's inline
34 * @c stack_data_ array (small-buffer optimisation / SBO).
35 * - Larger buffers are allocated from an optional @c pmr::synchronized_pool_resource (Linux
36 * only when @c __cpp_lib_memory_resource is available), or from the system heap via
37 * @c bytes_malloc() / @c bytes_free().
38 * - The total object size is exactly 128 bytes regardless of content.
39 *
40 * Ownership model:
41 *
42 * | Factory method | Owns memory | On copy | Use case |
43 * | ------------------------------- | ----------- | ----------- | --------------------------------- |
44 * | @c Bytes::create() | Yes | Deep copy | Fresh allocation |
45 * | @c Bytes::shallow_copy() | No | Ptr alias | Zero-copy wrapping of extern buf |
46 * | @c Bytes::deep_copy() | Yes | Deep copy | Owned copy of extern buf |
47 * | @c Bytes::loan_internal() | No (loaned) | Ptr alias | Iceoryx zero-copy loan |
48 * | @c Bytes::shallow_copy_ptr() | No | Ptr alias | Wrap opaque pointer (size == 0) |
49 *
50 * Compression support (LZAV):
51 * - @c compress_data() appends a 4-byte header magic and a 4-byte footer magic around the LZAV
52 * compressed payload, enabling @c is_compress_data() to detect compressed buffers.
53 * - @c uncompress_data() strips the header/footer and decompresses; optionally validates the
54 * magic bytes first.
55 *
56 * Utility helpers:
57 * - Base-64 encode/decode (@c encode_to_base64 / @c decode_from_base64)
58 * - CRC-32 checksum (@c get_crc_32)
59 * - Byte-order reversal (@c reverse_order)
60 * - Hex-string conversion (@c convert_to_hex_str)
61 * - User-input parsing from hex/binary string literals (@c from_user_input)
62 *
63 * @note
64 * - The @c offset_ field reserves a prefix region inside the allocated buffer. @c data()
65 * returns @c real_data() + @c offset(). This allows transport layers to prepend headers
66 * in-place without re-allocation.
67 * - @c is_loaned() is set by @c loan_internal(), which is used exclusively for iceoryx
68 * zero-copy payloads that must @b not be freed by VLink.
69 * - @c is_ptr() returns @c true when size == 0 and offset == 0 and the object is not an owner,
70 * meaning the buffer holds only an opaque pointer created via @c shallow_copy_ptr().
71 * - @c init_memory_pool() and @c release_memory_pool() must be called explicitly at application
72 * startup/shutdown if the memory pool is desired. They are no-ops when the pool is unavailable.
73 *
74 * @par Example
75 * @code
76 * // SBO path (no heap allocation):
77 * auto buf = vlink::Bytes::create(64);
78 * std::memcpy(buf.data(), payload, 64);
79 *
80 * // Zero-copy shallow wrap:
81 * auto view = vlink::Bytes::shallow_copy(ext_ptr, ext_size);
82 *
83 * // Compression:
84 * auto compressed = vlink::Bytes::compress_data(buf.data(), buf.size());
85 * if (vlink::Bytes::is_compress_data(compressed.data(), compressed.size())) {
86 * auto original = vlink::Bytes::uncompress_data(compressed.data(), compressed.size());
87 * }
88 * @endcode
89 */
90
91#pragma once
92
93#include <cstddef>
94#include <cstdint>
95#include <cstring>
96#include <iostream>
97#include <string>
98#include <string_view>
99#include <vector>
100
101#include "./macros.h"
102
103namespace vlink {
104
105/**
106 * @class Bytes
107 * @brief Versatile 128-byte byte buffer with SBO, five ownership modes and compression helpers.
108 *
109 * @details
110 * The total object size is always 128 bytes (96-byte inline stack storage + metadata).
111 * Allocations larger than 96 bytes spill to the memory pool or system heap.
112 */
113class VLINK_EXPORT Bytes final { // size == 128 bytes
114 public:
115 /**
116 * @brief Initialises the global thread-safe memory pool for @c Bytes allocations.
117 *
118 * @details
119 * On Linux with @c __cpp_lib_memory_resource support this initialises a
120 * @c pmr::synchronized_pool_resource that reduces per-allocation overhead.
121 * Call this once at application start before any @c Bytes objects are created.
122 * Safe to call multiple times; subsequent calls are no-ops.
123 */
124 static void init_memory_pool() noexcept;
125
126 /**
127 * @brief Releases the global memory pool and returns its memory to the OS.
128 *
129 * @details
130 * After this call, @c bytes_malloc falls back to the system heap allocator.
131 * Call once at application shutdown after all @c Bytes objects that used the pool
132 * have been destroyed, to avoid a use-after-free.
133 */
134 static void release_memory_pool() noexcept;
135
136 /**
137 * @brief Allocates a raw byte buffer from the memory pool (or heap if pool is unavailable).
138 *
139 * @param size Number of bytes to allocate.
140 * @return Pointer to the allocated memory, or @c nullptr on failure.
141 *
142 * @note The returned pointer must be freed with @c bytes_free() using the same @p size.
143 */
144 [[nodiscard]] static uint8_t* bytes_malloc(size_t size) noexcept;
145
146 /**
147 * @brief Frees a buffer previously allocated by @c bytes_malloc().
148 *
149 * @param ptr Pointer returned by @c bytes_malloc().
150 * @param size Original size passed to @c bytes_malloc().
151 */
152 static void bytes_free(uint8_t* ptr, size_t size) noexcept;
153
154 /**
155 * @brief Creates an owned @c Bytes buffer of the given size.
156 *
157 * @details
158 * Memory is allocated from the pool or heap. If @p size <= @c kStackSize (96) the inline
159 * stack buffer is used instead (no heap allocation). The contents are @b not initialised.
160 *
161 * @param size Number of usable bytes (i.e. @c size() after construction).
162 * @param offset Number of bytes to reserve at the start of the backing buffer as a prefix
163 * region (e.g., for protocol headers). @c data() = @c real_data() + @p offset.
164 * Default: 0.
165 * @return A new owned @c Bytes object.
166 */
167 [[nodiscard]] static Bytes create(size_t size, uint8_t offset = 0) noexcept;
168
169 /**
170 * @brief Creates a non-owning @c Bytes alias pointing to an external mutable buffer.
171 *
172 * @details
173 * No memory is allocated or copied. The caller is responsible for ensuring the external
174 * buffer outlives the returned @c Bytes object. @c is_owner() returns @c false.
175 *
176 * @param data Pointer to the external buffer.
177 * @param size Length of the buffer in bytes.
178 * @return A non-owning @c Bytes object wrapping @p data.
179 */
180 [[nodiscard]] static Bytes shallow_copy(uint8_t* data, size_t size) noexcept;
181
182 /**
183 * @brief Creates a non-owning @c Bytes alias pointing to an external read-only buffer.
184 *
185 * @details
186 * Same as the mutable overload except the @p data pointer is @c const.
187 * Calling the non-const @c data() accessor on the result returns @c nullptr.
188 *
189 * @param data Pointer to the external read-only buffer.
190 * @param size Length of the buffer in bytes.
191 * @return A non-owning @c Bytes object wrapping @p data.
192 */
193 [[nodiscard]] static Bytes shallow_copy(const uint8_t* data, size_t size) noexcept;
194
195 /**
196 * @brief Wraps an opaque pointer without associating a byte size.
197 *
198 * @details
199 * Sets size == 0 and offset == 0 so that @c is_ptr() returns @c true. Useful for
200 * passing opaque C handles or iceoryx chunk pointers through the @c Bytes transport.
201 * The caller owns the pointed-to memory; VLink will not free it.
202 *
203 * @param data Opaque pointer to wrap.
204 * @return A non-owning, zero-size @c Bytes object carrying the pointer.
205 */
206 [[nodiscard]] static Bytes shallow_copy_ptr(void* data) noexcept;
207
208 /**
209 * @brief Creates an owned deep copy of an external mutable buffer.
210 *
211 * @details
212 * Allocates new memory, copies @p size bytes from @p data, and returns a fully
213 * owned @c Bytes object. Safe even after the original buffer is freed.
214 *
215 * @param data Pointer to the source buffer.
216 * @param size Number of bytes to copy.
217 * @param offset Prefix-region size in the new buffer. Default: 0.
218 * @return A new owned @c Bytes containing a copy of the source data.
219 */
220 [[nodiscard]] static Bytes deep_copy(uint8_t* data, size_t size, uint8_t offset = 0) noexcept;
221
222 /**
223 * @brief Creates an owned deep copy of an external read-only buffer.
224 *
225 * @details
226 * Same as the mutable overload but accepts a @c const source pointer.
227 *
228 * @param data Pointer to the source read-only buffer.
229 * @param size Number of bytes to copy.
230 * @param offset Prefix-region size in the new buffer. Default: 0.
231 * @return A new owned @c Bytes containing a copy of the source data.
232 */
233 [[nodiscard]] static Bytes deep_copy(const uint8_t* data, size_t size, uint8_t offset = 0) noexcept;
234
235 /**
236 * @brief Creates a loaned (non-owning) alias for an iceoryx zero-copy payload (mutable).
237 *
238 * @details
239 * Marks the object with @c is_loaned() == @c true. VLink will not free the memory
240 * because it is owned by the iceoryx RouDi daemon. This factory is used internally
241 * by the @c shm:// transport backend.
242 *
243 * @param data Pointer to the iceoryx chunk payload.
244 * @param size Size of the payload in bytes.
245 * @return A loaned @c Bytes object.
246 */
247 [[nodiscard]] static Bytes loan_internal(uint8_t* data, size_t size) noexcept;
248
249 /**
250 * @brief Creates a loaned (non-owning) alias for an iceoryx zero-copy payload (read-only).
251 *
252 * @details
253 * Same as the mutable overload but for @c const payloads.
254 *
255 * @param data Pointer to the read-only iceoryx chunk payload.
256 * @param size Size of the payload in bytes.
257 * @return A loaned @c Bytes object.
258 */
259 [[nodiscard]] static Bytes loan_internal(const uint8_t* data, size_t size) noexcept;
260
261 /**
262 * @brief Constructs a @c Bytes buffer from a @c std::string by deep-copying its contents.
263 *
264 * @param str Source string.
265 * @param offset Prefix-region reserved before the string data. Default: 0.
266 * @return A new owned @c Bytes containing the UTF-8 bytes of @p str.
267 */
268 [[nodiscard]] static Bytes from_string(const std::string& str, uint8_t offset = 0) noexcept;
269
270 /**
271 * @brief Parses a user-provided hex or binary string literal into a @c Bytes buffer.
272 *
273 * @details
274 * Accepts formats such as @c "0x1A2B3C" (hex) or raw binary strings.
275 * Sets @p ok to @c false if parsing fails.
276 *
277 * @param str Input string to parse.
278 * @param ok Optional pointer set to @c true on success, @c false on failure.
279 * @return The parsed @c Bytes, or an empty object on failure.
280 */
281 [[nodiscard]] static Bytes from_user_input(const std::string& str, bool* ok = nullptr) noexcept;
282
283 /**
284 * @brief Converts a raw byte array to an uppercase hex string with spaces.
285 *
286 * @details
287 * Each byte is rendered as two uppercase hex digits followed by a space, e.g.,
288 * @c {0x1A, 0xB2} -> @c "1A B2 ". Useful for logging binary frames.
289 *
290 * @param value Pointer to the byte array.
291 * @param size Number of bytes to convert.
292 * @return Hex string representation.
293 */
294 [[nodiscard]] static std::string convert_to_hex_str(const uint8_t* value, size_t size) noexcept;
295
296 /**
297 * @brief Returns a new @c Bytes object with the byte order of @p target reversed.
298 *
299 * @param target Source buffer to reverse.
300 * @return A new owned @c Bytes with bytes in reversed order.
301 */
302 [[nodiscard]] static Bytes reverse_order(const Bytes& target) noexcept;
303
304 /**
305 * @brief Encodes a @c Bytes buffer as a standard Base-64 ASCII string.
306 *
307 * @param target Buffer to encode.
308 * @return Base-64 encoded string.
309 */
310 [[nodiscard]] static std::string encode_to_base64(const Bytes& target) noexcept;
311
312 /**
313 * @brief Decodes a Base-64 ASCII string into a @c Bytes buffer.
314 *
315 * @param target Base-64 encoded string.
316 * @return Decoded @c Bytes. Returns an empty object on invalid input.
317 */
318 [[nodiscard]] static Bytes decode_from_base64(const std::string& target) noexcept;
319
320 /**
321 * @brief Computes the CRC-32 checksum of a @c Bytes buffer.
322 *
323 * @param target Buffer to checksum.
324 * @return 32-bit CRC value.
325 */
326 [[nodiscard]] static uint32_t get_crc_32(const Bytes& target) noexcept;
327
328 /**
329 * @brief Constructs an empty, unallocated @c Bytes object.
330 *
331 * @details
332 * @c data() returns @c nullptr and @c size() returns 0.
333 * @c is_owner() and @c is_loaned() are both @c false.
334 */
335 Bytes() noexcept;
336
337 /**
338 * @brief Copy constructor.
339 *
340 * @details
341 * If @p target is an owner, a deep copy of its data is made.
342 * If @p target is a shallow alias (non-owner, non-loaned), the alias is shared.
343 * Loaned objects are shallow-copied as well (ownership is not transferred).
344 *
345 * @param target Source @c Bytes to copy.
346 */
347 Bytes(const Bytes& target) noexcept;
348
349 /**
350 * @brief Move constructor.
351 *
352 * @details
353 * Transfers all ownership and data from @p target. After the move, @p target is empty.
354 *
355 * @param target Source @c Bytes to move from.
356 */
357 Bytes(Bytes&& target) noexcept;
358
359 /**
360 * @brief Constructs an owned @c Bytes from an initialiser list of byte values.
361 *
362 * @details
363 * Equivalent to @c create(list.size()) followed by a @c memcpy of the list elements.
364 *
365 * @param list Initialiser list of @c uint8_t values.
366 */
367 Bytes(const std::initializer_list<uint8_t>& list) noexcept;
368
369 /**
370 * @brief Constructs an owned @c Bytes by deep-copying a @c std::vector<uint8_t>.
371 *
372 * @param data Vector whose contents are copied into the new buffer.
373 */
374 explicit Bytes(const std::vector<uint8_t>& data) noexcept;
375
376 /**
377 * @brief Destructor. Frees owned memory if @c is_owner() is @c true and @c is_loaned() is @c false.
378 */
379 ~Bytes() noexcept;
380
381 /**
382 * @brief Copy assignment operator.
383 *
384 * @details
385 * Releases the current buffer, then copies @p target (deep if owner, shallow otherwise).
386 *
387 * @param target Source @c Bytes to copy-assign from.
388 * @return Reference to @c *this.
389 */
390 Bytes& operator=(const Bytes& target) noexcept;
391
392 /**
393 * @brief Move assignment operator.
394 *
395 * @details
396 * Releases the current buffer, then transfers all state from @p target.
397 *
398 * @param target Source @c Bytes to move from.
399 * @return Reference to @c *this.
400 */
401 Bytes& operator=(Bytes&& target) noexcept;
402
403 /**
404 * @brief Assignment from a @c std::vector<uint8_t> (deep copy).
405 *
406 * @param data Vector whose contents are deep-copied into this buffer.
407 * @return Reference to @c *this.
408 */
409 Bytes& operator=(const std::vector<uint8_t>& data) noexcept;
410
411 /**
412 * @brief Equality comparison -- compares byte content.
413 *
414 * @param target @c Bytes to compare with.
415 * @return @c true if both objects have identical size and content.
416 */
417 [[nodiscard]] bool operator==(const Bytes& target) const noexcept;
418
419 /**
420 * @brief Inequality comparison -- compares byte content.
421 *
422 * @param target @c Bytes to compare with.
423 * @return @c true if the objects differ in size or content.
424 */
425 [[nodiscard]] bool operator!=(const Bytes& target) const noexcept;
426
427 /**
428 * @brief Equality comparison with a @c std::vector<uint8_t>.
429 *
430 * @param data Vector to compare with.
431 * @return @c true if sizes and contents match.
432 */
433 [[nodiscard]] bool operator==(const std::vector<uint8_t>& data) const noexcept;
434
435 /**
436 * @brief Inequality comparison with a @c std::vector<uint8_t>.
437 *
438 * @param data Vector to compare with.
439 * @return @c true if sizes or contents differ.
440 */
441 [[nodiscard]] bool operator!=(const std::vector<uint8_t>& data) const noexcept;
442
443 /**
444 * @brief Mutable subscript operator.
445 *
446 * @details
447 * Accesses the byte at logical index @p index (i.e., @c real_data()[offset + index]).
448 * No bounds checking is performed.
449 *
450 * @param index Logical index (0-based from the start of the user data region).
451 * @return Reference to the byte at @p index.
452 */
453 [[nodiscard]] uint8_t& operator[](size_t index) noexcept;
454
455 /**
456 * @brief Read-only subscript operator.
457 *
458 * @param index Logical index (0-based from the start of the user data region).
459 * @return Const reference to the byte at @p index.
460 */
461 [[nodiscard]] const uint8_t& operator[](size_t index) const noexcept;
462
463 /**
464 * @brief Returns a pointer to the start of the user data region (after the prefix offset).
465 *
466 * @details
467 * Equivalent to @c real_data() + @c offset().
468 * Returns @c nullptr if the buffer is empty.
469 *
470 * @return Mutable pointer to user data, or @c nullptr if not allocated.
471 */
472 [[nodiscard]] uint8_t* data() noexcept;
473
474 /**
475 * @brief Returns a const pointer to the start of the user data region.
476 *
477 * @return Read-only pointer to user data, or @c nullptr if not allocated.
478 */
479 [[nodiscard]] const uint8_t* data() const noexcept;
480
481 /**
482 * @brief Returns a pointer to the very beginning of the backing buffer (before the offset).
483 *
484 * @details
485 * Use this to write protocol headers into the reserved prefix region.
486 * @c real_data() + @c offset() == @c data().
487 *
488 * @return Mutable pointer to the raw buffer origin.
489 */
490 [[nodiscard]] uint8_t* real_data() noexcept;
491
492 /**
493 * @brief Returns a const pointer to the very beginning of the backing buffer.
494 *
495 * @return Read-only pointer to the raw buffer origin.
496 */
497 [[nodiscard]] const uint8_t* real_data() const noexcept;
498
499 /**
500 * @brief Returns the number of usable bytes (excluding the prefix offset region).
501 *
502 * @return Size in bytes. Returns 0 if empty.
503 */
504 [[nodiscard]] size_t size() const noexcept;
505
506 /**
507 * @brief Returns the total backing-buffer size including the prefix offset region.
508 *
509 * @details
510 * @c real_size() == @c size() + @c offset().
511 *
512 * @return Total size of the backing buffer in bytes.
513 */
514 [[nodiscard]] size_t real_size() const noexcept;
515
516 /**
517 * @brief Returns the allocated capacity of the backing buffer.
518 *
519 * @details
520 * @c capacity() >= @c real_size() always holds. For SBO buffers, @c capacity() == @c kStackSize.
521 *
522 * @return Capacity in bytes.
523 */
524 [[nodiscard]] size_t capacity() const noexcept;
525
526 /**
527 * @brief Returns the size of the prefix offset region in bytes.
528 *
529 * @details
530 * @c data() == @c real_data() + @c offset().
531 *
532 * @return Offset in bytes.
533 */
534 [[nodiscard]] uint8_t offset() const noexcept;
535
536 /**
537 * @brief Returns @c true if this object owns and is responsible for freeing its buffer.
538 *
539 * @return @c true for objects created via @c create() or @c deep_copy().
540 */
541 [[nodiscard]] bool is_owner() const noexcept;
542
543 /**
544 * @brief Returns @c true if this object holds an iceoryx loaned chunk.
545 *
546 * @details
547 * Loaned objects must not free the underlying memory; ownership belongs to RouDi.
548 *
549 * @return @c true for objects created via @c loan_internal().
550 */
551 [[nodiscard]] bool is_loaned() const noexcept;
552
553 /**
554 * @brief Returns @c true if the buffer is empty (no data pointer and size == 0).
555 *
556 * @return @c true if @c data_ == nullptr and @c size_ == 0.
557 */
558 [[nodiscard]] bool empty() const noexcept;
559
560 /**
561 * @brief Iterator begin (mutable) -- same as @c data().
562 *
563 * @return Pointer to the first usable byte.
564 */
565 [[nodiscard]] uint8_t* begin() noexcept;
566
567 /**
568 * @brief Iterator begin (const) -- same as @c data() const.
569 *
570 * @return Const pointer to the first usable byte.
571 */
572 [[nodiscard]] const uint8_t* begin() const noexcept;
573
574 /**
575 * @brief Iterator end (mutable) -- one past the last usable byte.
576 *
577 * @return Pointer to one past the last byte.
578 */
579 [[nodiscard]] uint8_t* end() noexcept;
580
581 /**
582 * @brief Iterator end (const) -- one past the last usable byte.
583 *
584 * @return Const pointer to one past the last byte.
585 */
586 [[nodiscard]] const uint8_t* end() const noexcept;
587
588 /**
589 * @brief Iterator begin for the full backing buffer (mutable) -- same as @c real_data().
590 *
591 * @return Pointer to the first byte of the raw buffer (before the offset).
592 */
593 [[nodiscard]] uint8_t* real_begin() noexcept;
594
595 /**
596 * @brief Iterator begin for the full backing buffer (const).
597 *
598 * @return Const pointer to the first byte of the raw buffer.
599 */
600 [[nodiscard]] const uint8_t* real_begin() const noexcept;
601
602 /**
603 * @brief Iterator end for the full backing buffer (mutable).
604 *
605 * @return Pointer to one past the last byte of the raw buffer.
606 */
607 [[nodiscard]] uint8_t* real_end() noexcept;
608
609 /**
610 * @brief Iterator end for the full backing buffer (const).
611 *
612 * @return Const pointer to one past the last byte of the raw buffer.
613 */
614 [[nodiscard]] const uint8_t* real_end() const noexcept;
615
616 /**
617 * @brief Returns @c true if the object holds only an opaque pointer (size == 0 and not owner).
618 *
619 * @details
620 * An object created via @c shallow_copy_ptr() has @c size_ == 0, @c offset_ == 0 and
621 * @c is_owner_ == false. Use @c to_ptr<T>() to retrieve the wrapped pointer.
622 *
623 * @return @c true if this is a pointer-only wrapper.
624 */
625 [[nodiscard]] bool is_ptr() const noexcept;
626
627 /**
628 * @brief Copies the usable byte region into a new @c std::vector<uint8_t>.
629 *
630 * @return A vector containing a copy of @c data()[0..size()-1].
631 */
632 [[nodiscard]] std::vector<uint8_t> to_raw_data() const noexcept;
633
634 /**
635 * @brief Returns the usable byte region as a @c std::string.
636 *
637 * @return A string constructed from @c data() and @c size().
638 */
639 [[nodiscard]] std::string to_string() const noexcept;
640
641 /**
642 * @brief Returns a zero-copy @c std::string_view over the usable byte region.
643 *
644 * @details
645 * The returned view is valid only as long as this @c Bytes object is alive and unmodified.
646 *
647 * @return @c string_view pointing into the buffer.
648 */
649 [[nodiscard]] std::string_view to_string_view() const noexcept;
650
651 /**
652 * @brief Reinterprets the backing buffer as a pointer to @c T.
653 *
654 * @details
655 * Calls @c reinterpret_cast<T*>(real_data()). Use with care; alignment must be
656 * compatible with @c T.
657 *
658 * @tparam T Target type. Defaults to @c void.
659 * @return Pointer to @c T, or @c nullptr if the buffer is empty.
660 */
661 template <typename T = void>
662 [[nodiscard]] T* to_ptr() const noexcept;
663
664 /**
665 * @brief Returns the size of the inline stack storage in bytes.
666 *
667 * @details
668 * Buffers of this size or smaller use the embedded @c stack_data_ array and
669 * never incur a heap allocation.
670 *
671 * @return @c kStackSize (96).
672 */
673 [[nodiscard]] static constexpr uint8_t stack_size() noexcept;
674
675 /**
676 * @brief Returns @c true if the platform uses little-endian byte order.
677 *
678 * @details
679 * Determined at compile time from preprocessor macros (@c __BYTE_ORDER__,
680 * @c __LITTLE_ENDIAN__, Windows defaults).
681 *
682 * @return @c true on little-endian platforms (x86, arm-le, etc.).
683 */
684 [[nodiscard]] static constexpr bool is_little_endian() noexcept;
685
686 /**
687 * @brief Returns @c true if the platform uses big-endian byte order.
688 *
689 * @return @c true on big-endian platforms. Equivalent to @c !is_little_endian().
690 */
691 [[nodiscard]] static constexpr bool is_big_endian() noexcept;
692
693 /**
694 * @brief Checks whether a raw byte buffer contains LZAV-compressed VLink data.
695 *
696 * @details
697 * Inspects the first 4 bytes for the header magic @c {0x17, 0x49, 0xB2, 0x6F}
698 * and the last 4 bytes for the footer magic @c {0xA7, 0x05, 0xED, 0x71}.
699 * Both must match for the function to return @c true.
700 *
701 * @param data Pointer to the buffer to inspect.
702 * @param size Length of the buffer.
703 * @return @c true if header and footer magic are present.
704 */
705 [[nodiscard]] static bool is_compress_data(const uint8_t* data, size_t size) noexcept;
706
707 /**
708 * @brief Compresses a raw byte buffer using the LZAV algorithm.
709 *
710 * @details
711 * Wraps the compressed payload with a 4-byte header magic and a 4-byte footer magic
712 * so that @c is_compress_data() can recognise it. Buffers larger than
713 * @c kMaxCompressCacheSize (1 MiB) are rejected and an empty @c Bytes is returned.
714 *
715 * @param data Pointer to the uncompressed data.
716 * @param size Number of bytes to compress.
717 * @param high_ratio If @c true, uses LZAV high-compression mode (slower but better ratio).
718 * Default: @c false (normal mode).
719 * @return Compressed @c Bytes with header/footer magic, or empty on failure.
720 */
721 [[nodiscard]] static Bytes compress_data(const uint8_t* data, size_t size, bool high_ratio = false) noexcept;
722
723 /**
724 * @brief Decompresses a LZAV-compressed @c Bytes buffer.
725 *
726 * @details
727 * Strips the 4-byte header and footer magic, then calls @c lzav_decompress().
728 * If @p check_valid is @c true the magic bytes are validated first; an invalid
729 * magic returns an empty @c Bytes.
730 *
731 * @param data Pointer to the compressed buffer (including header/footer).
732 * @param size Total size of the compressed buffer.
733 * @param check_valid If @c true, validate the header/footer magic before decompressing.
734 * Default: @c true.
735 * @return Decompressed @c Bytes, or empty on failure.
736 */
737 [[nodiscard]] static Bytes uncompress_data(const uint8_t* data, size_t size, bool check_valid = true) noexcept;
738
739 /**
740 * @brief Releases the buffer and resets the object to the empty state.
741 *
742 * @details
743 * If @c is_owner() is @c true, the backing memory is returned to the pool or heap.
744 * After this call, @c empty() returns @c true.
745 */
746 void clear() noexcept;
747
748 /**
749 * @brief Reduces the logical size of the buffer without reallocating.
750 *
751 * @details
752 * Sets @c size_ to @p size if @p size <= current @c size(); the backing buffer is
753 * not freed or shrunk.
754 *
755 * @param size New logical size in bytes. Must be <= current @c size().
756 * @return @c true on success, @c false if @p size exceeds the current size.
757 */
758 [[nodiscard]] bool shrink_to(size_t size) noexcept;
759
760 /**
761 * @brief Ensures the backing buffer can hold at least @p new_capacity bytes.
762 *
763 * @details
764 * If the current capacity is already >= @p new_capacity this is a no-op.
765 * Otherwise a new buffer is allocated and the existing data is copied.
766 *
767 * @param new_capacity Minimum required capacity in bytes.
768 * @return @c true on success, @c false if allocation failed.
769 */
770 bool reserve(size_t new_capacity) noexcept;
771
772 /**
773 * @brief Resizes the logical data region to @p size bytes.
774 *
775 * @details
776 * If @p size <= current capacity, only @c size_ is updated.
777 * If @p size > current capacity, @c reserve(size) is called first.
778 * Newly added bytes are @b not initialised.
779 *
780 * @param size New desired size in bytes.
781 * @return @c true on success, @c false if reallocation failed.
782 */
783 [[nodiscard]] bool resize(size_t size) noexcept;
784
785 /**
786 * @brief Makes this object a non-owning alias of @p bytes (shallow copy in-place).
787 *
788 * @details
789 * Releases the current buffer, then copies the pointer and metadata from @p bytes
790 * without copying the underlying data. After this call @c is_owner() is @c false.
791 *
792 * @param bytes Source to alias.
793 * @return Reference to @c *this.
794 */
795 Bytes& shallow_copy(const Bytes& bytes) noexcept;
796
797 /**
798 * @brief Replaces this object with a fully owned deep copy of @p bytes.
799 *
800 * @details
801 * Releases the current buffer, allocates new memory, and copies all bytes from
802 * @p bytes. After this call @c is_owner() is @c true.
803 *
804 * @param bytes Source to copy.
805 * @return Reference to @c *this.
806 */
807 Bytes& deep_copy(const Bytes& bytes) noexcept;
808
809 /**
810 * @brief Converts this object from a non-owning alias to a fully owned deep copy of its own data.
811 *
812 * @details
813 * If the object is already an owner, this is a no-op.
814 * Otherwise, new memory is allocated, the current data is copied, and
815 * @c is_owner_ is set to @c true.
816 *
817 * @return Reference to @c *this.
818 */
820
821 /**
822 * @brief Stream insertion operator -- prints bytes as space-separated hex pairs.
823 *
824 * @param ostream Output stream.
825 * @param target @c Bytes to print.
826 * @return Reference to @p ostream.
827 */
828 VLINK_EXPORT friend std::ostream& operator<<(std::ostream& ostream, const Bytes& target) noexcept;
829
830 private:
831 enum Type : uint8_t {
832 kCreate = 0,
833 kShallowCopy = 1,
834 kDeepCopy = 2,
835 kMove = 3,
836 };
837
838 Bytes(Type type, uint8_t* data, size_t size, uint8_t offset, bool loaned) noexcept;
839
840 void process_type(Type type, uint8_t* data, size_t size, uint8_t offset, bool loaned, Bytes* tmp = nullptr) noexcept;
841
842 static constexpr uint8_t kStackSize{96};
843 alignas(std::max_align_t) uint8_t stack_data_[kStackSize]{0};
844 bool is_owner_{false};
845 bool is_loaned_{false};
846 uint8_t offset_{0};
847 uint8_t* data_{nullptr};
848 size_t size_{0};
849 size_t capacity_{0};
850};
851
852////////////////////////////////////////////////////////////////
853/// Details
854////////////////////////////////////////////////////////////////
855
856inline uint8_t& Bytes::operator[](size_t index) noexcept { return data_[offset_ + index]; }
857
858inline const uint8_t& Bytes::operator[](size_t index) const noexcept { return data_[offset_ + index]; }
859
860inline uint8_t* Bytes::data() noexcept { return data_ ? (data_ + offset_) : nullptr; }
861
862inline const uint8_t* Bytes::data() const noexcept { return data_ ? (data_ + offset_) : nullptr; }
863
864inline uint8_t* Bytes::real_data() noexcept { return data_; }
865
866inline const uint8_t* Bytes::real_data() const noexcept { return data_; }
867
868inline size_t Bytes::size() const noexcept { return size_; }
869
870inline size_t Bytes::real_size() const noexcept { return size_ + offset_; }
871
872inline size_t Bytes::capacity() const noexcept { return capacity_; }
873
874inline uint8_t Bytes::offset() const noexcept { return offset_; }
875
876inline bool Bytes::is_owner() const noexcept { return is_owner_; }
877
878inline bool Bytes::is_loaned() const noexcept { return is_loaned_; }
879
880inline bool Bytes::empty() const noexcept { return data_ == nullptr && size_ == 0; }
881
882inline uint8_t* Bytes::begin() noexcept { return data_ ? (data_ + offset_) : nullptr; }
883
884inline const uint8_t* Bytes::begin() const noexcept { return data_ ? (data_ + offset_) : nullptr; }
885
886inline uint8_t* Bytes::end() noexcept { return data_ ? (data_ + offset_ + size_) : nullptr; }
887
888inline const uint8_t* Bytes::end() const noexcept { return data_ ? (data_ + offset_ + size_) : nullptr; }
889
890inline uint8_t* Bytes::real_begin() noexcept { return data_; }
891
892inline const uint8_t* Bytes::real_begin() const noexcept { return data_; }
893
894inline uint8_t* Bytes::real_end() noexcept { return data_ ? (data_ + offset_ + size_) : nullptr; }
895
896inline const uint8_t* Bytes::real_end() const noexcept { return data_ ? (data_ + offset_ + size_) : nullptr; }
897
898inline bool Bytes::is_ptr() const noexcept { return data_ != nullptr && size_ == 0 && offset_ == 0 && !is_owner_; }
899
900template <typename T>
901inline T* Bytes::to_ptr() const noexcept {
902 return reinterpret_cast<T*>(data_);
903}
904
905inline constexpr uint8_t Bytes::stack_size() noexcept { return kStackSize; }
906
907inline constexpr bool Bytes::is_little_endian() noexcept {
908#if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || \
909 (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
910 return true;
911#elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
912 return false;
913#else
914 return true;
915#endif
916}
917
918inline constexpr bool Bytes::is_big_endian() noexcept { return !is_little_endian(); }
919
920} // namespace vlink
Platform-independent macro definitions for the VLink library.
#define VLINK_EXPORT
Definition macros.h:85
STL namespace.