commit 4159fc4643b1898b3c839e73a8dd93c86bab91a4 Author: TennesseeTrash Date: Sun Sep 14 13:58:26 2025 +0200 Basic project structure, basic encoder implementation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc95448 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.cache/ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..61beea3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.25) + +project(LibCBOR + LANGUAGES CXX +) + +add_subdirectory(LibCBOR) +if(${PROJECT_IS_TOP_LEVEL}) + add_subdirectory(Tests) +endif() diff --git a/LibCBOR/CMakeLists.txt b/LibCBOR/CMakeLists.txt new file mode 100644 index 0000000..83061dc --- /dev/null +++ b/LibCBOR/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(LibCBOR STATIC) + +target_compile_features(LibCBOR + PUBLIC + cxx_std_23 +) + +target_include_directories(LibCBOR + PUBLIC + "Include" + + PRIVATE + "Include/CBOR" +) + +target_sources(LibCBOR + PRIVATE + "Source/Decoder.cpp" + "Source/Encoder.cpp" +) diff --git a/LibCBOR/Include/CBOR/CBOR.hpp b/LibCBOR/Include/CBOR/CBOR.hpp new file mode 100644 index 0000000..87e16bf --- /dev/null +++ b/LibCBOR/Include/CBOR/CBOR.hpp @@ -0,0 +1,8 @@ +#ifndef LIBCBOR_CBOR_HPP +#define LIBCBOR_CBOR_HPP + +#include "Core.hpp" // IWYU pragma: export +#include "Decoder.hpp" // IWYU pragma: export +#include "Encoder.hpp" // IWYU pragma: export + +#endif // LIBCBOR_CBOR_HPP diff --git a/LibCBOR/Include/CBOR/Core.hpp b/LibCBOR/Include/CBOR/Core.hpp new file mode 100644 index 0000000..b720f35 --- /dev/null +++ b/LibCBOR/Include/CBOR/Core.hpp @@ -0,0 +1,150 @@ +#ifndef LIBCBOR_CORE_HPP +#define LIBCBOR_CORE_HPP + +#include +#include +#include + +namespace CBOR +{ + // Note(3011): how2basic explantion of the CBOR format: + // - Major types are the basic divisions into data types + // - Major type 0 and 1 are integers, the argument is the value itself + // - However, nothing in life is simple, and the values 24, 25, 26, and 27 are special, as + // they specify that the actual value is stored in the next 1, 2, 4, or 8 bytes. Values + // 28, 29, and 30 are reserved, and currently should be treated as malformed. Specifically + // for these data types, the value 31 should also be treated as malformed. + // - Major types 2~5 use the argument value as a length specifier, but first, the meanings. + // - 2 is a binary string, i.e. raw binary data + // - 3 is a utf-8 string (always unescaped) + // - Indefinite length strings are composed of definite length chunks of the same major + // type. + // - 4 is an array of data items + // - 5 is a key:value map of data items + // - For these major types, the argument value specified the length of the data item. + // Similarly to before, values 0-23 have their direct meaning, and values 24-27 use 1-8B + // to store the length. Values 28-30 are reserved, and currently malformed. Value 31 + // signifies an item with indefinite length (these must be terminated with the special + // "break" stop code). + // - Major type 6 is a tag type that assigns a special meaning to the data items immediately + // after it. It's otherwise equivalent to major type 0. + // - Major type 7 specifies simple, or floating point values. Argument values 0-19 are + // unassigned, 20 means false, 21 means true, 22 means null, 23 means undefined. Value 24 + // signifies a simple value stored in the next byte (only values 32~255 are allowed, + // but none of these are currently in use). Values 25, 26, and 27 signify the the next 2, 4, + // or 8 bytes store an IEEE 754 floating point type (half, single, or double precision + // respectively). Values 28~30 are reserved, and currently malformed. Value 31 is used as the + // "break" stop code for terminating indefinite length items. + // - TREACHERY: The argument value is always in BIG ENDIAN BYTE ORDER. + + // Floating point types must be IEEE 754 (equivalent to IEC 559). + static_assert(std::numeric_limits::is_iec559); + static_assert(std::numeric_limits::is_iec559); + + enum class MajorType: std::uint8_t + { + Unsigned = 0b0000'0000, + Negative = 0b0010'0000, + Binary = 0b0100'0000, + String = 0b0110'0000, + Array = 0b1000'0000, + Map = 0b1010'0000, + Tagged = 0b1100'0000, + Other = 0b1110'0000, + + TypeMask = 0b1110'0000, + }; + + enum class ArgumentPosition: std::uint8_t + { + Direct00 = 0b0000'0000, + Direct01 = 0b0000'0001, + Direct02 = 0b0000'0010, + Direct03 = 0b0000'0011, + Direct04 = 0b0000'0100, + Direct05 = 0b0000'0101, + Direct06 = 0b0000'0110, + Direct07 = 0b0000'0111, + Direct08 = 0b0000'1000, + Direct09 = 0b0000'1001, + Direct10 = 0b0000'1010, + Direct11 = 0b0000'1011, + Direct12 = 0b0000'1100, + Direct13 = 0b0000'1101, + Direct14 = 0b0000'1110, + Direct15 = 0b0000'1111, + Direct16 = 0b0001'0000, + Direct17 = 0b0001'0001, + Direct18 = 0b0001'0010, + Direct19 = 0b0001'0011, + Direct21 = 0b0001'0101, + Direct20 = 0b0001'0100, + Direct23 = 0b0001'0111, + Direct22 = 0b0001'0110, + + Next1B = 0b0001'1000, + Next2B = 0b0001'1001, + Next4B = 0b0001'1010, + Next8B = 0b0001'1011, + + Reserved28 = 0b0001'1100, + Reserved29 = 0b0001'1101, + Reserved30 = 0b0001'1110, + + Indefinite = 0b0001'1111, + }; + + enum class MinorType: std::uint8_t + { // in the context of RFC 8949, read "unused" as "unassigned" + Unused00 = 0b0000'0000, + Unused01 = 0b0000'0001, + Unused02 = 0b0000'0010, + Unused03 = 0b0000'0011, + Unused04 = 0b0000'0100, + Unused05 = 0b0000'0101, + Unused06 = 0b0000'0110, + Unused07 = 0b0000'0111, + Unused08 = 0b0000'1000, + Unused09 = 0b0000'1001, + Unused10 = 0b0000'1010, + Unused11 = 0b0000'1011, + Unused12 = 0b0000'1100, + Unused13 = 0b0000'1101, + Unused14 = 0b0000'1110, + Unused15 = 0b0000'1111, + Unused16 = 0b0001'0000, + Unused17 = 0b0001'0001, + Unused18 = 0b0001'0010, + Unused19 = 0b0001'0011, + + False = 0b0001'0100, + True = 0b0001'0101, + Null = 0b0001'0110, + Undefined = 0b0001'0111, + + SimpleNext = 0b0001'1000, // followed by 1B with either 0-31 (malformed) or 32-255 (unused) + Half = 0b0001'1001, // followed by 2B with an IEEE 754 half precision float + Float = 0b0001'1010, // followed by 4B with an IEEE 754 single precision float + Double = 0b0001'1011, // followed by 8B with an IEEE 754 double precision float + Reserved28 = 0b0001'1100, + Reserved29 = 0b0001'1101, + Reserved30 = 0b0001'1110, + Break = 0b0001'1111, + + TypeMask = 0b0001'1111, + }; + + enum class Special: std::uint8_t + { + Null, + Undefined, + }; + + class Error: public std::runtime_error + { + public: + using std::runtime_error::runtime_error; + }; +} + +#endif // LIBCBOR_CORE_HPP diff --git a/LibCBOR/Include/CBOR/Decoder.hpp b/LibCBOR/Include/CBOR/Decoder.hpp new file mode 100644 index 0000000..9bfd0cf --- /dev/null +++ b/LibCBOR/Include/CBOR/Decoder.hpp @@ -0,0 +1,27 @@ +#ifndef LIBCBOR_DECODER_HPP +#define LIBCBOR_DECODER_HPP + +#include "Core.hpp" + +namespace CBOR +{ + class DecodeError: public Error + { + public: + using Error::Error; + }; + + class Decoder + { + public: + private: + }; + + class Validator + { + public: + private: + }; +} + +#endif // LIBCBOR_DECODER_HPP diff --git a/LibCBOR/Include/CBOR/Encoder.hpp b/LibCBOR/Include/CBOR/Encoder.hpp new file mode 100644 index 0000000..a2c7c86 --- /dev/null +++ b/LibCBOR/Include/CBOR/Encoder.hpp @@ -0,0 +1,65 @@ +#ifndef LIBCBOR_ENCODER_HPP +#define LIBCBOR_ENCODER_HPP + +#include "Core.hpp" + +#include +#include +#include + +namespace CBOR +{ + class EncodeError: public Error + { + public: + using Error::Error; + }; + + // Note(3011): This is the basic CBOR encoder implementation. It will encode just about any + // data into a valid CBOR representation (with the exception of half precision floating point + // numbers). It will also select the optimal storage for data item arguments, with the exception + // of floating point numbers, that are stored directly (i.e. a float will always take up 5B with + // the header, and a double will always take up 9B including the header). The main shortcoming + // of this interface is that the user must always keep track of all structures himself. + class BasicEncoder + { + public: + BasicEncoder(std::span buffer); + + void Encode(bool value); + void Encode(Special value); + + void Encode(std::int8_t value); + void Encode(std::int16_t value); + void Encode(std::int32_t value); + void Encode(std::int64_t value); + + void Encode(std::uint8_t value); + void Encode(std::uint16_t value); + void Encode(std::uint32_t value); + void Encode(std::uint64_t value); + + // Note(3011): float16_t is currently not supported + void Encode(float value); + void Encode(double value); + + void Encode(std::span value); + void Encode(const char *value); + void Encode(std::string_view value); + + void BeginArray(std::size_t size); + void BeginMap(std::size_t size); + + void BeginIndefiniteBinary(); + void BeginIndefiniteString(); + void BeginIndefiniteArray(); + void BeginIndefiniteMap(); + + void End(); + private: + std::size_t mCurrent; + std::span mBuffer; + }; +} + +#endif // LIBCBOR_ENCODER_HPP diff --git a/LibCBOR/Source/Decoder.cpp b/LibCBOR/Source/Decoder.cpp new file mode 100644 index 0000000..e69de29 diff --git a/LibCBOR/Source/Encoder.cpp b/LibCBOR/Source/Encoder.cpp new file mode 100644 index 0000000..b4cddf9 --- /dev/null +++ b/LibCBOR/Source/Encoder.cpp @@ -0,0 +1,478 @@ +#include "Encoder.hpp" +#include "Core.hpp" +#include "Utils.hpp" + +#include +#include +#include +#include + +namespace CBOR +{ + class NotEnoughSpace: public EncodeError + { + public: + NotEnoughSpace(std::size_t remaining, std::size_t needed) + : EncodeError(std::format("the encoder needs at least more {} bytes, " + "but only {} are left", + needed, remaining)) + {} + }; + + class UnknownValue: public EncodeError + { + public: + UnknownValue() + : EncodeError("tried to encode an unknown CBOR value") + {} + }; + + namespace + { + std::size_t ArgumentSize(std::size_t value) + { + if (value <= 23) { + return 0; + } + if (value <= 255) { + return 1; + } + else if (value <= 65535) { + return 2; + } + else if (value <= 4294967295) { + return 4; + } + else { + return 8; + } + } + + std::size_t SpaceLeft(std::span buffer, std::size_t offset) + { + return buffer.size() - offset; + } + + void EnsureEnoughSpace(std::span buffer, std::size_t offset, + std::size_t spaceRequired) + { + if (SpaceLeft(buffer, offset) < spaceRequired) { + throw NotEnoughSpace(SpaceLeft(buffer, offset), spaceRequired); + } + } + + void Write(std::span &buffer, std::size_t ¤t, std::uint16_t value) + { + static constexpr std::uint16_t mask = 0x00'FF; + std::uint16_t network = HostToNetwork(value); + buffer[current++] = static_cast((network ) & mask); + buffer[current++] = static_cast((network >> 8) & mask); + } + + void Write(std::span &buffer, std::size_t ¤t, std::uint32_t value) + { + static constexpr std::uint32_t mask = 0x00'00'00'FF; + std::uint32_t network = HostToNetwork(value); + buffer[current++] = static_cast((network ) & mask); + buffer[current++] = static_cast((network >> 8) & mask); + buffer[current++] = static_cast((network >> 16) & mask); + buffer[current++] = static_cast((network >> 24) & mask); + } + + void Write(std::span &buffer, std::size_t ¤t, std::uint64_t value) + { + static constexpr std::uint64_t mask = 0x00'00'00'00'00'00'00'FF; + std::uint64_t network = HostToNetwork(value); + buffer[current++] = static_cast((network ) & mask); + buffer[current++] = static_cast((network >> 8) & mask); + buffer[current++] = static_cast((network >> 16) & mask); + buffer[current++] = static_cast((network >> 24) & mask); + buffer[current++] = static_cast((network >> 32) & mask); + buffer[current++] = static_cast((network >> 40) & mask); + buffer[current++] = static_cast((network >> 48) & mask); + buffer[current++] = static_cast((network >> 56) & mask); + } + } + + BasicEncoder::BasicEncoder(std::span buffer) + : mCurrent(0), mBuffer(buffer) + {} + + void BasicEncoder::Encode(bool value) + { + EnsureEnoughSpace(mBuffer, mCurrent, 1); + if (value) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Other) + | std::to_underlying(MinorType::True); + } + else { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Other) + | std::to_underlying(MinorType::False); + } + } + + void BasicEncoder::Encode(Special value) + { + EnsureEnoughSpace(mBuffer, mCurrent, 1); + switch (value) { + case Special::Null: + mBuffer[mCurrent++] = std::to_underlying(MajorType::Other) + | std::to_underlying(MinorType::Null); + break; + case Special::Undefined: + mBuffer[mCurrent++] = std::to_underlying(MajorType::Other) + | std::to_underlying(MinorType::Undefined); + break; + default: + throw UnknownValue(); + } + } + + void BasicEncoder::Encode(std::int8_t value) + { + if (value >= 0) { + if (value <= 23) { + EnsureEnoughSpace(mBuffer, mCurrent, 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Unsigned) | value; + } + else { + EnsureEnoughSpace(mBuffer, mCurrent, 2); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Unsigned) + | std::to_underlying(ArgumentPosition::Next1B); + mBuffer[mCurrent++] = value; + } + } + else { + std::int8_t actual = std::abs(value + 1); + if (actual <= 23) { + EnsureEnoughSpace(mBuffer, mCurrent, 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Negative) | actual; + } + else { + EnsureEnoughSpace(mBuffer, mCurrent, 2); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Negative) + | std::to_underlying(ArgumentPosition::Next1B); + mBuffer[mCurrent++] = actual; + } + } + } + + void BasicEncoder::Encode(std::int16_t value) + { + if (value >= -128 && value <= 127) { + Encode(static_cast(value)); + } + else if (value >= 0 && value <= 255) { + Encode(static_cast(value)); + } + else if (value >= -256 && value <= -1) { + std::uint8_t actual = static_cast(std::abs(value + 1)); + EnsureEnoughSpace(mBuffer, mCurrent, 2); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Negative) + | std::to_underlying(ArgumentPosition::Next1B); + mBuffer[mCurrent++] = actual; + } + else if (value >= 0) { + EnsureEnoughSpace(mBuffer, mCurrent, 3); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Unsigned) + | std::to_underlying(ArgumentPosition::Next2B); + Write(mBuffer, mCurrent, static_cast(value)); + } + else { + EnsureEnoughSpace(mBuffer, mCurrent, 3); + std::int16_t actual = std::abs(value + 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Negative) + | std::to_underlying(ArgumentPosition::Next2B); + Write(mBuffer, mCurrent, static_cast(value)); + } + } + + void BasicEncoder::Encode(std::int32_t value) + { + if (value >= -32768 && value <= 32767){ + Encode(static_cast(value)); + } + else if (value >= 0 && value <= 65535) { + Encode(static_cast(value)); + } + else if (value >= -65536 && value <= -1) { + std::uint16_t actual = static_cast(std::abs(value + 1)); + EnsureEnoughSpace(mBuffer, mCurrent, 3); + Write(mBuffer, mCurrent, actual); + } + else if (value >= 0) { + EnsureEnoughSpace(mBuffer, mCurrent, 5); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Unsigned) + | std::to_underlying(ArgumentPosition::Next4B); + Write(mBuffer, mCurrent, static_cast(value)); + } + else { + EnsureEnoughSpace(mBuffer, mCurrent, 5); + std::int32_t actual = std::abs(value + 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Negative) + | std::to_underlying(ArgumentPosition::Next4B); + Write(mBuffer, mCurrent, static_cast(actual)); + } + } + + void BasicEncoder::Encode(std::int64_t value) + { + if (value >= -2147483648 && value <= 2147483647){ + Encode(static_cast(value)); + } + else if (value >= 0 && value <= 4294967295) { + Encode(static_cast(value)); + } + else if (value >= -2147483648 && value <= -1) { + std::uint32_t actual = static_cast(std::abs(value + 1)); + EnsureEnoughSpace(mBuffer, mCurrent, 5); + Write(mBuffer, mCurrent, actual); + } + else if (value >= 0) { + EnsureEnoughSpace(mBuffer, mCurrent, 9); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Unsigned) + | std::to_underlying(ArgumentPosition::Next8B); + Write(mBuffer, mCurrent, static_cast(value)); + } + else { + EnsureEnoughSpace(mBuffer, mCurrent, 9); + std::int64_t actual = std::abs(value + 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Negative) + | std::to_underlying(ArgumentPosition::Next8B); + Write(mBuffer, mCurrent, static_cast(actual)); + } + } + + void BasicEncoder::Encode(std::uint8_t value) + { + if (value <= 23) { + EnsureEnoughSpace(mBuffer, mCurrent, 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Unsigned) | value; + } + else { + EnsureEnoughSpace(mBuffer, mCurrent, 2); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Unsigned) + | std::to_underlying(ArgumentPosition::Next1B); + mBuffer[mCurrent++] = value; + } + } + + void BasicEncoder::Encode(std::uint16_t value) + { + if (value <= 255) { + Encode(static_cast(value)); + } + else { + EnsureEnoughSpace(mBuffer, mCurrent, 3); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Unsigned) + | std::to_underlying(ArgumentPosition::Next2B); + Write(mBuffer, mCurrent, value); + } + } + + void BasicEncoder::Encode(std::uint32_t value) + { + if (value <= 65535) { + Encode(static_cast(value)); + } + else { + EnsureEnoughSpace(mBuffer, mCurrent, 5); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Unsigned) + | std::to_underlying(ArgumentPosition::Next4B); + Write(mBuffer, mCurrent, value); + } + } + + void BasicEncoder::Encode(std::uint64_t value) + { + if (value <= 4294967295) { + Encode(static_cast(value)); + } + else { + EnsureEnoughSpace(mBuffer, mCurrent, 9); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Unsigned) + | std::to_underlying(ArgumentPosition::Next8B); + Write(mBuffer, mCurrent, value); + } + } + + void BasicEncoder::Encode(float value) + { + // Note(3011): This is suboptimal, but dealing with IEEE 754 numbers correctly + // is difficult, and has been left for later. + EnsureEnoughSpace(mBuffer, mCurrent, 5); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Other) + | std::to_underlying(MinorType::Float); + Write(mBuffer, mCurrent, std::bit_cast(value)); + } + + void BasicEncoder::Encode(double value) + { + // Note(3011): This is suboptimal, but dealing with IEEE 754 numbers correctly + // is difficult, and has been left for later. + EnsureEnoughSpace(mBuffer, mCurrent, 9); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Other) + | std::to_underlying(MinorType::Double); + Write(mBuffer, mCurrent, std::bit_cast(value)); + } + + void BasicEncoder::Encode(std::span value) + { + EnsureEnoughSpace(mBuffer, mCurrent, value.size() + 1 + ArgumentSize(value.size())); + if (value.size() <= 23) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary) + | static_cast(value.size()); + } + else if (value.size() <= 255) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary) + | std::to_underlying(ArgumentPosition::Next1B); + mBuffer[mCurrent++] = static_cast(value.size()); + } + else if (value.size() <= 65535) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary) + | std::to_underlying(ArgumentPosition::Next2B); + Write(mBuffer, mCurrent, static_cast(value.size())); + } + else if (value.size() <= 4294967295) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary) + | std::to_underlying(ArgumentPosition::Next4B); + Write(mBuffer, mCurrent, static_cast(value.size())); + } + else { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary) + | std::to_underlying(ArgumentPosition::Next8B); + Write(mBuffer, mCurrent, static_cast(value.size())); + } + std::memcpy(mBuffer.data() + mCurrent, value.data(), value.size()); + mCurrent += value.size(); + } + + void BasicEncoder::Encode(const char *value) + { + Encode(std::string_view(value)); + } + + void BasicEncoder::Encode(std::string_view value) + { + EnsureEnoughSpace(mBuffer, mCurrent, value.size() + ArgumentSize(value.size())); + if (value.size() <= 23) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::String) + | static_cast(value.size()); + } + else if (value.size() <= 255) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::String) + | std::to_underlying(ArgumentPosition::Next1B); + mBuffer[mCurrent++] = static_cast(value.size()); + } + else if (value.size() <= 65535) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::String) + | std::to_underlying(ArgumentPosition::Next2B); + Write(mBuffer, mCurrent, static_cast(value.size())); + } + else if (value.size() <= 4294967295) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::String) + | std::to_underlying(ArgumentPosition::Next4B); + Write(mBuffer, mCurrent, static_cast(value.size())); + } + else { + mBuffer[mCurrent++] = std::to_underlying(MajorType::String) + | std::to_underlying(ArgumentPosition::Next8B); + Write(mBuffer, mCurrent, static_cast(value.size())); + } + std::memcpy(mBuffer.data() + mCurrent, value.data(), value.size()); + mCurrent += value.size(); + } + + void BasicEncoder::BeginArray(std::size_t size) + { + EnsureEnoughSpace(mBuffer, mCurrent, ArgumentSize(size)); + if (size <= 23) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Array) + | static_cast(size); + } + else if (size <= 255) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Array) + | std::to_underlying(ArgumentPosition::Next1B); + mBuffer[mCurrent++] = static_cast(size); + } + else if (size <= 65535) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Array) + | std::to_underlying(ArgumentPosition::Next2B); + Write(mBuffer, mCurrent, static_cast(size)); + } + else if (size <= 4294967295) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Array) + | std::to_underlying(ArgumentPosition::Next4B); + Write(mBuffer, mCurrent, static_cast(size)); + } + else { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Array) + | std::to_underlying(ArgumentPosition::Next8B); + Write(mBuffer, mCurrent, static_cast(size)); + } + } + + void BasicEncoder::BeginMap(std::size_t size) + { + EnsureEnoughSpace(mBuffer, mCurrent, ArgumentSize(size)); + if (size <= 23) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Map) + | static_cast(size); + } + else if (size <= 255) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Map) + | std::to_underlying(ArgumentPosition::Next1B); + mBuffer[mCurrent++] = static_cast(size); + } + else if (size <= 65535) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Map) + | std::to_underlying(ArgumentPosition::Next2B); + Write(mBuffer, mCurrent, static_cast(size)); + } + else if (size <= 4294967295) { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Map) + | std::to_underlying(ArgumentPosition::Next4B); + Write(mBuffer, mCurrent, static_cast(size)); + } + else { + mBuffer[mCurrent++] = std::to_underlying(MajorType::Map) + | std::to_underlying(ArgumentPosition::Next8B); + Write(mBuffer, mCurrent, static_cast(size)); + } + } + + void BasicEncoder::BeginIndefiniteBinary() + { + EnsureEnoughSpace(mBuffer, mCurrent, 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary) + | std::to_underlying(ArgumentPosition::Indefinite); + } + + void BasicEncoder::BeginIndefiniteString() + { + EnsureEnoughSpace(mBuffer, mCurrent, 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::String) + | std::to_underlying(ArgumentPosition::Indefinite); + } + + void BasicEncoder::BeginIndefiniteArray() + { + EnsureEnoughSpace(mBuffer, mCurrent, 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Array) + | std::to_underlying(ArgumentPosition::Indefinite); + } + + void BasicEncoder::BeginIndefiniteMap() + { + EnsureEnoughSpace(mBuffer, mCurrent, 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Map) + | std::to_underlying(ArgumentPosition::Indefinite); + } + + void BasicEncoder::End() + { + EnsureEnoughSpace(mBuffer, mCurrent, 1); + mBuffer[mCurrent++] = std::to_underlying(MajorType::Other) + | std::to_underlying(MinorType::Break); + } +} diff --git a/LibCBOR/Source/Utils.hpp b/LibCBOR/Source/Utils.hpp new file mode 100644 index 0000000..e811572 --- /dev/null +++ b/LibCBOR/Source/Utils.hpp @@ -0,0 +1,77 @@ +#ifndef LIBCBOR_INTERNAL_UTILS_HPP +#define LIBCBOR_INTERNAL_UTILS_HPP + +#include +#include +#include + +namespace CBOR +{ + [[nodiscard, deprecated("Prefer HostToNetwork or NetworkToHost")]] constexpr + std::uint8_t FlipBytes(std::uint8_t value) + { + return value; + } + + [[nodiscard, deprecated("Prefer HostToNetwork or NetworkToHost")]] constexpr + std::uint16_t FlipBytes(std::uint16_t value) + { + static constexpr std::uint16_t upperMask = 0b1111'1111'0000'0000; + static constexpr std::uint16_t lowerMask = 0b0000'0000'1111'1111; + return (value & upperMask) >> 8 | (value & lowerMask) << 8; + } + + [[nodiscard, deprecated("Prefer HostToNetwork or NetworkToHost")]] constexpr + std::uint32_t FlipBytes(std::uint32_t value) + { + static constexpr std::uint32_t firstMask = 0xFF'00'00'00; + static constexpr std::uint32_t secondMask = 0x00'FF'00'00; + static constexpr std::uint32_t thirdMask = 0x00'00'FF'00; + static constexpr std::uint32_t fourthMask = 0x00'00'00'FF; + return (value & firstMask) >> 24 | (value & secondMask) >> 8 | + (value & thirdMask) << 8 | (value & fourthMask) << 24; + } + + [[nodiscard, deprecated("Prefer HostToNetwork or NetworkToHost")]] constexpr + std::uint64_t FlipBytes(std::uint64_t value) + { + static constexpr std::uint64_t firstMask = 0xFF'00'00'00'00'00'00'00; + static constexpr std::uint64_t secondMask = 0x00'FF'00'00'00'00'00'00; + static constexpr std::uint64_t thirdMask = 0x00'00'FF'00'00'00'00'00; + static constexpr std::uint64_t fourthMask = 0x00'00'00'FF'00'00'00'00; + static constexpr std::uint64_t fifthMask = 0x00'00'00'00'FF'00'00'00; + static constexpr std::uint64_t sixthMask = 0x00'00'00'00'00'FF'00'00; + static constexpr std::uint64_t seventhMask = 0x00'00'00'00'00'00'FF'00; + static constexpr std::uint64_t eighthMask = 0x00'00'00'00'00'00'00'FF; + return (value & firstMask ) >> 56 | (value & secondMask) >> 40 | + (value & thirdMask ) >> 24 | (value & fourthMask) >> 8 | + (value & fifthMask ) << 8 | (value & sixthMask ) >> 24 | + (value & seventhMask) << 40 | (value & eighthMask) << 56; + } + + template + [[nodiscard]] constexpr + T HostToNetwork(T value) + { + if constexpr (std::endian::native == std::endian::little) { + return std::byteswap(value); + } + else { + return value; + } + } + + template + [[nodiscard]] constexpr + T NetworkToHost(T value) + { + if constexpr (std::endian::native == std::endian::little) { + return std::byteswap(value); + } + else { + return value; + } + } +} + +#endif // LIBCBOR_INTERNAL_UTILS_HPP diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt new file mode 100644 index 0000000..1850f44 --- /dev/null +++ b/Tests/CMakeLists.txt @@ -0,0 +1,21 @@ +add_executable(Tests) + +target_compile_features(Tests + PRIVATE + cxx_std_23 +) + +target_include_directories(Tests + PRIVATE + "Include" +) + +target_link_libraries(Tests + PRIVATE + LibCBOR +) + +target_sources(Tests + PRIVATE + "Main.cpp" +) diff --git a/Tests/Main.cpp b/Tests/Main.cpp new file mode 100644 index 0000000..f21218f --- /dev/null +++ b/Tests/Main.cpp @@ -0,0 +1,39 @@ +#include "CBOR/Core.hpp" +#include "CBOR/Encoder.hpp" +#include +#include +#include +#include + +int main() +{ + using namespace std::string_view_literals; + + std::array buffer = {0}; + + std::vector binData(5, 'g'); + + CBOR::BasicEncoder enc(buffer); + + enc.BeginMap(7); + enc.Encode("Hello "); + enc.Encode("World! "); + enc.Encode("Behold by new power! "); + enc.Encode("It truly is a sight to see ..."sv); + enc.Encode(std::int64_t(1212121212121212)); + enc.Encode(binData); + enc.Encode("random double"); + enc.Encode(420.69); + enc.Encode("random float"); + enc.Encode(420.69f); + enc.Encode("undefined?"); + enc.Encode(CBOR::Special::Undefined); + enc.Encode("null?"); + enc.Encode(CBOR::Special::Null); + enc.End(); + + for (const auto &byte: buffer) { + std::print("{:02x} ", byte); + } + std::println(""); +}