/** @file

  Plugin Traffic Dump captures traffic on a per session basis. A sampling ratio can be set via plugin.config or traffic_ctl to dump
  one out of n sessions. The dump file schema can be found
  https://github.com/apache/trafficserver/tree/master/tests/tools/lib/replay_schema.json

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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 <getopt.h>

#include "global_variables.h"
#include "session_data.h"
#include "transaction_data.h"

namespace traffic_dump
{
DbgCtl dbg_ctl{debug_tag};

/// Handle LIFECYCLE_MSG from traffic_ctl.
static int
global_message_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void *edata)
{
  switch (event) {
  case TS_EVENT_LIFECYCLE_MSG: {
    TSPluginMsg                      *msg = static_cast<TSPluginMsg *>(edata);
    static constexpr std::string_view PLUGIN_PREFIX("traffic_dump.");

    std::string_view tag(msg->tag, strlen(msg->tag));
    if (tag.substr(0, PLUGIN_PREFIX.size()) == PLUGIN_PREFIX) {
      tag.remove_prefix(PLUGIN_PREFIX.size());
      if (tag == "sample" && msg->data_size) {
        const auto new_sample_size = static_cast<int64_t>(strtol(static_cast<char const *>(msg->data), nullptr, 0));
        Dbg(dbg_ctl, "TS_EVENT_LIFECYCLE_MSG: Received Msg to change sample size to %" PRId64 "bytes", new_sample_size);
        SessionData::set_sample_pool_size(new_sample_size);
      } else if (tag == "reset") {
        Dbg(dbg_ctl, "TS_EVENT_LIFECYCLE_MSG: Received Msg to reset disk usage counter");
        SessionData::reset_disk_usage();
      } else if (tag == "unlimit") {
        Dbg(dbg_ctl, "TS_EVENT_LIFECYCLE_MSG: Received Msg to disable disk usage limit enforcement");
        SessionData::disable_disk_limit_enforcement();
      } else if (tag == "limit" && msg->data_size) {
        const auto new_max_disk_usage = static_cast<int64_t>(strtol(static_cast<char const *>(msg->data), nullptr, 0));
        Dbg(dbg_ctl, "TS_EVENT_LIFECYCLE_MSG: Received Msg to change max disk usage to %" PRId64 "bytes", new_max_disk_usage);
        SessionData::set_max_disk_usage(new_max_disk_usage);
      }
    }
    return TS_SUCCESS;
  }
  default:
    Dbg(dbg_ctl, "session_aio_handler(): unhandled events %d", event);
    return TS_ERROR;
  }
}

} // namespace traffic_dump

void
TSPluginInit(int argc, char const *argv[])
{
  Dbg(traffic_dump::dbg_ctl, "initializing plugin");
  TSPluginRegistrationInfo info;

  info.plugin_name   = "traffic_dump";
  info.vendor_name   = "Apache Software Foundation";
  info.support_email = "dev@trafficserver.apache.org";

  if (TS_SUCCESS != TSPluginRegister(&info)) {
    TSError("[%s] Unable to initialize plugin (disabled). Failed to register plugin.", traffic_dump::debug_tag);
    return;
  }

  bool                             dump_body                       = false;
  bool                             sensitive_fields_were_specified = false;
  traffic_dump::sensitive_fields_t user_specified_fields;
  swoc::file::path                 log_dir{traffic_dump::SessionData::default_log_directory};
  int64_t                          sample_pool_size   = traffic_dump::SessionData::default_sample_pool_size;
  int64_t                          max_disk_usage     = traffic_dump::SessionData::default_max_disk_usage;
  bool                             enforce_disk_limit = traffic_dump::SessionData::default_enforce_disk_limit;
  std::string                      sni_filter;
  std::string                      client_ip_filter;

  /// Commandline options
  static const struct option longopts[] = {
    {"dump_body",        no_argument,       nullptr, 'b'},
    {"logdir",           required_argument, nullptr, 'l'},
    {"sample",           required_argument, nullptr, 's'},
    {"limit",            required_argument, nullptr, 'm'},
    {"sensitive-fields", required_argument, nullptr, 'f'},
    {"sni-filter",       required_argument, nullptr, 'n'},
    {"client_ipv4",      required_argument, nullptr, '4'},
    {"client_ipv6",      required_argument, nullptr, '6'},
    {nullptr,            no_argument,       nullptr, 0  }
  };
  int opt = 0;
  while (opt >= 0) {
    opt = getopt_long(argc, const_cast<char *const *>(argv), "bf:l:s:m:n:4:6", longopts, nullptr);
    switch (opt) {
    case 'b': {
      dump_body = true;
      break;
    }
    case 'f': {
      // --sensitive-fields takes a comma-separated list of HTTP fields that
      // are sensitive.  The field values for these fields will be replaced
      // with generic traffic_dump generated data.
      //
      // If this option is not used, then the default values in
      // default_sensitive_fields is used. If this option is used, then it
      // replaced the default sensitive fields with the user-supplied list of
      // sensitive fields.
      sensitive_fields_were_specified = true;
      swoc::TextView input_filter_fields{std::string_view{optarg}};
      swoc::TextView filter_field;
      while (!(filter_field = input_filter_fields.take_prefix_at(',')).empty()) {
        filter_field.trim_if(&isspace);
        if (filter_field.empty()) {
          continue;
        }
        user_specified_fields.emplace(filter_field);
      }
      break;
    }
    case 'n': {
      // --sni-filter is used to filter sessions based upon an SNI.
      sni_filter = std::string(optarg);
      break;
    }
    case 'l': {
      log_dir = swoc::file::path{optarg};
      break;
    }
    case 's': {
      sample_pool_size = static_cast<int64_t>(std::strtol(optarg, nullptr, 0));
      break;
    }
    case 'm': {
      max_disk_usage     = static_cast<int64_t>(std::strtol(optarg, nullptr, 0));
      enforce_disk_limit = true;
      break;
    }
    case '4':
    case '6': {
      client_ip_filter = std::string(optarg);
      break;
    }
    case -1:
    case '?':
      break;

    default:
      Dbg(traffic_dump::dbg_ctl, "Unexpected options.");
      TSError("[%s] Unexpected options error.", traffic_dump::debug_tag);
      return;
    }
  }
  if (!log_dir.is_absolute()) {
    log_dir = swoc::file::path(TSInstallDirGet()) / log_dir;
  }
  if (sni_filter.empty()) {
    if (!traffic_dump::SessionData::init(log_dir.view(), enforce_disk_limit, max_disk_usage, sample_pool_size, client_ip_filter)) {
      TSError("[%s] Failed to initialize session state.", traffic_dump::debug_tag);
      return;
    }
  } else {
    if (!traffic_dump::SessionData::init(log_dir.view(), enforce_disk_limit, max_disk_usage, sample_pool_size, client_ip_filter,
                                         sni_filter)) {
      TSError("[%s] Failed to initialize session state with an SNI filter.", traffic_dump::debug_tag);
      return;
    }
  }

  if (sensitive_fields_were_specified) {
    if (!traffic_dump::TransactionData::init(dump_body, std::move(user_specified_fields))) {
      TSError("[%s] Failed to initialize transaction state with user-specified fields.", traffic_dump::debug_tag);
      return;
    }
  } else {
    // The user did not provide their own list of sensitive fields. Use the
    // default.
    if (!traffic_dump::TransactionData::init(dump_body)) {
      TSError("[%s] Failed to initialize transaction state.", traffic_dump::debug_tag);
      return;
    }
  }

  TSCont message_continuation = TSContCreate(traffic_dump::global_message_handler, nullptr);
  TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK, message_continuation);
  return;
}
