/*
     This file is part of GNUnet.
     Copyright (C) 2024, 2026 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 ch3
 *
 * @file service/pils/pils_api.c
 * Peer Identity Lifecycle Service; the API for managing Peer Identities
 *
 * This api gives access to the PILS service.
 *
 * The service maintains the peer identity. On address change it generates a
 * new identity and informs subscribed components. It also signs data with the
 * identity on request.
 */
#include <string.h>
#include <stdint.h>
#include "gnunet_common.h"
#include "gnunet_protocols.h"
#include "gnunet_signatures.h"
#include "gnunet_util_lib.h"
#include "gnunet_hello_uri_lib.h"
#include "gnunet_pils_service.h"
#include "pils.h"

/* Shorthand for Logging */
#define LOG(kind, ...) GNUNET_log_from (kind, "pils-api", __VA_ARGS__)


struct GNUNET_PILS_Operation
{
  // DLL
  struct GNUNET_PILS_Operation *next;

  // DLL
  struct GNUNET_PILS_Operation *prev;

  // Service handle
  struct GNUNET_PILS_Handle *h;

  // Decaps callback
  GNUNET_PILS_DecapsResultCallback decaps_cb;

  // Ecdh callback
  GNUNET_PILS_EcdhResultCallback ecdh_cb;

  // Sign callback
  GNUNET_PILS_SignResultCallback sign_cb;

  // Decaps callback closure
  void *cb_cls;

  // Current message to send
  struct GNUNET_MQ_Envelope *env;

  // Op ID
  uint32_t op_id;
};


/**
 * @brief A handle for the PILS service.
 */
struct GNUNET_PILS_Handle
{
  /* The handle to the configuration */
  const struct GNUNET_CONFIGURATION_Handle *cfg;

  /* Callback called with the new peer id */
  GNUNET_PILS_PidChangeCallback pid_change_cb;

  /* Closure to the #pid_change_cb callback */
  void *pid_change_cb_cls;

  /* Task regularly trying to connect to the service */
  struct GNUNET_SCHEDULER_Task *reconnect_task;

  /* Delay until the next reconnect */
  struct GNUNET_TIME_Relative reconnect_delay;

  /* Handle to the mq to communicate with the service */
  struct GNUNET_MQ_Handle *mq;

  /* The current peer_id */
  struct GNUNET_PeerIdentity *peer_id;

  /* The current peer id hash */
  struct GNUNET_HashCode peer_hash;

  /* The hash from the last set of addresses fed to PILS. */
  struct GNUNET_HashCode hash;

  /**
   * DLL
   */
  struct GNUNET_PILS_Operation *op_head;

  /**
   * DLL
   */
  struct GNUNET_PILS_Operation *op_tail;

  /**
   * Op ID counter
   */
  uint32_t op_id_counter;

};


/**
 * @brief A simplified handle for using the peer identity key.
 */
struct GNUNET_PILS_KeyRing
{
  /**
   * PILS handle
   */
  struct GNUNET_PILS_Handle *pils;

  /**
   * Initial callback
   */
  GNUNET_SCHEDULER_TaskCallback init_cb;

  /**
   * Closure for initial callback
   */
  void *cls;

  /**
   * Initial key material
   */
  unsigned char initial_key_material[256 / 8];

  /**
   * Private key
   */
  struct GNUNET_CRYPTO_EddsaPrivateKey *private_key;

  /**
   * Peer identity
   */
  struct GNUNET_PeerIdentity identity;

  /**
   * Hash of peer identity
   */
  struct GNUNET_HashCode hash;
};

/**
 * Find the op that matches the @a rid
 *
 * @param h PILS handle
 * @param rid id to look up
 * @return NULL if @a rid was not found
 */
static struct GNUNET_PILS_Operation *
find_op (struct GNUNET_PILS_Handle *h, uint32_t rid)
{
  struct GNUNET_PILS_Operation *op;

  for (op = h->op_head; op != NULL; op = op->next)
    if (op->op_id == rid)
      return op;
  LOG (GNUNET_ERROR_TYPE_WARNING,
       "Finding request with id %u was unsuccessful\n",
       rid);
  return NULL;
}


/**
 * Handles sign result.
 *
 * @param cls closure - Handle to the PILS service
 * @param msg the message containing the signature
 */
static int
check_peer_id (void *cls, const struct PeerIdUpdateMessage *msg)
{
  size_t msg_size;
  uint32_t block_bytes;
  (void) cls;

  msg_size = ntohs (msg->header.size);
  block_bytes = ntohl (msg->block_len);
  if (msg_size != sizeof (*msg) + block_bytes)
  {
    LOG (GNUNET_ERROR_TYPE_ERROR,
         "The msg_size (%lu) is not %lu (header) + %u (block)\n",
         msg_size,
         sizeof (*msg),
         block_bytes);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;

}


/**
 * Handles peer ids sent from the service.
 *
 * @param cls closure - Handle to the PILS service
 * @param pid_msg the message containing peer id and addresses hash
 */
static void
handle_peer_id (void *cls, const struct PeerIdUpdateMessage *pid_msg)
{
  struct GNUNET_PILS_Handle *h = cls;
  struct GNUNET_HELLO_Parser *parser;
  uint32_t block_bytes;

  h->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
  block_bytes = ntohl (pid_msg->block_len);
  parser = GNUNET_HELLO_parser_from_block (&pid_msg[1],
                                           block_bytes);
  if (NULL == parser)
  {
    LOG (GNUNET_ERROR_TYPE_ERROR,
         "Error parsing Hello block from PILS!\n");
    return;
  }

  if (NULL == h->peer_id)
    h->peer_id = GNUNET_new (struct GNUNET_PeerIdentity);

  memcpy (&h->hash, &pid_msg->hash, sizeof (struct GNUNET_HashCode));
  memcpy (h->peer_id, GNUNET_HELLO_parser_get_id (parser),
          sizeof (struct GNUNET_PeerIdentity));
  GNUNET_CRYPTO_hash (h->peer_id, sizeof (struct GNUNET_PeerIdentity),
                      &h->peer_hash);

  if (NULL != h->pid_change_cb)
  {
    h->pid_change_cb (h->pid_change_cb_cls,
                      parser,
                      &pid_msg->hash);
  }
  GNUNET_HELLO_parser_free (parser);
}


/**
 * Handles sign result.
 *
 * @param cls closure - Handle to the PILS service
 * @param msg the message containing the signature
 */
static void
handle_sign_result (void *cls, const struct SignResultMessage *msg)
{
  struct GNUNET_PILS_Handle *h = cls;
  struct GNUNET_PILS_Operation *op;

  h->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
  op = find_op (h, ntohl (msg->rid));

  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Received SIGN_RESULT message from service\n");

  if (NULL == op)
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
         "Didn't find the operation corresponding to id %u\n",
         ntohl (msg->rid));
    return;
  }
  if (NULL != op->sign_cb)
  {
    // FIXME maybe return NULL of key is 0ed
    // as this indicates an error
    op->sign_cb (op->cb_cls,
                 &msg->peer_id,
                 &msg->sig);
  }
  GNUNET_CONTAINER_DLL_remove (h->op_head,
                               h->op_tail,
                               op);
  GNUNET_free (op);
}


/**
 * Handles decaps result.
 *
 * @param cls closure - Handle to the PILS service
 * @param msg the message containing the decapsulation result
 */
static void
handle_decaps_result (void *cls, const struct DecapsResultMessage *msg)
{
  struct GNUNET_PILS_Handle *h = cls;
  struct GNUNET_PILS_Operation *op;

  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Received KEM_DECAPS result from service!\n");

  h->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
  op = find_op (h, ntohl (msg->rid));

  if (NULL == op)
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
         "Didn't find the operation corresponding to id %u\n",
         ntohl (msg->rid));
    return;
  }
  if (NULL != op->decaps_cb)
  {
    // FIXME maybe return NULL of key is 0ed
    // as this indicates an error
    op->decaps_cb (op->cb_cls,
                   &msg->key);
  }
  GNUNET_CONTAINER_DLL_remove (h->op_head,
                               h->op_tail,
                               op);
  GNUNET_free (op);
}


/**
 * Handles ecdh result.
 *
 * @param cls closure - Handle to the PILS service
 * @param msg the message containing the ecdh result
 */
static void
handle_ecdh_result (void *cls, const struct EcdhResultMessage *msg)
{
  struct GNUNET_PILS_Handle *h = cls;
  struct GNUNET_PILS_Operation *op;

  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Received ECDH result from service!\n");

  h->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
  op = find_op (h, ntohl (msg->rid));

  if (NULL == op)
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
         "Didn't find the operation corresponding to id %u\n",
         ntohl (msg->rid));
    return;
  }
  if (NULL != op->ecdh_cb)
    op->ecdh_cb (op->cb_cls,
                 &msg->key);
  GNUNET_CONTAINER_DLL_remove (h->op_head,
                               h->op_tail,
                               op);
  GNUNET_free (op);
}


/**
 * Try again to connect to peer identity lifecycle service
 *
 * @param cls the `struct GNUNET_PILS_Handle *`
 */
static void
reconnect (void *cls);


/**
 * Handles errors with the mq. Schedules the reconnect task and updates the
 * reconnect delay.
 *
 * @param cls closure - handle to PILS
 * @param error error type indicating the kind of problem with mq
 */
static void
mq_error_handler (void *cls, enum GNUNET_MQ_Error error)
{
  struct GNUNET_PILS_Handle *h = cls;
  (void) error;

  // TODO logging
  LOG (GNUNET_ERROR_TYPE_WARNING,
       "Connection to pils service failed!\n");
  GNUNET_MQ_destroy (h->mq);
  h->mq = NULL;
  h->reconnect_task =
    GNUNET_SCHEDULER_add_delayed (h->reconnect_delay, &reconnect, h);
  h->reconnect_delay = GNUNET_TIME_STD_BACKOFF (h->reconnect_delay);
}


/**
 * Try again to connect to peer identity lifecycle service
 *
 * @param cls the `struct GNUNET_PILS_Handle *`
 */
static void
reconnect (void *cls)
{
  struct GNUNET_PILS_Handle *h = cls;
  struct GNUNET_MQ_MessageHandler handlers[] = {
    GNUNET_MQ_hd_var_size (peer_id,
                           GNUNET_MESSAGE_TYPE_PILS_PEER_ID,
                           struct PeerIdUpdateMessage,
                           h),
    GNUNET_MQ_hd_fixed_size (decaps_result,
                             GNUNET_MESSAGE_TYPE_PILS_DECAPS_RESULT,
                             struct DecapsResultMessage,
                             h),
    GNUNET_MQ_hd_fixed_size (ecdh_result,
                             GNUNET_MESSAGE_TYPE_PILS_ECDH_RESULT,
                             struct EcdhResultMessage,
                             h),
    GNUNET_MQ_hd_fixed_size (sign_result,
                             GNUNET_MESSAGE_TYPE_PILS_SIGN_RESULT,
                             struct SignResultMessage,
                             h),
    GNUNET_MQ_handler_end ()
  };

  h->reconnect_task = NULL;
  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Connecting to peer identity lifecycle service.\n");
  GNUNET_assert (NULL == h->mq);
  h->mq = GNUNET_CLIENT_connect (h->cfg,
                                 "pils",
                                 handlers,
                                 &mq_error_handler,
                                 h);
  if (NULL == h->mq)
  {
    LOG (GNUNET_ERROR_TYPE_ERROR,
         "Failed to connect.\n");
    {
      h->reconnect_task =
        GNUNET_SCHEDULER_add_delayed (h->reconnect_delay, &reconnect, h);
      h->reconnect_delay = GNUNET_TIME_STD_BACKOFF (h->reconnect_delay);
    }
    return;
  }
  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Connection to service successful!\n");
}


struct GNUNET_PILS_Handle *
GNUNET_PILS_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
                     GNUNET_PILS_PidChangeCallback pid_change_cb,
                     void *cls)
{
  struct GNUNET_PILS_Handle *h;

  h = GNUNET_new (struct GNUNET_PILS_Handle);
  h->cfg = cfg;
  h->pid_change_cb = pid_change_cb;
  h->pid_change_cb_cls = cls;
  h->reconnect_delay = GNUNET_TIME_UNIT_ZERO;
  reconnect (h);
  return h;
}


/**
 * @brief Disconnect from the PILS service
 *
 * @param handle handle to the PILS service (was returned by
 * #GNUNET_PILS_connect)
 */
void
GNUNET_PILS_disconnect (struct GNUNET_PILS_Handle *handle)
{
  struct GNUNET_PILS_Operation *op;

  GNUNET_assert (NULL != handle);
  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Disonnecting from peer identity lifecycle service.\n");
  if (NULL != handle->reconnect_task)
  {
    GNUNET_SCHEDULER_cancel (handle->reconnect_task);
    handle->reconnect_task = NULL;
  }
  if (NULL != handle->mq)
  {
    GNUNET_MQ_destroy (handle->mq);
    handle->mq = NULL;
  }
  LOG (GNUNET_ERROR_TYPE_DEBUG, "Cleaning up\n");
  while (NULL != (op = handle->op_head))
  {
    GNUNET_CONTAINER_DLL_remove (handle->op_head, handle->op_tail, op);
    GNUNET_free (op);
  }
  if (handle->peer_id)
    GNUNET_free (handle->peer_id);
  GNUNET_free (handle);
}


/**
 * @brief Sign data with the peer id
 *
 * @param handle handle to the PILS service
 * @param purpose what to sign (size, purpose and data)
 * @param cb callback to call once the signature is ready
 * @param cb_cls closure to @a cb
 *
 * @return handle to the operation, NULL on error
 */
struct GNUNET_PILS_Operation*
GNUNET_PILS_sign_by_peer_identity (struct GNUNET_PILS_Handle *handle,
                                   const struct
                                   GNUNET_CRYPTO_SignaturePurpose *purpose,
                                   GNUNET_PILS_SignResultCallback cb,
                                   void *cb_cls)

{
  struct GNUNET_PILS_Operation *op;
  struct SignRequestMessage *msg;

  op = GNUNET_new (struct GNUNET_PILS_Operation);
  op->env = GNUNET_MQ_msg_extra (msg,
                                 ntohl (purpose->size),
                                 GNUNET_MESSAGE_TYPE_PILS_SIGN_REQUEST);
  op->h = handle;
  op->sign_cb = cb;
  op->cb_cls = cb_cls;
  msg->rid = htonl (handle->op_id_counter++);
  op->op_id = ntohl (msg->rid);
  memcpy (&msg[1], purpose, ntohl (purpose->size));
  GNUNET_CONTAINER_DLL_insert (handle->op_head,
                               handle->op_tail,
                               op);
  // FIXME resend?
  GNUNET_MQ_send (handle->mq, op->env);
  op->env = NULL;
  return op;
}


/**
 * @brief Decaps an encapsulated key with our private key
 *
 * @param handle handle to the PILS service
 * @param c the encapsulated key
 * @param prk where to write the key material HKDF-Extract(c||aX)=HKDF-Extract(c||x(aG))
 *
 * @return handle to the operation, NULL on error
 */
struct GNUNET_PILS_Operation*
GNUNET_PILS_kem_decaps (struct GNUNET_PILS_Handle *handle,
                        const struct GNUNET_CRYPTO_HpkeEncapsulation *c,
                        GNUNET_PILS_DecapsResultCallback cb,
                        void *cb_cls)
{
  struct GNUNET_PILS_Operation *op;
  struct DecapsMessage *msg;

  op = GNUNET_new (struct GNUNET_PILS_Operation);
  op->env = GNUNET_MQ_msg (msg, GNUNET_MESSAGE_TYPE_PILS_KEM_DECAPS);
  msg->c = *c;
  op->h = handle;
  op->decaps_cb = cb;
  op->cb_cls = cb_cls;
  msg->rid = htonl (handle->op_id_counter++);
  op->op_id = ntohl (msg->rid);
  GNUNET_CONTAINER_DLL_insert (handle->op_head,
                               handle->op_tail,
                               op);
  // FIXME resend?
  GNUNET_MQ_send (handle->mq, op->env);
  op->env = NULL;
  return op;
}


struct GNUNET_PILS_Operation*
GNUNET_PILS_ecdh (struct GNUNET_PILS_Handle *handle,
                  const struct GNUNET_CRYPTO_EcdhePublicKey *pub,
                  GNUNET_PILS_EcdhResultCallback cb,
                  void *cb_cls)
{
  struct GNUNET_PILS_Operation *op;
  struct EcdhMessage *msg;

  GNUNET_assert ((handle) && (pub));

  op = GNUNET_new (struct GNUNET_PILS_Operation);
  op->env = GNUNET_MQ_msg (msg, GNUNET_MESSAGE_TYPE_PILS_ECDH);
  msg->pub = *pub;
  op->h = handle;
  op->ecdh_cb = cb;
  op->cb_cls = cb_cls;
  msg->rid = htonl (handle->op_id_counter++);
  op->op_id = ntohl (msg->rid);
  GNUNET_CONTAINER_DLL_insert (handle->op_head,
                               handle->op_tail,
                               op);
  GNUNET_MQ_send (handle->mq, op->env);
  op->env = NULL;
  return op;
}


void
GNUNET_PILS_cancel (struct GNUNET_PILS_Operation *op)
{
  struct GNUNET_PILS_Handle *h = op->h;

  GNUNET_CONTAINER_DLL_remove (h->op_head, h->op_tail, op);
  if (NULL != op->env)
    GNUNET_MQ_discard (op->env);
  GNUNET_free (op);
}


void
GNUNET_PILS_derive_pid (size_t seed_key_bytes,
                        const uint8_t seed_key[seed_key_bytes],
                        const struct GNUNET_HashCode *addrs_hash,
                        struct GNUNET_CRYPTO_EddsaPrivateKey *outkey)
{
  struct GNUNET_ShortHashCode prk;

  /**
   * Since we should have initial keying material of good quality here,
   * this is effectively a PRF called on the address hash with
   * a uniform random key.
   */
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CRYPTO_hkdf_extract (&prk,
                                             addrs_hash,
                                             sizeof *addrs_hash,
                                             seed_key,
                                             seed_key_bytes));
  /**
   * We now expand the PRK to the key size we actually require.
   * FIXME: IF we want to use elligator, we need to find a
   * private key that can actually be used as such.
   * For that, we may want to add a counter to the initial secret key
   * to the above PRF.
   */
  GNUNET_CRYPTO_hkdf_expand (
    outkey,
    sizeof *outkey,
    &prk,
    GNUNET_CRYPTO_kdf_arg_string ("gnunet-pils-ephemeral-peer-key"));
}


void
GNUNET_PILS_feed_addresses (struct GNUNET_PILS_Handle *handle,
                            const struct GNUNET_HELLO_Builder *builder)
{
  struct FeedAddressesMessage *msg;
  struct GNUNET_MQ_Envelope *env;
  size_t block_bytes;

  block_bytes = GNUNET_HELLO_get_builder_to_block_size (builder);
  // TODO check whether the new hash and the 'current' hash are the same -
  //      nothing to do in that case (directly return the peer id?)
  env = GNUNET_MQ_msg_extra (msg,
                             block_bytes,
                             GNUNET_MESSAGE_TYPE_PILS_FEED_ADDRESSES);
  msg->block_len = htonl (block_bytes);
  GNUNET_HELLO_builder_to_block (
    builder,
    NULL,
    NULL,
    GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO),
    (char*) &msg[1]);
  GNUNET_MQ_send (handle->mq, env);
}


/**
 * Create HELLO signature.
 *
 * @param handle handle to the pils service
 * @param builder the builder to use
 * @param et expiration time to sign
 * @param cb callback to call with the signature
 * @param cb_cls closure to @a cb
 */
struct GNUNET_PILS_Operation *
GNUNET_PILS_sign_hello (struct GNUNET_PILS_Handle *handle,
                        const struct GNUNET_HELLO_Builder *builder,
                        struct GNUNET_TIME_Absolute et,
                        GNUNET_PILS_SignResultCallback cb,
                        void *cb_cls)
{
  struct PilsHelloSignaturePurpose hsp = {
    .purpose.size = htonl (sizeof (hsp)),
    .purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_HELLO),
    .expiration_time = GNUNET_TIME_absolute_hton (et)
  };
  GNUNET_HELLO_builder_hash_addresses (builder,
                                       &hsp.h_addrs);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Address hash is %s\n",
              GNUNET_h2s_full (&hsp.h_addrs));
  return GNUNET_PILS_sign_by_peer_identity (handle,
                                            &hsp.purpose,
                                            cb,
                                            cb_cls);
}


const struct GNUNET_PeerIdentity*
GNUNET_PILS_get_identity (const struct GNUNET_PILS_Handle *handle)
{
  GNUNET_assert (handle);

  return handle->peer_id;
}


const struct GNUNET_HashCode*
GNUNET_PILS_get_identity_hash (const struct GNUNET_PILS_Handle *handle)
{
  GNUNET_assert (handle);

  if (NULL == handle->peer_id)
    return NULL;

  return &handle->peer_hash;
}


void
pid_change_cb (void *cls,
               GNUNET_UNUSED const struct GNUNET_HELLO_Parser *parser,
               const struct GNUNET_HashCode *addr_hash)
{
  struct GNUNET_PILS_KeyRing *key_ring;
  enum GNUNET_GenericReturnValue initialized;

  GNUNET_assert ((cls) && (addr_hash));

  key_ring = cls;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Got PID to derive from `%s':\n",
              GNUNET_h2s (addr_hash));
  if (NULL == key_ring->private_key)
  {
    key_ring->private_key = GNUNET_new (struct GNUNET_CRYPTO_EddsaPrivateKey);
    initialized = GNUNET_YES;
  }
  else
    initialized = GNUNET_NO;

  GNUNET_PILS_derive_pid (sizeof (key_ring->initial_key_material),
                          key_ring->initial_key_material,
                          addr_hash,
                          key_ring->private_key);
  GNUNET_CRYPTO_eddsa_key_get_public (key_ring->private_key,
                                      &(key_ring->identity.public_key));
  GNUNET_CRYPTO_hash (&(key_ring->identity),
                      sizeof (key_ring->identity),
                      &(key_ring->hash));


  GNUNET_assert (0 == GNUNET_memcmp (GNUNET_PILS_get_identity (key_ring->pils),
                                     &(key_ring->identity)));

  if (GNUNET_YES != initialized)
    return;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Initialize key ring\n");

  if (key_ring->init_cb)
    key_ring->init_cb (key_ring->cls);
}


/**
 * Get the initial secret key for generating the peer id. This is supposed to be generated at
 * random once in the lifetime of a peer, so all generated peer ids use the
 * same initial secret key to obtain the same peer id per set of addresses.
 *
 * First check whether there's already a initial secret key. If so: return it. If no initial secret key
 * exists yet, generate at random and store it where it will be found.
 *
 */
struct GNUNET_PILS_KeyRing*
GNUNET_PILS_create_key_ring (const struct GNUNET_CONFIGURATION_Handle *cfg,
                             GNUNET_SCHEDULER_TaskCallback init_cb,
                             void *cls)
{
  char *keyfile;
  struct GNUNET_CRYPTO_EddsaPrivateKey key;

  GNUNET_assert (cfg);

  LOG (GNUNET_ERROR_TYPE_DEBUG, "Create key ring!\n");

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_filename (cfg,
                                               "PEER",
                                               "PRIVATE_KEY",
                                               &keyfile))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "PEER",
                               "PRIVATE_KEY");
    return NULL;
  }

  if (GNUNET_SYSERR ==
      GNUNET_CRYPTO_eddsa_key_from_file (keyfile,
                                         GNUNET_YES,
                                         &key))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to setup peer's private key\n");
    GNUNET_free (keyfile);
    return NULL;
  }

  GNUNET_free (keyfile);

  struct GNUNET_PILS_KeyRing *key_ring =
    GNUNET_new (struct GNUNET_PILS_KeyRing);
  if (NULL == key_ring)
    return NULL;
  key_ring->init_cb = init_cb;
  key_ring->cls = cls;

  GNUNET_assert (sizeof (key_ring->initial_key_material) == sizeof key.d);

  key_ring->pils = GNUNET_PILS_connect (cfg, &pid_change_cb, key_ring);
  if (NULL == key_ring->pils)
  {
    GNUNET_free (key_ring);
    return NULL;
  }

  memcpy (key_ring->initial_key_material, key.d,
          sizeof (key_ring->initial_key_material));

  return key_ring;
}


void
GNUNET_PILS_destroy_key_ring (struct GNUNET_PILS_KeyRing *key_ring)
{
  GNUNET_assert (key_ring);

  LOG (GNUNET_ERROR_TYPE_DEBUG, "Destroy key ring!\n");

  if (key_ring->pils)
    GNUNET_PILS_disconnect (key_ring->pils);

  if (key_ring->private_key)
  {
    GNUNET_CRYPTO_zero_keys (key_ring->private_key,
                             sizeof (*(key_ring->private_key)));
    GNUNET_free (key_ring->private_key);
  }

  GNUNET_CRYPTO_zero_keys (key_ring->initial_key_material,
                           sizeof (key_ring->initial_key_material));
  GNUNET_free (key_ring);
}


const struct GNUNET_CRYPTO_EddsaPrivateKey*
GNUNET_PILS_key_ring_get_private_key (const struct GNUNET_PILS_KeyRing *key_ring
                                      )
{
  GNUNET_assert (key_ring);

  return key_ring->private_key;
}


/* end of pils_api.c */
