/*
   This file is part of GNUnet.
   Copyright (C) 2011-2014, 2016, 2017 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 src/transport/gnunet-transport.c
 * @brief Tool to help configure, measure and control the transport subsystem.
 * @author Christian Grothoff
 * @author Nathan Evans
 */
#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet_resolver_service.h"
#include "gnunet_protocols.h"
#include "gnunet_transport_service.h"

/**
 * Timeout for a name resolution
 */
#define RESOLUTION_TIMEOUT \
  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)

/**
 * Timeout for an operation
 */
#define OP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)


/**
 * Context to store name resolutions for validation
 */
struct ValidationResolutionContext
{
  /**
   * Next in DLL
   */
  struct ValidationResolutionContext *next;

  /**
   * Previous in DLL
   */
  struct ValidationResolutionContext *prev;

  /**
   * Address to resolve
   */
  struct GNUNET_HELLO_Address *addrcp;

  /**
   * Time of last validation
   */
  struct GNUNET_TIME_Absolute last_validation;

  /**
   * Address is valid until
   */
  struct GNUNET_TIME_Absolute valid_until;

  /**
   * Time of next validation
   */
  struct GNUNET_TIME_Absolute next_validation;

  /**
   * Transport conversion handle
   */
  struct GNUNET_TRANSPORT_AddressToStringContext *asc;

  /**
   * plugin name
   */
  char *transport;

  /**
   * was the entry printed
   */
  int printed;
};

/**
 * Struct to store information about peers in monitor mode
 */
struct MonitoredPeer
{
  /**
   * State of the peer
   */
  enum GNUNET_TRANSPORT_PeerState state;

  /**
   * Timeout
   */
  struct GNUNET_TIME_Absolute state_timeout;

  /**
   * The address to convert
   */
  struct GNUNET_HELLO_Address *address;
};

/**
 * Context to store name resolutions for validation
 */
struct PeerResolutionContext
{
  /**
   * Next in DLL
   */
  struct PeerResolutionContext *next;

  /**
   * Prev in DLL
   */
  struct PeerResolutionContext *prev;

  /**
   * address to resolve
   */
  struct GNUNET_HELLO_Address *addrcp;

  /**
   * transport conversiion context
   */
  struct GNUNET_TRANSPORT_AddressToStringContext *asc;

  /**
   * peer state
   */
  enum GNUNET_TRANSPORT_PeerState state;

  /**
   * state timeout
   */
  struct GNUNET_TIME_Absolute state_timeout;

  /**
   * transport plugin
   */
  char *transport;

  /**
   * was the entry printed
   */
  int printed;
};


/**
 * Benchmarking block size in KB
 */
#define BLOCKSIZE 4

/**
 * Handle to transport service.
 */
static struct GNUNET_TRANSPORT_CoreHandle *handle;

/**
 * Configuration handle
 */
static struct GNUNET_CONFIGURATION_Handle *cfg;

/**
 * Blacklisting handle
 */
struct GNUNET_TRANSPORT_Blacklist *blacklist;

/**
 * Option -s.
 */
static int benchmark_send;

/**
 * Option -b.
 */
static int benchmark_receive;

/**
 * Option -l.
 */
static int benchmark_receive;

/**
 * Option -i.
 */
static int iterate_connections;

/**
 * Option -a.
 */
static int iterate_all;

/**
 * Option -c.
 */
static int monitor_connects;

/**
 * Option -m.
 */
static int monitor_connections;

/**
 * Option -P.
 */
static int monitor_plugins;

/**
 * Option -D.
 */
static int do_disconnect;

/**
 * Option -n.
 */
static int numeric;

/**
 * Global return value (0 success).
 */
static int ret;

/**
 * Current number of connections in monitor mode
 */
static int monitor_connect_counter;

/**
 * Number of bytes of traffic we received so far.
 */
static unsigned long long traffic_received;

/**
 * Number of bytes of traffic we sent so far.
 */
static unsigned long long traffic_sent;

/**
 * Starting time of transmitting/receiving data.
 */
static struct GNUNET_TIME_Absolute start_time;

/**
 * Map storing information about monitored peers
 */
static struct GNUNET_CONTAINER_MultiPeerMap *monitored_peers;

/**
 * Map storing information about monitored plugins's sessions.
 */
static struct GNUNET_CONTAINER_MultiPeerMap *monitored_plugins;

/**
 * Handle if we are monitoring peers at the transport level.
 */
static struct GNUNET_TRANSPORT_PeerMonitoringContext *pic;

/**
 * Handle if we are monitoring plugin session activity.
 */
static struct GNUNET_TRANSPORT_PluginMonitor *pm;

/**
 * Identity of the peer we transmit to / connect to.
 * ('-p' command-line option).
 */
static struct GNUNET_PeerIdentity pid;

/**
 * Task for operation timeout
 */
static struct GNUNET_SCHEDULER_Task *op_timeout;

/**
 * Selected level of verbosity.
 */
static unsigned int verbosity;

/**
 * Resolver process handle.
 */
struct GNUNET_OS_Process *resolver;

/**
 * Number of address resolutions pending
 */
static unsigned int address_resolutions;

/**
 * DLL: head of validation resolution entries
 */
static struct ValidationResolutionContext *vc_head;

/**
 * DLL: tail of validation resolution entries
 */
static struct ValidationResolutionContext *vc_tail;

/**
 * DLL: head of resolution entries
 */
static struct PeerResolutionContext *rc_head;

/**
 * DLL: head of resolution entries
 */
static struct PeerResolutionContext *rc_tail;


/**
 * Function called to release data stored in the #monitored_peers map.
 *
 * @param cls unused
 * @param key the peer identity
 * @param value a `struct MonitoredPeer` to release
 * @return #GNUNET_OK (continue to iterate)
 */
static int
destroy_it (void *cls, const struct GNUNET_PeerIdentity *key, void *value)
{
  struct MonitoredPeer *m = value;

  GNUNET_assert (
    GNUNET_OK ==
    GNUNET_CONTAINER_multipeermap_remove (monitored_peers, key, value));
  GNUNET_free (m->address);
  GNUNET_free (value);
  return GNUNET_OK;
}


/**
 * Task run in monitor mode when the user presses CTRL-C to abort.
 * Stops monitoring activity.
 *
 * @param cls NULL
 */
static void
shutdown_task (void *cls)
{
  struct GNUNET_TIME_Relative duration;
  struct ValidationResolutionContext *cur;
  struct ValidationResolutionContext *next;
  struct PeerResolutionContext *rc;

  if (NULL != op_timeout)
  {
    GNUNET_SCHEDULER_cancel (op_timeout);
    op_timeout = NULL;
  }
  if (NULL != pic)
  {
    GNUNET_TRANSPORT_monitor_peers_cancel (pic);
    pic = NULL;
  }
  if (NULL != pm)
  {
    GNUNET_TRANSPORT_monitor_plugins_cancel (pm);
    pm = NULL;
  }

  next = vc_head;
  for (cur = next; NULL != cur; cur = next)
  {
    next = cur->next;

    GNUNET_TRANSPORT_address_to_string_cancel (cur->asc);
    GNUNET_CONTAINER_DLL_remove (vc_head, vc_tail, cur);
    GNUNET_free (cur->transport);
    GNUNET_HELLO_address_free (cur->addrcp);
    GNUNET_free (cur);
  }
  while (NULL != (rc = rc_head))
  {
    GNUNET_CONTAINER_DLL_remove (rc_head, rc_tail, rc);
    GNUNET_TRANSPORT_address_to_string_cancel (rc->asc);
    GNUNET_free (rc->transport);
    GNUNET_free (rc->addrcp);
    GNUNET_free (rc);
  }
  if (NULL != handle)
  {
    GNUNET_TRANSPORT_core_disconnect (handle);
    handle = NULL;
  }
  if (benchmark_send)
  {
    duration = GNUNET_TIME_absolute_get_duration (start_time);
    fprintf (stdout,
             _ ("Transmitted %llu bytes/s (%llu bytes in %s)\n"),
             1000LL * 1000LL * traffic_sent / (1 + duration.rel_value_us),
             traffic_sent,
             GNUNET_STRINGS_relative_time_to_string (duration, GNUNET_YES));
  }
  if (benchmark_receive)
  {
    duration = GNUNET_TIME_absolute_get_duration (start_time);
    fprintf (stdout,
             _ ("Received %llu bytes/s (%llu bytes in %s)\n"),
             1000LL * 1000LL * traffic_received / (1 + duration.rel_value_us),
             traffic_received,
             GNUNET_STRINGS_relative_time_to_string (duration, GNUNET_YES));
  }

  if (NULL != monitored_peers)
  {
    GNUNET_CONTAINER_multipeermap_iterate (monitored_peers, &destroy_it, NULL);
    GNUNET_CONTAINER_multipeermap_destroy (monitored_peers);
    monitored_peers = NULL;
  }
  if (NULL != monitored_plugins)
  {
    GNUNET_break (0 == GNUNET_CONTAINER_multipeermap_size (monitored_plugins));
    GNUNET_CONTAINER_multipeermap_destroy (monitored_plugins);
    monitored_plugins = NULL;
  }
  if (NULL != blacklist)
  {
    GNUNET_TRANSPORT_blacklist_cancel (blacklist);
    blacklist = NULL;
    ret = 0;
  }
}


/**
 * We are done, shut down.
 */
static void
operation_timeout (void *cls)
{
  struct PeerResolutionContext *cur;
  struct PeerResolutionContext *next;

  op_timeout = NULL;
  if ((benchmark_send) || (benchmark_receive))
  {
    fprintf (stdout, _ ("Failed to connect to `%s'\n"), GNUNET_i2s_full (&pid));
    GNUNET_SCHEDULER_shutdown ();
    ret = 1;
    return;
  }
  if (iterate_connections)
  {
    next = rc_head;
    while (NULL != (cur = next))
    {
      next = cur->next;
      fprintf (stdout,
               _ ("Failed to resolve address for peer `%s'\n"),
               GNUNET_i2s (&cur->addrcp->peer));

      GNUNET_CONTAINER_DLL_remove (rc_head, rc_tail, cur);
      GNUNET_TRANSPORT_address_to_string_cancel (cur->asc);
      GNUNET_free (cur->transport);
      GNUNET_free (cur->addrcp);
      GNUNET_free (cur);
    }
    fprintf (stdout,
             "%s",
             _ ("Failed to list connections, timeout occurred\n"));
    GNUNET_SCHEDULER_shutdown ();
    ret = 1;
    return;
  }
}


/**
 * Function called to notify a client about the socket
 * begin ready to queue more data.  Sends another message.
 *
 * @param cls closure with the message queue
 */
static void
do_send (void *cls)
{
  struct GNUNET_MQ_Handle *mq = cls;
  struct GNUNET_MessageHeader *m;
  struct GNUNET_MQ_Envelope *env;

  env = GNUNET_MQ_msg_extra (m, BLOCKSIZE * 1024, GNUNET_MESSAGE_TYPE_DUMMY);
  memset (&m[1], 52, BLOCKSIZE * 1024 - sizeof(struct GNUNET_MessageHeader));
  traffic_sent += BLOCKSIZE * 1024;
  GNUNET_MQ_notify_sent (env, &do_send, mq);
  if (verbosity > 0)
    fprintf (stdout,
             _ ("Transmitting %u bytes\n"),
             (unsigned int) BLOCKSIZE * 1024);
  GNUNET_MQ_send (mq, env);
}


/**
 * Function called to notify transport users that another
 * peer connected to us.
 *
 * @param cls closure
 * @param peer the peer that connected
 * @param mq message queue for sending to @a peer
 */
static void *
notify_connect (void *cls,
                const struct GNUNET_PeerIdentity *peer,
                struct GNUNET_MQ_Handle *mq)
{
  if (0 != memcmp (&pid, peer, sizeof(struct GNUNET_PeerIdentity)))
    return NULL;
  ret = 0;
  if (! benchmark_send)
    return NULL;
  if (NULL != op_timeout)
  {
    GNUNET_SCHEDULER_cancel (op_timeout);
    op_timeout = NULL;
  }
  if (verbosity > 0)
    fprintf (
      stdout,
      _ (
        "Successfully connected to `%s', starting to send benchmark data in %u Kb blocks\n"),
      GNUNET_i2s (peer),
      BLOCKSIZE);
  start_time = GNUNET_TIME_absolute_get ();
  do_send (mq);
  return mq;
}


/**
 * Function called to notify transport users that another
 * peer disconnected from us.
 *
 * @param cls closure
 * @param peer the peer that disconnected
 * @param internal_cls what we returned from #notify_connect()
 */
static void
notify_disconnect (void *cls,
                   const struct GNUNET_PeerIdentity *peer,
                   void *internal_cls)
{
  if (0 != memcmp (&pid, peer, sizeof(struct GNUNET_PeerIdentity)))
    return;
  if (NULL == internal_cls)
    return; /* not about target peer */
  if (! benchmark_send)
    return; /* not transmitting */
  fprintf (stdout,
           _ ("Disconnected from peer `%s' while benchmarking\n"),
           GNUNET_i2s (&pid));
}


/**
 * Function called to notify transport users that another
 * peer connected to us.
 *
 * @param cls closure
 * @param peer the peer that connected
 * @param mq for sending messages to @a peer
 * @return NULL
 */
static void *
monitor_notify_connect (void *cls,
                        const struct GNUNET_PeerIdentity *peer,
                        struct GNUNET_MQ_Handle *mq)
{
  struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
  const char *now_str = GNUNET_STRINGS_absolute_time_to_string (now);

  monitor_connect_counter++;
  fprintf (stdout,
           _ ("%24s: %-17s %4s   (%u connections in total)\n"),
           now_str,
           _ ("Connected to"),
           GNUNET_i2s (peer),
           monitor_connect_counter);
  return NULL;
}


/**
 * Function called to notify transport users that another
 * peer disconnected from us.
 *
 * @param cls closure
 * @param peer the peer that disconnected
 * @param internal_cls what we returned from #monitor_notify_connect()
 */
static void
monitor_notify_disconnect (void *cls,
                           const struct GNUNET_PeerIdentity *peer,
                           void *internal_cls)
{
  struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
  const char *now_str = GNUNET_STRINGS_absolute_time_to_string (now);

  GNUNET_assert (monitor_connect_counter > 0);
  monitor_connect_counter--;

  fprintf (stdout,
           _ ("%24s: %-17s %4s   (%u connections in total)\n"),
           now_str,
           _ ("Disconnected from"),
           GNUNET_i2s (peer),
           monitor_connect_counter);
}


/**
 * Function called by the transport for each received message.
 *
 * @param cls closure
 * @param message the message
 * @return #GNUNET_OK
 */
static int
check_dummy (void *cls, const struct GNUNET_MessageHeader *message)
{
  return GNUNET_OK; /* all messages are fine */
}


/**
 * Function called by the transport for each received message.
 *
 * @param cls closure
 * @param message the message
 */
static void
handle_dummy (void *cls, const struct GNUNET_MessageHeader *message)
{
  if (! benchmark_receive)
    return;
  if (verbosity > 0)
    fprintf (stdout,
             _ ("Received %u bytes\n"),
             (unsigned int) ntohs (message->size));
  if (0 == traffic_received)
    start_time = GNUNET_TIME_absolute_get ();
  traffic_received += ntohs (message->size);
}


/**
 * Convert address to a printable format.
 *
 * @param address the address
 * @param numeric #GNUNET_YES to convert to numeric format, #GNUNET_NO
 *                to try to use reverse DNS
 * @param state state the peer is in
 * @param state_timeout when will the peer's state expire
 */
static void
resolve_peer_address (const struct GNUNET_HELLO_Address *address,
                      int numeric,
                      enum GNUNET_TRANSPORT_PeerState state,
                      struct GNUNET_TIME_Absolute state_timeout);


static void
print_info (const struct GNUNET_PeerIdentity *id,
            const char *transport,
            const char *addr,
            enum GNUNET_TRANSPORT_PeerState state,
            struct GNUNET_TIME_Absolute state_timeout)
{
  if (((GNUNET_YES == iterate_connections) && (GNUNET_YES == iterate_all)) ||
      (GNUNET_YES == monitor_connections))
  {
    fprintf (stdout,
             _ ("Peer `%s': %s %s in state `%s' until %s\n"),
             GNUNET_i2s (id),
             (NULL == transport) ? "<none>" : transport,
             (NULL == transport) ? "<none>" : addr,
             GNUNET_TRANSPORT_ps2s (state),
             GNUNET_STRINGS_absolute_time_to_string (state_timeout));
  }
  else if ((GNUNET_YES == iterate_connections) &&
           (GNUNET_TRANSPORT_is_connected (state)))
  {
    /* Only connected peers, skip state */
    fprintf (stdout,
             _ ("Peer `%s': %s %s\n"),
             GNUNET_i2s (id),
             transport,
             addr);
  }
}


/**
 * Function called with a textual representation of an address.  This
 * function will be called several times with different possible
 * textual representations, and a last time with @a address being NULL
 * to signal the end of the iteration.  Note that @a address NULL
 * always is the last call, regardless of the value in @a res.
 *
 * @param cls closure
 * @param address NULL on end of iteration,
 *        otherwise 0-terminated printable UTF-8 string,
 *        in particular an empty string if @a res is #GNUNET_NO
 * @param res result of the address to string conversion:
 *        if #GNUNET_OK: conversion successful
 *        if #GNUNET_NO: address was invalid (or not supported)
 *        if #GNUNET_SYSERR: communication error (IPC error)
 */
static void
process_peer_string (void *cls, const char *address, int res)
{
  struct PeerResolutionContext *rc = cls;

  if (NULL != address)
  {
    if (GNUNET_SYSERR == res)
    {
      fprintf (
        stderr,
        "Failed to convert address for peer `%s' plugin `%s' length %u to string \n",
        GNUNET_i2s (&rc->addrcp->peer),
        rc->addrcp->transport_name,
        (unsigned int) rc->addrcp->address_length);
      print_info (&rc->addrcp->peer,
                  rc->transport,
                  NULL,
                  rc->state,
                  rc->state_timeout);
      rc->printed = GNUNET_YES;
      return;
    }
    if (GNUNET_OK == res)
    {
      print_info (&rc->addrcp->peer,
                  rc->transport,
                  address,
                  rc->state,
                  rc->state_timeout);
      rc->printed = GNUNET_YES;
      return;     /* Wait for done call */
    }
    /* GNUNET_NO == res: ignore, was simply not supported */
    return;
  }
  /* NULL == address, last call, we are done */

  rc->asc = NULL;
  GNUNET_assert (address_resolutions > 0);
  address_resolutions--;
  if (GNUNET_NO == rc->printed)
  {
    if (numeric == GNUNET_NO)
    {
      /* Failed to resolve address, try numeric lookup
         (note: this should not be needed, as transport
         should fallback to numeric conversion if DNS takes
         too long) */
      resolve_peer_address (rc->addrcp,
                            GNUNET_YES,
                            rc->state,
                            rc->state_timeout);
    }
    else
    {
      print_info (&rc->addrcp->peer,
                  rc->transport,
                  NULL,
                  rc->state,
                  rc->state_timeout);
    }
  }
  GNUNET_free (rc->transport);
  GNUNET_free (rc->addrcp);
  GNUNET_CONTAINER_DLL_remove (rc_head, rc_tail, rc);
  GNUNET_free (rc);
  if ((0 == address_resolutions) && (iterate_connections))
  {
    if (NULL != op_timeout)
    {
      GNUNET_SCHEDULER_cancel (op_timeout);
      op_timeout = NULL;
    }
    ret = 0;
    GNUNET_SCHEDULER_shutdown ();
  }
}


/**
 * Convert address to a printable format and print it
 * together with the given state data.
 *
 * @param address the address
 * @param numeric #GNUNET_YES to convert to numeric format, #GNUNET_NO
 *                to try to use reverse DNS
 * @param state state the peer is in
 * @param state_timeout when will the peer's state expire
 */
static void
resolve_peer_address (const struct GNUNET_HELLO_Address *address,
                      int numeric,
                      enum GNUNET_TRANSPORT_PeerState state,
                      struct GNUNET_TIME_Absolute state_timeout)
{
  struct PeerResolutionContext *rc;

  rc = GNUNET_new (struct PeerResolutionContext);
  GNUNET_CONTAINER_DLL_insert (rc_head, rc_tail, rc);
  address_resolutions++;
  rc->transport = GNUNET_strdup (address->transport_name);
  rc->addrcp = GNUNET_HELLO_address_copy (address);
  rc->printed = GNUNET_NO;
  rc->state = state;
  rc->state_timeout = state_timeout;
  /* Resolve address to string */
  rc->asc = GNUNET_TRANSPORT_address_to_string (cfg,
                                                address,
                                                numeric,
                                                RESOLUTION_TIMEOUT,
                                                &process_peer_string,
                                                rc);
}


/**
 * Function called with information about a peers during a one shot iteration
 *
 * @param cls closure
 * @param peer identity of the peer, NULL for final callback when operation done
 * @param address binary address used to communicate with this peer,
 *  NULL on disconnect or when done
 * @param state current state this peer is in
 * @param state_timeout time out for the current state
 */
static void
process_peer_iteration_cb (void *cls,
                           const struct GNUNET_PeerIdentity *peer,
                           const struct GNUNET_HELLO_Address *address,
                           enum GNUNET_TRANSPORT_PeerState state,
                           struct GNUNET_TIME_Absolute state_timeout)
{
  if (NULL == peer)
  {
    /* done */
    pic = NULL;
    return;
  }

  if ((GNUNET_NO == iterate_all) &&
      (GNUNET_NO == GNUNET_TRANSPORT_is_connected (state)))
    return; /* Display only connected peers */

  if (NULL != op_timeout)
    GNUNET_SCHEDULER_cancel (op_timeout);
  op_timeout =
    GNUNET_SCHEDULER_add_delayed (OP_TIMEOUT, &operation_timeout, NULL);

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received address for peer `%s': %s\n",
              GNUNET_i2s (peer),
              address ? address->transport_name : "");

  if (NULL != address)
    resolve_peer_address (address, numeric, state, state_timeout);
  else
    print_info (peer, NULL, NULL, state, state_timeout);
}


/**
 * Context for address resolution by #plugin_monitoring_cb().
 */
struct PluginMonitorAddress
{
  /**
   * Ongoing resolution request.
   */
  struct GNUNET_TRANSPORT_AddressToStringContext *asc;

  /**
   * Resolved address as string.
   */
  char *str;

  /**
   * Last event we got and did not yet print because
   * @e str was NULL (address not yet resolved).
   */
  struct GNUNET_TRANSPORT_SessionInfo si;
};


/**
 * Print information about a plugin monitoring event.
 *
 * @param addr out internal context
 * @param info the monitoring information
 */
static void
print_plugin_event_info (struct PluginMonitorAddress *addr,
                         const struct GNUNET_TRANSPORT_SessionInfo *info)
{
  const char *state;

  switch (info->state)
  {
  case GNUNET_TRANSPORT_SS_INIT:
    state = "INIT";
    break;

  case GNUNET_TRANSPORT_SS_HANDSHAKE:
    state = "HANDSHAKE";
    break;

  case GNUNET_TRANSPORT_SS_UP:
    state = "UP";
    break;

  case GNUNET_TRANSPORT_SS_UPDATE:
    state = "UPDATE";
    break;

  case GNUNET_TRANSPORT_SS_DONE:
    state = "DONE";
    break;

  default:
    state = "UNKNOWN";
    break;
  }
  fprintf (stdout,
           "%s: state %s timeout in %s @ %s%s\n",
           GNUNET_i2s (&info->address->peer),
           state,
           GNUNET_STRINGS_relative_time_to_string (
             GNUNET_TIME_absolute_get_remaining (info->session_timeout),
             GNUNET_YES),
           addr->str,
           (info->is_inbound == GNUNET_YES) ? " (INBOUND)" : "");
  fprintf (stdout,
           "%s: queue has %3u messages and %6u bytes\n",
           GNUNET_i2s (&info->address->peer),
           info->num_msg_pending,
           info->num_bytes_pending);
  if (0 !=
      GNUNET_TIME_absolute_get_remaining (info->receive_delay).rel_value_us)
    fprintf (stdout,
             "%s: receiving blocked until %s\n",
             GNUNET_i2s (&info->address->peer),
             GNUNET_STRINGS_absolute_time_to_string (info->receive_delay));
}


/**
 * Function called with a textual representation of an address.  This
 * function will be called several times with different possible
 * textual representations, and a last time with @a address being NULL
 * to signal the end of the iteration.  Note that @a address NULL
 * always is the last call, regardless of the value in @a res.
 *
 * @param cls closure
 * @param address NULL on end of iteration,
 *        otherwise 0-terminated printable UTF-8 string,
 *        in particular an empty string if @a res is #GNUNET_NO
 * @param res result of the address to string conversion:
 *        if #GNUNET_OK: conversion successful
 *        if #GNUNET_NO: address was invalid (or not supported)
 *        if #GNUNET_SYSERR: communication error (IPC error)
 */
static void
address_cb (void *cls, const char *address, int res)
{
  struct PluginMonitorAddress *addr = cls;

  if (NULL == address)
  {
    addr->asc = NULL;
    return;
  }
  if (NULL != addr->str)
    return;
  addr->str = GNUNET_strdup (address);
  print_plugin_event_info (addr, &addr->si);
}


/**
 * Function called by the plugin with information about the
 * current sessions managed by the plugin (for monitoring).
 *
 * @param cls closure (NULL)
 * @param session session handle this information is about,
 *        NULL to indicate that we are "in sync" (initial
 *        iteration complete)
 * @param session_ctx storage location where the application
 *        can store data; will point to NULL on #GNUNET_TRANSPORT_SS_INIT,
 *        and must be reset to NULL on #GNUNET_TRANSPORT_SS_DONE
 * @param info information about the state of the session,
 *        NULL if @a session is also NULL and we are
 *        merely signalling that the initial iteration is over;
 *        NULL with @a session being non-NULL if the monitor
 *        was being cancelled while sessions were active
 */
static void
plugin_monitoring_cb (void *cls,
                      struct GNUNET_TRANSPORT_PluginSession *session,
                      void **session_ctx,
                      const struct GNUNET_TRANSPORT_SessionInfo *info)
{
  struct PluginMonitorAddress *addr;

  if ((NULL == info) && (NULL == session))
    return; /* in sync with transport service */
  addr = *session_ctx;
  if (NULL == info)
  {
    if (NULL != addr)
    {
      if (NULL != addr->asc)
      {
        GNUNET_TRANSPORT_address_to_string_cancel (addr->asc);
        addr->asc = NULL;
      }
      GNUNET_free (addr->str);
      GNUNET_free (addr);
      *session_ctx = NULL;
    }
    return;   /* shutdown */
  }
  if (0 !=
      memcmp (&info->address->peer, &pid, sizeof(struct GNUNET_PeerIdentity)))
    return; /* filtered */
  if (NULL == addr)
  {
    addr = GNUNET_new (struct PluginMonitorAddress);
    addr->asc =
      GNUNET_TRANSPORT_address_to_string (cfg,
                                          info->address,
                                          numeric,
                                          GNUNET_TIME_UNIT_FOREVER_REL,
                                          &address_cb,
                                          addr);
    *session_ctx = addr;
  }
  if (NULL == addr->str)
    addr->si = *info;
  else
    print_plugin_event_info (addr, info);
  if (GNUNET_TRANSPORT_SS_DONE == info->state)
  {
    if (NULL != addr->asc)
    {
      GNUNET_TRANSPORT_address_to_string_cancel (addr->asc);
      addr->asc = NULL;
    }
    GNUNET_free (addr->str);
    GNUNET_free (addr);
    *session_ctx = NULL;
  }
}


/**
 * Function called with information about a peers
 *
 * @param cls closure, NULL
 * @param peer identity of the peer, NULL for final callback when operation done
 * @param address binary address used to communicate with this peer,
 *  NULL on disconnect or when done
 * @param state current state this peer is in
 * @param state_timeout time out for the current state
 */
static void
process_peer_monitoring_cb (void *cls,
                            const struct GNUNET_PeerIdentity *peer,
                            const struct GNUNET_HELLO_Address *address,
                            enum GNUNET_TRANSPORT_PeerState state,
                            struct GNUNET_TIME_Absolute state_timeout)
{
  struct MonitoredPeer *m;

  if (NULL == peer)
  {
    fprintf (stdout,
             "%s",
             _ (
               "Monitor disconnected from transport service. Reconnecting.\n"));
    return;
  }

  if (NULL != op_timeout)
    GNUNET_SCHEDULER_cancel (op_timeout);
  op_timeout =
    GNUNET_SCHEDULER_add_delayed (OP_TIMEOUT, &operation_timeout, NULL);

  if (NULL == (m = GNUNET_CONTAINER_multipeermap_get (monitored_peers, peer)))
  {
    m = GNUNET_new (struct MonitoredPeer);
    GNUNET_CONTAINER_multipeermap_put (
      monitored_peers,
      peer,
      m,
      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST);
  }
  else
  {
    if ((m->state == state) &&
        (m->state_timeout.abs_value_us == state_timeout.abs_value_us) &&
        (NULL == address) && (NULL == m->address))
    {
      return;     /* No real change */
    }
    if ((m->state == state) && (NULL != address) && (NULL != m->address) &&
        (0 == GNUNET_HELLO_address_cmp (m->address, address)))
      return;   /* No real change */
  }

  if (NULL != m->address)
  {
    GNUNET_free (m->address);
    m->address = NULL;
  }
  if (NULL != address)
    m->address = GNUNET_HELLO_address_copy (address);
  m->state = state;
  m->state_timeout = state_timeout;

  if (NULL != address)
    resolve_peer_address (m->address, numeric, m->state, m->state_timeout);
  else
    print_info (peer, NULL, NULL, m->state, m->state_timeout);
}


/**
 * Function called with the transport service checking if we
 * want to blacklist a peer. Return #GNUNET_SYSERR for the
 * peer that we should disconnect from.
 *
 * @param cls NULL
 * @param cpid peer to check blacklisting for
 * @return #GNUNET_OK if the connection is allowed, #GNUNET_SYSERR if not
 */
static int
blacklist_cb (void *cls, const struct GNUNET_PeerIdentity *cpid)
{
  if (0 == memcmp (cpid, &pid, sizeof(struct GNUNET_PeerIdentity)))
    return GNUNET_SYSERR;
  return GNUNET_OK;
}


/**
 * Main function that will be run by the scheduler.
 *
 * @param cls closure
 * @param args remaining command-line arguments
 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
 * @param mycfg configuration
 */
static void
run (void *cls,
     char *const *args,
     const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *mycfg)
{
  static struct GNUNET_PeerIdentity zero_pid;
  int counter = 0;

  ret = 1;

  cfg = (struct GNUNET_CONFIGURATION_Handle *) mycfg;

  counter = benchmark_send + benchmark_receive + iterate_connections
            + monitor_connections + monitor_connects + do_disconnect
            + monitor_plugins;

  if (1 < counter)
  {
    fprintf (
      stderr,
      _ (
        "Multiple operations given. Please choose only one operation: %s, %s, %s, %s, %s, %s %s\n"),
      "disconnect",
      "benchmark send",
      "benchmark receive",
      "information",
      "monitor",
      "events",
      "plugins");
    return;
  }
  if (0 == counter)
  {
    fprintf (
      stderr,
      _ (
        "No operation given. Please choose one operation: %s, %s, %s, %s, %s, %s, %s\n"),
      "disconnect",
      "benchmark send",
      "benchmark receive",
      "information",
      "monitor",
      "events",
      "plugins");
    return;
  }

  if (do_disconnect) /* -D: Disconnect from peer */
  {
    if (0 == memcmp (&zero_pid, &pid, sizeof(pid)))
    {
      fprintf (stderr,
               _ ("Option `%s' makes no sense without option `%s'.\n"),
               "-D",
               "-p");
      ret = 1;
      return;
    }
    blacklist = GNUNET_TRANSPORT_blacklist (cfg, &blacklist_cb, NULL);
    if (NULL == blacklist)
    {
      fprintf (stderr,
               "%s",
               _ (
                 "Failed to connect to transport service for disconnection\n"));
      ret = 1;
      return;
    }
    fprintf (stdout,
             "%s",
             _ ("Blacklisting request in place, stop with CTRL-C\n"));
  }
  else if (benchmark_send) /* -s: Benchmark sending */
  {
    if (0 == memcmp (&zero_pid, &pid, sizeof(pid)))
    {
      fprintf (stderr,
               _ ("Option `%s' makes no sense without option `%s'.\n"),
               "-s",
               "-p");
      ret = 1;
      return;
    }
    handle = GNUNET_TRANSPORT_core_connect (cfg,
                                            NULL,
                                            NULL,
                                            NULL,
                                            &notify_connect,
                                            &notify_disconnect,
                                            NULL);
    if (NULL == handle)
    {
      fprintf (stderr, "%s", _ ("Failed to connect to transport service\n"));
      ret = 1;
      return;
    }
    start_time = GNUNET_TIME_absolute_get ();
    op_timeout =
      GNUNET_SCHEDULER_add_delayed (OP_TIMEOUT, &operation_timeout, NULL);
  }
  else if (benchmark_receive) /* -b: Benchmark receiving */
  {
    struct GNUNET_MQ_MessageHandler handlers[] =
    { GNUNET_MQ_hd_var_size (dummy,
                             GNUNET_MESSAGE_TYPE_DUMMY,
                             struct GNUNET_MessageHeader,
                             NULL),
      GNUNET_MQ_handler_end () };

    handle = GNUNET_TRANSPORT_core_connect (cfg,
                                            NULL,
                                            handlers,
                                            NULL,
                                            NULL,
                                            NULL,
                                            NULL);
    if (NULL == handle)
    {
      fprintf (stderr, "%s", _ ("Failed to connect to transport service\n"));
      ret = 1;
      return;
    }
    if (verbosity > 0)
      fprintf (stdout, "%s", _ ("Starting to receive benchmark data\n"));
    start_time = GNUNET_TIME_absolute_get ();
  }
  else if (iterate_connections) /* -i: List information about peers once */
  {
    pic = GNUNET_TRANSPORT_monitor_peers (cfg,
                                          &pid,
                                          GNUNET_YES,
                                          &process_peer_iteration_cb,
                                          (void *) cfg);
    op_timeout =
      GNUNET_SCHEDULER_add_delayed (OP_TIMEOUT, &operation_timeout, NULL);
  }
  else if (monitor_connections) /* -m: List information about peers continuously
                                 */
  {
    monitored_peers = GNUNET_CONTAINER_multipeermap_create (10, GNUNET_NO);
    pic = GNUNET_TRANSPORT_monitor_peers (cfg,
                                          &pid,
                                          GNUNET_NO,
                                          &process_peer_monitoring_cb,
                                          NULL);
  }
  else if (monitor_plugins) /* -P: List information about plugins continuously
                             */
  {
    monitored_plugins = GNUNET_CONTAINER_multipeermap_create (10, GNUNET_NO);
    pm = GNUNET_TRANSPORT_monitor_plugins (cfg, &plugin_monitoring_cb, NULL);
  }
  else if (monitor_connects) /* -e : Monitor (dis)connect events continuously */
  {
    monitor_connect_counter = 0;
    handle = GNUNET_TRANSPORT_core_connect (cfg,
                                            NULL,
                                            NULL,
                                            NULL,
                                            &monitor_notify_connect,
                                            &monitor_notify_disconnect,
                                            NULL);
    if (NULL == handle)
    {
      fprintf (stderr, "%s", _ ("Failed to connect to transport service\n"));
      ret = 1;
      return;
    }
    ret = 0;
  }
  else
  {
    GNUNET_break (0);
    return;
  }

  GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
}


int
main (int argc, char *const *argv)
{
  int res;
  struct GNUNET_GETOPT_CommandLineOption options[] =
  { GNUNET_GETOPT_option_flag (
      'a',
      "all",
      gettext_noop (
        "print information for all peers (instead of only connected peers)"),
      &iterate_all),
    GNUNET_GETOPT_option_flag (
      'b',
      "benchmark",
      gettext_noop (
        "measure how fast we are receiving data from all peers (until CTRL-C)"),
      &benchmark_receive),
    GNUNET_GETOPT_option_flag ('D',
                               "disconnect",
                               gettext_noop ("disconnect from a peer"),
                               &do_disconnect),
    GNUNET_GETOPT_option_flag (
      'i',
      "information",
      gettext_noop (
        "provide information about all current connections (once)"),
      &iterate_connections),
    GNUNET_GETOPT_option_flag (
      'm',
      "monitor",
      gettext_noop (
        "provide information about all current connections (continuously)"),
      &monitor_connections),
    GNUNET_GETOPT_option_flag (
      'e',
      "events",
      gettext_noop (
        "provide information about all connects and disconnect events (continuously)"),
      &monitor_connects),
    GNUNET_GETOPT_option_flag ('n',
                               "numeric",
                               gettext_noop ("do not resolve hostnames"),
                               &numeric),
    GNUNET_GETOPT_option_base32_auto ('p',
                                      "peer",
                                      "PEER",
                                      gettext_noop ("peer identity"),
                                      &pid),
    GNUNET_GETOPT_option_flag ('P',
                               "plugins",
                               gettext_noop ("monitor plugin sessions"),
                               &monitor_plugins),
    GNUNET_GETOPT_option_flag (
      's',
      "send",
      gettext_noop (
        "send data for benchmarking to the other peer (until CTRL-C)"),
      &benchmark_send),
    GNUNET_GETOPT_option_verbose (&verbosity),
    GNUNET_GETOPT_OPTION_END };

  res =
    GNUNET_PROGRAM_run (argc,
                        argv,
                        "gnunet-transport",
                        gettext_noop ("Direct access to transport service."),
                        options,
                        &run,
                        NULL);
  if (GNUNET_OK == res)
    return ret;
  return 1;
}


/* end of gnunet-transport.c */
