// Glaze Library // For the license information refer to glaze.hpp #pragma once #include #include #include #include #include #include #include #include #include "glaze/bson/header.hpp" #include "glaze/bson/skip.hpp" #include "glaze/core/opts.hpp" #include "glaze/core/read.hpp" #include "glaze/core/reflect.hpp" #include "glaze/core/to.hpp" #include "glaze/file/file_ops.hpp" #include "glaze/util/for_each.hpp" #include "glaze/util/uuid.hpp" #include "glaze/util/variant.hpp" // BSON reader — https://bsonspec.org/spec.html // // BSON values do not carry a self-describing tag byte; the element type lives // one level up, in the containing document. The document reader therefore // reads `type_byte | cstring key | value` triples and supplies the tag to the // per-type parser via `from::op(value, tag, ctx, it, end)`. namespace glz { namespace bson_detail { // RAII guard bumping ctx.depth on entry to a document/array reader and // popping on exit. Errors with exceeded_max_recursive_depth before the // stack can overflow on adversarial input — pathologically nested BSON // (e.g. `{"a": {"a": {...}}}`) is cheap to produce and a real DoS vector. template struct depth_guard { Ctx& ctx; bool entered = false; depth_guard(Ctx& c) noexcept : ctx(c) { if (ctx.depth >= max_recursive_depth_limit) [[unlikely]] { ctx.error = error_code::exceeded_max_recursive_depth; return; } ++ctx.depth; entered = true; } ~depth_guard() { if (entered) --ctx.depth; } explicit operator bool() const noexcept { return entered; } }; // --- Little-endian readers ---------------------------------------------- // // On little-endian hosts these compile to a single aligned load. On big- // endian hosts we byteswap first. template GLZ_ALWAYS_INLINE bool read_le(is_context auto& ctx, It& it, const End& end, T& out) noexcept { if (static_cast(end - it) < sizeof(T)) [[unlikely]] { ctx.error = error_code::unexpected_end; return false; } T v{}; std::memcpy(&v, it, sizeof(T)); if constexpr (std::endian::native == std::endian::big) { v = static_cast(std::byteswap(static_cast>(v))); } it += sizeof(T); out = v; return true; } template GLZ_ALWAYS_INLINE bool read_le_double(is_context auto& ctx, It& it, const End& end, double& out) noexcept { uint64_t bits{}; if (!read_le(ctx, it, end, bits)) return false; out = std::bit_cast(bits); return true; } // Return a string_view over a cstring in the buffer. Advances `it` to // just past the trailing 0x00. template GLZ_ALWAYS_INLINE bool read_cstring(is_context auto& ctx, It& it, const End& end, std::string_view& out) noexcept { auto start = it; while (it < end) { if (*it == 0) { out = std::string_view{reinterpret_cast(static_cast(start)), static_cast(it - start)}; ++it; return true; } ++it; } ctx.error = error_code::unexpected_end; return false; } // Read a BSON string value: int32(len) | UTF-8 | 0x00. `out` points into // the input buffer; lifetime matches the buffer. template GLZ_ALWAYS_INLINE bool read_bson_string(is_context auto& ctx, It& it, const End& end, std::string_view& out) noexcept { int32_t len{}; if (!read_le(ctx, it, end, len)) return false; if (len < 1) [[unlikely]] { // Length must include the trailing null. ctx.error = error_code::syntax_error; return false; } const size_t payload_bytes = static_cast(len) - 1; // Drop the trailing null. if (static_cast(end - it) < static_cast(len)) [[unlikely]] { ctx.error = error_code::unexpected_end; return false; } if (it[payload_bytes] != 0) [[unlikely]] { ctx.error = error_code::syntax_error; return false; } out = std::string_view{reinterpret_cast(static_cast(it)), payload_bytes}; it += len; return true; } // Helper — read a BSON document's 4-byte length and compute the // terminating position (`stop`) such that a correct document consumes // exactly `stop - doc_start` bytes ending in 0x00 at `stop - 1`. template GLZ_ALWAYS_INLINE bool read_document_stop(is_context auto& ctx, It& it, const End& end, It& stop) noexcept { auto doc_start = it; int32_t len{}; if (!read_le(ctx, it, end, len)) return false; if (len < 5) [[unlikely]] { // Minimum legal document: 4-byte length + 0x00 terminator. ctx.error = error_code::syntax_error; return false; } if (static_cast(end - doc_start) < static_cast(len)) [[unlikely]] { ctx.error = error_code::unexpected_end; return false; } stop = doc_start + len; return true; } // Confirm the trailing 0x00 of a document is where we expect it, then // advance past it. template GLZ_ALWAYS_INLINE bool finish_document(is_context auto& ctx, It& it, const End& /*end*/, It stop) noexcept { // Caller guarantees it <= stop. After the element loop the document // reader expects the null terminator at `stop - 1` and `it` pointing // there. if (it != stop - 1) [[unlikely]] { ctx.error = error_code::syntax_error; return false; } if (*it != 0) [[unlikely]] { ctx.error = error_code::syntax_error; return false; } ++it; return true; } } // namespace bson_detail // Top-level dispatcher. BSON's top-level is always a document, so the // implicit tag is bson::type::document. Per-element dispatch inside a // document is handled by the struct/array reader calling `from::op` directly with the element's tag. template <> struct parse { template GLZ_ALWAYS_INLINE static void op(T&& value, Ctx&& ctx, It& it, const End& end) noexcept { if constexpr (const_value_v) { if constexpr (check_error_on_const_read(Opts)) { ctx.error = error_code::attempt_const_read; } else { skip_value::template op(bson::type::document, ctx, it, end); } return; } using V = std::remove_cvref_t; // Top-level BSON has no element tag byte on the wire; a top-level // array is byte-for-byte identical to a document. Dispatch with the // tag the target type's reader expects so array-shaped top-level // targets (std::array, std::vector, std::tuple, glaze_array_t) route // to the array reader instead of tripping its tag check. constexpr uint8_t top_tag = (writable_array_t || glaze_array_t) ? bson::type::array : bson::type::document; from::template op(std::forward(value), top_tag, ctx, it, end); } }; // ========================================================================== // Primitive readers. Each from receives the element tag from the // enclosing document and reads the corresponding value bytes. // ========================================================================== // --- Boolean -------------------------------------------------------------- template <> struct from { template GLZ_ALWAYS_INLINE static void op(bool& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::boolean) [[unlikely]] { ctx.error = error_code::syntax_error; return; } if (it >= end) [[unlikely]] { ctx.error = error_code::unexpected_end; return; } value = (*it++ != 0); } }; // --- Integers ------------------------------------------------------------- // // Accept both int32 (0x10) and int64 (0x12) tags regardless of the C++ // target width — the usual coercion rules apply: error on out-of-range. template requires(std::integral && !std::same_as) struct from { template static void op(T& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag == bson::type::int32) { int32_t v{}; if (!bson_detail::read_le(ctx, it, end, v)) return; // Reject negative wire values into any unsigned target (covers // uint32_t, uint64_t, and the smaller widths uniformly). if constexpr (!std::is_signed_v) { if (v < 0) [[unlikely]] { ctx.error = error_code::parse_number_failure; return; } } // Narrowing bounds check only when T is strictly narrower than // int32. For sizeof(T) == 4 && unsigned (uint32_t), the non-negative // check above plus the int32 input width guarantee the value fits. if constexpr (sizeof(T) < 4) { if (v < static_cast(std::numeric_limits::min()) || v > static_cast(std::numeric_limits::max())) [[unlikely]] { ctx.error = error_code::parse_number_failure; return; } } value = static_cast(v); return; } if (tag == bson::type::int64) { int64_t v{}; if (!bson_detail::read_le(ctx, it, end, v)) return; // Narrow to T with range check. uint64_t target: accept only // non-negative values. if constexpr (std::is_same_v) { if (v < 0) [[unlikely]] { ctx.error = error_code::parse_number_failure; return; } value = static_cast(v); return; } else if constexpr (sizeof(T) <= 8) { if constexpr (std::is_signed_v) { if (v < static_cast(std::numeric_limits::min()) || v > static_cast(std::numeric_limits::max())) [[unlikely]] { ctx.error = error_code::parse_number_failure; return; } } else { if (v < 0 || static_cast(v) > static_cast(std::numeric_limits::max())) [[unlikely]] { ctx.error = error_code::parse_number_failure; return; } } value = static_cast(v); return; } } ctx.error = error_code::syntax_error; } }; // --- Floating point ------------------------------------------------------- // // BSON double (0x01) is the native mapping; integer tags are accepted as a // widening conversion so `double x` can read an int32/int64 field. template struct from { template static void op(T& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag == bson::type::double_) { double d{}; if (!bson_detail::read_le_double(ctx, it, end, d)) return; value = static_cast(d); return; } if (tag == bson::type::int32) { int32_t v{}; if (!bson_detail::read_le(ctx, it, end, v)) return; value = static_cast(v); return; } if (tag == bson::type::int64) { int64_t v{}; if (!bson_detail::read_le(ctx, it, end, v)) return; value = static_cast(v); return; } ctx.error = error_code::syntax_error; } }; // --- Enums ---------------------------------------------------------------- template requires(std::is_enum_v) struct from { template GLZ_ALWAYS_INLINE static void op(T& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { using U = std::underlying_type_t; U u{}; from::template op(u, tag, ctx, it, end); if (bool(ctx.error)) return; value = static_cast(u); } }; // --- Strings -------------------------------------------------------------- template struct from { template static void op(auto& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::string) [[unlikely]] { ctx.error = error_code::syntax_error; return; } std::string_view sv{}; if (!bson_detail::read_bson_string(ctx, it, end, sv)) return; if constexpr (requires { value.assign(sv.data(), sv.size()); }) { value.assign(sv.data(), sv.size()); } else { // Non-assignable targets (notably std::string_view) alias into // the input buffer — the caller must keep the buffer alive as // long as the view is used. See docs/bson.md "Strings". value = sv; } } }; // --- Nullable / optional -------------------------------------------------- template struct from { template static void op(auto& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag == bson::type::null) { if constexpr (requires { value.reset(); }) { value.reset(); } else { value = T{}; } return; } if (!value) { if constexpr (requires { value.emplace(); }) { value.emplace(); } else if constexpr (std::is_constructible_v) { value = typename T::value_type{}; } else { ctx.error = error_code::invalid_nullable_read; return; } } using Inner = std::remove_cvref_t; from::template op(*value, tag, ctx, it, end); } }; // --- always_null_t (std::monostate, std::nullptr_t, std::nullopt_t) ------- // // These carry no state and never appear as top-level BSON values, but are // reachable as variant alternatives. The tag is always bson::type::null // when the variant reader routes here; anything else is a wire error. template struct from { template static void op(auto&&, uint8_t tag, is_context auto& ctx, It&, const End&) noexcept { if (tag != bson::type::null) [[unlikely]] { ctx.error = error_code::syntax_error; } } }; // --- BSON-native helper types -------------------------------------------- template <> struct from { template GLZ_ALWAYS_INLINE static void op(bson::object_id& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::object_id) [[unlikely]] { ctx.error = error_code::syntax_error; return; } if (static_cast(end - it) < 12) [[unlikely]] { ctx.error = error_code::unexpected_end; return; } std::memcpy(value.bytes.data(), it, 12); it += 12; } }; template <> struct from { template GLZ_ALWAYS_INLINE static void op(bson::datetime& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::datetime) [[unlikely]] { ctx.error = error_code::syntax_error; return; } (void)bson_detail::read_le(ctx, it, end, value.ms_since_epoch); } }; template <> struct from { template GLZ_ALWAYS_INLINE static void op(bson::timestamp& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::timestamp) [[unlikely]] { ctx.error = error_code::syntax_error; return; } if (!bson_detail::read_le(ctx, it, end, value.increment)) return; (void)bson_detail::read_le(ctx, it, end, value.seconds); } }; template <> struct from { template static void op(bson::regex& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::regex) [[unlikely]] { ctx.error = error_code::syntax_error; return; } std::string_view pat{}; std::string_view opt{}; if (!bson_detail::read_cstring(ctx, it, end, pat)) return; if (!bson_detail::read_cstring(ctx, it, end, opt)) return; value.pattern.assign(pat); value.options.assign(opt); } }; template <> struct from { template static void op(bson::javascript& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::javascript) [[unlikely]] { ctx.error = error_code::syntax_error; return; } std::string_view sv{}; if (!bson_detail::read_bson_string(ctx, it, end, sv)) return; value.code.assign(sv); } }; template <> struct from { template static void op(bson::decimal128& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::decimal128) [[unlikely]] { ctx.error = error_code::syntax_error; return; } if (static_cast(end - it) < 16) [[unlikely]] { ctx.error = error_code::unexpected_end; return; } std::memcpy(value.bytes.data(), it, 16); it += 16; } }; template <> struct from { template static void op(bson::min_key&, uint8_t tag, is_context auto& ctx, It&, const End&) noexcept { if (tag != bson::type::min_key) [[unlikely]] { ctx.error = error_code::syntax_error; } } }; template <> struct from { template static void op(bson::max_key&, uint8_t tag, is_context auto& ctx, It&, const End&) noexcept { if (tag != bson::type::max_key) [[unlikely]] { ctx.error = error_code::syntax_error; } } }; template struct from> { template static void op(bson::binary& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::binary) [[unlikely]] { ctx.error = error_code::syntax_error; return; } int32_t outer_len{}; if (!bson_detail::read_le(ctx, it, end, outer_len)) return; if (outer_len < 0) [[unlikely]] { ctx.error = error_code::syntax_error; return; } if (it >= end) [[unlikely]] { ctx.error = error_code::unexpected_end; return; } value.subtype = static_cast(*it++); int32_t payload_len = outer_len; if (value.subtype == bson::binary_subtype::binary_old) [[unlikely]] { // Spec: 0x02 wraps the payload in a redundant inner int32 length. // Outer length must equal 4 + inner. if (outer_len < 4) [[unlikely]] { ctx.error = error_code::syntax_error; return; } int32_t inner_len{}; if (!bson_detail::read_le(ctx, it, end, inner_len)) return; if (inner_len < 0 || inner_len != outer_len - 4) [[unlikely]] { ctx.error = error_code::syntax_error; return; } payload_len = inner_len; } if (static_cast(end - it) < static_cast(payload_len)) [[unlikely]] { ctx.error = error_code::unexpected_end; return; } value.data.resize(static_cast(payload_len)); if (payload_len) { std::memcpy(value.data.data(), it, static_cast(payload_len)); it += payload_len; } } }; // --- glz::uuid ← binary subtype 0x04 ------------------------------------- template <> struct from { template static void op(uuid& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::binary) [[unlikely]] { ctx.error = error_code::syntax_error; return; } int32_t len{}; if (!bson_detail::read_le(ctx, it, end, len)) return; if (len != 16) [[unlikely]] { ctx.error = error_code::syntax_error; return; } if (it >= end) [[unlikely]] { ctx.error = error_code::unexpected_end; return; } const uint8_t subtype = static_cast(*it++); // Only accept canonical subtype 0x04 (RFC 9562 byte order). Legacy // 0x03 (uuid_old) is rejected because its bytes are ambiguous: Java, // C#, and Python drivers each laid them out differently, so decoding // without the origin flavor silently yields scrambled bytes. if (subtype != bson::binary_subtype::uuid) [[unlikely]] { ctx.error = error_code::syntax_error; return; } if (static_cast(end - it) < 16) [[unlikely]] { ctx.error = error_code::unexpected_end; return; } std::memcpy(value.bytes.data(), it, 16); it += 16; } }; // --- std::chrono::system_clock::time_point ← datetime -------------------- template <> struct from { template GLZ_ALWAYS_INLINE static void op(std::chrono::system_clock::time_point& value, uint8_t tag, is_context auto& ctx, It& it, const End& end) noexcept { if (tag != bson::type::datetime) [[unlikely]] { ctx.error = error_code::syntax_error; return; } int64_t ms{}; if (!bson_detail::read_le(ctx, it, end, ms)) return; value = std::chrono::system_clock::time_point{std::chrono::milliseconds{ms}}; } }; // ========================================================================== // Variant — BSON-style auto-deduction from the element tag byte. // // Mirrors the JSONB pattern: each BSON wire type maps to a category trait, // and for each category the writer picks the first variant alternative // matching that trait. This requires at most one alternative per category, // enforced by a static_assert. Users needing multiple alternatives sharing // a BSON type (e.g., two distinct document shapes) must drop one or switch // to an explicit tag-based variant convention (not yet supported here). // // The writer serializes the active alternative directly (its type byte and // key are already on the wire from the enclosing document), so round-trip // works as long as the alternative category is unique. // ========================================================================== namespace bson_detail { // --- Category traits --------------------------------------------------- template struct is_bson_variant_null : std::bool_constant> {}; template struct is_bson_variant_bool : std::bool_constant> {}; template struct is_bson_variant_int : std::bool_constant> && !bool_t> {}; template struct is_bson_variant_float : std::bool_constant>> {}; template struct is_bson_variant_string : std::bool_constant> {}; // BSON helper types: these are reflectable aggregates but have dedicated // wire tags, so they must NOT be classified as generic documents. Listed // ahead of the document/array traits so those can exclude them. template struct is_bson_variant_binary : std::false_type {}; template struct is_bson_variant_binary> : std::true_type {}; template struct is_bson_variant_uuid : std::bool_constant, uuid>> {}; template struct is_bson_variant_object_id : std::bool_constant, bson::object_id>> {}; template struct is_bson_variant_datetime : std::bool_constant, bson::datetime> || std::same_as, std::chrono::system_clock::time_point>> {}; template struct is_bson_variant_timestamp : std::bool_constant, bson::timestamp>> {}; template struct is_bson_variant_regex : std::bool_constant, bson::regex>> {}; template struct is_bson_variant_javascript : std::bool_constant, bson::javascript>> {}; template struct is_bson_variant_decimal128 : std::bool_constant, bson::decimal128>> {}; template struct is_bson_variant_min_key : std::bool_constant, bson::min_key>> {}; template struct is_bson_variant_max_key : std::bool_constant, bson::max_key>> {}; // True if T is one of the BSON helper types above. Used to exclude them // from the generic document / array categories. template struct is_bson_helper_type : std::bool_constant::value || is_bson_variant_uuid::value || is_bson_variant_object_id::value || is_bson_variant_datetime::value || is_bson_variant_timestamp::value || is_bson_variant_regex::value || is_bson_variant_javascript::value || is_bson_variant_decimal128::value || is_bson_variant_min_key::value || is_bson_variant_max_key::value> {}; // Matches reflected structs, reflectable aggregates, and string-keyed maps. // Excludes arrays (which land in their own category), strings, and the BSON // helper types (which are reflectable aggregates but have dedicated tags). template struct is_bson_variant_document : std::bool_constant<(glaze_object_t || reflectable || writable_map_t) && !writable_array_t && !str_t && !is_bson_helper_type::value> {}; template struct is_bson_variant_array : std::bool_constant<(writable_array_t || glaze_array_t) && !str_t && !writable_map_t && !is_bson_helper_type::value> {}; // --- first_matching_index : finds the first alternative index // whose alternative type satisfies Trait, or std::variant_npos. template class Trait> struct first_matching_index_impl; template