/**
 * Force plugin to terminate session due to communication
 * issue.
 *
 * @param plugin_name name of the plugin
 * @param session session to termiante
 */
static void
kill_session (const char *plugin_name,
              struct GNUNET_ATS_Session *session)
{
  struct GNUNET_TRANSPORT_PluginFunctions *plugin;
  struct GNUNET_ATS_SessionKiller *sk;

  for (sk = sk_head; NULL != sk; sk = sk->next)
    if (sk->session == session)
      return;
  plugin = GST_plugins_find (plugin_name);
  if (NULL == plugin)
  {
    GNUNET_break(0);
    return;
  }
  /* need to issue disconnect asynchronously */
  sk = GNUNET_new (struct GNUNET_ATS_SessionKiller);
  sk->session = session;
  sk->plugin = plugin;
  sk->task = GNUNET_SCHEDULER_add_now (&kill_session_task, sk);
  GNUNET_CONTAINER_DLL_insert (sk_head,
                               sk_tail,
                               sk);
}
/**
 * Send the given PONG to the given address.
 *
 * @param cls the PONG message
 * @param public_key public key for the peer, never NULL
 * @param valid_until is ZERO if we never validated the address,
 *                    otherwise a time up to when we consider it (or was) valid
 * @param validation_block  is FOREVER if the address is for an unsupported plugin (from PEERINFO)
 *                          is ZERO if the address is considered valid (no validation needed)
 *                          otherwise a time in the future if we're currently denying re-validation
 * @param address target address
 */
static void
multicast_pong (void *cls,
                const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded
                *public_key, struct GNUNET_TIME_Absolute valid_until,
                struct GNUNET_TIME_Absolute validation_block,
                const struct GNUNET_HELLO_Address *address)
{
  struct TransportPongMessage *pong = cls;
  struct GNUNET_TRANSPORT_PluginFunctions *papi;

  papi = GST_plugins_find (address->transport_name);
  if (papi == NULL)
    return;

  GNUNET_assert (papi->send != NULL);
  GNUNET_assert (papi->get_session != NULL);

  struct Session * session = papi->get_session(papi->cls, address);
  if (session == NULL)
  {
     GNUNET_break (0);
     return;
  }

  papi->send (papi->cls, session,
              (const char *) pong, ntohs (pong->header.size),
              PONG_PRIORITY, ACCEPTABLE_PING_DELAY,
              NULL, NULL);
}
/**
 * Convert a given address to a human-readable format.  Note that the
 * return value will be overwritten on the next call to this function.
 *
 * @param address the address to convert
 * @return statically allocated (!) human-readable address
 */
const char *
GST_plugins_a2s (const struct GNUNET_HELLO_Address *address)
{
    struct GNUNET_TRANSPORT_PluginFunctions *api;
    static char unable_to_show[1024];

    if (address == NULL)
        return "<inbound>";
    api = GST_plugins_find (address->transport_name);
    if (NULL == api)
        return "<plugin unknown>";
    if (0 == address->address_length)
    {
        GNUNET_snprintf (unable_to_show, sizeof (unable_to_show),
                         "<unable to stringify %u-byte long address of %s transport>",
                         (unsigned int) address->address_length,
                         address->transport_name);
        return unable_to_show;
    }
    return api->address_to_string (NULL, address->address,
                                   address->address_length);
}
/**
 * We've received a PING.  If appropriate, generate a PONG.
 *
 * @param sender peer sending the PING
 * @param hdr the PING
 * @param sender_address the sender address as we got it
 * @param session session we got the PING from
 */
void
GST_validation_handle_ping (const struct GNUNET_PeerIdentity *sender,
                            const struct GNUNET_MessageHeader *hdr,
                            const struct GNUNET_HELLO_Address *sender_address,
                            struct Session *session)
{
  const struct TransportPingMessage *ping;
  struct TransportPongMessage *pong;
  struct GNUNET_TRANSPORT_PluginFunctions *papi;
  struct GNUNET_CRYPTO_RsaSignature *sig_cache;
  struct GNUNET_TIME_Absolute *sig_cache_exp;
  const char *addr;
  const char *addrend;
  size_t alen;
  size_t slen;
  ssize_t ret;
  struct GNUNET_HELLO_Address address;

  if (ntohs (hdr->size) < sizeof (struct TransportPingMessage))
  {
    GNUNET_break_op (0);
    return;
  }
  ping = (const struct TransportPingMessage *) hdr;
  if (0 !=
      memcmp (&ping->target, &GST_my_identity,
              sizeof (struct GNUNET_PeerIdentity)))
  {
    GNUNET_STATISTICS_update (GST_stats,
                              gettext_noop
                              ("# PING message for different peer received"), 1,
                              GNUNET_NO);
    return;
  }
  GNUNET_STATISTICS_update (GST_stats,
                            gettext_noop ("# PING messages received"), 1,
                            GNUNET_NO);
  addr = (const char *) &ping[1];
  alen = ntohs (hdr->size) - sizeof (struct TransportPingMessage);
  /* peer wants to confirm that this is one of our addresses, this is what is
   * used for address validation */

  sig_cache = NULL;
  sig_cache_exp = NULL;

  if (0 < alen)
  {
    addrend = memchr (addr, '\0', alen);
    if (NULL == addrend)
    {
      GNUNET_break_op (0);
      return;
    }
    addrend++;
    slen = strlen (addr) + 1;
    alen -= slen;
    address.address = addrend;
    address.address_length = alen;
    address.transport_name = addr;
    address.peer = *sender;
    if (GNUNET_YES !=
        GST_hello_test_address (&address, &sig_cache, &sig_cache_exp))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  _
                  ("Not confirming PING with address `%s' since I cannot confirm having this address.\n"),
                  GST_plugins_a2s (&address));
      return;
    }
  }
  else
  {
    addrend = NULL;             /* make gcc happy */
    slen = 0;
    static struct GNUNET_CRYPTO_RsaSignature no_address_signature;
    static struct GNUNET_TIME_Absolute no_address_signature_expiration;

    sig_cache = &no_address_signature;
    sig_cache_exp = &no_address_signature_expiration;
  }

  pong = GNUNET_malloc (sizeof (struct TransportPongMessage) + alen + slen);
  pong->header.size =
      htons (sizeof (struct TransportPongMessage) + alen + slen);
  pong->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_PONG);
  pong->purpose.size =
      htonl (sizeof (struct GNUNET_CRYPTO_RsaSignaturePurpose) +
             sizeof (uint32_t) + sizeof (struct GNUNET_TIME_AbsoluteNBO) +
             alen + slen);
  pong->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_TRANSPORT_PONG_OWN);
  pong->challenge = ping->challenge;
  pong->addrlen = htonl (alen + slen);
  memcpy (&pong[1], addr, slen);
  memcpy (&((char *) &pong[1])[slen], addrend, alen);
  if (GNUNET_TIME_absolute_get_remaining (*sig_cache_exp).rel_value <
      PONG_SIGNATURE_LIFETIME.rel_value / 4)
  {
    /* create / update cached sig */
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Creating PONG signature to indicate ownership.\n");
    *sig_cache_exp = GNUNET_TIME_relative_to_absolute (PONG_SIGNATURE_LIFETIME);
    pong->expiration = GNUNET_TIME_absolute_hton (*sig_cache_exp);
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_CRYPTO_rsa_sign (GST_my_private_key, &pong->purpose,
                                           sig_cache));
  }
  else
  {
    pong->expiration = GNUNET_TIME_absolute_hton (*sig_cache_exp);
  }
  pong->signature = *sig_cache;

  GNUNET_assert (sender_address != NULL);

  /* first see if the session we got this PING from can be used to transmit
   * a response reliably */
  papi = GST_plugins_find (sender_address->transport_name);
  if (papi == NULL)
    ret = -1;
  else
  {
    GNUNET_assert (papi->send != NULL);
    GNUNET_assert (papi->get_session != NULL);

    if (session == NULL)
    {
      session = papi->get_session (papi->cls, sender_address);
    }
    if (session == NULL)
    {
      GNUNET_break (0);
      ret = -1;
    }
    else
    {
      ret = papi->send (papi->cls, session,
                        (const char *) pong, ntohs (pong->header.size),
                        PONG_PRIORITY, ACCEPTABLE_PING_DELAY,
                        NULL, NULL);
    }
  }
  if (ret != -1)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Transmitted PONG to `%s' via reliable mechanism\n",
                GNUNET_i2s (sender));
    /* done! */
    GNUNET_STATISTICS_update (GST_stats,
                              gettext_noop
                              ("# PONGs unicast via reliable transport"), 1,
                              GNUNET_NO);
    GNUNET_free (pong);
    return;
  }

  /* no reliable method found, try transmission via all known addresses */
  GNUNET_STATISTICS_update (GST_stats,
                            gettext_noop
                            ("# PONGs multicast to all available addresses"), 1,
                            GNUNET_NO);
  GST_validation_get_addresses (sender, &multicast_pong, pong);
  GNUNET_free (pong);
}
/**
 * Function called with the result from blacklisting.
 * Send a PING to the other peer if a communication is allowed.
 *
 * @param cls our 'struct ValidationEntry'
 * @param pid identity of the other peer
 * @param result GNUNET_OK if the connection is allowed, GNUNET_NO if not
 */
static void
transmit_ping_if_allowed (void *cls, const struct GNUNET_PeerIdentity *pid,
                          int result)
{
  struct ValidationEntry *ve = cls;
  struct TransportPingMessage ping;
  struct GNUNET_TRANSPORT_PluginFunctions *papi;
  const struct GNUNET_MessageHeader *hello;
  ssize_t ret;
  size_t tsize;
  size_t slen;
  uint16_t hsize;

  ve->bc = NULL;
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Transmitting plain PING to `%s' %s\n",
              GNUNET_i2s (pid), GST_plugins_a2s (ve->address));

  slen = strlen (ve->address->transport_name) + 1;
  hello = GST_hello_get ();
  hsize = ntohs (hello->size);
  tsize =
      sizeof (struct TransportPingMessage) + ve->address->address_length +
      slen + hsize;

  ping.header.size =
      htons (sizeof (struct TransportPingMessage) +
             ve->address->address_length + slen);
  ping.header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_PING);
  ping.challenge = htonl (ve->challenge);
  ping.target = *pid;

  if (tsize >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _
                ("Not transmitting `%s' with `%s', message too big (%u bytes!). This should not happen.\n"),
                "HELLO", "PING", (unsigned int) tsize);
    /* message too big (!?), get rid of HELLO */
    hsize = 0;
    tsize =
        sizeof (struct TransportPingMessage) + ve->address->address_length +
        slen + hsize;
  }
  {
    char message_buf[tsize];

    /* build message with structure:
     *  [HELLO][TransportPingMessage][Transport name][Address] */
    memcpy (message_buf, hello, hsize);
    memcpy (&message_buf[hsize], &ping, sizeof (struct TransportPingMessage));
    memcpy (&message_buf[sizeof (struct TransportPingMessage) + hsize],
            ve->address->transport_name, slen);
    memcpy (&message_buf[sizeof (struct TransportPingMessage) + slen + hsize],
            ve->address, ve->address->address_length);
    papi = GST_plugins_find (ve->address->transport_name);
    if (papi == NULL)
      ret = -1;
    else
    {
      GNUNET_assert (papi->send != NULL);
      GNUNET_assert (papi->get_session != NULL);
      struct Session * session = papi->get_session(papi->cls, ve->address);

      if (session != NULL)
      {
        ret = papi->send (papi->cls, session,
                          message_buf, tsize,
                          PING_PRIORITY, ACCEPTABLE_PING_DELAY,
                          NULL, NULL);
      }
      else
      {
        /* Could not get a valid session */
        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Could not get a valid session for `%s' %s\n",
                    GNUNET_i2s (pid), GST_plugins_a2s (ve->address));
        ret = -1;
      }
    }
  }
  if (-1 != ret)
  {
    ve->send_time = GNUNET_TIME_absolute_get ();
    GNUNET_STATISTICS_update (GST_stats,
                              gettext_noop
                              ("# PING without HELLO messages sent"), 1,
                              GNUNET_NO);
    ve->expecting_pong = GNUNET_YES;
  }
}