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

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

     You should have received a copy of the GNU General Public License
     along with GNUnet; see the file COPYING.  If not, write to the
     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     Boston, MA 02110-1301, USA.
*/

/**
 * @file src/setup/gnunet-setup-transport-test.c
 * @brief support for testing transport configurations
 * @author Christian Grothoff
 */
#include "gnunet-setup.h"
#include <gnunet/gnunet_resolver_service.h>
#include <gnunet/gnunet_nat_auto_service.h>

/**
 * How long do we wait for the NAT test to report success?
 */
#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)


/**
 * Data structure we keep for NAT tests that run asynchronously until
 * #TIMEOUT or shutdown.
 */
struct TestContext
{

  /**
   * Handle to the active NAT test.
   */
  struct GNUNET_NAT_AUTO_Test *tst;

  /**
   * Timeout task.
   */
  struct GNUNET_SCHEDULER_Task *tt;

  /**
   * Shutdown task.
   */
  struct GNUNET_SCHEDULER_Task *st;

  /**
   * Name of widget to show on success.
   */
  const char *success_image;

  /**
   * Name of widget to show on failure.
   */
  const char *failure_image;
};


/**
 * Number of active tests.
 */
static unsigned int num_tests;

/**
 * Handle for NAT service.
 */
static struct GNUNET_OS_Process *nat;

/**
 * Handle for NAT-AUTO service.
 */
static struct GNUNET_OS_Process *nat_auto;

/**
 * Handle for RESOLVER service.
 */
static struct GNUNET_OS_Process *resolver;


/**
 * Stop the given process, nicely.
 *
 * @param proc process to stop.
 */
static void
stop_service (struct GNUNET_OS_Process *proc)
{
  if (NULL == proc)
    return;
  GNUNET_break (0 == GNUNET_OS_process_kill (proc, SIGTERM));
  GNUNET_break (GNUNET_OK == GNUNET_OS_process_wait (proc));
  GNUNET_OS_process_destroy (proc);
}


/**
 * Start the given service process.
 *
 * @param name name of the service to run
 * @return handle to the service
 */
static struct GNUNET_OS_Process *
start_service (const char *name)
{
  struct GNUNET_OS_Process *proc;
  char *binary;
  char *filename;
  char *path;

  GNUNET_asprintf (&binary, "gnunet-service-%s", name);
  path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBEXECDIR);
  GNUNET_asprintf (&filename, "%s/%s", path, binary);
  GNUNET_free (path);
  proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
                                  NULL,
                                  NULL,
                                  NULL,
                                  filename,
                                  binary,
                                  "-c",
                                  option_cfg_name,
                                  NULL);
  if (NULL == proc)
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "exec", filename);
  GNUNET_free (binary);
  GNUNET_free (filename);
  return proc;
}


/**
 * Stop the helper services.
 */
static void
stop_services ()
{
  stop_service (nat_auto);
  nat_auto = NULL;
  stop_service (nat);
  nat = NULL;
  stop_service (resolver);
  resolver = NULL;
}


/**
 * Start the helper services.
 */
static void
start_services ()
{
  resolver = start_service ("resolver");
  nat = start_service ("nat");
  nat_auto = start_service ("nat-auto");
}


/**
 * Display the result of the test.
 *
 * @param tc test context
 * @param result #GNUNET_YES on success
 */
static void
display_test_result (struct TestContext *tc, int result)
{
  GObject *w;

  if (GNUNET_YES != result)
  {
    w = GNUNET_SETUP_get_object (tc->failure_image);
    if (NULL != w)
      gtk_widget_show (GTK_WIDGET (w));
    w = GNUNET_SETUP_get_object (tc->success_image);
    if (NULL != w)
      gtk_widget_hide (GTK_WIDGET (w));
  }
  else
  {
    w = GNUNET_SETUP_get_object (tc->failure_image);
    if (NULL != w)
      gtk_widget_hide (GTK_WIDGET (w));
    w = GNUNET_SETUP_get_object (tc->success_image);
    if (NULL != w)
      gtk_widget_show (GTK_WIDGET (w));
  }
  if (NULL != tc->tst)
  {
    GNUNET_NAT_AUTO_test_stop (tc->tst);
    tc->tst = NULL;
  }
  num_tests--;
  GNUNET_free (tc);
  if (0 == num_tests)
    stop_services ();
}


/**
 * Task run on timeout.
 *
 * @param cls the `struct TestContext`
 */
static void
timeout_task (void *cls)
{
  struct TestContext *tc = cls;

  tc->tt = NULL;
  GNUNET_SCHEDULER_cancel (tc->st);
  tc->st = NULL;
  display_test_result (tc, GNUNET_SYSERR);
}


/**
 * Task run on shutdown.
 *
 * @param cls the `struct TestContext`
 */
static void
shutdown_task (void *cls)
{
  struct TestContext *tc = cls;

  tc->st = NULL;
  GNUNET_SCHEDULER_cancel (tc->tt);
  GNUNET_NAT_AUTO_test_stop (tc->tst);
  num_tests--;
  GNUNET_free (tc);
  if (0 == num_tests)
    stop_services ();
}


/**
 * Function called by NAT on success.
 * Clean up and update GUI (with success).
 *
 * @param cls test context
 * @param result status code for the NAT test
 */
static void
result_callback (void *cls, enum GNUNET_NAT_StatusCode result)
{
  struct TestContext *tc = cls;

  GNUNET_SCHEDULER_cancel (tc->tt);
  tc->tt = NULL;
  GNUNET_SCHEDULER_cancel (tc->st);
  tc->st = NULL;
  display_test_result (tc,
                       (GNUNET_NAT_ERROR_SUCCESS == result) ? GNUNET_OK
                                                            : GNUNET_SYSERR);
}


/**
 * Function called whenever the user wants to test a
 * transport configuration.
 *
 * @param section_name section with the port numbers
 * @param proto IPPROTO_TCP or IPPROTO_UDP
 * @param success_image image to show on success
 * @param failure_image image to show on failure
 */
void
GNUNET_setup_transport_test (const char *section_name,
                             int proto,
                             const char *success_image,
                             const char *failure_image)
{
  struct TestContext *tc;
  GtkWidget *w;

  if (0 == num_tests)
    start_services ();
  tc = GNUNET_new (struct TestContext);
  num_tests++;
  tc->success_image = success_image;
  tc->failure_image = failure_image;
  w = GTK_WIDGET (GNUNET_SETUP_get_object (success_image));
  gtk_widget_hide (w);
  GNUNET_assert (NULL != cfg);
  GNUNET_RESOLVER_connect (cfg);
  tc->tt = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &timeout_task, tc);
  tc->st = GNUNET_SCHEDULER_add_shutdown (&shutdown_task, tc);
  tc->tst =
    GNUNET_NAT_AUTO_test_start (cfg, proto, section_name, &result_callback, tc);
}


/* end of gnunet-setup-transport-test.c */
