/*
    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 "adnl-ext-server.hpp"
#include "keys/encryptor.h"
#include "utils.hpp"
namespace ton {
namespace adnl {
td::Status AdnlInboundConnection::process_packet(td::BufferSlice data) {
  TRY_RESULT(f, fetch_tl_object(std::move(data), true));
  auto P =
      td::PromiseCreator::lambda([SelfId = actor_id(this), query_id = f->query_id_](td::Result R) {
        if (R.is_error()) {
          auto S = R.move_as_error();
          LOG(WARNING) << "failed ext query: " << S;
        } else {
          auto B = create_tl_object(query_id, R.move_as_ok());
          td::actor::send_closure(SelfId, &AdnlInboundConnection::send, serialize_tl_object(B, true));
        }
      });
  td::actor::send_closure(peer_table_, &AdnlPeerTable::deliver_query, remote_id_, local_id_, std::move(f->query_),
                          std::move(P));
  return td::Status::OK();
}
td::Status AdnlInboundConnection::process_init_packet(td::BufferSlice data) {
  if (data.size() < 32) {
    return td::Status::Error(ErrorCode::protoviolation, "too small init packet");
  }
  local_id_ = AdnlNodeIdShort{data.as_slice().truncate(32)};
  data.confirm_read(32);
  auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) {
    td::actor::send_closure(SelfId, &AdnlInboundConnection::inited_crypto, std::move(R));
  });
  td::actor::send_closure(ext_server_, &AdnlExtServerImpl::decrypt_init_packet, local_id_, std::move(data),
                          std::move(P));
  stop_read();
  return td::Status::OK();
}
void AdnlInboundConnection::inited_crypto(td::Result R) {
  if (R.is_error()) {
    LOG(ERROR) << "failed to init crypto: " << R.move_as_error();
    stop();
    return;
  }
  auto S = init_crypto(R.move_as_ok().as_slice());
  if (S.is_error()) {
    LOG(ERROR) << "failed to init crypto (2): " << R.move_as_error();
    stop();
    return;
  }
  send(td::BufferSlice());
  resume_read();
  notify();
}
td::Status AdnlInboundConnection::process_custom_packet(td::BufferSlice &data, bool &processed) {
  if (data.size() == 12) {
    auto F = fetch_tl_object(data.clone(), true);
    if (F.is_ok()) {
      auto f = F.move_as_ok();
      auto obj = create_tl_object(f->random_id_);
      send(serialize_tl_object(obj, true));
      processed = true;
      return td::Status::OK();
    }
  }
  if (1) {
    auto F = fetch_tl_object(data.clone(), true);
    if (F.is_ok()) {
      if (nonce_.size() > 0 || !remote_id_.is_zero()) {
        return td::Status::Error(ErrorCode::protoviolation, "duplicate authenticate");
      }
      auto f = F.move_as_ok();
      nonce_ = td::SecureString{f->nonce_.size() + 256};
      nonce_.as_mutable_slice().truncate(f->nonce_.size()).copy_from(f->nonce_.as_slice());
      td::Random::secure_bytes(nonce_.as_mutable_slice().remove_prefix(f->nonce_.size()));
      auto obj = create_tl_object(
          td::BufferSlice{nonce_.as_slice().remove_prefix(f->nonce_.size())});
      send(serialize_tl_object(obj, true));
      processed = true;
      return td::Status::OK();
    }
  }
  if (nonce_.size() != 0) {
    auto F = fetch_tl_object(data.clone(), true);
    if (F.is_ok()) {
      auto f = F.move_as_ok();
      if (nonce_.size() == 0 || !remote_id_.is_zero()) {
        return td::Status::Error(ErrorCode::protoviolation, "duplicate authentificate");
      }
      auto pub_key = PublicKey{f->key_};
      TRY_RESULT(enc, pub_key.create_encryptor());
      TRY_STATUS(enc->check_signature(nonce_.as_slice(), f->signature_.as_slice()));
      remote_id_ = AdnlNodeIdShort{pub_key.compute_short_id()};
      nonce_.clear();
      processed = true;
      return td::Status::OK();
    }
  }
  return td::Status::OK();
}
void AdnlExtServerImpl::add_tcp_port(td::uint16 port) {
  auto it = listeners_.find(port);
  if (it != listeners_.end()) {
    return;
  }
  class Callback : public td::TcpListener::Callback {
   private:
    td::actor::ActorId id_;
   public:
    Callback(td::actor::ActorId id) : id_(id) {
    }
    void accept(td::SocketFd fd) override {
      td::actor::send_closure(id_, &AdnlExtServerImpl::accepted, std::move(fd));
    }
  };
  auto act = td::actor::create_actor(
      td::actor::ActorOptions().with_name("listener").with_poll(), port, std::make_unique(actor_id(this)));
  listeners_.emplace(port, std::move(act));
}
void AdnlExtServerImpl::add_local_id(AdnlNodeIdShort id) {
  local_ids_.insert(id);
}
void AdnlExtServerImpl::accepted(td::SocketFd fd) {
  td::actor::create_actor(td::actor::ActorOptions().with_name("inconn").with_poll(),
                                                 std::move(fd), peer_table_, actor_id(this))
      .release();
}
void AdnlExtServerImpl::decrypt_init_packet(AdnlNodeIdShort dst, td::BufferSlice data,
                                            td::Promise promise) {
  auto it = local_ids_.find(dst);
  if (it != local_ids_.end()) {
    td::actor::send_closure(peer_table_, &AdnlPeerTable::decrypt_message, dst, std::move(data), std::move(promise));
  } else {
    promise.set_error(td::Status::Error());
  }
}
td::actor::ActorOwn AdnlExtServerCreator::create(td::actor::ActorId adnl,
                                                                std::vector ids,
                                                                std::vector ports) {
  return td::actor::create_actor("extserver", adnl, std::move(ids), std::move(ports));
}
}  // namespace adnl
}  // namespace ton