diff --git a/src/coders/binary_json.cpp b/src/coders/binary_json.cpp index 9b8bac22..fd5d2c23 100644 --- a/src/coders/binary_json.cpp +++ b/src/coders/binary_json.cpp @@ -14,17 +14,24 @@ static void to_binary(ByteBuilder& builder, const Value& value) { case Type::none: throw std::runtime_error("none value is not implemented"); case Type::map: { - auto bytes = to_binary(std::get(value).get()); + const auto bytes = to_binary(std::get(value).get()); builder.put(bytes.data(), bytes.size()); break; } case Type::list: builder.put(BJSON_TYPE_LIST); - for (auto& element : std::get(value)->values) { + for (const auto& element : std::get(value)->values) { to_binary(builder, element); } builder.put(BJSON_END); break; + case Type::bytes: { + const auto& bytes = std::get(value).get(); + builder.put(BJSON_TYPE_BYTES); + builder.putInt32(bytes->size()); + builder.put(bytes->data(), bytes->size()); + break; + } case Type::integer: { auto val = std::get(value); if (val >= 0 && val <= 255) { @@ -113,11 +120,25 @@ static Value value_from_binary(ByteReader& reader) { return (typecode - BJSON_TYPE_FALSE) != 0; case BJSON_TYPE_STRING: return reader.getString(); - default: - throw std::runtime_error( - "type " + std::to_string(typecode) + " is not supported" - ); + case BJSON_TYPE_NULL: + return NONE; + case BJSON_TYPE_BYTES: { + int32_t size = reader.getInt32(); + if (size < 0) { + throw std::runtime_error( + "invalid byte-buffer size "+std::to_string(size)); + } + if (size > reader.remaining()) { + throw std::runtime_error( + "buffer_size > remaining_size "+std::to_string(size)); + } + auto bytes = std::make_shared(reader.pointer(), size); + reader.skip(size); + return bytes; + } } + throw std::runtime_error( + "type support not implemented for <"+std::to_string(typecode)+">"); } static std::unique_ptr array_from_binary(ByteReader& reader) { diff --git a/src/coders/byte_utils.cpp b/src/coders/byte_utils.cpp index d28e31ce..84e33a30 100644 --- a/src/coders/byte_utils.cpp +++ b/src/coders/byte_utils.cpp @@ -9,7 +9,7 @@ void ByteBuilder::put(ubyte b) { } void ByteBuilder::putCStr(const char* str) { - size_t size = strlen(str) + 1; + size_t size = std::strlen(str) + 1; buffer.reserve(buffer.size() + size); for (size_t i = 0; i < size; i++) { buffer.push_back(str[i]); @@ -188,7 +188,7 @@ const char* ByteReader::getCString() { } std::string ByteReader::getString() { - uint32_t length = (uint32_t)getInt32(); + uint32_t length = static_cast(getInt32()); if (pos + length > size) { throw std::runtime_error("buffer underflow"); } @@ -202,6 +202,10 @@ bool ByteReader::hasNext() const { return pos < size; } +size_t ByteReader::remaining() const { + return size - pos; +} + const ubyte* ByteReader::pointer() const { return data + pos; } diff --git a/src/coders/byte_utils.hpp b/src/coders/byte_utils.hpp index 20c5d150..3aefa1f0 100644 --- a/src/coders/byte_utils.hpp +++ b/src/coders/byte_utils.hpp @@ -74,6 +74,8 @@ public: std::string getString(); /// @return true if there is at least one byte remains bool hasNext() const; + /// @return Number of remaining bytes in buffer + size_t remaining() const; const ubyte* pointer() const; void skip(size_t n); diff --git a/src/coders/json.cpp b/src/coders/json.cpp index ea2d33c9..a15a40a2 100644 --- a/src/coders/json.cpp +++ b/src/coders/json.cpp @@ -63,6 +63,10 @@ void stringifyValue( stringifyObj(map->get(), ss, indent, indentstr, nice); } else if (auto listptr = std::get_if(&value)) { stringifyArr(listptr->get(), ss, indent, indentstr, nice); + } else if (auto bytesptr = std::get_if(&value)) { + auto bytes = bytesptr->get(); + ss << "\"" << util::base64_encode(bytes->data(), bytes->size()); + ss << "\""; } else if (auto flag = std::get_if(&value)) { ss << (*flag ? "true" : "false"); } else if (auto num = std::get_if(&value)) { diff --git a/src/data/dynamic.cpp b/src/data/dynamic.cpp index f49a0aee..42100735 100644 --- a/src/data/dynamic.cpp +++ b/src/data/dynamic.cpp @@ -249,6 +249,12 @@ List_sptr Map::list(const std::string& key) const { return nullptr; } +ByteBuffer_sptr Map::bytes(const std::string& key) const { + auto found = values.find(key); + if (found != values.end()) return std::get(found->second); + return nullptr; +} + void Map::flag(const std::string& key, bool& dst) const { dst = get(key, dst); } diff --git a/src/data/dynamic.hpp b/src/data/dynamic.hpp index 9525a898..ed7a0786 100644 --- a/src/data/dynamic.hpp +++ b/src/data/dynamic.hpp @@ -11,7 +11,16 @@ #include "dynamic_fwd.hpp" namespace dynamic { - enum class Type { none = 0, map, list, string, number, boolean, integer }; + enum class Type { + none = 0, + map, + list, + bytes, + string, + number, + boolean, + integer + }; const std::string& type_name(const Value& value); List_sptr create_list(std::initializer_list values = {}); @@ -59,10 +68,13 @@ namespace dynamic { } List& put(std::unique_ptr value) { - return put(Map_sptr(value.release())); + return put(Map_sptr(std::move(value))); } List& put(std::unique_ptr value) { - return put(List_sptr(value.release())); + return put(List_sptr(std::move(value))); + } + List& put(std::unique_ptr value) { + return put(ByteBuffer_sptr(std::move(value))); } List& put(const Value& value); @@ -115,6 +127,7 @@ namespace dynamic { void num(const std::string& key, double& dst) const; Map_sptr map(const std::string& key) const; List_sptr list(const std::string& key) const; + ByteBuffer_sptr bytes(const std::string& key) const; void flag(const std::string& key, bool& dst) const; Map& put(std::string key, std::unique_ptr value) { @@ -144,6 +157,13 @@ namespace dynamic { Map& put(std::string key, bool value) { return put(key, Value(static_cast(value))); } + Map& put(const std::string& key, const ByteBuffer* bytes) { + return put(key, std::make_unique( + bytes->data(), bytes->size())); + } + Map& put(std::string key, const ubyte* bytes, size_t size) { + return put(key, std::make_unique(bytes, size)); + } Map& put(std::string key, const char* value) { return put(key, Value(value)); } diff --git a/src/data/dynamic_fwd.hpp b/src/data/dynamic_fwd.hpp index 148e244b..c4f8eb87 100644 --- a/src/data/dynamic_fwd.hpp +++ b/src/data/dynamic_fwd.hpp @@ -6,13 +6,16 @@ #include #include "typedefs.hpp" +#include "util/Buffer.hpp" namespace dynamic { class Map; class List; + using ByteBuffer = util::Buffer; using Map_sptr = std::shared_ptr; using List_sptr = std::shared_ptr; + using ByteBuffer_sptr = std::shared_ptr; struct none {}; @@ -22,6 +25,7 @@ namespace dynamic { none, Map_sptr, List_sptr, + ByteBuffer_sptr, std::string, number_t, bool, diff --git a/src/util/Buffer.hpp b/src/util/Buffer.hpp new file mode 100644 index 00000000..68e185c6 --- /dev/null +++ b/src/util/Buffer.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +namespace util { + template + class Buffer { + std::unique_ptr ptr; + size_t length; + public: + Buffer(size_t length) + : ptr(std::make_unique(length)), length(length) { + } + + Buffer(std::unique_ptr ptr, size_t length) + : ptr(std::move(ptr)), length(length) {} + + Buffer(const T* src, size_t length) + : ptr(std::make_unique(length)), length(length) { + std::memcpy(ptr.get(), src, length); + } + + T& operator[](long long index) { + return ptr[index]; + } + + const T& operator[](long long index) const { + return ptr[index]; + } + + T* data() { + return ptr.get(); + } + + const T* data() const { + return ptr.get(); + } + + size_t size() const { + return length; + } + + std::unique_ptr release() { + return std::move(ptr); + } + + Buffer clone() const { + return Buffer(ptr.get(), length); + } + + void resizeFast(size_t size) { + length = size; + } + }; +} diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 97ca4786..4452301b 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -319,8 +319,8 @@ std::string util::mangleid(uint64_t value) { return ss.str(); } -std::vector util::base64_decode(const char* str, size_t size) { - std::vector bytes((size / 4) * 3); +util::Buffer util::base64_decode(const char* str, size_t size) { + util::Buffer bytes((size / 4) * 3); ubyte* dst = bytes.data(); for (size_t i = 0; i < size;) { ubyte a = base64_decode_char(ubyte(str[i++])); @@ -335,12 +335,12 @@ std::vector util::base64_decode(const char* str, size_t size) { size_t outsize = bytes.size(); if (str[size - 1] == '=') outsize--; if (str[size - 2] == '=') outsize--; - bytes.resize(outsize); + bytes.resizeFast(outsize); } return bytes; } -std::vector util::base64_decode(const std::string& str) { +util::Buffer util::base64_decode(const std::string& str) { return base64_decode(str.c_str(), str.size()); } diff --git a/src/util/stringutil.hpp b/src/util/stringutil.hpp index 38e7cf89..21d58e5e 100644 --- a/src/util/stringutil.hpp +++ b/src/util/stringutil.hpp @@ -4,6 +4,7 @@ #include #include "typedefs.hpp" +#include "util/Buffer.hpp" namespace util { /// @brief Function used for string serialization in text formats @@ -56,8 +57,8 @@ namespace util { std::wstring to_wstring(double x, int precision); std::string base64_encode(const ubyte* data, size_t size); - std::vector base64_decode(const char* str, size_t size); - std::vector base64_decode(const std::string& str); + util::Buffer base64_decode(const char* str, size_t size); + util::Buffer base64_decode(const std::string& str); std::string mangleid(uint64_t value); diff --git a/test/coders/binary_json.cpp b/test/coders/binary_json.cpp new file mode 100644 index 00000000..2a3ecea1 --- /dev/null +++ b/test/coders/binary_json.cpp @@ -0,0 +1,38 @@ +#include + +#include "data/dynamic.hpp" +#include "coders/binary_json.hpp" + +TEST(BJSON, EncodeDecode) { + const std::string name = "JSON-encoder"; + const int bytesSize = 5000; + const int year = 2019; + const float score = 3.141592; + dynamic::ByteBuffer srcBytes(bytesSize); + for (int i = 0; i < bytesSize; i ++) { + srcBytes[i] = rand(); + } + + std::vector bjsonBytes; + { + dynamic::Map map; + map.put("name", name); + map.put("year", year); + map.put("score", score); + map.put("data", &srcBytes); + + bjsonBytes = json::to_binary(&map, false); + } + { + auto map = json::from_binary(bjsonBytes.data(), bjsonBytes.size()); + EXPECT_EQ(map->get("name"), name); + EXPECT_EQ(map->get("year"), year); + EXPECT_FLOAT_EQ(map->get("score"), score); + auto bytesptr = map->bytes("data"); + const auto& bytes = *bytesptr; + EXPECT_EQ(bytes.size(), bytesSize); + for (int i = 0; i < bytesSize; i++) { + EXPECT_EQ(bytes[i], srcBytes[i]); + } + } +} diff --git a/test/coders/json.cpp b/test/coders/json.cpp new file mode 100644 index 00000000..5738559b --- /dev/null +++ b/test/coders/json.cpp @@ -0,0 +1,39 @@ +#include + +#include "coders/json.hpp" +#include "util/stringutil.hpp" + +TEST(JSON, EncodeDecode) { + const std::string name = "JSON-encoder"; + const int bytesSize = 20; + const int year = 2019; + const float score = 3.141592; + dynamic::ByteBuffer srcBytes(bytesSize); + for (int i = 0; i < bytesSize; i ++) { + srcBytes[i] = rand(); + } + + std::string text; + { + dynamic::Map map; + map.put("name", name); + map.put("year", year); + map.put("score", score); + map.put("data", &srcBytes); + + text = json::stringify(&map, false, ""); + } + { + auto map = json::parse(text); + EXPECT_EQ(map->get("name"), name); + EXPECT_EQ(map->get("year"), year); + EXPECT_FLOAT_EQ(map->get("score"), score); + auto b64string = map->get("data"); + + auto bytes = util::base64_decode(b64string); + EXPECT_EQ(bytes.size(), bytesSize); + for (int i = 0; i < bytesSize; i++) { + EXPECT_EQ(bytes[i], srcBytes[i]); + } + } +}