#include "native_db.h" #include #include namespace dingosdk::native_db { namespace { class Reader { public: Reader(std::span input, std::string_view context, Options options) : input_(input), context_(context), options_(options) {} Node read(unsigned depth = 0) { if (depth > 32 || ++nodes_ > 32768) fail("DB nesting/entry limit exceeded"); const auto tag = take(1)[0]; Node node; node.type = tag & 31; if (!node.type) return node; node.named = (tag & 128) == 0; if (node.named) { while (true) { const auto ch = take(1)[0]; if (!ch) break; if (node.name.size() >= 1024) fail("DB field name too long"); node.name.push_back(static_cast(ch)); } } if (node.type == 1 || node.type == 2) { const auto size = variable(); if (size > input_.size() - position_) fail("Truncated DB container"); const auto end = position_ + size; while (position_ < end) { auto child = read(depth + 1); if (!child.type) { node.terminated = true; break; } if (options_.unique_fields && node.type == 2 && child.named && node.field(child.name)) fail("Duplicate DB field"); node.children.push_back(std::move(child)); } if (position_ != end) fail("DB container length mismatch"); } else if (node.type == 7 || node.type == 19) { node.bytes = take(variable()); if (node.type == 7) { if (node.bytes.size() > 65536) fail("DB string too long"); node.text.assign(reinterpret_cast(node.bytes.data()), node.bytes.size()); if (!node.text.empty() && node.text.back() == '\0') node.text.pop_back(); } } else { std::size_t size{}; switch (node.type) { case 6: size = 1; break; case 8: case 11: size = 4; break; case 9: case 12: size = 8; break; case 15: size = 16; break; case 16: size = 20; break; default: fail("Unsupported DB value type"); } node.bytes = take(size); node.boolean = node.bytes[0] != 0; } return node; } std::size_t position() const { return position_; } private: [[noreturn]] void fail(std::string_view message) const { throw std::runtime_error(std::string(context_) + ": " + std::string(message)); } std::span take(std::size_t size) { if (size > input_.size() - position_) fail("Truncated DB value"); const auto value = input_.subspan(position_, size); position_ += size; return value; } std::size_t variable() { std::size_t value{}; for (unsigned shift = 0; shift < 63; shift += 7) { const auto byte = take(1)[0]; value |= static_cast(byte & 127) << shift; if (!(byte & 128)) return value; } fail("Invalid DB variable length"); } std::span input_; std::string_view context_; Options options_; std::size_t position_{}, nodes_{}; }; void write_variable(std::vector& out, std::size_t value) { while (true) { auto byte = static_cast(value & 127); value >>= 7; if (!value) { out.push_back(byte); return; } out.push_back(static_cast(byte | 128)); } } void emit(const Node& node, std::vector& out) { if (!node.type) { out.push_back(0); return; } out.push_back(static_cast(node.type | (node.named ? 0u : 128u))); if (node.named) { out.insert(out.end(), node.name.begin(), node.name.end()); out.push_back(0); } if (node.type == 1 || node.type == 2) { std::vector body; for (const auto& child : node.children) emit(child, body); if (node.terminated) body.push_back(0); write_variable(out, body.size()); out.insert(out.end(), body.begin(), body.end()); return; } const auto payload = node.payload(); if (node.type == 7 || node.type == 19) write_variable(out, payload.size()); out.insert(out.end(), payload.begin(), payload.end()); } } // namespace const Node* Node::field(std::string_view key) const { for (const auto& child : children) if (child.named && child.name == key) return &child; return nullptr; } Node* Node::field(std::string_view key) { return const_cast(std::as_const(*this).field(key)); } Node read(std::span input, std::string_view context, std::size_t* consumed, Options options) { Reader reader(input, context, options); auto root = reader.read(); if (consumed) *consumed = reader.position(); return root; } std::vector write(const Node& root) { std::vector out; emit(root, out); return out; } Node make_named_record(std::string_view field, std::string_view value) { Node text; text.type = 7; text.named = true; text.name = field; text.text = value; text.owned.assign(value.begin(), value.end()); text.owned.push_back(0); Node record; record.type = 2; record.terminated = true; record.children.push_back(std::move(text)); return record; } } // namespace dingosdk::native_db