Basic project structure, basic encoder implementation

This commit is contained in:
TennesseeTrash 2025-09-14 13:58:26 +02:00
commit 4159fc4643
12 changed files with 897 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.cache/
build/

10
CMakeLists.txt Normal file
View file

@ -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()

20
LibCBOR/CMakeLists.txt Normal file
View file

@ -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"
)

View file

@ -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

View file

@ -0,0 +1,150 @@
#ifndef LIBCBOR_CORE_HPP
#define LIBCBOR_CORE_HPP
#include <cstdint>
#include <limits>
#include <stdexcept>
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<float>::is_iec559);
static_assert(std::numeric_limits<double>::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

View file

@ -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

View file

@ -0,0 +1,65 @@
#ifndef LIBCBOR_ENCODER_HPP
#define LIBCBOR_ENCODER_HPP
#include "Core.hpp"
#include <cstdint>
#include <span>
#include <string_view>
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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> mBuffer;
};
}
#endif // LIBCBOR_ENCODER_HPP

View file

478
LibCBOR/Source/Encoder.cpp Normal file
View file

@ -0,0 +1,478 @@
#include "Encoder.hpp"
#include "Core.hpp"
#include "Utils.hpp"
#include <cmath>
#include <cstring>
#include <format>
#include <utility>
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<std::uint8_t> buffer, std::size_t offset)
{
return buffer.size() - offset;
}
void EnsureEnoughSpace(std::span<std::uint8_t> buffer, std::size_t offset,
std::size_t spaceRequired)
{
if (SpaceLeft(buffer, offset) < spaceRequired) {
throw NotEnoughSpace(SpaceLeft(buffer, offset), spaceRequired);
}
}
void Write(std::span<std::uint8_t> &buffer, std::size_t &current, std::uint16_t value)
{
static constexpr std::uint16_t mask = 0x00'FF;
std::uint16_t network = HostToNetwork(value);
buffer[current++] = static_cast<std::uint8_t>((network ) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 8) & mask);
}
void Write(std::span<std::uint8_t> &buffer, std::size_t &current, std::uint32_t value)
{
static constexpr std::uint32_t mask = 0x00'00'00'FF;
std::uint32_t network = HostToNetwork(value);
buffer[current++] = static_cast<std::uint8_t>((network ) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 8) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 16) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 24) & mask);
}
void Write(std::span<std::uint8_t> &buffer, std::size_t &current, 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<std::uint8_t>((network ) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 8) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 16) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 24) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 32) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 40) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 48) & mask);
buffer[current++] = static_cast<std::uint8_t>((network >> 56) & mask);
}
}
BasicEncoder::BasicEncoder(std::span<std::uint8_t> 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<std::int8_t>(value));
}
else if (value >= 0 && value <= 255) {
Encode(static_cast<std::uint8_t>(value));
}
else if (value >= -256 && value <= -1) {
std::uint8_t actual = static_cast<std::uint8_t>(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<std::uint16_t>(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<std::uint16_t>(value));
}
}
void BasicEncoder::Encode(std::int32_t value)
{
if (value >= -32768 && value <= 32767){
Encode(static_cast<std::int16_t>(value));
}
else if (value >= 0 && value <= 65535) {
Encode(static_cast<std::uint16_t>(value));
}
else if (value >= -65536 && value <= -1) {
std::uint16_t actual = static_cast<std::uint16_t>(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<std::uint32_t>(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<std::uint32_t>(actual));
}
}
void BasicEncoder::Encode(std::int64_t value)
{
if (value >= -2147483648 && value <= 2147483647){
Encode(static_cast<std::int32_t>(value));
}
else if (value >= 0 && value <= 4294967295) {
Encode(static_cast<std::uint32_t>(value));
}
else if (value >= -2147483648 && value <= -1) {
std::uint32_t actual = static_cast<std::uint32_t>(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<std::uint64_t>(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<std::uint64_t>(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<std::uint8_t>(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<std::uint16_t>(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<std::uint32_t>(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<std::uint32_t>(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<std::uint64_t>(value));
}
void BasicEncoder::Encode(std::span<std::uint8_t> value)
{
EnsureEnoughSpace(mBuffer, mCurrent, value.size() + 1 + ArgumentSize(value.size()));
if (value.size() <= 23) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary)
| static_cast<std::uint8_t>(value.size());
}
else if (value.size() <= 255) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary)
| std::to_underlying(ArgumentPosition::Next1B);
mBuffer[mCurrent++] = static_cast<std::uint8_t>(value.size());
}
else if (value.size() <= 65535) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary)
| std::to_underlying(ArgumentPosition::Next2B);
Write(mBuffer, mCurrent, static_cast<std::uint16_t>(value.size()));
}
else if (value.size() <= 4294967295) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary)
| std::to_underlying(ArgumentPosition::Next4B);
Write(mBuffer, mCurrent, static_cast<std::uint32_t>(value.size()));
}
else {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Binary)
| std::to_underlying(ArgumentPosition::Next8B);
Write(mBuffer, mCurrent, static_cast<std::uint64_t>(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<std::uint8_t>(value.size());
}
else if (value.size() <= 255) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::String)
| std::to_underlying(ArgumentPosition::Next1B);
mBuffer[mCurrent++] = static_cast<std::uint8_t>(value.size());
}
else if (value.size() <= 65535) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::String)
| std::to_underlying(ArgumentPosition::Next2B);
Write(mBuffer, mCurrent, static_cast<std::uint16_t>(value.size()));
}
else if (value.size() <= 4294967295) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::String)
| std::to_underlying(ArgumentPosition::Next4B);
Write(mBuffer, mCurrent, static_cast<std::uint32_t>(value.size()));
}
else {
mBuffer[mCurrent++] = std::to_underlying(MajorType::String)
| std::to_underlying(ArgumentPosition::Next8B);
Write(mBuffer, mCurrent, static_cast<std::uint64_t>(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<std::uint8_t>(size);
}
else if (size <= 255) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Array)
| std::to_underlying(ArgumentPosition::Next1B);
mBuffer[mCurrent++] = static_cast<std::uint8_t>(size);
}
else if (size <= 65535) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Array)
| std::to_underlying(ArgumentPosition::Next2B);
Write(mBuffer, mCurrent, static_cast<std::uint16_t>(size));
}
else if (size <= 4294967295) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Array)
| std::to_underlying(ArgumentPosition::Next4B);
Write(mBuffer, mCurrent, static_cast<std::uint32_t>(size));
}
else {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Array)
| std::to_underlying(ArgumentPosition::Next8B);
Write(mBuffer, mCurrent, static_cast<std::uint64_t>(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<std::uint8_t>(size);
}
else if (size <= 255) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Map)
| std::to_underlying(ArgumentPosition::Next1B);
mBuffer[mCurrent++] = static_cast<std::uint8_t>(size);
}
else if (size <= 65535) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Map)
| std::to_underlying(ArgumentPosition::Next2B);
Write(mBuffer, mCurrent, static_cast<std::uint16_t>(size));
}
else if (size <= 4294967295) {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Map)
| std::to_underlying(ArgumentPosition::Next4B);
Write(mBuffer, mCurrent, static_cast<std::uint32_t>(size));
}
else {
mBuffer[mCurrent++] = std::to_underlying(MajorType::Map)
| std::to_underlying(ArgumentPosition::Next8B);
Write(mBuffer, mCurrent, static_cast<std::uint64_t>(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);
}
}

77
LibCBOR/Source/Utils.hpp Normal file
View file

@ -0,0 +1,77 @@
#ifndef LIBCBOR_INTERNAL_UTILS_HPP
#define LIBCBOR_INTERNAL_UTILS_HPP
#include <bit>
#include <concepts>
#include <cstdint>
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 <std::integral T>
[[nodiscard]] constexpr
T HostToNetwork(T value)
{
if constexpr (std::endian::native == std::endian::little) {
return std::byteswap(value);
}
else {
return value;
}
}
template <std::integral T>
[[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

21
Tests/CMakeLists.txt Normal file
View file

@ -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"
)

39
Tests/Main.cpp Normal file
View file

@ -0,0 +1,39 @@
#include "CBOR/Core.hpp"
#include "CBOR/Encoder.hpp"
#include <array>
#include <cstdint>
#include <print>
#include <vector>
int main()
{
using namespace std::string_view_literals;
std::array<std::uint8_t, 256> buffer = {0};
std::vector<std::uint8_t> 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("");
}