/*
   This file is part of GNUnet.
   Copyright (C) 2012-2015 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 Martin Schanzenbach
 * @file src/reclaim/gnunet-service-reclaim.c
 * @brief reclaim Service
 *
 */
#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet-service-reclaim_tickets.h"
#include "gnunet_constants.h"
#include "gnunet_gnsrecord_lib.h"
#include "gnunet_protocols.h"
#include "gnunet_reclaim_lib.h"
#include "gnunet_reclaim_service.h"
#include "gnunet_signatures.h"
#include "reclaim.h"


/**
 * Namestore handle
 */
static struct GNUNET_NAMESTORE_Handle *nsh;

/**
 * Timeout task
 */
static struct GNUNET_SCHEDULER_Task *timeout_task;

/**
 * Our configuration.
 */
static const struct GNUNET_CONFIGURATION_Handle *cfg;

/**
 * An idp client
 */
struct IdpClient;

/**
 * A ticket iteration operation.
 */
struct TicketIteration
{
  /**
   * DLL
   */
  struct TicketIteration *next;

  /**
   * DLL
   */
  struct TicketIteration *prev;

  /**
   * Client which intiated this zone iteration
   */
  struct IdpClient *client;

  /**
   * The operation id for the iteration in the response for the client
   */
  uint32_t r_id;

  /**
   * The ticket iterator
   */
  struct RECLAIM_TICKETS_Iterator *iter;
};


/**
 * An attribute iteration operation.
 */
struct Iterator
{
  /**
   * Next element in the DLL
   */
  struct Iterator *next;

  /**
   * Previous element in the DLL
   */
  struct Iterator *prev;

  /**
   * IDP client which intiated this zone iteration
   */
  struct IdpClient *client;

  /**
   * Key of the zone we are iterating over.
   */
  struct GNUNET_IDENTITY_PrivateKey identity;

  /**
   * Namestore iterator
   */
  struct GNUNET_NAMESTORE_ZoneIterator *ns_it;

  /**
   * The operation id for the zone iteration in the response for the client
   */
  uint32_t request_id;

  /**
   * Context
   */
  void *ctx;
};


/**
 * An idp client
 */
struct IdpClient
{
  /**
   * DLL
   */
  struct IdpClient *prev;

  /**
   * DLL
   */
  struct IdpClient *next;

  /**
   * The client
   */
  struct GNUNET_SERVICE_Client *client;

  /**
   * Message queue for transmission to @e client
   */
  struct GNUNET_MQ_Handle *mq;

  /**
   * Head of the DLL of
   * Attribute iteration operations in
   * progress initiated by this client
   */
  struct Iterator *attr_iter_head;

  /**
   * Tail of the DLL of
   * Attribute iteration operations
   * in progress initiated by this client
   */
  struct Iterator *attr_iter_tail;

  /**
   * Head of the DLL of
   * Credential iteration operations in
   * progress initiated by this client
   */
  struct Iterator *cred_iter_head;

  /**
   * Tail of the DLL of
   * Credential iteration operations
   * in progress initiated by this client
   */
  struct Iterator *cred_iter_tail;

  /**
   * Head of DLL of ticket iteration ops
   */
  struct TicketIteration *ticket_iter_head;

  /**
   * Tail of DLL of ticket iteration ops
   */
  struct TicketIteration *ticket_iter_tail;

  /**
   * Head of DLL of ticket revocation ops
   */
  struct TicketRevocationOperation *revoke_op_head;

  /**
   * Tail of DLL of ticket revocation ops
   */
  struct TicketRevocationOperation *revoke_op_tail;

  /**
   * Head of DLL of ticket issue ops
   */
  struct TicketIssueOperation *issue_op_head;

  /**
   * Tail of DLL of ticket issue ops
   */
  struct TicketIssueOperation *issue_op_tail;

  /**
   * Head of DLL of ticket consume ops
   */
  struct ConsumeTicketOperation *consume_op_head;

  /**
   * Tail of DLL of ticket consume ops
   */
  struct ConsumeTicketOperation *consume_op_tail;

  /**
   * Head of DLL of attribute store ops
   */
  struct AttributeStoreHandle *store_op_head;

  /**
   * Tail of DLL of attribute store ops
   */
  struct AttributeStoreHandle *store_op_tail;
  /**
   * Head of DLL of attribute delete ops
   */
  struct AttributeDeleteHandle *delete_op_head;

  /**
   * Tail of DLL of attribute delete ops
   */
  struct AttributeDeleteHandle *delete_op_tail;
};


/**
 * Handle for attribute deletion request
 */
struct AttributeDeleteHandle
{
  /**
   * DLL
   */
  struct AttributeDeleteHandle *next;

  /**
   * DLL
   */
  struct AttributeDeleteHandle *prev;

  /**
   * Client connection
   */
  struct IdpClient *client;

  /**
   * Identity
   */
  struct GNUNET_IDENTITY_PrivateKey identity;


  /**
   * QueueEntry
   */
  struct GNUNET_NAMESTORE_QueueEntry *ns_qe;

  /**
   * Iterator
   */
  struct GNUNET_NAMESTORE_ZoneIterator *ns_it;

  /**
   * The attribute to delete
   */
  struct GNUNET_RECLAIM_Attribute *claim;

  /**
   * The credential to delete
   */
  struct GNUNET_RECLAIM_Credential *credential;

  /**
   * Tickets to update
   */
  struct TicketRecordsEntry *tickets_to_update_head;

  /**
   * Tickets to update
   */
  struct TicketRecordsEntry *tickets_to_update_tail;

  /**
   * Existing attributes
   */
  struct GNUNET_RECLAIM_AttributeList *existing_attributes;

  /**
   * Existing credentials
   */
  struct GNUNET_RECLAIM_CredentialList *existing_credentials;

  /**
   * Attribute label
   */
  char *label;

  /**
   * request id
   */
  uint32_t r_id;
};


/**
 * Handle for attribute store request
 */
struct AttributeStoreHandle
{
  /**
   * DLL
   */
  struct AttributeStoreHandle *next;

  /**
   * DLL
   */
  struct AttributeStoreHandle *prev;

  /**
   * Client connection
   */
  struct IdpClient *client;

  /**
   * Identity
   */
  struct GNUNET_IDENTITY_PrivateKey identity;

  /**
   * Identity pubkey
   */
  struct GNUNET_IDENTITY_PublicKey identity_pkey;

  /**
   * QueueEntry
   */
  struct GNUNET_NAMESTORE_QueueEntry *ns_qe;

  /**
   * The attribute to store
   */
  struct GNUNET_RECLAIM_Attribute *claim;

  /**
  * The credential to store
  */
  struct GNUNET_RECLAIM_Credential *credential;

  /**
   * The attribute expiration interval
   */
  struct GNUNET_TIME_Relative exp;

  /**
   * request id
   */
  uint32_t r_id;
};


/**
 * Handle for ticket consume request
 */
struct ConsumeTicketOperation
{
  /**
   * DLL
   */
  struct ConsumeTicketOperation *next;

  /**
   * DLL
   */
  struct ConsumeTicketOperation *prev;

  /**
   * Client connection
   */
  struct IdpClient *client;

  /**
   * request id
   */
  uint32_t r_id;

  /**
   * Ticket consume handle
   */
  struct RECLAIM_TICKETS_ConsumeHandle *ch;
};


/**
 * Ticket revocation request handle
 */
struct TicketRevocationOperation
{
  /**
   * DLL
   */
  struct TicketRevocationOperation *prev;

  /**
   * DLL
   */
  struct TicketRevocationOperation *next;

  /**
   * Client connection
   */
  struct IdpClient *client;

  /**
   * Revocation handle
   */
  struct RECLAIM_TICKETS_RevokeHandle *rh;

  /**
   * request id
   */
  uint32_t r_id;
};


/**
 * Ticket issue operation handle
 */
struct TicketIssueOperation
{
  /**
   * DLL
   */
  struct TicketIssueOperation *prev;

  /**
   * DLL
   */
  struct TicketIssueOperation *next;

  /**
   * Client connection
   */
  struct IdpClient *client;

  /**
   * request id
   */
  uint32_t r_id;
};


/**
 * Client list
 */
static struct IdpClient *client_list_head = NULL;

/**
 * Client list
 */
static struct IdpClient *client_list_tail = NULL;


/**
 * Cleanup attribute delete handle
 *
 * @param adh the attribute to cleanup
 */
static void
cleanup_adh (struct AttributeDeleteHandle *adh)
{
  struct TicketRecordsEntry *le;

  if (NULL != adh->ns_it)
    GNUNET_NAMESTORE_zone_iteration_stop (adh->ns_it);
  if (NULL != adh->ns_qe)
    GNUNET_NAMESTORE_cancel (adh->ns_qe);
  if (NULL != adh->label)
    GNUNET_free (adh->label);
  if (NULL != adh->claim)
    GNUNET_free (adh->claim);
  if (NULL != adh->credential)
    GNUNET_free (adh->credential);
  if (NULL != adh->existing_credentials)
    GNUNET_RECLAIM_credential_list_destroy (adh->existing_credentials);
  if (NULL != adh->existing_attributes)
    GNUNET_RECLAIM_attribute_list_destroy (adh->existing_attributes);
  while (NULL != (le = adh->tickets_to_update_head))
  {
    GNUNET_CONTAINER_DLL_remove (adh->tickets_to_update_head,
                                 adh->tickets_to_update_tail,
                                 le);
    if (NULL != le->label)
      GNUNET_free (le->label);
    if (NULL != le->data)
      GNUNET_free (le->data);
    GNUNET_free (le);
  }
  GNUNET_free (adh);
}


/**
 * Cleanup attribute store handle
 *
 * @param handle handle to clean up
 */
static void
cleanup_as_handle (struct AttributeStoreHandle *ash)
{
  if (NULL != ash->ns_qe)
    GNUNET_NAMESTORE_cancel (ash->ns_qe);
  if (NULL != ash->claim)
    GNUNET_free (ash->claim);
  if (NULL != ash->credential)
    GNUNET_free (ash->credential);
  GNUNET_free (ash);
}


/**
 * Cleanup client
 *
 * @param idp the client to clean up
 */
static void
cleanup_client (struct IdpClient *idp)
{
  struct Iterator *ai;
  struct TicketIteration *ti;
  struct TicketRevocationOperation *rop;
  struct TicketIssueOperation *iss;
  struct ConsumeTicketOperation *ct;
  struct AttributeStoreHandle *as;
  struct AttributeDeleteHandle *adh;

  while (NULL != (iss = idp->issue_op_head))
  {
    GNUNET_CONTAINER_DLL_remove (idp->issue_op_head, idp->issue_op_tail, iss);
    GNUNET_free (iss);
  }
  while (NULL != (ct = idp->consume_op_head))
  {
    GNUNET_CONTAINER_DLL_remove (idp->consume_op_head,
                                 idp->consume_op_tail,
                                 ct);
    if (NULL != ct->ch)
      RECLAIM_TICKETS_consume_cancel (ct->ch);
    GNUNET_free (ct);
  }
  while (NULL != (as = idp->store_op_head))
  {
    GNUNET_CONTAINER_DLL_remove (idp->store_op_head, idp->store_op_tail, as);
    cleanup_as_handle (as);
  }
  while (NULL != (adh = idp->delete_op_head))
  {
    GNUNET_CONTAINER_DLL_remove (idp->delete_op_head, idp->delete_op_tail, adh);
    cleanup_adh (adh);
  }

  while (NULL != (ai = idp->attr_iter_head))
  {
    GNUNET_CONTAINER_DLL_remove (idp->attr_iter_head, idp->attr_iter_tail, ai);
    GNUNET_free (ai);
  }
  while (NULL != (ai = idp->cred_iter_head))
  {
    GNUNET_CONTAINER_DLL_remove (idp->cred_iter_head, idp->cred_iter_tail,
                                 ai);
    GNUNET_free (ai);
  }

  while (NULL != (rop = idp->revoke_op_head))
  {
    GNUNET_CONTAINER_DLL_remove (idp->revoke_op_head, idp->revoke_op_tail, rop);
    if (NULL != rop->rh)
      RECLAIM_TICKETS_revoke_cancel (rop->rh);
    GNUNET_free (rop);
  }
  while (NULL != (ti = idp->ticket_iter_head))
  {
    GNUNET_CONTAINER_DLL_remove (idp->ticket_iter_head,
                                 idp->ticket_iter_tail,
                                 ti);
    if (NULL != ti->iter)
      RECLAIM_TICKETS_iteration_stop (ti->iter);
    GNUNET_free (ti);
  }
  GNUNET_free (idp);
}


/**
 * Cleanup task
 */
static void
cleanup ()
{
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Cleaning up\n");

  RECLAIM_TICKETS_deinit ();
  if (NULL != timeout_task)
    GNUNET_SCHEDULER_cancel (timeout_task);
  if (NULL != nsh)
    GNUNET_NAMESTORE_disconnect (nsh);
}


/**
 * Shutdown task
 *
 * @param cls NULL
 */
static void
do_shutdown (void *cls)
{
  GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Shutting down...\n");
  cleanup ();
}


/**
 * Sends a ticket result message to the client
 *
 * @param client the client to send to
 * @param r_id the request message ID to reply to
 * @param ticket the ticket to include (may be NULL)
 * @param success the success status of the request
 */
static void
send_ticket_result (const struct IdpClient *client,
                    uint32_t r_id,
                    const struct GNUNET_RECLAIM_Ticket *ticket,
                    const struct GNUNET_RECLAIM_PresentationList *presentations,
                    uint32_t success)
{
  struct TicketResultMessage *irm;
  struct GNUNET_MQ_Envelope *env;
  size_t pres_len = 0;

  if (NULL != presentations)
  {
    pres_len =
      GNUNET_RECLAIM_presentation_list_serialize_get_size (presentations);
  }
  env = GNUNET_MQ_msg_extra (irm,
                             pres_len,
                             GNUNET_MESSAGE_TYPE_RECLAIM_TICKET_RESULT);
  if (NULL != ticket)
  {
    irm->ticket = *ticket;
  }
  // TODO add success member
  irm->id = htonl (r_id);
  irm->presentations_len = htons (pres_len);
  if (NULL != presentations)
  {
    GNUNET_RECLAIM_presentation_list_serialize (presentations,
                                                (char*) &irm[1]);
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending TICKET_RESULT message\n");
  GNUNET_MQ_send (client->mq, env);
}


/**
 * Issue ticket result
 *
 * @param cls out ticket issue operation handle
 * @param ticket the issued ticket
 * @param presentations newly created credential presentations (NULL on error)
 * @param success issue success status (GNUNET_OK if successful)
 * @param emsg error message (NULL of success is GNUNET_OK)
 */
static void
issue_ticket_result_cb (void *cls,
                        struct GNUNET_RECLAIM_Ticket *ticket,
                        struct GNUNET_RECLAIM_PresentationList *presentations,
                        int32_t success,
                        const char *emsg)
{
  struct TicketIssueOperation *tio = cls;

  if (GNUNET_OK != success)
  {
    send_ticket_result (tio->client, tio->r_id, NULL, NULL, GNUNET_SYSERR);
    GNUNET_CONTAINER_DLL_remove (tio->client->issue_op_head,
                                 tio->client->issue_op_tail,
                                 tio);
    GNUNET_free (tio);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error issuing ticket: %s\n", emsg);
    return;
  }
  send_ticket_result (tio->client, tio->r_id,
                      ticket, presentations, GNUNET_SYSERR);
  GNUNET_CONTAINER_DLL_remove (tio->client->issue_op_head,
                               tio->client->issue_op_tail,
                               tio);
  GNUNET_free (tio);
}


/**
 * Check issue ticket message
 *
 * @cls unused
 * @im message to check
 * @return GNUNET_OK if message is ok
 */
static int
check_issue_ticket_message (void *cls, const struct IssueTicketMessage *im)
{
  uint16_t size;
  size_t attrs_len;

  size = ntohs (im->header.size);
  attrs_len = ntohs (im->attr_len);

  if (attrs_len > size - sizeof(struct IssueTicketMessage))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Handle ticket issue message
 *
 * @param cls our client
 * @param im the message
 */
static void
handle_issue_ticket_message (void *cls, const struct IssueTicketMessage *im)
{
  struct TicketIssueOperation *tio;
  struct IdpClient *idp = cls;
  struct GNUNET_RECLAIM_AttributeList *attrs;
  struct GNUNET_RECLAIM_AttributeListEntry *le;
  size_t attrs_len;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received ISSUE_TICKET message\n");
  tio = GNUNET_new (struct TicketIssueOperation);
  attrs_len = ntohs (im->attr_len);
  attrs = GNUNET_RECLAIM_attribute_list_deserialize ((char *) &im[1],
                                                     attrs_len);
  for (le = attrs->list_head; NULL != le; le = le->next)
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "List entry: %s\n", le->attribute->name);

  tio->r_id = ntohl (im->id);
  tio->client = idp;
  GNUNET_CONTAINER_DLL_insert (idp->issue_op_head, idp->issue_op_tail, tio);
  RECLAIM_TICKETS_issue (&im->identity,
                         attrs,
                         &im->rp,
                         &issue_ticket_result_cb,
                         tio);
  GNUNET_SERVICE_client_continue (idp->client);
  GNUNET_RECLAIM_attribute_list_destroy (attrs);
}


/**********************************************************
* Revocation
**********************************************************/

/**
 * Handles revocation result
 *
 * @param cls our revocation operation handle
 * @param success revocation result (GNUNET_OK if successful)
 */
static void
revoke_result_cb (void *cls, int32_t success)
{
  struct TicketRevocationOperation *rop = cls;
  struct GNUNET_MQ_Envelope *env;
  struct RevokeTicketResultMessage *trm;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Sending REVOKE_TICKET_RESULT message\n");
  rop->rh = NULL;
  env = GNUNET_MQ_msg (trm, GNUNET_MESSAGE_TYPE_RECLAIM_REVOKE_TICKET_RESULT);
  trm->id = htonl (rop->r_id);
  trm->success = htonl (success);
  GNUNET_MQ_send (rop->client->mq, env);
  GNUNET_CONTAINER_DLL_remove (rop->client->revoke_op_head,
                               rop->client->revoke_op_tail,
                               rop);
  GNUNET_free (rop);
}


/**
 * Check revocation message format
 *
 * @param cls unused
 * @param im the message to check
 * @return GNUNET_OK if message is ok
 */
static int
check_revoke_ticket_message (void *cls, const struct RevokeTicketMessage *im)
{
  uint16_t size;

  size = ntohs (im->header.size);
  if (size != sizeof(struct RevokeTicketMessage))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Handle a revocation message to a ticket.
 *
 * @param cls our client
 * @param rm the message to handle
 */
static void
handle_revoke_ticket_message (void *cls, const struct RevokeTicketMessage *rm)
{
  struct TicketRevocationOperation *rop;
  struct IdpClient *idp = cls;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received REVOKE_TICKET message\n");
  rop = GNUNET_new (struct TicketRevocationOperation);
  rop->r_id = ntohl (rm->id);
  rop->client = idp;
  GNUNET_CONTAINER_DLL_insert (idp->revoke_op_head, idp->revoke_op_tail, rop);
  rop->rh
    = RECLAIM_TICKETS_revoke (&rm->ticket, &rm->identity, &revoke_result_cb,
                              rop);
  GNUNET_SERVICE_client_continue (idp->client);
}


/**
 * Handle a ticket consume result
 *
 * @param cls our consume ticket operation handle
 * @param identity the attribute authority
 * @param attrs the attribute/claim list
 * @param success GNUNET_OK if successful
 * @param emsg error message (NULL if success=GNUNET_OK)
 */
static void
consume_result_cb (void *cls,
                   const struct GNUNET_IDENTITY_PublicKey *identity,
                   const struct GNUNET_RECLAIM_AttributeList *attrs,
                   const struct GNUNET_RECLAIM_PresentationList *presentations,
                   int32_t success,
                   const char *emsg)
{
  struct ConsumeTicketOperation *cop = cls;
  struct ConsumeTicketResultMessage *crm;
  struct GNUNET_MQ_Envelope *env;
  char *data_tmp;
  size_t attrs_len = 0;
  size_t pres_len = 0;

  if (GNUNET_OK != success)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error consuming ticket: %s\n", emsg);
  }
  attrs_len = GNUNET_RECLAIM_attribute_list_serialize_get_size (attrs);
  pres_len = GNUNET_RECLAIM_presentation_list_serialize_get_size (
    presentations);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Sending CONSUME_TICKET_RESULT message\n");
  env = GNUNET_MQ_msg_extra (crm,
                             attrs_len + pres_len,
                             GNUNET_MESSAGE_TYPE_RECLAIM_CONSUME_TICKET_RESULT);
  crm->id = htonl (cop->r_id);
  crm->attrs_len = htons (attrs_len);
  crm->presentations_len = htons (pres_len);
  crm->identity = *identity;
  crm->result = htonl (success);
  data_tmp = (char *) &crm[1];
  GNUNET_RECLAIM_attribute_list_serialize (attrs, data_tmp);
  data_tmp += attrs_len;
  GNUNET_RECLAIM_presentation_list_serialize (presentations, data_tmp);
  GNUNET_MQ_send (cop->client->mq, env);
  GNUNET_CONTAINER_DLL_remove (cop->client->consume_op_head,
                               cop->client->consume_op_tail,
                               cop);
  GNUNET_free (cop);
}


/**
 * Check a consume ticket message
 *
 * @param cls unused
 * @param cm the message to handle
 */
static int
check_consume_ticket_message (void *cls, const struct ConsumeTicketMessage *cm)
{
  uint16_t size;

  size = ntohs (cm->header.size);
  if (size != sizeof(struct ConsumeTicketMessage))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Handle a consume ticket message
 *
 * @param cls our client handle
 * @cm the message to handle
 */
static void
handle_consume_ticket_message (void *cls, const struct ConsumeTicketMessage *cm)
{
  struct ConsumeTicketOperation *cop;
  struct IdpClient *idp = cls;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received CONSUME_TICKET message\n");
  cop = GNUNET_new (struct ConsumeTicketOperation);
  cop->r_id = ntohl (cm->id);
  cop->client = idp;
  cop->ch
    = RECLAIM_TICKETS_consume (&cm->identity, &cm->ticket, &consume_result_cb,
                               cop);
  GNUNET_CONTAINER_DLL_insert (idp->consume_op_head, idp->consume_op_tail, cop);
  GNUNET_SERVICE_client_continue (idp->client);
}


/*****************************************
* Attribute store
*****************************************/


/**
 * Attribute store result handler
 *
 * @param cls our attribute store handle
 * @param success GNUNET_OK if successful
 * @param emsg error message (NULL if success=GNUNET_OK)
 */
static void
attr_store_cont (void *cls, int32_t success, const char *emsg)
{
  struct AttributeStoreHandle *ash = cls;
  struct GNUNET_MQ_Envelope *env;
  struct SuccessResultMessage *acr_msg;

  ash->ns_qe = NULL;
  GNUNET_CONTAINER_DLL_remove (ash->client->store_op_head,
                               ash->client->store_op_tail,
                               ash);

  if (GNUNET_SYSERR == success)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to store attribute %s\n",
                emsg);
    cleanup_as_handle (ash);
    GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
    return;
  }

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending SUCCESS_RESPONSE message\n");
  env = GNUNET_MQ_msg (acr_msg, GNUNET_MESSAGE_TYPE_RECLAIM_SUCCESS_RESPONSE);
  acr_msg->id = htonl (ash->r_id);
  acr_msg->op_result = htonl (GNUNET_OK);
  GNUNET_MQ_send (ash->client->mq, env);
  cleanup_as_handle (ash);
}


/**
 * Add a new attribute
 *
 * @param cls the AttributeStoreHandle
 */
static void
attr_store_task (void *cls)
{
  struct AttributeStoreHandle *ash = cls;
  struct GNUNET_GNSRECORD_Data rd[1];
  char *buf;
  char *label;
  size_t buf_size;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Storing attribute\n");
  buf_size = GNUNET_RECLAIM_attribute_serialize_get_size (ash->claim);
  buf = GNUNET_malloc (buf_size);
  // Give the ash a new id if unset
  if (GNUNET_YES == GNUNET_RECLAIM_id_is_zero (&ash->claim->id))
    GNUNET_RECLAIM_id_generate (&ash->claim->id);
  GNUNET_RECLAIM_attribute_serialize (ash->claim, buf);
  label
    = GNUNET_STRINGS_data_to_string_alloc (&ash->claim->id,
                                           sizeof (ash->claim->id));
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Encrypting with label %s\n", label);

  rd[0].data_size = buf_size;
  rd[0].data = buf;
  rd[0].record_type = GNUNET_GNSRECORD_TYPE_RECLAIM_ATTRIBUTE;
  rd[0].flags = GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
  rd[0].expiration_time = ash->exp.rel_value_us;
  ash->ns_qe = GNUNET_NAMESTORE_records_store (nsh,
                                               &ash->identity,
                                               label,
                                               1,
                                               rd,
                                               &attr_store_cont,
                                               ash);
  GNUNET_free (buf);
  GNUNET_free (label);
}


/**
 * Check an attribute store message
 *
 * @param cls unused
 * @param sam the message to check
 */
static int
check_attribute_store_message (void *cls,
                               const struct AttributeStoreMessage *sam)
{
  uint16_t size;

  size = ntohs (sam->header.size);
  if (size <= sizeof(struct AttributeStoreMessage))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Handle an attribute store message
 *
 * @param cls our client
 * @param sam the message to handle
 */
static void
handle_attribute_store_message (void *cls,
                                const struct AttributeStoreMessage *sam)
{
  struct AttributeStoreHandle *ash;
  struct IdpClient *idp = cls;
  size_t data_len;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received ATTRIBUTE_STORE message\n");

  data_len = ntohs (sam->attr_len);

  ash = GNUNET_new (struct AttributeStoreHandle);
  GNUNET_RECLAIM_attribute_deserialize ((char *) &sam[1],
                                        data_len,
                                        &ash->claim);

  ash->r_id = ntohl (sam->id);
  ash->identity = sam->identity;
  ash->exp.rel_value_us = GNUNET_ntohll (sam->exp);
  GNUNET_IDENTITY_key_get_public (&sam->identity, &ash->identity_pkey);

  GNUNET_SERVICE_client_continue (idp->client);
  ash->client = idp;
  GNUNET_CONTAINER_DLL_insert (idp->store_op_head, idp->store_op_tail, ash);
  GNUNET_SCHEDULER_add_now (&attr_store_task, ash);
}


/**
 * Credential store result handler
 *
 * @param cls our attribute store handle
 * @param success GNUNET_OK if successful
 * @param emsg error message (NULL if success=GNUNET_OK)
 */
static void
cred_store_cont (void *cls, int32_t success, const char *emsg)
{
  struct AttributeStoreHandle *ash = cls;
  struct GNUNET_MQ_Envelope *env;
  struct SuccessResultMessage *acr_msg;

  ash->ns_qe = NULL;
  GNUNET_CONTAINER_DLL_remove (ash->client->store_op_head,
                               ash->client->store_op_tail,
                               ash);

  if (GNUNET_SYSERR == success)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to store credential: %s\n",
                emsg);
    cleanup_as_handle (ash);
    GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
    return;
  }

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending SUCCESS_RESPONSE message\n");
  env = GNUNET_MQ_msg (acr_msg, GNUNET_MESSAGE_TYPE_RECLAIM_SUCCESS_RESPONSE);
  acr_msg->id = htonl (ash->r_id);
  acr_msg->op_result = htonl (GNUNET_OK);
  GNUNET_MQ_send (ash->client->mq, env);
  cleanup_as_handle (ash);
}


/**
 * Error looking up potential credential. Abort.
 *
 * @param cls our attribute store handle
 */
static void
cred_error (void *cls)
{
  struct AttributeStoreHandle *ash = cls;
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
              "Failed to check for existing credential.\n");
  cleanup_as_handle (ash);
  GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
  return;
}


/**
* Check for existing record before storing credential
*
* @param cls our attribute store handle
* @param zone zone we are iterating
* @param label label of the records
* @param rd_count record count
* @param rd records
*/
static void
cred_add_cb (void *cls,
             const struct GNUNET_IDENTITY_PrivateKey *zone,
             const char *label,
             unsigned int rd_count,
             const struct GNUNET_GNSRECORD_Data *rd)
{
  struct AttributeStoreHandle *ash = cls;
  struct GNUNET_GNSRECORD_Data rd_new[1];
  char *buf;
  size_t buf_size;

  buf_size = GNUNET_RECLAIM_credential_serialize_get_size (ash->credential);
  buf = GNUNET_malloc (buf_size);
  GNUNET_RECLAIM_credential_serialize (ash->credential, buf);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Storing new credential under `%s'.\n",
              label);
  rd_new[0].data_size = buf_size;
  rd_new[0].data = buf;
  rd_new[0].record_type = GNUNET_GNSRECORD_TYPE_RECLAIM_CREDENTIAL;
  rd_new[0].flags = GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
  rd_new[0].expiration_time = ash->exp.rel_value_us;
  ash->ns_qe = GNUNET_NAMESTORE_records_store (nsh,
                                               &ash->identity,
                                               label,
                                               1,
                                               rd_new,
                                               &cred_store_cont,
                                               ash);
  GNUNET_free (buf);
  return;
}


/**
 * Add a new credential
 *
 * @param cls the AttributeStoreHandle
 */
static void
cred_store_task (void *cls)
{
  struct AttributeStoreHandle *ash = cls;
  char *label;

  // Give the ash a new id if unset
  if (GNUNET_YES == GNUNET_RECLAIM_id_is_zero (&ash->credential->id))
    GNUNET_RECLAIM_id_generate (&ash->credential->id);
  label = GNUNET_STRINGS_data_to_string_alloc (&ash->credential->id,
                                               sizeof (ash->credential->id));
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Looking up existing data under label `%s'\n", label);
  ash->ns_qe = GNUNET_NAMESTORE_records_lookup (nsh,
                                                &ash->identity,
                                                label,
                                                &cred_error,
                                                ash,
                                                &cred_add_cb,
                                                ash);
  GNUNET_free (label);
}


/**
 * Check an credential store message
 *
 * @param cls unused
 * @param sam the message to check
 */
static int
check_credential_store_message (void *cls,
                                const struct AttributeStoreMessage *sam)
{
  uint16_t size;

  size = ntohs (sam->header.size);
  if (size <= sizeof(struct AttributeStoreMessage))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
* Handle a credential store message
*
* @param cls our client
* @param sam the message to handle
*/
static void
handle_credential_store_message (void *cls,
                                 const struct AttributeStoreMessage *sam)
{
  struct AttributeStoreHandle *ash;
  struct IdpClient *idp = cls;
  size_t data_len;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received CREDENTIAL_STORE message\n");

  data_len = ntohs (sam->attr_len);

  ash = GNUNET_new (struct AttributeStoreHandle);
  ash->credential = GNUNET_RECLAIM_credential_deserialize ((char *) &sam[1],
                                                           data_len);

  ash->r_id = ntohl (sam->id);
  ash->identity = sam->identity;
  ash->exp.rel_value_us = GNUNET_ntohll (sam->exp);
  GNUNET_IDENTITY_key_get_public (&sam->identity, &ash->identity_pkey);

  GNUNET_SERVICE_client_continue (idp->client);
  ash->client = idp;
  GNUNET_CONTAINER_DLL_insert (idp->store_op_head, idp->store_op_tail, ash);
  GNUNET_SCHEDULER_add_now (&cred_store_task, ash);
}


/**
 * Send a deletion success response
 *
 * @param adh our attribute deletion handle
 * @param success the success status
 */
static void
send_delete_response (struct AttributeDeleteHandle *adh, int32_t success)
{
  struct GNUNET_MQ_Envelope *env;
  struct SuccessResultMessage *acr_msg;

  GNUNET_CONTAINER_DLL_remove (adh->client->delete_op_head,
                               adh->client->delete_op_tail,
                               adh);

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending SUCCESS_RESPONSE message\n");
  env = GNUNET_MQ_msg (acr_msg, GNUNET_MESSAGE_TYPE_RECLAIM_SUCCESS_RESPONSE);
  acr_msg->id = htonl (adh->r_id);
  acr_msg->op_result = htonl (success);
  GNUNET_MQ_send (adh->client->mq, env);
}


/**
 * Namestore iteration within attribute deletion.
 * We need to reissue tickets with the deleted attribute removed.
 *
 * @param cls our attribute deletion handle
 * @param zone the private key of the ticket issuer
 * @param label the label of the record
 * @param rd_count number of records
 * @param rd record data
 */
static void
consistency_iter (void *cls,
                  const struct GNUNET_IDENTITY_PrivateKey *zone,
                  const char *label,
                  unsigned int rd_count,
                  const struct GNUNET_GNSRECORD_Data *rd)
{
  struct AttributeDeleteHandle *adh = cls;
  struct TicketRecordsEntry *le;
  struct GNUNET_RECLAIM_AttributeListEntry *ale;
  struct GNUNET_RECLAIM_CredentialListEntry *cle;
  int is_ticket = GNUNET_NO;
  for (int i = 0; i < rd_count; i++)
  {
    switch (rd[i].record_type)
    {
    case GNUNET_GNSRECORD_TYPE_RECLAIM_ATTRIBUTE:
      ale = GNUNET_new (struct GNUNET_RECLAIM_AttributeListEntry);
      GNUNET_RECLAIM_attribute_deserialize (rd[i].data,
                                            rd[i].data_size,
                                            &ale->attribute);
      GNUNET_CONTAINER_DLL_insert (adh->existing_attributes->list_head,
                                   adh->existing_attributes->list_tail,
                                   ale);
      break;
    case GNUNET_GNSRECORD_TYPE_RECLAIM_CREDENTIAL:
      cle = GNUNET_new (struct GNUNET_RECLAIM_CredentialListEntry);
      cle->credential = GNUNET_RECLAIM_credential_deserialize (rd[i].data,
                                                               rd[i].data_size);
      GNUNET_CONTAINER_DLL_insert (adh->existing_credentials->list_head,
                                   adh->existing_credentials->list_tail,
                                   cle);
      break;
    case GNUNET_GNSRECORD_TYPE_RECLAIM_TICKET:
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Ticket to delete found (%s)\n",
                  label);
      is_ticket = GNUNET_YES;
      break;
    default:
      break;
    }
    if (GNUNET_YES == is_ticket)
      break;
  }
  if (GNUNET_YES == is_ticket)
  {
    le = GNUNET_new (struct TicketRecordsEntry);
    le->data_size = GNUNET_GNSRECORD_records_get_size (rd_count, rd);
    le->data = GNUNET_malloc (le->data_size);
    le->rd_count = rd_count;
    le->label = GNUNET_strdup (label);
    GNUNET_GNSRECORD_records_serialize (rd_count, rd, le->data_size, le->data);
    GNUNET_CONTAINER_DLL_insert (adh->tickets_to_update_head,
                                 adh->tickets_to_update_tail,
                                 le);
  }
  GNUNET_NAMESTORE_zone_iterator_next (adh->ns_it, 1);
}


/**
 * Recursion prototype for function
 * @param cls our deletion handle
 */
static void
update_tickets (void *cls);


/**
 * Callback called when a ticket was updated
 *
 * @param cls our attribute deletion handle
 * @param success GNUNET_OK if successful
 * @param emsg error message (NULL if success=GNUNET_OK)
 */
static void
ticket_updated (void *cls, int32_t success, const char *emsg)
{
  struct AttributeDeleteHandle *adh = cls;

  adh->ns_qe = NULL;
  GNUNET_SCHEDULER_add_now (&update_tickets, adh);
}


/**
 * Update tickets: Remove shared attribute which has just been deleted.
 * This method is called recursively until all tickets are processed.
 * Eventually, the updated tickets are stored using ``update_tickets''.
 *
 * @param cls our attribute deletion handle
 */
static void
update_tickets (void *cls)
{
  struct AttributeDeleteHandle *adh = cls;
  struct TicketRecordsEntry *le;

  if (NULL == adh->tickets_to_update_head)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Finished updating tickets, success\n");
    send_delete_response (adh, GNUNET_OK);
    cleanup_adh (adh);
    return;
  }
  le = adh->tickets_to_update_head;
  GNUNET_CONTAINER_DLL_remove (adh->tickets_to_update_head,
                               adh->tickets_to_update_tail,
                               le);
  struct GNUNET_GNSRECORD_Data rd[le->rd_count];
  struct GNUNET_GNSRECORD_Data rd_new[le->rd_count - 1];
  if (GNUNET_OK != GNUNET_GNSRECORD_records_deserialize (le->data_size,
                                                         le->data,
                                                         le->rd_count,
                                                         rd))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unable to deserialize record data!\n");
    send_delete_response (adh, GNUNET_SYSERR);
    cleanup_adh (adh);
    return;
  }
  int j = 0;
  int i = 0;
  struct GNUNET_RECLAIM_AttributeListEntry *ale;
  struct GNUNET_RECLAIM_CredentialListEntry *cle;
  struct GNUNET_RECLAIM_Presentation *presentation;
  for (i = 0; i < le->rd_count; i++)
  {
    switch (rd[i].record_type)
    {
    case GNUNET_GNSRECORD_TYPE_RECLAIM_ATTRIBUTE_REF:
      for (ale = adh->existing_attributes->list_head; NULL != ale; ale =
             ale->next)
      {
        if (GNUNET_YES == GNUNET_RECLAIM_id_is_equal (rd[i].data,
                                                      &ale->attribute->id))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                      "Found attribute %s, readding...\n",
                      ale->attribute->name);
          rd_new[j] = rd[i];
          j++;
          break;   // Found and added
        }
      }
      break;
    case GNUNET_GNSRECORD_TYPE_RECLAIM_PRESENTATION:
      presentation = GNUNET_RECLAIM_presentation_deserialize (rd[i].data,
                                                              rd[i].data_size);
      for (cle = adh->existing_credentials->list_head; NULL != cle; cle =
             cle->next)
      {
        if (GNUNET_YES == GNUNET_RECLAIM_id_is_equal (
              &presentation->credential_id,
              &cle->credential->id))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                      "Found presentation for credential %s, readding...\n",
                      cle->credential->name);
          rd_new[j] = rd[i];
          j++;
          break;   // Found and added
        }
      }
      GNUNET_free (presentation);
      break;
    case GNUNET_GNSRECORD_TYPE_RECLAIM_TICKET:
      rd_new[j] = rd[i];
      j++;
      break;   // Found and added
    default:
      GNUNET_break (0);
    }
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Updating ticket with %d entries (%d before)...\n",
              j, i);
  adh->ns_qe = GNUNET_NAMESTORE_records_store (nsh,
                                               &adh->identity,
                                               le->label,
                                               j,
                                               rd_new,
                                               &ticket_updated,
                                               adh);
  GNUNET_free (le->label);
  GNUNET_free (le->data);
  GNUNET_free (le);
}


/**
 * Delete all attributes which reference credentials
 * that no longer exist
 */
static void
purge_attributes (void *cls);;

static void
offending_attr_delete_cont (void *cls, int32_t success, const char *emsg)
{
  struct AttributeDeleteHandle *adh = cls;

  adh->ns_qe = NULL;
  if (GNUNET_SYSERR == success)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Error deleting attribute %s\n",
                adh->label);
    send_delete_response (adh, GNUNET_SYSERR);
    cleanup_adh (adh);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Continuing consistency check...\n");
  GNUNET_SCHEDULER_add_now (&purge_attributes, adh);
}


/**
 * Delete all attributes which reference credentials
 * that no longer exist
 */
static void
purge_attributes (void *cls)
{
  struct AttributeDeleteHandle *adh = cls;
  struct GNUNET_RECLAIM_AttributeListEntry *ale;
  struct GNUNET_RECLAIM_CredentialListEntry *cle;

  for (ale = adh->existing_attributes->list_head; NULL != ale; ale = ale->next)
  {
    if (GNUNET_YES ==
        GNUNET_RECLAIM_id_is_zero (&ale->attribute->credential))
      continue;

    for (cle = adh->existing_credentials->list_head;
         NULL != cle; cle = cle->next)
    {
      if (GNUNET_YES !=
          GNUNET_RECLAIM_id_is_equal (&cle->credential->id,
                                      &ale->attribute->credential))
        continue;
      break;
    }
    if (NULL == cle)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Found attribute with missing credential\n");
      break;
    }
  }
  if (NULL == ale)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Attributes consistent, updating tickets.\n");
    GNUNET_SCHEDULER_add_now (&update_tickets, adh);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Attributes inconsistent, deleting offending attribute.\n");
  char *label
    = GNUNET_STRINGS_data_to_string_alloc (&ale->attribute->id,
                                           sizeof(ale->attribute->id));

  adh->ns_qe = GNUNET_NAMESTORE_records_store (nsh,
                                               &adh->identity,
                                               label,
                                               0,
                                               NULL,
                                               &offending_attr_delete_cont,
                                               adh);
  GNUNET_CONTAINER_DLL_remove (adh->existing_attributes->list_head,
                               adh->existing_attributes->list_tail,
                               ale);
  GNUNET_free (ale);
  GNUNET_free (label);
}


/**
 * Done collecting affected tickets, start updating.
 *
 * @param cls our attribute deletion handle
 */
static void
consistency_iter_fin (void *cls)
{
  struct AttributeDeleteHandle *adh = cls;
  adh->ns_it = NULL;
  GNUNET_SCHEDULER_add_now (&purge_attributes, adh);
}


/**
 * Error collecting affected tickets. Abort.
 *
 * @param cls our attribute deletion handle
 */
static void
consistency_iter_err (void *cls)
{
  struct AttributeDeleteHandle *adh = cls;

  adh->ns_it = NULL;
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
              "Namestore error on consistency check\n");
  send_delete_response (adh, GNUNET_SYSERR);
  cleanup_adh (adh);
}


/**
 * Start processing tickets which may still contain reference to deleted
 * attribute.
 *
 * @param cls attribute deletion handle
 */
static void
start_consistency_update (void *cls)
{
  struct AttributeDeleteHandle *adh = cls;

  adh->existing_attributes = GNUNET_new (struct GNUNET_RECLAIM_AttributeList);
  adh->existing_credentials = GNUNET_new (struct GNUNET_RECLAIM_CredentialList);

  adh->ns_it = GNUNET_NAMESTORE_zone_iteration_start (nsh,
                                                      &adh->identity,
                                                      &consistency_iter_err,
                                                      adh,
                                                      &consistency_iter,
                                                      adh,
                                                      &consistency_iter_fin,
                                                      adh);
}


/**
 * Attribute deleted callback
 *
 * @param cls our handle
 * @param success success status
 * @param emsg error message (NULL if success=GNUNET_OK)
 */
static void
attr_delete_cont (void *cls, int32_t success, const char *emsg)
{
  struct AttributeDeleteHandle *adh = cls;

  adh->ns_qe = NULL;
  if (GNUNET_SYSERR == success)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Error deleting attribute %s\n",
                adh->label);
    send_delete_response (adh, GNUNET_SYSERR);
    cleanup_adh (adh);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating tickets...\n");
  GNUNET_SCHEDULER_add_now (&start_consistency_update, adh);
}


/**
 * Check attribute delete message format
 *
 * @cls unused
 * @dam message to check
 */
static int
check_attribute_delete_message (void *cls,
                                const struct AttributeDeleteMessage *dam)
{
  uint16_t size;

  size = ntohs (dam->header.size);
  if (size <= sizeof(struct AttributeDeleteMessage))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Handle attribute deletion
 *
 * @param cls our client
 * @param dam deletion message
 */
static void
handle_attribute_delete_message (void *cls,
                                 const struct AttributeDeleteMessage *dam)
{
  struct AttributeDeleteHandle *adh;
  struct IdpClient *idp = cls;
  size_t data_len;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received ATTRIBUTE_DELETE message\n");

  data_len = ntohs (dam->attr_len);

  adh = GNUNET_new (struct AttributeDeleteHandle);
  GNUNET_RECLAIM_attribute_deserialize ((char *) &dam[1],
                                        data_len,
                                        &adh->claim);
  adh->credential = NULL;

  adh->r_id = ntohl (dam->id);
  adh->identity = dam->identity;
  adh->label
    = GNUNET_STRINGS_data_to_string_alloc (&adh->claim->id,
                                           sizeof(adh->claim->id));
  GNUNET_SERVICE_client_continue (idp->client);
  adh->client = idp;
  GNUNET_CONTAINER_DLL_insert (idp->delete_op_head, idp->delete_op_tail, adh);
  adh->ns_qe = GNUNET_NAMESTORE_records_store (nsh,
                                               &adh->identity,
                                               adh->label,
                                               0,
                                               NULL,
                                               &attr_delete_cont,
                                               adh);
}


/**
 * Credential deleted callback
 *
 * @param cls our handle
 * @param success success status
 * @param emsg error message (NULL if success=GNUNET_OK)
 */
static void
cred_delete_cont (void *cls, int32_t success, const char *emsg)
{
  struct AttributeDeleteHandle *adh = cls;

  adh->ns_qe = NULL;
  if (GNUNET_SYSERR == success)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Error deleting credential `%s'\n",
                adh->label);
    send_delete_response (adh, GNUNET_SYSERR);
    cleanup_adh (adh);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating tickets...\n");
  GNUNET_SCHEDULER_add_now (&start_consistency_update, adh);
}


/**
 * Check credential delete message format
 *
 * @cls unused
 * @dam message to check
 */
static int
check_credential_delete_message (void *cls,
                                 const struct AttributeDeleteMessage *dam)
{
  uint16_t size;

  size = ntohs (dam->header.size);
  if (size <= sizeof(struct AttributeDeleteMessage))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Handle credential deletion
 *
 * @param cls our client
 * @param dam deletion message
 */
static void
handle_credential_delete_message (void *cls,
                                  const struct AttributeDeleteMessage *dam)
{
  struct AttributeDeleteHandle *adh;
  struct IdpClient *idp = cls;
  size_t data_len;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received CREDENTIAL_DELETE message\n");

  data_len = ntohs (dam->attr_len);

  adh = GNUNET_new (struct AttributeDeleteHandle);
  adh->credential = GNUNET_RECLAIM_credential_deserialize ((char *) &dam[1],
                                                           data_len);
  adh->claim = NULL;

  adh->r_id = ntohl (dam->id);
  adh->identity = dam->identity;
  adh->label
    = GNUNET_STRINGS_data_to_string_alloc (&adh->credential->id,
                                           sizeof(adh->credential->id));
  GNUNET_SERVICE_client_continue (idp->client);
  adh->client = idp;
  GNUNET_CONTAINER_DLL_insert (idp->delete_op_head, idp->delete_op_tail, adh);
  adh->ns_qe = GNUNET_NAMESTORE_records_store (nsh,
                                               &adh->identity,
                                               adh->label,
                                               0,
                                               NULL,
                                               &cred_delete_cont,
                                               adh);
}


/*************************************************
 * Attribute iteration
 *************************************************/


/**
 * Done iterating over attributes
 *
 * @param cls our iterator handle
 */
static void
attr_iter_finished (void *cls)
{
  struct Iterator *ai = cls;
  struct GNUNET_MQ_Envelope *env;
  struct AttributeResultMessage *arm;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending ATTRIBUTE_RESULT message\n");
  env = GNUNET_MQ_msg (arm, GNUNET_MESSAGE_TYPE_RECLAIM_ATTRIBUTE_RESULT);
  arm->id = htonl (ai->request_id);
  arm->attr_len = htons (0);
  GNUNET_MQ_send (ai->client->mq, env);
  GNUNET_CONTAINER_DLL_remove (ai->client->attr_iter_head,
                               ai->client->attr_iter_tail,
                               ai);
  GNUNET_free (ai);
}


/**
 * Error iterating over attributes. Abort.
 *
 * @param cls our attribute iteration handle
 */
static void
attr_iter_error (void *cls)
{
  struct Iterator *ai = cls;

  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to iterate over attributes\n");
  attr_iter_finished (ai);
}


/**
 * Got record. Return if it is an attribute.
 *
 * @param cls our attribute iterator
 * @param zone zone we are iterating
 * @param label label of the records
 * @param rd_count record count
 * @param rd records
 */
static void
attr_iter_cb (void *cls,
              const struct GNUNET_IDENTITY_PrivateKey *zone,
              const char *label,
              unsigned int rd_count,
              const struct GNUNET_GNSRECORD_Data *rd)
{
  struct Iterator *ai = cls;
  struct GNUNET_MQ_Envelope *env;
  char *data_tmp;

  if ((rd_count != 1) ||
      (GNUNET_GNSRECORD_TYPE_RECLAIM_ATTRIBUTE != rd->record_type))
  {
    GNUNET_NAMESTORE_zone_iterator_next (ai->ns_it, 1);
    return;
  }
  struct AttributeResultMessage *arm;
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found attribute under: %s\n",
              label);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Sending ATTRIBUTE_RESULT message\n");
  env = GNUNET_MQ_msg_extra (arm,
                             rd->data_size,
                             GNUNET_MESSAGE_TYPE_RECLAIM_ATTRIBUTE_RESULT);
  arm->id = htonl (ai->request_id);
  arm->attr_len = htons (rd->data_size);
  GNUNET_IDENTITY_key_get_public (zone, &arm->identity);
  data_tmp = (char *) &arm[1];
  GNUNET_memcpy (data_tmp, rd->data, rd->data_size);
  GNUNET_MQ_send (ai->client->mq, env);
}


/**
 * Iterate over zone to get attributes
 *
 * @param cls our client
 * @param ais_msg the iteration message to start
 */
static void
handle_iteration_start (void *cls,
                        const struct AttributeIterationStartMessage *ais_msg)
{
  struct IdpClient *idp = cls;
  struct Iterator *ai;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received ATTRIBUTE_ITERATION_START message\n");
  ai = GNUNET_new (struct Iterator);
  ai->request_id = ntohl (ais_msg->id);
  ai->client = idp;
  ai->identity = ais_msg->identity;

  GNUNET_CONTAINER_DLL_insert (idp->attr_iter_head, idp->attr_iter_tail, ai);
  ai->ns_it = GNUNET_NAMESTORE_zone_iteration_start (nsh,
                                                     &ai->identity,
                                                     &attr_iter_error,
                                                     ai,
                                                     &attr_iter_cb,
                                                     ai,
                                                     &attr_iter_finished,
                                                     ai);
  GNUNET_SERVICE_client_continue (idp->client);
}


/**
 * Handle iteration stop message from client
 *
 * @param cls the client
 * @param ais_msg the stop message
 */
static void
handle_iteration_stop (void *cls,
                       const struct AttributeIterationStopMessage *ais_msg)
{
  struct IdpClient *idp = cls;
  struct Iterator *ai;
  uint32_t rid;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received `%s' message\n",
              "ATTRIBUTE_ITERATION_STOP");
  rid = ntohl (ais_msg->id);
  for (ai = idp->attr_iter_head; NULL != ai; ai = ai->next)
    if (ai->request_id == rid)
      break;
  if (NULL == ai)
  {
    GNUNET_break (0);
    GNUNET_SERVICE_client_drop (idp->client);
    return;
  }
  GNUNET_CONTAINER_DLL_remove (idp->attr_iter_head, idp->attr_iter_tail, ai);
  GNUNET_free (ai);
  GNUNET_SERVICE_client_continue (idp->client);
}


/**
 * Client requests next attribute from iterator
 *
 * @param cls the client
 * @param ais_msg the message
 */
static void
handle_iteration_next (void *cls,
                       const struct AttributeIterationNextMessage *ais_msg)
{
  struct IdpClient *idp = cls;
  struct Iterator *ai;
  uint32_t rid;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received ATTRIBUTE_ITERATION_NEXT message\n");
  rid = ntohl (ais_msg->id);
  for (ai = idp->attr_iter_head; NULL != ai; ai = ai->next)
    if (ai->request_id == rid)
      break;
  if (NULL == ai)
  {
    GNUNET_break (0);
    GNUNET_SERVICE_client_drop (idp->client);
    return;
  }
  GNUNET_NAMESTORE_zone_iterator_next (ai->ns_it, 1);
  GNUNET_SERVICE_client_continue (idp->client);
}


/*************************************************
 * Credential iteration
 *************************************************/


/**
 * Done iterating over credentials
 *
 * @param cls our iterator handle
 */
static void
cred_iter_finished (void *cls)
{
  struct Iterator *ai = cls;
  struct GNUNET_MQ_Envelope *env;
  struct CredentialResultMessage *arm;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending CREDENTIAL_RESULT message\n");
  env = GNUNET_MQ_msg (arm, GNUNET_MESSAGE_TYPE_RECLAIM_CREDENTIAL_RESULT);
  arm->id = htonl (ai->request_id);
  arm->credential_len = htons (0);
  GNUNET_MQ_send (ai->client->mq, env);
  GNUNET_CONTAINER_DLL_remove (ai->client->cred_iter_head,
                               ai->client->cred_iter_tail,
                               ai);
  GNUNET_free (ai);
}


/**
 * Error iterating over credentials. Abort.
 *
 * @param cls our attribute iteration handle
 */
static void
cred_iter_error (void *cls)
{
  struct Iterator *ai = cls;

  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to iterate over credentials\n");
  cred_iter_finished (ai);
}


/**
 * Got record. Return credential.
 *
 * @param cls our attribute iterator
 * @param zone zone we are iterating
 * @param label label of the records
 * @param rd_count record count
 * @param rd records
 */
static void
cred_iter_cb (void *cls,
              const struct GNUNET_IDENTITY_PrivateKey *zone,
              const char *label,
              unsigned int rd_count,
              const struct GNUNET_GNSRECORD_Data *rd)
{
  struct Iterator *ai = cls;
  struct GNUNET_MQ_Envelope *env;
  struct CredentialResultMessage *arm;
  char *data_tmp;

  if ((rd_count != 1) ||
      (GNUNET_GNSRECORD_TYPE_RECLAIM_CREDENTIAL != rd->record_type))
  {
    GNUNET_NAMESTORE_zone_iterator_next (ai->ns_it, 1);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found credential under: %s\n",
              label);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Sending CREDENTIAL_RESULT message\n");
  env = GNUNET_MQ_msg_extra (arm,
                             rd->data_size,
                             GNUNET_MESSAGE_TYPE_RECLAIM_CREDENTIAL_RESULT);
  arm->id = htonl (ai->request_id);
  arm->credential_len = htons (rd->data_size);
  GNUNET_IDENTITY_key_get_public (zone, &arm->identity);
  data_tmp = (char *) &arm[1];
  GNUNET_memcpy (data_tmp, rd->data, rd->data_size);

  GNUNET_MQ_send (ai->client->mq, env);
}


/**
 * Iterate over zone to get attributes
 *
 * @param cls our client
 * @param ais_msg the iteration message to start
 */
static void
handle_credential_iteration_start (void *cls,
                                   const struct
                                   CredentialIterationStartMessage *ais_msg)
{
  struct IdpClient *idp = cls;
  struct Iterator *ai;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received CREDENTIAL_ITERATION_START message\n");
  ai = GNUNET_new (struct Iterator);
  ai->request_id = ntohl (ais_msg->id);
  ai->client = idp;
  ai->identity = ais_msg->identity;

  GNUNET_CONTAINER_DLL_insert (idp->cred_iter_head, idp->cred_iter_tail,
                               ai);
  ai->ns_it = GNUNET_NAMESTORE_zone_iteration_start (nsh,
                                                     &ai->identity,
                                                     &cred_iter_error,
                                                     ai,
                                                     &cred_iter_cb,
                                                     ai,
                                                     &cred_iter_finished,
                                                     ai);
  GNUNET_SERVICE_client_continue (idp->client);
}


/**
 * Handle iteration stop message from client
 *
 * @param cls the client
 * @param ais_msg the stop message
 */
static void
handle_credential_iteration_stop (void *cls,
                                  const struct
                                  CredentialIterationStopMessage *ais_msg)
{
  struct IdpClient *idp = cls;
  struct Iterator *ai;
  uint32_t rid;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received `%s' message\n",
              "CREDENTIAL_ITERATION_STOP");
  rid = ntohl (ais_msg->id);
  for (ai = idp->cred_iter_head; NULL != ai; ai = ai->next)
    if (ai->request_id == rid)
      break;
  if (NULL == ai)
  {
    GNUNET_break (0);
    GNUNET_SERVICE_client_drop (idp->client);
    return;
  }
  GNUNET_CONTAINER_DLL_remove (idp->cred_iter_head, idp->cred_iter_tail,
                               ai);
  GNUNET_free (ai);
  GNUNET_SERVICE_client_continue (idp->client);
}


/**
 * Client requests next credential from iterator
 *
 * @param cls the client
 * @param ais_msg the message
 */
static void
handle_credential_iteration_next (void *cls,
                                  const struct
                                  CredentialIterationNextMessage *ais_msg)
{
  struct IdpClient *idp = cls;
  struct Iterator *ai;
  uint32_t rid;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received CREDENTIAL_ITERATION_NEXT message\n");
  rid = ntohl (ais_msg->id);
  for (ai = idp->cred_iter_head; NULL != ai; ai = ai->next)
    if (ai->request_id == rid)
      break;
  if (NULL == ai)
  {
    GNUNET_break (0);
    GNUNET_SERVICE_client_drop (idp->client);
    return;
  }
  GNUNET_NAMESTORE_zone_iterator_next (ai->ns_it, 1);
  GNUNET_SERVICE_client_continue (idp->client);
}


/******************************************************
 * Ticket iteration
 ******************************************************/

/**
 * Got a ticket. Return to client
 *
 * @param cls our ticket iterator
 * @param ticket the ticket
 */
static void
ticket_iter_cb (void *cls, struct GNUNET_RECLAIM_Ticket *ticket)
{
  struct TicketIteration *ti = cls;
  struct GNUNET_MQ_Envelope *env;
  struct TicketResultMessage *trm;

  env = GNUNET_MQ_msg (trm, GNUNET_MESSAGE_TYPE_RECLAIM_TICKET_RESULT);
  if (NULL == ticket)
  {
    /* send empty response to indicate end of list */
    GNUNET_CONTAINER_DLL_remove (ti->client->ticket_iter_head,
                                 ti->client->ticket_iter_tail,
                                 ti);
  }
  else
  {
    trm->ticket = *ticket;
  }
  trm->id = htonl (ti->r_id);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending TICKET_RESULT message\n");
  GNUNET_MQ_send (ti->client->mq, env);
  if (NULL == ticket)
    GNUNET_free (ti);
}


/**
 * Client requests a ticket iteration
 *
 * @param cls the client
 * @param tis_msg the iteration request message
 */
static void
handle_ticket_iteration_start (
  void *cls,
  const struct TicketIterationStartMessage *tis_msg)
{
  struct IdpClient *client = cls;
  struct TicketIteration *ti;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received TICKET_ITERATION_START message\n");
  ti = GNUNET_new (struct TicketIteration);
  ti->r_id = ntohl (tis_msg->id);
  ti->client = client;

  GNUNET_CONTAINER_DLL_insert (client->ticket_iter_head,
                               client->ticket_iter_tail,
                               ti);
  ti->iter
    = RECLAIM_TICKETS_iteration_start (&tis_msg->identity, &ticket_iter_cb, ti);
  GNUNET_SERVICE_client_continue (client->client);
}


/**
 * Client has had enough tickets
 *
 * @param cls the client
 * @param tis_msg the stop message
 */
static void
handle_ticket_iteration_stop (void *cls,
                              const struct TicketIterationStopMessage *tis_msg)
{
  struct IdpClient *client = cls;
  struct TicketIteration *ti;
  uint32_t rid;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received `%s' message\n",
              "TICKET_ITERATION_STOP");
  rid = ntohl (tis_msg->id);
  for (ti = client->ticket_iter_head; NULL != ti; ti = ti->next)
    if (ti->r_id == rid)
      break;
  if (NULL == ti)
  {
    GNUNET_break (0);
    GNUNET_SERVICE_client_drop (client->client);
    return;
  }
  RECLAIM_TICKETS_iteration_stop (ti->iter);
  GNUNET_CONTAINER_DLL_remove (client->ticket_iter_head,
                               client->ticket_iter_tail,
                               ti);
  GNUNET_free (ti);
  GNUNET_SERVICE_client_continue (client->client);
}


/**
 * Client requests next result.
 *
 * @param cls the client
 * @param tis_msg the message
 */
static void
handle_ticket_iteration_next (void *cls,
                              const struct TicketIterationNextMessage *tis_msg)
{
  struct IdpClient *client = cls;
  struct TicketIteration *ti;
  uint32_t rid;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received TICKET_ITERATION_NEXT message\n");
  rid = ntohl (tis_msg->id);
  for (ti = client->ticket_iter_head; NULL != ti; ti = ti->next)
    if (ti->r_id == rid)
      break;
  if (NULL == ti)
  {
    GNUNET_break (0);
    GNUNET_SERVICE_client_drop (client->client);
    return;
  }
  RECLAIM_TICKETS_iteration_next (ti->iter);
  GNUNET_SERVICE_client_continue (client->client);
}


/**
 * Main function that will be run
 *
 * @param cls closure
 * @param c the configuration used
 * @param server the service handle
 */
static void
run (void *cls,
     const struct GNUNET_CONFIGURATION_Handle *c,
     struct GNUNET_SERVICE_Handle *server)
{
  cfg = c;

  if (GNUNET_OK != RECLAIM_TICKETS_init (cfg))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unable to initialize TICKETS subsystem.\n");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  // Connect to identity and namestore services
  nsh = GNUNET_NAMESTORE_connect (cfg);
  if (NULL == nsh)
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "error connecting to namestore");
  }

  GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL);
}


/**
 * Called whenever a client is disconnected.
 *
 * @param cls closure
 * @param client identification of the client
 * @param app_ctx @a client
 */
static void
client_disconnect_cb (void *cls,
                      struct GNUNET_SERVICE_Client *client,
                      void *app_ctx)
{
  struct IdpClient *idp = app_ctx;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client %p disconnected\n", client);
  GNUNET_CONTAINER_DLL_remove (client_list_head,
                               client_list_tail,
                               idp);
  cleanup_client (idp);
}


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

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client %p connected\n", client);
  idp = GNUNET_new (struct IdpClient);
  idp->client = client;
  idp->mq = mq;
  GNUNET_CONTAINER_DLL_insert (client_list_head,
                               client_list_tail,
                               idp);
  return idp;
}


/**
 * Define "main" method using service macro.
 */
GNUNET_SERVICE_MAIN (
  "reclaim",
  GNUNET_SERVICE_OPTION_NONE,
  &run,
  &client_connect_cb,
  &client_disconnect_cb,
  NULL,
  GNUNET_MQ_hd_var_size (attribute_store_message,
                         GNUNET_MESSAGE_TYPE_RECLAIM_ATTRIBUTE_STORE,
                         struct AttributeStoreMessage,
                         NULL),
  GNUNET_MQ_hd_var_size (credential_store_message,
                         GNUNET_MESSAGE_TYPE_RECLAIM_CREDENTIAL_STORE,
                         struct AttributeStoreMessage,
                         NULL),
  GNUNET_MQ_hd_var_size (attribute_delete_message,
                         GNUNET_MESSAGE_TYPE_RECLAIM_ATTRIBUTE_DELETE,
                         struct AttributeDeleteMessage,
                         NULL),
  GNUNET_MQ_hd_var_size (credential_delete_message,
                         GNUNET_MESSAGE_TYPE_RECLAIM_CREDENTIAL_DELETE,
                         struct AttributeDeleteMessage,
                         NULL),
  GNUNET_MQ_hd_fixed_size (iteration_start,
                           GNUNET_MESSAGE_TYPE_RECLAIM_ATTRIBUTE_ITERATION_START,
                           struct AttributeIterationStartMessage,
                           NULL),
  GNUNET_MQ_hd_fixed_size (iteration_next,
                           GNUNET_MESSAGE_TYPE_RECLAIM_ATTRIBUTE_ITERATION_NEXT,
                           struct AttributeIterationNextMessage,
                           NULL),
  GNUNET_MQ_hd_fixed_size (iteration_stop,
                           GNUNET_MESSAGE_TYPE_RECLAIM_ATTRIBUTE_ITERATION_STOP,
                           struct AttributeIterationStopMessage,
                           NULL),
  GNUNET_MQ_hd_fixed_size (credential_iteration_start,
                           GNUNET_MESSAGE_TYPE_RECLAIM_CREDENTIAL_ITERATION_START,
                           struct CredentialIterationStartMessage,
                           NULL),
  GNUNET_MQ_hd_fixed_size (credential_iteration_next,
                           GNUNET_MESSAGE_TYPE_RECLAIM_CREDENTIAL_ITERATION_NEXT,
                           struct CredentialIterationNextMessage,
                           NULL),
  GNUNET_MQ_hd_fixed_size (credential_iteration_stop,
                           GNUNET_MESSAGE_TYPE_RECLAIM_CREDENTIAL_ITERATION_STOP,
                           struct CredentialIterationStopMessage,
                           NULL),

  GNUNET_MQ_hd_var_size (issue_ticket_message,
                         GNUNET_MESSAGE_TYPE_RECLAIM_ISSUE_TICKET,
                         struct IssueTicketMessage,
                         NULL),
  GNUNET_MQ_hd_var_size (consume_ticket_message,
                         GNUNET_MESSAGE_TYPE_RECLAIM_CONSUME_TICKET,
                         struct ConsumeTicketMessage,
                         NULL),
  GNUNET_MQ_hd_fixed_size (ticket_iteration_start,
                           GNUNET_MESSAGE_TYPE_RECLAIM_TICKET_ITERATION_START,
                           struct TicketIterationStartMessage,
                           NULL),
  GNUNET_MQ_hd_fixed_size (ticket_iteration_next,
                           GNUNET_MESSAGE_TYPE_RECLAIM_TICKET_ITERATION_NEXT,
                           struct TicketIterationNextMessage,
                           NULL),
  GNUNET_MQ_hd_fixed_size (ticket_iteration_stop,
                           GNUNET_MESSAGE_TYPE_RECLAIM_TICKET_ITERATION_STOP,
                           struct TicketIterationStopMessage,
                           NULL),
  GNUNET_MQ_hd_var_size (revoke_ticket_message,
                         GNUNET_MESSAGE_TYPE_RECLAIM_REVOKE_TICKET,
                         struct RevokeTicketMessage,
                         NULL),
  GNUNET_MQ_handler_end ());
/* end of gnunet-service-reclaim.c */
