/*
     This file is part of GNUnet.
     Copyright (C) 2009-2014, 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/transport_api_address_to_string.c
 * @author Christian Grothoff
 * @brief enable clients to convert addresses to human readable strings
 */
#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet_arm_service.h"
#include "gnunet_hello_lib.h"
#include "gnunet_protocols.h"
#include "gnunet_transport_service.h"
#include "transport.h"

/**
 * Context for the address lookup.
 */
struct GNUNET_TRANSPORT_AddressToStringContext
{
  /**
   * Function to call with the human-readable address.
   */
  GNUNET_TRANSPORT_AddressToStringCallback cb;

  /**
   * Closure for @e cb.
   */
  void *cb_cls;

  /**
   * Connection to the service.
   */
  struct GNUNET_MQ_Handle *mq;
};


/**
 * Function called with responses from the service.
 *
 * @param cls our `struct GNUNET_TRANSPORT_AddressToStringContext *`
 * @param msg message with the human-readable address
 * @return #GNUNET_OK if message is well-formed
 */
static int
check_reply (void *cls,
             const struct AddressToStringResultMessage *atsm)
{
  uint16_t size = ntohs (atsm->header.size) - sizeof(*atsm);
  const char *address;
  int result;
  uint32_t addr_len;

  result = (int) ntohl (atsm->res);
  addr_len = ntohl (atsm->addr_len);
  if (GNUNET_SYSERR == result)
    return GNUNET_OK;
  if (0 == size)
  {
    if (GNUNET_OK != result)
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
    return GNUNET_OK;
  }
  address = (const char *) &atsm[1];
  if ((addr_len > size) ||
      (address[addr_len - 1] != '\0'))
  {
    /* invalid reply */
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Function called with responses from the service.
 *
 * @param cls our `struct GNUNET_TRANSPORT_AddressToStringContext *`
 * @param msg message with the human-readable address
 */
static void
handle_reply (void *cls,
              const struct AddressToStringResultMessage *atsm)
{
  struct GNUNET_TRANSPORT_AddressToStringContext *alucb = cls;
  uint16_t size = ntohs (atsm->header.size) - sizeof(*atsm);
  const char *address;
  int result;

  result = (int) ntohl (atsm->res);
  if (GNUNET_SYSERR == result)
  {
    /* expect more replies; as this is not the last
       call, we must pass the empty string for the address */
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Address resolution failed\n");
    alucb->cb (alucb->cb_cls,
               "",
               GNUNET_NO);
    return;
  }
  if (0 == size)
  {
    /* we are done (successfully, without communication errors) */
    alucb->cb (alucb->cb_cls,
               NULL,
               GNUNET_OK);
    GNUNET_TRANSPORT_address_to_string_cancel (alucb);
    return;
  }
  address = (const char *) &atsm[1];
  /* return normal reply to caller, also expect more replies */
  alucb->cb (alucb->cb_cls,
             address,
             GNUNET_OK);
}


/**
 * Generic error handler, called with the appropriate
 * error code and the same closure specified at the creation of
 * the message queue.
 * Not every message queue implementation supports an error handler.
 *
 * @param cls the `struct GNUNET_TRANSPORT_AddressToStringContext *`
 * @param error error code
 */
static void
mq_error_handler (void *cls,
                  enum GNUNET_MQ_Error error)
{
  struct GNUNET_TRANSPORT_AddressToStringContext *alucb = cls;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Disconnected from transport, address resolution failed\n");
  alucb->cb (alucb->cb_cls,
             NULL,
             GNUNET_SYSERR);
  GNUNET_TRANSPORT_address_to_string_cancel (alucb);
}


/**
 * Convert a binary address into a human readable address.
 *
 * @param cfg configuration to use
 * @param address address to convert (binary format)
 * @param numeric should (IP) addresses be displayed in numeric form
 *                (otherwise do reverse DNS lookup)
 * @param timeout how long is the lookup allowed to take at most
 * @param aluc function to call with the results
 * @param aluc_cls closure for @a aluc
 * @return handle to cancel the operation, NULL on error
 */
struct GNUNET_TRANSPORT_AddressToStringContext *
GNUNET_TRANSPORT_address_to_string (const struct
                                    GNUNET_CONFIGURATION_Handle *cfg,
                                    const struct GNUNET_HELLO_Address *address,
                                    int numeric,
                                    struct GNUNET_TIME_Relative timeout,
                                    GNUNET_TRANSPORT_AddressToStringCallback
                                    aluc,
                                    void *aluc_cls)
{
  struct GNUNET_TRANSPORT_AddressToStringContext *alc
    = GNUNET_new (struct GNUNET_TRANSPORT_AddressToStringContext);
  struct GNUNET_MQ_MessageHandler handlers[] = {
    GNUNET_MQ_hd_var_size (reply,
                           GNUNET_MESSAGE_TYPE_TRANSPORT_ADDRESS_TO_STRING_REPLY,
                           struct AddressToStringResultMessage,
                           alc),
    GNUNET_MQ_handler_end ()
  };
  size_t alen;
  size_t slen;
  struct AddressLookupMessage *msg;
  struct GNUNET_MQ_Envelope *env;
  char *addrbuf;

  alen = address->address_length;
  slen = strlen (address->transport_name) + 1;
  if ((alen + slen >= GNUNET_MAX_MESSAGE_SIZE
       - sizeof(struct AddressLookupMessage)) ||
      (alen >= GNUNET_MAX_MESSAGE_SIZE) ||
      (slen >= GNUNET_MAX_MESSAGE_SIZE))
  {
    GNUNET_break (0);
    GNUNET_free (alc);
    return NULL;
  }
  alc->cb = aluc;
  alc->cb_cls = aluc_cls;
  alc->mq = GNUNET_CLIENT_connect (cfg,
                                   "transport",
                                   handlers,
                                   &mq_error_handler,
                                   alc);
  if (NULL == alc->mq)
  {
    GNUNET_break (0);
    GNUNET_free (alc);
    return NULL;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Client tries to resolve for peer `%s' address plugin %s len %u\n",
              GNUNET_i2s (&address->peer),
              address->transport_name,
              (unsigned int) address->address_length);
  env = GNUNET_MQ_msg_extra (msg,
                             alen + slen,
                             GNUNET_MESSAGE_TYPE_TRANSPORT_ADDRESS_TO_STRING);
  msg->numeric_only = htons ((int16_t) numeric);
  msg->addrlen = htons ((uint16_t) alen);
  msg->timeout = GNUNET_TIME_relative_hton (timeout);
  addrbuf = (char *) &msg[1];
  GNUNET_memcpy (addrbuf,
                 address->address,
                 alen);
  GNUNET_memcpy (&addrbuf[alen],
                 address->transport_name,
                 slen);
  GNUNET_MQ_send (alc->mq,
                  env);
  return alc;
}


/**
 * Cancel request for address conversion.
 *
 * @param alc the context handle
 */
void
GNUNET_TRANSPORT_address_to_string_cancel (struct
                                           GNUNET_TRANSPORT_AddressToStringContext
                                           *alc)
{
  GNUNET_MQ_destroy (alc->mq);
  GNUNET_free (alc);
}


/* end of transport_api_address_to_string.c */
