// Copyright 2016-2019 Envoy Project Authors
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <unordered_set>

#include "include/proxy-wasm/context.h"
#include "include/proxy-wasm/wasm.h"
#include "src/hash.h"
#include "src/shared_data.h"
#include "src/shared_queue.h"

#define CHECK_FAIL(_stream_type, _stream_type2, _return_open, _return_closed)                      \
  if (isFailed()) {                                                                                \
    if (plugin_->fail_open_) {                                                                     \
      return _return_open;                                                                         \
    }                                                                                              \
    if (!stream_failed_) {                                                                         \
      failStream(_stream_type);                                                                    \
      failStream(_stream_type2);                                                                   \
      stream_failed_ = true;                                                                       \
    }                                                                                              \
    return _return_closed;                                                                         \
  }

#define CHECK_FAIL_HTTP(_return_open, _return_closed)                                              \
  CHECK_FAIL(WasmStreamType::Request, WasmStreamType::Response, _return_open, _return_closed)
#define CHECK_FAIL_NET(_return_open, _return_closed)                                               \
  CHECK_FAIL(WasmStreamType::Downstream, WasmStreamType::Upstream, _return_open, _return_closed)

namespace proxy_wasm {

DeferAfterCallActions::~DeferAfterCallActions() {
  wasm_->stopNextIteration(false);
  wasm_->doAfterVmCallActions();
}

WasmResult BufferBase::copyTo(WasmBase *wasm, size_t start, size_t length, uint64_t ptr_ptr,
                              uint64_t size_ptr) const {
  if (owned_data_) {
    std::string_view s(owned_data_.get() + start, length);
    if (!wasm->copyToPointerSize(s, ptr_ptr, size_ptr)) {
      return WasmResult::InvalidMemoryAccess;
    }
    return WasmResult::Ok;
  }
  std::string_view s = data_.substr(start, length);
  if (!wasm->copyToPointerSize(s, ptr_ptr, size_ptr)) {
    return WasmResult::InvalidMemoryAccess;
  }
  return WasmResult::Ok;
}

// Test support.
uint32_t resolveQueueForTest(std::string_view vm_id, std::string_view queue_name) {
  return getGlobalSharedQueue().resolveQueue(vm_id, queue_name);
}

std::string PluginBase::makeLogPrefix() const {
  std::string prefix;
  if (!name_.empty()) {
    prefix = prefix + " " + name_;
  }
  if (!root_id_.empty()) {
    prefix = prefix + " " + std::string(root_id_);
  }
  if (!vm_id_.empty()) {
    prefix = prefix + " " + std::string(vm_id_);
  }
  return prefix;
}

std::string PluginBase::makePluginKey(std::string_view root_id,
                                      std::string_view plugin_configuration, std::string_view key) {
  return Sha256String({root_id, "||", plugin_configuration, "||", key});
}

ContextBase::ContextBase() : parent_context_(this) {}

ContextBase::ContextBase(WasmBase *wasm) : wasm_(wasm), parent_context_(this) {
  wasm_->contexts_[id_] = this;
}

ContextBase::ContextBase(WasmBase *wasm, const std::shared_ptr<PluginBase> &plugin)
    : wasm_(wasm), id_(wasm->allocContextId()), parent_context_(this), root_id_(plugin->root_id_),
      root_log_prefix_(makeRootLogPrefix(plugin->vm_id_)), plugin_(plugin) {
  wasm_->contexts_[id_] = this;
}

// NB: wasm can be nullptr if it failed to be created successfully.
ContextBase::ContextBase(WasmBase *wasm, uint32_t parent_context_id,
                         const std::shared_ptr<PluginHandleBase> &plugin_handle)
    : wasm_(wasm), id_(wasm != nullptr ? wasm->allocContextId() : 0),
      parent_context_id_(parent_context_id), plugin_(plugin_handle->plugin()),
      plugin_handle_(plugin_handle) {
  if (wasm_ != nullptr) {
    wasm_->contexts_[id_] = this;
    parent_context_ = wasm_->contexts_[parent_context_id_];
  }
}

WasmVm *ContextBase::wasmVm() const { return wasm_->wasm_vm(); }

bool ContextBase::isFailed() { return (wasm_ == nullptr || wasm_->isFailed()); }

std::string ContextBase::makeRootLogPrefix(std::string_view vm_id) const {
  std::string prefix;
  if (!root_id_.empty()) {
    prefix = prefix + " " + std::string(root_id_);
  }
  if (!vm_id.empty()) {
    prefix = prefix + " " + std::string(vm_id);
  }
  return prefix;
}

//
// Calls into the WASM code.
//
bool ContextBase::onStart(std::shared_ptr<PluginBase> plugin) {
  DeferAfterCallActions actions(this);
  bool result = true;
  if (wasm_->on_context_create_) {
    temp_plugin_ = plugin;
    wasm_->on_context_create_(this, id_, 0);
    in_vm_context_created_ = true;
    temp_plugin_.reset();
  }
  if (wasm_->on_vm_start_) {
    // Do not set plugin_ as the on_vm_start handler should be independent of the plugin since the
    // specific plugin which ends up calling it is not necessarily known by the Wasm module.
    result =
        wasm_->on_vm_start_(this, id_, static_cast<uint32_t>(wasm()->vm_configuration().size()))
            .u64_ != 0;
  }
  return result;
}

bool ContextBase::onConfigure(std::shared_ptr<PluginBase> plugin) {
  if (isFailed()) {
    return true;
  }

  // on_context_create is yet to be executed for all the root contexts except the first one
  if (!in_vm_context_created_ && wasm_->on_context_create_) {
    DeferAfterCallActions actions(this);
    wasm_->on_context_create_(this, id_, 0);
  }

  // NB: If no on_context_create function is registered the in-VM SDK is responsible for
  // managing any required in-VM state.
  in_vm_context_created_ = true;

  if (!wasm_->on_configure_) {
    return true;
  }

  DeferAfterCallActions actions(this);
  temp_plugin_ = plugin;
  auto result =
      wasm_->on_configure_(this, id_, static_cast<uint32_t>(plugin->plugin_configuration_.size()))
          .u64_ != 0;
  temp_plugin_.reset();
  return result;
}

void ContextBase::onCreate() {
  if (!isFailed() && !in_vm_context_created_ && wasm_->on_context_create_) {
    DeferAfterCallActions actions(this);
    wasm_->on_context_create_(this, id_, parent_context_ != nullptr ? parent_context()->id() : 0);
  }
  // NB: If no on_context_create function is registered the in-VM SDK is responsible for
  // managing any required in-VM state.
  in_vm_context_created_ = true;
}

// Shared Data
WasmResult ContextBase::getSharedData(std::string_view key,
                                      std::pair<std::string, uint32_t> *data) {
  return getGlobalSharedData().get(wasm_->vm_id(), key, data);
}

WasmResult ContextBase::setSharedData(std::string_view key, std::string_view value, uint32_t cas) {
  return getGlobalSharedData().set(wasm_->vm_id(), key, value, cas);
}

WasmResult ContextBase::getSharedDataKeys(std::vector<std::string> *result) {
  return getGlobalSharedData().keys(wasm_->vm_id(), result);
}

WasmResult ContextBase::removeSharedDataKey(std::string_view key, uint32_t cas,
                                            std::pair<std::string, uint32_t> *result) {
  return getGlobalSharedData().remove(wasm_->vm_id(), key, cas, result);
}

// Shared Queue

WasmResult ContextBase::registerSharedQueue(std::string_view queue_name,
                                            SharedQueueDequeueToken *token_ptr) {
  // Get the id of the root context if this is a stream context because onQueueReady is on the
  // root.
  *token_ptr = getGlobalSharedQueue().registerQueue(wasm_->vm_id(), queue_name,
                                                    isRootContext() ? id_ : parent_context_id_,
                                                    wasm_->callOnThreadFunction(), wasm_->vm_key());
  return WasmResult::Ok;
}

WasmResult ContextBase::lookupSharedQueue(std::string_view vm_id, std::string_view queue_name,
                                          SharedQueueDequeueToken *token_ptr) {
  SharedQueueDequeueToken token =
      getGlobalSharedQueue().resolveQueue(vm_id.empty() ? wasm_->vm_id() : vm_id, queue_name);
  if (isFailed() || token == 0U) {
    return WasmResult::NotFound;
  }
  *token_ptr = token;
  return WasmResult::Ok;
}

WasmResult ContextBase::dequeueSharedQueue(uint32_t token, std::string *data) {
  return getGlobalSharedQueue().dequeue(token, data);
}

WasmResult ContextBase::enqueueSharedQueue(uint32_t token, std::string_view value) {
  return getGlobalSharedQueue().enqueue(token, value);
}
void ContextBase::destroy() {
  if (destroyed_) {
    return;
  }
  destroyed_ = true;
  onDone();
}

void ContextBase::onTick(uint32_t /*token*/) {
  if (!isFailed() && wasm_->on_tick_) {
    DeferAfterCallActions actions(this);
    wasm_->on_tick_(this, id_);
  }
}

void ContextBase::onForeignFunction(uint32_t foreign_function_id, uint32_t data_size) {
  if (wasm_->on_foreign_function_) {
    DeferAfterCallActions actions(this);
    wasm_->on_foreign_function_(this, id_, foreign_function_id, data_size);
  }
}

FilterStatus ContextBase::onNetworkNewConnection() {
  CHECK_FAIL_NET(FilterStatus::Continue, FilterStatus::StopIteration);
  if (!wasm_->on_new_connection_) {
    return FilterStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  const auto result = wasm_->on_new_connection_(this, id_);
  CHECK_FAIL_NET(FilterStatus::Continue, FilterStatus::StopIteration);
  return result == 0 ? FilterStatus::Continue : FilterStatus::StopIteration;
}

FilterStatus ContextBase::onDownstreamData(uint32_t data_length, bool end_of_stream) {
  CHECK_FAIL_NET(FilterStatus::Continue, FilterStatus::StopIteration);
  if (!wasm_->on_downstream_data_) {
    return FilterStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  auto result = wasm_->on_downstream_data_(this, id_, static_cast<uint32_t>(data_length),
                                           static_cast<uint32_t>(end_of_stream));
  // TODO(PiotrSikora): pull Proxy-WASM's FilterStatus values.
  CHECK_FAIL_NET(FilterStatus::Continue, FilterStatus::StopIteration);
  return result == 0 ? FilterStatus::Continue : FilterStatus::StopIteration;
}

FilterStatus ContextBase::onUpstreamData(uint32_t data_length, bool end_of_stream) {
  CHECK_FAIL_NET(FilterStatus::Continue, FilterStatus::StopIteration);
  if (!wasm_->on_upstream_data_) {
    return FilterStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  auto result = wasm_->on_upstream_data_(this, id_, static_cast<uint32_t>(data_length),
                                         static_cast<uint32_t>(end_of_stream));
  // TODO(PiotrSikora): pull Proxy-WASM's FilterStatus values.
  CHECK_FAIL_NET(FilterStatus::Continue, FilterStatus::StopIteration);
  return result == 0 ? FilterStatus::Continue : FilterStatus::StopIteration;
}

void ContextBase::onDownstreamConnectionClose(CloseType close_type) {
  if (!isFailed() && wasm_->on_downstream_connection_close_) {
    DeferAfterCallActions actions(this);
    wasm_->on_downstream_connection_close_(this, id_, static_cast<uint32_t>(close_type));
  }
}

void ContextBase::onUpstreamConnectionClose(CloseType close_type) {
  if (!isFailed() && wasm_->on_upstream_connection_close_) {
    DeferAfterCallActions actions(this);
    wasm_->on_upstream_connection_close_(this, id_, static_cast<uint32_t>(close_type));
  }
}

// Empty headers/trailers have zero size.
template <typename P> static uint32_t headerSize(const P &p) { return p ? p->size() : 0; }

FilterHeadersStatus ContextBase::onRequestHeaders(uint32_t headers, bool end_of_stream) {
  CHECK_FAIL_HTTP(FilterHeadersStatus::Continue, FilterHeadersStatus::StopAllIterationAndWatermark);
  if (!wasm_->on_request_headers_abi_01_ && !wasm_->on_request_headers_abi_02_) {
    return FilterHeadersStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  const auto result = wasm_->on_request_headers_abi_01_
                          ? wasm_->on_request_headers_abi_01_(this, id_, headers)
                          : wasm_->on_request_headers_abi_02_(this, id_, headers,
                                                              static_cast<uint32_t>(end_of_stream));
  CHECK_FAIL_HTTP(FilterHeadersStatus::Continue, FilterHeadersStatus::StopAllIterationAndWatermark);
  return convertVmCallResultToFilterHeadersStatus(result);
}

FilterDataStatus ContextBase::onRequestBody(uint32_t body_length, bool end_of_stream) {
  CHECK_FAIL_HTTP(FilterDataStatus::Continue, FilterDataStatus::StopIterationNoBuffer);
  if (!wasm_->on_request_body_) {
    return FilterDataStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  const auto result =
      wasm_->on_request_body_(this, id_, body_length, static_cast<uint32_t>(end_of_stream));
  CHECK_FAIL_HTTP(FilterDataStatus::Continue, FilterDataStatus::StopIterationNoBuffer);
  return convertVmCallResultToFilterDataStatus(result);
}

FilterTrailersStatus ContextBase::onRequestTrailers(uint32_t trailers) {
  CHECK_FAIL_HTTP(FilterTrailersStatus::Continue, FilterTrailersStatus::StopIteration);
  if (!wasm_->on_request_trailers_) {
    return FilterTrailersStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  const auto result = wasm_->on_request_trailers_(this, id_, trailers);
  CHECK_FAIL_HTTP(FilterTrailersStatus::Continue, FilterTrailersStatus::StopIteration);
  return convertVmCallResultToFilterTrailersStatus(result);
}

FilterMetadataStatus ContextBase::onRequestMetadata(uint32_t elements) {
  CHECK_FAIL_HTTP(FilterMetadataStatus::Continue, FilterMetadataStatus::Continue);
  if (!wasm_->on_request_metadata_) {
    return FilterMetadataStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  const auto result = wasm_->on_request_metadata_(this, id_, elements);
  CHECK_FAIL_HTTP(FilterMetadataStatus::Continue, FilterMetadataStatus::Continue);
  return convertVmCallResultToFilterMetadataStatus(result);
}

FilterHeadersStatus ContextBase::onResponseHeaders(uint32_t headers, bool end_of_stream) {
  CHECK_FAIL_HTTP(FilterHeadersStatus::Continue, FilterHeadersStatus::StopAllIterationAndWatermark);
  if (!wasm_->on_response_headers_abi_01_ && !wasm_->on_response_headers_abi_02_) {
    return FilterHeadersStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  const auto result = wasm_->on_response_headers_abi_01_
                          ? wasm_->on_response_headers_abi_01_(this, id_, headers)
                          : wasm_->on_response_headers_abi_02_(
                                this, id_, headers, static_cast<uint32_t>(end_of_stream));
  CHECK_FAIL_HTTP(FilterHeadersStatus::Continue, FilterHeadersStatus::StopAllIterationAndWatermark);
  return convertVmCallResultToFilterHeadersStatus(result);
}

FilterDataStatus ContextBase::onResponseBody(uint32_t body_length, bool end_of_stream) {
  CHECK_FAIL_HTTP(FilterDataStatus::Continue, FilterDataStatus::StopIterationNoBuffer);
  if (!wasm_->on_response_body_) {
    return FilterDataStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  const auto result =
      wasm_->on_response_body_(this, id_, body_length, static_cast<uint32_t>(end_of_stream));
  CHECK_FAIL_HTTP(FilterDataStatus::Continue, FilterDataStatus::StopIterationNoBuffer);
  return convertVmCallResultToFilterDataStatus(result);
}

FilterTrailersStatus ContextBase::onResponseTrailers(uint32_t trailers) {
  CHECK_FAIL_HTTP(FilterTrailersStatus::Continue, FilterTrailersStatus::StopIteration);
  if (!wasm_->on_response_trailers_) {
    return FilterTrailersStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  const auto result = wasm_->on_response_trailers_(this, id_, trailers);
  CHECK_FAIL_HTTP(FilterTrailersStatus::Continue, FilterTrailersStatus::StopIteration);
  return convertVmCallResultToFilterTrailersStatus(result);
}

FilterMetadataStatus ContextBase::onResponseMetadata(uint32_t elements) {
  CHECK_FAIL_HTTP(FilterMetadataStatus::Continue, FilterMetadataStatus::Continue);
  if (!wasm_->on_response_metadata_) {
    return FilterMetadataStatus::Continue;
  }
  DeferAfterCallActions actions(this);
  const auto result = wasm_->on_response_metadata_(this, id_, elements);
  CHECK_FAIL_HTTP(FilterMetadataStatus::Continue, FilterMetadataStatus::Continue);
  return convertVmCallResultToFilterMetadataStatus(result);
}

void ContextBase::onHttpCallResponse(uint32_t token, uint32_t headers, uint32_t body_size,
                                     uint32_t trailers) {
  if (isFailed() || !wasm_->on_http_call_response_) {
    return;
  }
  DeferAfterCallActions actions(this);
  wasm_->on_http_call_response_(this, id_, token, headers, body_size, trailers);
}

void ContextBase::onQueueReady(uint32_t token) {
  if (!isFailed() && wasm_->on_queue_ready_) {
    DeferAfterCallActions actions(this);
    wasm_->on_queue_ready_(this, id_, token);
  }
}

void ContextBase::onGrpcReceiveInitialMetadata(uint32_t token, uint32_t elements) {
  if (isFailed() || !wasm_->on_grpc_receive_initial_metadata_) {
    return;
  }
  DeferAfterCallActions actions(this);
  wasm_->on_grpc_receive_initial_metadata_(this, id_, token, elements);
}

void ContextBase::onGrpcReceiveTrailingMetadata(uint32_t token, uint32_t trailers) {
  if (isFailed() || !wasm_->on_grpc_receive_trailing_metadata_) {
    return;
  }
  DeferAfterCallActions actions(this);
  wasm_->on_grpc_receive_trailing_metadata_(this, id_, token, trailers);
}

void ContextBase::onGrpcReceive(uint32_t token, uint32_t response_size) {
  if (isFailed() || !wasm_->on_grpc_receive_) {
    return;
  }
  DeferAfterCallActions actions(this);
  wasm_->on_grpc_receive_(this, id_, token, response_size);
}

void ContextBase::onGrpcClose(uint32_t token, uint32_t status_code) {
  if (isFailed() || !wasm_->on_grpc_close_) {
    return;
  }
  DeferAfterCallActions actions(this);
  wasm_->on_grpc_close_(this, id_, token, status_code);
}

bool ContextBase::onDone() {
  if (!isFailed() && wasm_->on_done_) {
    DeferAfterCallActions actions(this);
    return wasm_->on_done_(this, id_).u64_ != 0;
  }
  return true;
}

void ContextBase::onLog() {
  if (!isFailed() && wasm_->on_log_) {
    DeferAfterCallActions actions(this);
    wasm_->on_log_(this, id_);
  }
}

void ContextBase::onDelete() {
  if (in_vm_context_created_ && !isFailed() && wasm_->on_delete_) {
    DeferAfterCallActions actions(this);
    wasm_->on_delete_(this, id_);
  }
}

WasmResult ContextBase::setTimerPeriod(std::chrono::milliseconds period,
                                       uint32_t *timer_token_ptr) {
  wasm()->setTimerPeriod(root_context()->id(), period);
  *timer_token_ptr = 0;
  return WasmResult::Ok;
}

FilterHeadersStatus ContextBase::convertVmCallResultToFilterHeadersStatus(uint64_t result) {
  if (wasm()->isNextIterationStopped() ||
      result > static_cast<uint64_t>(FilterHeadersStatus::StopAllIterationAndWatermark)) {
    return FilterHeadersStatus::StopAllIterationAndWatermark;
  }
  if (result == static_cast<uint64_t>(FilterHeadersStatus::StopIteration)) {
    // Always convert StopIteration (pause processing headers, but continue processing body)
    // to StopAllIterationAndWatermark (pause all processing), since the former breaks all
    // assumptions about HTTP processing.
    return FilterHeadersStatus::StopAllIterationAndWatermark;
  }
  return static_cast<FilterHeadersStatus>(result);
}

FilterDataStatus ContextBase::convertVmCallResultToFilterDataStatus(uint64_t result) {
  if (wasm()->isNextIterationStopped() ||
      result > static_cast<uint64_t>(FilterDataStatus::StopIterationNoBuffer)) {
    return FilterDataStatus::StopIterationNoBuffer;
  }
  return static_cast<FilterDataStatus>(result);
}

FilterTrailersStatus ContextBase::convertVmCallResultToFilterTrailersStatus(uint64_t result) {
  if (wasm()->isNextIterationStopped() ||
      result > static_cast<uint64_t>(FilterTrailersStatus::StopIteration)) {
    return FilterTrailersStatus::StopIteration;
  }
  return static_cast<FilterTrailersStatus>(result);
}

FilterMetadataStatus ContextBase::convertVmCallResultToFilterMetadataStatus(uint64_t result) {
  if (static_cast<FilterMetadataStatus>(result) == FilterMetadataStatus::Continue) {
    return FilterMetadataStatus::Continue;
  }
  return FilterMetadataStatus::Continue; // This is currently the only return code.
}

ContextBase::~ContextBase() {
  // Do not remove vm context which has the same lifetime as wasm_.
  if (id_ != 0U) {
    wasm_->contexts_.erase(id_);
  }
}

} // namespace proxy_wasm
