#pragma once
|
|
|
|
#include <gp/algorithms/repeat.hpp>
|
|
#include <gp/math/boolean/bitops.hpp>
|
|
#include <gp/functional/optional.hpp>
|
|
#include <gp/utils/pair.hpp>
|
|
#include <gp/functional/variant.hpp>
|
|
#include <gp/containers/vector.hpp>
|
|
|
|
#include <concepts>
|
|
|
|
// TODO: Rewrite in a more ORM-like way
|
|
// TODO: Implement some bignum for support
|
|
|
|
namespace gp {
|
|
enum class cbor_type : uint8_t {
|
|
uint = 0,
|
|
nint = 1,
|
|
bstr = 2,
|
|
tstr = 3,
|
|
list = 4,
|
|
hmap = 5,
|
|
tags = 6,
|
|
oths = 7
|
|
};
|
|
|
|
enum class cbor_oths : uint8_t {
|
|
value_false = 20,
|
|
value_true = 21,
|
|
value_null = 22,
|
|
value_undefined = 23,
|
|
byte = 24,
|
|
word = 25,
|
|
dword = 26,
|
|
qword = 27,
|
|
terminator = 31
|
|
};
|
|
|
|
struct cbor_number final {
|
|
bool sign;
|
|
uint64_t value;
|
|
|
|
bool is_negative() {
|
|
return sign;
|
|
}
|
|
|
|
cbor_number(int64_t v)
|
|
: sign{v < 0}
|
|
, value{uint64_t((sign ? -1 : 1) * v)}
|
|
{}
|
|
|
|
cbor_number(bool s, uint64_t v)
|
|
: sign{s}
|
|
, value{v}
|
|
{}
|
|
};
|
|
|
|
struct ieee754_hf final {
|
|
uint16_t sign : 1;
|
|
uint16_t exponent : 5;
|
|
uint16_t mantissa : 10;
|
|
|
|
// TODO: support for denormalized values and NaNs
|
|
operator float() {
|
|
auto a = (uint32_t)((sign<<16) | ((exponent+0x1C000)<<13) | (mantissa<<13));
|
|
return *(float*)&a;
|
|
}
|
|
|
|
operator double() {
|
|
return (float)*this;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline vector<char>& push_integer_with_header_as_cbor(vector<char>& src, uint8_t header, uint64_t value) {
|
|
auto norm_v = (value<0) ? -value : value;
|
|
if(norm_v <= 23) {
|
|
src.push_back(header+norm_v);
|
|
} else if(norm_v < (1ll<<8ll)) {
|
|
src.push_back(header+24);
|
|
src.push_back(norm_v);
|
|
} else if(norm_v < (1ll<<16ll)) {
|
|
endian_wrapper<uint16_t, endian::big> wrapper = norm_v;
|
|
src.push_back(header+25);
|
|
for(auto byte : wrapper.bytes()) {
|
|
src.push_back(byte);
|
|
}
|
|
} else if(norm_v < (1ll<<32ll)) {
|
|
endian_wrapper<uint32_t, endian::big> wrapper = norm_v;
|
|
src.push_back(header+26);
|
|
for(auto byte : wrapper.bytes()) {
|
|
src.push_back(byte);
|
|
}
|
|
} else {
|
|
endian_wrapper<uint64_t, endian::big> wrapper = norm_v;
|
|
src.push_back(header+27);
|
|
for(auto byte : wrapper.bytes()) {
|
|
src.push_back(byte);
|
|
}
|
|
}
|
|
return src;
|
|
}
|
|
|
|
/**
|
|
* @brief Pushes an integer as CBOR on the vector
|
|
*
|
|
* @param src the vector on which the push happens
|
|
* @param value the value to push, can be signed
|
|
* @return vector<char>& the same reference that was received for the source
|
|
*/
|
|
template<std::signed_integral T>
|
|
inline vector<char>& push_as_cbor(vector<char>& src, T value) {
|
|
uint8_t sign = (value<0) ? 0b00100000 : 0;
|
|
auto norm_v = (value<0) ? -value : value;
|
|
return push_integer_with_header_as_cbor(src, sign, norm_v);
|
|
}
|
|
|
|
/**
|
|
* @brief Pushes an unsigned integer as CBOR on the vector
|
|
*
|
|
* @param src the vector on which the push happens
|
|
* @param value the value to push, cannot be signed
|
|
* @return vector<char>& the same reference that was received for the source
|
|
*/
|
|
template<std::unsigned_integral T>
|
|
inline vector<char>& push_as_cbor(vector<char>& src, T value) {
|
|
return push_integer_with_header_as_cbor(src, 0, value);
|
|
}
|
|
|
|
inline vector<char>& push_as_cbor(vector<char>& src, std::nullptr_t) {
|
|
src.push_back(0b11110110);
|
|
return src;
|
|
}
|
|
|
|
struct cbor_undefined{};
|
|
|
|
inline vector<char>& push_as_cbor(vector<char>& src, cbor_undefined) {
|
|
src.push_back(0b11110111);
|
|
return src;
|
|
}
|
|
|
|
inline vector<char>& push_as_cbor(vector<char>& src, bool value) {
|
|
src.push_back(0b11110100+(value ? 1 : 0));
|
|
return src;
|
|
}
|
|
|
|
inline vector<char>& push_as_cbor(vector<char>& src, gp::buffer<char> value) {
|
|
push_integer_with_header_as_cbor(src, (uint8_t)0b01000000, value.size());
|
|
for(auto byte : value) {
|
|
src.push_back(byte);
|
|
}
|
|
return src;
|
|
}
|
|
|
|
struct cbor_array_initiator {
|
|
size_t size;
|
|
};
|
|
|
|
struct cbor_associative_array_initiator {
|
|
size_t size;
|
|
};
|
|
|
|
inline vector<char>& push_as_cbor(vector<char>& src, cbor_array_initiator value) {
|
|
return push_integer_with_header_as_cbor(src, (uint8_t)0b10000000, value.size);
|
|
}
|
|
|
|
inline vector<char>& push_as_cbor(vector<char>& src, cbor_associative_array_initiator value) {
|
|
return push_integer_with_header_as_cbor(src, (uint8_t)0b10100000, value.size);
|
|
}
|
|
|
|
template<typename First, typename Second>
|
|
inline vector<char>& push_as_cbor(vector<char>& src, gp::pair<First, Second> value) {
|
|
push_as_cbor(src,value.first);
|
|
return push_as_cbor(src,value.second);
|
|
}
|
|
|
|
template<typename First, typename Second>
|
|
inline vector<char>& push_as_cbor(vector<char>& src, gp::pair<First, Second>& value) {
|
|
push_as_cbor(src,value.first);
|
|
return push_as_cbor(src,value.second);
|
|
}
|
|
|
|
struct cbor_tag_initiator {
|
|
union {
|
|
size_t as_integer;
|
|
gp_config::cbor_tag tag;
|
|
};
|
|
};
|
|
|
|
inline vector<char>& push_as_cbor(vector<char>& src, cbor_tag_initiator value) {
|
|
return push_integer_with_header_as_cbor(src, (uint8_t)0b11000000, value.as_integer);
|
|
}
|
|
|
|
using parsing_state = gp::buffer<char>;
|
|
|
|
template<typename T>
|
|
gp::pair<gp::optional<T>, parsing_state> read_cbor(parsing_state state, gp::allocator&);
|
|
|
|
inline gp::pair<gp::optional<uint64_t>, parsing_state> pull_arbitrary_integer_from_cbor(parsing_state state) {
|
|
auto local = (uint8_t)0b00011111 & (uint8_t)*state.begin();
|
|
if(local <= 23) {
|
|
return {local, {state.begin()+1, state.end()}};
|
|
} else {
|
|
switch((cbor_oths)local) {
|
|
case cbor_oths::byte: {
|
|
if(state.size() < 2) return {nullopt, state};
|
|
return {*(state.begin()+1), {state.begin()+2, state.end()}};
|
|
}
|
|
case cbor_oths::word: {
|
|
if(state.size() < 3) return {nullopt, state};
|
|
return {
|
|
uint16_t(*(state.slice_start(3).slice_end(2).cast<gp::endian_wrapper<uint16_t, endian::big>>().begin())),
|
|
{state.begin()+3, state.end()}
|
|
};
|
|
}
|
|
case cbor_oths::dword: {
|
|
if(state.size() < 5) return {nullopt, state};
|
|
return {
|
|
uint32_t(*(state.slice_start(5).slice_end(4).cast<gp::endian_wrapper<uint32_t, endian::big>>().begin())),
|
|
{state.begin()+5, state.end()}
|
|
};
|
|
}
|
|
case cbor_oths::qword: {
|
|
if(state.size() < 9) return {nullopt, state};
|
|
return {
|
|
uint64_t(*(state.slice_start(9).slice_end(8).cast<gp::endian_wrapper<uint64_t, endian::big>>().begin())),
|
|
{state.begin()+9, state.end()}
|
|
};
|
|
}
|
|
default: {
|
|
return {nullopt, state};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<std::integral T>
|
|
inline gp::pair<gp::optional<T>, parsing_state> read_cbor(parsing_state state, gp::allocator&) {
|
|
// TODO: Handling of over and underflow
|
|
if(!state.size()) return {nullopt, state};
|
|
auto type = cbor_type(((uint8_t)0b11100000 & (uint8_t)*state.begin()) >> 5);
|
|
|
|
switch(type) {
|
|
case cbor_type::uint:
|
|
{
|
|
auto[value, new_state] = pull_arbitrary_integer_from_cbor(state);
|
|
if(value.has_value()) return {value.value(), new_state};
|
|
break;
|
|
}
|
|
case cbor_type::nint:
|
|
{
|
|
auto[value, new_state] = pull_arbitrary_integer_from_cbor(state);
|
|
if(value.has_value()) return {-value.value(), new_state};
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return {nullopt, state};
|
|
}
|
|
|
|
|
|
template<>
|
|
inline gp::pair<gp::optional<cbor_tag_initiator>, parsing_state> read_cbor<cbor_tag_initiator>(parsing_state state, gp::allocator&) {
|
|
if(!state.size()) return {nullopt, state};
|
|
auto type = cbor_type(((uint8_t)0b11100000 & (uint8_t)*state.begin()) >> 5);
|
|
|
|
switch(type) {
|
|
case cbor_type::tags:
|
|
{
|
|
auto[value, new_state] = pull_arbitrary_integer_from_cbor(state);
|
|
if(value.has_value()) return {cbor_tag_initiator{.as_integer = value.value()}, new_state};
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
return {nullopt, state};
|
|
}
|
|
|
|
template<>
|
|
inline gp::pair<gp::optional<gp::vector<char>>, parsing_state> read_cbor<gp::vector<char>>(parsing_state state, gp::allocator& alloc) {
|
|
if(!state.size()) return {nullopt, state};
|
|
auto type = cbor_type(((uint8_t)0b11100000 & (uint8_t)*state.begin()) >> 5);
|
|
|
|
switch(type) {
|
|
case cbor_type::bstr:
|
|
{
|
|
const auto[size, new_state] = pull_arbitrary_integer_from_cbor(state);
|
|
if(!size.has_value()) break;
|
|
if(new_state.size()<size.value()) break;
|
|
gp::vector<char> return_value{alloc};
|
|
if(!return_value.reserve(size.value())) break;
|
|
auto end_it = new_state.begin() + size.value();
|
|
for(auto it = new_state.begin(); it != end_it; it++) {
|
|
return_value.push_back(*it);
|
|
}
|
|
return {optional<gp::vector<char>>(gp::move(return_value)), parsing_state(new_state.begin() + size.value(), new_state.end())};
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
return {nullopt, state};
|
|
}
|
|
|
|
template<>
|
|
inline gp::pair<gp::optional<std::nullptr_t>, parsing_state> read_cbor<std::nullptr_t>(parsing_state state, gp::allocator& alloc) {
|
|
if(!state.size()) return {nullopt, state};
|
|
auto type = cbor_type(((uint8_t)0b11100000 & (uint8_t)*state.begin()) >> 5);
|
|
|
|
|
|
switch(type) {
|
|
case cbor_type::oths:
|
|
{
|
|
const auto[value, new_state] = pull_arbitrary_integer_from_cbor(state);
|
|
if(!value.has_value()) break;
|
|
if(value.value() == 22)
|
|
{
|
|
return {optional(nullptr), parsing_state(new_state.begin()+1, new_state.end())};
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
return {nullopt, state};
|
|
}
|
|
|
|
template<>
|
|
inline gp::pair<gp::optional<bool>, parsing_state> read_cbor<bool>(parsing_state state, gp::allocator& alloc) {
|
|
if(!state.size()) return {nullopt, state};
|
|
auto type = cbor_type(((uint8_t)0b11100000 & (uint8_t)*state.begin()) >> 5);
|
|
|
|
|
|
switch(type) {
|
|
case cbor_type::oths:
|
|
{
|
|
const auto[value, new_state] = pull_arbitrary_integer_from_cbor(state);
|
|
if(!value.has_value()) break;
|
|
if(value.value() == 20)
|
|
{
|
|
return {false, parsing_state(new_state.begin()+1, new_state.end())};
|
|
}
|
|
else if(value.value() == 21)
|
|
{
|
|
return {true, parsing_state(new_state.begin()+1, new_state.end())};
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
return {nullopt, state};
|
|
}
|
|
|
|
template<>
|
|
inline gp::pair<gp::optional<cbor_undefined>, parsing_state> read_cbor<cbor_undefined>(parsing_state state, gp::allocator& alloc) {
|
|
if(!state.size()) return {nullopt, state};
|
|
auto type = cbor_type(((uint8_t)0b11100000 & (uint8_t)*state.begin()) >> 5);
|
|
|
|
switch(type) {
|
|
case cbor_type::oths:
|
|
{
|
|
const auto[value, new_state] = pull_arbitrary_integer_from_cbor(state);
|
|
if(!value.has_value()) break;
|
|
if(value.value() == 23)
|
|
{
|
|
return {optional(cbor_undefined{}), parsing_state(new_state.begin()+1, new_state.end())};
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
return {nullopt, state};
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
*
|
|
* @param state
|
|
* @param callback a callback that returns a new parsing state for every element read. It should follow the heredescribed signature: parsing_state(parsing_state, gp::allocator&)
|
|
* @param count_callback a callback that is used to check if the process should proceed given a count of elements in the list. It should follow the heredescribed signature: bool(uint64_t)
|
|
* @param alloc
|
|
*/
|
|
template<typename applier_cb, typename counter_cb>
|
|
inline parsing_state read_cbor_array(parsing_state state, gp::allocator& alloc, applier_cb callback, counter_cb count_callback = [](uint64_t) -> bool {return true;}) {
|
|
if(!state.size()) return state;
|
|
auto type = cbor_type(((uint8_t)0b11100000 & (uint8_t)*state.begin()) >> 5);
|
|
|
|
switch(type) {
|
|
case cbor_type::list:
|
|
{
|
|
const auto[size, new_state] = pull_arbitrary_integer_from_cbor(state);
|
|
if(!size.has_value()) return state;
|
|
if(new_state.size()<size.value()) return state;
|
|
if(!count_callback(size.value()))return state;
|
|
parsing_state forward = new_state;
|
|
for(auto idx = 0ull; idx != size.value(); idx++) {
|
|
if(forward.size() == 0) return state;
|
|
forward = callback(forward, alloc);
|
|
}
|
|
return forward;
|
|
}
|
|
default: return state;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
*
|
|
* @param state
|
|
* @param callback a callback that returns a new parsing state for every element read. It should follow the heredescribed signature: parsing_state(parsing_state, gp::allocator&), it MUST read exactly 2 cbor value from the stream.
|
|
* @param count_callback a callback that is used to check if the process should proceed given a count of elements in the list. It should follow the heredescribed signature: bool(uint64_t)
|
|
* @param alloc
|
|
*/
|
|
template<typename applier_cb, typename counter_cb>
|
|
inline parsing_state read_cbor_kv_list(parsing_state state, gp::allocator& alloc, applier_cb callback, counter_cb count_callback = [](uint64_t) -> bool {return true;}) {
|
|
if(!state.size()) return state;
|
|
auto type = cbor_type(((uint8_t)0b11100000 & (uint8_t)*state.begin()) >> 5);
|
|
|
|
switch(type) {
|
|
case cbor_type::hmap:
|
|
{
|
|
const auto[size, new_state] = pull_arbitrary_integer_from_cbor(state);
|
|
if(!size.has_value()) return state;
|
|
if(!count_callback(size.value()))return state;
|
|
parsing_state forward = new_state;
|
|
for(auto idx = 0ull; idx != size.value(); idx++) {
|
|
if(forward.size() < 2ull) return state;
|
|
forward = callback(forward, alloc);
|
|
forward = callback(forward, alloc);
|
|
}
|
|
return forward;
|
|
}
|
|
default: return state;
|
|
}
|
|
}
|
|
}
|