diff --git a/examples/bsoncxx/bson_binary_vector.cpp b/examples/bsoncxx/bson_binary_vector.cpp new file mode 100644 index 0000000000..0a427cf9b9 --- /dev/null +++ b/examples/bsoncxx/bson_binary_vector.cpp @@ -0,0 +1,205 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +int EXAMPLES_CDECL main() { + using bsoncxx::binary_sub_type; + using bsoncxx::builder::basic::kvp; + using bsoncxx::builder::basic::make_document; + using bsoncxx::builder::basic::sub_binary; + using bsoncxx::vector::accessor; + using bsoncxx::vector::formats::f_float32; + using bsoncxx::vector::formats::f_int8; + using bsoncxx::vector::formats::f_packed_bit; + + bsoncxx::document::value doc = make_document( + + // + // Added along with BSON Binary Vector support, the new sub_binary builder + // allows allocating any type of BSON Binary item in-place. A callback taking + // a sub_binary argument can calculate the required space before calling allocate() + // on the sub_binary to get a pointer to the new in-place allocation. + // + // Every byte of the allocated binary region must be written or the resulting BSON + // will have undefined contents. If allocate() isn't called exactly once, + // an exception will be thrown. + // + kvp("binary", + [&](sub_binary sbin) { + uint32_t len = 10; + uint8_t* data = sbin.allocate(binary_sub_type::k_binary, len); + memset(data, 0x55, len); + }), + + // + // The sub_binary also provides an allocate() method for BSON Binary Vector. + // Instead of a sub_type and byte length, this takes a vector format + // and an element count. + // + // This example uses the f_int8 vector format, which has int8_t elements. + // The allocate() call here returns a bsoncxx::vector::accessor instance + // that works like a random access container but does not own memory directly. + // + kvp("vector_int8", + [&](sub_binary sbin) { + auto vec = sbin.allocate(f_int8{}, 10); + int8_t i = -5; + std::generate(vec.begin(), vec.end(), [&] { return ++i; }); + }), + + // + // BSON Binary Vector supports formats that do not map directly to C++ + // built-in types. The f_float32 format is an unaligned little endian + // serialization of IEEE 754 32-bit binary floating point. On some platforms, + // this sort of data could be accessed using a raw float*, but for consistent + // portability we have a bsoncxx::v_noabi::vector::elements::float32 type which + // has the unaligned little-endian representation in memory but supports automatic + // conversion to and from 'float'. + // + // The vector accessor works like a container of floats. Elements can be assigned + // from float expressions or used as float expressions. Assignment operators + // operate by automatically convering to float and then back to elements::float32. + // + kvp("vector_float32", + [&](sub_binary sbin) { + auto vec = sbin.allocate(f_float32{}, 10); + // Calculate a fibonacci sequence starting near the smallest representable value + vec[0] = 0.f; + vec[1] = 1e-38f; + for (size_t i = 2; i < vec.size(); i++) { + vec[i] = vec[i - 1] + vec[i - 2]; + } + // Demonstrate assignment operators + vec[0] += 1.f; + vec[1] *= 1e38f; + vec[1] /= 2.f; + vec[1] -= 1.f + vec[0]; + }), + + // + // packed_bit vectors support any number of single-bit elements, + // using an accessor that works like a random-access container of + // bool values. This works using a reference-proxy type + // bsoncxx::v_noabi::vector::elements::packed_bit_element and an iterator + // bsoncxx::v_noabi::vector::iterators::packed_bit_element. + // + // Every bsoncxx::vector::accessor can be accessed either in per-element + // or per-byte mode. Byte mode is particularly useful for applications that + // may want to use packed_bit vectors in the serialized format without + // accessing individual elements. + // + kvp("vector_packed_bit", [&](sub_binary sbin) { + auto vec = sbin.allocate(f_packed_bit{}, 61); + // Start by setting all bits to 1 + std::fill(vec.begin(), vec.end(), true); + // Flip a bit using a boolean expression + vec[5] = !vec[5]; + // Assignment of a packed_bit_element reference copies the referenced bit value + vec[6] = vec[1]; + vec[7] = vec[5]; + // Bits can be assigned from boolean expressions, and from zero. + vec[8] = 0; + vec[60] = false; + // Demonstrate addressing bits backward from the end of the vector + std::fill(vec.end() - 20, vec.end() - 4, false); + std::fill(vec.end() - 8, vec.end() - 5, true); + // Flip all bits, operating an entire byte at a time. + // The last byte will have bits that do not correspond to any elements, and writes to these are ignored. + for (auto i = vec.byte_begin(); i != vec.byte_end(); i++) { + *i ^= 0xFF; + } + // Demonstrate copying bit ranges and byte ranges using std::copy + std::copy(vec.byte_begin(), vec.byte_begin() + 2, vec.byte_begin() + 2); + std::copy(vec.begin() + 5, vec.begin() + 9, vec.begin() + 56); + })); + + // Demonstrate extended JSON serialization of the entire document + std::cout << bsoncxx::to_json(doc) << std::endl; + + // Iterate over elements in the int8 vector + { + accessor vec{doc["vector_int8"].get_binary()}; + std::cout << "int8: " << vec.size() << std::endl; + for (auto&& i : vec) { + std::cout << int{i} << " "; + } + std::cout << std::endl; + } + + // Iterate over bytes in the int8 vector + { + accessor vec{doc["vector_int8"].get_binary()}; + std::cout << "int8 bytes: " << vec.byte_size() << std::hex << std::endl; + for (auto i = vec.byte_begin(); i != vec.byte_end(); i++) { + std::cout << int{*i} << " "; + } + std::cout << std::dec << std::endl; + } + + // Iterate over elements in the float32 vector + { + accessor vec{doc["vector_float32"].get_binary()}; + std::cout << "float32: " << vec.size() << std::endl; + for (auto&& i : vec) { + std::cout << i << " "; + } + std::cout << std::endl; + } + + // Iterate over bytes in the float32 vector + { + accessor vec{doc["vector_float32"].get_binary()}; + std::cout << "float32 bytes: " << vec.byte_size() << std::hex << std::endl; + for (auto i = vec.byte_begin(); i != vec.byte_end(); i++) { + std::cout << int{*i} << " "; + } + std::cout << std::dec << std::endl; + } + + // Iterate over elements in the packed_bit vector + { + accessor vec{doc["vector_packed_bit"].get_binary()}; + std::cout << "packed_bit: " << vec.size() << std::endl; + for (auto&& i : vec) { + std::cout << i << " "; + } + std::cout << std::endl; + } + + // Iterate over bytes in the packed_bit vector + { + accessor vec{doc["vector_packed_bit"].get_binary()}; + std::cout << "packed_bit bytes: " << vec.byte_size() << std::hex << std::endl; + for (auto i = vec.byte_begin(); i != vec.byte_end(); i++) { + std::cout << int{*i} << " "; + } + std::cout << std::dec << std::endl; + } + + return 0; +} diff --git a/src/bsoncxx/include/bsoncxx/docs/top.hpp b/src/bsoncxx/include/bsoncxx/docs/top.hpp index 3d4530d9fe..fa23e0d442 100644 --- a/src/bsoncxx/include/bsoncxx/docs/top.hpp +++ b/src/bsoncxx/include/bsoncxx/docs/top.hpp @@ -88,3 +88,13 @@ /// @namespace bsoncxx::types::bson_value /// Declares entities representing any BSON value type. /// + +/// +/// @namespace bsoncxx::vector +/// Declarations related to the BSON Binary Vector subtype. +/// + +/// +/// @namespace bsoncxx::vector::formats +/// Declares supported BSON Binary Vector formats. +/// diff --git a/src/bsoncxx/include/bsoncxx/docs/v_noabi.hpp b/src/bsoncxx/include/bsoncxx/docs/v_noabi.hpp index a9275ae284..0262be0c8d 100644 --- a/src/bsoncxx/include/bsoncxx/docs/v_noabi.hpp +++ b/src/bsoncxx/include/bsoncxx/docs/v_noabi.hpp @@ -96,6 +96,11 @@ /// Provides headers declaring entities in @ref bsoncxx::v_noabi::types::bson_value. /// +/// +/// @dir bsoncxx/v_noabi/bsoncxx/vector +/// Provides headers declaring entities in @ref bsoncxx::v_noabi::vector. +/// + /// /// @namespace bsoncxx::v_noabi /// Declares entities whose ABI stability is NOT guaranteed. @@ -148,3 +153,23 @@ /// @namespace bsoncxx::v_noabi::types::bson_value /// Declares entities representing any BSON value type. /// + +/// +/// @namespace bsoncxx::v_noabi::vector +/// @copydoc bsoncxx::vector +/// + +/// +/// @namespace bsoncxx::v_noabi::vector::formats +/// @copydoc bsoncxx::vector::formats +/// + +/// +/// @namespace bsoncxx::v_noabi::vector::elements +/// Declares element accessor types for BSON Binary Vector. +/// + +/// +/// @namespace bsoncxx::v_noabi::vector::iterators +/// Declares iterator types for BSON Binary Vector. +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/document.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/document.hpp index e5212419b0..e5dd3af672 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/document.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/document.hpp @@ -14,9 +14,12 @@ #pragma once -#include #include +// + +#include + #include #include #include diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/impl.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/impl.hpp index 578a41ae7e..a7c50ff812 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/impl.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/impl.hpp @@ -15,6 +15,7 @@ #pragma once #include +#include #include #include @@ -29,21 +30,31 @@ namespace impl { template detail::requires_t> generic_append(core* core, T&& func) { core->open_document(); - detail::invoke(std::forward(func), sub_document(core)); + detail::invoke(std::forward(func), sub_document{core}); core->close_document(); } template // placeholder 'void' for VS2015 compat detail::requires_t> generic_append(core* core, T&& func) { core->open_array(); - detail::invoke(std::forward(func), sub_array(core)); + detail::invoke(std::forward(func), sub_array{core}); core->close_array(); } +template +detail::requires_t> generic_append(core* core, T&& func) { + // Opened by the user invoking `sub_binary::allocate()` in `func`. + detail::invoke(std::forward(func), sub_binary{core}); + core->close_binary(); +} + template -detail::requires_not_t, detail::is_invocable> generic_append( - core* core, - T&& t) { +detail::requires_not_t< + void, + detail::is_invocable, + detail::is_invocable, + detail::is_invocable> +generic_append(core* core, T&& t) { core->append(std::forward(t)); } diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/sub_binary-fwd.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/sub_binary-fwd.hpp new file mode 100644 index 0000000000..6056685462 --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/sub_binary-fwd.hpp @@ -0,0 +1,42 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +namespace bsoncxx { +namespace v_noabi { +namespace builder { +namespace basic { + +class sub_binary; + +} // namespace basic +} // namespace builder +} // namespace v_noabi +} // namespace bsoncxx + +namespace bsoncxx { +namespace builder { +namespace basic { + +using ::bsoncxx::v_noabi::builder::basic::sub_binary; + +} // namespace basic +} // namespace builder +} // namespace bsoncxx + +/// +/// @file +/// Declares @ref bsoncxx::v_noabi::builder::basic::sub_binary +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/sub_binary.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/sub_binary.hpp new file mode 100644 index 0000000000..f86162c29c --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/basic/sub_binary.hpp @@ -0,0 +1,89 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +// + +#include + +#include +#include +#include + +#include +#include + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace builder { +namespace basic { + +/// +/// Represents a BSON Binary element being constructed during an append operation. +/// +class sub_binary { + public: + /// + /// Default constructor + /// + sub_binary(core* core) : _core{core} {} + + /// @brief Allocate space for an un-initialized BSON Binary element of any subtype. + /// @param sub_type BSON binary subtype code, identifying the format of the data within. + /// @param length Number of bytes to allocate + /// @return Pointer to uninitialized memory within the bson_t, valid only during this sub_binary builder's lifetime. + /// The caller must overwrite every byte if the resulting BSON document is to be used. + /// @throws bsoncxx::v_noabi::exception if this sub_binary has already allocated. + /// @throws bsoncxx::v_noabi::exception if the binary fails to append due to the BSON size limit. + std::uint8_t* allocate(binary_sub_type sub_type, std::uint32_t length) { + return _core->append(sub_type, length); + } + + /// @brief Allocate and format space for a BSON Binary Vector with uninitialized elements. + /// @param fmt Instance of a format type from @ref bsoncxx::v_noabi::vector::formats + /// @param element_count Number of elements to allocate space for. + /// @return A writable vector::accessor, valid during the lifetime of this sub_binary builder. Every element must be + /// overwritten before that element is read or the resulting document is used. + /// @throws bsoncxx::v_noabi::exception if this sub_binary has already allocated. + /// @throws bsoncxx::v_noabi::exception if the binary fails to append due to the BSON size limit. + /// @throws bsoncxx::v_noabi::exception if a vector of the requested size would be too large to represent. + template ::value_type> + vector::accessor allocate(Format fmt, std::size_t element_count) { + (void)fmt; + std::uint32_t binary_data_length = Format::length_for_append(element_count); + std::uint8_t* binary_data = allocate(binary_sub_type::k_vector, binary_data_length); + Format::write_frame(binary_data, binary_data_length, element_count); + return {vector::detail::accessor_data(binary_data, binary_data_length)}; + } + + private: + core* _core; +}; + +} // namespace basic +} // namespace builder +} // namespace v_noabi +} // namespace bsoncxx + +#include + +/// +/// @file +/// Declares @ref bsoncxx::v_noabi::builder::basic::sub_binary +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/core.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/core.hpp index 2c04a739fc..cda028f685 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/core.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/builder/core.hpp @@ -237,6 +237,20 @@ class core { /// BSONCXX_ABI_EXPORT_CDECL(core&) append(types::b_binary const& value); + /// + /// Appends a BSON binary datum by allocating space that the caller must fill with content. + /// + /// @return + /// A pointer to the allocated binary data block. The caller must write to every + /// byte or discard the builder. + /// + /// @throws + /// bsoncxx::v_noabi::exception if the current BSON datum is a document that is waiting for a + /// key to be appended to start a new key/value pair. + /// bsoncxx::v_noabi::exception if the binary fails to append. + /// + BSONCXX_ABI_EXPORT_CDECL(uint8_t*) append(binary_sub_type sub_type, uint32_t length); + /// /// Appends a BSON undefined. /// @@ -693,6 +707,22 @@ class core { /// BSONCXX_ABI_EXPORT_CDECL(void) clear(); + /// + /// A sub-binary must be opened by invoking @ref bsoncxx::v_noabi::builder::basic::sub_binary::allocate() + /// + core& open_binary() = delete; + + /// + /// Closes the current sub-binary within this BSON datum. + /// + /// @return + /// A reference to the object on which this member function is being called. This facilitates + /// method chaining. + /// + /// @throws bsoncxx::v_noabi::exception if the binary contents were never allocated. + /// + BSONCXX_ABI_EXPORT_CDECL(core&) close_binary(); + private: std::unique_ptr _impl; }; diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/enums/binary_sub_type.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/enums/binary_sub_type.hpp index 5ab13f1001..374e379e62 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/enums/binary_sub_type.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/enums/binary_sub_type.hpp @@ -26,6 +26,7 @@ BSONCXX_ENUM(md5, 0x05) BSONCXX_ENUM(encrypted, 0x06) BSONCXX_ENUM(column, 0x07) BSONCXX_ENUM(sensitive, 0x08) +BSONCXX_ENUM(vector, 0x09) BSONCXX_ENUM(user, 0x80) // clang-format on diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/exception/error_code.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/exception/error_code.hpp index 925adc4305..c8a96a4d09 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/exception/error_code.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/exception/error_code.hpp @@ -143,6 +143,15 @@ enum class error_code : std::int32_t { k_cannot_append_minkey, /// @} + /// A BSON Binary Vector failed to parse in the requested format. + k_invalid_vector, + + /// A BSON Binary Vector would be too large to represent. + k_vector_too_large, + + /// Attempted out-of-range access to a BSON Binary Vector element. + k_vector_out_of_range, + // Add new constant string message to error_code.cpp as well! }; diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/fwd.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/fwd.hpp index 03e9a53f6a..0cdf7fd084 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/fwd.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/fwd.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,11 @@ #include #include #include +#include +#include +#include +#include +#include #include /// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/types.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/types.hpp index b5aeb37d68..4636ee1954 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/types.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/types.hpp @@ -79,6 +79,7 @@ enum class binary_sub_type : std::uint8_t { k_encrypted = 0x06, ///< Encrypted BSON value. k_column = 0x07, ///< Compressed BSON column. k_sensitive = 0x08, ///< Sensitive. + k_vector = 0x09, ///< BSON Binary Vector. k_user = 0x80, ///< User defined. }; diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/accessor-fwd.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/accessor-fwd.hpp new file mode 100644 index 0000000000..bd0972cf1f --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/accessor-fwd.hpp @@ -0,0 +1,43 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { + +template +class accessor; + +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx + +namespace bsoncxx { +namespace vector { + +using ::bsoncxx::v_noabi::vector::accessor; + +} // namespace vector +} // namespace bsoncxx + +#include + +/// +/// @file +/// Declares @ref bsoncxx::v_noabi::vector::accessor. +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/accessor.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/accessor.hpp new file mode 100644 index 0000000000..8bb3283403 --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/accessor.hpp @@ -0,0 +1,350 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +// + +#include + +#include + +#include +#include +#include +#include + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { + +/// @brief Accessor for the contents of a valid BSON Binary Vector +/// @tparam Format One of the @ref bsoncxx::v_noabi::vector::formats types, optionally with a const qualifier. +/// +/// This accessor operates on data formatted for the bsoncxx::v_noabi::binary_sub_type::k_vector BSON binary +/// subtype. A mutable accessor may be constructed only using bsoncxx::v_noabi::builder::basic::sub_binary. A const +/// accessor may be constructed by validating any bsoncxx::v_noabi::types::b_binary. +/// +/// The specific iterator and element types vary for each supported format. +/// +/// bsoncxx::v_noabi::vector::formats::f_float32 uses a custom element type to support packed storage with a fixed byte +/// order. +/// +/// bsoncxx::v_noabi::vector::formats::f_packed_bit uses a custom element and iterator type for single bits that unpacks +/// them as bool. It also has custom element and iterator types for byte access, which serve to mask writes to reserved +/// bits. +/// +template +class accessor { + using format_traits = typename detail::format_traits::type>; + + public: + /// The type from bsoncxx::v_noabi::vector::formats representing this vector's layout and element type + using format = Format; + + /// Const qualified version of @ref value_type + /// @hideinitializer + using const_value_type = typename format_traits::value_type const; + + /// A type suitable for holding element values. + /// + /// @hideinitializer + /// For example: std::int8_t, float, bool + using value_type = typename std::conditional< + std::is_const::value, + typename format_traits::value_type const, + typename format_traits::value_type>::type; + + /// Type for referencing const-qualified vector elements in-place + /// @hideinitializer + using const_reference = typename format_traits::const_reference; + + /// Type for referencing vector elements in-place + /// + /// @hideinitializer + /// For example: std::int8_t&, bsoncxx::v_noabi::vector::elements::float32&, + /// bsoncxx::v_noabi::vector::elements::packed_bit_element + using reference = typename std::conditional< + std::is_const::value, + typename format_traits::const_reference, + typename format_traits::reference>::type; + + /// Iterator for const-qualified vector elements + /// @hideinitializer + using const_iterator = typename format_traits::const_iterator; + + /// Element iterator type + /// + /// @hideinitializer + /// For example: std::int8_t*, bsoncxx::v_noabi::vector::elements::float32*, + /// bsoncxx::v_noabi::vector::iterators::packed_bit_element + using iterator = typename std::conditional< + std::is_const::value, + typename format_traits::const_iterator, + typename format_traits::iterator>::type; + + /// Type for the underlying byte data + /// + /// @hideinitializer + /// For example: std::uint8_t, std::uint8_t const + using byte_type = typename detail::accessor_data::byte_type; + + /// Type for byte counts + /// + /// @hideinitializer + /// For example: std::uint32_t, due to BSON size limits. + using byte_count_type = typename detail::accessor_data::byte_count_type; + + /// Type for element counts + /// + /// @hideinitializer + /// For example: std::size_t + using element_count_type = typename format_traits::element_count_type; + + /// Type for signed differences between byte iterators + /// + /// @hideinitializer + /// For example: std::ptrdiff_t + using byte_difference_type = typename format_traits::byte_difference_type; + + /// Type for signed differences between element iterators + /// + /// @hideinitializer + /// For example: std::ptrdiff_t + using element_difference_type = typename format_traits::element_difference_type; + + /// Type for referencing const-qualified vector bytes in-place + /// @hideinitializer + using const_byte_reference = typename format_traits::const_byte_reference; + + /// Type for referencing vector bytes in-place + /// + /// @hideinitializer + /// For example: std::uint8_t&, std::uint8_t const&, bsoncxx::v_noabi::vector::elements::packed_bit_byte + using byte_reference = typename std::conditional< + std::is_const::value, + typename format_traits::const_byte_reference, + typename format_traits::byte_reference>::type; + + /// Iterator for const-qualified vector bytes + /// @hideinitializer + using const_byte_iterator = typename format_traits::const_byte_iterator; + + /// Byte iterator type + /// + /// @hideinitializer + /// For example: std::uint8_t*, std::uint8_t const*, bsoncxx::v_noabi::vector::iterators::packed_bit_byte + using byte_iterator = typename std::conditional< + std::is_const::value, + typename format_traits::const_byte_iterator, + typename format_traits::byte_iterator>::type; + + /// @brief Construct a const vector accessor by validating a bsoncxx::v_noabi::types::b_binary reference. + /// @param binary Non-owning reference to BSON binary data + /// @throws bsoncxx::v_noabi::exception with bsoncxx::v_noabi::error_code::k_invalid_vector, if validation fails. + /// + /// The Binary data is validated as a vector of the templated Format. On success, an accessor is created which + /// references the same data as the bsoncxx::v_noabi::types::b_binary pointer. + accessor(types::b_binary const& binary) : _data{(format::validate(binary), binary)} {} + + /// Obtain a const version of this vector accessor, without re-validating the vector data. + constexpr accessor as_const() const noexcept { + // Erase the template parameter from accessor_data to allow conversion from possibly-not-const to const. + return {{_data.bytes, _data.size, _data.header_copy}}; + } + + /// Count the bytes of element data, not including any headers + constexpr byte_count_type byte_size() const noexcept { + return _data.size - byte_count_type(detail::header_size); + } + + /// Count the number of elements + constexpr element_count_type size() const noexcept { + return format_traits::element_count(_data.size, _data.header_copy); + } + + /// Obtain a per-element iterator pointing to the beginning of the vector + constexpr iterator begin() const noexcept { + return iterator(_data.bytes + detail::header_size); + } + + /// Obtain a per-element end iterator + constexpr iterator end() const noexcept { + return begin() + element_difference_type(size()); + } + + /// Obtain a const per-element iterator pointing to the beginning of the vector + constexpr const_iterator cbegin() const noexcept { + return const_iterator(_data.bytes + detail::header_size); + } + + /// Obtain a const per-element end iterator + constexpr const_iterator cend() const noexcept { + return cbegin() + element_difference_type(size()); + } + + /// Obtain a reference to the first element. + /// @warning Undefined behavior if the vector is empty. + reference front() noexcept { + return *begin(); + } + + /// Obtain a const reference to the first element + /// @warning Undefined behavior if the vector is empty. + constexpr const_reference front() const noexcept { + return *begin(); + } + + /// Obtain a reference to the last element + /// @warning Undefined behavior if the vector is empty. + reference back() noexcept { + return *(begin() + element_difference_type(size() - 1u)); + } + + /// Obtain a const reference to the last element + /// @warning Undefined behavior if the vector is empty. + constexpr const_reference back() const noexcept { + return *(begin() + element_difference_type(size() - 1u)); + } + + /// Obtain a per-byte iterator pointing to the beginning of the vector + constexpr byte_iterator byte_begin() const noexcept { + return format_traits::make_byte_iterator(begin(), end()); + } + + /// Obtain a per-byte end iterator + constexpr byte_iterator byte_end() const noexcept { + return byte_begin() + byte_difference_type(byte_size()); + } + + /// Obtain a const per-byte iterator pointing to the beginning of the vector + constexpr const_byte_iterator byte_cbegin() const noexcept { + return format_traits::make_byte_iterator(cbegin(), cend()); + } + + /// Obtain a const per-byte end iterator + constexpr const_byte_iterator byte_cend() const noexcept { + return byte_cbegin() + byte_difference_type(byte_size()); + } + + /// Obtain a reference to the first byte + /// @warning Undefined behavior if the vector is empty. + byte_reference byte_front() noexcept { + return *byte_begin(); + } + + /// Obtain a const reference to the first byte + /// @warning Undefined behavior if the vector is empty. + constexpr const_byte_reference byte_front() const noexcept { + return *byte_begin(); + } + + /// Obtain a reference to the last byte + /// @warning Undefined behavior if the vector is empty. + byte_reference byte_back() noexcept { + return *(byte_begin() + byte_difference_type(byte_size() - 1u)); + } + + /// Obtain a const reference to the last byte + /// @warning Undefined behavior if the vector is empty. + constexpr const_byte_reference byte_back() const noexcept { + return *(byte_begin() + byte_difference_type(byte_size() - 1u)); + } + + /// Obtain a reference to a numbered byte, with bounds checking + /// @param index Index in the range 0 to byte_size()-1 inclusive. + /// @throws bsoncxx::v_noabi::exception with bsoncxx::v_noabi::error_code::k_vector_out_of_range, if the index is + /// outside the allowed range. + byte_reference byte_at(byte_count_type index) { + if (index >= byte_size()) { + throw bsoncxx::v_noabi::exception{error_code::k_vector_out_of_range}; + } + return *(byte_begin() + byte_difference_type(index)); + } + + /// Obtain a const reference to a numbered byte, with bounds checking + /// @param index Index in the range 0 to byte_size()-1 inclusive. + /// @throws bsoncxx::v_noabi::exception with bsoncxx::v_noabi::error_code::k_vector_out_of_range, if the index is + /// outside the allowed range. + const_byte_reference byte_at(byte_count_type index) const { + if (index >= byte_size()) { + throw bsoncxx::v_noabi::exception{error_code::k_vector_out_of_range}; + } + return *(byte_begin() + byte_difference_type(index)); + } + + /// Obtain a reference to a numbered element, with bounds checking + /// @param index Index in the range 0 to size()-1 inclusive. + /// @throws bsoncxx::v_noabi::exception with bsoncxx::v_noabi::error_code::k_vector_out_of_range, if the index is + /// outside the allowed range. + reference at(element_count_type index) { + if (index >= size()) { + throw bsoncxx::v_noabi::exception{error_code::k_vector_out_of_range}; + } + return *(begin() + element_difference_type(index)); + } + + /// Obtain a const reference to a numbered element, with bounds checking + /// @param index Index in the range 0 to size()-1 inclusive. + /// @throws bsoncxx::v_noabi::exception with bsoncxx::v_noabi::error_code::k_vector_out_of_range, if the index is + /// outside the allowed range. + const_reference at(element_count_type index) const { + if (index >= size()) { + throw bsoncxx::v_noabi::exception{error_code::k_vector_out_of_range}; + } + return *(begin() + element_difference_type(index)); + } + + /// Obtain a reference to a numbered element, without bounds checking + /// @param index Index in the range 0 to size()-1 inclusive. + /// @warning Undefined behavior if the index is out of bounds. + reference operator[](element_count_type index) noexcept { + return *(begin() + element_difference_type(index)); + } + + /// Obtain a const reference to a numbered element, without bounds checking + /// @param index Index in the range 0 to size()-1 inclusive. + /// @warning Undefined behavior if the index is out of bounds. + constexpr const_reference operator[](element_count_type index) const noexcept { + return *(begin() + element_difference_type(index)); + } + + /// Test whether the vector is empty + constexpr bool empty() const noexcept { + return size() == 0u; + } + + private: + friend class bsoncxx::v_noabi::builder::basic::sub_binary; + friend class accessor::type>; + + accessor(detail::accessor_data data) noexcept : _data{data} {} + + detail::accessor_data _data; +}; + +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx + +#include + +/// +/// @file +/// Declares @ref bsoncxx::v_noabi::vector::accessor. +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/detail-fwd.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/detail-fwd.hpp new file mode 100644 index 0000000000..299115c1d3 --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/detail-fwd.hpp @@ -0,0 +1,40 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { +namespace detail { + +template +struct format_traits; + +template +struct accessor_data; + +} // namespace detail +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx + +#include + +/// +/// @file +/// For internal use only! +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/detail.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/detail.hpp new file mode 100644 index 0000000000..5da014a32d --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/detail.hpp @@ -0,0 +1,175 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +// + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { +namespace detail { + +// Implementation detail. Size of the BSON Binary Vector header, in bytes. +constexpr std::size_t header_size = 2; + +// Implementation detail. Type for local copies of the vector header. +typedef std::array header; + +// @brief Implementation detail. Common data for each accessor type. +// @tparam Format One of the @ref bsoncxx::v_noabi::vector::formats types, optionally const. +template +struct accessor_data { + using byte_type = typename std::conditional::value, std::uint8_t const, std::uint8_t>::type; + using byte_count_type = std::uint32_t; + + byte_type* bytes; + byte_count_type size; + header header_copy; + + // Construct accessor_data around a b_binary that has already had its subtype and size validated. + accessor_data(types::b_binary const& binary) : accessor_data{binary.bytes, binary.size} {} + + // Construct accessor_data with an existing header copy + accessor_data(byte_type* bytes, byte_count_type size, header header_copy) + : bytes{bytes}, size{size}, header_copy{header_copy} {} + + // Construct accessor_data around binary data that has already been validated, and capture a new header copy. + accessor_data(byte_type* bytes, byte_count_type size) : bytes{bytes}, size{size} { + std::memcpy(header_copy.data(), bytes, header_size); + } +}; + +// @brief Implementation detail. Default format traits. +struct format_traits_base { + using element_count_type = std::size_t; + + using byte_difference_type = std::ptrdiff_t; + using element_difference_type = std::ptrdiff_t; + + using byte_reference = std::uint8_t&; + using const_byte_reference = std::uint8_t const&; + + using byte_iterator = std::uint8_t*; + using const_byte_iterator = std::uint8_t const*; +}; + +// @brief Implementation detail. Format traits, specialized by format. +// @tparam Format One of the @ref bsoncxx::v_noabi::vector::formats types, without qualifiers. +template +struct format_traits; + +// @brief Implementation detail. Format traits for bsoncxx::v_noabi::vector::formats::f_int8. +template <> +struct format_traits : format_traits_base { + using value_type = std::int8_t; + + using reference = std::int8_t&; + using const_reference = std::int8_t const&; + using iterator = std::int8_t*; + using const_iterator = std::int8_t const*; + + static constexpr std::size_t element_count(std::uint32_t binary_data_length, header) noexcept { + return binary_data_length - header_size; + } + + static byte_iterator make_byte_iterator(iterator element, iterator) noexcept { + return byte_iterator(static_cast(element)); + } + + static const_byte_iterator make_byte_iterator(const_iterator element, const_iterator) noexcept { + return const_byte_iterator(static_cast(element)); + } +}; + +// @brief Implementation detail. Format traits for bsoncxx::v_noabi::vector::formats::f_float32. +template <> +struct format_traits : format_traits_base { + using value_type = float; + + using reference = elements::float32&; + using const_reference = elements::float32 const&; + using iterator = elements::float32*; + using const_iterator = elements::float32 const*; + + static constexpr std::size_t element_count(std::uint32_t binary_data_length, header) noexcept { + return (binary_data_length - header_size) / sizeof(float); + } + + static byte_iterator make_byte_iterator(iterator element, iterator) noexcept { + return byte_iterator(static_cast(element)); + } + + static const_byte_iterator make_byte_iterator(const_iterator element, const_iterator) noexcept { + return const_byte_iterator(static_cast(element)); + } +}; + +// @brief Implementation detail. Format traits for bsoncxx::v_noabi::vector::formats::f_packed_bit. +template <> +struct format_traits : format_traits_base { + using value_type = bool; + + using iterator = iterators::packed_bit_element; + using const_iterator = iterators::packed_bit_element; + using reference = iterator::reference; + using const_reference = const_iterator::reference; + + using byte_iterator = iterators::packed_bit_byte; + using const_byte_iterator = iterators::packed_bit_byte; + using byte_reference = byte_iterator::reference; + using const_byte_reference = const_byte_iterator::reference; + + using byte_difference_type = byte_iterator::difference_type; + using element_difference_type = iterator::difference_type; + + static std::size_t element_count(std::uint32_t binary_data_length, header hdr) noexcept { + return std::size_t{binary_data_length - header_size} * std::size_t{8u} - std::size_t{hdr[1] & 7u}; + } + + static byte_iterator make_byte_iterator(iterator element, iterator element_end) noexcept { + return {element, element_end}; + } + + static constexpr const_byte_iterator make_byte_iterator( + const_iterator element, + const_iterator element_end) noexcept { + return {element, element_end}; + } +}; + +} // namespace detail +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx + +#include + +/// +/// @file +/// For internal use only! +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/elements-fwd.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/elements-fwd.hpp new file mode 100644 index 0000000000..b7aef1b30f --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/elements-fwd.hpp @@ -0,0 +1,42 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { +namespace elements { + +class float32; + +template +class packed_bit_element; + +template +class packed_bit_byte; + +} // namespace elements +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx + +#include + +/// +/// @file +/// Forward declarations for @ref bsoncxx::v_noabi::vector::elements. +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/elements.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/elements.hpp new file mode 100644 index 0000000000..5e2030a49a --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/elements.hpp @@ -0,0 +1,271 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +// + +#ifndef _WIN32 +#include // Endian detection +#endif + +#include +#include + +#include + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { +namespace elements { + +/// @brief A 32-bit float value in packed little-endian format +class float32 { + public: + /// @brief Construct a packed little-endian value from a float input in the local byte order. + /// @param value Floating point value to convert + float32(float value) noexcept { +#if defined(_WIN32) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) || \ + defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && (__BYTE_ORDER == __LITTLE_ENDIAN) + std::memcpy(bytes, &value, sizeof value); +#elif (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) || \ + defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && (__BYTE_ORDER == __BIG_ENDIAN) + std::uint32_t u32; + std::memcpy(&u32, &value, sizeof value); + u32 = __builtin_bswap32(u32); + std::memcpy(bytes, &u32, sizeof value); +#else +#error No implementation for storing 32-bit little endian unaligned float +#endif + } + + /// Convert a packed little-endian floating point value back to the local byte order. + operator float() const noexcept { + float value; +#if defined(_WIN32) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) || \ + defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && (__BYTE_ORDER == __LITTLE_ENDIAN) + std::memcpy(&value, bytes, sizeof value); +#elif (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) || \ + defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && (__BYTE_ORDER == __BIG_ENDIAN) + std::uint32_t u32; + std::memcpy(&u32, bytes, sizeof value); + u32 = __builtin_bswap32(u32); + std::memcpy(&value, &u32, sizeof value); +#else +#error No implementation for loading 32-bit little endian unaligned float +#endif + return value; + } + + /// Operator +=, emulating normal float behavior + float32& operator+=(float const& other) noexcept { + return *this = *this + other; + } + + /// Operator -=, emulating normal float behavior + float32& operator-=(float const& other) noexcept { + return *this = *this - other; + } + + /// Operator *=, emulating normal float behavior + float32& operator*=(float const& other) noexcept { + return *this = *this * other; + } + + /// Operator /=, emulating normal float behavior + float32& operator/=(float const& other) noexcept { + return *this = *this / other; + } + + private: + std::uint8_t bytes[4]; +}; + +/// @brief Reference to a single element in a packed_bit vector. +/// @tparam Iterator Underlying byte iterator type, optionally const. +template +class packed_bit_element { + public: + /// Value type chosen to represent a single-bit element + using value_type = bool; + + /// Obtain the referenced element's current value + constexpr operator value_type() const { + return ((*byte & mask) == UINT8_C(0)) ? value_type(0) : value_type(1); + } + + /// Set the value of the element referenced + packed_bit_element const& operator=(value_type const& v) const { + if (v == 0) { + *byte &= std::uint8_t(~mask); + } else { + *byte |= mask; + } + return *this; + } + + /// Copy the referenced value from another reference of the same type + packed_bit_element const& operator=(packed_bit_element const& v) const noexcept { + return *this = value_type{v}; + } + + /// Operator ^=, emulating bool reference behavior + packed_bit_element const& operator^=(value_type const& other) const noexcept { + return *this = *this ^ other; + } + + /// Operator &=, emulating bool reference behavior + packed_bit_element const& operator&=(value_type const& other) const noexcept { + return *this = *this & other; + } + + /// Operator |=, emulating bool reference behavior + packed_bit_element const& operator|=(value_type const& other) const noexcept { + return *this = *this | other; + } + + constexpr packed_bit_element(packed_bit_element const& other) : byte(other.byte), mask(other.mask) {} + + private: + friend class iterators::packed_bit_element; + + constexpr packed_bit_element(Iterator byte_iter, uint8_t bit_index) noexcept + : byte{byte_iter}, mask{uint8_t(0x80u >> bit_index)} {} + + Iterator byte; + std::uint8_t mask; +}; + +/// packed_bit_element is Swappable even when it's not an lvalue reference +template +void swap(packed_bit_element a, packed_bit_element b) noexcept { + bool a_value = a; + bool b_value = b; + a = b_value; + b = a_value; +} + +/// @brief Reference to a byte or partial byte within a vector of packed_bit elements. +/// Allows access to each byte as a uint8_t, while masking off writes to reserved bits. +/// @tparam Iterator Underlying byte iterator type, optionally const. +template +class packed_bit_byte { + public: + /// @brief Read the entire byte, as a std::uint8_t. + constexpr operator std::uint8_t() const noexcept { + return *byte; + } + + /// @brief Overwrite the usable portion of the byte, and set reserved bits to zero. + /// @param v Byte to write. Reserved bits are ignored. + /// @return *this + packed_bit_byte const& operator=(std::uint8_t const& v) const noexcept { + *byte = v & mask; + return *this; + } + + /// Copy the referenced value from another reference of the same type + packed_bit_byte const& operator=(packed_bit_byte const& v) const noexcept { + return *this = std::uint8_t(v); + } + + /// Operator +=, emulating number reference behavior + packed_bit_byte const& operator+=(std::uint8_t const& other) const noexcept { + return *this = std::uint8_t(*this + other); + } + + /// Operator -=, emulating number reference behavior + packed_bit_byte const& operator-=(std::uint8_t const& other) const noexcept { + return *this = std::uint8_t(*this - other); + } + + /// Operator *=, emulating number reference behavior + packed_bit_byte const& operator*=(std::uint8_t const& other) const noexcept { + return *this = std::uint8_t(*this * other); + } + + /// Operator /=, emulating number reference behavior + packed_bit_byte const& operator/=(std::uint8_t const& other) const noexcept { + return *this = *this / other; + } + + /// Operator %=, emulating number reference behavior + packed_bit_byte const& operator%=(std::uint8_t const& other) const noexcept { + return *this = *this % other; + } + + /// Operator ^=, emulating number reference behavior + packed_bit_byte const& operator^=(std::uint8_t const& other) const noexcept { + return *this = *this ^ other; + } + + /// Operator &=, emulating number reference behavior + packed_bit_byte const& operator&=(std::uint8_t const& other) const noexcept { + return *this = *this & other; + } + + /// Operator |=, emulating number reference behavior + packed_bit_byte const& operator|=(std::uint8_t const& other) const noexcept { + return *this = *this | other; + } + + /// Operator <<=, emulating number reference behavior + packed_bit_byte const& operator<<=(unsigned other) const noexcept { + return *this = *this << other; + } + + /// Operator >>=, emulating number reference behavior + packed_bit_byte const& operator>>=(unsigned other) const noexcept { + return *this = *this >> other; + } + + constexpr packed_bit_byte(packed_bit_byte const& other) : byte{other.byte}, mask{other.mask} {} + + private: + friend class iterators::packed_bit_byte; + + constexpr packed_bit_byte(Iterator byte_iter, uint8_t byte_mask) noexcept : byte{byte_iter}, mask{byte_mask} {} + + Iterator byte; + std::uint8_t mask; +}; + +/// Swap the referenced values for `a` and `b`. +/// +/// @note `packed_bit_byte` is a proxy reference and behaves like an lvalue reference. +template +void swap(packed_bit_byte a, packed_bit_byte b) noexcept { + std::uint8_t a_value = a; + std::uint8_t b_value = b; + a = b_value; + b = a_value; +} + +} // namespace elements +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx + +#include + +/// +/// @file +/// Declares entities in @ref bsoncxx::v_noabi::vector::elements. +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/formats-fwd.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/formats-fwd.hpp new file mode 100644 index 0000000000..110a2a1c2a --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/formats-fwd.hpp @@ -0,0 +1,50 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { +namespace formats { + +struct f_float32; +struct f_int8; +struct f_packed_bit; + +} // namespace formats +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx + +namespace bsoncxx { +namespace vector { +namespace formats { + +using ::bsoncxx::v_noabi::vector::formats::f_float32; +using ::bsoncxx::v_noabi::vector::formats::f_int8; +using ::bsoncxx::v_noabi::vector::formats::f_packed_bit; + +} // namespace formats +} // namespace vector +} // namespace bsoncxx + +#include + +/// +/// @file +/// Declares entities in @ref bsoncxx::v_noabi::vector::formats. +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/formats.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/formats.hpp new file mode 100644 index 0000000000..5a9bc315a2 --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/formats.hpp @@ -0,0 +1,67 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +// + +#include + +#include +#include + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { +namespace formats { + +/// @brief Vector format for 32-bit floating point elements, packed least significant byte first. +struct f_float32 { + static BSONCXX_ABI_EXPORT_CDECL(std::uint32_t) length_for_append(std::size_t element_count); + static BSONCXX_ABI_EXPORT_CDECL(void) + write_frame(std::uint8_t* binary_data, std::uint32_t binary_data_length, std::size_t element_count); + static BSONCXX_ABI_EXPORT_CDECL(void) validate(types::b_binary const& binary); +}; + +/// @brief Vector format for signed 8-bit integer elements. +struct f_int8 { + static BSONCXX_ABI_EXPORT_CDECL(std::uint32_t) length_for_append(std::size_t element_count); + static BSONCXX_ABI_EXPORT_CDECL(void) + write_frame(std::uint8_t* binary_data, std::uint32_t binary_data_length, std::size_t element_count); + static BSONCXX_ABI_EXPORT_CDECL(void) validate(types::b_binary const& binary); +}; + +/// @brief Vector format for single bit elements, packed most significant bit first. +struct f_packed_bit { + static BSONCXX_ABI_EXPORT_CDECL(std::uint32_t) length_for_append(std::size_t element_count); + static BSONCXX_ABI_EXPORT_CDECL(void) + write_frame(std::uint8_t* binary_data, std::uint32_t binary_data_length, std::size_t element_count); + static BSONCXX_ABI_EXPORT_CDECL(void) validate(types::b_binary const& binary); +}; + +} // namespace formats +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx + +#include + +/// +/// @file +/// Declares entities in @ref bsoncxx::v_noabi::vector::formats. +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/iterators-fwd.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/iterators-fwd.hpp new file mode 100644 index 0000000000..7647911523 --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/iterators-fwd.hpp @@ -0,0 +1,40 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { +namespace iterators { + +template +class packed_bit_element; + +template +class packed_bit_byte; + +} // namespace iterators +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx + +#include + +/// +/// @file +/// Forward declarations for @ref bsoncxx::v_noabi::vector::iterators. +/// diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/iterators.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/iterators.hpp new file mode 100644 index 0000000000..487cbc726c --- /dev/null +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/vector/iterators.hpp @@ -0,0 +1,301 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +// + +#include +#include + +#include +#include + +#include +#include + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { +namespace iterators { + +/// @brief Iterator for elements within a packed_bit vector +/// @tparam Iterator Underlying byte iterator type +template +class packed_bit_element { + public: + /// Values pointed to by this iterator are single bits represented by bool. + using value_type = bool; + + /// References to individual bits are each bsoncxx::v_noabi::elements::packed_bit_element. + using reference = elements::packed_bit_element const; + + /// Element pointers aren't really a useful concept here, but this is defined for compatibility with standard + /// random-access iterators. + using pointer = elements::packed_bit_element const*; + + /// This is a standard random-access iterator. + using iterator_category = std::random_access_iterator_tag; + + /// A signed bit count + using difference_type = std::ptrdiff_t; + + /// @brief Dereference this bit iterator into a bit reference. + /// @return An individual bit reference, as a bsoncxx::v_noabi::elements::packed_bit_element. + constexpr reference operator*() const noexcept { + return {byte, bit}; + } + + /// Compare two bit iterators + constexpr bool operator==(packed_bit_element const& other) const noexcept { + return byte == other.byte && bit == other.bit; + } + + /// Compare two bit iterators + constexpr bool operator!=(packed_bit_element const& other) const noexcept { + return byte != other.byte || bit != other.bit; + } + + /// Compare two bit iterators + constexpr bool operator<(packed_bit_element const& other) const noexcept { + return byte < other.byte || (byte == other.byte && bit < other.bit); + } + + /// Compare two bit iterators + constexpr bool operator<=(packed_bit_element const& other) const noexcept { + return byte < other.byte || (byte == other.byte && bit <= other.bit); + } + + /// Compare two bit iterators + constexpr bool operator>(packed_bit_element const& other) const noexcept { + return byte > other.byte || (byte == other.byte && bit > other.bit); + } + + /// Compare two bit iterators + constexpr bool operator>=(packed_bit_element const& other) const noexcept { + return byte > other.byte || (byte == other.byte && bit >= other.bit); + } + + /// @brief Calculate a signed addition of this iterator with a ptrdiff_t, moving it forward or backward the + /// indicated number of bits. + /// If the iterator goes out of range, behavior is undefined. + constexpr packed_bit_element operator+(difference_type const& other) const noexcept { + return { + byte + ((difference_type{bit} + other - ((difference_type{bit} + other) & 7)) / 8), + std::uint8_t((difference_type{bit} + other) & 7)}; + } + + /// @brief Calculate a signed subtraction of a ptrdiff_t from this iterator, moving it backward or forward the + /// indicated number of bits. + /// If the iterator goes out of range, behavior is undefined. + constexpr packed_bit_element operator-(difference_type const& other) const noexcept { + return *this + (-other); + } + + /// @brief Calculate the difference in position between two bit iterators + /// If the two iterators do not point into the same vector, behavior is undefined. + constexpr difference_type operator-(packed_bit_element const& other) const noexcept { + return {(byte - other.byte) * 8 + (difference_type{bit} - difference_type{other.bit})}; + } + + /// Advance this iterator forward by the indicated number of bits + packed_bit_element& operator+=(difference_type const& other) noexcept { + return *this = *this + other; + } + + /// Move this iterator backward by the indicated number of bits + packed_bit_element& operator-=(difference_type const& other) noexcept { + return *this = *this - other; + } + + /// Pre-increment + packed_bit_element& operator++() noexcept { + return *this += difference_type{1}; + } + + /// Pre-decrement + packed_bit_element& operator--() noexcept { + return *this -= difference_type{1}; + } + + /// Post-increment + packed_bit_element operator++(int) noexcept { + packed_bit_element prev = *this; + ++*this; + return prev; + } + + /// Post-decrement + packed_bit_element operator--(int) noexcept { + packed_bit_element prev = *this; + --*this; + return prev; + } + + private: + friend class packed_bit_byte; + friend class accessor; + friend class accessor; + + constexpr packed_bit_element(Iterator byte_iter, std::uint8_t bit_index = 0) noexcept + : byte{byte_iter}, bit{bit_index} {} + + Iterator byte; + std::uint8_t bit; +}; + +/// @brief Iterator for bytes within a packed_bit vector +/// @tparam Iterator Underlying byte iterator type +template +class packed_bit_byte { + public: + /// Values pointed to by this iterator are unsigned bytes. + using value_type = std::uint8_t; + + /// References to individual bytes are each bsoncxx::v_noabi::elements::packed_bit_byte, to protect the validity of + /// bytes with reserved portions. + using reference = elements::packed_bit_byte const; + + /// Element pointers aren't really a useful concept here, but this is defined for compatibility with standard + /// random-access iterators. + using pointer = elements::packed_bit_byte const*; + + /// This is a standard random-access iterator. + using iterator_category = std::random_access_iterator_tag; + + /// A signed byte count + using difference_type = std::ptrdiff_t; + + /// @brief Dereference the byte iterator + /// @return A bsoncxx::v_noabi::elements::packed_bit_byte that can be used like a byte reference. + constexpr reference operator*() const noexcept { + return {byte, (byte + 1) == byte_end ? last_byte_mask : value_type{0xFFu}}; + } + + /// Compare two byte iterators + constexpr bool operator==(packed_bit_byte const& other) const noexcept { + return byte == other.byte; + } + + /// Compare two byte iterators + constexpr bool operator!=(packed_bit_byte const& other) const noexcept { + return byte != other.byte; + } + + /// Compare two byte iterators + constexpr bool operator<(packed_bit_byte const& other) const noexcept { + return byte < other.byte; + } + + /// Compare two byte iterators + constexpr bool operator<=(packed_bit_byte const& other) const noexcept { + return byte <= other.byte; + } + + /// Compare two byte iterators + constexpr bool operator>(packed_bit_byte const& other) const noexcept { + return byte > other.byte; + } + + /// Compare two byte iterators + constexpr bool operator>=(packed_bit_byte const& other) const noexcept { + return byte >= other.byte; + } + + /// @brief Calculate a signed addition of this iterator with a ptrdiff_t, moving it forward or backward the + /// indicated number of bytes. + /// If the iterator goes out of range, behavior is undefined. + constexpr packed_bit_byte operator+(difference_type const& other) const noexcept { + return {byte + other, byte_end, last_byte_mask}; + } + + /// @brief Calculate a signed subtraction of a ptrdiff_t from this iterator, moving it backward or forward the + /// indicated number of bytes. + /// If the iterator goes out of range, behavior is undefined. + constexpr packed_bit_byte operator-(difference_type const& other) const noexcept { + return *this + (-other); + } + + /// @brief Calculate the difference in position between two byte iterators + /// If the two iterators do not point into the same vector, behavior is undefined. + constexpr difference_type operator-(packed_bit_byte const& other) const noexcept { + return {byte - other.byte}; + } + + /// Advance this iterator forward by the indicated number of bytes + packed_bit_byte& operator+=(difference_type const& other) noexcept { + return *this = *this + other; + } + + /// Move this iterator backward by the indicated number of bytes + packed_bit_byte& operator-=(difference_type const& other) noexcept { + return *this = *this - other; + } + + /// Pre-increment + packed_bit_byte& operator++() noexcept { + return *this += difference_type{1}; + } + + /// Pre-decrement + packed_bit_byte& operator--() noexcept { + return *this -= difference_type{1}; + } + + /// Post-increment + packed_bit_byte operator++(int) noexcept { + packed_bit_byte prev = *this; + ++*this; + return prev; + } + + /// Post-decrement + packed_bit_byte operator--(int) noexcept { + packed_bit_byte prev = *this; + --*this; + return prev; + } + + private: + friend struct detail::format_traits; + friend struct detail::format_traits; + + constexpr packed_bit_byte(packed_bit_element element, packed_bit_element element_end) + : byte{element.byte}, + byte_end{(element_end + 7u).byte}, + last_byte_mask{value_type(0xFFu << (-element_end.bit & 7u))} {} + + constexpr packed_bit_byte(Iterator byte, Iterator byte_end, value_type last_byte_mask) + : byte{byte}, byte_end{byte_end}, last_byte_mask{last_byte_mask} {} + + Iterator byte; + Iterator byte_end; + value_type last_byte_mask; +}; + +} // namespace iterators +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx + +#include + +/// +/// @file +/// Declares entities in @ref bsoncxx::v_noabi::vector::iterators. +/// diff --git a/src/bsoncxx/lib/CMakeLists.txt b/src/bsoncxx/lib/CMakeLists.txt index 02a5c2f8c2..e067bb2c2e 100644 --- a/src/bsoncxx/lib/CMakeLists.txt +++ b/src/bsoncxx/lib/CMakeLists.txt @@ -37,6 +37,7 @@ set(bsoncxx_sources_v_noabi bsoncxx/v_noabi/bsoncxx/types/bson_value/value.cpp bsoncxx/v_noabi/bsoncxx/types/bson_value/view.cpp bsoncxx/v_noabi/bsoncxx/validate.cpp + bsoncxx/v_noabi/bsoncxx/vector.cpp ) set(bsoncxx_sources_v1 diff --git a/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/builder/core.cpp b/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/builder/core.cpp index da833c22db..5f6734eae8 100644 --- a/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/builder/core.cpp +++ b/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/builder/core.cpp @@ -349,6 +349,23 @@ core& core::append(types::b_binary const& value) { return *this; } +uint8_t* core::append(binary_sub_type sub_type, uint32_t length) { + stdx::string_view key = _impl->next_key(); + uint8_t* allocated_bytes; + + if (!bson_append_binary_uninit( + _impl->back(), + key.data(), + static_cast(key.length()), + static_cast(sub_type), + &allocated_bytes, + length)) { + throw bsoncxx::v_noabi::exception{error_code::k_cannot_append_binary}; + } + + return allocated_bytes; +} + core& core::append(types::b_undefined const&) { stdx::string_view key = _impl->next_key(); @@ -683,6 +700,13 @@ core& core::close_array() { return *this; } +core& core::close_binary() { + if (!_impl->is_viewable()) { + throw bsoncxx::v_noabi::exception{error_code::k_unmatched_key_in_builder}; + } + return *this; +} + bsoncxx::v_noabi::document::view core::view_document() const { if (!_impl->is_viewable()) { throw bsoncxx::v_noabi::exception{error_code::k_unmatched_key_in_builder}; diff --git a/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/exception/error_code.cpp b/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/exception/error_code.cpp index 35f497cb3b..6d5c7f1596 100644 --- a/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/exception/error_code.cpp +++ b/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/exception/error_code.cpp @@ -81,6 +81,12 @@ class error_category_impl final : public std::error_category { return {"unable to append " #name}; #include #undef BSONCXX_ENUM + case error_code::k_invalid_vector: + return "invalid BSON vector"; + case error_code::k_vector_too_large: + return "BSON vector too large"; + case error_code::k_vector_out_of_range: + return "BSON vector access out of range"; default: return "unknown bsoncxx error code"; } diff --git a/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/vector.cpp b/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/vector.cpp new file mode 100644 index 0000000000..4f39c448b9 --- /dev/null +++ b/src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/vector.cpp @@ -0,0 +1,121 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +// + +#include + +#include +#include + +#include + +namespace bsoncxx { +namespace v_noabi { +namespace vector { +namespace detail { + +// Equivalent to bson_vector_element_type_t. +enum element_type : std::uint8_t { + signed_integer = 0, + unsigned_integer = 1, + floating_point = 2, +}; + +// Equivalent to bson_vector_element_size_t. +enum element_size : std::uint8_t { + bits_1 = 0, + bits_8 = 3, + bits_32 = 7, +}; + +static header make_header(element_type element_type, element_size element_size, std::uint8_t padding) { + return {{static_cast((element_type << 4) | element_size), padding}}; +} + +static void write_header(std::uint8_t* binary_data, header const& hdr) { + std::memcpy(binary_data, hdr.data(), header_size); +} + +template +static std::uint32_t libbson_length_for_append(std::size_t element_count, Impl func) { + std::uint32_t result = func(element_count); + if (result < BSON_VECTOR_HEADER_LEN) { + throw exception{error_code::k_vector_too_large}; + } + return result; +} + +template +static void libbson_validate(types::b_binary const& binary, Impl func) { + if (binary.sub_type != binary_sub_type::k_vector || !func(NULL, binary.bytes, binary.size)) { + throw exception{error_code::k_invalid_vector}; + } +} + +} // namespace detail + +namespace formats { + +std::uint32_t f_int8::length_for_append(std::size_t element_count) { + return detail::libbson_length_for_append(element_count, bson_vector_int8_binary_data_length); +} + +std::uint32_t f_float32::length_for_append(std::size_t element_count) { + return detail::libbson_length_for_append(element_count, bson_vector_float32_binary_data_length); +} + +std::uint32_t f_packed_bit::length_for_append(std::size_t element_count) { + return detail::libbson_length_for_append(element_count, bson_vector_packed_bit_binary_data_length); +} + +void f_int8::write_frame(std::uint8_t* binary_data, std::uint32_t, std::size_t) { + detail::write_header( + binary_data, detail::make_header(detail::element_type::signed_integer, detail::element_size::bits_8, 0)); +} + +void f_float32::write_frame(std::uint8_t* binary_data, std::uint32_t, std::size_t) { + detail::write_header( + binary_data, detail::make_header(detail::element_type::floating_point, detail::element_size::bits_32, 0)); +} + +void f_packed_bit::write_frame(std::uint8_t* binary_data, std::uint32_t binary_data_length, std::size_t element_count) { + binary_data[binary_data_length - 1] = UINT8_C(0); + detail::write_header( + binary_data, + detail::make_header( + detail::element_type::unsigned_integer, + detail::element_size::bits_1, + std::uint8_t(std::size_t{7u} & -element_count))); +} + +void formats::f_int8::validate(types::b_binary const& binary) { + return detail::libbson_validate(binary, bson_vector_int8_const_view_init); +} + +void formats::f_float32::validate(types::b_binary const& binary) { + return detail::libbson_validate(binary, bson_vector_float32_const_view_init); +} + +void formats::f_packed_bit::validate(types::b_binary const& binary) { + return detail::libbson_validate(binary, bson_vector_packed_bit_const_view_init); +} + +} // namespace formats +} // namespace vector +} // namespace v_noabi +} // namespace bsoncxx diff --git a/src/bsoncxx/test/CMakeLists.txt b/src/bsoncxx/test/CMakeLists.txt index b2ddd04ffa..2a53864813 100644 --- a/src/bsoncxx/test/CMakeLists.txt +++ b/src/bsoncxx/test/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable(test_bson exception_guard.cpp json.cpp oid.cpp + vector.cpp optional.test.cpp view_or_value.cpp make_unique.test.cpp @@ -158,6 +159,7 @@ set_dist_list(src_bsoncxx_test_DIST exception_guard.hh json.cpp oid.cpp + vector.cpp optional.test.cpp test_macro_guards.cpp.in to_string.hh diff --git a/src/bsoncxx/test/bson_builder.cpp b/src/bsoncxx/test/bson_builder.cpp index c882ee11b7..494503b110 100644 --- a/src/bsoncxx/test/bson_builder.cpp +++ b/src/bsoncxx/test/bson_builder.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -44,7 +45,7 @@ void bson_eq_stream(bson_t const* bson, bsoncxx::builder::stream::document const INFO("expected = " << to_json(expected)); INFO("builder = " << to_json(test)); REQUIRE(expected.length() == test.length()); - REQUIRE(std::memcmp(expected.data(), test.data(), expected.length()) == 0); + CHECK(std::memcmp(expected.data(), test.data(), expected.length()) == 0); } template @@ -55,7 +56,7 @@ void viewable_eq_viewable(T const& stream, U const& basic) { INFO("expected = " << to_json(expected)); INFO("basic = " << to_json(test)); REQUIRE(expected.length() == test.length()); - REQUIRE(std::memcmp(expected.data(), test.data(), expected.length()) == 0); + CHECK(std::memcmp(expected.data(), test.data(), expected.length()) == 0); } template @@ -65,7 +66,7 @@ void bson_eq_object(bson_t const* bson, T const actual) { INFO("expected = " << to_json(expected)); INFO("actual = " << to_json(actual)); REQUIRE(expected.length() == actual.length()); - REQUIRE(std::memcmp(expected.data(), actual.data(), expected.length()) == 0); + CHECK(std::memcmp(expected.data(), actual.data(), expected.length()) == 0); } TEST_CASE("builder appends string", "[bsoncxx::builder::stream]") { @@ -417,7 +418,7 @@ TEST_CASE("builder appends decimal128", "[bsoncxx::builder::stream]") { auto d = types::b_decimal128{"-1234E+999"}; auto v = types::bson_value::view{d}; - REQUIRE(v.get_decimal128() == d); + CHECK(v.get_decimal128() == d); b << "foo" << v; @@ -685,8 +686,8 @@ TEST_CASE("document core builder ownership", "[bsoncxx::builder::core]") { b.append(types::b_int32{1}); auto doc = b.view_document(); auto ele = doc["falafel"]; - REQUIRE(ele.type() == type::k_int32); - REQUIRE(ele.get_value() == types::b_int32{1}); + CHECK(ele.type() == type::k_int32); + CHECK(ele.get_value() == types::b_int32{1}); } SECTION("when passing a stdx::string_view, ownership handled by caller") { @@ -701,13 +702,13 @@ TEST_CASE("document core builder throws on insufficient stack", "[bsoncxx::build builder::core b(false); b.key_view("hi"); - REQUIRE_THROWS(b.close_document()); + CHECK_THROWS(b.close_document()); } TEST_CASE("array core builder throws on insufficient stack", "[bsoncxx::builder::core]") { builder::core b(true); - REQUIRE_THROWS(b.close_array()); + CHECK_THROWS(b.close_array()); } TEST_CASE("core builder open/close works", "[bsoncxx::builder::core]") { @@ -727,26 +728,26 @@ TEST_CASE("core builder open/close works", "[bsoncxx::builder::core]") { SECTION("opening a document and closing an array throws") { b.open_document(); - REQUIRE_THROWS(b.close_array()); + CHECK_THROWS(b.close_array()); } SECTION("opening an array and closing a document throws") { b.open_array(); - REQUIRE_THROWS(b.close_document()); + CHECK_THROWS(b.close_document()); } SECTION("opening an array and viewing throws") { b.open_array(); - REQUIRE_THROWS(b.view_document()); + CHECK_THROWS(b.view_document()); } SECTION("opening a document and viewing throws") { b.open_document(); - REQUIRE_THROWS(b.view_document()); + CHECK_THROWS(b.view_document()); } SECTION("viewing with with only the key and no value fails") { - REQUIRE_THROWS(b.view_document()); + CHECK_THROWS(b.view_document()); } SECTION("viewing with with a key and value suceeds") { @@ -760,72 +761,72 @@ TEST_CASE("core view/extract methods throw when called with wrong top-level type builder::core core_document(false); SECTION("view_array only throws when called on document") { - REQUIRE_NOTHROW(core_array.view_array()); - REQUIRE_THROWS(core_document.view_array()); + CHECK_NOTHROW(core_array.view_array()); + CHECK_THROWS(core_document.view_array()); } SECTION("extract_array only throws when called on document") { - REQUIRE_NOTHROW(core_array.extract_array()); - REQUIRE_THROWS(core_document.extract_array()); + CHECK_NOTHROW(core_array.extract_array()); + CHECK_THROWS(core_document.extract_array()); } SECTION("view_document only throws when called on array") { - REQUIRE_THROWS(core_array.view_document()); - REQUIRE_NOTHROW(core_document.view_document()); + CHECK_THROWS(core_array.view_document()); + CHECK_NOTHROW(core_document.view_document()); } SECTION("extract_document only throws when called on array") { - REQUIRE_THROWS(core_array.extract_document()); - REQUIRE_NOTHROW(core_document.extract_document()); + CHECK_THROWS(core_array.extract_document()); + CHECK_NOTHROW(core_document.extract_document()); } } TEST_CASE("core builder throws on consecutive keys", "[bsoncxx::builder::core]") { SECTION("appending key_view twice") { builder::core builder{false}; - REQUIRE_NOTHROW(builder.key_view("foo")); - REQUIRE_THROWS_AS(builder.key_view("bar"), bsoncxx::exception); + CHECK_NOTHROW(builder.key_view("foo")); + CHECK_THROWS_AS(builder.key_view("bar"), bsoncxx::exception); } SECTION("appending key_view then key_owned") { builder::core builder{false}; - REQUIRE_NOTHROW(builder.key_view("foo")); - REQUIRE_THROWS_AS(builder.key_owned("bar"), bsoncxx::exception); + CHECK_NOTHROW(builder.key_view("foo")); + CHECK_THROWS_AS(builder.key_owned("bar"), bsoncxx::exception); } SECTION("appending key_owned then key_view") { builder::core builder{false}; - REQUIRE_NOTHROW(builder.key_owned("foo")); - REQUIRE_THROWS_AS(builder.key_view("bar"), bsoncxx::exception); + CHECK_NOTHROW(builder.key_owned("foo")); + CHECK_THROWS_AS(builder.key_view("bar"), bsoncxx::exception); } SECTION("appending key_owned twice") { builder::core builder{false}; - REQUIRE_NOTHROW(builder.key_owned("foo")); - REQUIRE_THROWS_AS(builder.key_owned("bar"), bsoncxx::exception); + CHECK_NOTHROW(builder.key_owned("foo")); + CHECK_THROWS_AS(builder.key_owned("bar"), bsoncxx::exception); } } TEST_CASE("core method chaining to build document works", "[bsoncxx::builder::core]") { auto full_doc = builder::core{false}.key_owned("foo").append(1).key_owned("bar").append(true).extract_document(); - REQUIRE(full_doc.view()["foo"].type() == types::b_int32::type_id); - REQUIRE(full_doc.view()["foo"].get_int32() == 1); - REQUIRE(full_doc.view()["bar"].type() == types::b_bool::type_id); - REQUIRE(full_doc.view()["bar"].get_bool() == true); + CHECK(full_doc.view()["foo"].type() == types::b_int32::type_id); + CHECK(full_doc.view()["foo"].get_int32() == 1); + CHECK(full_doc.view()["bar"].type() == types::b_bool::type_id); + CHECK(full_doc.view()["bar"].get_bool() == true); } TEST_CASE("core method chaining to build array works", "[bsoncxx::builder::core]") { auto array = builder::core{true}.append("foo").append(1).append(true).extract_array(); auto array_view = array.view(); - REQUIRE(std::distance(array_view.begin(), array_view.end()) == 3); - REQUIRE(array_view[0].type() == type::k_string); - REQUIRE(string::to_string(array_view[0].get_string().value) == "foo"); - REQUIRE(array_view[1].type() == type::k_int32); - REQUIRE(array_view[1].get_int32().value == 1); - REQUIRE(array_view[2].type() == type::k_bool); - REQUIRE(array_view[2].get_bool().value == true); + CHECK(std::distance(array_view.begin(), array_view.end()) == 3); + CHECK(array_view[0].type() == type::k_string); + CHECK(string::to_string(array_view[0].get_string().value) == "foo"); + CHECK(array_view[1].type() == type::k_int32); + CHECK(array_view[1].get_int32().value == 1); + CHECK(array_view[2].type() == type::k_bool); + CHECK(array_view[2].get_bool().value == true); } TEST_CASE("basic document builder works", "[bsoncxx::builder::basic]") { @@ -886,6 +887,69 @@ TEST_CASE("basic document builder works", "[bsoncxx::builder::basic]") { viewable_eq_viewable(stream, basic); } + SECTION("b_binary works") { + { + using namespace builder::stream; + stream << "foo" + << types::b_binary{binary_sub_type::k_binary, 8, reinterpret_cast("deadbeef")}; + } + { + using namespace builder::basic; + basic.append( + kvp("hello", "world"), + kvp("foo", + types::b_binary{binary_sub_type::k_binary, 8, reinterpret_cast("deadbeef")})); + } + viewable_eq_viewable(stream, basic); + } + SECTION("sub_binary builder works") { + { + using namespace builder::stream; + stream << "foo" + << types::b_binary{binary_sub_type::k_binary, 8, reinterpret_cast("deadbeef")}; + } + { + using namespace builder::basic; + basic.append(kvp("hello", "world"), kvp("foo", [](sub_binary sb) { + memcpy(sb.allocate(binary_sub_type::k_binary, 8), "deadbeef", 8); + })); + } + viewable_eq_viewable(stream, basic); + } + SECTION("sub_binary can allocate and fill a larger memory region") { + using namespace builder::basic; + uint32_t const size = 32 * 1024 * 1024; + basic.append( + kvp("foo", [&](sub_binary sb) { memset(sb.allocate(binary_sub_type::k_binary, size), 0x55, size); })); + CHECK(basic.view().length() > size); + } + SECTION("sub_binary builder can allocate with length zero") { + { + using namespace builder::stream; + stream << "foo" << types::b_binary{binary_sub_type::k_binary, 0, reinterpret_cast("")}; + } + { + using namespace builder::basic; + basic.append( + kvp("hello", "world"), kvp("foo", [](sub_binary sb) { sb.allocate(binary_sub_type::k_binary, 0); })); + } + viewable_eq_viewable(stream, basic); + } + SECTION("sub_binary throws on double allocation") { + using namespace builder::basic; + CHECK_THROWS_AS( + basic.append( + kvp("foo", + [](sub_binary sb) { + sb.allocate(binary_sub_type::k_binary, 0); + sb.allocate(binary_sub_type::k_binary, 0); + })), + bsoncxx::exception); + } + SECTION("sub_binary throws on missing allocation") { + using namespace builder::basic; + CHECK_THROWS_AS(basic.append(kvp("foo", [](sub_binary) {})), bsoncxx::exception); + } } TEST_CASE("basic document builder move semantics work", "[bsoncxx::builder::basic::document]") { @@ -1184,31 +1248,31 @@ TEST_CASE("array::view works", "[bsoncxx::builder::array]") { stream << 100 << 99 << 98; - REQUIRE(stream.view()[0].get_int32() == 100); - REQUIRE(stream.view()[1].get_int32() == 99); - REQUIRE(stream.view()[2].get_int32() == 98); + CHECK(stream.view()[0].get_int32() == 100); + CHECK(stream.view()[1].get_int32() == 99); + CHECK(stream.view()[2].get_int32() == 98); } TEST_CASE("builder::basic::make_document works", "[bsoncxx::builder::basic::make_document]") { auto full_doc = builder::basic::make_document(builder::basic::kvp("foo", 1), builder::basic::kvp("bar", true)); - REQUIRE(full_doc.view()["foo"].type() == types::b_int32::type_id); - REQUIRE(full_doc.view()["foo"].get_int32() == 1); - REQUIRE(full_doc.view()["bar"].type() == types::b_bool::type_id); - REQUIRE(full_doc.view()["bar"].get_bool() == true); + CHECK(full_doc.view()["foo"].type() == types::b_int32::type_id); + CHECK(full_doc.view()["foo"].get_int32() == 1); + CHECK(full_doc.view()["bar"].type() == types::b_bool::type_id); + CHECK(full_doc.view()["bar"].get_bool() == true); } TEST_CASE("builder::basic::make_array works", "[bsoncxx::builder::basic::make_array]") { auto array = builder::basic::make_array("foo", 1, true); auto array_view = array.view(); - REQUIRE(std::distance(array_view.begin(), array_view.end()) == 3); - REQUIRE(array_view[0].type() == type::k_string); - REQUIRE(string::to_string(array_view[0].get_string().value) == "foo"); - REQUIRE(array_view[1].type() == type::k_int32); - REQUIRE(array_view[1].get_int32().value == 1); - REQUIRE(array_view[2].type() == type::k_bool); - REQUIRE(array_view[2].get_bool().value == true); + CHECK(std::distance(array_view.begin(), array_view.end()) == 3); + CHECK(array_view[0].type() == type::k_string); + CHECK(string::to_string(array_view[0].get_string().value) == "foo"); + CHECK(array_view[1].type() == type::k_int32); + CHECK(array_view[1].get_int32().value == 1); + CHECK(array_view[2].type() == type::k_bool); + CHECK(array_view[2].get_bool().value == true); } TEST_CASE("stream in a document::view works", "[bsoncxx::builder::stream]") { @@ -1217,9 +1281,9 @@ TEST_CASE("stream in a document::view works", "[bsoncxx::builder::stream]") { auto sub_doc = builder::stream::document{} << "b" << 1 << finalize; auto full_doc = builder::stream::document{} << "a" << sub_doc.view() << finalize; - REQUIRE(full_doc.view()["a"].type() == bsoncxx::types::b_document::type_id); - REQUIRE(full_doc.view()["a"]["b"].type() == bsoncxx::types::b_int32::type_id); - REQUIRE(full_doc.view()["a"]["b"].get_int32().value == 1); + CHECK(full_doc.view()["a"].type() == bsoncxx::types::b_document::type_id); + CHECK(full_doc.view()["a"]["b"].type() == bsoncxx::types::b_int32::type_id); + CHECK(full_doc.view()["a"]["b"].get_int32().value == 1); } TEST_CASE("stream in an array::view works", "[bsoncxx::builder::stream]") { @@ -1228,18 +1292,18 @@ TEST_CASE("stream in an array::view works", "[bsoncxx::builder::stream]") { auto sub_array = builder::stream::array{} << 1 << 2 << 3 << finalize; auto full_doc = builder::stream::document{} << "a" << sub_array.view() << finalize; - REQUIRE(full_doc.view()["a"].type() == bsoncxx::types::b_array::type_id); - REQUIRE(full_doc.view()["a"][1].type() == bsoncxx::types::b_int32::type_id); - REQUIRE(full_doc.view()["a"][1].get_int32().value == 2); + CHECK(full_doc.view()["a"].type() == bsoncxx::types::b_array::type_id); + CHECK(full_doc.view()["a"][1].type() == bsoncxx::types::b_int32::type_id); + CHECK(full_doc.view()["a"][1].get_int32().value == 2); } TEST_CASE("builder::stream::document throws on consecutive keys", "[bsoncxx::builder::core]") { builder::stream::document doc; - REQUIRE_NOTHROW( + CHECK_NOTHROW( doc << "foo" << "bar"); - REQUIRE_NOTHROW(doc << "far"); - REQUIRE_THROWS_AS(doc << "boo", bsoncxx::exception); + CHECK_NOTHROW(doc << "far"); + CHECK_THROWS_AS(doc << "boo", bsoncxx::exception); } TEST_CASE("list builder appends utf8", "[bsoncxx::builder::list]") { @@ -1553,7 +1617,7 @@ TEST_CASE("list builder appends decimal128", "[bsoncxx::builder::list]") { auto d = types::b_decimal128{"-1234E+999"}; auto v = types::bson_value::view{d}; - REQUIRE(v.get_decimal128() == d); + CHECK(v.get_decimal128() == d); builder::list b{"foo", v}; bson_eq_object(&expected, b.view().get_document().value); @@ -1708,10 +1772,10 @@ TEST_CASE("list builder with explicit type deduction", "[bsoncxx::builder::list] SECTION("document") { builder::list b; auto kvp_regex = Catch::Matchers::Matches("(.*)must be list of key-value pairs(.*)", Catch::CaseSensitive::No); - REQUIRE_THROWS_WITH((b = builder::document{"foo", 1, 2}), kvp_regex); + CHECK_THROWS_WITH((b = builder::document{"foo", 1, 2}), kvp_regex); auto type_regex = Catch::Matchers::Matches("(.*)must be string type(.*)int32(.*)", Catch::CaseSensitive::No); - REQUIRE_THROWS_WITH((b = builder::document{"foo", 1, 2, 4}), type_regex); + CHECK_THROWS_WITH((b = builder::document{"foo", 1, 2, 4}), type_regex); } } diff --git a/src/bsoncxx/test/vector.cpp b/src/bsoncxx/test/vector.cpp new file mode 100644 index 0000000000..38c2d27c5b --- /dev/null +++ b/src/bsoncxx/test/vector.cpp @@ -0,0 +1,593 @@ +// Copyright 2009-present MongoDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace { + +using namespace bsoncxx; + +template +struct format_specific; + +template <> +struct format_specific { + using value_type = float; + static constexpr std::array bytes_empty() { + return {0x27, 0x00}; + } + static constexpr std::array bytes_unit() { + return {0x27, 0x00, 0x00, 0x00, 0x80, 0x3f}; + } + static constexpr value_type element_unit() { + return 1.f; + } +}; + +template <> +struct format_specific { + using value_type = int8_t; + static constexpr std::array bytes_empty() { + return {0x03, 0x00}; + } + static constexpr std::array bytes_unit() { + return {0x03, 0x00, 0x01}; + } + static constexpr value_type element_unit() { + return 1; + } +}; + +template <> +struct format_specific { + using value_type = bool; + static constexpr std::array bytes_empty() { + return {0x10, 0x00}; + } + static constexpr std::array bytes_unit() { + return {0x10, 0x07, 0x80}; + } + static constexpr value_type element_unit() { + return true; + } +}; + +template +void binary_eq_bytes(types::b_binary const& binary, Sequence const& bytes) { + REQUIRE(binary.size == bytes.size()); + CHECK(std::memcmp(binary.bytes, bytes.data(), bytes.size()) == 0); +} + +template +void iterator_operations( + Iterator const& begin, + Iterator const& end, + std::ptrdiff_t expected_size, + Element element_unit) { + CHECK(end - begin == expected_size); + + for (std::ptrdiff_t outer_index = 0; outer_index < 50; outer_index++) { + Iterator outer_front = begin + outer_index; + Iterator outer_back = end - outer_index; + + CHECK(outer_front - begin == outer_index); + CHECK(begin - outer_front == -outer_index); + + CHECK(end - outer_back == outer_index); + CHECK(outer_back - end == -outer_index); + + for (std::ptrdiff_t inner_index = 0; inner_index < 20; inner_index++) { + Iterator inner_front = outer_front + inner_index; + Iterator inner_back = outer_back - inner_index; + + CHECK(inner_front - outer_front == inner_index); + CHECK(outer_front - inner_front == -inner_index); + + CHECK(outer_back - inner_back == inner_index); + CHECK(inner_back - outer_back == -inner_index); + } + } + + Iterator iter_copy = begin; + CHECK(iter_copy == begin); + CHECK(iter_copy >= begin); + CHECK(iter_copy <= begin); + CHECK_FALSE(iter_copy != begin); + CHECK_FALSE(iter_copy < begin); + + CHECK(++iter_copy - begin == 1); + CHECK(iter_copy > begin); + CHECK(iter_copy >= begin); + CHECK(iter_copy != begin); + CHECK_FALSE(iter_copy == begin); + CHECK_FALSE(iter_copy <= begin); + CHECK_FALSE(iter_copy < begin); + + CHECK(--iter_copy - begin == 0); + CHECK(iter_copy == begin); + + CHECK(iter_copy++ - begin == 0); + CHECK(iter_copy-- - begin == 1); + CHECK(iter_copy == begin); + + BSONCXX_PRIVATE_WARNINGS_PUSH(); + BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wfloat-equal")); + + std::generate(begin, end, [&] { return element_unit; }); + std::for_each(begin, end, [&](auto const& value) { CHECK(value == element_unit); }); + + std::copy(begin, begin + (expected_size / 2), begin + (expected_size / 2)); + std::for_each(begin, end, [&](auto const& value) { CHECK(value == element_unit); }); + + std::for_each(begin, end, [&](auto&& value) { value = element_unit; }); + std::for_each(begin, end, [&](auto const& value) { CHECK(value == element_unit); }); + + std::fill(begin, end, element_unit); + std::for_each(begin, end, [&](auto const& value) { CHECK(value == element_unit); }); + + std::sort(begin, end); + std::for_each(begin, end, [&](auto const& value) { CHECK(value == element_unit); }); + + // Verify ADL chooses our swap() even when std::swap is in scope + *begin = Element{0}; + CHECK(*begin < *(end - 1)); + CHECK_FALSE(*(end - 1) < *begin); + { + using std::swap; + swap(*begin, *(end - 1)); + } + CHECK(*(end - 1) < *begin); + CHECK_FALSE(*begin < *(end - 1)); + *begin = element_unit; + + // Sort, check that a 0 moves from the back to the front. + *(end - 1) = Element{0}; + std::sort(begin, end); + CHECK(*begin == Element{0}); + std::for_each(begin + 1, end, [&](auto const& value) { CHECK(value == element_unit); }); + + std::for_each(begin, end, [&](auto&& value) { value = Element{0}; }); + std::for_each(begin, end, [&](auto const& value) { CHECK(value != element_unit); }); + std::for_each(begin, end, [&](auto const& value) { CHECK(value < element_unit); }); + std::for_each(begin, end, [&](auto const& value) { CHECK(value <= element_unit); }); + std::for_each(begin, end, [&](auto const& value) { CHECK_FALSE(value == element_unit); }); + std::for_each(begin, end, [&](auto const& value) { CHECK_FALSE(value > element_unit); }); + std::for_each(begin, end, [&](auto const& value) { CHECK_FALSE(value >= element_unit); }); + + BSONCXX_PRIVATE_WARNINGS_POP(); +} + +TEMPLATE_TEST_CASE( + "all vector accessor formats", + "[bsoncxx::vector::accessor]", + vector::formats::f_float32, + vector::formats::f_int8, + vector::formats::f_packed_bit) { + using test_format_specific = format_specific; + using value_type = typename test_format_specific::value_type; + + SECTION("accept a valid vector with no elements") { + auto bytes = test_format_specific::bytes_empty(); + types::b_binary const binary{binary_sub_type::k_vector, bytes.size(), bytes.data()}; + vector::accessor vec{binary}; + CHECK(vec.empty()); + CHECK(vec.size() == 0); + CHECK(vec.byte_size() == 0); + CHECK_THROWS_WITH_CODE(vec.at(0), bsoncxx::v_noabi::error_code::k_vector_out_of_range); + CHECK_THROWS_WITH_CODE(vec.byte_at(0), bsoncxx::v_noabi::error_code::k_vector_out_of_range); + } + + SECTION("decode a valid vector with a single element") { + auto bytes = test_format_specific::bytes_unit(); + auto element = test_format_specific::element_unit(); + types::b_binary const binary{binary_sub_type::k_vector, bytes.size(), bytes.data()}; + vector::accessor vec{binary}; + CHECK_FALSE(vec.empty()); + CHECK(vec.size() == 1u); + CHECK(vec.byte_size() == bytes.size() - 2u); + + BSONCXX_PRIVATE_WARNINGS_PUSH(); + BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wfloat-equal")); + + CHECK(vec.at(0) == element); + CHECK(vec[0] == element); + + BSONCXX_PRIVATE_WARNINGS_POP(); + + CHECK(vec.byte_at(0) == bytes[2]); + CHECK(vec.byte_at(bytes.size() - 3u) == bytes[bytes.size() - 1u]); + CHECK_THROWS_WITH_CODE(vec.at(1), bsoncxx::v_noabi::error_code::k_vector_out_of_range); + CHECK_THROWS_WITH_CODE(vec.byte_at(bytes.size() - 2u), bsoncxx::v_noabi::error_code::k_vector_out_of_range); + } + + SECTION("reject binary data of the wrong sub_type") { + auto bytes = test_format_specific::bytes_empty(); + auto invalid_type = GENERATE( + binary_sub_type::k_binary, binary_sub_type::k_encrypted, binary_sub_type::k_uuid, binary_sub_type::k_user); + types::b_binary const binary{invalid_type, bytes.size(), bytes.data()}; + CHECK_THROWS_WITH_CODE( + vector::accessor{binary}, bsoncxx::v_noabi::error_code::k_invalid_vector); + } + + SECTION("reject binary data that's too short to include a header") { + auto bytes = test_format_specific::bytes_empty(); + auto bytes_to_remove = GENERATE(1u, 2u); + REQUIRE(bytes.size() >= bytes_to_remove); + types::b_binary const binary{binary_sub_type::k_vector, uint32_t(bytes.size() - bytes_to_remove), bytes.data()}; + CHECK_THROWS_WITH_CODE( + vector::accessor{binary}, bsoncxx::v_noabi::error_code::k_invalid_vector); + } + + SECTION("reject empty vectors with any modified header bits") { + for (unsigned bit_index = 0; bit_index < 16; bit_index++) { + auto bytes = test_format_specific::bytes_empty(); + bytes[bit_index >> 3u] ^= std::uint8_t(1u << (bit_index & 7u)); + types::b_binary const binary{binary_sub_type::k_vector, bytes.size(), bytes.data()}; + CHECK_THROWS_WITH_CODE( + vector::accessor{binary}, bsoncxx::v_noabi::error_code::k_invalid_vector); + } + } + + SECTION("encode a single element as expected") { + using namespace builder::basic; + auto expected_bytes = test_format_specific::bytes_unit(); + auto element = test_format_specific::element_unit(); + bsoncxx::document::value doc = + make_document(kvp("vector", [&](sub_binary sbin) { sbin.allocate(TestType{}, 1u)[0u] = element; })); + types::b_binary const& binary = doc.view()["vector"].get_binary(); + CHECK(binary.sub_type == binary_sub_type::k_vector); + binary_eq_bytes(binary, expected_bytes); + vector::accessor validate_encoded{binary}; + } + + SECTION("support algorithms and operators on element iterators") { + using namespace builder::basic; + bsoncxx::document::value doc = make_document(kvp("vector", [&](sub_binary sbin) { + // Avoid multiples of 8, to cover nonzero packed_bit 'padding'. + auto vec = sbin.allocate(TestType{}, 8007u); + auto const element_unit = test_format_specific::element_unit(); + iterator_operations(vec.begin(), vec.end(), std::ptrdiff_t(vec.size()), element_unit); + + // Two ways of iterating as const + BSONCXX_PRIVATE_WARNINGS_PUSH(); + BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wfloat-equal")); + std::for_each(vec.cbegin(), vec.cend(), [&](auto const& value) { CHECK_FALSE(value == element_unit); }); + std::for_each(vec.as_const().begin(), vec.as_const().end(), [&](auto const& value) { + CHECK_FALSE(value == element_unit); + }); + BSONCXX_PRIVATE_WARNINGS_POP(); + })); + types::b_binary const& binary = doc.view()["vector"].get_binary(); + vector::accessor validate_encoded{binary}; + CHECK(binary.size > 1000u); + } + + SECTION("support algorithms and operators on byte iterators") { + using namespace builder::basic; + bsoncxx::document::value doc = make_document(kvp("vector", [&](sub_binary sbin) { + // Choose a multiple of 8, to avoid the effects of masking unused bits. + auto vec = sbin.allocate(TestType{}, 8000u); + uint8_t const element_unit(1); + iterator_operations(vec.byte_begin(), vec.byte_end(), std::ptrdiff_t(vec.byte_size()), element_unit); + + // Two ways of iterating as const + std::for_each( + vec.byte_cbegin(), vec.byte_cend(), [&](auto const& value) { CHECK_FALSE(value == element_unit); }); + std::for_each(vec.as_const().byte_begin(), vec.as_const().byte_end(), [&](auto const& value) { + CHECK_FALSE(value == element_unit); + }); + })); + types::b_binary const& binary = doc.view()["vector"].get_binary(); + vector::accessor validate_encoded{binary}; + CHECK(binary.size > 1000u); + } + + SECTION("support assignment between referenced elements") { + using namespace builder::basic; + bsoncxx::document::value doc = make_document(kvp("vector", [&](sub_binary sbin) { + auto vec = sbin.allocate(TestType{}, 2u); + vec[0] = test_format_specific::element_unit(); + vec[1] = value_type{0}; + + BSONCXX_PRIVATE_WARNINGS_PUSH(); + BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wfloat-equal")); + + CHECK(vec.at(0) != vec.at(1)); + CHECK_FALSE(vec.at(0) == vec.at(1)); + vec[1] = vec[0]; + CHECK(vec.at(0) == vec.at(1)); + CHECK_FALSE(vec.at(0) != vec.at(1)); + + BSONCXX_PRIVATE_WARNINGS_POP(); + })); + types::b_binary const& binary = doc.view()["vector"].get_binary(); + vector::accessor validate_encoded{binary}; + } + + SECTION("support assignment between referenced bytes") { + using namespace builder::basic; + bsoncxx::document::value doc = make_document(kvp("vector", [&](sub_binary sbin) { + auto vec = sbin.allocate(TestType{}, 16u); + std::fill(vec.begin(), vec.end(), test_format_specific::element_unit()); + *(vec.end() - 2) = value_type{0}; + CHECK(vec.byte_at(vec.byte_size() - 2) != vec.byte_at(vec.byte_size() - 1)); + CHECK_FALSE(vec.byte_at(vec.byte_size() - 2) == vec.byte_at(vec.byte_size() - 1)); + vec.byte_at(vec.byte_size() - 2) = vec.byte_at(vec.byte_size() - 1); + CHECK(vec.byte_at(vec.byte_size() - 2) == vec.byte_at(vec.byte_size() - 1)); + CHECK_FALSE(vec.byte_at(vec.byte_size() - 2) != vec.byte_at(vec.byte_size() - 1)); + })); + types::b_binary const& binary = doc.view()["vector"].get_binary(); + vector::accessor validate_encoded{binary}; + } + + SECTION("fail to allocate unrepresentably large vectors") { + using namespace builder::basic; + // This checks that we can detect overlarge sizes and throw an exception. + // Detailed checks for the size limit are delegated to Libbson (via libbson_length_for_append) + CHECK_THROWS_WITH_CODE( + make_document(kvp("vector", [&](sub_binary sbin) { sbin.allocate(TestType{}, SIZE_MAX); })), + bsoncxx::v_noabi::error_code::k_vector_too_large); + } + + SECTION("support front and back element references") { + using namespace builder::basic; + bsoncxx::document::value doc = make_document(kvp("vector", [&](sub_binary sbin) { + auto vec = sbin.allocate(TestType{}, 2u); + std::fill(vec.begin(), vec.end(), test_format_specific::element_unit()); + *(vec.end() - 1) = value_type{0}; + + BSONCXX_PRIVATE_WARNINGS_PUSH(); + BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wfloat-equal")); + + CHECK(vec.back() == value_type{0}); + CHECK(vec.back() == vec[vec.size() - 1u]); + CHECK(vec.front() != vec.back()); + CHECK_FALSE(vec.front() == vec.back()); + vec.front() = vec.back(); + CHECK(vec[0] == value_type{0}); + CHECK(vec.front() == vec.back()); + CHECK_FALSE(vec.front() != vec.back()); + CHECK(vec[vec.size() - 1u] == value_type{0}); + vec.back() = test_format_specific::element_unit(); + CHECK(vec[0] == value_type{0}); + CHECK(vec[vec.size() - 1u] != value_type{0}); + CHECK(vec.front() != vec.back()); + CHECK_FALSE(vec.front() == vec.back()); + + BSONCXX_PRIVATE_WARNINGS_POP(); + })); + types::b_binary const& binary = doc.view()["vector"].get_binary(); + vector::accessor validate_encoded{binary}; + } + + SECTION("support front and back byte references") { + using namespace builder::basic; + bsoncxx::document::value doc = make_document(kvp("vector", [&](sub_binary sbin) { + auto vec = sbin.allocate(TestType{}, 16u); + std::fill(vec.begin(), vec.end(), value_type{0}); + + BSONCXX_PRIVATE_WARNINGS_PUSH(); + BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wfloat-equal")); + + CHECK(vec.front() == vec.back()); + CHECK_FALSE(vec.front() != vec.back()); + CHECK(vec.byte_front() == vec.byte_back()); + CHECK_FALSE(vec.byte_front() != vec.byte_back()); + vec.back() = test_format_specific::element_unit(); + CHECK(vec.byte_front() != vec.byte_back()); + CHECK_FALSE(vec.byte_front() == vec.byte_back()); + vec.byte_front() = UINT8_C(0); + vec.byte_back() = UINT8_C(0); + CHECK(vec.byte_front() == vec.byte_back()); + CHECK_FALSE(vec.byte_front() != vec.byte_back()); + + BSONCXX_PRIVATE_WARNINGS_POP(); + })); + types::b_binary const& binary = doc.view()["vector"].get_binary(); + vector::accessor validate_encoded{binary}; + } +} + +TEST_CASE("vector accessor float32", "[bsoncxx::vector::accessor]") { + SECTION("rejects binary data with an incorrect length") { + static uint8_t const bytes[] = {0x27, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00}; + auto invalid_length = GENERATE(0u, 1u, 3u, 4u, 5u, 7u, 8u, 9u); + types::b_binary const binary{binary_sub_type::k_vector, uint32_t(invalid_length), bytes}; + CHECK_THROWS_WITH_CODE( + vector::accessor{binary}, bsoncxx::v_noabi::error_code::k_invalid_vector); + } + + SECTION("rejects binary data from other vector formats") { + auto bytes = GENERATE( + format_specific::bytes_empty(), + format_specific::bytes_empty()); + types::b_binary const binary{binary_sub_type::k_vector, bytes.size(), bytes.data()}; + CHECK_THROWS_WITH_CODE( + vector::accessor{binary}, bsoncxx::v_noabi::error_code::k_invalid_vector); + } + + SECTION("accepts and correctly decodes elements with infinite value") { + static uint8_t const bytes[] = { + 0x27, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7F}; + types::b_binary const binary{binary_sub_type::k_vector, sizeof bytes, bytes}; + vector::accessor vec{binary}; + REQUIRE(vec.size() == 3u); + + BSONCXX_PRIVATE_WARNINGS_PUSH(); + BSONCXX_PRIVATE_WARNINGS_DISABLE(GNU("-Wfloat-equal")); + + CHECK(vec[0] < 0.f); + CHECK(vec[0] * 0.f != 0.f); + CHECK(vec[1] == 0.f); + CHECK(vec[2] > 0.f); + CHECK(vec[2] * 0.f != 0.f); + + BSONCXX_PRIVATE_WARNINGS_POP(); + } +} + +TEST_CASE("vector accessor int8_t", "[bsoncxx::vector::accessor]") { + SECTION("rejects binary data from other vector formats") { + auto bytes = GENERATE( + format_specific::bytes_empty(), + format_specific::bytes_empty()); + types::b_binary const binary{binary_sub_type::k_vector, bytes.size(), bytes.data()}; + CHECK_THROWS_WITH_CODE( + vector::accessor{binary}, bsoncxx::v_noabi::error_code::k_invalid_vector); + } +} + +TEST_CASE("vector accessor packed_bit", "[bsoncxx::vector::accessor]") { + SECTION("rejects empty vectors with nonzero padding") { + for (unsigned byte_value = 1u; byte_value <= UINT8_MAX; byte_value++) { + auto bytes = format_specific::bytes_empty(); + bytes[1] = uint8_t(byte_value); + types::b_binary const binary{binary_sub_type::k_vector, bytes.size(), bytes.data()}; + CHECK_THROWS_WITH_CODE( + vector::accessor{binary}, + bsoncxx::v_noabi::error_code::k_invalid_vector); + } + } + + SECTION("rejects nonempty vectors with reserved values in header padding byte") { + for (unsigned byte_value = 8u; byte_value <= UINT8_MAX; byte_value++) { + uint8_t const bytes[] = {0x10, uint8_t(byte_value), 0x00}; + types::b_binary const binary{binary_sub_type::k_vector, sizeof bytes, bytes}; + CHECK_THROWS_WITH_CODE( + vector::accessor{binary}, + bsoncxx::v_noabi::error_code::k_invalid_vector); + } + } + + SECTION("rejects nonempty vectors with nonzero values in unused trailing bits") { + for (unsigned byte_value = 1u; byte_value <= 7u; byte_value++) { + uint8_t bytes[] = {0x10, uint8_t(byte_value), 0xff}; + types::b_binary const binary{binary_sub_type::k_vector, sizeof bytes, bytes}; + CHECK_THROWS_WITH_CODE( + vector::accessor{binary}, + bsoncxx::v_noabi::error_code::k_invalid_vector); + // Succeeds when unused bits are then zeroed + bytes[2] = 0; + vector::accessor vec{binary}; + CHECK(vec.size() == 8u - byte_value); + } + } + + SECTION("masks writes to unused portions of the last byte") { + using namespace builder::basic; + bsoncxx::document::value doc = make_document(kvp("vector", [&](sub_binary sbin) { + auto vec = sbin.allocate(vector::formats::f_packed_bit{}, 9u); + std::fill(vec.begin(), vec.end(), true); + std::for_each(vec.begin(), vec.end(), [&](bool value) { CHECK(value == true); }); + std::for_each(vec.cbegin(), vec.cend(), [&](bool value) { CHECK(value == true); }); + CHECK(vec.byte_size() == 2u); + CHECK(vec.byte_at(0) == 0xff); + CHECK(vec.byte_at(1) == 0x80); + vec.byte_at(1) = 0x7f; + CHECK(vec.at(7) == true); + CHECK(vec.at(8) == false); + CHECK(vec.byte_at(1) == 0x00); + vec.byte_at(1) = 0xff; + vec.byte_at(0) = 0xaa; + CHECK(vec.at(0) == true); + CHECK(vec.at(1) == false); + CHECK(vec.at(2) == true); + CHECK(vec.at(3) == false); + CHECK(vec.at(4) == true); + CHECK(vec.at(5) == false); + CHECK(vec.at(6) == true); + CHECK(vec.at(7) == false); + CHECK(vec.at(8) == true); + CHECK(vec.byte_at(0) == 0xaa); + CHECK(vec.byte_at(1) == 0x80); + })); + types::b_binary const& binary = doc.view()["vector"].get_binary(); + CHECK(binary.sub_type == binary_sub_type::k_vector); + std::array expected_bytes{0x10, 7, 0xaa, 0x80}; + binary_eq_bytes(binary, expected_bytes); + } + + SECTION("validates nonempty vectors with any padding value") { + for (unsigned byte_value = 0; byte_value <= 7; byte_value++) { + uint8_t const bytes[] = {0x10, uint8_t(byte_value), 0x00}; + types::b_binary const binary{binary_sub_type::k_vector, sizeof bytes, bytes}; + vector::accessor vec{binary}; + CHECK(vec.size() == 8 - byte_value); + } + } + + SECTION("rejects binary data from other vector formats") { + auto bytes = GENERATE( + format_specific::bytes_empty(), + format_specific::bytes_empty()); + types::b_binary const binary{binary_sub_type::k_vector, bytes.size(), bytes.data()}; + CHECK_THROWS_WITH_CODE( + vector::accessor{binary}, + bsoncxx::v_noabi::error_code::k_invalid_vector); + } + + SECTION("writes and successfully re-validates vectors of any length") { + for (std::size_t element_count = 0; element_count < 1000; element_count++) { + using namespace builder::basic; + bsoncxx::document::value doc = make_document(kvp("vector", [&](sub_binary sbin) { + auto vec = sbin.allocate(vector::formats::f_packed_bit{}, element_count); + REQUIRE(vec.size() == element_count); + REQUIRE(vec.byte_size() == (element_count + 7u) / 8); + std::fill(vec.byte_begin(), vec.byte_end(), UINT8_C(0xFF)); + CHECK(vec.empty() == (element_count == 0)); + if (!vec.empty()) { + std::for_each(vec.byte_begin(), vec.byte_end() - 1, [&](std::uint8_t value) { + CHECK(value == UINT8_C(0xFF)); + }); + std::for_each(vec.byte_cbegin(), vec.byte_cend() - 1, [&](std::uint8_t value) { + CHECK(value == UINT8_C(0xFF)); + }); + std::size_t padding = vec.byte_size() * std::size_t(8) - vec.size(); + CHECK(vec.byte_back() == std::uint8_t(0xFF << padding)); + } + })); + types::b_binary const& binary = doc.view()["vector"].get_binary(); + CHECK(binary.sub_type == binary_sub_type::k_vector); + vector::accessor vec{binary}; + REQUIRE(vec.size() == element_count); + REQUIRE(vec.byte_size() == (element_count + 7u) / 8); + CHECK(vec.empty() == (element_count == 0u)); + if (!vec.empty()) { + std::for_each( + vec.byte_begin(), vec.byte_end() - 1, [&](std::uint8_t value) { CHECK(value == UINT8_C(0xFF)); }); + std::for_each( + vec.byte_cbegin(), vec.byte_cend() - 1, [&](std::uint8_t value) { CHECK(value == UINT8_C(0xFF)); }); + std::size_t padding = vec.byte_size() * std::size_t(8) - vec.size(); + CHECK(padding == binary.bytes[1]); + CHECK(vec.byte_back() == std::uint8_t(0xFF << padding)); + } + } + } +} + +} // namespace