/*
   This file is part of GNUnet.
   Copyright (C) 2020 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
 */

/**
 * @author Johannes Späth
 * @file escrow/escrow_api.c
 * 
 * @brief api to interact with the escrow component
 */

#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet_escrow_lib.h"
#include "gnunet_escrow_plugin.h"
#include "escrow.h"


/**
 * Init canary for the plaintext plugin
 */
static int plaintext_initialized;


/**
 * Init canary for the GNS plugin
 */
static int gns_initialized;


/**
 * Init canary for the Anastasis plugin
 */
static int anastasis_initialized;


/**
 * Plaintext method string
 */
static const char *plaintext_string = "plaintext";


/**
 * GNS method string
 */
static const char *gns_string = "gns";


/**
 * Anastasis method string
 */
static const char *anastasis_string = "anastasis";


/**
 * None method string
 */
static const char *none_string = "INVALID-METHOD";


/**
 * Pointer to the plaintext plugin API
 */
static struct GNUNET_ESCROW_KeyPluginFunctions *plaintext_api;


/**
 * Pointer to the GNS plugin API
 */
static struct GNUNET_ESCROW_KeyPluginFunctions *gns_api;


/**
 * Pointer to the Anastasis plugin API
 */
static struct GNUNET_ESCROW_KeyPluginFunctions *anastasis_api;


/**
 * Initialize an escrow plugin
 * 
 * @param method the escrow method determining the plugin
 * 
 * @return pointer to the escrow plugin API
 */
static const struct GNUNET_ESCROW_KeyPluginFunctions *
init_plugin (const struct GNUNET_ESCROW_Handle *h,
             enum GNUNET_ESCROW_Key_Escrow_Method method)
{
  switch (method)
  {
    case GNUNET_ESCROW_KEY_PLAINTEXT:
      if (GNUNET_YES == plaintext_initialized)
        return plaintext_api;
      plaintext_initialized = GNUNET_YES;
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Loading PLAINTEXT escrow plugin\n");
      plaintext_api = GNUNET_PLUGIN_load ("libgnunet_plugin_escrow_plaintext",
                                          (void *)h->cfg);
      return plaintext_api;
    case GNUNET_ESCROW_KEY_GNS:
      if (GNUNET_YES == gns_initialized)
        return gns_api;
      gns_initialized = GNUNET_YES;
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Loading GNS escrow plugin\n");
      gns_api = GNUNET_PLUGIN_load ("libgnunet_plugin_escrow_gns",
                                    (void *)h->cfg);
      return gns_api;
    case GNUNET_ESCROW_KEY_ANASTASIS:
      if (GNUNET_YES == anastasis_initialized)
        return anastasis_api;
      anastasis_initialized = GNUNET_YES;
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Loading ANASTASIS escrow plugin\n");
      anastasis_api = GNUNET_PLUGIN_load ("libgnunet_plugin_escrow_anastasis",
                                          (void *)h->cfg);
      return anastasis_api;
    case GNUNET_ESCROW_KEY_NONE: // error case
      fprintf (stderr, "incorrect escrow method!\n");
      return NULL;
  }
  // should never be reached
  return NULL;
}


/**
 * Get a fresh operation id to distinguish between escrow operations
 *
 * @param h the escrow handle
 * 
 * @return next operation id to use
 */
static uint32_t
get_op_id (struct GNUNET_ESCROW_Handle *h)
{
  return h->last_op_id_used++;
}


/**
 * Initialize the escrow component.
 * 
 * @param cfg the configuration to use
 * 
 * @return handle to use
 */
struct GNUNET_ESCROW_Handle *
GNUNET_ESCROW_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  struct GNUNET_ESCROW_Handle *h;

  h = GNUNET_new (struct GNUNET_ESCROW_Handle);
  h->cfg = GNUNET_CONFIGURATION_dup (cfg);
  h->op_head = NULL;
  h->op_tail = NULL;
  return h;
}


/**
 * Unload all loaded plugins on destruction.
 * 
 * @param h the escrow handle
 */
void
GNUNET_ESCROW_fini (struct GNUNET_ESCROW_Handle *h)
{
  struct GNUNET_ESCROW_Operation *op;

  /* unload all loaded plugins */
  if (GNUNET_YES == plaintext_initialized)
  {
    plaintext_initialized = GNUNET_NO;
    GNUNET_break (NULL == 
                  GNUNET_PLUGIN_unload ("libgnunet_plugin_escrow_plaintext",
                                        plaintext_api));
    plaintext_api = NULL;
  }

  if (GNUNET_YES == gns_initialized)
  {
    gns_initialized = GNUNET_NO;
    GNUNET_break (NULL == 
                  GNUNET_PLUGIN_unload ("libgnunet_plugin_escrow_gns",
                                        gns_api));
    gns_api = NULL;
  }

  if (GNUNET_YES == anastasis_initialized)
  {
    anastasis_initialized = GNUNET_NO;
    GNUNET_break (NULL == 
                  GNUNET_PLUGIN_unload ("libgnunet_plugin_escrow_anastasis",
                                        anastasis_api));
    anastasis_api = NULL;
  }

  /* clean up the operation DLL */
  while (NULL != (op = h->op_head))
  {
    GNUNET_ESCROW_cancel (op);
  }

  /* free the configuration */
  GNUNET_free (h->cfg);

  /* free the escrow handle */
  GNUNET_free (h);
}


static void
handle_start_escrow_result (void *cls)
{
  struct ESCROW_Plugin_AnchorContinuationWrapper *w = cls;
  struct GNUNET_ESCROW_Operation *op;

  for (op = w->h->op_head; NULL != op; op = op->next)
    if (op->id == w->op_id)
      break;

  if (NULL == op)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_CONTAINER_DLL_remove (w->h->op_head, w->h->op_tail, op);
  if (NULL != op->cb_put)
    op->cb_put (op->cb_cls, w->anchor, w->emsg);
  GNUNET_free (op);
}


/**
 * Put some data in escrow using the specified escrow method
 * 
 * @param h the handle for the escrow component
 * @param ego the identity ego to put in escrow
 * @param userSecret the user secret (e.g. for derivation of escrow identities)
 *                   for GNS escrow, this has to be UNIQUE in the whole network!
 * @param method the escrow method to use
 * @param cb function to call with the escrow anchor on completion
 * @param cb_cls closure for @a cb
 * 
 * @return handle to abort the operation
 */
struct GNUNET_ESCROW_Operation *
GNUNET_ESCROW_put (struct GNUNET_ESCROW_Handle *h,
                   struct GNUNET_IDENTITY_Ego *ego,
                   const char *userSecret,
                   enum GNUNET_ESCROW_Key_Escrow_Method method,
                   GNUNET_ESCROW_AnchorContinuation cb,
                   void *cb_cls)
{
  struct GNUNET_ESCROW_Operation *op;
  const struct GNUNET_ESCROW_KeyPluginFunctions *api;

  op = GNUNET_new (struct GNUNET_ESCROW_Operation);
  op->h = h;
  op->id = get_op_id (h);
  op->method = method;
  op->cb_put = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head, h->op_tail, op);

  api = init_plugin (h, method);
  op->plugin_op_wrap = api->start_key_escrow (h,
                                              ego,
                                              userSecret,
                                              &handle_start_escrow_result,
                                              op->id);

  return op;
}


static void
handle_restore_key_result (void *cls)
{
  struct ESCROW_Plugin_EgoContinuationWrapper *w = cls;
  struct GNUNET_ESCROW_Operation *op;

  for (op = w->h->op_head; NULL != op; op = op->next)
    if (op->id == w->op_id)
      break;

  if (NULL == op)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_CONTAINER_DLL_remove (w->h->op_head, w->h->op_tail, op);
  if (NULL != op->cb_get)
    op->cb_get (op->cb_cls, w->ego, w->emsg);
  GNUNET_free (op);
}


/**
 * Get the escrowed data back
 * 
 * @param h the handle for the escrow component
 * @param anchor the escrow anchor returned by the GNUNET_ESCROW_put method
 * @param cb function to call with the restored ego on completion
 * @param cb_cls closure for @a cb
 * 
 * @return handle to abort the operation
 */
struct GNUNET_ESCROW_Operation *
GNUNET_ESCROW_get (struct GNUNET_ESCROW_Handle *h,
                   const struct GNUNET_ESCROW_Anchor *anchor,
                   GNUNET_ESCROW_EgoContinuation cb,
                   void *cb_cls)
{
  struct GNUNET_ESCROW_Operation *op;
  const struct GNUNET_ESCROW_KeyPluginFunctions *api;

  op = GNUNET_new (struct GNUNET_ESCROW_Operation);
  op->h = h;
  op->id = get_op_id (h);
  op->method = anchor->method;
  op->cb_get = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head, h->op_tail, op);

  api = init_plugin (h, anchor->method);
  op->plugin_op_wrap = api->restore_key (h, anchor, &handle_restore_key_result, op->id);

  return op;
}


static void
handle_verify_escrow_result (void *cls)
{
  struct ESCROW_Plugin_VerifyContinuationWrapper *w = cls;
  struct GNUNET_ESCROW_Operation *op;

  for (op = w->h->op_head; NULL != op; op = op->next)
    if (op->id == w->op_id)
      break;

  if (NULL == op)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_CONTAINER_DLL_remove (w->h->op_head, w->h->op_tail, op);
  if (NULL != op->cb_verify)
    op->cb_verify (op->cb_cls, w->verificationResult, w->emsg);
  GNUNET_free (op);
}


/**
 * Verify the escrowed data
 * 
 * @param h the handle for the escrow component
 * @param ego the identity ego that was put into escrow
 * @param anchor the escrow anchor returned by the GNUNET_ESCROW_put method
 * @param method the escrow method to use
 * @param cb function to call with the verification result on completion
 * @param cb_cls closure for @a cb
 * 
 * @return handle to abort the operation
 */
struct GNUNET_ESCROW_Operation *
GNUNET_ESCROW_verify (struct GNUNET_ESCROW_Handle *h,
                      struct GNUNET_IDENTITY_Ego *ego,
                      const struct GNUNET_ESCROW_Anchor *anchor,
                      enum GNUNET_ESCROW_Key_Escrow_Method method,
                      GNUNET_ESCROW_VerifyContinuation cb,
                      void *cb_cls)
{
  struct GNUNET_ESCROW_Operation *op;
  const struct GNUNET_ESCROW_KeyPluginFunctions *api;

  op = GNUNET_new (struct GNUNET_ESCROW_Operation);
  op->h = h;
  op->id = get_op_id (h);
  op->method = method;
  op->cb_verify = cb;
  op->cb_cls = cb_cls;
  GNUNET_CONTAINER_DLL_insert_tail (h->op_head, h->op_tail, op);

  api = init_plugin (h, method);
  op->plugin_op_wrap = api->verify_key_escrow (h, ego, anchor, &handle_verify_escrow_result, op->id);

  return op;
}


/**
 * Get the status of an escrow, i.e.
 *   -> when the last escrow was
 *   -> when the next escrow is recommended
 * 
 * @param h the handle for the escrow component
 * @param ego the identity ego of which the escrow status has to be determined
 * @param method the escrow method to use
 * 
 * @return the status of the escrow packed into a GNUNET_ESCROW_Status struct
 */
struct GNUNET_ESCROW_Status *
GNUNET_ESCROW_get_status (struct GNUNET_ESCROW_Handle *h,
                          struct GNUNET_IDENTITY_Ego *ego,
                          enum GNUNET_ESCROW_Key_Escrow_Method method)
{
  const struct GNUNET_ESCROW_KeyPluginFunctions *api;

  api = init_plugin (h, method);
  return api->get_status (h, ego);
}


/**
 * Deserialize an escrow anchor string (e.g. from command line) into a
 * GNUNET_ESCROW_Anchor struct
 * The anchor string is expected to have the following form:
 *    <method>:<egoName>:<anchorData>
 * with <method>, <egoName> and <anchorData> being URL-encoded
 * 
 * 
 * @param anchorString the encoded escrow anchor string
 * 
 * @return the deserialized data packed into a GNUNET_ESCROW_Anchor struct,
 *         NULL if we failed to parse the string
 */
struct GNUNET_ESCROW_Anchor *
GNUNET_ESCROW_anchor_string_to_data (const char *anchorString)
{
  struct GNUNET_ESCROW_Anchor *anchor;
  uint32_t data_size;
  char *anchorStringCopy, *ptr;
  char *methodString, *egoNameString, *anchorDataString;
  char delimiter[] = ":";
  
  anchorStringCopy = GNUNET_strdup (anchorString);
  anchor = NULL;
  methodString = egoNameString = anchorDataString = NULL;

  /* parse and decode method */
  ptr = strtok (anchorStringCopy, delimiter);
  if (NULL == ptr)
    goto END;
  GNUNET_STRINGS_urldecode (ptr, strlen (ptr), &methodString);
  /* parse and decode ego name */
  ptr = strtok (NULL, delimiter);
  if (NULL == ptr)
    goto END;
  GNUNET_STRINGS_urldecode (ptr, strlen (ptr), &egoNameString);
  /* parse and decode anchor data */
  ptr = strtok (NULL, delimiter);
  if (NULL == ptr)
    goto END;
  GNUNET_STRINGS_urldecode (ptr, strlen (ptr), &anchorDataString);
  /* check if string is over */
  ptr = strtok (NULL, delimiter);
  if (NULL != ptr)
    goto END;

  data_size = strlen (anchorDataString); // data is NOT null-terminated
  anchor = GNUNET_malloc (sizeof (struct GNUNET_ESCROW_Anchor)
                          + data_size
                          + strlen (egoNameString) + 1);
  anchor->size = data_size;
  anchor->method = GNUNET_ESCROW_method_string_to_number (methodString);
  
  // ptr is now used to fill the anchor
  ptr = (char *)&anchor[1];
  strcpy (ptr, anchorDataString);
  ptr += data_size;
  anchor->egoName = ptr;
  strcpy (ptr, egoNameString);

  END:
  /* free all non-NULL strings */
  if (NULL != anchorStringCopy)
    GNUNET_free (anchorStringCopy);
  if (NULL != methodString)
    GNUNET_free (methodString);
  if (NULL != egoNameString)
    GNUNET_free (egoNameString);
  if (NULL != anchorDataString)
    GNUNET_free (anchorDataString);

  return anchor;
}


/**
 * Serialize an escrow anchor (struct GNUNET_ESCROW_Anchor) into a string
 * 
 * @param anchor the escrow anchor struct
 * 
 * @return the encoded escrow anchor string
 */
char *
GNUNET_ESCROW_anchor_data_to_string (const struct GNUNET_ESCROW_Anchor *anchor)
{
  char *anchorString, *ptr;
  const char *methodString, *egoNameString, *anchorData;
  char *methodStringEnc, *egoNameStringEnc, *anchorDataEnc;

  methodString = GNUNET_ESCROW_method_number_to_string (anchor->method);
  GNUNET_STRINGS_urlencode (methodString, strlen (methodString), &methodStringEnc);
  egoNameString = anchor->egoName;
  GNUNET_STRINGS_urlencode (egoNameString, strlen (egoNameString), &egoNameStringEnc);
  anchorData = (const char *)&anchor[1];
  GNUNET_STRINGS_urlencode (anchorData, anchor->size, &anchorDataEnc);

  anchorString = GNUNET_malloc (strlen (methodStringEnc) + 1
                                + strlen (egoNameStringEnc) + 1
                                + strlen (anchorDataEnc)
                                + 1);

  ptr = anchorString;
  GNUNET_memcpy (ptr, methodStringEnc, strlen (methodStringEnc));
  ptr += strlen (methodStringEnc);
  GNUNET_free (methodStringEnc);
  *(ptr++) = ':';
  GNUNET_memcpy (ptr, egoNameStringEnc, strlen (egoNameStringEnc));
  ptr += strlen (egoNameStringEnc);
  GNUNET_free (egoNameStringEnc);
  *(ptr++) = ':';
  GNUNET_memcpy (ptr, anchorDataEnc, strlen (anchorDataEnc));
  ptr += strlen (anchorDataEnc);
  GNUNET_free (anchorDataEnc);
  *(ptr++) = '\0';
  
  return anchorString;
}


/**
 * Convert a method name string to the respective enum number
 * 
 * @param methodString the method name string
 * 
 * @return the enum number
 */
enum GNUNET_ESCROW_Key_Escrow_Method
GNUNET_ESCROW_method_string_to_number (const char *methodString)
{
  if (NULL == methodString)
    return GNUNET_ESCROW_KEY_NONE;
  if (!strcmp (plaintext_string, methodString))
    return GNUNET_ESCROW_KEY_PLAINTEXT;
  else if (!strcmp (gns_string, methodString))
    return GNUNET_ESCROW_KEY_GNS;
  else if (!strcmp (anastasis_string, methodString))
    return GNUNET_ESCROW_KEY_ANASTASIS;
  else
    return GNUNET_ESCROW_KEY_NONE;
}


/**
 * Convert a method enum number to the respective method string
 * 
 * @param method the method enum number
 * 
 * @return the method string
 */
const char *
GNUNET_ESCROW_method_number_to_string (enum GNUNET_ESCROW_Key_Escrow_Method method)
{
  switch (method)
  {
    case GNUNET_ESCROW_KEY_PLAINTEXT:
      return plaintext_string;
    case GNUNET_ESCROW_KEY_GNS:
      return gns_string;
    case GNUNET_ESCROW_KEY_ANASTASIS:
      return anastasis_string;
    default:
      return none_string;
  }
}


/**
 * Cancel an escrow operation.
 *
 * @param op operation to cancel
 */
void
GNUNET_ESCROW_cancel (struct GNUNET_ESCROW_Operation *op)
{
  const struct GNUNET_ESCROW_KeyPluginFunctions *api;
  struct GNUNET_ESCROW_Handle *h = op->h;

  api = init_plugin (op->h, op->method);
  api->cancel_plugin_operation (op->plugin_op_wrap);
  GNUNET_CONTAINER_DLL_remove (h->op_head, h->op_tail, op);
  GNUNET_free (op);
}


/* end of escrow_api.c */
