#pragma once #include #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 }; 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& 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 inline vector& push_as_cbor(vector& 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& the same reference that was received for the source */ template inline vector& push_as_cbor(vector& src, T value) { return push_integer_with_header_as_cbor(src, 0, value); } inline vector& push_as_cbor(vector& src, std::nullptr_t) { src.push_back(0b11110110); return src; } struct cbor_undefined{}; inline vector& push_as_cbor(vector& src, cbor_undefined) { src.push_back(0b11110111); return src; } inline vector& push_as_cbor(vector& src, bool value) { src.push_back(0b11110100+(value ? 1 : 0)); return src; } inline 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; }; inline vector& push_as_cbor(vector& src, cbor_array_initiator value) { return push_integer_with_header_as_cbor(src, (uint8_t)0b10000000, value.size); } inline 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 inline vector& push_as_cbor(vector& src, gp::pair value) { push_as_cbor(src,value.first); return push_as_cbor(src,value.second); } template inline 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; gp_config::cbor_tag tag; }; }; inline 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, gp::allocator&); 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 {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>().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>().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>().begin())), {state.begin()+9, state.end()} }; } default: { return {nullopt, state}; } } } } template inline gp::pair, 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, parsing_state> read_cbor(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>, parsing_state> read_cbor>(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() 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 {return_value, parsing_state(new_state.begin() + size.value(), new_state.end())}; break; } default: break; } return {nullopt, state}; } template<> inline gp::pair, parsing_state> read_cbor(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, parsing_state> read_cbor(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, parsing_state> read_cbor(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 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() 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; } } }