/*
    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 .
    Copyright 2017-2020 Telegram Systems LLP
*/
#include "vm/cells/DataCell.h"
#include "openssl/digest.hpp"
#include "td/utils/ScopeGuard.h"
#include "vm/cells/CellWithStorage.h"
namespace vm {
std::unique_ptr DataCell::create_empty_data_cell(Info info) {
  return detail::CellWithUniquePtrStorage::create(info.get_storage_size(), info);
}
DataCell::DataCell(Info info) : info_(std::move(info)) {
  get_thread_safe_counter().add(1);
}
DataCell::~DataCell() {
  get_thread_safe_counter().add(-1);
}
void DataCell::destroy_storage(char* storage) {
  auto* refs = info_.get_refs(storage);
  for (size_t i = 0; i < get_refs_cnt(); i++) {
    Ref| (refs[i], Ref | ::acquire_t{});  // call destructor
  }
}
td::Result [> DataCell::create(td::ConstBitPtr data, unsigned bits, td::Span][> refs,
                                           bool special) {
  std::array][, max_refs> copied_refs;
  CHECK(refs.size() <= copied_refs.size());
  for (size_t i = 0; i < refs.size(); i++) {
    copied_refs[i] = refs[i];
  }
  return create(std::move(data), bits, td::MutableSpan][>(copied_refs.data(), refs.size()), special);
}
DataCell::SpecialType DataCell::special_type() const {
  if (is_special()) {
    return static_cast(td::bitstring::bits_load_ulong(get_data(), 8));
  }
  return SpecialType::Ordinary;
}
td::Result][> DataCell::create(td::ConstBitPtr data, unsigned bits, td::MutableSpan][> refs,
                                           bool special) {
  for (auto& ref : refs) {
    if (ref.is_null()) {
      return td::Status::Error("Has null cell reference");
    }
  }
  SpecialType type = SpecialType::Ordinary;
  if (special) {
    if (bits < 8) {
      return td::Status::Error("Not enough data for a special cell");
    }
    type = static_cast(td::bitstring::bits_load_ulong(data, 8));
    if (type == SpecialType::Ordinary) {
      return td::Status::Error("Special cell has Ordinary type");
    }
  }
  LevelMask level_mask;
  td::uint32 virtualization = 0;
  switch (type) {
    case SpecialType::Ordinary: {
      for (auto& ref : refs) {
        level_mask = level_mask.apply_or(ref->get_level_mask());
        virtualization = td::max(virtualization, ref->get_virtualization());
      }
      break;
    }
    case SpecialType::PrunnedBranch: {
      if (refs.size() != 0) {
        return td::Status::Error("PrunnedBranch special cell has a cell reference");
      }
      if (bits < 16) {
        return td::Status::Error("Not enough data for a PrunnedBranch special cell");
      }
      level_mask = LevelMask((td::bitstring::bits_load_ulong(data + 8, 8)) & 0xff);
      auto level = level_mask.get_level();
      if (level > max_level || level == 0) {
        return td::Status::Error("Prunned Branch has an invalid level");
      }
      if (bits != (2 + level_mask.apply(level - 1).get_hashes_count() * (hash_bytes + depth_bytes)) * 8) {
        return td::Status::Error("Not enouch data for a PrunnedBranch special cell");
      }
      // depth will be checked later!
      break;
    }
    case SpecialType::Library: {
      if (bits != 8 + hash_bytes * 8) {
        return td::Status::Error("Not enouch data for a Library special cell");
      }
      break;
    }
    case SpecialType::MerkleProof: {
      if (bits != 8 + (hash_bytes + depth_bytes) * 8) {
        return td::Status::Error("Not enouch data for a MerkleProof special cell");
      }
      if (refs.size() != 1) {
        return td::Status::Error("Wrong references count for a MerkleProof special cell");
      }
      if (td::bitstring::bits_memcmp(data + 8, refs[0]->get_hash(0).as_bitslice().get_ptr(), hash_bits) != 0) {
        return td::Status::Error("Hash mismatch in a MerkleProof special cell");
      }
      if (td::bitstring::bits_load_ulong(data + 8 + hash_bits, depth_bytes * 8) != refs[0]->get_depth(0)) {
        return td::Status::Error("Depth mismatch in a MerkleProof special cell");
      }
      level_mask = refs[0]->get_level_mask().shift_right();
      virtualization = refs[0]->get_virtualization();
      break;
    }
    case SpecialType::MerkleUpdate: {
      if (bits != 8 + (hash_bytes + depth_bytes) * 8 * 2) {
        return td::Status::Error("Not enouch data for a MerkleUpdate special cell");
      }
      if (refs.size() != 2) {
        return td::Status::Error("Wrong references count for a MerkleUpdate special cell");
      }
      if (td::bitstring::bits_memcmp(data + 8, refs[0]->get_hash(0).as_bitslice().get_ptr(), hash_bits) != 0) {
        return td::Status::Error("First hash mismatch in a MerkleProof special cell");
      }
      if (td::bitstring::bits_memcmp(data + 8 + hash_bits, refs[1]->get_hash(0).as_bitslice().get_ptr(), hash_bits) !=
          0) {
        return td::Status::Error("Second hash mismatch in a MerkleProof special cell");
      }
      if (td::bitstring::bits_load_ulong(data + 8 + 2 * hash_bits, depth_bytes * 8) != refs[0]->get_depth(0)) {
        return td::Status::Error("First depth mismatch in a MerkleProof special cell");
      }
      if (td::bitstring::bits_load_ulong(data + 8 + 2 * hash_bits + depth_bytes * 8, depth_bytes * 8) !=
          refs[1]->get_depth(0)) {
        return td::Status::Error("Second depth mismatch in a MerkleProof special cell");
      }
      level_mask = refs[0]->get_level_mask().apply_or(refs[1]->get_level_mask()).shift_right();
      virtualization = td::max(refs[0]->get_virtualization(), refs[1]->get_virtualization());
      break;
    }
    default:
      return td::Status::Error("Unknown special cell type");
  }
  Info info;
  if (td::unlikely(bits > max_bits)) {
    return td::Status::Error("Too many bits");
  }
  if (td::unlikely(refs.size() > max_refs)) {
    return td::Status::Error("Too many cell references");
  }
  if (td::unlikely(virtualization > max_virtualization)) {
    return td::Status::Error("Too big virtualization");
  }
  CHECK(level_mask.get_level() <= max_level);
  auto hash_count = type == SpecialType::PrunnedBranch ? 1 : level_mask.get_hashes_count();
  DCHECK(hash_count <= max_level + 1);
  info.bits_ = bits;
  info.refs_count_ = refs.size() & 7;
  info.is_special_ = special;
  info.level_mask_ = level_mask.get_mask() & 7;
  info.hash_count_ = hash_count & 7;
  info.virtualization_ = virtualization & 7;
  auto data_cell = create_empty_data_cell(info);
  auto* storage = data_cell->get_storage();
  // init data
  auto* data_ptr = info.get_data(storage);
  td::BitPtr{data_ptr}.copy_from(data, bits);
  // prepare for serialization
  if (bits & 7) {
    int m = (0x80 >> (bits & 7));
    unsigned l = bits / 8;
    data_ptr[l] = static_cast((data_ptr[l] & -m) | m);
  }
  // init refs
  auto refs_ptr = info.get_refs(storage);
  for (size_t i = 0; i < refs.size(); i++) {
    refs_ptr[i] = refs[i].release();
  }
  // init hashes and depth
  auto* hashes_ptr = info.get_hashes(storage);
  auto* depth_ptr = info.get_depth(storage);
  // NB: be careful with special cells
  auto total_hash_count = level_mask.get_hashes_count();
  auto hash_i_offset = total_hash_count - hash_count;
  for (td::uint32 level_i = 0, hash_i = 0, level = level_mask.get_level(); level_i <= level; level_i++) {
    if (!level_mask.is_significant(level_i)) {
      continue;
    }
    SCOPE_EXIT {
      hash_i++;
    };
    if (hash_i < hash_i_offset) {
      continue;
    }
    unsigned char tmp[2];
    tmp[0] = info.d1(level_mask.apply(level_i));
    tmp[1] = info.d2();
    static TD_THREAD_LOCAL digest::SHA256* hasher;
    td::init_thread_local(hasher);
    hasher->reset();
    hasher->feed(td::Slice(tmp, 2));
    if (hash_i == hash_i_offset) {
      DCHECK(level_i == 0 || type == SpecialType::PrunnedBranch);
      hasher->feed(td::Slice(data_ptr, (bits + 7) >> 3));
    } else {
      DCHECK(level_i != 0 && type != SpecialType::PrunnedBranch);
      hasher->feed(hashes_ptr[hash_i - hash_i_offset - 1].as_slice());
    }
    auto dest_i = hash_i - hash_i_offset;
    // calc depth
    td::uint16 depth = 0;
    for (int i = 0; i < info.refs_count_; i++) {
      td::uint16 child_depth = 0;
      if (type == SpecialType::MerkleProof || type == SpecialType::MerkleUpdate) {
        child_depth = refs_ptr[i]->get_depth(level_i + 1);
      } else {
        child_depth = refs_ptr[i]->get_depth(level_i);
      }
      // add depth into hash
      td::uint8 child_depth_buf[depth_bytes];
      store_depth(child_depth_buf, child_depth);
      hasher->feed(td::Slice(child_depth_buf, depth_bytes));
      depth = std::max(depth, child_depth);
    }
    if (info.refs_count_ != 0) {
      if (depth >= max_depth) {
        return td::Status::Error("Depth is too big");
      }
      depth++;
    }
    depth_ptr[dest_i] = depth;
    // children hash
    for (int i = 0; i < info.refs_count_; i++) {
      if (type == SpecialType::MerkleProof || type == SpecialType::MerkleUpdate) {
        hasher->feed(refs_ptr[i]->get_hash(level_i + 1).as_slice());
      } else {
        hasher->feed(refs_ptr[i]->get_hash(level_i).as_slice());
      }
    }
    auto extracted_size = hasher->extract(hashes_ptr[dest_i].as_slice());
    DCHECK(extracted_size == hash_bytes);
  }
  return Ref(data_cell.release(), Ref::acquire_t{});
}
const DataCell::Hash DataCell::do_get_hash(td::uint32 level) const {
  auto hash_i = get_level_mask().apply(level).get_hash_i();
  if (special_type() == SpecialType::PrunnedBranch) {
    auto this_hash_i = get_level_mask().get_hash_i();
    if (hash_i != this_hash_i) {
      return reinterpret_cast(info_.get_data(get_storage()) + 2)[hash_i];
    }
    hash_i = 0;
  }
  return info_.get_hashes(get_storage())[hash_i];
}
td::uint16 DataCell::do_get_depth(td::uint32 level) const {
  auto hash_i = get_level_mask().apply(level).get_hash_i();
  if (special_type() == SpecialType::PrunnedBranch) {
    auto this_hash_i = get_level_mask().get_hash_i();
    if (hash_i != this_hash_i) {
      return load_depth(info_.get_data(get_storage()) + 2 + hash_bytes * this_hash_i + hash_i * depth_bytes);
    }
    hash_i = 0;
  }
  return info_.get_depth(get_storage())[hash_i];
}
int DataCell::serialize(unsigned char* buff, int buff_size, bool with_hashes) const {
  int len = get_serialized_size(with_hashes);
  if (len > buff_size) {
    return 0;
  }
  buff[0] = static_cast(info_.d1() | (with_hashes * 16));
  buff[1] = info_.d2();
  int hs = 0;
  if (with_hashes) {
    hs = (get_level_mask().get_hashes_count()) * (hash_bytes + depth_bytes);
    assert(len >= 2 + hs);
    std::memset(buff + 2, 0, hs);
    auto dest = td::MutableSlice(buff + 2, hs);
    auto level = get_level();
    // TODO: optimize for prunned brandh
    for (unsigned i = 0; i <= level; i++) {
      if (!get_level_mask().is_significant(i)) {
        continue;
      }
      dest.copy_from(get_hash(i).as_slice());
      dest.remove_prefix(hash_bytes);
    }
    for (unsigned i = 0; i <= level; i++) {
      if (!get_level_mask().is_significant(i)) {
        continue;
      }
      store_depth(dest.ubegin(), get_depth(i));
      dest.remove_prefix(depth_bytes);
    }
    // buff[2] = 0;  // for testing hash verification in deserialization
    buff += hs;
    len -= hs;
  }
  std::memcpy(buff + 2, get_data(), len - 2);
  return len + hs;
}
std::string DataCell::serialize() const {
  unsigned char buff[max_serialized_bytes];
  int len = serialize(buff, sizeof(buff));
  return std::string(buff, buff + len);
}
std::string DataCell::to_hex() const {
  unsigned char buff[max_serialized_bytes];
  int len = serialize(buff, sizeof(buff));
  char hex_buff[max_serialized_bytes * 2 + 1];
  for (int i = 0; i < len; i++) {
    sprintf(hex_buff + 2 * i, "%02x", buff[i]);
  }
  return hex_buff;
}
std::ostream& operator<<(std::ostream& os, const DataCell& c) {
  return os << c.to_hex();
}
}  // namespace vm
] |  |