/*
     This file is part of GNUnet.
     Copyright (C) 2009, 2010, 2011, 2016 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     or (at your option) any later version.

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */
/**
 * @file transport/test_quota_compliance.c
 * @brief base test case for transport implementations
 *
 * This test case tests quota compliance both on transport level
 */
#include "platform.h"
#include "gnunet_transport_service.h"
#include "gnunet_ats_service.h"
#include "gauger.h"
#include "transport-testing.h"

/**
 * Testcase timeout
 */
#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 480)

#define DURATION GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 80)


static struct GNUNET_SCHEDULER_Task *measure_task;

static char *gen_cfgs[2];

static unsigned long long quota_in[] = { 10000, 10000 };

static unsigned long long quota_out[] = { 10000, 10000 };

static struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc;


/**
 * Note that this value must not significantly exceed
 * 'MAX_PENDING' in 'gnunet-service-transport.c', otherwise
 * messages may be dropped even for a reliable transport.
 */
#define TOTAL_MSGS (1024 * 32)

static unsigned long long total_bytes_recv;

static struct GNUNET_TIME_Absolute start_time;


static void
report ()
{
  unsigned long long delta;
  unsigned long long datarate;

  delta = GNUNET_TIME_absolute_get_duration (start_time).rel_value_us;
  if (0 == delta)
    delta = 1;
  datarate = (total_bytes_recv * 1000 * 1000) / delta;

  fprintf (stderr,
           "Throughput was %llu b/s\n",
           datarate);
  ccc->global_ret = GNUNET_OK;
  if (datarate > 1.5 * quota_in[1])
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Datarate of %llu b/s significantly higher than allowed inbound quota of %llu b/s\n",
                datarate,
                quota_in[1]);
    ccc->global_ret = GNUNET_SYSERR;
  }
  if (datarate > 1.5 * quota_out[0])
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Datarate of %llu b/s significantly higher than allowed outbound quota of %llu b/s\n",
                datarate,
                quota_out[0]);
    ccc->global_ret = GNUNET_SYSERR;
  }
  if (GNUNET_OK == ccc->global_ret)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Datarate of %llu b/s complied to allowed outbound quota of %llu b/s and inbound quota of %llu b/s\n",
                datarate,
                quota_out[0],
                quota_in[1]);
  }
}


static void
custom_shutdown (void *cls)
{
  if (NULL != measure_task)
  {
    GNUNET_SCHEDULER_cancel (measure_task);
    measure_task = NULL;
  }
  report ();
}


static size_t
get_size (unsigned int iter)
{
  size_t ret;

  ret = (iter * iter * iter) % 60000;
  ret += sizeof(struct GNUNET_TRANSPORT_TESTING_TestMessage);
  return ret;
}


static void
notify_receive (void *cls,
                struct GNUNET_TRANSPORT_TESTING_PeerContext *receiver,
                const struct GNUNET_PeerIdentity *sender,
                const struct GNUNET_TRANSPORT_TESTING_TestMessage *hdr)
{
  if (GNUNET_TRANSPORT_TESTING_SIMPLE_MTYPE != ntohs (hdr->header.type))
    return;
  total_bytes_recv += ntohs (hdr->header.size);

  {
    char *ps = GNUNET_strdup (GNUNET_i2s (&receiver->id));

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Peer %u (`%s') got message %u of size %u from peer (`%s')\n",
                receiver->no,
                ps,
                ntohl (hdr->num),
                ntohs (hdr->header.size),
                GNUNET_i2s (sender));
    GNUNET_free (ps);
  }
}


static void
measure (void *cls)
{
  static int counter;

  measure_task = NULL;
  counter++;
  if ((DURATION.rel_value_us / 1000 / 1000LL) < counter)
  {
    fprintf (stderr, "%s", ".\n");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  fprintf (stderr, "%s", ".");
  measure_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
                                               &measure,
                                               NULL);
}


static void
start_task (void *cls)
{
  static struct GNUNET_TRANSPORT_TESTING_SendClosure sc = {
    .num_messages = TOTAL_MSGS,
    .get_size_cb = &get_size
  };

  sc.ccc = ccc;
  measure_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
                                               &measure,
                                               NULL);
  start_time = GNUNET_TIME_absolute_get ();
  GNUNET_SCHEDULER_add_now (&GNUNET_TRANSPORT_TESTING_simple_send,
                            &sc);
}


static char *
generate_config (const char *cfg_file,
                 unsigned long long quota_in,
                 unsigned long long quota_out)
{
  char *in_name;
  char *out_name;
  char *fname = NULL;
  struct GNUNET_CONFIGURATION_Handle *cfg = GNUNET_CONFIGURATION_create ();

  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CONFIGURATION_load (cfg,
                                            cfg_file));
  GNUNET_asprintf (&fname,
                   "q_in_%llu_q_out_%llu_%s",
                   quota_in,
                   quota_out,
                   cfg_file);
  GNUNET_CONFIGURATION_set_value_string (cfg,
                                         "PATHS",
                                         "DEFAULTCONFIG",
                                         fname);
  for (int c = 0; c < GNUNET_NT_COUNT; c++)
  {
    GNUNET_asprintf (&in_name,
                     "%s_QUOTA_IN",
                     GNUNET_NT_to_string (c));
    GNUNET_asprintf (&out_name,
                     "%s_QUOTA_OUT",
                     GNUNET_NT_to_string (c));
    GNUNET_CONFIGURATION_set_value_number (cfg,
                                           "ats",
                                           in_name,
                                           quota_in);
    GNUNET_CONFIGURATION_set_value_number (cfg,
                                           "ats",
                                           out_name,
                                           quota_out);
    GNUNET_free (in_name);
    GNUNET_free (out_name);
  }
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CONFIGURATION_write (cfg,
                                             fname));
  GNUNET_CONFIGURATION_destroy (cfg);
  return fname;
}


static int
check (void *cls,
       struct GNUNET_TRANSPORT_TESTING_Handle *tth_,
       const char *test_plugin_,
       const char *test_name_,
       unsigned int num_peers,
       char *cfg_files[])
{
  struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext my_ccc = {
    .connect_continuation = &start_task,
    .config_file = "test_quota_compliance_data.conf",
    .rec = &notify_receive,
    .nc = &GNUNET_TRANSPORT_TESTING_log_connect,
    .nd = &GNUNET_TRANSPORT_TESTING_log_disconnect,
    .shutdown_task = &custom_shutdown,
    .timeout = TIMEOUT
  };

  ccc = &my_ccc;

  if (NULL != strstr (test_name_,
                      "asymmetric"))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Running asymmetric test with sending peer unlimited, receiving peer (in/out): %llu/%llu b/s \n",
                quota_in[1],
                quota_out[1]);
    quota_out[0] = 1024 * 1024 * 1024;
    quota_in[0] = 1024 * 1024 * 1024;
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Running symmetric test with (in/out) %llu/%llu b/s \n",
                quota_in[1],
                quota_out[1]);
  }
  for (unsigned int i = 0; i < 2; i++)
  {
    gen_cfgs[i] = generate_config (cfg_files[i],
                                   quota_in[i],
                                   quota_out[i]);
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Generated config file `%s'\n",
                gen_cfgs[i]);
  }

  return GNUNET_TRANSPORT_TESTING_connect_check (&my_ccc,
                                                 tth_,
                                                 test_plugin_,
                                                 test_name_,
                                                 num_peers,
                                                 gen_cfgs);
}


int
main (int argc,
      char *argv[])
{
  if (GNUNET_OK !=
      GNUNET_TRANSPORT_TESTING_main (2,
                                     &check,
                                     NULL))
  {
    GNUNET_break (0);
    return 1;
  }
  for (unsigned int i = 0; i < 2; i++)
  {
    if ((NULL != gen_cfgs[i]) &&
        (GNUNET_YES == GNUNET_DISK_file_test (gen_cfgs[i])))
    {
      GNUNET_DISK_directory_remove (gen_cfgs[i]);
      GNUNET_free (gen_cfgs[i]);
    }
  }
  return 0;
}


/* end of test_quota_compliance.c */
