From 9a0dddc5d2536d6d1960a2fb89027e3849794306 Mon Sep 17 00:00:00 2001
From: Julian Becker <becker.julian@gmail.com>
Date: Sat, 15 Sep 2018 03:08:50 +0200
Subject: [PATCH] BSON: Object with single boolean

---
 .../nlohmann/detail/input/binary_reader.hpp   |  60 +++++++-
 .../nlohmann/detail/output/binary_writer.hpp  |  44 +++++-
 .../detail/output/output_adapters.hpp         |  43 ++++++
 single_include/nlohmann/json.hpp              | 141 +++++++++++++++++-
 test/src/unit-bson.cpp                        |  48 ++++++
 5 files changed, 330 insertions(+), 6 deletions(-)

diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp
index 9b00cc49..54833cc3 100644
--- a/include/nlohmann/detail/input/binary_reader.hpp
+++ b/include/nlohmann/detail/input/binary_reader.hpp
@@ -125,19 +125,75 @@ class binary_reader
 
   private:
 
+    template<class OutputIt, class UnaryPredicate, class Gen>
+    OutputIt generate_until(OutputIt&& d_first, UnaryPredicate&& pred, Gen&& gen)
+    {
+        for (auto x = gen(); !pred(x); x = gen())
+        {
+            *d_first++ = x;
+        }
+
+        return d_first;
+    }
+
+    /*!
+    @param[in] len  the length of the array or std::size_t(-1) for an
+                    array of indefinite size
+    @return whether array creation completed
+    */
+    bool get_bson_str(string_t& result)
+    {
+        bool success = true;
+        generate_until(std::back_inserter(result), [](char c)
+        {
+            return c == 0x00;
+        }, [this, &success]
+        {
+            get();
+            if (JSON_UNLIKELY(unexpect_eof()))
+            {
+                success = false;
+            }
+            return static_cast<char>(current);
+        });
+        return success;
+    }
+
+
     bool parse_bson_internal()
     {
         std::int32_t documentSize;
         get_number_little_endian(documentSize);
 
-        if (not JSON_UNLIKELY(sax->start_object(documentSize - 5)))
+        if (not JSON_UNLIKELY(sax->start_object(-1)))
         {
             return false;
         }
 
-        const auto result = sax->end_object();
+        while (auto entry_type = get())
+        {
+            switch (entry_type)
+            {
+                case 0x01:
+                {
+                    string_t key;
+                    get_bson_str(key);
+                    sax->key(key);
+                    sax->boolean(static_cast<bool>(get()));
+                } break;
+                case 0x08:
+                {
+                    string_t key;
+                    get_bson_str(key);
+                    sax->key(key);
+                    sax->boolean(static_cast<bool>(get()));
+                } break;
+            }
+        }
 
         get();
+        const auto result = sax->end_object();
+
         return result;
     }
 
diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp
index 4b9a1343..98e6104b 100644
--- a/include/nlohmann/detail/output/binary_writer.hpp
+++ b/include/nlohmann/detail/output/binary_writer.hpp
@@ -677,6 +677,16 @@ class binary_writer
     }
 
 
+    std::size_t write_bson_object_entry(const typename BasicJsonType::string_t& name, const BasicJsonType& j)
+    {
+        oa->write_character(static_cast<CharType>(0x08)); // boolean
+        oa->write_characters(
+            reinterpret_cast<const CharType*>(name.c_str()),
+            name.size() + 1u);
+        oa->write_character(j.m_value.boolean ? static_cast<CharType>(0x01) : static_cast<CharType>(0x00));
+        return /*id*/ 1ul + name.size() + 1u + /*boolean value*/ 1u;
+    }
+
     /*!
     @param[in] j  JSON value to serialize
     @pre       j.type() == value_t::object
@@ -684,8 +694,16 @@ class binary_writer
     void write_bson_object(const BasicJsonType& j)
     {
         assert(j.type() == value_t::object);
-        write_number_little_endian(5);
+        auto document_size_offset = oa->reserve_characters(4ul);
+        std::int32_t document_size = 5ul;
+
+        for (const auto& el : *j.m_value.object)
+        {
+            document_size += write_bson_object_entry(el.first, el.second);
+        }
+
         oa->write_character(static_cast<CharType>(0x00));
+        write_number_little_endian_at(document_size_offset, document_size);
     }
 
     /*!
@@ -759,6 +777,30 @@ class binary_writer
         oa->write_characters(vec.data(), sizeof(NumberType));
     }
 
+    /*
+    @brief write a number to output in little endian format
+
+    @param[in] offset The offset where to start writing
+    @param[in] n number of type @a NumberType
+    @tparam NumberType the type of the number
+    */
+    template<typename NumberType>
+    void write_number_little_endian_at(std::size_t offset, const NumberType n)
+    {
+        // step 1: write number to array of length NumberType
+        std::array<CharType, sizeof(NumberType)> vec;
+        std::memcpy(vec.data(), &n, sizeof(NumberType));
+
+        // step 2: write array to output (with possible reordering)
+        if (!is_little_endian)
+        {
+            // reverse byte order prior to conversion if necessary
+            std::reverse(vec.begin(), vec.end());
+        }
+
+        oa->write_characters_at(offset, vec.data(), sizeof(NumberType));
+    }
+
 
     // UBJSON: write number (floating point)
     template<typename NumberType, typename std::enable_if<
diff --git a/include/nlohmann/detail/output/output_adapters.hpp b/include/nlohmann/detail/output/output_adapters.hpp
index ff86a6e1..64960011 100644
--- a/include/nlohmann/detail/output/output_adapters.hpp
+++ b/include/nlohmann/detail/output/output_adapters.hpp
@@ -18,6 +18,8 @@ template<typename CharType> struct output_adapter_protocol
 {
     virtual void write_character(CharType c) = 0;
     virtual void write_characters(const CharType* s, std::size_t length) = 0;
+    virtual void write_characters_at(std::size_t position, const CharType* s, std::size_t length) = 0;
+    virtual std::size_t reserve_characters(std::size_t length) = 0;
     virtual ~output_adapter_protocol() = default;
 };
 
@@ -42,6 +44,18 @@ class output_vector_adapter : public output_adapter_protocol<CharType>
         std::copy(s, s + length, std::back_inserter(v));
     }
 
+    void write_characters_at(std::size_t position, const CharType* s, std::size_t length) override
+    {
+        std::copy(s, s + length, std::begin(v) + position);
+    }
+
+    std::size_t reserve_characters(std::size_t length) override
+    {
+        const auto position = v.size();
+        std::fill_n(std::back_inserter(v), length, static_cast<CharType>(0x00));
+        return position;
+    }
+
   private:
     std::vector<CharType>& v;
 };
@@ -63,6 +77,22 @@ class output_stream_adapter : public output_adapter_protocol<CharType>
         stream.write(s, static_cast<std::streamsize>(length));
     }
 
+    void write_characters_at(std::size_t position, const CharType* s, std::size_t length) override
+    {
+        const auto orig_offset = stream.tellp();
+        stream.seekp(static_cast<typename std::basic_ostream<CharType>::pos_type>(position));
+        stream.write(s, static_cast<std::streamsize>(length));
+        stream.seekp(orig_offset);
+    }
+
+    std::size_t reserve_characters(std::size_t length) override
+    {
+        const auto position = stream.tellp();
+        std::vector<CharType> empty(length, static_cast<CharType>(0));
+        stream.write(empty.data(), length);
+        return static_cast<std::size_t>(position);
+    }
+
   private:
     std::basic_ostream<CharType>& stream;
 };
@@ -84,6 +114,19 @@ class output_string_adapter : public output_adapter_protocol<CharType>
         str.append(s, length);
     }
 
+    void write_characters_at(std::size_t position, const CharType* s, std::size_t length) override
+    {
+        std::copy(s, s + length, std::begin(str) + position);
+    }
+
+    std::size_t reserve_characters(std::size_t length) override
+    {
+        const auto position = str.size();
+        std::fill_n(std::back_inserter(str), length, static_cast<CharType>(0x00));
+        return position;
+    }
+
+
   private:
     StringType& str;
 };
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index a68c64a9..6b81d354 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -5838,6 +5838,8 @@ template<typename CharType> struct output_adapter_protocol
 {
     virtual void write_character(CharType c) = 0;
     virtual void write_characters(const CharType* s, std::size_t length) = 0;
+    virtual void write_characters_at(std::size_t position, const CharType* s, std::size_t length) = 0;
+    virtual std::size_t reserve_characters(std::size_t length) = 0;
     virtual ~output_adapter_protocol() = default;
 };
 
@@ -5862,6 +5864,18 @@ class output_vector_adapter : public output_adapter_protocol<CharType>
         std::copy(s, s + length, std::back_inserter(v));
     }
 
+    void write_characters_at(std::size_t position, const CharType* s, std::size_t length) override
+    {
+        std::copy(s, s + length, std::begin(v) + position);
+    }
+
+    std::size_t reserve_characters(std::size_t length) override
+    {
+        const auto position = v.size();
+        std::fill_n(std::back_inserter(v), length, static_cast<CharType>(0x00));
+        return position;
+    }
+
   private:
     std::vector<CharType>& v;
 };
@@ -5883,6 +5897,22 @@ class output_stream_adapter : public output_adapter_protocol<CharType>
         stream.write(s, static_cast<std::streamsize>(length));
     }
 
+    void write_characters_at(std::size_t position, const CharType* s, std::size_t length) override
+    {
+        const auto orig_offset = stream.tellp();
+        stream.seekp(static_cast<typename std::basic_ostream<CharType>::pos_type>(position));
+        stream.write(s, static_cast<std::streamsize>(length));
+        stream.seekp(orig_offset);
+    }
+
+    std::size_t reserve_characters(std::size_t length) override
+    {
+        const auto position = stream.tellp();
+        std::vector<CharType> empty(length, static_cast<CharType>(0));
+        stream.write(empty.data(), length);
+        return static_cast<std::size_t>(position);
+    }
+
   private:
     std::basic_ostream<CharType>& stream;
 };
@@ -5904,6 +5934,19 @@ class output_string_adapter : public output_adapter_protocol<CharType>
         str.append(s, length);
     }
 
+    void write_characters_at(std::size_t position, const CharType* s, std::size_t length) override
+    {
+        std::copy(s, s + length, std::begin(str) + position);
+    }
+
+    std::size_t reserve_characters(std::size_t length) override
+    {
+        const auto position = str.size();
+        std::fill_n(std::back_inserter(str), length, static_cast<CharType>(0x00));
+        return position;
+    }
+
+
   private:
     StringType& str;
 };
@@ -6066,19 +6109,69 @@ class binary_reader
 
   private:
 
+    template<class OutputIt, class UnaryPredicate, class Gen>
+    OutputIt generate_until(OutputIt&& d_first, UnaryPredicate&& pred, Gen&& gen)
+    {
+        for (auto x = gen(); !pred(x); x = gen())
+        {
+            *d_first++ = x;
+        }
+
+        return d_first;
+    }
+
+    /*!
+    @param[in] len  the length of the array or std::size_t(-1) for an
+                    array of indefinite size
+    @return whether array creation completed
+    */
+    bool get_bson_str(string_t& result)
+    {
+        bool success = true;
+        generate_until(std::back_inserter(result), [](char c)
+        {
+            return c == 0x00;
+        }, [this, &success]
+        {
+            get();
+            if (JSON_UNLIKELY(unexpect_eof()))
+            {
+                success = false;
+            }
+            return static_cast<char>(current);
+        });
+        return success;
+    }
+
+
     bool parse_bson_internal()
     {
         std::int32_t documentSize;
         get_number_little_endian(documentSize);
 
-        if (not JSON_UNLIKELY(sax->start_object(documentSize - 5)))
+        if (not JSON_UNLIKELY(sax->start_object(-1)))
         {
             return false;
         }
 
-        const auto result = sax->end_object();
+        while (auto entry_type = get())
+        {
+            switch (entry_type)
+            {
+                case 0x08:
+                {
+                    string_t key;
+                    get_bson_str(key);
+                    sax->key(key);
+                    sax->boolean(static_cast<bool>(get()));
+                }
+                break;
+            }
+        }
 
         get();
+        const auto result = sax->end_object();
+
         return result;
     }
 
@@ -8369,6 +8462,16 @@ class binary_writer
     }
 
 
+    std::size_t write_bson_object_entry(const typename BasicJsonType::string_t& name, const BasicJsonType& j)
+    {
+        oa->write_character(static_cast<CharType>(0x08)); // boolean
+        oa->write_characters(
+            reinterpret_cast<const CharType*>(name.c_str()),
+            name.size() + 1u);
+        oa->write_character(j.m_value.boolean ? static_cast<CharType>(0x01) : static_cast<CharType>(0x00));
+        return /*id*/ 1ul + name.size() + 1u + /*boolean value*/ 1u;
+    }
+
     /*!
     @param[in] j  JSON value to serialize
     @pre       j.type() == value_t::object
@@ -8376,8 +8479,16 @@ class binary_writer
     void write_bson_object(const BasicJsonType& j)
     {
         assert(j.type() == value_t::object);
-        write_number_little_endian(5);
+        auto document_size_offset = oa->reserve_characters(4ul);
+        std::int32_t document_size = 5ul;
+
+        for (const auto& el : *j.m_value.object)
+        {
+            document_size += write_bson_object_entry(el.first, el.second);
+        }
+
         oa->write_character(static_cast<CharType>(0x00));
+        write_number_little_endian_at(document_size_offset, document_size);
     }
 
     /*!
@@ -8451,6 +8562,30 @@ class binary_writer
         oa->write_characters(vec.data(), sizeof(NumberType));
     }
 
+    /*
+    @brief write a number to output in little endian format
+
+    @param[in] offset The offset where to start writing
+    @param[in] n number of type @a NumberType
+    @tparam NumberType the type of the number
+    */
+    template<typename NumberType>
+    void write_number_little_endian_at(std::size_t offset, const NumberType n)
+    {
+        // step 1: write number to array of length NumberType
+        std::array<CharType, sizeof(NumberType)> vec;
+        std::memcpy(vec.data(), &n, sizeof(NumberType));
+
+        // step 2: write array to output (with possible reordering)
+        if (!is_little_endian)
+        {
+            // reverse byte order prior to conversion if necessary
+            std::reverse(vec.begin(), vec.end());
+        }
+
+        oa->write_characters_at(offset, vec.data(), sizeof(NumberType));
+    }
+
 
     // UBJSON: write number (floating point)
     template<typename NumberType, typename std::enable_if<
diff --git a/test/src/unit-bson.cpp b/test/src/unit-bson.cpp
index 78a30139..9a710707 100644
--- a/test/src/unit-bson.cpp
+++ b/test/src/unit-bson.cpp
@@ -111,5 +111,53 @@ TEST_CASE("BSON")
             CHECK(json::from_bson(result) == j);
             CHECK(json::from_bson(result, true, false) == j);
         }
+
+        SECTION("non-empty object with bool")
+        {
+            json j =
+            {
+                { "entry", true }
+            };
+
+            std::vector<uint8_t> expected =
+            {
+                0x0D, 0x00, 0x00, 0x00, // size (little endian)
+                0x08,               // entry: boolean
+                'e', 'n', 't', 'r', 'y', '\x00',
+                0x01,           // value = true
+                0x00                    // end marker
+            };
+
+            const auto result = json::to_bson(j);
+            CHECK(result == expected);
+
+            // roundtrip
+            CHECK(json::from_bson(result) == j);
+            CHECK(json::from_bson(result, true, false) == j);
+        }
+
+        // SECTION("non-empty object with double")
+        // {
+        //     json j =
+        //     {
+        //         { "entry", true }
+        //     };
+
+        //     std::vector<uint8_t> expected =
+        //     {
+        //         0x14, 0x00, 0x00, 0x00, // size (little endian)
+        //         0x01, /// entry: double
+        //         'e', 'n', 't', 'r', 'y', '\x00',
+        //         0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x10, 0x40,
+        //         0x00 // end marker
+        //     };
+
+        //     const auto result = json::to_bson(j);
+        //     CHECK(result == expected);
+
+        //     // roundtrip
+        //     //CHECK(json::from_bson(result) == j);
+        //     //CHECK(json::from_bson(result, true, false) == j);
+        // }
     }
 }