diff --git a/LibCBOR/Include/CBOR/Concepts.hpp b/LibCBOR/Include/CBOR/Concepts.hpp new file mode 100644 index 0000000..fa5d8a8 --- /dev/null +++ b/LibCBOR/Include/CBOR/Concepts.hpp @@ -0,0 +1,74 @@ +#ifndef LIBCBOR_CONCEPTS_HPP +#define LIBCBOR_CONCEPTS_HPP + +#include "Core.hpp" + +#include +#include +#include +#include + +namespace CBOR +{ + template + concept SimpleType = std::integral || + std::floating_point || + std::same_as, Special> || + std::same_as, bool>; + + template + concept StringLike = std::convertible_to; + + template + concept BinaryLike = std::convertible_to>; + + template + concept EncodableContainer = !StringLike && requires(T t) { + { t.size() } -> std::same_as; + t.begin(); + t.end(); + typename T::value_type; + }; + + template + concept MapContainer = EncodableContainer && requires { + typename T::key_type; + typename T::mapped_type; + }; + + template + concept SequenceContainer = EncodableContainer && !MapContainer; + + namespace Implementation + { + template + struct First; + + template + struct First + { + using Type = T; + }; + + template + struct First + { + using Type = T; + }; + } + + template + using First = Implementation::First::Type; + + template + struct MemberTraits; + + template + struct MemberTraits + { + using ContainingType = T; + using MemberType = MemberT; + }; +} + +#endif // LIBCBOR_CONCEPTS_HPP diff --git a/LibCBOR/Include/CBOR/Decoder.hpp b/LibCBOR/Include/CBOR/Decoder.hpp index b3c957e..41663d4 100644 --- a/LibCBOR/Include/CBOR/Decoder.hpp +++ b/LibCBOR/Include/CBOR/Decoder.hpp @@ -1,10 +1,15 @@ #ifndef LIBCBOR_DECODER_HPP #define LIBCBOR_DECODER_HPP +#include "Concepts.hpp" #include "Core.hpp" +#include #include #include +#include +#include +#include namespace CBOR { @@ -59,6 +64,9 @@ namespace CBOR class Map Map(); class TaggedItem TaggedItem(); + template + T Decode(); + private: friend class Decoder; friend class Array; @@ -183,6 +191,8 @@ namespace CBOR Array &operator=(const Array &) = delete; public: + static constexpr std::size_t Indefinite = std::numeric_limits::max(); + Array(Array &&other); Array &operator=(Array &&other); ~Array() = default; @@ -190,6 +200,8 @@ namespace CBOR bool Done(); Item Next(); + std::size_t Size(); + private: friend class Decoder; @@ -210,6 +222,8 @@ namespace CBOR Map &operator=(const Map &) = delete; public: + static constexpr std::size_t Indefinite = std::numeric_limits::max(); + Map(Map &&other); Map &operator=(Map &&other); ~Map() = default; @@ -217,6 +231,8 @@ namespace CBOR bool Done(); KeyValue Next(); + std::size_t Size(); + private: friend class Decoder; @@ -272,6 +288,10 @@ namespace CBOR class TaggedItem TaggedItem(); class Item AsItem(); + + template + T Decode(); + private: friend class Binary; friend class String; @@ -282,6 +302,136 @@ namespace CBOR std::size_t mCurrent; ConstBuffer mBuffer; }; + + template + T Item::Decode() + { + return mDecoder->Decode(); + } + + template + T Decoder::Decode() + { + if constexpr (std::same_as) { + return Bool(); + } + else if constexpr (std::same_as) { + return Special(); + } + else if constexpr (std::signed_integral) { + if constexpr (sizeof(T) == 1) { + return Int8(); + } + else if constexpr (sizeof(T) == 2) { + return Int16(); + } + else if constexpr (sizeof(T) == 4) { + return Int32(); + } + else if constexpr (sizeof(T) == 8) { + return Int64(); + } + } + else if constexpr (std::unsigned_integral) { + if constexpr (sizeof(T) == 1) { + return Uint8(); + } + else if constexpr (sizeof(T) == 2) { + return Uint16(); + } + else if constexpr (sizeof(T) == 4) { + return Uint32(); + } + else if constexpr (sizeof(T) == 8) { + return Uint64(); + } + } + else if constexpr (std::same_as) { + return Float(); + } + else if constexpr (std::same_as) { + return Double(); + } + else if constexpr (std::same_as) { + return String(); + } + else if constexpr (std::same_as) { + return Binary(); + } + else if constexpr (requires (T a) { DecodeHook(*this, a); }) { + T result; + DecodeHook(*this, result); + return result; + } + else { + static_assert(false, "This type is not decodable (needs a custom DecodeHook)"); + } + } + + template + requires std::is_member_object_pointer_v + class StructMember + { + public: + using KeyType = Key; + using MemberPtrType = ValuePtr; + using ContainingType = MemberTraits::ContainingType; + using MemberType = MemberTraits::MemberType; + + StructMember(Key key, ValuePtr ptr) + : mEnabled(true), mKey(key), mValuePtr(ptr) + {} + + bool Match(const Key &other) const + { + return mEnabled && other == mKey; + } + + void Decode(Item &item, ContainingType &result) + { + result.*mValuePtr = item.Decode(); + mEnabled = false; + } + private: + bool mEnabled; + Key mKey; + ValuePtr mValuePtr; + }; + + template + StructMember(const char *, ValuePtr) -> StructMember; + + template + class StructHelper + { + public: + using ContainingType = MemberTraits>::ContainingType; + + StructHelper(StructMember &&...items) + : mStructItems(std::forward>(items)...) + {} + + void Decode(Decoder &decoder, ContainingType &result) + { + return Decode(decoder.AsItem(), result); + } + + void Decode(Item item, ContainingType &result) + { + CBOR::Map map = item.Map(); + while (!map.Done()) { + CBOR::KeyValue kv = map.Next(); + Key key = kv.Key().Decode(); + Item value = kv.Value(); + std::apply([&key, &value, &result](auto &&...args) { + void(((args.Match(key) ? (args.Decode(value, result), true) : false) || ...)); + }, mStructItems); + } + } + + private: + std::tuple...> mStructItems; + }; } #endif // LIBCBOR_DECODER_HPP diff --git a/LibCBOR/Include/CBOR/DecoderHooks.hpp b/LibCBOR/Include/CBOR/DecoderHooks.hpp new file mode 100644 index 0000000..ddd143a --- /dev/null +++ b/LibCBOR/Include/CBOR/DecoderHooks.hpp @@ -0,0 +1,80 @@ +#ifndef LIBCBOR_DECODERHOOKS_HPP +#define LIBCBOR_DECODERHOOKS_HPP + +#include "Decoder.hpp" + +#include +#include +#include +#include +#include +#include + +namespace CBOR +{ + constexpr + void DecodeHook(Decoder &decoder, std::string &value) + { + String string = decoder.IndefiniteString(); + while (!string.Done()) { + value.append(string.Next()); + } + } + + constexpr + void DecodeHook(Decoder &decoder, std::vector &value) + { + Binary binary = decoder.IndefiniteBinary(); + while(!binary.Done()) { + value.append_range(binary.Next()); + } + } + + template + constexpr + void DecodeHook(Decoder &decoder, std::vector &value) + { + Array array = decoder.Array(); + while (!array.Done()) { + value.push_back(array.Next().Decode()); + } + } + + template + constexpr + void DecodeHook(Decoder &decoder, std::unordered_map &value) + { + Map map = decoder.Map(); + while (!map.Done()) { + KeyValue kv = map.Next(); + value.insert(std::make_pair(kv.Key().Decode(), kv.Value().Decode())); + } + } + + template + constexpr + void DecodeHook(Decoder &decoder, std::map &value) + { + Map map = decoder.Map(); + while (!map.Done()) { + KeyValue kv = map.Next(); + value.insert(std::make_pair(kv.Key().Decode(), kv.Value().Decode())); + } + } + + template + constexpr + void DecodeHook(Decoder &decoder, std::tuple &value) + { + Array array = decoder.Array(); + if (array.Size() != sizeof...(Items)) { + throw DecodeError(std::format("Expected {} elements to fill tuple, got {} instead", + sizeof...(Items), array.Size())); + } + std::apply([&array] (Args &...args) { + ((array.Done(), args = array.Next().Decode()), ...); + }, value); + } +} + +#endif // LIBCBOR_DECODERHOOKS_HPP diff --git a/LibCBOR/Include/CBOR/Encoder.hpp b/LibCBOR/Include/CBOR/Encoder.hpp index 9b22bde..9c42047 100644 --- a/LibCBOR/Include/CBOR/Encoder.hpp +++ b/LibCBOR/Include/CBOR/Encoder.hpp @@ -1,6 +1,7 @@ #ifndef LIBCBOR_ENCODER_HPP #define LIBCBOR_ENCODER_HPP +#include "Concepts.hpp" #include "Core.hpp" #include @@ -39,7 +40,7 @@ namespace CBOR void Write(std::uint16_t value); void Write(std::uint32_t value); void Write(std::uint64_t value); - void Write(Buffer value); + void Write(ConstBuffer value); void Write(std::string_view value); std::size_t Size() const; @@ -51,7 +52,7 @@ namespace CBOR void Write(std::uint16_t value); void Write(std::uint32_t value); void Write(std::uint64_t value); - void Write(Buffer value); + void Write(ConstBuffer value); void Write(std::string_view value); std::size_t Size() const; @@ -67,7 +68,7 @@ namespace CBOR void Write(std::uint16_t value); void Write(std::uint32_t value); void Write(std::uint64_t value); - void Write(Buffer value); + void Write(ConstBuffer value); void Write(std::string_view value); std::size_t Size() const; @@ -108,7 +109,7 @@ namespace CBOR void Encode(float value); void Encode(double value); - void Encode(Buffer value); + void Encode(ConstBuffer value); void Encode(const char *value); void Encode(std::string_view value); @@ -125,9 +126,91 @@ namespace CBOR void EncodeTag(std::uint64_t value); std::size_t Size() const; + private: EncoderBuffer mBuffer; }; + + class Encoder + { + public: + Encoder(EncoderBuffer buffer); + + template + Encoder &Encode(const T &value) + { + if constexpr (SimpleType || StringLike || BinaryLike) { + mEncoder.Encode(value); + } + else if constexpr (SequenceContainer) { + mEncoder.BeginArray(value.size()); + for (const auto &item: value) { + Encode(item); + } + } + else if constexpr (MapContainer) { + mEncoder.BeginMap(value.size()); + for (const auto &[key, value]: value) { + Encode(key); + Encode(value); + } + } + else if constexpr (requires { EncodeHook(*this, value); }) { + EncodeHook(*this, value); + } + else if constexpr (requires { EncodeHook(mEncoder, value); }) { + EncodeHook(mEncoder, value); + } + else { + static_assert(false, "This type is not encodable (needs a custom EncodeHook)"); + } + return *this; + } + + template + Encoder &EncodeArray(Ts &&...values) + { + mEncoder.BeginArray(sizeof...(values)); + (Encode(std::forward(values)), ...); + return *this; + } + + template + requires (sizeof...(Ts) % 2 == 0) + Encoder &EncodeMap(Ts &&...values) + { + mEncoder.BeginMap(sizeof...(values) / 2); + (Encode(std::forward(values)), ...); + return *this; + } + + template + Encoder &EncodeTagged(std::uint64_t tag, const T &value) + { + mEncoder.EncodeTag(tag); + return Encode(value); + } + + template + Encoder &EncodeTaggedArray(std::uint64_t tag, Ts &&...values) + { + mEncoder.EncodeTag(tag); + return EncodeArray(std::forward(values)...); + } + + template + requires (sizeof...(Ts) % 2 == 0) + Encoder &EncodeTaggedMap(std::uint64_t tag, Ts &&...values) + { + mEncoder.EncodeTag(tag); + return EncodeMap(std::forward(values)...); + } + + std::size_t Size() const; + + private: + BasicEncoder mEncoder; + }; } #endif // LIBCBOR_ENCODER_HPP diff --git a/LibCBOR/Source/Decoder.cpp b/LibCBOR/Source/Decoder.cpp index dae31e0..526dda7 100644 --- a/LibCBOR/Source/Decoder.cpp +++ b/LibCBOR/Source/Decoder.cpp @@ -55,8 +55,6 @@ namespace CBOR namespace { - static constexpr std::size_t Indefinite = std::numeric_limits::max(); - std::size_t SpaceLeft(ConstBuffer buffer, std::size_t offset) { if (offset >= buffer.size()) { @@ -851,7 +849,7 @@ namespace CBOR } if (!mCheckedDone) { - throw InvalidUsageError("check whether the indefinite string is done first"); + throw InvalidUsageError("check whether the array is done first"); } mCheckedDone = false; @@ -859,6 +857,27 @@ namespace CBOR return Item(*mDecoder); } + std::size_t Array::Size() + { + if (!mHeaderParsed) { + std::uint8_t header = Consume1B(mDecoder->mBuffer, mDecoder->mCurrent); + mHeaderParsed = true; + if (GetMajorType(header) != MajorType::Array) { + throw TypeMismatchError("array", ToString(GetMajorType(header))); + } + + bool indefinite = GetArgumentPosition(header) == ArgumentPosition::Indefinite; + mSize = indefinite ? Indefinite + : ArgumentValue(header, mDecoder->mBuffer, mDecoder->mCurrent); + + if (!mSize) { + mDone = true; + mCheckedDone = true; + } + } + return mSize; + } + Map::Map(Decoder &decoder) : mHeaderParsed(false) , mDone(false) @@ -935,7 +954,7 @@ namespace CBOR } if (!mCheckedDone) { - throw InvalidUsageError("check whether the indefinite string is done first"); + throw InvalidUsageError("check whether the map is done first"); } mCheckedDone = false; @@ -943,6 +962,27 @@ namespace CBOR return KeyValue(*mDecoder); } + std::size_t Map::Size() + { + if (!mHeaderParsed) { + std::uint8_t header = Consume1B(mDecoder->mBuffer, mDecoder->mCurrent); + mHeaderParsed = true; + if (GetMajorType(header) != MajorType::Map) { + throw TypeMismatchError("map", ToString(GetMajorType(header))); + } + + bool indefinite = GetArgumentPosition(header) == ArgumentPosition::Indefinite; + mSize = indefinite ? Indefinite + : ArgumentValue(header, mDecoder->mBuffer, mDecoder->mCurrent); + + if (!mSize) { + mDone = true; + mCheckedDone = true; + } + } + return mSize; + } + Decoder::Decoder(ConstBuffer buffer) : mCurrent(0), mBuffer(buffer) {} diff --git a/LibCBOR/Source/Encoder.cpp b/LibCBOR/Source/Encoder.cpp index f21a177..aac2827 100644 --- a/LibCBOR/Source/Encoder.cpp +++ b/LibCBOR/Source/Encoder.cpp @@ -120,7 +120,7 @@ namespace CBOR }, mBuffer); } - void EncoderBuffer::Write(Buffer value) + void EncoderBuffer::Write(ConstBuffer value) { std::visit(Overload { [value] (FixedBuffer &buffer) { buffer.Write(value); }, @@ -185,7 +185,7 @@ namespace CBOR mBuffer[mCurrent++] = static_cast((network >> 56) & mask); } - void EncoderBuffer::FixedBuffer::Write(Buffer value) + void EncoderBuffer::FixedBuffer::Write(ConstBuffer value) { EnsureSpace(value.size()); std::memcpy(mBuffer.data() + mCurrent, value.data(), value.size()); @@ -250,7 +250,7 @@ namespace CBOR mBuffer->push_back(static_cast((network >> 56) & mask)); } - void EncoderBuffer::DynamicBuffer::Write(Buffer value) + void EncoderBuffer::DynamicBuffer::Write(ConstBuffer value) { mBuffer->append_range(value); } @@ -447,7 +447,7 @@ namespace CBOR mBuffer.Write(std::bit_cast(value)); } - void BasicEncoder::Encode(Buffer value) + void BasicEncoder::Encode(ConstBuffer value) { WriteHeader(mBuffer, MajorType::Binary, value.size()); mBuffer.Write(value); @@ -508,4 +508,13 @@ namespace CBOR { return mBuffer.Size(); } + + Encoder::Encoder(EncoderBuffer buffer) + : mEncoder(std::move(buffer)) + {} + + std::size_t Encoder::Size() const + { + return mEncoder.Size(); + } } diff --git a/Tests/Main.cpp b/Tests/Main.cpp index 67653a7..a44c00c 100644 --- a/Tests/Main.cpp +++ b/Tests/Main.cpp @@ -1,11 +1,14 @@ -#include "CBOR/Decoder.hpp" -#include "CBOR/Encoder.hpp" -#include "CBOR/Printer.hpp" +#include +#include +#include // IWYU pragma: keep +#include +#include + #include +#include // IWYU pragma: keep #include #include #include -#include #include #include @@ -18,166 +21,47 @@ struct SomeStruct std::int64_t slots; std::uint32_t times; std::vector tools; + + constexpr auto operator<=>(const SomeStruct &) const = default; }; -std::size_t Encode(const SomeStruct &value, auto &buffer) +void EncodeHook(CBOR::Encoder &enc, const SomeStruct &value) { - CBOR::BasicEncoder enc(buffer); - enc.EncodeTag(15'000); - - enc.BeginMap(7); - - enc.Encode("name"); - enc.Encode(value.name); - - enc.Encode("speed"); - enc.Encode(value.speed); - - enc.Encode("fov"); - enc.Encode(value.fov); - - enc.Encode("thing"); - enc.Encode(value.thing); - - enc.Encode("slots"); - enc.Encode(value.slots); - - enc.Encode("times"); - enc.Encode(value.times); - - enc.Encode("tools"); - enc.BeginIndefiniteArray(); - for (std::string_view tool: value.tools) { - enc.Encode(tool); - } - enc.End(); - - return enc.Size(); + enc.EncodeTaggedMap( + 15'000, + "name", value.name, + "speed", value.speed, + "fov", value.fov, + "thing", value.thing, + "slots", value.slots, + "times", value.times, + "tools", value.tools + ); } -SomeStruct Decode1(std::span buffer) +void DecodeHook(CBOR::Decoder &dec, SomeStruct &value) { - SomeStruct result; - - CBOR::Decoder dec(buffer); + CBOR::StructHelper helper { + CBOR::StructMember { "name", &SomeStruct::name }, + CBOR::StructMember { "speed", &SomeStruct::speed }, + CBOR::StructMember { "fov", &SomeStruct::fov }, + CBOR::StructMember { "thing", &SomeStruct::thing }, + CBOR::StructMember { "slots", &SomeStruct::slots }, + CBOR::StructMember { "times", &SomeStruct::times }, + CBOR::StructMember { "tools", &SomeStruct::tools }, + }; CBOR::TaggedItem tagged = dec.TaggedItem(); if (tagged.Tag() != 15'000) { - throw std::runtime_error("test error: could not extract object tag"); - } - CBOR::Map object = tagged.Item().Map(); - while (!object.Done()) { - CBOR::KeyValue kv = object.Next(); - std::string_view key = kv.Key().String(); - CBOR::Item value = kv.Value(); - if (key == "name") { - result.name = value.String(); - } - else if (key == "speed") { - result.speed = value.Double(); - } - else if (key == "fov") { - result.fov = value.Float(); - } - else if (key == "thing") { - result.thing = value.Int8(); - } - else if (key == "slots") { - result.slots = value.Int64(); - } - else if (key == "times") { - result.times = value.Uint32(); - } - else if (key == "tools") { - CBOR::Array tools = value.Array(); - while(!tools.Done()) { - result.tools.push_back(std::string(tools.Next().String())); - } - } - } - - return result; -} - -SomeStruct Decode2(std::span buffer) -{ - SomeStruct result; - - CBOR::Decoder dec(buffer); - CBOR::TaggedItem tagged = dec.TaggedItem(); - if (tagged.Tag() != 15'000) { - throw std::runtime_error("test error: could not extract object tag"); - } - CBOR::Map object = tagged.Item().Map(); - while (!object.Done()) { - CBOR::KeyValue kv = object.Next(); - std::string_view key = kv.Key().String(); - CBOR::Item value = kv.Value(); - if (key == "name") { - result.name = value.String(); - } - else if (key == "speed") { - result.speed = value.Double(); - } - else if (key == "fov") { - result.fov = value.Float(); - } - else if (key == "thing") { - result.thing = value.Int8(); - } - else if (key == "slots") { - result.slots = value.Int64(); - } - else if (key == "times") { - result.times = value.Uint32(); - } - else if (key == "tools") { - CBOR::Array tools = value.Array(); - while(!tools.Done()) { - result.tools.push_back(""); - CBOR::String tool = tools.Next().IndefiniteString(); - while (!tool.Done()) { - result.tools.back().append(tool.Next()); - } - } - } - } - - return result; -} - -void Compare(const SomeStruct &s1, const SomeStruct &s2) -{ - if (s1.name != s2.name) { - throw std::runtime_error("test error: names are not the same"); - } - if (s1.speed != s2.speed) { - throw std::runtime_error("test error: speed is not the same"); - } - if (s1.fov != s2.fov) { - throw std::runtime_error("test error: fovs are not the same"); - } - if (s1.thing != s2.thing) { - throw std::runtime_error("test error: things are not the same"); - } - if (s1.slots != s2.slots) { - throw std::runtime_error("test error: slots are not the same"); - } - if (s1.times != s2.times) { - throw std::runtime_error("test error: times are not the same"); - } - for (const auto &[t1, t2]: std::ranges::views::zip(s1.tools, s2.tools)) { - if (t1 != t2) { - throw std::runtime_error("test error: some tools are not the same"); - } + throw std::runtime_error("Expected item with tag value 15000"); } + helper.Decode(tagged.Item(), value); } int main() { using namespace std::string_view_literals; - //std::array buffer {}; - std::vector buffer; + std::array buffer {}; SomeStruct expected { .name = "Player1", @@ -196,23 +80,20 @@ int main() }; try { - std::size_t encodedSize = Encode(expected, buffer); - std::println("Encoded size: {}", encodedSize); + CBOR::Encoder enc(buffer); + enc.Encode(expected); - std::print("Encoded hex: "); - for (std::size_t i = 0; i < encodedSize; ++i) { - std::print("{:02X} ", buffer[i]); + CBOR::ConstBuffer encoded(buffer.data(), enc.Size()); + + CBOR::Decoder dec(encoded); + SomeStruct res = dec.Decode(); + + if (res != expected) { + throw std::runtime_error("test error: the encode/decode round trip should be lossless"); } - std::println(); - SomeStruct result1 = Decode1(std::span(buffer.data(), encodedSize)); - SomeStruct result2 = Decode2(std::span(buffer.data(), encodedSize)); + CBOR::Print(std::cout, encoded); - std::println("JSON-esque serialization:"); - CBOR::Print(std::cout, std::span(buffer.data(), encodedSize)); - - Compare(expected, result1); - Compare(expected, result2); std::println("The test has been completed successfully."); } catch (const std::exception &e) {