#include #include #include #include #include #include #include #include #include using std::back_inserter; using std::copy; using std::domain_error; using std::dec; using std::hex; using std::make_tuple; using std::numeric_limits; using std::ostream; using std::setfill; using std::setw; using std::string; using std::string_view; using std::stringstream; using std::tuple; namespace cbor { const buffer buffer::null_value = buffer{0xF6}; auto buffer::push_typed_val(int type, uint64_t value) -> void { type <<= 5; if (value < 24ULL) { push_back(type | value); } else if (value < 256ULL) { push_back(type | 24); push_back(value); } else if (value < 65536ULL) { push_back(type | 25); push_back(value >> 8); push_back(value); } else if (value < 4294967296ULL) { push_back(type | 26); push_back(value >> 24); push_back(value >> 16); push_back(value >> 8); push_back(value); } else { push_back(type | 27); push_back(value >> 56); push_back(value >> 48); push_back(value >> 40); push_back(value >> 32); push_back(value >> 24); push_back(value >> 16); push_back(value >> 8); push_back(value); } } auto buffer::push_string(const string &s) -> buffer & { push_typed_val(3, s.size()); copy(s.begin(), s.end(), back_inserter(*this)); return *this; } auto buffer::push_string(const string_view &s) -> buffer & { push_typed_val(3, s.size()); copy(s.begin(), s.end(), back_inserter(*this)); return *this; } using json_iter = string::const_iterator; namespace { static auto match_wsp_char(json_iter &b, const json_iter &e) -> bool { if (b == e) return false; char c = *b; switch (c) { case ' ': case '\t': case '\r': case '\n': ++b; return true; default: return false; } } static auto skip_wsp(json_iter &b, const json_iter &e) -> void { while (match_wsp_char(b, e)) ; } static auto match_char(int c, json_iter &b, const json_iter &e) -> bool { if (b == e || *b != c) return false; ++b; return true; } // NOLINTBEGIN(*-pointer-arithmetic) static auto match_literal(const char *str, json_iter &b, const json_iter &e) -> bool { json_iter i = b; while (*str && match_char(*str, i, e)) ++str; if (!*str) { b = i; return true; } return false; } constexpr auto int_str_max = 3 * sizeof(uint64_t) + 1; static auto push_json_number(buffer &buf, json_iter &b, const json_iter &e) -> bool { const bool neg = match_char('-', b, e); std::array s{}; char *p = s.data(); char *se = s.data() + int_str_max; if (b == e) return false; char c = *b; if (c >= '0' && c <= '9') { ++b; *p = c; ++p; if (p == se) return false; } else return false; while (true) { if (b == e) break; c = *b; if (c >= '0' && c <= '9') { ++b; *p = c; ++p; if (p == se) return false; } else { break; } } *p = 0; char *ep = s.data(); int64_t i = strtoll(s.data(), &ep, 10); if (ep != p) return false; if (neg) i = -i; buf.push_int(i); return true; } // NOLINTEND(*-pointer-arithmetic) static auto push_json_qstring_slow(buffer &buf, json_iter &b, const json_iter &e) -> bool { if (!match_char('"', b, e)) return false; string s; while (true) { if (b == e) return false; char c = *b; ++b; if (c == '\\') { if (b == e) return false; c = *b; ++b; switch (c) { case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case '\\': c = '\\'; break; default: break; } } else if (c == '"') { buf.push_string(s); return true; } s.push_back(c); } } static auto scan_json_qstring(json_iter &b, json_iter &e, bool &is_escaped) -> bool { if (!match_char('"', b, e)) return false; json_iter i = b; is_escaped = false; while (true) { if (i == e) return false; char c = *i; if (c == '\\') { is_escaped = true; return true; } if (c == '"') { e = i; return true; } ++i; } } static auto push_json_qstring(buffer &buf, json_iter &b, const json_iter &e) -> bool { json_iter sb = b; json_iter se = e; bool is_escaped{false}; if (!scan_json_qstring(sb, se, is_escaped)) return false; if (is_escaped) return push_json_qstring_slow(buf, b, e); b = se; ++b; buf.push_string(string_view(&*sb, (&*se - &*sb))); return true; } static auto push_json_keyword(buffer &buf, json_iter &b, const json_iter &e) -> bool { if (match_literal("false", b, e)) buf.push_bool(false); else if (match_literal("true", b, e)) buf.push_bool(true); else if (match_literal("null", b, e)) buf.push_null(); else return false; return true; } static auto push_json_value(buffer &buf, json_iter &b, const json_iter &e) -> bool; static auto push_json_array(buffer &buf, json_iter &b, const json_iter &e) -> bool { if (!match_char('[', b, e)) return false; skip_wsp(b, e); buffer inner; size_t count{0}; if (!push_json_value(inner, b, e)) return false; ++count; while (true) { skip_wsp(b, e); if (b == e) return false; char c = *b; switch (c) { case ',': ++b; skip_wsp(b, e); continue; case ']': ++b; inner.to_cbor(buf.array_header(count)); return true; default: if (!push_json_value(inner, b, e)) return false; ++count; } } } static auto push_json_map(buffer &buf, json_iter &b, const json_iter &e) -> bool { if (!match_char('{', b, e)) return false; skip_wsp(b, e); buffer inner; size_t count{0}; if (!push_json_value(inner, b, e)) return false; ++count; while (true) { skip_wsp(b, e); if (b == e) return false; char c = *b; switch (c) { case ':': case ',': ++b; skip_wsp(b, e); continue; case '}': ++b; if (count % 2) return false; inner.to_cbor(buf.map_header(count / 2)); return true; default: if (!push_json_value(inner, b, e)) return false; ++count; } } } static auto push_json_value(buffer &buf, json_iter &b, const json_iter &e) -> bool { skip_wsp(b, e); if (b == e) return false; char c = *b; if ((c >= '0' && c <= '9') || c == '-') { // integer return push_json_number(buf, b, e); } if ((c >= 'A' && c <= 'Z') || (c >= 'z' && c <= 'z')) { // keyword return push_json_keyword(buf, b, e); } switch (c) { case '"': // string return push_json_qstring(buf, b, e); case '[': // array return push_json_array(buf, b, e); case '{': // map return push_json_map(buf, b, e); default: break; } return false; } } // namespace auto buffer::push_json(const string &s) -> void { json_iter b = s.cbegin(); if (!push_json_value(*this, b, s.cend())) { stringstream ss; ss << "invalid json value at pos " << std::distance(s.cbegin(), b) << " in: " << s; throw domain_error{ss.str()}; } } using iter = buffer::const_iterator; namespace { static auto decode_int_length_recurse(size_t length, iter &b, const iter &e, int64_t i) -> int64_t { if (b == e) throw domain_error{"cbor message too short"}; i |= *b; ++b; if (length == 1) return i; i <<= 8; return decode_int_length_recurse(length - 1, b, e, i); }; static auto decode_int_length(size_t length, iter &b, const iter &e) -> int64_t { return decode_int_length_recurse(length, b, e, 0); } static auto decode_pint(uint8_t type, iter &b, const iter &e) -> uint64_t { if (type < 24) return type; switch (type) { case 24: // 1 byte return decode_int_length(1, b, e); case 25: // 2 byte return decode_int_length(2, b, e); case 26: // 4 byte return decode_int_length(4, b, e); case 27: // 8 byte return decode_int_length(8, b, e); default: throw domain_error{"cbor invalid integer type"}; } } static auto decode_nint(uint8_t type, iter &b, const iter &e) -> int64_t { return -(decode_pint(type, b, e) + 1); // NOLINT(*-narrowing-conversions) } static auto decode_string(uint8_t type, iter &b, const iter &e) -> string_view { auto len = decode_pint(type, b, e); const uint8_t *s = &*b; const auto sz = len; while (len) { if (b == e) throw domain_error{"cbor message too short"}; ++b; --len; } return { reinterpret_cast< // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const char *>(s), static_cast(sz)}; } static auto decode_bytes(uint8_t type, iter &b, const iter &e) -> string_view { return decode_string(type, b, e); } static auto decode_type(iter &b, const iter &e) -> tuple { if (b == e) throw domain_error{"cbor message too short"}; uint8_t type = *b; ++b; return make_tuple(uint8_t(type >> 5), uint8_t(type & 31), type); } } // namespace auto buffer::decode_range_header(iter &b, const iter &e) -> size_t { const auto [major, minor, type] = decode_type(b, e); if (type == 0xf6) return 0; auto sz = decode_pint(minor, b, e); switch (major) { case 4: // array return sz; case 5: // map return sz * 2; default: throw domain_error{"cbor unexpected type (expected array or map)"}; } } auto buffer::decode_array_header(iter &b, const iter &e) -> size_t { const auto [major, minor, type] = decode_type(b, e); if (type == 0xf6) return 0; if (major != 4) throw domain_error{"cbor unexpected type (expected array)"}; return decode_pint(minor, b, e); } auto buffer::decode_map_header(iter &b, const iter &e) -> size_t { const auto [major, minor, type] = decode_type(b, e); if (type == 0xf6) return 0; if (major != 5) throw domain_error{"cbor unexpected type (expected map)"}; return decode_pint(minor, b, e); } namespace { static auto skip_string(uint8_t type, iter &b, const iter &e) -> void { auto len = decode_pint(type, b, e); while (len) { if (b == e) throw domain_error{"cbor message too short"}; ++b; --len; } } static auto skip_bytes(uint8_t type, iter &b, const iter &e) -> void { return skip_string(type, b, e); } static auto skip_array(uint8_t type, iter &b, const iter &e) -> void; static auto skip_map(uint8_t type, iter &b, const iter &e) -> void; static auto skip_value_type(uint8_t major, uint8_t minor, iter &b, const iter &e) -> void { switch (major) { case 0: // positive integer decode_pint(minor, b, e); break; case 1: // negative integer decode_nint(minor, b, e); break; case 2: // bytes skip_bytes(minor, b, e); break; case 3: // string skip_string(minor, b, e); break; case 4: // array skip_array(minor, b, e); break; case 5: // map skip_map(minor, b, e); break; case 6: // tag throw domain_error{"cbor unsupported type tag"}; case 7: // special break; default: throw domain_error{"cbor unsupported type unknown"}; } } static auto skip_value(iter &b, const iter &e) -> void { const auto [major, minor, type] = decode_type(b, e); skip_value_type(major, minor, b, e); } static auto skip_array(uint8_t type, iter &b, const iter &e) -> void { auto len = decode_pint(type, b, e); while (len) { skip_value(b, e); --len; } } static auto skip_map(uint8_t type, iter &b, const iter &e) -> void { auto len = decode_pint(type, b, e); len = len * 2; while (len) { skip_value(b, e); --len; } } static auto match_type(iter &b, const iter &e, type &v) -> bool { const auto [major, minor, type] = decode_type(b, e); skip_value_type(major, minor, b, e); switch (major) { case 0: // positive integer case 1: // negative integer v = type::number; break; case 2: // bytes v = type::bytes; break; case 3: // string v = type::string; break; case 4: // array v = type::array; break; case 5: // map v = type::map; break; case 7: // special if (type == 0xf6) v = type::null; else if (type == 0xf4 || type == 0xf5) v = type::boolean; break; default: return false; } return true; } } // namespace auto buffer::match_value(iter &b, const iter &e, type t) -> bool { type v{type::any}; if (match_type(b, e, v)) { if (t == type::any) return true; return t == v; } return false; } namespace { static auto match_uint(iter &b, const iter &e, unsigned long long int &val) -> bool { const auto [major, minor, type] = decode_type(b, e); if (major != 0) return false; val = decode_pint(minor, b, e); return true; } static auto match_int(iter &b, const iter &e, signed long long int &val) -> bool { const auto [major, minor, type] = decode_type(b, e); switch (major) { case 0: // positive integer val = decode_pint(minor, b, e); // NOLINT(*-narrowing-conversions) if (val < 0) return false; break; case 1: // negative integer val = decode_nint(minor, b, e); break; default: return false; } return true; } } // namespace auto buffer::match_value(iter &b, const iter &e, unsigned long long int lit) -> bool { unsigned long long int val{0}; if (match_uint(b, e, val)) return val == lit; return false; } auto buffer::match_value(iter &b, const iter &e, signed long long int lit) -> bool { signed long long int val{0}; if (match_int(b, e, val)) return val == lit; return false; } namespace { static auto match_bool(iter &b, const iter &e, bool &v) -> bool { const auto [major, minor, type] = decode_type(b, e); if (major == 7) { // special if (type == 0xf4) { v = false; return true; } if (type == 0xf5) { v = true; return true; } } return false; } } // namespace auto buffer::match_value(iter &b, const iter &e, bool lit) -> bool { bool val{}; if (match_bool(b, e, val)) return val == lit; return false; } namespace { static auto match_string(iter &b, const iter &e, string_view &val) -> bool { const auto [major, minor, type] = decode_type(b, e); switch (major) { case 2: // bytes val = decode_bytes(minor, b, e); break; case 3: // string val = decode_string(minor, b, e); break; default: return false; } return true; } } // namespace auto buffer::match_value(iter &b, const iter &e, const string_view lit) -> bool { string_view val; if (match_string(b, e, val)) return val == lit; return false; } auto buffer::match_value(iter &b, const iter &e, const string &lit) -> bool { string_view val; if (match_string(b, e, val)) return val == lit; return false; } auto extract(type &t) -> buffer::extractor { return [&t](iter &b, const iter &e) { return match_type(b, e, t); }; } namespace { template static auto extract_int(T &i) -> buffer::extractor { return [&i](iter &b, const iter &e) { signed long long int i_{}; if (match_int(b, e, i_)) { if (i_ < numeric_limits::min() or i_ > numeric_limits::max()) return false; i = i_; return true; } return false; }; } } // namespace // clang-format off auto extract(signed long long int &i) -> buffer::extractor { return extract_int(i); } auto extract(signed long int &i) -> buffer::extractor { return extract_int(i); } auto extract(signed int &i) -> buffer::extractor { return extract_int(i); } auto extract(signed short int &i) -> buffer::extractor { return extract_int(i); } auto extract(signed char &i) -> buffer::extractor { return extract_int(i); } // clang-format on auto extract(unsigned long long int &i) -> buffer::extractor { return [&i](iter &b, const iter &e) { return match_uint(b, e, i); }; } namespace { template static auto extract_uint(T &i) -> buffer::extractor { return [&i](iter &b, const iter &e) { unsigned long long int i_{}; if (match_uint(b, e, i_)) { if (i_ > numeric_limits::max()) return false; i = i_; return true; } return false; }; } } // namespace // clang-format off auto extract(unsigned long int &i) -> buffer::extractor { return extract_uint(i); } auto extract(unsigned int &i) -> buffer::extractor { return extract_uint(i); } auto extract(unsigned short int &i) -> buffer::extractor { return extract_uint(i); } auto extract(unsigned char &i) -> buffer::extractor { return extract_uint(i); } // clang-format on auto extract(bool &val) -> buffer::extractor { return [&val](iter &b, const iter &e) { return match_bool(b, e, val); }; } auto extract(std::string &s) -> buffer::extractor { return [&s](iter &b, const iter &e) { string_view val; if (match_string(b, e, val)) { s.assign(val.data(), val.size()); return true; } return false; }; } auto extract(string_view &s) -> buffer::extractor { return [&s](iter &b, const iter &e) { return match_string(b, e, s); }; } auto extract(buffer::range &r) -> buffer::extractor { return [&r](iter &b, const iter &e) { auto r_b = b; type v{type::any}; if (match_type(b, e, v) && (v == type::null || v == type::array || v == type::map)) { r.b_ = r_b; r.e_ = b; return true; } return false; }; } auto buffer::match_value(iter &b, const iter &e, const extractor &ex) -> bool { return ex(b, e); } namespace { static auto tohex(ostream &os, uint8_t v) -> ostream & { return os << hex << setfill('0') << setw(2) << static_cast(v) << dec; } static auto to_json(ostream &os, string_view s) -> ostream & { os << '"'; for (auto c : s) { switch (c) { case '\b': os << "\\b"; break; case '\f': os << "\\f"; break; case '\n': os << "\\n"; break; case '\r': os << "\\r"; break; case '\t': os << "\\t"; break; case '\\': os << "\\\\"; break; case '"': os << "\\\""; break; default: if (c >= 0 && c <= 0x1f) { os << "\\u00"; tohex(os, c); } else { os << c; } } } os << '"'; return os; } static auto to_json_stream(ostream &ss, iter &b, const iter &e) -> void { const auto [major, minor, type] = decode_type(b, e); switch (major) { case 0: // positive integer { const auto i = decode_pint(minor, b, e); ss << i; } break; case 1: // negative integer { const auto i = decode_nint(minor, b, e); ss << i; } break; case 2: // bytes { const string_view v = decode_bytes(minor, b, e); to_json(ss, v); } break; case 3: // string { const string_view v = decode_string(minor, b, e); to_json(ss, v); } break; case 4: // array { auto i = decode_pint(minor, b, e); ss << '['; while (i) { to_json_stream(ss, b, e); --i; if (i) ss << ','; } ss << ']'; } break; case 5: // map { auto i = decode_pint(minor, b, e); ss << '{'; while (i) { to_json_stream(ss, b, e); ss << ':'; to_json_stream(ss, b, e); --i; if (i) ss << ','; } ss << '}'; } break; case 6: // tag throw domain_error{"cbor unsupported type tag"}; case 7: // special { if (type == 0xf4) ss << "false"; else if (type == 0xf5) ss << "true"; else if (type == 0xf6) ss << "null"; } break; default: throw domain_error{"cbor unsupported type unknown"}; } } static auto to_json_(const iter &b_, const iter &e) -> string { stringstream ss; iter b = b_; try { to_json_stream(ss, b, e); } catch (const domain_error &e) { throw domain_error{string("cbor to json failed: ") + e.what() + "\nafter:\n" + ss.str()}; } return ss.str(); } } // namespace auto buffer::to_json() const -> string { return to_json_(raw_cbegin(), raw_cend()); } auto buffer::range::to_json() const -> string { return to_json_(raw_cbegin(), raw_cend()); } auto buffer::to_json(ostream &os) const -> void { auto b = raw_cbegin(); return to_json_stream(os, b, raw_cend()); } auto buffer::range::to_json(ostream &os) const -> void { auto b = raw_cbegin(); return to_json_stream(os, b, raw_cend()); } extern "C" void cbor_to_json(cbor_buffer buf, cbor_to_json_callback cb) { auto cbor = cbor::buffer(); const uint8_t *data = buf.base; std::copy(data, data + buf.len, // NOLINT(*-pointer-arithmetic) back_inserter(cbor)); auto json = cbor.to_json(); cb({json.data(), json.size()}); } auto buffer::hexdump() const -> string { stringstream ss; ss << size() << ':'; for (auto c : static_cast(*this)) { ss << ' '; tohex(ss, c); } return ss.str(); } auto buffer::value_accessor::type_() const -> type { type t{}; iter b_ = b; if (not match_value(b_, e, extract(t))) return type::unknown; return t; } namespace { template static auto get(iter b, const iter &e) -> T { T val; extract(val)(b, e); return val; } } // namespace // clang-format off buffer::value_accessor::operator type() const { return get(b, e); } buffer::value_accessor::operator unsigned long long int() const { return get(b, e); } buffer::value_accessor::operator unsigned long int() const { return get(b, e); } buffer::value_accessor::operator unsigned int() const { return get(b, e); } buffer::value_accessor::operator unsigned short int() const { return get(b, e); } buffer::value_accessor::operator unsigned char() const { return get(b, e); } buffer::value_accessor::operator signed long long int() const { return get(b, e); } buffer::value_accessor::operator signed long int() const { return get(b, e); } buffer::value_accessor::operator signed int() const { return get(b, e); } // NOLINT(*-narrowing-conversions) buffer::value_accessor::operator signed short int() const { return get(b, e); } // NOLINT(*-narrowing-conversions) buffer::value_accessor::operator signed char() const { return get(b, e); } // NOLINT(*-narrowing-conversions) buffer::value_accessor::operator bool() const { return get(b, e); } buffer::value_accessor::operator string_view() const { return get(b, e); } buffer::value_accessor::operator buffer::range() const { return get(b, e); } // clang-format on } // namespace cbor