1
0
mirror of https://github.com/danog/ton.git synced 2024-11-26 20:14:55 +01:00
ton/crypto/vm/dict.cpp
2019-09-07 14:33:36 +04:00

2648 lines
94 KiB
C++

/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/dict.h"
#include "vm/cells.h"
#include "vm/cellslice.h"
#include "vm/stack.hpp"
#include "common/bitstring.h"
#include "td/utils/bits.h"
namespace vm {
/*
*
* DictionaryBase : basic (common) dictionary manipulation
*
*/
DictionaryBase::DictionaryBase(Ref<CellSlice> _root, int _n, bool validate)
: root(std::move(_root)), root_cell(), key_bits(_n), flags(f_root_cached) {
if (validate) {
force_validate();
}
}
DictionaryBase::DictionaryBase(const CellSlice& root_cs, int _n, bool validate)
: root(), root_cell(), key_bits(_n), flags(0) {
int f = (int)root_cs.prefetch_ulong(1);
if (f < 0) {
flags |= f_invalid;
} else if (f > 0) {
if (root_cs.size_refs()) {
root_cell = root_cs.prefetch_ref();
} else {
flags |= f_invalid;
}
}
if (validate) {
force_validate();
}
}
DictionaryBase::DictionaryBase(DictAdvance, CellSlice& root_cs, int _n, bool validate)
: root(), root_cell(), key_bits(_n), flags(0) {
int f = (int)root_cs.prefetch_ulong(1);
if (!f) {
root_cs.advance(1);
} else if (f > 0 && root_cs.size_refs()) {
root_cs.advance(1);
root_cell = root_cs.fetch_ref();
} else {
flags |= f_invalid;
}
if (validate) {
force_validate();
}
}
DictionaryBase::DictionaryBase(Ref<Cell> cell, int _n, bool validate)
: root(), root_cell(std::move(cell)), key_bits(_n), flags(0) {
if (validate) {
force_validate();
}
}
DictionaryBase::DictionaryBase(int _n, bool validate) : root(), root_cell(), key_bits(_n), flags(0) {
if (validate) {
force_validate();
}
}
DictionaryBase::DictionaryBase(DictNonEmpty, Ref<CellSlice> _root, int _n, bool validate)
: root(), root_cell(), key_bits(_n), flags(0) {
if (_root.is_null() || !init_root_for_nonempty(*_root)) { // empty ?
invalidate(); // invalidate
}
if (validate) {
force_validate();
}
}
DictionaryBase::DictionaryBase(DictNonEmpty, const CellSlice& _root, int _n, bool validate)
: root(), root_cell(), key_bits(_n), flags(0) {
if (!init_root_for_nonempty(_root)) {
invalidate();
}
if (validate) {
force_validate();
}
}
bool DictionaryBase::init_root_for_nonempty(const CellSlice& cs) {
vm::CellBuilder cb;
return cb.append_cellslice_bool(cs) && cb.finalize_to(root_cell);
}
Ref<Cell> DictionaryBase::construct_root_from(const CellSlice& root_node_cs) {
vm::CellBuilder cb;
if (cb.append_cellslice_bool(root_node_cs)) {
return cb.finalize();
} else {
return {};
}
}
void DictionaryBase::force_validate() {
if (!is_valid() && !validate()) {
throw VmError{Excno::dict_err, "invalid dictionary"};
}
}
bool DictionaryBase::validate() {
if (is_valid()) {
return true;
}
if (flags & f_invalid) {
return false;
}
if (key_bits < 0 || key_bits > max_key_bits) {
return invalidate();
}
if (flags & f_root_cached) {
if (root.is_null() || root->size() != 1) {
return invalidate();
}
bool non_empty = root->prefetch_ulong(1);
if (root->size_refs() != (non_empty ? 1u : 0u)) {
return invalidate();
}
if (root_cell.not_null()) {
return invalidate();
}
if (non_empty) {
root_cell = root->prefetch_ref();
}
} else if (root.not_null()) {
return invalidate();
}
flags |= f_valid;
return true;
}
Ref<CellSlice> DictionaryBase::get_root() const {
if (!(flags & f_root_cached) && !compute_root()) {
return {};
}
return root;
}
Ref<CellSlice> DictionaryBase::extract_root() && {
if (!(flags & f_root_cached) && !compute_root()) {
return {};
}
flags = f_invalid;
return std::move(root);
}
bool DictionaryBase::append_dict_to_bool(CellBuilder& cb) && {
if (!is_valid()) {
return false;
}
flags = f_invalid;
return cb.store_maybe_ref(std::move(root_cell));
}
bool DictionaryBase::append_dict_to_bool(CellBuilder& cb) const& {
return is_valid() && cb.store_maybe_ref(root_cell);
}
bool DictionaryBase::compute_root() const {
if (!is_valid()) {
return false;
}
if (root_cell.is_null()) {
root = get_empty_dictionary();
flags |= f_root_cached;
return true;
}
CellBuilder cb;
cb.store_long(1, 1);
cb.store_ref(root_cell);
root = Ref<CellSlice>{true, cb.finalize()};
flags |= f_root_cached;
return true;
}
Ref<CellSlice> DictionaryBase::get_empty_dictionary() {
static Ref<CellSlice> empty_dict{new_empty_dictionary()};
return empty_dict;
}
Ref<CellSlice> DictionaryBase::new_empty_dictionary() {
CellBuilder cb; // Builder
cb.store_long(0, 1);
return Ref<CellSlice>{true, cb.finalize()};
}
Ref<Cell> DictionaryFixed::finish_create_leaf(CellBuilder& cb, const CellSlice& value) const {
if (!cb.append_cellslice_bool(value)) {
throw VmError{Excno::dict_err, "cannot store new value into a dictionary leaf cell"};
}
return cb.finalize();
}
Ref<Cell> DictionaryFixed::finish_create_fork(CellBuilder& cb, Ref<Cell> c1, Ref<Cell> c2, int n) const {
assert(n > 0);
if (!(cb.store_ref_bool(std::move(c1)) && cb.store_ref_bool(std::move(c2)))) {
throw VmError{Excno::dict_err, "cannot store branch references into a dictionary fork cell"};
}
return cb.finalize();
}
bool DictionaryFixed::check_fork_raw(Ref<CellSlice> cs_ref, int n) const {
if (cs_ref.is_null()) {
return false;
}
Ref<Cell> c1, c2;
CellSlice& cs = cs_ref.write();
return cs.fetch_ref_to(c1) && cs.fetch_ref_to(c2) && check_fork(cs, std::move(c1), std::move(c2), n);
}
/*
*
* Label parser (HmLabel n ~l) for all dictionary types
*
*/
namespace dict {
LabelParser::LabelParser(Ref<CellSlice> cs, int max_label_len, int auto_validate) : remainder(), l_offs(0), l_same(0) {
if (!parse_label(cs.write(), max_label_len)) {
l_offs = 0;
} else {
s_bits = (l_same ? 0 : l_bits);
remainder = std::move(cs);
}
if (auto_validate) {
if (auto_validate > 2) {
validate_ext(max_label_len);
} else if (auto_validate == 2) {
validate_simple(max_label_len);
} else {
validate();
}
}
}
LabelParser::LabelParser(Ref<Cell> cell, int max_label_len, int auto_validate) : remainder(), l_offs(0), l_same(0) {
Ref<CellSlice> cs = load_cell_slice_ref(std::move(cell));
if (!parse_label(cs.unique_write(), max_label_len)) {
l_offs = 0;
} else {
s_bits = (l_same ? 0 : l_bits);
remainder = std::move(cs);
}
if (auto_validate) {
if (auto_validate > 2) {
validate_ext(max_label_len);
} else if (auto_validate == 2) {
validate_simple(max_label_len);
} else {
validate();
}
}
}
bool LabelParser::parse_label(CellSlice& cs, int max_label_len) {
int ltype = (int)cs.prefetch_ulong(2);
// std::cerr << "parse_label of type " << ltype << " and maximal length " << max_label_len << " in ";
// cs.dump_hex(std::cerr, 0, true);
switch (ltype) {
case 0: {
l_bits = 0;
l_offs = 2;
cs.advance(2);
return true;
}
case 1: {
cs.advance(1);
l_bits = cs.count_leading(1);
// std::cerr << "unary-encoded l_bits = " << l_bits << ", have " << cs.size() << std::endl;
if (l_bits > max_label_len || !cs.have(2 * l_bits + 1)) {
return false;
}
l_offs = l_bits + 2;
cs.advance(l_bits + 1);
return true;
}
case 2: {
int len_bits = 32 - td::count_leading_zeroes32(max_label_len);
cs.advance(2);
l_bits = (int)cs.fetch_ulong(len_bits);
if (l_bits < 0 || l_bits > max_label_len) {
return false;
}
l_offs = len_bits + 2;
return cs.have(l_bits);
}
case 3: {
int len_bits = 32 - td::count_leading_zeroes32(max_label_len);
// std::cerr << "len_bits = " << len_bits << ", have " << cs.size() << std::endl;
if (!cs.have(3 + len_bits)) {
return false;
}
l_same = (int)cs.fetch_ulong(3);
l_bits = (int)cs.fetch_ulong(len_bits);
// std::cerr << "l_bits = " << l_bits << ", l_same = " << l_same << std::endl;
if (l_bits < 0 || l_bits > max_label_len) {
return false;
}
l_offs = -1;
return true;
}
default:
return false;
}
}
void LabelParser::validate() const {
if (!is_valid()) {
throw VmError{Excno::cell_und, "error while parsing a dictionary node label"};
}
}
void LabelParser::validate_ext(int n) const {
validate();
if (l_bits > n) {
throw VmError{Excno::dict_err, "invalid dictionary node"};
} else if (l_bits < n && (remainder->size() != s_bits || remainder->size_refs() != 2)) {
throw VmError{Excno::dict_err, "invalid dictionary fork node"};
}
}
void LabelParser::validate_simple(int n) const {
validate();
if (l_bits > n) {
throw VmError{Excno::dict_err, "invalid dictionary node"};
} else if (l_bits < n && (remainder->size() < s_bits || remainder->size_refs() < 2)) {
throw VmError{Excno::dict_err, "invalid dictionary fork node"};
}
}
bool LabelParser::is_prefix_of(td::ConstBitPtr key, int len) const {
if (l_bits > len) {
return false;
} else if (!l_same) {
//std::cerr << "key is " << key.to_hex(len) << "; len = " << len << "; label_bits = " << l_bits << "; remainder = ";
//remainder->dump_hex(std::cerr, 0, true);
return remainder->has_prefix(key, l_bits);
} else {
return td::bitstring::bits_memscan(key, l_bits, l_same & 1) == (unsigned)l_bits;
}
}
bool LabelParser::has_prefix(td::ConstBitPtr key, int len) const {
return len >= 0 && len <= l_bits && common_prefix_len(key, len) == len;
}
int LabelParser::common_prefix_len(td::ConstBitPtr key, int len) const {
if (!l_same) {
//std::cerr << "key is " << key.to_hex(len) << "; len = " << len << "; label_bits = " << l_bits << "; remainder = ";
//remainder->dump_hex(std::cerr, 0, true);
return remainder->common_prefix_len(key, std::min(l_bits, len));
} else {
return (int)td::bitstring::bits_memscan(key, std::min(l_bits, len), l_same & 1);
}
}
int LabelParser::extract_label_to(td::BitPtr to) {
if (!l_same) {
to.copy_from(remainder->data_bits(), l_bits);
remainder.write().advance(l_bits);
} else {
to.fill(l_same & 1, l_bits);
}
return l_bits;
}
int LabelParser::copy_label_prefix_to(td::BitPtr to, int max_len) const {
if (max_len <= 0) {
return max_len;
}
int sz = std::min(max_len, l_bits);
if (!l_same) {
to.copy_from(remainder->data_bits(), sz);
} else {
to.fill(l_same & 1, sz);
}
return sz;
}
} // namespace dict
/*
*
* Usual Dictionary
*
*/
using dict::LabelParser;
BitSlice DictionaryFixed::integer_key(td::RefInt256 x, unsigned n, bool sgnd, unsigned char buffer[128], bool quiet) {
if (x.not_null() && x->fits_bits(n, sgnd)) {
if (buffer) {
if (x->export_bits(buffer, 0, n, sgnd)) {
return BitSlice{{}, buffer, 0, n};
}
} else {
Ref<td::BitString> bs{true, n};
if (x->export_bits(bs.unique_write().reserve_bitslice(n), sgnd)) {
return static_cast<BitSlice>(*bs);
}
}
}
if (!quiet) {
throw VmError{Excno::range_chk, "dictionary index out of bounds"};
}
return {};
}
bool DictionaryFixed::integer_key_simple(td::RefInt256 x, unsigned n, bool sgnd, td::BitPtr buffer, bool quiet) {
if (x.not_null() && x->fits_bits(n, sgnd) && x->export_bits(buffer, n, sgnd)) {
return true;
}
if (!quiet) {
throw VmError{Excno::range_chk, "dictionary index out of bounds"};
}
return false;
}
Ref<Cell> Dictionary::extract_value_ref(Ref<CellSlice> cs) {
if (cs.is_null()) {
return {};
} else if (!cs->size() && cs->size_refs() == 1) {
return cs->prefetch_ref();
} else {
throw VmError{Excno::dict_err, "dictionary value does not consist of exactly one reference"};
}
}
Ref<CellSlice> DictionaryFixed::lookup(td::ConstBitPtr key, int key_len) {
force_validate();
if (key_len != get_key_bits() || is_empty()) {
return {};
}
//std::cerr << "dictionary lookup for key = " << key.to_hex(key_len) << std::endl;
Ref<Cell> cell = get_root_cell();
int n = key_len;
while (true) {
LabelParser label{std::move(cell), n, label_mode()};
if (!label.is_prefix_of(key, n)) {
//std::cerr << "(not a prefix)\n";
return {};
}
n -= label.l_bits;
if (n <= 0) {
assert(!n);
label.skip_label();
return std::move(label.remainder);
}
key += label.l_bits;
bool sw = *key++;
//std::cerr << "key bit at position " << key_bits - n << " equals " << sw << std::endl;
--n;
cell = label.remainder->prefetch_ref(sw);
}
}
Ref<Cell> Dictionary::lookup_ref(td::ConstBitPtr key, int key_len) {
return extract_value_ref(lookup(key, key_len));
}
bool DictionaryFixed::has_common_prefix(td::ConstBitPtr prefix, int prefix_len) {
force_validate();
if (is_empty() || prefix_len <= 0) {
return true;
}
if (prefix_len > get_key_bits()) {
return false;
}
LabelParser label{get_root_cell(), get_key_bits(), label_mode()};
return label.has_prefix(prefix, prefix_len);
}
int DictionaryFixed::get_common_prefix(td::BitPtr buffer, unsigned buffer_len) {
force_validate();
if (is_empty()) {
return 0;
}
LabelParser label{get_root_cell(), get_key_bits(), label_mode()};
return label.copy_label_prefix_to(buffer, (int)buffer_len);
}
bool DictionaryFixed::key_exists(td::ConstBitPtr key, int key_len) {
return lookup(key, key_len).not_null();
}
bool DictionaryFixed::int_key_exists(long long key) {
force_validate();
int l = get_key_bits();
if (is_empty() || l > 64) {
return false;
}
if (l < 64) {
long long m = (1LL << (l - 1));
if (key < -m || key >= m) {
return false;
}
}
td::BitArray<64> a;
a.bits().store_int(key, l);
return key_exists(a.cbits(), l);
}
bool DictionaryFixed::uint_key_exists(unsigned long long key) {
force_validate();
int l = get_key_bits();
if (is_empty() || l > 64) {
return false;
}
if (l < 64 && key >= (1ULL << l)) {
return false;
}
td::BitArray<64> a;
a.bits().store_uint(key, l);
return key_exists(a.cbits(), l);
}
namespace {
void append_dict_label_same(CellBuilder& cb, bool same, int len, int max_len) {
int k = 32 - td::count_leading_zeroes32(max_len);
assert(len >= 0 && len <= max_len && max_len <= 1023);
// options: mode '0', requires 2n+2 bits (always for n=0)
// mode '10', requires 2+k+n bits (only for n<=1)
// mode '11', requires 3+k bits (for n>=2, k<2n-1)
if (len > 1 && k < 2 * len - 1) {
// mode '11'
cb.store_long(6 + same, 3).store_long(len, k);
} else if (k < len) {
// mode '10'
cb.store_long(2, 2).store_long(len, k).store_long(-static_cast<int>(same), len);
} else {
// mode '0'
cb.store_long(0, 1).store_long(-2, len + 1).store_long(-static_cast<int>(same), len);
}
}
void append_dict_label(CellBuilder& cb, td::ConstBitPtr label, int len, int max_len) {
assert(len <= max_len && max_len <= 1023);
if (len > 0 && (int)td::bitstring::bits_memscan(label, len, *label) == len) {
return append_dict_label_same(cb, *label, len, max_len);
}
int k = 32 - td::count_leading_zeroes32(max_len);
// two options: mode '0', requires 2n+2 bits
// mode '10', requires 2+k+n bits
if (k < len) {
cb.store_long(2, 2).store_long(len, k);
} else {
cb.store_long(0, 1).store_long(-2, len + 1);
}
if ((int)cb.remaining_bits() < len) {
throw VmError{Excno::cell_ov, "cannot store a label into a dictionary cell"};
}
cb.store_bits(label, len);
}
std::pair<Ref<Cell>, bool> dict_set(Ref<Cell> dict, td::ConstBitPtr key, int n,
const Dictionary::store_value_func_t& store_val, Dictionary::SetMode mode) {
//std::cerr << "dictionary modification for " << n << "-bit key = " << key.to_hex(n) << std::endl;
if (dict.is_null()) {
// the dictionary is very empty
if (mode == Dictionary::SetMode::Replace) {
return std::make_pair<Ref<Cell>, bool>({}, false);
}
// create an one-element dictionary
CellBuilder cb;
append_dict_label(cb, key, n, n);
if (!store_val(cb)) {
throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
}
return std::make_pair(cb.finalize(), true);
}
LabelParser label{std::move(dict), n};
label.validate();
int pfx_len = label.common_prefix_len(key, n);
assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
if (pfx_len < label.l_bits) {
// have to insert a new node (fork) inside the current edge
if (mode == Dictionary::SetMode::Replace) {
// key not found, return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
// first, create the edge + new leaf cell
int m = n - pfx_len - 1;
CellBuilder cb;
append_dict_label(cb, key + (pfx_len + 1), m, m);
if (!store_val(cb)) {
throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
}
Ref<Cell> c1 = cb.finalize(); // new leaf cell corresponding to `key`
//cb.reset();
// create the lower portion of the old edge
int t = label.l_bits - pfx_len - 1;
auto cs = std::move(label.remainder);
if (label.l_same) {
append_dict_label_same(cb, label.l_same & 1, t, m);
} else {
cs.write().advance(pfx_len + 1);
append_dict_label(cb, cs->data_bits(), t, m);
cs.unique_write().advance(t);
}
// now cs is the old payload of the edge, either a value or two subdictionary references
if (!cell_builder_add_slice_bool(cb, *cs)) {
throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell (?)"};
}
Ref<Cell> c2 = cb.finalize(); // the other child of the new fork
// cb.reset();
append_dict_label(cb, key, pfx_len, n);
bool sw_bit = key[pfx_len];
if (sw_bit) {
c1.swap(c2);
}
cb.store_ref(std::move(c1)).store_ref(std::move(c2));
return std::make_pair(cb.finalize(), true);
}
if (label.l_bits == n) {
// the edge leads to a leaf node
// this leaf node already contains a value for the key wanted
if (mode == Dictionary::SetMode::Add) {
// return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
// replace the value of the only element of the dictionary
CellBuilder cb;
append_dict_label(cb, key, n, n);
if (!store_val(cb)) {
throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
}
return std::make_pair(cb.finalize(), true);
}
// main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
auto c1 = label.remainder->prefetch_ref(0);
auto c2 = label.remainder->prefetch_ref(1);
label.remainder.clear();
if (key[label.l_bits]) {
// insert key into the right child (c2)
auto res = dict_set(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
if (!res.second) {
// return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
c2 = std::move(res.first);
} else {
// insert key into the left child (c1)
auto res = dict_set(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
if (!res.second) {
// return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
c1 = std::move(res.first);
}
// create a new label with the same content
CellBuilder cb;
append_dict_label(cb, key, label.l_bits, n);
cb.store_ref(std::move(c1)).store_ref(std::move(c2));
return std::make_pair(cb.finalize(), true);
}
std::tuple<Ref<CellSlice>, Ref<Cell>, bool> dict_lookup_set(Ref<Cell> dict, td::ConstBitPtr key, int n,
const Dictionary::store_value_func_t& store_val,
Dictionary::SetMode mode) {
//std::cerr << "dictionary lookup/modification for " << n << "-bit key = " << key.to_hex(n) << std::endl;
if (dict.is_null()) {
// the dictionary is very empty
if (mode == Dictionary::SetMode::Replace) {
return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>({}, {}, false);
}
// create an one-element dictionary
CellBuilder cb;
append_dict_label(cb, key, n, n);
if (!store_val(cb)) {
throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
}
return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>({}, cb.finalize(), true);
}
LabelParser label{std::move(dict), n};
int pfx_len = label.common_prefix_len(key, n);
assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
if (pfx_len < label.l_bits) {
// have to insert a new node (fork) inside the current edge
if (mode == Dictionary::SetMode::Replace) {
// key not found, return unchanged dictionary
return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>({}, {}, false);
}
// first, create the edge + new leaf cell
int m = n - pfx_len - 1;
CellBuilder cb;
append_dict_label(cb, key + (pfx_len + 1), m, m);
if (!store_val(cb)) {
throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
}
Ref<Cell> c1 = cb.finalize(); // new leaf cell corresponding to `key`
//cb.reset();
// create the lower portion of the old edge
int t = label.l_bits - pfx_len - 1;
auto cs = std::move(label.remainder);
if (label.l_same) {
append_dict_label_same(cb, label.l_same & 1, t, m);
} else {
cs.write().advance(pfx_len + 1);
append_dict_label(cb, cs->data_bits(), t, m);
cs.unique_write().fetch_bits(t);
}
// now cs is the old payload of the edge, either a value or two subdictionary references
if (!cell_builder_add_slice_bool(cb, *cs)) {
throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell (?)"};
}
Ref<Cell> c2 = cb.finalize(); // the other child of the new fork
//cb.reset();
append_dict_label(cb, key, pfx_len, n);
bool sw_bit = key[pfx_len];
if (sw_bit) {
c1.swap(c2);
}
cb.store_ref(std::move(c1)).store_ref(std::move(c2));
return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>({}, cb.finalize(), true);
}
if (label.l_bits == n) {
// the edge leads to a leaf node
// this leaf node already contains a value for the key wanted
auto old_val = std::move(label.remainder);
old_val.write().advance(label.s_bits);
if (mode == Dictionary::SetMode::Add) {
// return unchanged dictionary
return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>(std::move(old_val), {}, false);
}
// replace the value of the only element of the dictionary
CellBuilder cb;
append_dict_label(cb, key, n, n);
if (!store_val(cb)) {
throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
}
return std::make_tuple(std::move(old_val), cb.finalize(), true);
}
// main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
auto c1 = label.remainder->prefetch_ref(0);
auto c2 = label.remainder->prefetch_ref(1);
Ref<CellSlice> old_val;
label.remainder.clear();
if (key[label.l_bits]) {
// insert key into the right child (c2)
auto res = dict_lookup_set(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
old_val = std::get<Ref<CellSlice>>(res);
if (!std::get<bool>(res)) {
// return unchanged dictionary
return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>(std::move(old_val), {}, false);
}
c2 = std::get<Ref<Cell>>(std::move(res));
} else {
// insert key into the left child (c1)
auto res = dict_lookup_set(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
old_val = std::get<Ref<CellSlice>>(res);
if (!std::get<bool>(res)) {
// return unchanged dictionary
return std::make_tuple(std::move(old_val), Ref<Cell>{}, false);
}
c1 = std::get<Ref<Cell>>(std::move(res));
}
// create a new label with the same content
CellBuilder cb;
append_dict_label(cb, key, label.l_bits, n);
cb.store_ref(std::move(c1)).store_ref(std::move(c2));
return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>(std::move(old_val), cb.finalize(), true);
}
std::pair<Ref<Cell>, bool> pfx_dict_set(Ref<Cell> dict, td::ConstBitPtr key, int m, int n,
const PrefixDictionary::store_value_func_t& store_val,
Dictionary::SetMode mode) {
std::cerr << "up to " << n << "-bit prefix code dictionary modification for " << m << "-bit key = " << key.to_hex(m)
<< std::endl;
if (m > n) {
return std::make_pair(Ref<Cell>{}, false);
}
if (dict.is_null()) {
// the dictionary is very empty
if (mode == Dictionary::SetMode::Replace) {
return std::make_pair(Ref<Cell>{}, false);
}
// create an one-element dictionary
CellBuilder cb;
append_dict_label(cb, key, m, n);
cb.store_long(0, 1);
if (!store_val(cb)) {
throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
}
return std::make_pair(cb.finalize(), true);
}
LabelParser label{std::move(dict), n, 1};
int l = label.common_prefix_len(key, m);
assert(l >= 0 && l <= label.l_bits && label.l_bits <= n && l <= m && m <= n);
if (l < label.l_bits) {
// have to insert a new node (fork) inside the current edge
if (l == m || mode == Dictionary::SetMode::Replace) {
// key not found, return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
// first, create the edge + new leaf cell
int q = l + 1;
CellBuilder cb;
append_dict_label(cb, key + q, m - q, n - q);
cb.store_long(0, 1);
if (!store_val(cb)) {
throw VmError{Excno::cell_ov, "cannot store new value into a prefix dictionary cell"};
}
Ref<Cell> c1 = cb.finalize(); // new leaf cell corresponding to `key`
// cb.reset(); // contained in finalize()
// create the lower portion of the old edge
int t = label.l_bits - q;
auto cs = std::move(label.remainder);
if (label.l_same) {
append_dict_label_same(cb, label.l_same & 1, t, n - q);
} else {
cs.write().advance(l + 1);
append_dict_label(cb, cs->data_bits(), t, n - q);
cs.unique_write().advance(t);
}
// now cs is the old payload of the edge, either a value or two subdictionary references
if (!cell_builder_add_slice_bool(cb, *cs)) {
throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell (?)"};
}
Ref<Cell> c2 = cb.finalize(); // the other child of the new fork
//cb.reset();
append_dict_label(cb, key, l, n);
bool sw_bit = key[l];
if (sw_bit) {
c1.swap(c2);
}
cb.store_long(1, 1).store_ref(c1).store_ref(c2);
return std::make_pair<Ref<Cell>, bool>(cb.finalize(), true);
}
assert(label.l_bits == l);
label.skip_label();
if (!label.remainder->have(1)) {
throw VmError{Excno::dict_err, "no node constructor in a prefix code dictionary"};
}
if (!label.remainder.unique_write().fetch_ulong(1)) {
// the edge leads to a leaf node
if (l != m || mode == Dictionary::SetMode::Add) {
// return unchanged dictionary
return std::make_pair<Ref<Cell>, bool>({}, false);
}
// this leaf node already contains a value for the key wanted
// replace the value of the only element of the dictionary
CellBuilder cb;
append_dict_label(cb, key, m, n);
cb.store_long(0, 1);
if (!store_val(cb)) {
throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
}
return std::make_pair<Ref<Cell>, bool>(cb.finalize(), true);
}
// main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
if (label.remainder->size() || label.remainder->size_refs() != 2) {
throw VmError{Excno::dict_err, "invalid fork node in a prefix code dictionary"};
}
if (m == l) {
// cannot insert a value into a fork
return std::make_pair(Ref<Cell>{}, false);
}
auto c1 = label.remainder->prefetch_ref(0);
auto c2 = label.remainder->prefetch_ref(1);
label.remainder.clear();
if (key[l++]) {
// insert key into the right child (c2)
auto res = pfx_dict_set(std::move(c2), key + l, m - l, n - l, store_val, mode);
if (!res.second) {
// return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
c2 = std::move(res.first);
} else {
// insert key into the left child (c1)
auto res = pfx_dict_set(std::move(c1), key + l, m - l, n - l, store_val, mode);
if (!res.second) {
// return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
c1 = std::move(res.first);
}
// create a new label with the same content
CellBuilder cb;
append_dict_label(cb, key, l - 1, n);
cb.store_long(1, 1).store_ref(std::move(c1)).store_ref(std::move(c2));
return std::make_pair(cb.finalize(), true);
}
std::pair<Ref<CellSlice>, Ref<Cell>> pfx_dict_lookup_delete(Ref<Cell> dict, td::ConstBitPtr key, int m, int n) {
//std::cerr << "up to " << n << "-bit prefix dictionary delete for " << m << "-bit key = " << key.to_hex(m) << std::endl;
if (dict.is_null()) {
// the dictionary is very empty
return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
}
LabelParser label{std::move(dict), n, 1};
int l = label.common_prefix_len(key, m);
assert(l >= 0 && l <= label.l_bits && l <= m && m <= n && label.l_bits <= n);
if (l < label.l_bits) {
// key not found
return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
}
assert(label.l_bits == l);
label.skip_label();
if (!label.remainder->have(1)) {
throw VmError{Excno::dict_err, "no node constructor in a prefix code dictionary"};
}
if (!label.remainder.unique_write().fetch_ulong(1)) {
// the edge leads to a leaf node
if (l < m) {
// key not found
return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
}
// this leaf node contains the value for the key wanted
return std::make_pair(std::move(label.remainder), Ref<Cell>{});
}
// main case: the edge leads to a fork, have to delete the key either from the right or from the left subtree
if (label.remainder->size() || label.remainder->size_refs() != 2) {
throw VmError{Excno::dict_err, "invalid fork node in a prefix code dictionary"};
}
if (l == m) {
// the fork itself cannot correspond to a key, key not found
return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
}
auto c1 = label.remainder->prefetch_ref(0);
auto c2 = label.remainder->prefetch_ref(1);
Ref<CellSlice> old_val;
label.remainder.clear();
bool sw_bit = key[l++];
if (sw_bit) {
// delete key from the right child (c2)
auto res = pfx_dict_lookup_delete(std::move(c2), key + l, m - l, n - l);
if (res.first.is_null()) {
// return unchanged dictionary
return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
}
old_val = std::move(res.first);
c2 = std::move(res.second);
} else {
// delete key from the left child (c1)
auto res = pfx_dict_lookup_delete(std::move(c1), key + l, m - l, n - l);
if (res.first.is_null()) {
// return unchanged dictionary
return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
}
old_val = std::move(res.first);
c1 = std::move(res.second);
}
if (c1.not_null() && c2.not_null()) {
// create a new label with the same content leading to a fork with modified children
CellBuilder cb;
append_dict_label(cb, key, label.l_bits, n);
cb.store_long(1, 1).store_ref(std::move(c1)).store_ref(std::move(c2));
return std::make_pair(std::move(old_val), cb.finalize());
}
// have to merge current edge with the edge leading to c1 or c2
if (!sw_bit) {
c1.swap(c2);
}
assert(c1.not_null() && c2.is_null());
unsigned char buffer[Dictionary::max_key_bytes];
td::BitPtr bw{buffer};
bw.concat(key, label.l_bits);
bw.concat_same(!sw_bit, 1);
LabelParser label2{std::move(c1), n - l, 1};
bw += label2.extract_label_to(bw);
assert(bw.offs >= 0 && bw.offs <= Dictionary::max_key_bits);
CellBuilder cb;
append_dict_label(cb, td::ConstBitPtr{buffer}, bw.offs, n);
if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
throw VmError{Excno::cell_ov, "cannot change label of an old prefix code dictionary cell while merging edges"};
}
label2.remainder.clear();
return std::make_pair(std::move(old_val), cb.finalize());
}
Ref<Cell> dict_map(Ref<Cell> dict, td::BitPtr key_buffer, int n, int total_key_len,
const Dictionary::map_func_t& map_func) {
if (dict.is_null()) {
// dictionary is empty
return dict;
}
LabelParser label{std::move(dict), n};
int l = label.l_bits;
label.extract_label_to(key_buffer);
if (l == n) {
// leaf node, value left in label.remainder
CellBuilder cb;
append_dict_label(cb, key_buffer, l, n);
if (!map_func(cb, std::move(label.remainder), key_buffer + n - total_key_len, total_key_len)) {
return {}; // leaf to be omitted from the result altogether
}
return cb.finalize();
}
assert(l >= 0 && l < n);
// a fork with two children, c1 and c2
auto c1 = label.remainder->prefetch_ref(0);
auto c2 = label.remainder->prefetch_ref(1);
key_buffer += l + 1;
key_buffer[-1] = 0;
// recursive map applied to both children
c1 = dict_map(std::move(c1), key_buffer, n - l - 1, total_key_len, map_func);
key_buffer[-1] = 1;
c2 = dict_map(std::move(c2), key_buffer, n - l - 1, total_key_len, map_func);
if (c1.is_null() && c2.is_null()) {
return {}; // both children have become empty
}
if (c1.is_null() || c2.is_null()) {
if (c1.is_null()) {
c1 = std::move(c2);
// notice that the label of c2 is still in key_buffer
} else {
// recover label of c1
key_buffer[-1] = 0;
}
// one of children is empty, have to combine current edge with the root edge of c1
LabelParser label1{std::move(c1), n - l - 1};
label1.extract_label_to(key_buffer);
CellBuilder cb;
key_buffer -= l + 1;
// store combined label for the new edge
append_dict_label(cb, key_buffer, l + 1 + label1.l_bits, n);
// store payload
if (!cell_builder_add_slice_bool(cb, *label1.remainder)) {
throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
}
return cb.finalize();
}
// main case: both children c1 and c2 remain non-empty
key_buffer -= l + 1;
CellBuilder cb;
append_dict_label(cb, key_buffer, l, n);
return cb.store_ref(std::move(c1)).store_ref(std::move(c2)).finalize();
}
} // namespace
bool Dictionary::set_gen(td::ConstBitPtr key, int key_len, const std::function<bool(CellBuilder&)>& store_val,
SetMode mode) {
force_validate();
if (key_len != get_key_bits()) {
return false;
}
auto res = dict_set(get_root_cell(), key, key_len, store_val, mode);
if (res.second) {
set_root_cell(std::move(res.first));
}
return res.second;
}
bool Dictionary::set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode) {
return set_gen(key, key_len, [value](CellBuilder& cb) { return cell_builder_add_slice_bool(cb, *value); }, mode);
}
bool Dictionary::set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> val_ref, SetMode mode) {
return set_gen(key, key_len, [val_ref](CellBuilder& cb) { return cb.store_ref_bool(val_ref); }, mode);
}
bool Dictionary::set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b, SetMode mode) {
return set_gen(key, key_len, [val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
}
bool Dictionary::set_builder(td::ConstBitPtr key, int key_len, const CellBuilder& val_b, SetMode mode) {
return set_gen(key, key_len, [&val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
}
Ref<CellSlice> Dictionary::lookup_set_gen(td::ConstBitPtr key, int key_len, const store_value_func_t& store_val,
SetMode mode) {
force_validate();
if (key_len != get_key_bits()) {
return {};
}
auto res = dict_lookup_set(get_root_cell(), key, key_len, store_val, mode);
if (std::get<bool>(res)) {
set_root_cell(std::get<Ref<Cell>>(res));
}
return std::get<Ref<CellSlice>>(std::move(res));
}
Ref<CellSlice> Dictionary::lookup_set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode) {
return lookup_set_gen(key, key_len, [value](CellBuilder& cb) { return cell_builder_add_slice_bool(cb, *value); },
mode);
}
Ref<Cell> Dictionary::lookup_set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> val_ref, SetMode mode) {
return extract_value_ref(
lookup_set_gen(key, key_len, [val_ref](CellBuilder& cb) { return cb.store_ref_bool(val_ref); }, mode));
}
Ref<CellSlice> Dictionary::lookup_set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b, SetMode mode) {
return lookup_set_gen(key, key_len, [val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
}
std::pair<Ref<CellSlice>, Ref<Cell>> DictionaryFixed::dict_lookup_delete(Ref<Cell> dict, td::ConstBitPtr key,
int n) const {
// std::cerr << "dictionary delete for " << n << "-bit key = " << key.to_hex(n) << std::endl;
if (dict.is_null()) {
// the dictionary is very empty
return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
}
LabelParser label{std::move(dict), n, label_mode()};
int pfx_len = label.common_prefix_len(key, n);
assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
if (pfx_len < label.l_bits) {
// key not found
return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
}
if (label.l_bits == n) {
// the edge leads to a leaf node
// this leaf node contains the value for the key wanted
label.skip_label();
return std::make_pair(std::move(label.remainder), Ref<Cell>{});
}
// main case: the edge leads to a fork, have to delete the key either from the right or from the left subtree
auto c1 = label.remainder->prefetch_ref(0);
auto c2 = label.remainder->prefetch_ref(1);
Ref<CellSlice> old_val;
label.remainder.clear();
bool sw_bit = key[label.l_bits];
if (sw_bit) {
// delete key from the right child (c2)
auto res = dict_lookup_delete(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1);
if (res.first.is_null()) {
// return unchanged dictionary
return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
}
old_val = std::move(res.first);
c2 = std::move(res.second);
} else {
// delete key from the left child (c1)
auto res = dict_lookup_delete(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1);
if (res.first.is_null()) {
// return unchanged dictionary
return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
}
old_val = std::move(res.first);
c1 = std::move(res.second);
}
if (c1.not_null() && c2.not_null()) {
// create a new label with the same content leading to a fork with modified children
CellBuilder cb;
append_dict_label(cb, key, label.l_bits, n);
return std::make_pair(std::move(old_val), finish_create_fork(cb, std::move(c1), std::move(c2), n - label.l_bits));
}
// have to merge current edge with the edge leading to c1 or c2
if (!sw_bit) {
c1.swap(c2);
}
assert(c1.not_null() && c2.is_null());
unsigned char buffer[Dictionary::max_key_bytes];
td::BitPtr bw{buffer};
bw.concat(key, label.l_bits);
bw.concat_same(!sw_bit, 1);
LabelParser label2{std::move(c1), n - label.l_bits - 1, label_mode()};
bw += label2.extract_label_to(bw);
assert(bw.offs >= 0 && bw.offs <= Dictionary::max_key_bits);
CellBuilder cb;
append_dict_label(cb, td::ConstBitPtr{buffer}, bw.offs, n);
if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
}
label2.remainder.clear();
return std::make_pair(std::move(old_val), cb.finalize());
}
Ref<CellSlice> DictionaryFixed::lookup_delete(td::ConstBitPtr key, int key_len) {
force_validate();
if (key_len != get_key_bits()) {
return {};
}
auto res = dict_lookup_delete(get_root_cell(), key, key_len);
if (res.first.not_null()) {
set_root_cell(std::move(res.second));
}
return std::move(res.first);
}
Ref<Cell> Dictionary::lookup_delete_ref(td::ConstBitPtr key, int key_len) {
return extract_value_ref(lookup_delete(key, key_len));
}
Ref<CellSlice> DictionaryFixed::dict_lookup_minmax(Ref<Cell> dict, td::BitPtr key_buffer, int n, int mode) const {
if (dict.is_null()) {
return {};
}
while (1) {
LabelParser label{std::move(dict), n, label_mode()};
int l = label.extract_label_to(key_buffer);
assert(l >= 0 && l <= n);
key_buffer += l;
n -= l;
if (!n) {
return std::move(label.remainder);
}
if (l) {
mode >>= 1;
}
bool bit = mode & 1;
dict = label.remainder->prefetch_ref(bit);
*key_buffer++ = bit;
--n;
mode >>= 1;
}
}
Ref<CellSlice> DictionaryFixed::dict_lookup_nearest(Ref<Cell> dict, td::BitPtr key_buffer, int n, bool allow_eq,
int mode) const {
if (dict.is_null()) {
return {};
}
LabelParser label{dict, n, label_mode()};
int pfx_len = label.common_prefix_len(key_buffer, n);
assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
if (pfx_len < label.l_bits) {
if (key_buffer[pfx_len] == ((mode >> static_cast<int>(pfx_len != 0)) & 1)) {
return {};
} else {
return dict_lookup_minmax(std::move(dict), key_buffer, n, ~mode);
}
}
dict.clear();
if (label.l_bits) {
mode >>= 1;
}
key_buffer += label.l_bits;
n -= label.l_bits;
if (!n) {
if (!allow_eq) {
return {};
}
label.skip_label();
return std::move(label.remainder);
}
bool bit = *key_buffer++;
auto res = dict_lookup_nearest(label.remainder->prefetch_ref(bit), key_buffer, n - 1, allow_eq, mode >> 1);
if (res.not_null() || bit == (mode & 1)) {
return res;
}
key_buffer[-1] = mode & 1;
dict = label.remainder->prefetch_ref(mode & 1);
label.remainder.clear();
return dict_lookup_minmax(std::move(dict), key_buffer, n - 1, ~mode >> 1);
}
Ref<CellSlice> DictionaryFixed::lookup_nearest_key(td::BitPtr key_buffer, int key_len, bool fetch_next, bool allow_eq,
bool invert_first) {
force_validate();
if (key_len != get_key_bits()) {
return {};
}
return dict_lookup_nearest(get_root_cell(), key_buffer, key_len, allow_eq,
(-static_cast<int>(fetch_next)) ^ static_cast<int>(invert_first));
}
Ref<CellSlice> DictionaryFixed::get_minmax_key(td::BitPtr key_buffer, int key_len, bool fetch_max, bool invert_first) {
force_validate();
if (key_len != get_key_bits()) {
return {};
}
return dict_lookup_minmax(get_root_cell(), key_buffer, key_len,
(-static_cast<int>(fetch_max)) ^ static_cast<int>(invert_first));
}
Ref<Cell> Dictionary::get_minmax_key_ref(td::BitPtr key_buffer, int key_len, bool fetch_max, bool invert_first) {
return extract_value_ref(get_minmax_key(key_buffer, key_len, fetch_max, invert_first));
}
Ref<CellSlice> DictionaryFixed::extract_minmax_key(td::BitPtr key_buffer, int key_len, bool fetch_max,
bool invert_first) {
force_validate();
if (key_len != get_key_bits()) {
return {};
}
auto val = dict_lookup_minmax(get_root_cell(), key_buffer, key_len, -(fetch_max ? 1 : 0) ^ (invert_first ? 1 : 0));
if (val.is_null()) {
return {};
}
auto res = dict_lookup_delete(get_root_cell(), key_buffer, key_len);
assert(res.first.not_null());
set_root_cell(std::move(res.second));
return val;
}
Ref<Cell> Dictionary::extract_minmax_key_ref(td::BitPtr key_buffer, int key_len, bool fetch_max, bool invert_first) {
return extract_value_ref(extract_minmax_key(key_buffer, key_len, fetch_max, invert_first));
}
std::pair<Ref<Cell>, bool> DictionaryFixed::extract_prefix_subdict_internal(Ref<Cell> dict, td::ConstBitPtr prefix,
int prefix_len, bool remove_prefix) const {
if (is_empty() || prefix_len <= 0) {
return {{}, false}; // unchanged
}
if (prefix_len > get_key_bits()) {
return {{}, true}; // empty dict
}
int n = get_key_bits(), m = 0;
while (true) {
LabelParser label{std::move(dict), n - m, label_mode()};
int l = std::min(prefix_len - m, label.l_bits);
if (label.common_prefix_len(prefix + m, l) < l) {
return {{}, true}; // empty dict
}
if (m + label.l_bits < prefix_len) {
m += label.l_bits;
dict = label.remainder->prefetch_ref(prefix[m++]);
continue;
}
// end, have consumed all of prefix
vm::CellBuilder cb;
if (!remove_prefix) {
if (!m) {
// dictionary unchanged: all keys already begin with prefix
return {{}, false};
}
// concatenate prefix with a suffix of the label
assert(m <= prefix_len);
unsigned char buffer[max_key_bytes];
auto p = td::BitPtr{buffer};
p.copy_from(prefix, m);
label.extract_label_to(p + m);
append_dict_label(cb, p, m + label.l_bits, key_bits);
} else if (!label.l_same) {
m += label.l_bits - prefix_len; // leave that many last bits of the label
append_dict_label(cb, label.bits_end() - m, m, key_bits - prefix_len);
label.skip_label();
} else {
m += label.l_bits - prefix_len; // leave that many last bits of the label
append_dict_label_same(cb, label.l_same & 1, m, key_bits - prefix_len);
}
if (!cb.append_cellslice_bool(*label.remainder)) {
throw VmError{Excno::cell_ov, "cannot create new dictionary root while constructing prefix subdictionary"};
}
return {Ref<Cell>{cb.finalize()}, true};
}
}
bool DictionaryFixed::cut_prefix_subdict(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix) {
force_validate();
if (prefix_len < 0) {
return false;
}
if (prefix_len > key_bits && remove_prefix) {
return false;
}
auto res = extract_prefix_subdict_internal(get_root_cell(), prefix, prefix_len, remove_prefix);
if (remove_prefix) {
key_bits -= prefix_len;
}
if (res.second) {
set_root_cell(std::move(res.first));
}
return true;
}
Ref<vm::Cell> DictionaryFixed::extract_prefix_subdict_root(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix) {
force_validate();
auto res = extract_prefix_subdict_internal(get_root_cell(), prefix, prefix_len, remove_prefix);
return res.second ? res.first : root_cell;
}
std::pair<Ref<Cell>, int> DictionaryFixed::dict_filter(Ref<Cell> dict, td::BitPtr key, int n,
const DictionaryFixed::filter_func_t& check_leaf) const {
// std::cerr << "dictionary filter for " << n << "-bit key = " << (key + n - key_bits).to_hex(key_bits - n)
// << std::endl;
if (dict.is_null()) {
// empty dictionary, return unchanged
return {{}, 0};
}
LabelParser label{std::move(dict), n, label_mode()};
assert(label.l_bits >= 0 && label.l_bits <= n);
label.extract_label_to(key);
key += label.l_bits;
if (label.l_bits == n) {
// leaf
int res = check_leaf(label.remainder.write(), key - key_bits, key_bits);
return {{}, res < 0 ? res : !res};
}
// fork, process left and right subtrees
++key;
key[-1] = false;
int delta = label.l_bits + 1;
n -= delta;
auto left_res = dict_filter(label.remainder->prefetch_ref(0), key, n, check_leaf);
if (left_res.second < 0) {
return left_res;
}
key[-1] = true;
auto right_res = dict_filter(label.remainder->prefetch_ref(1), key, n, check_leaf);
if ((left_res.second | right_res.second) <= 0) {
// error in right, or both left and right unchanged
return right_res;
}
auto left = left_res.second ? std::move(left_res.first) : label.remainder->prefetch_ref(0);
auto right = right_res.second ? std::move(right_res.first) : label.remainder->prefetch_ref(1);
auto changes = left_res.second + right_res.second;
label.clear();
if (left.is_null()) {
if (right.is_null()) {
// both branches are empty => the result is an empty tree
return {{}, changes};
}
std::swap(left, right);
} else if (right.is_null()) {
key[-1] = false;
} else {
// both new branches are non-empty => create new fork
CellBuilder cb;
append_dict_label(cb, key - delta, label.l_bits, n + delta);
return {finish_create_fork(cb, std::move(left), std::move(right), n + 1), changes};
}
// only one child (in `left`) remains, collapse an edge
// NB: similar to code in lookup_delete()
assert(left.not_null() && right.is_null());
LabelParser label2{std::move(left), n, label_mode()};
label2.extract_label_to(key);
CellBuilder cb;
append_dict_label(cb, key - delta, delta + label2.l_bits, n + delta);
if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
}
label2.remainder.clear();
return {cb.finalize(), changes};
}
int DictionaryFixed::filter(DictionaryFixed::filter_func_t check_leaf) {
force_validate();
unsigned char buffer[DictionaryFixed::max_key_bytes];
auto res = dict_filter(get_root_cell(), td::BitPtr{buffer}, key_bits, check_leaf);
if (res.second > 0) {
// std::cerr << "after filter (" << res.second << " changes): new augmented dictionary root is:\n";
// vm::load_cell_slice(res.first).print_rec(std::cerr);
set_root_cell(std::move(res.first));
}
return res.second;
}
void Dictionary::map(const map_func_t& map_func) {
force_validate();
int key_len = get_key_bits();
unsigned char key_buffer[max_key_bytes];
auto res = dict_map(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, map_func);
set_root_cell(std::move(res));
}
void Dictionary::map(const simple_map_func_t& simple_map_func) {
using namespace std::placeholders;
map_func_t map_func = std::bind(simple_map_func, _1, _2);
map(map_func);
}
// mode: +1 = forbid empty dict1 with non-empty dict2
// +2 = forbid empty dict2 with non-empty dict1
Ref<Cell> DictionaryFixed::dict_combine_with(Ref<Cell> dict1, Ref<Cell> dict2, td::BitPtr key_buffer, int n,
int total_key_len, const DictionaryFixed::combine_func_t& combine_func,
int mode, int skip1, int skip2) const {
if (dict1.is_null()) {
assert(!skip2);
if ((mode & 1) && dict2.is_null()) {
throw CombineError{};
}
return dict2;
} else if (dict2.is_null()) {
assert(!skip1);
if ((mode & 2)) {
throw CombineError{};
}
return dict1;
}
// both dictionaries non-empty
// skip1: remove that much first bits from all keys in dictionary dict1 (its keys are actually n + skip1 bits long)
// skip2: similar for dict2
// resulting dictionary will have n-bit keys
LabelParser label1{dict1, n + skip1, label_mode()}, label2{dict2, n + skip2, label_mode()};
int l1 = label1.l_bits - skip1, l2 = label2.l_bits - skip2;
assert(l1 >= 0 && l2 >= 0);
assert(!skip1 || label1.common_prefix_len(key_buffer - skip1, skip1) == skip1);
assert(!skip2 || label2.common_prefix_len(key_buffer - skip2, skip2) == skip2);
label1.extract_label_to(key_buffer - skip1);
int c = label2.common_prefix_len(key_buffer - skip2, skip2 + l1) - skip2;
assert(c >= 0 && c <= l1 && c <= l2);
if (c < l1 && c < l2) {
// the two dictionaries have disjoint keys
dict1.clear();
dict2.clear();
if ((mode & 3)) {
throw CombineError{};
}
CellBuilder cb;
append_dict_label(cb, key_buffer + c + 1, l1 - c - 1, n - c - 1);
if (!cell_builder_add_slice_bool(cb, *label1.remainder)) {
throw VmError{Excno::cell_ov, "cannot prune label of an old dictionary cell while merging dictionaries"};
}
label1.remainder.clear();
dict1 = cb.finalize();
// cb.reset(); // included into finalize();
// now dict1 has been "pruned" -- first skip1+c+1 bits removed from its root egde label
label2.extract_label_to(key_buffer - skip2);
append_dict_label(cb, key_buffer + c + 1, l2 - c - 1, n - c - 1);
if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
}
label2.remainder.clear();
dict2 = cb.finalize();
// now dict2 has also been pruned
if (!key_buffer[c]) {
std::swap(dict1, dict2);
}
// put dict1 into the left tree (with smaller labels), dict2 into the right tree
append_dict_label(cb, key_buffer, c, n);
return finish_create_fork(cb, std::move(dict1), std::move(dict2), n - c);
}
if (c == l1 && c == l2) {
// funny enough, the non-skipped parts of labels of l1 and l2 match
dict1.clear();
dict2.clear();
label2.skip_label();
CellBuilder cb;
append_dict_label(cb, key_buffer, c, n);
if (c == n) {
// our two dictionaries are in fact leafs with matching edge labels (keys)
if (!combine_func(cb, std::move(label1.remainder), std::move(label2.remainder), key_buffer + n - total_key_len,
total_key_len)) {
// alas, the two values did not combine, this key will be absent from resulting dictionary
return {};
}
return cb.finalize();
}
assert(c < n);
key_buffer += c + 1;
key_buffer[-1] = 0;
// combine left subtrees
auto c1 = dict_combine_with(label1.remainder->prefetch_ref(0), label2.remainder->prefetch_ref(0), key_buffer,
n - c - 1, total_key_len, combine_func);
key_buffer[-1] = 1;
// combine right subtrees
auto c2 = dict_combine_with(label1.remainder->prefetch_ref(1), label2.remainder->prefetch_ref(1), key_buffer,
n - c - 1, total_key_len, combine_func);
label1.remainder.clear();
label2.remainder.clear();
// c1 and c2 are merged left and right children of dict1 and dict2
if (!c1.is_null() && !c2.is_null()) {
// both children non-empty, simply put them into the new node
return finish_create_fork(cb, std::move(c1), std::move(c2), n - c);
}
if (c1.is_null() && c2.is_null()) {
return {}; // both children empty, resulting dictionary also empty
}
// exactly one of c1 and c2 is non-empty, have to merge labels
bool sw = c1.is_null();
key_buffer[-1] = sw;
if (sw) {
c1 = std::move(c2);
}
LabelParser label3{std::move(c1), n - c - 1, label_mode()};
label3.extract_label_to(key_buffer);
key_buffer -= c + 1;
// store combined label for the new edge
cb.reset();
append_dict_label(cb, key_buffer, c + 1 + label3.l_bits, n);
// store payload
if (!cell_builder_add_slice_bool(cb, *label3.remainder)) {
throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
}
return cb.finalize();
}
if (c == l1) {
assert(c < l2);
dict1.clear();
if ((mode & 2)) {
throw CombineError{};
}
// children of root node of dict1
auto c1 = label1.remainder->prefetch_ref(0);
auto c2 = label1.remainder->prefetch_ref(1);
label1.remainder.clear();
// have to merge dict2 with one of the children of dict1
label2.extract_label_to(key_buffer - skip2); // dict2 has longer label, extract it
bool sw = key_buffer[c];
if (!sw) {
// merge c1 with dict2
c1 = dict_combine_with(std::move(c1), std::move(dict2), key_buffer + c + 1, n - c - 1, total_key_len,
combine_func, mode, 0, skip2 + c + 1);
} else {
// merge c2 with dict2
c2 = dict_combine_with(std::move(c2), std::move(dict2), key_buffer + c + 1, n - c - 1, total_key_len,
combine_func, mode, 0, skip2 + c + 1);
}
if (!c1.is_null() && !c2.is_null()) {
CellBuilder cb;
append_dict_label(cb, key_buffer, c, n);
return finish_create_fork(cb, std::move(c1), std::move(c2), n - c);
}
// one of children is empty, have to merge root edges
key_buffer[c] = !sw;
if (!sw) {
std::swap(c1, c2);
}
assert(!c1.is_null() && c2.is_null());
LabelParser label3{std::move(c1), n - c - 1, label_mode()};
label3.extract_label_to(key_buffer + c + 1);
CellBuilder cb;
append_dict_label(cb, key_buffer, c + 1 + label3.l_bits, n);
// store payload
if (!cell_builder_add_slice_bool(cb, *label3.remainder)) {
throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
}
return cb.finalize();
} else {
assert(c == l2 && c < l1);
dict2.clear();
if ((mode & 1)) {
throw CombineError{};
}
// children of root node of dict2
label2.skip_label(); // dict2 had shorter label anyway, label1 is already unpacked
auto c1 = label2.remainder->prefetch_ref(0);
auto c2 = label2.remainder->prefetch_ref(1);
label2.remainder.clear();
// have to merge dict1 with one of the children of dict2
bool sw = key_buffer[c];
if (!sw) {
// merge dict1 with c1
c1 = dict_combine_with(std::move(dict1), std::move(c1), key_buffer + c + 1, n - c - 1, total_key_len,
combine_func, mode, skip1 + c + 1, 0);
} else {
// merge dict1 with c2
c2 = dict_combine_with(std::move(dict1), std::move(c2), key_buffer + c + 1, n - c - 1, total_key_len,
combine_func, mode, skip1 + c + 1, 0);
}
if (!c1.is_null() && !c2.is_null()) {
CellBuilder cb;
append_dict_label(cb, key_buffer, c, n);
return finish_create_fork(cb, std::move(c1), std::move(c2), n - c);
}
// one of children is empty, have to merge root edges
key_buffer[c] = !sw;
if (!sw) {
std::swap(c1, c2);
}
assert(!c1.is_null() && c2.is_null());
LabelParser label3{std::move(c1), n - c - 1, label_mode()};
label3.extract_label_to(key_buffer + c + 1);
CellBuilder cb;
append_dict_label(cb, key_buffer, c + 1 + label3.l_bits, n);
// store payload
if (!cell_builder_add_slice_bool(cb, *label3.remainder)) {
throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
}
return cb.finalize();
}
}
bool DictionaryFixed::combine_with(DictionaryFixed& dict2, const combine_func_t& combine_func, int mode) {
force_validate();
dict2.force_validate();
int key_len = get_key_bits();
if (key_len != dict2.get_key_bits()) {
throw VmError{Excno::dict_err, "cannot combine dictionaries with different key lengths"};
}
unsigned char key_buffer[max_key_bytes];
try {
auto res = dict_combine_with(get_root_cell(), dict2.get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len,
combine_func, mode);
set_root_cell(std::move(res));
return true;
} catch (CombineError) {
return false;
}
}
bool DictionaryFixed::combine_with(DictionaryFixed& dict2, const simple_combine_func_t& simple_combine_func, int mode) {
using namespace std::placeholders;
combine_func_t combine_func = std::bind(simple_combine_func, _1, _2, _3);
return combine_with(dict2, combine_func, mode);
}
bool DictionaryFixed::combine_with(DictionaryFixed& dict2) {
return combine_with(dict2,
[](CellBuilder&, Ref<CellSlice>, Ref<CellSlice>, td::ConstBitPtr key, int key_len) -> bool {
LOG(WARNING) << "dictionary merge conflict for key " << key.to_hex(key_len);
throw CombineError{};
});
}
bool DictionaryFixed::dict_check_for_each(Ref<Cell> dict, td::BitPtr key_buffer, int n, int total_key_len,
const DictionaryFixed::foreach_func_t& foreach_func,
bool invert_first) const {
if (dict.is_null()) {
return true;
}
LabelParser label{std::move(dict), n, label_mode()};
int l = label.l_bits;
label.extract_label_to(key_buffer);
if (l == n) {
// leaf node, value left in label.remainder
return foreach_func(std::move(label.remainder), key_buffer + n - total_key_len, total_key_len);
}
assert(l >= 0 && l < n);
// a fork with two children, c1 and c2
auto c1 = label.remainder->prefetch_ref(0);
auto c2 = label.remainder->prefetch_ref(1);
label.remainder.clear();
key_buffer += l + 1;
if (l) {
invert_first = false;
} else if (invert_first) {
std::swap(c1, c2);
}
key_buffer[-1] = invert_first;
// recursive check_foreach applied to both children
if (!dict_check_for_each(std::move(c1), key_buffer, n - l - 1, total_key_len, foreach_func)) {
return false;
}
key_buffer[-1] = !invert_first;
return dict_check_for_each(std::move(c2), key_buffer, n - l - 1, total_key_len, foreach_func);
}
bool DictionaryFixed::check_for_each(const foreach_func_t& foreach_func, bool invert_first) {
force_validate();
if (is_empty()) {
return true;
}
int key_len = get_key_bits();
unsigned char key_buffer[max_key_bytes];
return dict_check_for_each(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, foreach_func, invert_first);
}
static inline bool set_bit(td::BitPtr ptr, bool value = true) {
*ptr = value;
return true;
}
// mode: +1 = check augmentation of dict1, +2 = ... of dict2
bool DictionaryFixed::dict_scan_diff(Ref<Cell> dict1, Ref<Cell> dict2, td::BitPtr key_buffer, int n, int total_key_len,
const scan_diff_func_t& diff_func, int mode, int skip1, int skip2) const {
// skip1: remove that much first bits from all keys in dictionary dict1 (its keys are actually n + skip1 bits long)
// skip2: similar for dict2
// pretending to compare subdictionaries with n-bit keys
if (dict1.is_null()) {
if (dict2.is_null()) {
return true; // both dictionaries are empty
}
assert(!skip2);
// dict1 empty, dict2 non-empty -> parse label of dict2
LabelParser label{dict2, n, label_mode()};
label.extract_label_to(key_buffer);
if (label.l_bits >= n) {
assert(label.l_bits == n);
// leaf in dict2, empty dict1
auto key = key_buffer + label.l_bits - total_key_len;
if ((mode & 2) && !check_leaf(label.remainder, key, total_key_len)) {
throw VmError{Excno::dict_err, "invalid leaf in the second dictionary being compared"};
}
return diff_func(key, total_key_len, {}, std::move(label.remainder));
}
n -= label.l_bits + 1;
key_buffer += label.l_bits + 1;
if ((mode & 2) && !check_fork_raw(label.remainder, n + 1)) {
throw VmError{Excno::dict_err, "invalid fork in the second dictionary being compared"};
}
// compare {} with each of children of dict2
for (unsigned sw = 0; sw < 2; sw++) {
key_buffer[-1] = (bool)sw;
if (!dict_scan_diff({}, label.remainder->prefetch_ref(sw), key_buffer, n, total_key_len, diff_func, mode)) {
return false;
}
}
return true;
} else if (dict2.is_null()) {
assert(!skip1);
// dict2 empty, dict1 non-empty -> parse label of dict1
LabelParser label{dict1, n, label_mode()};
label.extract_label_to(key_buffer);
if (label.l_bits >= n) {
assert(label.l_bits == n);
// leaf in dict1, empty dict2
auto key = key_buffer + label.l_bits - total_key_len;
if ((mode & 1) && !check_leaf(label.remainder, key, total_key_len)) {
throw VmError{Excno::dict_err, "invalid leaf in the first dictionary being compared"};
}
return diff_func(key, total_key_len, std::move(label.remainder), {});
}
n -= label.l_bits + 1;
key_buffer += label.l_bits + 1;
if ((mode & 1) && !check_fork_raw(label.remainder, n + 1)) {
throw VmError{Excno::dict_err, "invalid fork in the first dictionary being compared"};
}
// compare each of children of dict1 with {}
for (unsigned sw = 0; sw < 2; sw++) {
key_buffer[-1] = (bool)sw;
if (!dict_scan_diff(label.remainder->prefetch_ref(sw), {}, key_buffer, n, total_key_len, diff_func, mode)) {
return false;
}
}
return true;
}
// both dictionaries non-empty
if (skip1 == skip2 && (dict1 == dict2 || dict1->get_hash() == dict2->get_hash())) {
// dictionaries match, subtree comparison not necessary
return true;
}
LabelParser label1{dict1, n + skip1, label_mode()}, label2{dict2, n + skip2, label_mode()};
int l1 = label1.l_bits - skip1, l2 = label2.l_bits - skip2;
assert(l1 >= 0 && l2 >= 0);
assert(!skip1 || label1.common_prefix_len(key_buffer - skip1, skip1) == skip1);
assert(!skip2 || label2.common_prefix_len(key_buffer - skip2, skip2) == skip2);
label1.extract_label_to(key_buffer - skip1);
int c = label2.common_prefix_len(key_buffer - skip2, skip2 + l1) - skip2;
assert(c >= 0 && c <= l1 && c <= l2);
if (c < l1 && c < l2) {
// the two dictionaries have disjoint keys
if (!key_buffer[c]) {
// all keys of dict1 are before dict2
return dict_scan_diff(std::move(dict1), {}, key_buffer - skip1, n + skip1, total_key_len, diff_func, mode) &&
dict_scan_diff({}, std::move(dict2), key_buffer - skip2, n + skip2, total_key_len, diff_func, mode);
} else {
// all keys of dict2 are before dict1
return dict_scan_diff({}, std::move(dict2), key_buffer - skip2, n + skip2, total_key_len, diff_func, mode) &&
dict_scan_diff(std::move(dict1), {}, key_buffer - skip1, n + skip1, total_key_len, diff_func, mode);
}
}
if (c == l1 && c == l2) {
// funny enough, the non-skipped parts of labels of l1 and l2 match
dict1.clear();
dict2.clear();
label2.skip_label();
if (c == n) {
// our two dictionaries are in fact leafs with matching edge labels (keys)
auto key = key_buffer + n - total_key_len;
if ((mode & 1) && !check_leaf(label1.remainder, key, total_key_len)) {
throw VmError{Excno::dict_err, "invalid leaf in the first dictionary being compared"};
}
if ((mode & 2) && !check_leaf(label2.remainder, key, total_key_len)) {
throw VmError{Excno::dict_err, "invalid leaf in the second dictionary being compared"};
}
return label1.remainder->contents_equal(*label2.remainder) ||
diff_func(key, total_key_len, std::move(label1.remainder), std::move(label2.remainder));
}
assert(c < n);
key_buffer += c + 1;
n -= c + 1;
if ((mode & 1) && !check_fork_raw(label1.remainder, n + 1)) {
throw VmError{Excno::dict_err, "invalid fork in the first dictionary being compared"};
}
if ((mode & 2) && !check_fork_raw(label2.remainder, n + 1)) {
throw VmError{Excno::dict_err, "invalid fork in the second dictionary being compared"};
}
for (unsigned sw = 0; sw <= 1; sw++) {
key_buffer[-1] = (bool)sw;
// compare left and then right subtrees
if (!dict_scan_diff(label1.remainder->prefetch_ref(sw), label2.remainder->prefetch_ref(sw), key_buffer, n,
total_key_len, diff_func, mode)) {
return false;
}
}
return true;
}
if (c == l1) {
assert(c < l2);
dict1.clear();
if ((mode & 1) && !check_fork_raw(label1.remainder, n - c)) {
throw VmError{Excno::dict_err, "invalid fork in the first dictionary being compared"};
}
// children of root node of dict1
auto c1 = label1.remainder->prefetch_ref(0);
auto c2 = label1.remainder->prefetch_ref(1);
label1.remainder.clear();
// have to compare dict2 with one of the children of dict1
label2.extract_label_to(key_buffer - skip2); // dict2 has longer label, extract it
key_buffer += c + 1;
n -= c + 1;
bool sw = key_buffer[-1];
key_buffer[-1] = false;
if (!sw) {
// compare c1 with dict2, then c2 with {}
return dict_scan_diff(std::move(c1), std::move(dict2), key_buffer, n, total_key_len, diff_func, mode, 0,
skip2 + c + 1) &&
set_bit(key_buffer - 1) &&
dict_scan_diff(std::move(c2), {}, key_buffer, n, total_key_len, diff_func, mode);
} else {
// compare c1 with {}, then c2 with dict2
return dict_scan_diff(std::move(c1), {}, key_buffer, n, total_key_len, diff_func, mode) &&
set_bit(key_buffer - 1) &&
dict_scan_diff(std::move(c2), std::move(dict2), key_buffer, n, total_key_len, diff_func, mode, 0,
skip2 + c + 1);
}
} else {
assert(c == l2 && c < l1);
dict2.clear();
label2.skip_label(); // dict2 had shorter label anyway, label1 is already unpacked
if ((mode & 2) && !check_fork_raw(label2.remainder, n - c)) {
throw VmError{Excno::dict_err, "invalid fork in the second dictionary being compared"};
}
// children of root node of dict2
auto c1 = label2.remainder->prefetch_ref(0);
auto c2 = label2.remainder->prefetch_ref(1);
label2.remainder.clear();
// have to compare dict1 with one of the children of dict2
key_buffer += c + 1;
n -= c + 1;
bool sw = key_buffer[-1];
key_buffer[-1] = false;
if (!sw) {
// compare dict1 with c1, then {} with c2
return dict_scan_diff(std::move(dict1), std::move(c1), key_buffer, n, total_key_len, diff_func, mode,
skip1 + c + 1, 0) &&
set_bit(key_buffer - 1) &&
dict_scan_diff({}, std::move(c2), key_buffer, n, total_key_len, diff_func, mode);
} else {
// compare {} with c1, then dict1 with c2
return dict_scan_diff({}, std::move(c1), key_buffer, n, total_key_len, diff_func, mode) &&
set_bit(key_buffer - 1) &&
dict_scan_diff(std::move(dict1), std::move(c2), key_buffer, n, total_key_len, diff_func, mode,
skip1 + c + 1, 0);
}
}
}
bool DictionaryFixed::scan_diff(DictionaryFixed& dict2, const scan_diff_func_t& diff_func, int check_augm) {
force_validate();
dict2.force_validate();
int key_len = get_key_bits();
if (key_len != dict2.get_key_bits()) {
throw VmError{Excno::dict_err, "cannot compare dictionaries with different key lengths"};
}
unsigned char key_buffer[max_key_bytes];
try {
return dict_scan_diff(get_root_cell(), dict2.get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, diff_func,
check_augm);
} catch (CombineError) {
return false;
}
}
bool DictionaryFixed::dict_validate_check(Ref<Cell> dict, td::BitPtr key_buffer, int n, int total_key_len,
const DictionaryFixed::foreach_func_t& foreach_func,
bool invert_first) const {
//LOG(DEBUG) << "dict_validate_check for " << total_key_len - n << "-bit key prefix " << (key_buffer - n + total_key_len).to_hex(total_key_len - n);
if (dict.is_null()) {
return true;
}
LabelParser label{std::move(dict), n, label_mode()};
int l = label.l_bits;
label.extract_label_to(key_buffer);
if (l == n) {
// leaf node, value left in label.remainder
vm::CellSlice cs{*label.remainder};
auto key = key_buffer + n - total_key_len;
if (!(check_leaf(cs, key, total_key_len) && foreach_func(std::move(label.remainder), key, total_key_len))) {
LOG(DEBUG) << "invalid dictionary leaf node with " << total_key_len << "-bit key " << key.to_hex(total_key_len);
return false;
}
return true;
}
assert(l >= 0 && l < n);
// a fork with two children, c1 and c2
auto c1 = label.remainder.write().fetch_ref();
auto c2 = label.remainder.unique_write().fetch_ref();
key_buffer += l + 1;
n -= l + 1;
if (!check_fork(label.remainder.write(), c1, c2, n + 1)) {
LOG(DEBUG) << "invalid dictionary fork augmentation for fork node with " << total_key_len - n - 1
<< "-bit key prefix " << (key_buffer + n - total_key_len).to_hex(total_key_len - n - 1);
return false;
}
label.remainder.clear();
if (l) {
invert_first = false;
} else if (invert_first) {
std::swap(c1, c2);
}
key_buffer[-1] = invert_first;
// recursive check_foreach applied to both children
if (!dict_validate_check(std::move(c1), key_buffer, n, total_key_len, foreach_func)) {
return false;
}
key_buffer[-1] = !invert_first;
return dict_validate_check(std::move(c2), key_buffer, n, total_key_len, foreach_func);
}
bool DictionaryFixed::validate_check(const DictionaryFixed::foreach_func_t& foreach_func, bool invert_first) {
if (!validate()) {
return false;
}
if (is_empty()) {
return true;
}
int key_len = get_key_bits();
unsigned char key_buffer[max_key_bytes];
return dict_validate_check(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, foreach_func, invert_first);
}
bool DictionaryFixed::validate_all() {
return validate_check([](Ref<CellSlice> value, td::ConstBitPtr key, int n) { return true; }) || invalidate();
}
/*
*
* PREFIX DICTIONARIES
*
*/
std::pair<Ref<CellSlice>, int> PrefixDictionary::lookup_prefix(td::ConstBitPtr key, int key_len) {
force_validate();
int n = get_key_bits();
if (is_empty()) {
return std::make_pair(Ref<CellSlice>{}, 0);
}
//std::cerr << "dictionary lookup for key = " << key.to_hex(key_len) << std::endl;
Ref<Cell> cell = get_root_cell();
int m = key_len;
while (true) {
LabelParser label{std::move(cell), n, 1};
int l = label.common_prefix_len(key, m);
if (l < label.l_bits) {
//std::cerr << "(not a prefix)\n";
return std::make_pair(Ref<CellSlice>{}, key_len - m + l);
}
n -= label.l_bits;
m -= label.l_bits;
assert(m >= 0);
label.skip_label();
Ref<CellSlice> cs = std::move(label.remainder);
if (!cs->have(1)) {
throw VmError{Excno::dict_err, "no node constructor in a prefix code dictionary"};
}
if (!cs.unique_write().fetch_ulong(1)) {
return std::make_pair(std::move(cs), key_len - m);
}
if (!n) {
throw VmError{Excno::dict_err, "a fork node in a prefix code dictionary with zero remaining key length"};
}
if (cs->size() != 0 || cs->size_refs() != 2) {
throw VmError{Excno::dict_err, "invalid fork node in a prefix code dictionary"};
}
if (!m) {
return std::make_pair(Ref<CellSlice>{}, key_len);
}
key += label.l_bits;
bool sw = *key++;
//std::cerr << "key bit at position " << key_bits - n << " equals " << sw << std::endl;
--n;
--m;
cell = cs->prefetch_ref(sw);
}
}
Ref<CellSlice> PrefixDictionary::lookup(td::ConstBitPtr key, int key_len) {
force_validate();
if (key_len > get_key_bits()) {
return {};
}
auto res = lookup_prefix(key, key_len);
return res.second == key_len ? std::move(res.first) : Ref<CellSlice>{};
}
bool PrefixDictionary::set_gen(td::ConstBitPtr key, int key_len, const std::function<bool(CellBuilder&)>& store_val,
SetMode mode) {
force_validate();
if (key_len > get_key_bits() || key_len < 0) {
return false;
}
auto res = pfx_dict_set(get_root_cell(), key, key_len, get_key_bits(), store_val, mode);
if (res.second) {
set_root_cell(std::move(res.first));
}
return res.second;
}
bool PrefixDictionary::set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode) {
return set_gen(key, key_len, [value](CellBuilder& cb) { return cell_builder_add_slice_bool(cb, *value); }, mode);
}
bool PrefixDictionary::set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b, SetMode mode) {
return set_gen(key, key_len, [val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
}
Ref<CellSlice> PrefixDictionary::lookup_delete(td::ConstBitPtr key, int key_len) {
force_validate();
if (key_len > get_key_bits() || key_len < 0) {
return {};
}
auto res = pfx_dict_lookup_delete(get_root_cell(), key, key_len, get_key_bits());
if (res.first.not_null()) {
set_root_cell(std::move(res.second));
}
return std::move(res.first);
}
/*
*
* AUGMENTED DICTIONARIES
*
*/
namespace dict {
bool AugmentationData::check_empty(vm::CellSlice& cs) const {
vm::CellBuilder cb;
return eval_empty(cb) && cb.contents_equal(cs);
}
bool AugmentationData::check_leaf(vm::CellSlice& cs, vm::CellSlice& val_cs) const {
vm::CellBuilder cb;
return eval_leaf(cb, val_cs) && cb.contents_equal(cs);
}
bool AugmentationData::check_fork(vm::CellSlice& cs, vm::CellSlice& left_cs, vm::CellSlice& right_cs) const {
vm::CellBuilder cb;
return eval_fork(cb, left_cs, right_cs) && cb.contents_equal(cs);
}
Ref<vm::CellSlice> AugmentationData::extract_extra(vm::CellSlice& cs) const {
Ref<CellSlice> res{true, cs};
return skip_extra(cs) && res.write().cut_tail(cs) ? std::move(res) : Ref<CellSlice>{};
}
Ref<vm::CellSlice> AugmentationData::extract_extra(Ref<vm::CellSlice> cs_ref) const {
CellSlice cs{*cs_ref};
return skip_extra(cs) && cs_ref.write().cut_tail(cs) ? std::move(cs_ref) : Ref<CellSlice>{};
}
bool AugmentationData::extract_extra_to(vm::CellSlice& cs, vm::CellSlice& extra) const {
extra = cs;
return cs.is_valid() && skip_extra(cs) && extra.cut_tail(cs);
}
} // namespace dict
using dict::AugmentationData;
using dict::LabelParser;
AugmentedDictionary::AugmentedDictionary(int _n, const AugmentationData& _aug, bool validate)
: DictionaryFixed(_n, false), aug(_aug) {
if (validate) {
force_validate();
}
}
AugmentedDictionary::AugmentedDictionary(Ref<CellSlice> _root, int _n, const AugmentationData& _aug, bool validate)
: DictionaryFixed(std::move(_root), _n, false), aug(_aug) {
if (validate) {
force_validate();
}
}
AugmentedDictionary::AugmentedDictionary(Ref<Cell> cell, int _n, const AugmentationData& _aug, bool validate)
: DictionaryFixed(std::move(cell), _n, false), aug(_aug) {
if (validate) {
force_validate();
}
}
AugmentedDictionary::AugmentedDictionary(DictNonEmpty, Ref<CellSlice> _root, int _n, const AugmentationData& _aug,
bool validate)
: DictionaryFixed(DictNonEmpty{}, std::move(_root), _n, false), aug(_aug) {
if (validate) {
force_validate();
}
}
bool AugmentedDictionary::validate() {
if (is_valid()) {
return true;
}
if (flags & f_invalid) {
return false;
}
if (key_bits < 0 || key_bits > max_key_bits) {
return invalidate();
}
if (flags & f_root_cached) {
if (root.is_null() || !root->size()) {
return invalidate();
}
bool non_empty = root->prefetch_ulong(1);
if (non_empty && !root->size_refs()) {
return invalidate();
}
if (root_cell.not_null()) {
return invalidate();
}
vm::CellSlice cs{*root};
if (!cs.advance(1)) {
return invalidate();
}
if (non_empty) {
root_cell = cs.fetch_ref();
auto root_extra = get_root_extra();
if (!(root_extra.not_null() && root_extra->contents_equal(cs))) {
return invalidate();
}
} else {
if (!aug.check_empty(cs)) {
return invalidate();
}
}
} else if (root.not_null()) {
return invalidate();
}
flags |= f_valid;
return true;
}
Ref<CellSlice> AugmentedDictionary::get_root() const {
if (!(flags & f_root_cached) && !compute_root()) {
return {};
}
return root;
}
Ref<CellSlice> AugmentedDictionary::extract_root() && {
if (!(flags & f_root_cached) && !compute_root()) {
return {};
}
flags = f_invalid;
return std::move(root);
}
bool AugmentedDictionary::append_dict_to_bool(CellBuilder& cb) const& {
if (!is_valid()) {
return false;
}
if (root_cell.is_null()) {
return cb.store_zeroes_bool(1) && aug.eval_empty(cb);
} else {
return cb.store_ones_bool(1) && cb.store_ref_bool(root_cell) && cb.append_cellslice_bool(get_root_extra());
}
}
bool AugmentedDictionary::append_dict_to_bool(CellBuilder& cb) && {
if (!is_valid()) {
return false;
}
flags = f_invalid;
if (root_cell.is_null()) {
return cb.store_zeroes_bool(1) && aug.eval_empty(cb);
} else {
return cb.store_ones_bool(1) && cb.store_ref_bool(root_cell) && cb.append_cellslice_bool(get_root_extra());
}
}
bool AugmentedDictionary::compute_root() const {
if (!is_valid()) {
return false;
}
if (root_cell.is_null()) {
root = get_empty_dictionary();
flags |= f_root_cached;
return true;
}
CellBuilder cb;
if (cb.store_long_bool(1, 1) && cb.store_ref_bool(root_cell) && cb.append_cellslice_bool(get_root_extra())) {
root = Ref<CellSlice>{true, cb.finalize()};
flags |= f_root_cached;
return true;
} else {
return false;
}
}
Ref<CellSlice> AugmentedDictionary::get_empty_dictionary() const {
CellBuilder cb;
cb.store_long(0, 1);
return aug.eval_empty(cb) ? Ref<CellSlice>{true, cb.finalize()} : Ref<CellSlice>{};
}
Ref<CellSlice> AugmentedDictionary::get_node_extra(Ref<Cell> cell_ref, int n) const {
if (cell_ref.is_null()) {
CellBuilder cb;
if (!aug.eval_empty(cb)) {
return {};
}
return Ref<CellSlice>{true, cb.finalize()};
}
LabelParser label{std::move(cell_ref), n, 2};
label.skip_label();
if (label.l_bits == n) {
return aug.extract_extra(std::move(label.remainder));
} else if (label.remainder.write().advance_refs(2)) {
vm::CellSlice cs{*label.remainder};
if (aug.skip_extra(cs) && cs.empty_ext()) {
return std::move(label.remainder);
}
}
return {};
}
Ref<CellSlice> AugmentedDictionary::get_root_extra() const {
return get_node_extra(root_cell, key_bits);
}
Ref<CellSlice> AugmentedDictionary::extract_value(Ref<CellSlice> value_extra) const {
if (value_extra.not_null() && aug.skip_extra(value_extra.write())) {
return value_extra;
} else {
return {};
}
}
Ref<Cell> AugmentedDictionary::extract_value_ref(Ref<CellSlice> value_extra) const {
if (value_extra.not_null() && aug.skip_extra(value_extra.write()) && value_extra->size_ext() == 0x10000) {
return value_extra->prefetch_ref();
} else {
return {};
}
}
std::pair<Ref<CellSlice>, Ref<CellSlice>> AugmentedDictionary::decompose_value_extra(Ref<CellSlice> value_extra) const {
if (value_extra.is_null()) {
return {};
}
auto extra = aug.extract_extra(value_extra.write());
if (extra.is_null()) {
return {};
} else {
return {std::move(value_extra), std::move(extra)};
}
}
std::pair<Ref<Cell>, Ref<CellSlice>> AugmentedDictionary::decompose_value_ref_extra(Ref<CellSlice> value_extra) const {
if (value_extra.is_null()) {
return {};
}
auto extra = aug.extract_extra(value_extra.write());
if (extra.is_null() || value_extra->size_ext() != 0x10000) {
return {};
} else {
return {value_extra->prefetch_ref(), std::move(extra)};
}
}
Ref<CellSlice> AugmentedDictionary::lookup_with_extra(td::ConstBitPtr key, int key_len) {
return DictionaryFixed::lookup(key, key_len);
}
Ref<CellSlice> AugmentedDictionary::lookup(td::ConstBitPtr key, int key_len) {
return extract_value(lookup_with_extra(key, key_len));
}
Ref<Cell> AugmentedDictionary::lookup_ref(td::ConstBitPtr key, int key_len) {
return extract_value_ref(lookup_with_extra(key, key_len));
}
std::pair<Ref<CellSlice>, Ref<CellSlice>> AugmentedDictionary::lookup_extra(td::ConstBitPtr key, int key_len) {
return decompose_value_extra(lookup_with_extra(key, key_len));
}
std::pair<Ref<Cell>, Ref<CellSlice>> AugmentedDictionary::lookup_ref_extra(td::ConstBitPtr key, int key_len) {
return decompose_value_ref_extra(lookup_with_extra(key, key_len));
}
Ref<CellSlice> AugmentedDictionary::lookup_delete_with_extra(td::ConstBitPtr key, int key_len) {
return DictionaryFixed::lookup_delete(key, key_len);
}
Ref<CellSlice> AugmentedDictionary::lookup_delete(td::ConstBitPtr key, int key_len) {
return extract_value(lookup_delete_with_extra(key, key_len));
}
Ref<Cell> AugmentedDictionary::lookup_delete_ref(td::ConstBitPtr key, int key_len) {
return extract_value_ref(lookup_delete_with_extra(key, key_len));
}
std::pair<Ref<CellSlice>, Ref<CellSlice>> AugmentedDictionary::lookup_delete_extra(td::ConstBitPtr key, int key_len) {
return decompose_value_extra(lookup_delete_with_extra(key, key_len));
}
bool AugmentedDictionary::check_leaf(CellSlice& cs, td::ConstBitPtr key, int key_len) const {
vm::CellSlice extra;
return aug.extract_extra_to(cs, extra) && aug.check_leaf_key_extra(cs, extra, key, key_len);
}
bool AugmentedDictionary::check_fork(CellSlice& cs, Ref<Cell> c1, Ref<Cell> c2, int n) const {
if (n <= 0) {
return false;
}
auto extra1 = get_node_extra(std::move(c1), n - 1);
auto extra2 = get_node_extra(std::move(c2), n - 1);
return extra1.not_null() && extra2.not_null() && aug.check_fork(cs, extra1.write(), extra2.write());
}
Ref<Cell> AugmentedDictionary::finish_create_leaf(CellBuilder& cb, const CellSlice& value) const {
CellSlice value_copy{value};
if (!aug.eval_leaf(cb, value_copy)) {
throw VmError{Excno::dict_err, "cannot compute and store extra value into an augmented dictionary cell"};
}
if (!cb.append_cellslice_bool(value)) {
throw VmError{Excno::dict_err, "cannot store new value into an augmented dictionary cell"};
}
return cb.finalize();
}
Ref<Cell> AugmentedDictionary::finish_create_fork(CellBuilder& cb, Ref<Cell> c1, Ref<Cell> c2, int n) const {
assert(n > 0);
if (!(cb.store_ref_bool(c1) && cb.store_ref_bool(c2))) {
throw VmError{Excno::dict_err, "cannot store branch references into an augmented dictionary cell"};
}
auto extra1 = get_node_extra(std::move(c1), n - 1);
auto extra2 = get_node_extra(std::move(c2), n - 1);
if (extra1.is_null()) {
throw VmError{Excno::dict_err, "cannot extract extra value from left branch of an augmented dictionary fork node"};
}
if (extra2.is_null()) {
throw VmError{Excno::dict_err, "cannot extract extra value from left branch of an augmented dictionary fork node"};
}
if (!aug.eval_fork(cb, extra1.write(), extra2.write())) {
throw VmError{Excno::dict_err, "cannot compute extra value for an augmented dictionary fork node"};
}
return cb.finalize();
}
std::pair<Ref<Cell>, bool> AugmentedDictionary::dict_set(Ref<Cell> dict, td::ConstBitPtr key, int n,
const CellSlice& value, Dictionary::SetMode mode) const {
//std::cerr << "augmented dictionary modification for " << n << "-bit key = " << key.to_hex(n) << std::endl;
if (dict.is_null()) {
// the dictionary is very empty
if (mode == Dictionary::SetMode::Replace) {
return std::make_pair<Ref<Cell>, bool>({}, false);
}
// create an one-element dictionary
CellBuilder cb;
append_dict_label(cb, key, n, n);
return std::make_pair(finish_create_leaf(cb, value), true);
}
LabelParser label{std::move(dict), n, 2};
label.validate();
int pfx_len = label.common_prefix_len(key, n);
assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
if (pfx_len < label.l_bits) {
// have to insert a new node (fork) inside the current edge
if (mode == Dictionary::SetMode::Replace) {
// key not found, return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
// first, create the edge + new leaf cell
int m = n - pfx_len - 1;
CellBuilder cb;
append_dict_label(cb, key + (pfx_len + 1), m, m);
Ref<Cell> c1 = finish_create_leaf(cb, value); // new leaf cell corresponding to `key`
//cb.reset();
// create the lower portion of the old edge
int t = label.l_bits - pfx_len - 1;
auto cs = std::move(label.remainder);
if (label.l_same) {
append_dict_label_same(cb, label.l_same & 1, t, m);
} else {
cs.write().advance(pfx_len + 1);
append_dict_label(cb, cs->data_bits(), t, m);
cs.unique_write().advance(t);
}
// now cs is the old payload of the edge, either a value or two subdictionary references
if (!cell_builder_add_slice_bool(cb, *cs)) {
throw VmError{Excno::cell_ov, "cannot change label of an old augmented dictionary cell (?)"};
}
Ref<Cell> c2 = cb.finalize(); // the other child of the new fork
// cb.reset();
append_dict_label(cb, key, pfx_len, n);
bool sw_bit = key[pfx_len];
if (sw_bit) {
c1.swap(c2);
}
return std::make_pair(finish_create_fork(cb, std::move(c1), std::move(c2), n - pfx_len), true);
}
if (label.l_bits == n) {
// the edge leads to a leaf node
// this leaf node already contains a value for the key wanted
if (mode == Dictionary::SetMode::Add) {
// return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
// replace the value of the only element of the dictionary
CellBuilder cb;
append_dict_label(cb, key, n, n);
return std::make_pair(finish_create_leaf(cb, value), true);
}
// main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
auto c1 = label.remainder->prefetch_ref(0);
auto c2 = label.remainder->prefetch_ref(1);
label.remainder.clear();
if (key[label.l_bits]) {
// insert key into the right child (c2)
auto res = dict_set(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1, value, mode);
if (!res.second) {
// return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
c2 = std::move(res.first);
} else {
// insert key into the left child (c1)
auto res = dict_set(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1, value, mode);
if (!res.second) {
// return unchanged dictionary
return std::make_pair(Ref<Cell>{}, false);
}
c1 = std::move(res.first);
}
// create a new label with the same content
CellBuilder cb;
append_dict_label(cb, key, label.l_bits, n);
return std::make_pair(finish_create_fork(cb, std::move(c1), std::move(c2), n - label.l_bits), true);
}
bool AugmentedDictionary::set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode) {
return value.not_null() && set(key, key_len, *value, mode);
}
bool AugmentedDictionary::set(td::ConstBitPtr key, int key_len, const CellSlice& value, SetMode mode) {
force_validate();
if (key_len != get_key_bits()) {
return false;
}
auto res = dict_set(get_root_cell(), key, key_len, value, mode);
if (res.second) {
//vm::CellSlice cs{vm::NoVm{}, res.first};
//std::cerr << "new augmented dictionary root is:\n";
//cs.print_rec(std::cerr);
set_root_cell(std::move(res.first));
}
return res.second;
}
bool AugmentedDictionary::set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> value_ref, SetMode mode) {
if (value_ref.not_null()) {
CellBuilder cb;
cb.store_ref(std::move(value_ref));
return set(key, key_len, load_cell_slice(cb.finalize()));
} else {
return false;
}
}
bool AugmentedDictionary::set_builder(td::ConstBitPtr key, int key_len, const CellBuilder& value, SetMode mode) {
return set(key, key_len, load_cell_slice(value.finalize_copy()));
}
bool AugmentedDictionary::check_for_each_extra(const foreach_extra_func_t& foreach_extra_func, bool invert_first) {
force_validate();
const auto& augm = aug;
foreach_func_t foreach_func = [&foreach_extra_func, &augm](Ref<vm::CellSlice> value_extra, td::ConstBitPtr key,
int key_len) {
auto extra = augm.extract_extra(value_extra.write());
return extra.not_null() && foreach_extra_func(std::move(value_extra), std::move(extra), key, key_len);
};
return DictionaryFixed::check_for_each(foreach_func, invert_first);
}
std::pair<Ref<CellSlice>, Ref<CellSlice>> AugmentedDictionary::dict_traverse_extra(
Ref<Cell> dict, td::BitPtr key_buffer, int n, const traverse_func_t& traverse_node) const {
int m = get_key_bits();
while (true) {
CHECK(dict.not_null());
LabelParser label{std::move(dict), n, 2};
label.extract_label_to(key_buffer);
key_buffer += label.l_bits;
n -= label.l_bits;
if (n <= 0) {
// reached a leaf, check it
assert(!n);
auto pair = decompose_value_extra(std::move(label.remainder));
if (pair.first.is_null()) {
throw VmError{Excno::dict_err, "invalid leaf value/extra in an augmented dictionary"};
}
int r = traverse_node(key_buffer - m, m, pair.second /* extra */, pair.first /* value */);
if (r < 0) {
throw CombineErrorValue{r};
} else if (r > 0) {
return pair;
} else {
return {};
}
}
// visit (traverse) fork
auto c1 = label.remainder.write().fetch_ref(), c2 = label.remainder.write().fetch_ref();
int r = traverse_node(key_buffer + n - m, m - n, std::move(label.remainder) /* extra */, {});
if (r < 0 || (r & 3) == 3) {
throw CombineErrorValue{r};
} else if (!(r & 3)) {
return {};
}
// r = 1 : visit only left, 2 = visit only right, 5 = visit right, then left, 6 = visit left, then right
++key_buffer;
--n;
bool sw = r & 1;
if (sw) {
std::swap(c1, c2);
}
if (r & 4) {
// have to visit both children in some order; do a recursive call to visit the first child
key_buffer[-1] = sw;
auto tmp = dict_traverse_extra(std::move(c1), key_buffer, n, traverse_node);
if (tmp.first.not_null()) {
return tmp;
}
}
// visit the remaining child
key_buffer[-1] = !sw;
dict = std::move(c2);
}
}
std::pair<Ref<CellSlice>, Ref<CellSlice>> AugmentedDictionary::traverse_extra(td::BitPtr key_buffer, int key_len,
const traverse_func_t& traverse_node) {
force_validate();
if (key_len != get_key_bits() || is_empty()) {
return {};
}
return dict_traverse_extra(get_root_cell(), key_buffer, key_len, traverse_node);
}
bool AugmentedDictionary::validate_check_extra(const AugmentedDictionary::foreach_extra_func_t& foreach_extra_func,
bool invert_first) {
const AugmentationData& augm = aug;
int key_len = get_key_bits();
return validate_check(
[&foreach_extra_func, &augm, key_len](Ref<CellSlice> value_extra, td::ConstBitPtr key, int value) {
auto extra = augm.extract_extra(value_extra.write());
return extra.not_null() && foreach_extra_func(std::move(value_extra), std::move(extra), key, key_len);
},
invert_first);
}
} // namespace vm