#pragma once #include #include #include #include #include #include // 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 }; enum class cbor_tags { datetime = 0, unix_time = 1, ubignum = 2, nbignum = 3, decimal = 4, bigfloat = 5, cose_encrypt0 = 16, cose_mac0 = 17, cose_sign1 = 18, expected_base64url = 21, expected_base64 = 22, expected_base16 = 23, encoded_cbor = 24, url = 32, base64url = 33, base64 = 34, regexp = 35, mime = 36, cose_encrypt = 96, cose_mac = 97, cose_sign = 98, signature = 55799 }; 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; } }; template vector& push_as_cbor(vector&, T); template vector& push_as_cbor(vector&, T&); inline vector& push_integer_with_header_as_cbor(vector& 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 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 wrapper = norm_v; src.push_back(header+26); for(auto byte : wrapper.bytes) { src.push_back(byte); } } else { endian_wrapper 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& the same reference that was received for the source */ template<> vector& push_as_cbor(vector& src, int64_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& the same reference that was received for the source */ template<> vector& push_as_cbor(vector& src, uint64_t& value) { return push_integer_with_header_as_cbor(src, 0, value); } template<> vector& push_as_cbor(vector& src, std::nullptr_t) { src.push_back(0b11110110); return src; } struct cbor_undefined{}; template<> vector& push_as_cbor(vector& src, cbor_undefined) { src.push_back(0b11110111); return src; } template<> vector& push_as_cbor(vector& src, bool value) { src.push_back(0b11110100+(value ? 1 : 0)); return src; } template<> vector& push_as_cbor>(vector& src, gp::buffer 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; }; template<> vector& push_as_cbor(vector& src, cbor_array_initiator value) { return push_integer_with_header_as_cbor(src, (uint8_t)0b10000000, value.size); } template<> vector& push_as_cbor(vector& src, cbor_associative_array_initiator value) { return push_integer_with_header_as_cbor(src, (uint8_t)0b10100000, value.size); } template vector& push_as_cbor(vector& src, gp::pair& value) { push_as_cbor(src,value.first); return push_as_cbor(src,value.second); } struct cbor_tag_initiator { union { size_t as_integer; cbor_tags tag; }; }; template<> vector& push_as_cbor(vector& src, cbor_tag_initiator value) { return push_integer_with_header_as_cbor(src, (uint8_t)0b11000000, value.as_integer); } using parsing_state = gp::buffer; template gp::pair, parsing_state> read_cbor(parsing_state state); inline gp::pair, parsing_state> pull_arbitrary_integer_from_cbor(parsing_state state) { auto local = (uint8_t)0b00011111 & (uint8_t)*state.begin(); if(local <= 23) { return {*state.begin(), {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>().begin().data)), {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>().begin().data)), {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>().begin().data)), {state.begin()+9, state.end()} }; } default: { return {nullopt, state}; } } } } template<> gp::pair, parsing_state> read_cbor(parsing_state state) { if(state.size()) return {nullopt, state}; auto type = cbor_type(((uint8_t)0b11100000 & (uint8_t)*state.begin()) >> 5); int64_t sign = type == cbor_type::nint ? -1 : 1; switch(type) { case cbor_type::uint: case cbor_type::nint: { auto[value, new_state] = pull_arbitrary_integer_from_cbor(state); if(value.has_value()) return {value.value() * sign, new_state}; break; } default: break; } return {nullopt, state}; } template<> gp::pair, parsing_state> read_cbor(parsing_state state) { 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; } default: break; } return {nullopt, state}; } struct cbor_floating_point final { using backing = gp::fixed_variant< ieee754_hf, float, double >; backing contents; cbor_floating_point(backing& p) : contents{p}{} cbor_floating_point(ieee754_hf p) : contents{p}{} cbor_floating_point(float p) : contents{p}{} cbor_floating_point(double p) : contents{p}{} operator ieee754_hf() { return contents.value(); } operator float() { switch(contents.type()) { case backing::alt(): return contents.value(); case backing::alt(): return contents.value(); default: gp_config::assertion(false,"this code should never be reached"); return std::numeric_limits::quiet_NaN(); } } operator double() { switch(contents.type()) { case backing::alt(): return contents.value(); case backing::alt(): return contents.value(); case backing::alt(): return contents.value(); default: gp_config::assertion(false,"this code should never be reached"); return std::numeric_limits::quiet_NaN(); } } }; struct undefined_t final {}; template using cbor_composite = gp::fixed_variant< gp::undefined_t, cbor_number, gp::vector, bool, gp::vector, gp::vector>, gp::nullopt_t, cbor_floating_point >; class cbor_value { cbor_composite contents; gp::reference_wrapper alloc; public: cbor_value(allocator& alloc_v) : contents() , alloc(alloc_v) {} cbor_value(cbor_number number, allocator& alloc_v) : contents(number) , alloc(alloc_v) {} cbor_value(cbor_composite val, allocator& alloc_v) : contents(val) , alloc(alloc_v) {} cbor_value(const cbor_value& oth) : contents(oth.contents) , alloc(oth.alloc) {} cbor_value(cbor_value&& oth) : contents(gp::move(oth.contents)) , alloc(gp::move(oth.alloc)) {} cbor_value& operator=(cbor_value& value) { contents = value.contents; alloc = value.alloc; return *this; } cbor_value& operator=(cbor_value&& value) { gp::swap(contents, value.contents); gp::swap(alloc, value.alloc); return *this; } cbor_value& operator=(cbor_composite& value) { contents = value; return *this; } template cbor_value& operator=(T& value) { contents = value; return *this; } template cbor_value& operator=(T&& value) { contents = gp::move(value); return *this; } auto new_array() { return gp::vector{alloc}; } auto new_object() { return gp::vector>{alloc}; } static gp::pair::associated_iterator> decode_integer(gp::buffer src) { #define ERROR return {0, src.begin()} auto local = (uint8_t)0b00011111 & (uint8_t)src[0]; if(local <= 23) { return {local, src.begin()+1}; } else { switch((cbor_oths)local) { case cbor_oths::byte: { if(src.size()<2) ERROR; return { (uint8_t)*(src.begin()+1), src.begin()+2 }; } case cbor_oths::word: { if(src.size()<3) ERROR; return { uint16_t(*(src.slice_start(3).slice_end(2).cast>().begin().data)), src.begin()+3 }; } case cbor_oths::dword: { if(src.size()<5) ERROR; return { uint32_t(*(src.slice_start(5).slice_end(4).cast>().begin().data)), src.begin()+5 }; } case cbor_oths::qword: { if(src.size()<9) ERROR; return { uint64_t(*(src.slice_start(9).slice_end(8).cast>().begin().data)), src.begin()+9 }; } default: { ERROR; } } } #undef ERROR } template bool is_a() { if constexpr ( std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v ) { if(contents.is_a()) { auto& v = contents.value(); if(!std::is_signed_v && v.is_negative()) return false; if( std::numeric_limits::max() || v.is_negative() ) ) return false; int64_t signed_v = (v.is_negative() ? -1 : 1 ) * v.value; if( std::numeric_limits::min() <= signed_v && std::numeric_limits::max() >= signed_v ) return true; return false; } else return false; } else if constexpr ( std::is_same_v || std::is_same_v || std::is_same_v ) { auto& v = contents.value(); switch(v.contents.type()) { case cbor_floating_point::backing::alt(): return std::is_same_v; case cbor_floating_point::backing::alt(): return std::is_same_v || std::is_same_v; case cbor_floating_point::backing::alt(): return std::is_same_v || std::is_same_v || std::is_same_v; default: return false; } } else { return contents.is_a(); } } template U get_value() { gp_config::assertion(is_a(), "can't convert value legally"); if constexpr ( std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v ) { auto& v = contents.value(); return (v.is_negative() ? -1 : 1 ) * v.value; } else if constexpr ( std::is_same_v || std::is_same_v || std::is_same_v ) { auto& v = contents.value(); return v; } else { return contents.value(); } } static gp::pair::associated_iterator> decode(allocator& alloc, gp::buffer src) { #define ERROR return {cbor_value{alloc}, src.begin()} if(src.size()==0) ERROR; auto discriminant = (cbor_type)(((uint8_t)*src.begin()) >> 5); auto local = uint8_t(((uint8_t)*src.begin()) & 0b00011111); switch(discriminant) { case cbor_type::uint: case cbor_type::nint: { auto nb = decode_integer(src); return {cbor_value{cbor_number{(bool)discriminant, nb.first}, alloc}, nb.second}; } case cbor_type::bstr: { gp::vector str{alloc}; if(local == 31) { auto sub = src.slice_end(src.size() - 1); if(!sub.size()) ERROR; while((uint8_t)sub[0] && 0b00011111 != 31) { auto len = decode_integer(sub); if(len.second == sub.begin()) ERROR; str.reserve(str.size() + len.first); auto end = (len.second + len.first); if(len.first + (len.second - src.begin()) > src.size()) ERROR; for(auto it = len.second; it != end; it++) { str.push_back(*it); } sub = sub.slice_end(sub.size() - (len.first + (len.second - sub.begin()))); } return {cbor_value{str, alloc}, sub.begin()+1}; } else if(auto len = decode_integer(src); len.second != src.begin()) { str.reserve(len.first); auto end = (len.second + len.first); if(len.first + (len.second - src.begin()) > src.size()) ERROR; for(auto it = len.second; it != end; it++) { str.push_back(*it); } return {cbor_value{str, alloc}, end}; } ERROR; } case cbor_type::tstr: { ERROR; } case cbor_type::list: { gp::vector list{alloc}; if(local == 31) { // TODO: Add the indefinite length version } else if(auto len = decode_integer(src); len.second != src.begin()) { auto sub = src.slice_end(src.size()-(len.second - src.begin())); while(len.first--) { auto tmp = decode(alloc, sub); if(tmp.second == sub.begin()) ERROR; list.push_back(tmp.first); sub = sub.slice_end(sub.size() - (tmp.second - sub.begin())); } return {cbor_value{list, alloc}, sub.begin()}; } ERROR; } case cbor_type::hmap: { gp::vector> list{alloc}; if(local == 31) { // TODO: Add the indefinite length version } else if(auto len = decode_integer(src); len.second != src.begin()) { auto sub = src.slice_end(src.size()-(len.second - src.begin())); while(len.first--) { auto tmpl = decode(alloc, sub); if(tmpl.second == sub.begin()) ERROR; sub = sub.slice_end(sub.size() - (tmpl.second - sub.begin())); auto tmpr = decode(alloc, sub); if(tmpr.second == sub.begin()) ERROR; list.push_back(gp::make_pair( tmpl.first, tmpr.first )); sub = sub.slice_end(sub.size() - (tmpr.second - sub.begin())); } return {cbor_value{list, alloc}, sub.begin()}; } ERROR; } case cbor_type::tags: { // TODO: add tag decoding methods } case cbor_type::oths: { switch((cbor_oths)local) { case cbor_oths::value_false: { return { cbor_value{ cbor_composite(false), alloc }, src.begin()+1 }; } case cbor_oths::value_true: { return { cbor_value{ cbor_composite(true), alloc }, src.begin()+1 }; } case cbor_oths::value_null: { return { cbor_value{ cbor_composite(nullopt), alloc }, src.begin()+1 }; } case cbor_oths::value_undefined: { return { cbor_value{ cbor_composite(undefined_t{}), alloc }, src.begin()+1 }; } case cbor_oths::word: { if(src.size()<3) ERROR; return { cbor_value{ cbor_floating_point{ieee754_hf(*(src.slice_start(3).slice_end(2).cast>().begin().data))}, alloc }, src.begin()+3 }; } case cbor_oths::dword: { if(src.size()<5) ERROR; return { cbor_value{ cbor_floating_point{float(*(src.slice_start(5).slice_end(4).cast>().begin().data))}, alloc }, src.begin()+5 }; } case cbor_oths::qword: { if(src.size()<9) ERROR; return { cbor_value{ cbor_floating_point{double(*(src.slice_start(9).slice_end(8).cast>().begin().data))}, alloc }, src.begin()+9 }; } default: { ERROR; } } } } ERROR; #undef ERROR } static auto encode_float(buffer dest, cbor_floating_point& value) { switch(value.contents.type()) { case cbor_floating_point::backing::alt():{ if(dest.size() < 3) return dest.begin(); dest[0] = std::byte(((uint8_t)cbor_type::oths << 5u) + (uint8_t)cbor_oths::word); (dest.slice_start(3).slice_end(2).cast>())[0] = value.contents.value(); return dest.begin()+3; } case cbor_floating_point::backing::alt():{ if(dest.size() < 5) return dest.begin(); dest[0] = std::byte(((uint8_t)cbor_type::oths << 5u) + (uint8_t)cbor_oths::dword); (dest.slice_start(5).slice_end(4).cast>())[0] = value.contents.value(); return dest.begin()+5; } case cbor_floating_point::backing::alt():{ if(dest.size() < 9) return dest.begin(); dest[0] = std::byte(((uint8_t)cbor_type::oths << 5u) + (uint8_t)cbor_oths::qword); (dest.slice_start(9).slice_end(8).cast>())[0] = value.contents.value(); return dest.begin()+9; } default: return dest.begin(); } } static auto encode_length(buffer dest, cbor_type major, uint64_t value) { auto num = value; if(value <= 23) { if(dest.size() < 1) return dest.begin(); dest[0] = std::byte(((uint8_t)major << 5u) + value); return dest.begin()+1; } else if(value <= 255) { if(dest.size() < 2) return dest.begin(); dest[0] = std::byte(((uint8_t)major << 5u) + (uint8_t)cbor_oths::byte); dest[1] = std::byte(value); return dest.begin() + 2; } else if(value <= 65535) { if(dest.size() < 3) return dest.begin(); dest[0] = std::byte(((uint8_t)major << 5u) + (uint8_t)cbor_oths::word); (dest.slice_start(3).slice_end(2).cast>())[0] = num; return dest.begin()+3; } else if(value <= 4294967295) { if(dest.size() < 5) return dest.begin(); dest[0] = std::byte(((uint8_t)major << 5u) + (uint8_t)cbor_oths::dword); (dest.slice_start(5).slice_end(4).cast>())[0] = num; return dest.begin()+5; } else { if(dest.size() < 9) return dest.begin(); dest[0] = std::byte(((uint8_t)major << 5u) + (uint8_t)cbor_oths::qword); (dest.slice_start(9).slice_end(8).cast>())[0] = num; return dest.begin()+9; } } auto encode(buffer dest) { switch(contents.type()) { case cbor_composite::alt(): { if(dest.size() < 1) return dest.begin(); dest[0] = std::byte(((uint8_t)cbor_type::oths << 5u) + (uint8_t)cbor_oths::value_undefined); return dest.begin()+1; } case cbor_composite::alt(): { auto& ref = contents.value(); return encode_length( dest, ref.is_negative() ? cbor_type::nint : cbor_type::uint, ref.value ); } case cbor_composite::alt>(): { auto& ref = contents.value>(); auto it = encode_length( dest, cbor_type::bstr, ref.size() ); if(it == dest.begin()) return it; for(auto a : ref) { *(it++) = a; } return it; } case cbor_composite::alt(): { if(dest.size() < 1) return dest.begin(); if(contents.value()) dest[0] = std::byte(((uint8_t)cbor_type::oths << 5u) + (uint8_t)cbor_oths::value_true); else dest[0] = std::byte(((uint8_t)cbor_type::oths << 5u) + (uint8_t)cbor_oths::value_false); return dest.begin()+1; } case cbor_composite::alt>(): { auto& ary = contents.value>(); auto it_begin = encode_length(dest, cbor_type::list, ary.size()); if(it_begin == dest.begin()) return dest.begin(); for(auto& elem : ary) { auto slice = dest.slice_end(dest.size() - (it_begin - dest.begin())); auto it = elem.encode(slice); if(it == it_begin) return dest.begin(); it_begin = it; } return it_begin; } case cbor_composite::alt>>(): { auto& ary = contents.value>>(); auto it_begin = encode_length(dest, cbor_type::hmap, ary.size()); if(it_begin == dest.begin()) return dest.begin(); for(auto& elem : ary) { auto slice = dest.slice_end(dest.size() - (it_begin - dest.begin())); auto it = elem.first.encode(slice); if(it == it_begin) return dest.begin(); it_begin = it; slice = dest.slice_end(dest.size() - (it_begin - dest.begin())); it = elem.second.encode(slice); if(it == it_begin) return dest.begin(); it_begin = it; } return it_begin; } case cbor_composite::alt(): { if(dest.size() < 1) return dest.begin(); dest[0] = std::byte(((uint8_t)cbor_type::oths << 5u) + (uint8_t)cbor_oths::value_null); return dest.begin()+1; } case cbor_composite::alt(): { return encode_float(dest, contents.value()); } default: return dest.begin(); } } }; }