// Glaze Library // For the license information refer to glaze.hpp #pragma once #include "glaze/beve/header.hpp" #include "glaze/core/context.hpp" #include "glaze/util/expected.hpp" namespace glz { // Extension subtypes for beve_header namespace extension { constexpr uint8_t delimiter = 0; // Data delimiter (tag = 0x06) constexpr uint8_t variant = 1; // Variant type (tag = 0x0E), count = variant index constexpr uint8_t complex = 3; // Complex number/array (tag = 0x1E) constexpr uint8_t complex_number = 0; // Single complex (count = 2) constexpr uint8_t complex_array = 1; // Array of complex (count = element count) } // Information extracted from a BEVE header without full deserialization struct beve_header { uint8_t tag{}; // The raw tag byte uint8_t type{}; // Base type: null(0), number(1), string(2), object(3), typed_array(4), generic_array(5), // extensions(6) uint8_t ext_type{}; // For extensions: subtype (extension::variant, extension::complex, etc.) size_t count{}; // Element/member count for containers, string length for strings, 1 for scalars // For variants: the variant index; for complex_number: 2; for complex_array: element count size_t header_size{}; // Total bytes consumed by tag + count encoding (for seeking past header) }; // Internal helper to peek at compressed integer without modifying iterator namespace detail { [[nodiscard]] GLZ_ALWAYS_INLINE constexpr size_t peek_compressed_int_size(const uint8_t* data, size_t available) noexcept { if (available == 0) return 0; const uint8_t config = data[0] & 0b000000'11; return byte_count_lookup[config]; } [[nodiscard]] GLZ_ALWAYS_INLINE constexpr size_t peek_compressed_int_value(const uint8_t* data, size_t available) noexcept { if (available == 0) return 0; const uint8_t header = data[0]; const uint8_t config = header & 0b000000'11; const size_t required = byte_count_lookup[config]; if (available < required) return 0; switch (config) { case 0: return header >> 2; case 1: { uint16_t h; std::memcpy(&h, data, 2); if constexpr (std::endian::native == std::endian::big) { h = std::byteswap(h); } return h >> 2; } case 2: { uint32_t h; std::memcpy(&h, data, 4); if constexpr (std::endian::native == std::endian::big) { h = std::byteswap(h); } return h >> 2; } case 3: { if constexpr (sizeof(size_t) > sizeof(uint32_t)) { uint64_t h; std::memcpy(&h, data, 8); if constexpr (std::endian::native == std::endian::big) { h = std::byteswap(h); } return static_cast(h >> 2); } else { return 0; // 8-byte length encoding not supported on 32-bit systems } } default: return 0; } } } // Peek at a BEVE buffer's header to extract type and count information // without performing full deserialization. // // This is useful for: // - Pre-allocating containers before reading // - Validating buffer structure before committing to deserialization // - Making decisions about how to process incoming data // // Returns the header information on success, or an error_ctx on failure. template [[nodiscard]] expected beve_peek_header(const Buffer& buffer) noexcept { const auto* data = reinterpret_cast(buffer.data()); const size_t size = buffer.size(); if (size == 0) [[unlikely]] { return unexpected(error_ctx{0, error_code::unexpected_end}); } beve_header info{}; info.tag = data[0]; info.type = info.tag & 0b00000'111; switch (info.type) { case tag::null: { // null or boolean - check if it's actually a boolean if ((info.tag & 0b00001'000) != 0) { // It's a boolean info.count = 1; } else { info.count = 0; } info.header_size = 1; break; } case tag::number: { // Single number - count is always 1 info.count = 1; info.header_size = 1; break; } case tag::string: { // String: tag + compressed_int(length) if (size < 2) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } const size_t int_size = detail::peek_compressed_int_size(data + 1, size - 1); if (int_size == 0 || size < 1 + int_size) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } info.count = detail::peek_compressed_int_value(data + 1, size - 1); info.header_size = 1 + int_size; break; } case tag::object: { // Object: tag + compressed_int(member_count) if (size < 2) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } const size_t int_size = detail::peek_compressed_int_size(data + 1, size - 1); if (int_size == 0 || size < 1 + int_size) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } info.count = detail::peek_compressed_int_value(data + 1, size - 1); info.header_size = 1 + int_size; break; } case tag::typed_array: { // Check for aligned typed array (category 3, sub-type 2) if (info.tag == tag::aligned_typed_array) { // Aligned typed array: aligned_header + numeric_header + compressed_int(count) + padding_length if (size < 3) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } // data[1] is the numeric header byte const size_t int_size = detail::peek_compressed_int_size(data + 2, size - 2); if (int_size == 0 || size < 2 + int_size + 1) [[unlikely]] { return unexpected(error_ctx{2, error_code::unexpected_end}); } info.count = detail::peek_compressed_int_value(data + 2, size - 2); info.header_size = 2 + int_size + 1; // +1 for padding_length byte } else { // Standard typed array: tag + compressed_int(element_count) if (size < 2) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } const size_t int_size = detail::peek_compressed_int_size(data + 1, size - 1); if (int_size == 0 || size < 1 + int_size) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } info.count = detail::peek_compressed_int_value(data + 1, size - 1); info.header_size = 1 + int_size; } break; } case tag::generic_array: { // Generic array: tag + compressed_int(element_count) if (size < 2) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } const size_t int_size = detail::peek_compressed_int_size(data + 1, size - 1); if (int_size == 0 || size < 1 + int_size) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } info.count = detail::peek_compressed_int_value(data + 1, size - 1); info.header_size = 1 + int_size; break; } case tag::extensions: { // Extensions: delimiter, variant, or complex // Subtype is encoded in bits 3-4 of the tag const uint8_t subtype = (info.tag >> 3) & 0b11; info.ext_type = subtype; if (subtype == extension::delimiter) { // Delimiter (tag = 0x06): just a separator marker info.count = 0; info.header_size = 1; } else if (subtype == extension::variant) { // Variant (tag = 0x0E): tag + compressed_int(index) + value if (size < 2) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } const size_t int_size = detail::peek_compressed_int_size(data + 1, size - 1); if (int_size == 0 || size < 1 + int_size) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } info.count = detail::peek_compressed_int_value(data + 1, size - 1); // variant index info.header_size = 1 + int_size; } else if (subtype == extension::complex) { // Complex (tag = 0x1E): tag + complex_header + ... if (size < 2) [[unlikely]] { return unexpected(error_ctx{1, error_code::unexpected_end}); } const uint8_t complex_header = data[1]; const bool is_array = (complex_header & 1) == extension::complex_array; if (is_array) { // Complex array: tag + complex_header + compressed_int(count) + data if (size < 3) [[unlikely]] { return unexpected(error_ctx{2, error_code::unexpected_end}); } const size_t int_size = detail::peek_compressed_int_size(data + 2, size - 2); if (int_size == 0 || size < 2 + int_size) [[unlikely]] { return unexpected(error_ctx{2, error_code::unexpected_end}); } info.count = detail::peek_compressed_int_value(data + 2, size - 2); info.header_size = 2 + int_size; } else { // Single complex number: tag + complex_header + real + imag info.count = 2; // real + imag parts info.header_size = 2; } } else { // Unknown extension subtype return unexpected(error_ctx{0, error_code::syntax_error}); } break; } default: { return unexpected(error_ctx{0, error_code::syntax_error}); } } return info; } // Convenience overload for C-style arrays and raw pointers with size [[nodiscard]] inline expected beve_peek_header(const void* data, size_t size) noexcept { return beve_peek_header(std::string_view{reinterpret_cast(data), size}); } // Peek at a BEVE buffer's header at a specific byte offset. // // This avoids creating substrings or slicing the buffer when you need to // inspect headers at arbitrary positions. Useful for: // - Buffers with custom headers/prefixes before the BEVE data // - Memory-mapped files where you seek to specific positions // - Resuming parsing after partial reads // - Concatenated/delimited BEVE streams // - BEVE data embedded within larger binary structures // // Parameters: // buffer - The buffer containing BEVE data // offset - The byte offset at which to start peeking // // Returns the header information on success, or an error_ctx on failure. // The header_size in the result is relative to the offset position. template [[nodiscard]] expected beve_peek_header_at(const Buffer& buffer, size_t offset) noexcept { const size_t size = buffer.size(); if (offset >= size) [[unlikely]] { return unexpected(error_ctx{offset, error_code::unexpected_end}); } const auto* data = reinterpret_cast(buffer.data()) + offset; const size_t remaining = size - offset; return beve_peek_header(std::string_view{reinterpret_cast(data), remaining}); } // Convenience overload for raw pointers with size and offset [[nodiscard]] inline expected beve_peek_header_at(const void* data, size_t size, size_t offset) noexcept { if (offset >= size) [[unlikely]] { return unexpected(error_ctx{offset, error_code::unexpected_end}); } return beve_peek_header(std::string_view{reinterpret_cast(data) + offset, size - offset}); } }