/*
     This file is part of GNUnet.
     Copyright (C) 2012, 2013 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 namecache/gnunet-service-namecache.c
 * @brief namecache for the GNUnet naming system
 * @author Matthias Wachs
 * @author Christian Grothoff
 */
#include "gnunet_util_lib.h"
#include "gnunet_statistics_service.h"
#include "gnunet_namecache_plugin.h"
#include "namecache.h"

#define LOG_STRERROR_FILE(kind, syscall, \
                          filename) GNUNET_log_from_strerror_file (kind, "util", \
                                                                   syscall, \
                                                                   filename)


/**
 * A namecache client
 */
struct NamecacheClient
{
  /**
   * The client
   */
  struct GNUNET_SERVICE_Client *client;

  /**
   * The message queue to talk to @e client.
   */
  struct GNUNET_MQ_Handle *mq;
};


/**
 * Configuration handle.
 */
static const struct GNUNET_CONFIGURATION_Handle *GSN_cfg;

/**
 * Handle to the statistics service
 */
static struct GNUNET_STATISTICS_Handle *statistics;

/**
 * Database handle
 */
static struct GNUNET_NAMECACHE_PluginFunctions *GSN_database;

/**
 * Name of the database plugin
 */
static char *db_lib_name;


/**
 * Task run during shutdown.
 *
 * @param cls unused
 */
static void
cleanup_task (void *cls)
{
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Stopping namecache service\n");
  GNUNET_break (NULL ==
                GNUNET_PLUGIN_unload (db_lib_name,
                                      GSN_database));
  GNUNET_free (db_lib_name);
  db_lib_name = NULL;
  if (NULL != statistics)
  {
    GNUNET_STATISTICS_destroy (statistics,
                               GNUNET_NO);
    statistics = NULL;
  }
}


/**
 * Called whenever a client is disconnected.
 * Frees our resources associated with that client.
 *
 * @param cls closure
 * @param client identification of the client
 * @param app_ctx the `struct NamecacheClient` for this @a client
 */
static void
client_disconnect_cb (void *cls,
                      struct GNUNET_SERVICE_Client *client,
                      void *app_ctx)
{
  struct NamecacheClient *nc = app_ctx;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Client %p disconnected\n",
              client);
  GNUNET_free (nc);
}


/**
 * Add a client to our list of active clients.
 *
 * @param cls NULL
 * @param client client to add
 * @param mq queue to talk to @a client
 * @return internal namecache client structure for this client
 */
static void *
client_connect_cb (void *cls,
                   struct GNUNET_SERVICE_Client *client,
                   struct GNUNET_MQ_Handle *mq)
{
  struct NamecacheClient *nc;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Client %p connected\n",
              client);
  nc = GNUNET_new (struct NamecacheClient);
  nc->client = client;
  nc->mq = mq;
  return nc;
}


/**
 * Context for name lookups passed from #handle_lookup_block to
 * #handle_lookup_block_it as closure
 */
struct LookupBlockContext
{
  /**
   * The client to send the response to
   */
  struct NamecacheClient *nc;

  /**
   * Operation id for the name lookup
   */
  uint32_t request_id;

  /**
   * Lookup status
   */
  int status;
};


/**
 * A #GNUNET_NAMECACHE_BlockCallback for name lookups in #handle_lookup_block
 *
 * @param cls a `struct LookupNameContext *` with information about the request
 * @param block the block
 */
static void
handle_lookup_block_it (void *cls,
                        const struct GNUNET_GNSRECORD_Block *block)
{
  struct LookupBlockContext *lnc = cls;
  struct GNUNET_MQ_Envelope *env;
  struct LookupBlockResponseMessage *r;
  size_t bsize;

  bsize = GNUNET_GNSRECORD_block_get_size (block);
  env = GNUNET_MQ_msg_extra (r,
                             bsize,
                             GNUNET_MESSAGE_TYPE_NAMECACHE_LOOKUP_BLOCK_RESPONSE);
  r->gns_header.r_id = htonl (lnc->request_id);
  GNUNET_memcpy (&r[1],
                 block,
                 bsize);
  GNUNET_STATISTICS_update (statistics,
                            "blocks found in cache",
                            1,
                            GNUNET_NO);
  r->expire = GNUNET_TIME_absolute_hton (
    GNUNET_GNSRECORD_block_get_expiration (block));
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Sending NAMECACHE_LOOKUP_BLOCK_RESPONSE message\n");
  GNUNET_MQ_send (lnc->nc->mq,
                  env);
}


/**
 * Handles a #GNUNET_MESSAGE_TYPE_NAMECACHE_LOOKUP_BLOCK message
 *
 * @param cls a `struct NamecacheClient *`
 * @param ln_msg the inbound message
 */
static void
handle_lookup_block (void *cls,
                     const struct LookupBlockMessage *ln_msg)
{
  struct NamecacheClient *nc = cls;
  struct GNUNET_MQ_Envelope *env;
  struct LookupBlockContext lnc;
  struct LookupBlockResponseMessage *zir_end;
  int ret;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received NAMECACHE_LOOKUP_BLOCK message\n");
  GNUNET_STATISTICS_update (statistics,
                            "blocks looked up",
                            1,
                            GNUNET_NO);
  lnc.request_id = ntohl (ln_msg->gns_header.r_id);
  lnc.nc = nc;
  lnc.status = GNUNET_OK;
  if (GNUNET_SYSERR ==
      (ret = GSN_database->lookup_block (GSN_database->cls,
                                         &ln_msg->query,
                                         &handle_lookup_block_it,
                                         &lnc)))
  {
    /* internal error (in database plugin); might be best to just hang up on
       plugin rather than to signal that there are 'no' results, which
       might also be false... */
    GNUNET_break (0);
    GNUNET_SERVICE_client_drop (nc->client);
    return;
  }
  if ((0 == ret) || (GNUNET_SYSERR == lnc.status))
  {
    /* no records match at all, generate empty response */
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Sending empty NAMECACHE_LOOKUP_BLOCK_RESPONSE message\n");
    env = GNUNET_MQ_msg (zir_end,
                         GNUNET_MESSAGE_TYPE_NAMECACHE_LOOKUP_BLOCK_RESPONSE);
    zir_end->gns_header.r_id = ln_msg->gns_header.r_id;
    GNUNET_MQ_send (nc->mq,
                    env);
  }
  GNUNET_SERVICE_client_continue (nc->client);
}


/**
 * Check a #GNUNET_MESSAGE_TYPE_NAMECACHE_BLOCK_CACHE message
 *
 * @param cls our `struct NamecacheClient`
 * @param rp_msg message to process
 * @return #GNUNET_OK (always fine)
 */
static int
check_block_cache (void *cls,
                   const struct BlockCacheMessage *rp_msg)
{
  return GNUNET_OK;
}


/**
 * Handles a #GNUNET_MESSAGE_TYPE_NAMECACHE_BLOCK_CACHE message
 *
 * @param cls our `struct NamecacheClient`
 * @param rp_msg message to process
 */
static void
handle_block_cache (void *cls,
                    const struct BlockCacheMessage *rp_msg)
{
  struct NamecacheClient *nc = cls;
  struct GNUNET_MQ_Envelope *env;
  struct BlockCacheResponseMessage *rpr_msg;
  struct GNUNET_GNSRECORD_Block *block;
  size_t esize;
  int res;

  GNUNET_STATISTICS_update (statistics,
                            "blocks cached",
                            1,
                            GNUNET_NO);
  esize = ntohs (rp_msg->gns_header.header.size) - sizeof(struct
                                                          BlockCacheMessage);
  block = GNUNET_malloc (esize);
  memcpy (block, &rp_msg[1], esize);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received NAMECACHE_BLOCK_CACHE message with type %u\n",
              htonl (block->type));
  res = GSN_database->cache_block (GSN_database->cls,
                                   block);
  GNUNET_free (block);
  env = GNUNET_MQ_msg (rpr_msg,
                       GNUNET_MESSAGE_TYPE_NAMECACHE_BLOCK_CACHE_RESPONSE);
  rpr_msg->gns_header.r_id = rp_msg->gns_header.r_id;
  rpr_msg->op_result = htonl (res);
  GNUNET_MQ_send (nc->mq,
                  env);
  GNUNET_SERVICE_client_continue (nc->client);
}


/**
 * Process namecache requests.
 *
 * @param cls closure
 * @param cfg configuration to use
 * @param service the initialized service
 */
static void
run (void *cls,
     const struct GNUNET_CONFIGURATION_Handle *cfg,
     struct GNUNET_SERVICE_Handle *service)
{
  char *database;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Starting namecache service\n");
  GSN_cfg = cfg;

  /* Loading database plugin */
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (cfg,
                                             "namecache",
                                             "database",
                                             &database))
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "No database backend configured\n");

  GNUNET_asprintf (&db_lib_name,
                   "libgnunet_plugin_namecache_%s",
                   database);
  GSN_database = GNUNET_PLUGIN_load (GNUNET_OS_project_data_gnunet (),
                                     db_lib_name,
                                     (void *) GSN_cfg);
  GNUNET_free (database);
  if (NULL == GSN_database)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Could not load database backend `%s'\n",
                db_lib_name);
    GNUNET_SCHEDULER_add_now (&cleanup_task,
                              NULL);
    return;
  }
  statistics = GNUNET_STATISTICS_create ("namecache",
                                         cfg);

  /* Configuring server handles */
  GNUNET_SCHEDULER_add_shutdown (&cleanup_task,
                                 NULL);
}


/**
 * Define "main" method using service macro.
 */
GNUNET_SERVICE_MAIN
(GNUNET_OS_project_data_gnunet(),
 "namecache",
  GNUNET_SERVICE_OPTION_NONE,
  &run,
  &client_connect_cb,
  &client_disconnect_cb,
  NULL,
  GNUNET_MQ_hd_fixed_size (lookup_block,
                           GNUNET_MESSAGE_TYPE_NAMECACHE_LOOKUP_BLOCK,
                           struct LookupBlockMessage,
                           NULL),
  GNUNET_MQ_hd_var_size (block_cache,
                         GNUNET_MESSAGE_TYPE_NAMECACHE_BLOCK_CACHE,
                         struct BlockCacheMessage,
                         NULL),
  GNUNET_MQ_handler_end ());


/* end of gnunet-service-namecache.c */
