/*
 *
 * (C) 2013-25 - ntop.org
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

#include "ntop_includes.h"

struct http_walk_info {
  char *virtual_host;
  Host *h;
  lua_State *vm;
  u_int32_t num;
};

/* *************************************** */

HTTPstats::HTTPstats(Host *_host) {
  struct timeval tv;

  if(trace_new_delete) ntop->getTrace()->traceEvent(TRACE_NORMAL, "[new] %s", __FILE__);
  
  host = _host;
  h = host->getInterface()->get_hosts_hash(), warning_shown = false;
  memset(&query, 0, sizeof(query));
  memset(&response, 0, sizeof(response));
  memset(&query_rate, 0, sizeof(query_rate));
  memset(&response_rate, 0, sizeof(response_rate));
  memset(&last_query_sample, 0, sizeof(last_query_sample));
  memset(&last_response_sample, 0, sizeof(last_response_sample));

  gettimeofday(&tv, NULL);
  memcpy(&last_update_time, &tv, sizeof(struct timeval));
  if ((virtualHosts = new (std::nothrow)
           VirtualHostHash(host->getInterface(), 8 /* Buckets */,
                           64 /* Maximum number of virtual hosts */)) == NULL) {
    ntop->getTrace()->traceEvent(
        TRACE_WARNING, "Internal error: are you running out of memory?");
  }
}

/* *************************************** */

HTTPstats::~HTTPstats() {
  if(trace_new_delete) ntop->getTrace()->traceEvent(TRACE_NORMAL, "[delete] %s", __FILE__);
  if (virtualHosts) delete (virtualHosts);
}

/* *************************************** */

static bool http_stats_summary(GenericHashEntry *node, void *user_data,
                               bool *matched) {
  VirtualHost *host = (VirtualHost *)node;
  struct http_walk_info *info = (struct http_walk_info *)user_data;

  if (host->get_name()) {
    if ((info->virtual_host != NULL) &&
        strcmp(info->virtual_host, host->get_name()))
      return (false); /* false = keep on walking */

    info->num++, *matched = true;

    lua_newtable(info->vm);

    if (info->h) {
      IpAddress *ip = info->h->get_ip();

      if (ip) {
        char ip_buf[64];

        lua_push_str_table_entry(info->vm, "server.ip",
                                 ip->print(ip_buf, sizeof(ip_buf)));
        lua_push_uint64_table_entry(info->vm, "server.vlan",
                                    info->h->get_vlan_id());
      }
    }

    lua_push_uint64_table_entry(info->vm, "bytes.sent", host->get_sent_bytes());
    lua_push_uint64_table_entry(info->vm, "bytes.rcvd", host->get_rcvd_bytes());
    lua_push_uint64_table_entry(info->vm, "http.requests",
                                host->get_num_requests());
    lua_push_uint64_table_entry(info->vm, "http.act_num_requests",
                                host->get_diff_num_requests());
    lua_push_uint64_table_entry(info->vm, "http.requests_trend",
                                host->get_trend());

    lua_pushstring(info->vm, host->get_name());
    lua_insert(info->vm, -2);
    lua_settable(info->vm, -3);
  }

  return (false); /* false = keep on walking */
}

/* **************************************************** */

u_int32_t HTTPstats::luaVirtualHosts(lua_State *vm, char *virtual_host,
                                     Host *h) {
  if (virtualHosts) {
    struct http_walk_info info;
    u_int32_t begin_slot = 0;
    bool walk_all = true;

    info.virtual_host = virtual_host, info.h = h, info.vm = vm, info.num = 0;
    virtualHosts->walk(&begin_slot, walk_all, http_stats_summary, &info);
    return (info.num);
  } else
    return (0);
}

/* *************************************** */

void HTTPstats::getRequests(const struct http_query_stats *q,
                            u_int32_t *num_get, u_int32_t *num_post,
                            u_int32_t *num_head, u_int32_t *num_put,
                            u_int32_t *num_other) {
  if (q == NULL) return;
  *num_get += q->num_get;
  *num_post += q->num_post;
  *num_head += q->num_head;
  *num_put += q->num_put;
  *num_other += q->num_other;
}

/* *************************************** */

void HTTPstats::getResponses(const struct http_response_stats *r,
                             u_int32_t *num_1xx, u_int32_t *num_2xx,
                             u_int32_t *num_3xx, u_int32_t *num_4xx,
                             u_int32_t *num_5xx) {
  if (r == NULL) return;
  *num_1xx += r->num_1xx;
  *num_2xx += r->num_2xx;
  *num_3xx += r->num_3xx;
  *num_4xx += r->num_4xx;
  *num_5xx += r->num_5xx;
}

/* *************************************** */

void HTTPstats::getRequestsRates(const struct http_query_rates *dq,
                                 u_int16_t *rate_get, u_int16_t *rate_post,
                                 u_int16_t *rate_head, u_int16_t *rate_put,
                                 u_int16_t *rate_other) {
  if (dq == NULL) return;
  *rate_get += dq->rate_get;
  *rate_post += dq->rate_post;
  *rate_head += dq->rate_head;
  *rate_put += dq->rate_put;
  *rate_other += dq->rate_other;
}

/* *************************************** */

void HTTPstats::getResponsesRates(const struct http_response_rates *dr,
                                  u_int16_t *rate_1xx, u_int16_t *rate_2xx,
                                  u_int16_t *rate_3xx, u_int16_t *rate_4xx,
                                  u_int16_t *rate_5xx) {
  if (dr == NULL) return;
  *rate_1xx += dr->rate_1xx;
  *rate_2xx += dr->rate_2xx;
  *rate_3xx += dr->rate_3xx;
  *rate_4xx += dr->rate_4xx;
  *rate_5xx += dr->rate_5xx;
}

/* *************************************** */

void HTTPstats::getRequestsDelta(const struct http_query_stats *q0,
                                 const struct http_query_stats *q1,
                                 u_int32_t *delta_get, u_int32_t *delta_post,
                                 u_int32_t *delta_head, u_int32_t *delta_put,
                                 u_int32_t *delta_other) {
  if (q0 == NULL || q1 == NULL) return;
  *delta_get += q1->num_get - q0->num_get;
  *delta_post += q1->num_post - q0->num_post;
  *delta_head += q1->num_head - q0->num_head;
  *delta_put += q1->num_put - q0->num_put;
  *delta_other += q1->num_other - q0->num_other;
}

/* *************************************** */

void HTTPstats::getResponsesDelta(const struct http_response_stats *r0,
                                  const struct http_response_stats *r1,
                                  u_int32_t *delta_1xx, u_int32_t *delta_2xx,
                                  u_int32_t *delta_3xx, u_int32_t *delta_4xx,
                                  u_int32_t *delta_5xx) {
  if (r0 == NULL || r1 == NULL) return;
  *delta_1xx += r1->num_1xx - r0->num_1xx;
  *delta_2xx += r1->num_2xx - r0->num_2xx;
  *delta_3xx += r1->num_3xx - r0->num_3xx;
  *delta_4xx += r1->num_4xx - r0->num_4xx;
  *delta_5xx += r1->num_5xx - r0->num_5xx;
}

/* *************************************** */

u_int32_t HTTPstats::getSentNumQueries() {
  u_int32_t num_get = 0, num_post = 0, num_head = 0, num_put = 0, num_other = 0;

  getRequests(&query[AS_SENDER], &num_get, &num_post, &num_head, &num_put,
              &num_other);

  return num_get + num_post + num_head + num_put + num_other;
}

/* *************************************** */

u_int32_t HTTPstats::getSentNumResponses() {
  u_int32_t num_1xx = 0, num_2xx = 0, num_3xx = 0, num_4xx = 0, num_5xx = 0;

  getResponses(&response[AS_SENDER], &num_1xx, &num_2xx, &num_3xx, &num_4xx,
               &num_5xx);

  return num_1xx + num_2xx + num_3xx + num_4xx + num_5xx;
}

/* *************************************** */

u_int32_t HTTPstats::getRcvdNumQueries() {
  u_int32_t num_get = 0, num_post = 0, num_head = 0, num_put = 0, num_other = 0;

  getRequests(&query[AS_RECEIVER], &num_get, &num_post, &num_head, &num_put,
              &num_other);

  return num_get + num_post + num_head + num_put + num_other;
}

/* *************************************** */

u_int32_t HTTPstats::getRcvdNumResponses() {
  u_int32_t num_1xx = 0, num_2xx = 0, num_3xx = 0, num_4xx = 0, num_5xx = 0;

  getResponses(&response[AS_RECEIVER], &num_1xx, &num_2xx, &num_3xx, &num_4xx,
               &num_5xx);

  return num_1xx + num_2xx + num_3xx + num_4xx + num_5xx;
}

/* *************************************** */

void HTTPstats::luaAddCounters(lua_State *vm, bool as_sender) {
  u_int32_t num_get = 0, num_post = 0, num_head = 0, num_put = 0, num_other = 0;
  u_int32_t num_1xx = 0, num_2xx = 0, num_3xx = 0, num_4xx = 0, num_5xx = 0;

  getRequests(&query[as_sender ? AS_SENDER : AS_RECEIVER], &num_get, &num_post,
              &num_head, &num_put, &num_other);
  getResponses(&response[as_sender ? AS_SENDER : AS_RECEIVER], &num_1xx,
               &num_2xx, &num_3xx, &num_4xx, &num_5xx);

  lua_newtable(vm);
  lua_push_uint64_table_entry(
      vm, "total", num_get + num_post + num_head + num_put + num_other);
  lua_push_uint64_table_entry(vm, "num_get", num_get);
  lua_push_uint64_table_entry(vm, "num_post", num_post);
  lua_push_uint64_table_entry(vm, "num_head", num_head);
  lua_push_uint64_table_entry(vm, "num_put", num_put);
  lua_push_uint64_table_entry(vm, "num_other", num_other);
  lua_pushstring(vm, "query");
  lua_insert(vm, -2);
  lua_settable(vm, -3);

  lua_newtable(vm);
  lua_push_uint64_table_entry(vm, "total",
                              num_1xx + num_2xx + num_3xx + num_4xx + num_5xx);
  lua_push_uint64_table_entry(vm, "num_1xx", num_1xx);
  lua_push_uint64_table_entry(vm, "num_2xx", num_2xx);
  lua_push_uint64_table_entry(vm, "num_3xx", num_3xx);
  lua_push_uint64_table_entry(vm, "num_4xx", num_4xx);
  lua_push_uint64_table_entry(vm, "num_5xx", num_5xx);
  lua_pushstring(vm, "response");
  lua_insert(vm, -2);
  lua_settable(vm, -3);
}

/* *************************************** */

void HTTPstats::luaAddRates(lua_State *vm, bool as_sender) {
  u_int16_t rate_get = 0, rate_post = 0, rate_head = 0, rate_put = 0,
            rate_other = 0;
  u_int16_t rate_1xx = 0, rate_2xx = 0, rate_3xx = 0, rate_4xx = 0,
            rate_5xx = 0;

  getRequestsRates(&query_rate[as_sender ? AS_SENDER : AS_RECEIVER], &rate_get,
                   &rate_post, &rate_head, &rate_put, &rate_other);
  getResponsesRates(&response_rate[as_sender ? AS_SENDER : AS_RECEIVER],
                    &rate_1xx, &rate_2xx, &rate_3xx, &rate_4xx, &rate_5xx);

  lua_newtable(vm);

  lua_newtable(vm);
  lua_push_uint64_table_entry(vm, "get", rate_get);
  lua_push_uint64_table_entry(vm, "post", rate_post);
  lua_push_uint64_table_entry(vm, "head", rate_head);
  lua_push_uint64_table_entry(vm, "put", rate_put);
  lua_push_uint64_table_entry(vm, "other", rate_other);
  lua_pushstring(vm, "query");
  lua_insert(vm, -2);
  lua_settable(vm, -3);

  lua_newtable(vm);
  lua_push_uint64_table_entry(vm, "1xx", rate_1xx);
  lua_push_uint64_table_entry(vm, "2xx", rate_2xx);
  lua_push_uint64_table_entry(vm, "3xx", rate_3xx);
  lua_push_uint64_table_entry(vm, "4xx", rate_4xx);
  lua_push_uint64_table_entry(vm, "5xx", rate_5xx);
  lua_pushstring(vm, "response");
  lua_insert(vm, -2);
  lua_settable(vm, -3);

  lua_pushstring(vm, "rate");
  lua_insert(vm, -2);
  lua_settable(vm, -3);
}

/* **************************************************** */

void HTTPstats::lua(lua_State *vm) {
  lua_newtable(vm);

  if (virtualHosts) {
    struct http_walk_info info;
    u_int32_t begin_slot = 0;
    bool walk_all = true;

    info.virtual_host = NULL, info.h = NULL, info.vm = vm;

    lua_newtable(vm);
    virtualHosts->walk(&begin_slot, walk_all, http_stats_summary, &info);
    lua_pushstring(vm, "virtual_hosts");
    lua_insert(vm, -2);
    lua_settable(vm, -3);
  }

  lua_newtable(vm);
  luaAddCounters(vm, true);
  luaAddRates(vm, true);
  lua_pushstring(vm, "sender");
  lua_insert(vm, -2);
  lua_settable(vm, -3);

  lua_newtable(vm);
  luaAddRates(vm, false);
  luaAddCounters(vm, false);
  lua_pushstring(vm, "receiver");
  lua_insert(vm, -2);
  lua_settable(vm, -3);

  lua_pushstring(vm, "http");
  lua_insert(vm, -2);
  lua_settable(vm, -3);
}

/* ******************************************* */

void HTTPstats::JSONObjectAddCounters(json_object *my_object, bool as_sender) {
  u_int32_t num_get = 0, num_post = 0, num_head = 0, num_put = 0, num_other = 0;
  u_int32_t num_1xx = 0, num_2xx = 0, num_3xx = 0, num_4xx = 0, num_5xx = 0;
  json_object *sub_object;

  if (!my_object) return;

  getRequests(&query[as_sender ? AS_SENDER : AS_RECEIVER], &num_get, &num_post,
              &num_head, &num_put, &num_other);
  getResponses(&response[as_sender ? AS_SENDER : AS_RECEIVER], &num_1xx,
               &num_2xx, &num_3xx, &num_4xx, &num_5xx);

  if (!(sub_object = json_object_new_object())) return;
  if (num_get > 0)
    json_object_object_add(sub_object, "num_get",
                           json_object_new_int64(num_get));
  if (num_post > 0)
    json_object_object_add(sub_object, "num_post",
                           json_object_new_int64(num_post));
  if (num_head > 0)
    json_object_object_add(sub_object, "num_head",
                           json_object_new_int64(num_head));
  if (num_put > 0)
    json_object_object_add(sub_object, "num_put",
                           json_object_new_int64(num_put));
  if (num_other > 0)
    json_object_object_add(sub_object, "num_other",
                           json_object_new_int64(num_other));
  json_object_object_add(my_object, "query", sub_object);

  if (!(sub_object = json_object_new_object())) return;
  if (num_1xx > 0)
    json_object_object_add(sub_object, "num_1xx",
                           json_object_new_int64(num_1xx));
  if (num_2xx > 0)
    json_object_object_add(sub_object, "num_2xx",
                           json_object_new_int64(num_2xx));
  if (num_3xx > 0)
    json_object_object_add(sub_object, "num_3xx",
                           json_object_new_int64(num_3xx));
  if (num_4xx > 0)
    json_object_object_add(sub_object, "num_4xx",
                           json_object_new_int64(num_4xx));
  if (num_5xx > 0)
    json_object_object_add(sub_object, "num_5xx",
                           json_object_new_int64(num_5xx));
  json_object_object_add(my_object, "response", sub_object);
}

/* ******************************************* */

void HTTPstats::JSONObjectAddRates(json_object *my_object, bool as_sender) {
  u_int16_t rate_get = 0, rate_post = 0, rate_head = 0, rate_put = 0,
            rate_other = 0;
  u_int16_t rate_1xx = 0, rate_2xx = 0, rate_3xx = 0, rate_4xx = 0,
            rate_5xx = 0;
  json_object *sub_object;

  if (!my_object) return;

  getRequestsRates(&query_rate[as_sender ? AS_SENDER : AS_RECEIVER], &rate_get,
                   &rate_post, &rate_head, &rate_put, &rate_other);
  getResponsesRates(&response_rate[as_sender ? AS_SENDER : AS_RECEIVER],
                    &rate_1xx, &rate_2xx, &rate_3xx, &rate_4xx, &rate_5xx);

  if (!(sub_object = json_object_new_object())) return;
  if (rate_get > 0)
    json_object_object_add(sub_object, "get", json_object_new_int64(rate_get));
  if (rate_post > 0)
    json_object_object_add(sub_object, "post",
                           json_object_new_int64(rate_post));
  if (rate_head > 0)
    json_object_object_add(sub_object, "head",
                           json_object_new_int64(rate_head));
  if (rate_put > 0)
    json_object_object_add(sub_object, "put", json_object_new_int64(rate_put));
  if (rate_other > 0)
    json_object_object_add(sub_object, "other",
                           json_object_new_int64(rate_other));
  json_object_object_add(my_object, "query_rate", sub_object);

  if (!(sub_object = json_object_new_object())) return;
  if (rate_1xx > 0)
    json_object_object_add(sub_object, "1xx", json_object_new_int64(rate_1xx));
  if (rate_2xx > 0)
    json_object_object_add(sub_object, "2xx", json_object_new_int64(rate_2xx));
  if (rate_3xx > 0)
    json_object_object_add(sub_object, "3xx", json_object_new_int64(rate_3xx));
  if (rate_4xx > 0)
    json_object_object_add(sub_object, "4xx", json_object_new_int64(rate_4xx));
  if (rate_5xx > 0)
    json_object_object_add(sub_object, "3xx", json_object_new_int64(rate_5xx));
  json_object_object_add(my_object, "response_rate", sub_object);
}

/* ******************************************* */

json_object *HTTPstats::getJSONObject() {
  json_object *sub_object, *my_object = json_object_new_object();

  if (!my_object) return (NULL);

  if ((sub_object = json_object_new_object()) != NULL) {
    JSONObjectAddCounters(sub_object, true);
    JSONObjectAddRates(sub_object, true);
    json_object_object_add(my_object, "sender", sub_object);
  }

  if ((sub_object = json_object_new_object()) != NULL) {
    JSONObjectAddCounters(sub_object, false);
    JSONObjectAddRates(sub_object, false);
    json_object_object_add(my_object, "receiver", sub_object);
  }

  return (my_object);
}

/* ******************************************* */

void HTTPstats::incStats(bool as_client, const FlowHTTPStats *fts) {
  struct http_query_stats *q =
      as_client ? &query[AS_SENDER] : &query[AS_RECEIVER];
  struct http_response_stats *r =
      as_client ? &response[AS_RECEIVER] : &response[AS_SENDER];

  if (fts->num_get) q->num_get += fts->num_get;
  if (fts->num_post) q->num_post += fts->num_post;
  if (fts->num_head) q->num_head += fts->num_head;
  if (fts->num_put) q->num_put += fts->num_put;
  if (fts->num_other) q->num_other += fts->num_other;

  if (fts->num_1xx) r->num_1xx += fts->num_1xx;
  if (fts->num_2xx) r->num_2xx += fts->num_2xx;
  if (fts->num_3xx) r->num_3xx += fts->num_3xx;
  if (fts->num_4xx) r->num_4xx += fts->num_4xx;
  if (fts->num_5xx) r->num_5xx += fts->num_5xx;
}

/* ******************************************* */

bool HTTPstats::updateHTTPHostRequest(time_t t, char *virtual_host_name,
                                      u_int32_t num_requests,
                                      u_int32_t bytes_sent,
                                      u_int32_t bytes_rcvd) {
  VirtualHost *vh;
  bool rc = false;

  if ((num_requests == 0) && (bytes_sent == 0) && (bytes_rcvd == 0))
    return (rc);

  if (!virtualHosts) return (rc); /* Looks like we're running out of memory */

  /*
    Needed because this method can be called by both
    NetworkInterface::updateHostStats() and
    Flow::~Flow() on the same instance and thus
    create a memory leak
  */
  if ((vh = virtualHosts->get(virtual_host_name)) == NULL) {
    if (virtualHosts->hasEmptyRoom()) {
      if ((vh = new (std::nothrow) VirtualHost(h, virtual_host_name)) == NULL) {
        ntop->getTrace()->traceEvent(
            TRACE_WARNING, "Internal error: are you running out of memory?");
      } else {
        if(virtualHosts->add(vh, false /* no need to lock the bucket, purgeIdle is sequential wrt to this */) == false) {
          /* Unable to add a new virtual host */
          delete vh;
          vh = NULL;
        } else
          rc = true;
      }
    }
  }

  if (vh) vh->incStats(t, num_requests, bytes_sent, bytes_rcvd);

  return (rc);
}

/* *************************************** */

static bool update_http_stats(GenericHashEntry *node, void *user_data,
                              bool *matched) {
  VirtualHost *host = (VirtualHost *)node;

  host->update_stats();
  *matched = true;

  return (false); /* false = keep on walking */
}

/* ******************************************* */

void HTTPstats::updateStats(const struct timeval *tv) {
  float tdiff_msec = Utils::msTimevalDiff(tv, &last_update_time);
  const u_int8_t indices[2] = {AS_SENDER, AS_RECEIVER};

  // refresh the last update time with the new values
  // also refresh the statistics on request variations
  if (virtualHosts) {
    u_int32_t begin_slot = 0;
    bool walk_all = true;

    virtualHosts->walk(&begin_slot, walk_all, update_http_stats, (void *)tv);
    virtualHosts->purgeIdle(tv, false, false);
    virtualHosts->purgeQueuedIdleEntries(); /* Actually reclaim memory (delete*)
                                               of idle virtual hosts */
  }

  if (tdiff_msec < 1000) {
    return;  // too early
  }

  for (u_int8_t i = 0; i < 2; i++) {
    u_int8_t direction = indices[i];
    struct http_query_rates *dq = &query_rate[direction];
    struct http_response_rates *dr = &response_rate[direction];
    u_int32_t d_get = 0, d_post = 0, d_head = 0, d_put = 0, d_other = 0;
    u_int32_t d_1xx = 0, d_2xx = 0, d_3xx = 0, d_4xx = 0, d_5xx = 0;

    getRequestsDelta(&last_query_sample[direction], &query[direction], &d_get,
                     &d_post, &d_head, &d_put, &d_other);
    getResponsesDelta(&last_response_sample[direction], &response[direction],
                      &d_1xx, &d_2xx, &d_3xx, &d_4xx, &d_5xx);

    dq->rate_get = makeRate(d_get, tdiff_msec),
    dq->rate_post = makeRate(d_post, tdiff_msec),
    dq->rate_head = makeRate(d_head, tdiff_msec),
    dq->rate_put = makeRate(d_put, tdiff_msec),
    dq->rate_other = makeRate(d_other, tdiff_msec),
    dr->rate_1xx = makeRate(d_1xx, tdiff_msec),
    dr->rate_2xx = makeRate(d_2xx, tdiff_msec),
    dr->rate_3xx = makeRate(d_3xx, tdiff_msec),
    dr->rate_4xx = makeRate(d_4xx, tdiff_msec),
    dr->rate_5xx = makeRate(d_5xx, tdiff_msec);
  }

  last_update_time.tv_sec = tv->tv_sec, last_update_time.tv_usec = tv->tv_usec;
  memcpy(&last_query_sample, &query, sizeof(query));
  memcpy(&last_response_sample, &response, sizeof(response));
}
