Add the ability to print a nearly arbitrary CBOR encoded value

This commit is contained in:
TennesseeTrash 2025-09-26 01:52:23 +02:00
parent c3278e807b
commit f383ea9f66
6 changed files with 332 additions and 43 deletions

View file

@ -25,4 +25,5 @@ target_sources(LibCBOR
"Source/Core.cpp" "Source/Core.cpp"
"Source/Decoder.cpp" "Source/Decoder.cpp"
"Source/Encoder.cpp" "Source/Encoder.cpp"
"Source/Printer.cpp"
) )

View file

@ -3,6 +3,7 @@
#include "Core.hpp" #include "Core.hpp"
#include <limits>
#include <span> #include <span>
#include <string_view> #include <string_view>
@ -20,6 +21,13 @@ namespace CBOR
Item(class Decoder &decoder); Item(class Decoder &decoder);
public: public:
static constexpr
std::uint64_t ArgumentIndefinite = std::numeric_limits<std::uint64_t>::max();
MajorType GetMajor() const;
MinorType GetMinor() const;
std::uint64_t GetArgument() const;
bool Bool(); bool Bool();
Special Special(); Special Special();
@ -37,7 +45,7 @@ namespace CBOR
float Float(); float Float();
double Double(); double Double();
std::span<std::uint8_t> Binary(); std::span<const std::uint8_t> Binary();
std::string_view String(); std::string_view String();
class Binary IndefiniteBinary(); class Binary IndefiniteBinary();
class String IndefiniteString(); class String IndefiniteString();
@ -103,7 +111,7 @@ namespace CBOR
public: public:
bool Done(); bool Done();
std::span<std::uint8_t> Next(); std::span<const std::uint8_t> Next();
private: private:
friend class Decoder; friend class Decoder;
@ -175,7 +183,14 @@ namespace CBOR
class Decoder class Decoder
{ {
public: public:
Decoder(std::span<std::uint8_t> buffer); Decoder(std::span<const std::uint8_t> buffer);
static constexpr
std::uint64_t ArgumentIndefinite = std::numeric_limits<std::uint64_t>::max();
MajorType GetMajor() const;
MinorType GetMinor() const;
std::uint64_t GetArgument() const;
bool Bool(); bool Bool();
Special Special(); Special Special();
@ -194,7 +209,7 @@ namespace CBOR
float Float(); float Float();
double Double(); double Double();
std::span<std::uint8_t> Binary(); std::span<const std::uint8_t> Binary();
std::string_view String(); std::string_view String();
class Binary IndefiniteBinary(); class Binary IndefiniteBinary();
class String IndefiniteString(); class String IndefiniteString();
@ -202,6 +217,7 @@ namespace CBOR
class Map Map(); class Map Map();
class TaggedItem TaggedItem(); class TaggedItem TaggedItem();
class Item AsItem();
private: private:
friend class Binary; friend class Binary;
friend class String; friend class String;
@ -210,7 +226,7 @@ namespace CBOR
friend class TaggedItem; friend class TaggedItem;
std::size_t mCurrent; std::size_t mCurrent;
std::span<std::uint8_t> mBuffer; std::span<const std::uint8_t> mBuffer;
}; };
} }

View file

@ -0,0 +1,12 @@
#ifndef LIBCBOR_PRINTER_HPP
#define LIBCBOR_PRINTER_HPP
#include <ostream>
#include <span>
namespace CBOR
{
void Print(std::ostream &out, std::span<const std::uint8_t> buffer);
}
#endif // LIBCBOR_PRINTER_HPP

View file

@ -57,7 +57,7 @@ namespace CBOR
{ {
static constexpr std::size_t Indefinite = std::numeric_limits<std::size_t>::max(); static constexpr std::size_t Indefinite = std::numeric_limits<std::size_t>::max();
std::size_t SpaceLeft(std::span<std::uint8_t> buffer, std::size_t offset) std::size_t SpaceLeft(std::span<const std::uint8_t> buffer, std::size_t offset)
{ {
if (offset >= buffer.size()) { if (offset >= buffer.size()) {
return 0; return 0;
@ -65,7 +65,7 @@ namespace CBOR
return buffer.size() - offset; return buffer.size() - offset;
} }
void EnsureEnoughSpace(std::span<std::uint8_t> buffer, std::size_t offset, void EnsureEnoughSpace(std::span<const std::uint8_t> buffer, std::size_t offset,
std::size_t spaceRequired) std::size_t spaceRequired)
{ {
if (SpaceLeft(buffer, offset) < spaceRequired) { if (SpaceLeft(buffer, offset) < spaceRequired) {
@ -75,19 +75,13 @@ namespace CBOR
} }
} }
std::uint8_t Read1B(std::span<std::uint8_t> buffer, std::size_t current) std::uint8_t Read1B(std::span<const std::uint8_t> buffer, std::size_t current)
{ {
EnsureEnoughSpace(buffer, current, 1); EnsureEnoughSpace(buffer, current, 1);
return buffer[current]; return buffer[current];
} }
std::uint8_t Consume1B(std::span<std::uint8_t> buffer, std::size_t &current) std::uint16_t Read2B(std::span<const std::uint8_t> buffer, std::size_t current)
{
EnsureEnoughSpace(buffer, current, 1);
return buffer[current++];
}
std::uint16_t Consume2B(std::span<std::uint8_t> buffer, std::size_t &current)
{ {
EnsureEnoughSpace(buffer, current, 2); EnsureEnoughSpace(buffer, current, 2);
std::uint16_t result = 0; std::uint16_t result = 0;
@ -96,7 +90,7 @@ namespace CBOR
return NetworkToHost(result); return NetworkToHost(result);
} }
std::uint32_t Consume4B(std::span<std::uint8_t> buffer, std::size_t &current) std::uint32_t Read4B(std::span<const std::uint8_t> buffer, std::size_t current)
{ {
EnsureEnoughSpace(buffer, current, 4); EnsureEnoughSpace(buffer, current, 4);
std::uint32_t result = 0; std::uint32_t result = 0;
@ -107,7 +101,48 @@ namespace CBOR
return NetworkToHost(result); return NetworkToHost(result);
} }
std::uint64_t Consume8B(std::span<std::uint8_t> buffer, std::size_t &current) std::uint64_t Read8B(std::span<const std::uint8_t> buffer, std::size_t current)
{
EnsureEnoughSpace(buffer, current, 8);
std::uint64_t result = 0;
result |= std::uint64_t(buffer[current++]) ;
result |= std::uint64_t(buffer[current++]) << 8;
result |= std::uint64_t(buffer[current++]) << 16;
result |= std::uint64_t(buffer[current++]) << 24;
result |= std::uint64_t(buffer[current++]) << 32;
result |= std::uint64_t(buffer[current++]) << 40;
result |= std::uint64_t(buffer[current++]) << 48;
result |= std::uint64_t(buffer[current++]) << 56;
return NetworkToHost(result);
}
std::uint8_t Consume1B(std::span<const std::uint8_t> buffer, std::size_t &current)
{
EnsureEnoughSpace(buffer, current, 1);
return buffer[current++];
}
std::uint16_t Consume2B(std::span<const std::uint8_t> buffer, std::size_t &current)
{
EnsureEnoughSpace(buffer, current, 2);
std::uint16_t result = 0;
result |= std::uint16_t(buffer[current++]) ;
result |= std::uint16_t(buffer[current++]) << 8;
return NetworkToHost(result);
}
std::uint32_t Consume4B(std::span<const std::uint8_t> buffer, std::size_t &current)
{
EnsureEnoughSpace(buffer, current, 4);
std::uint32_t result = 0;
result |= std::uint32_t(buffer[current++]) ;
result |= std::uint32_t(buffer[current++]) << 8;
result |= std::uint32_t(buffer[current++]) << 16;
result |= std::uint32_t(buffer[current++]) << 24;
return NetworkToHost(result);
}
std::uint64_t Consume8B(std::span<const std::uint8_t> buffer, std::size_t &current)
{ {
EnsureEnoughSpace(buffer, current, 8); EnsureEnoughSpace(buffer, current, 8);
std::uint64_t result = 0; std::uint64_t result = 0;
@ -137,7 +172,8 @@ namespace CBOR
return ArgumentPosition(header & std::to_underlying(ArgumentPosition::PositionMask)); return ArgumentPosition(header & std::to_underlying(ArgumentPosition::PositionMask));
} }
std::uint64_t ArgumentValue(std::uint8_t header, std::span<std::uint8_t> buffer, std::size_t &current) std::uint64_t ArgumentValue(std::uint8_t header, std::span<const std::uint8_t> buffer,
std::size_t &current)
{ {
ArgumentPosition position = GetArgumentPosition(header); ArgumentPosition position = GetArgumentPosition(header);
if (std::to_underlying(position) <= 23) { if (std::to_underlying(position) <= 23) {
@ -158,8 +194,30 @@ namespace CBOR
} }
} }
std::uint64_t ReadArgumentValue(std::uint8_t header, std::span<const std::uint8_t> buffer,
std::size_t current)
{
ArgumentPosition position = GetArgumentPosition(header);
if (std::to_underlying(position) <= 23) {
return std::to_underlying(position);
}
switch (position) {
case ArgumentPosition::Next1B:
return Read1B(buffer, current);
case ArgumentPosition::Next2B:
return Read2B(buffer, current);
case ArgumentPosition::Next4B:
return Read4B(buffer, current);
case ArgumentPosition::Next8B:
return Read8B(buffer, current);
default:
throw MalformedDataError("argument position is reserved for future use, incorrect, "
"or the parser is out of date");
}
}
template <std::unsigned_integral T> template <std::unsigned_integral T>
T ExtractUnsigned(std::span<std::uint8_t> buffer, std::size_t &current) T ExtractUnsigned(std::span<const std::uint8_t> buffer, std::size_t &current)
{ {
static constexpr std::uint64_t maxValue = std::numeric_limits<T>::max(); static constexpr std::uint64_t maxValue = std::numeric_limits<T>::max();
@ -214,7 +272,8 @@ namespace CBOR
// Note(3011): In this case it includes zero, even though zero is not technically positive. // Note(3011): In this case it includes zero, even though zero is not technically positive.
template <std::signed_integral T> template <std::signed_integral T>
T SignedPositive(std::uint8_t header, std::span<std::uint8_t> buffer, std::size_t &current) T SignedPositive(std::uint8_t header, std::span<const std::uint8_t> buffer,
std::size_t &current)
{ {
static constexpr std::uint64_t maxValue = std::numeric_limits<T>::max(); static constexpr std::uint64_t maxValue = std::numeric_limits<T>::max();
@ -266,7 +325,8 @@ namespace CBOR
} }
template <std::signed_integral T> template <std::signed_integral T>
T SignedNegative(std::uint8_t header, std::span<std::uint8_t> buffer, std::size_t &current) T SignedNegative(std::uint8_t header, std::span<const std::uint8_t> buffer,
std::size_t &current)
{ {
static constexpr auto actualMin = std::numeric_limits<T>::min(); static constexpr auto actualMin = std::numeric_limits<T>::min();
static constexpr std::uint64_t minValue = -std::int64_t(actualMin + 1); static constexpr std::uint64_t minValue = -std::int64_t(actualMin + 1);
@ -319,7 +379,7 @@ namespace CBOR
} }
template <std::signed_integral T> template <std::signed_integral T>
T ExtractSigned(std::span<std::uint8_t> buffer, std::size_t &current) T ExtractSigned(std::span<const std::uint8_t> buffer, std::size_t &current)
{ {
std::uint8_t header = Consume1B(buffer, current); std::uint8_t header = Consume1B(buffer, current);
MajorType major = GetMajorType(header); MajorType major = GetMajorType(header);
@ -335,15 +395,17 @@ namespace CBOR
} }
} }
std::span<std::uint8_t> ExtractBinary(std::size_t size, std::span<std::uint8_t> buffer, std::size_t &current) std::span<const std::uint8_t>
ExtractBinary(std::size_t size, std::span<const std::uint8_t> buffer, std::size_t &current)
{ {
EnsureEnoughSpace(buffer, current, size); EnsureEnoughSpace(buffer, current, size);
std::span<std::uint8_t> result(buffer.data() + current, size); std::span<const std::uint8_t> result(buffer.data() + current, size);
current += size; current += size;
return result; return result;
} }
std::string_view ExtractString(std::size_t size, std::span<std::uint8_t> buffer, std::size_t &current) std::string_view ExtractString(std::size_t size, std::span<const std::uint8_t> buffer,
std::size_t &current)
{ {
EnsureEnoughSpace(buffer, current, size); EnsureEnoughSpace(buffer, current, size);
std::string_view result(reinterpret_cast<const char *>(buffer.data() + current), size); std::string_view result(reinterpret_cast<const char *>(buffer.data() + current), size);
@ -356,6 +418,21 @@ namespace CBOR
: mDecoder(&decoder) : mDecoder(&decoder)
{} {}
MajorType Item::GetMajor() const
{
return mDecoder->GetMajor();
}
MinorType Item::GetMinor() const
{
return mDecoder->GetMinor();
}
std::uint64_t Item::GetArgument() const
{
return mDecoder->GetArgument();
}
bool Item::Bool() bool Item::Bool()
{ {
return mDecoder->Bool(); return mDecoder->Bool();
@ -416,7 +493,7 @@ namespace CBOR
return mDecoder->Double(); return mDecoder->Double();
} }
std::span<std::uint8_t> Item::Binary() std::span<const std::uint8_t> Item::Binary()
{ {
return mDecoder->Binary(); return mDecoder->Binary();
} }
@ -531,7 +608,7 @@ namespace CBOR
return mDone; return mDone;
} }
std::span<std::uint8_t> Binary::Next() std::span<const std::uint8_t> Binary::Next()
{ {
if (!mHeaderParsed) { if (!mHeaderParsed) {
std::uint8_t header = Consume1B(mDecoder->mBuffer, mDecoder->mCurrent); std::uint8_t header = Consume1B(mDecoder->mBuffer, mDecoder->mCurrent);
@ -767,10 +844,37 @@ namespace CBOR
return KeyValue(*mDecoder); return KeyValue(*mDecoder);
} }
Decoder::Decoder(std::span<std::uint8_t> buffer) Decoder::Decoder(std::span<const std::uint8_t> buffer)
: mCurrent(0), mBuffer(buffer) : mCurrent(0), mBuffer(buffer)
{} {}
MajorType Decoder::GetMajor() const
{
std::uint8_t header = Read1B(mBuffer, mCurrent);
return GetMajorType(header);
}
MinorType Decoder::GetMinor() const
{
std::uint8_t header = Read1B(mBuffer, mCurrent);
if (GetMajorType(header) != MajorType::Other) {
throw InvalidUsageError("use the GetArgument function instead");
}
return GetMinorType(header);
}
std::uint64_t Decoder::GetArgument() const
{
std::uint8_t header = Read1B(mBuffer, mCurrent);
if (GetMajorType(header) != MajorType::Other) {
throw InvalidUsageError("use the GetMinor function instead");
}
if (GetArgumentPosition(header) == ArgumentPosition::Indefinite) {
return Decoder::ArgumentIndefinite;
}
return ReadArgumentValue(header, mBuffer, mCurrent);
}
bool Decoder::Bool() bool Decoder::Bool()
{ {
std::uint8_t header = Consume1B(mBuffer, mCurrent); std::uint8_t header = Consume1B(mBuffer, mCurrent);
@ -881,7 +985,7 @@ namespace CBOR
throw TypeMismatchError("double", ToString(major)); throw TypeMismatchError("double", ToString(major));
} }
std::span<std::uint8_t> Decoder::Binary() std::span<const std::uint8_t> Decoder::Binary()
{ {
std::uint8_t header = Consume1B(mBuffer, mCurrent); std::uint8_t header = Consume1B(mBuffer, mCurrent);
MajorType major = GetMajorType(header); MajorType major = GetMajorType(header);
@ -937,4 +1041,9 @@ namespace CBOR
{ {
return { *this }; return { *this };
} }
Item Decoder::AsItem()
{
return { *this };
}
} }

146
LibCBOR/Source/Printer.cpp Normal file
View file

@ -0,0 +1,146 @@
#include "Printer.hpp"
#include "Core.hpp"
#include "Decoder.hpp"
#include <print>
#include <string>
namespace CBOR
{
namespace
{
void Print(std::ostream &out, std::size_t depth, CBOR::Item item);
char AsChar(std::uint8_t nibble)
{
if (nibble < 10) {
return nibble + '0';
}
if (nibble < 16) {
return nibble + 'A';
}
return 'X';
}
std::string AsString(std::uint8_t byte)
{
std::string result;
result.push_back(AsChar((byte >> 4) & 15));
result.push_back(AsChar((byte ) & 15));
return result;
}
void PrintBinary(std::ostream &out, CBOR::Binary binary)
{
out << "b\"";
while (!binary.Done()) {
std::span<const std::uint8_t> chunk = binary.Next();
for (std::uint8_t byte: chunk) {
out << AsString(byte);
}
}
out << '\"';
}
void PrintString(std::ostream &out, CBOR::String string)
{
out << '\"';
while (!string.Done()) {
out << string.Next();
}
out << '\"';
}
void PrintArray(std::ostream &out, std::size_t depth, CBOR::Array array)
{
out << "[\n";
while (!array.Done()) {
out << std::string((depth + 1) * 4, ' ');
Print(out, depth + 1, array.Next());
out << ",\n";
}
out << std::string(depth * 4, ' ') << "]";
}
void PrintMap(std::ostream &out, std::size_t depth, CBOR::Map map)
{
out << "{\n";
while (!map.Done()) {
CBOR::KeyValue kv = map.Next();
out << std::string((depth + 1) * 4, ' ');
Print(out, depth + 1, kv.Key());
out << ": ";
Print(out, depth + 1, kv.Value());
out << ",\n";
}
out << std::string(depth * 4, ' ') << "}";
}
void PrintTagged(std::ostream &out, std::size_t depth, CBOR::TaggedItem item)
{
out << item.Tag();
out << '(';
Print(out, depth, item.Item());
out << ')';
}
void Print(std::ostream &out, std::size_t depth, CBOR::Item item)
{
switch (item.GetMajor()) {
case MajorType::Unsigned:
out << item.Uint64();
break;
case MajorType::Negative:
out << item.Int64();
break;
case MajorType::Binary:
PrintBinary(out, item.IndefiniteBinary());
break;
case MajorType::String:
PrintString(out, item.IndefiniteString());
break;
case MajorType::Array:
PrintArray(out, depth, item.Array());
break;
case MajorType::Map:
PrintMap(out, depth, item.Map());
break;
case MajorType::Tag:
PrintTagged(out, depth, item.TaggedItem());
break;
case MajorType::Other:
switch (item.GetMinor()) {
case MinorType::False:
out << "false";
break;
case MinorType::True:
out << "true";
break;
case MinorType::Null:
out << "null";
break;
case MinorType::Undefined:
out << "undefined";
break;
case MinorType::Float:
std::print(out, "{:.6f}", item.Float());
break;
case MinorType::Double:
std::print(out, "{:.15f}", item.Double());
break;
default:
out << "invalid_value";
break;
}
}
}
}
void Print(std::ostream &out, std::span<const std::uint8_t> buffer)
{
CBOR::Decoder dec(buffer);
Print(out, 0, dec.AsItem());
out << '\n';
}
}

View file

@ -1,7 +1,9 @@
#include "CBOR/Decoder.hpp" #include "CBOR/Decoder.hpp"
#include "CBOR/Encoder.hpp" #include "CBOR/Encoder.hpp"
#include "Cbor/Printer.hpp"
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <iostream>
#include <print> #include <print>
#include <ranges> #include <ranges>
#include <stdexcept> #include <stdexcept>
@ -206,6 +208,9 @@ int main()
SomeStruct result1 = Decode1(std::span<std::uint8_t>(buffer.data(), encodedSize)); SomeStruct result1 = Decode1(std::span<std::uint8_t>(buffer.data(), encodedSize));
SomeStruct result2 = Decode2(std::span<std::uint8_t>(buffer.data(), encodedSize)); SomeStruct result2 = Decode2(std::span<std::uint8_t>(buffer.data(), encodedSize));
std::println("JSON-esque serialization:");
CBOR::Print(std::cout, std::span<std::uint8_t>(buffer.data(), encodedSize));
Compare(expected, result1); Compare(expected, result1);
Compare(expected, result2); Compare(expected, result2);
std::println("The test has been completed successfully."); std::println("The test has been completed successfully.");