/*
  This file is part of TALER
  Copyright (C) 2014-2024 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published
  by the Free Software Foundation; either version 3, or (at your
  option) any later version.

  TALER 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 General Public License for more details.

  You should have received a copy of the GNU General Public
  License along with TALER; see the file COPYING.  If not, see
  <http://www.gnu.org/licenses/>
*/

/**
 * @file lib/donau_api_handle.c
 * @brief Implementation of the "handle" component of the donau's HTTP API
 * @author Sree Harsha Totakura <sreeharsha@totakura.in>
 * @author Christian Grothoff
 * @author Lukas Matyja
 */
#include <gnunet/gnunet_curl_lib.h>
#include <taler/taler_json_lib.h>
#include "donau_service.h"
#include "donau_api_curl_defaults.h"
#include "donau_util.h"
#include "donau_json_lib.h"
#include <sodium.h>


/**
 * Which version of the Donau protocol is implemented
 * by this library?  Used to determine compatibility.
 */
#define DONAU_PROTOCOL_CURRENT 0

/**
 * How many versions are we backwards compatible with?
 */
#define DONAU_PROTOCOL_AGE 0

/**
 * Set to 1 for extra debug logging.
 */
#define DEBUG 0

/**
 * Current version for (local) JSON serialization of persisted
 * /keys data.
 */
#define DONAU_SERIALIZATION_FORMAT_VERSION 0

/**
 * How far off do we allow key liftimes to be?
 */
#define LIFETIME_TOLERANCE GNUNET_TIME_UNIT_HOURS

/**
 * If the "Expire" cache control header is missing, for
 * how long do we assume the reply to be valid at least?
 */
#define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS

/**
 * If the "Expire" cache control header is missing, for
 * how long do we assume the reply to be valid at least?
 */
#define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \
          GNUNET_TIME_UNIT_MINUTES, 2)


/**
 * Handle for a GET /keys request.
 */
struct DONAU_GetKeysHandle
{

  /**
   * The donau base URL (i.e. "http://donau.taler.net/")
   */
  char *donau_url;

  /**
   * The url for the /keys request.
   */
  char *url;

  /**
   * Previous /keys response, NULL for none.
   */
  struct DONAU_Keys *prev_keys; // not used, as keys are always completely reloaded

  /**
   * Entry for this request with the `struct GNUNET_CURL_Context`.
   */
  struct GNUNET_CURL_Job *job;

  /**
   * Expiration time according to "Expire:" header.
   * 0 if not provided by the server.
   */
  struct GNUNET_TIME_Timestamp expire; // not used -> no expiration, always 0

  /**
   * Function to call with the donau's certification data,
   * NULL if this has already been done.
   */
  DONAU_GetKeysCallback cert_cb;

  /**
   * Closure to pass to @e cert_cb.
   */
  void *cert_cb_cls;

};


#define EXITIF(cond)                                              \
        do {                                                            \
          if (cond) { GNUNET_break (0); goto EXITIF_exit; }             \
        } while (0)

/**
 * Parse a donau's signing key encoded in JSON.
 *
 * @param[out] sign_key where to return the result
 * @param sign_key_obj json to parse
 * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if @a sign_key_obj
 * is malformed.
 */
static enum GNUNET_GenericReturnValue
parse_json_signkey (struct DONAU_SigningPublicKeyAndValidity *sign_key,
                    const json_t *sign_key_obj)
{
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_fixed_auto ("key",
                                 &sign_key->key),
    GNUNET_JSON_spec_timestamp ("stamp_start",
                                &sign_key->valid_from),
    GNUNET_JSON_spec_timestamp ("stamp_expire",
                                &sign_key->expire_sign),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (sign_key_obj,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Parse a donau's donation unit key encoded in JSON.
 *
 * @param[out] du where to return the result
 * @param donation_unit_obj json to parse
 * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if @a donation_unit_obj
 * is malformed.
 */
static enum GNUNET_GenericReturnValue
parse_json_donation_unit (struct DONAU_DonationUnitInformation *du,
                          const json_t *donation_unit_obj)
{
  struct GNUNET_JSON_Specification spec[] = {
    DONAU_JSON_spec_donation_unit_pub ("donation_unit_pub",
                                       &du->key),
    GNUNET_JSON_spec_uint64 ("year",
                             &du->year),
    GNUNET_JSON_spec_bool ("lost",
                           &du->lost),
    TALER_JSON_spec_amount_any ("value",
                                &du->value),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (donation_unit_obj,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  return GNUNET_OK;
}


/**
 * Decode the JSON in @a resp_obj from the /keys response
 * and store the data in the @a key_data.
 *
 * @param[in] resp_obj JSON object to parse
 * @param[out] key_data where to store the results we decoded
 * @param[out] vc where to store version compatibility data
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 * (malformed JSON)
 */
static enum GNUNET_GenericReturnValue
decode_keys_json (const json_t *resp_obj,
                  struct DONAU_Keys *key_data,
                  enum DONAU_VersionCompatibility *vc)
{
  const json_t *sign_keys_array;
  const json_t *donation_units_array;

  if (JSON_OBJECT != json_typeof (resp_obj))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
#if DEBUG
  json_dumpf (resp_obj,
              stderr,
              JSON_INDENT (2));
#endif
  /* check the version first */
  {
    const char *ver;
    unsigned int age;
    unsigned int revision;
    unsigned int current;
    char dummy;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_string ("version",
                               &ver),
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        GNUNET_JSON_parse (resp_obj,
                           spec,
                           NULL, NULL))
    {
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    if (3 != sscanf (ver,
                     "%u:%u:%u%c",
                     &current,
                     &revision,
                     &age,
                     &dummy))
    {
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    *vc = DONAU_VC_MATCH; // 0
    if (DONAU_PROTOCOL_CURRENT < current)
    {
      *vc |= DONAU_VC_NEWER; // 4
      if (DONAU_PROTOCOL_CURRENT < current - age)
        *vc |= DONAU_VC_INCOMPATIBLE; // 1
    }
    if (DONAU_PROTOCOL_CURRENT > current)
    {
      *vc |= DONAU_VC_OLDER; // 2
      if (DONAU_PROTOCOL_CURRENT - DONAU_PROTOCOL_AGE > current)
        *vc |= DONAU_VC_INCOMPATIBLE; // 1
    }
    key_data->version = GNUNET_strdup (ver);
  }

  {
    const char *currency;
    struct GNUNET_JSON_Specification mspec[] = {
      GNUNET_JSON_spec_array_const (
        "signkeys",
        &sign_keys_array),
      GNUNET_JSON_spec_string (
        "currency",
        &currency),
      GNUNET_JSON_spec_array_const (
        "donation_units",
        &donation_units_array),
      GNUNET_JSON_spec_end ()
    };
    const char *emsg;
    unsigned int eline;

    if (GNUNET_OK !=
        GNUNET_JSON_parse (resp_obj,
                           mspec,
                           &emsg,
                           &eline))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Parsing /keys failed for `%s' (%u)\n",
                  emsg,
                  eline);
      EXITIF (1);
    }

    key_data->currency = GNUNET_strdup (currency);
  }

  /* parse the signing keys */
  key_data->num_sign_keys
    = json_array_size (sign_keys_array);
  if (0 != key_data->num_sign_keys)
  {
    json_t *sign_key_obj;
    unsigned int index;

    key_data->sign_keys
      = GNUNET_new_array (key_data->num_sign_keys,
                          struct DONAU_SigningPublicKeyAndValidity);
    json_array_foreach (sign_keys_array, index, sign_key_obj) {
      EXITIF (GNUNET_SYSERR ==
              parse_json_signkey (&key_data->sign_keys[index],
                                  sign_key_obj));
    }
  }

  /*
   * Parse the donation unit keys
   */
  key_data->num_donation_unit_keys
    = json_array_size (donation_units_array);
  if (0 != key_data->num_donation_unit_keys)
  {
    json_t *donation_unit_obj;
    size_t index;

    key_data->donation_unit_keys
      = GNUNET_new_array (key_data->num_donation_unit_keys,
                          struct DONAU_DonationUnitInformation);
    json_array_foreach (donation_units_array, index, donation_unit_obj) {
      EXITIF (GNUNET_SYSERR ==
              parse_json_donation_unit (&key_data->donation_unit_keys[index],
                                        donation_unit_obj));
    }
  }

  return GNUNET_OK;

EXITIF_exit:
  *vc = DONAU_VC_PROTOCOL_ERROR;
  return GNUNET_SYSERR;
}


/**
 * Callback used when downloading the reply to a /keys request
 * is complete.
 *
 * @param cls the `struct KeysRequest`
 * @param response_code HTTP response code, 0 on error
 * @param resp_obj parsed JSON result, NULL on error
 */
static void
keys_completed_cb (void *cls,
                   long response_code,
                   const void *resp_obj)
{
  struct DONAU_GetKeysHandle *gkh = cls;
  const json_t *j = resp_obj;
  struct DONAU_Keys *kd = NULL;
  struct DONAU_KeysResponse kresp = {
    .hr.reply = j,
    .hr.http_status = (unsigned int) response_code,
    .details.ok.compat = DONAU_VC_PROTOCOL_ERROR
  };

  gkh->job = NULL;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Received keys from URL `%s' with status %ld.\n",
              gkh->url,
              response_code);
  switch (response_code)
  {
  case 0:
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Failed to receive /keys response from donau %s\n",
                gkh->donau_url);
    break;
  case MHD_HTTP_OK:
    if (NULL == j)
    {
      GNUNET_break (0);
      response_code = 0;
      break;
    }
    kd = GNUNET_new (struct DONAU_Keys);
    kd->donau_url = GNUNET_strdup (gkh->donau_url);

    if (GNUNET_OK !=
        decode_keys_json (j,
                          kd,
                          &kresp.details.ok.compat))
    {
      TALER_LOG_ERROR ("Could not decode /keys response\n");
      kd->rc = 1;
      DONAU_keys_decref (kd);
      kd = NULL;
      kresp.hr.http_status = 0;
      kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
      break;
    }
    kd->rc = 1;

    kresp.details.ok.keys = kd;
    break;
  case MHD_HTTP_BAD_REQUEST:
  case MHD_HTTP_UNAUTHORIZED:
  case MHD_HTTP_FORBIDDEN:
  case MHD_HTTP_NOT_FOUND:
    if (NULL == j)
    {
      kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
      kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    }
    else
    {
      kresp.hr.ec = TALER_JSON_get_error_code (j);
      kresp.hr.hint = TALER_JSON_get_error_hint (j);
    }
    break;
  default:
    if (NULL == j)
    {
      kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
      kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    }
    else
    {
      kresp.hr.ec = TALER_JSON_get_error_code (j);
      kresp.hr.hint = TALER_JSON_get_error_hint (j);
    }
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u/%d\n",
                (unsigned int) response_code,
                (int) kresp.hr.ec);
    break;
  }
  gkh->cert_cb (gkh->cert_cb_cls,
                &kresp,
                kd);
  DONAU_get_keys_cancel (gkh);
}


struct DONAU_GetKeysHandle *
DONAU_get_keys (
  struct GNUNET_CURL_Context *ctx,
  const char *url,
  DONAU_GetKeysCallback cert_cb,
  void *cert_cb_cls)
{
  struct DONAU_GetKeysHandle *gkh;
  CURL *eh;

  TALER_LOG_DEBUG ("Connecting to the donau (%s)\n",
                   url);
  gkh = GNUNET_new (struct DONAU_GetKeysHandle);
  gkh->donau_url = GNUNET_strdup (url);
  gkh->cert_cb = cert_cb;
  gkh->cert_cb_cls = cert_cb_cls;
  gkh->url = TALER_url_join (url,
                             "keys",
                             NULL);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Requesting keys with URL `%s'.\n",
              gkh->url);
  eh = DONAU_curl_easy_get_ (gkh->url);
  if (NULL == eh)
  {
    GNUNET_break (0);
    GNUNET_free (gkh->donau_url);
    GNUNET_free (gkh->url);
    GNUNET_free (gkh);
    return NULL;
  }
  GNUNET_break (CURLE_OK ==
                curl_easy_setopt (eh,
                                  CURLOPT_VERBOSE,
                                  0));
  GNUNET_break (CURLE_OK ==
                curl_easy_setopt (eh,
                                  CURLOPT_TIMEOUT,
                                  120 /* seconds */));
  gkh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
                                               eh,
                                               &keys_completed_cb,
                                               gkh);
  return gkh;
}


void
DONAU_get_keys_cancel (
  struct DONAU_GetKeysHandle *gkh)
{
  if (NULL != gkh->job)
  {
    GNUNET_CURL_job_cancel (gkh->job);
    gkh->job = NULL;
  }
  // DONAU_keys_decref (gkh->prev_keys);
  GNUNET_free (gkh->donau_url);
  GNUNET_free (gkh->url);
  GNUNET_free (gkh);
}


const struct DONAU_DonationUnitInformation *
DONAU_get_donation_unit_key (
  const struct DONAU_Keys *keys,
  const struct DONAU_DonationUnitPublicKey *pk)
{
  for (unsigned int i = 0; i<keys->num_donation_unit_keys; i++)
    if (0 ==
        DONAU_donation_unit_pub_cmp (pk,
                                     &keys->donation_unit_keys[i].key))
      return &keys->donation_unit_keys[i];
  return NULL;
}


const struct DONAU_DonationUnitInformation *
DONAU_get_donation_unit_key_by_hash (
  const struct DONAU_Keys *keys,
  const struct DONAU_DonationUnitHashP *hc)
{
  for (unsigned int i = 0; i<keys->num_donation_unit_keys; i++)
    // memcmp needs two pointer of the same type
    if (0 == GNUNET_memcmp (&hc->hash,
                            &keys->donation_unit_keys[i].key.bsign_pub_key->
                            pub_key_hash))
      return &keys->donation_unit_keys[i];
  return NULL;
}


bool
DONAU_compute_salted_tax_id_hash (const char *donor_tax_id,
                                  const char *salt,
                                  unsigned char out_hash[512 / 8])
{
  crypto_hash_sha512_state st;

  if ( (NULL == donor_tax_id) ||
       (NULL == salt) ||
       (NULL == out_hash) )
  {
    GNUNET_break (0);
    return false;
  }
  /* FIXME: use GNUnet wrapper */
  crypto_hash_sha512_init (&st);
  crypto_hash_sha512_update (&st,
                             (const unsigned char *) donor_tax_id,
                             strlen (donor_tax_id) + 1);
  crypto_hash_sha512_update (&st,
                             (const unsigned char *) salt,
                             strlen (salt) + 1);
  crypto_hash_sha512_final (&st,
                            out_hash);
  return true;
}


/**
 * Local helper for the #DONAU_select_donation_unit_keys_for_amount()
 */
struct DUEntry
{
  struct TALER_Amount value;
  const struct DONAU_DonationUnitPublicKey *pub; /* points into keys */
};

/**
 * Small helper function for sorting
 */
static int
du_amount_desc_cmp (const void *a,
                    const void *b)
{
  const struct DUEntry *ea = a;
  const struct DUEntry *eb = b;

  int c = TALER_amount_cmp (&ea->value,
                            &eb->value);
  /* Descending */
  return (c < 0) ? 1 : (c > 0 ? -1 : 0);
}


enum GNUNET_GenericReturnValue
DONAU_select_donation_unit_keys_for_amount (
  const struct DONAU_Keys *keys,
  const struct TALER_Amount *requested_amount,
  uint64_t year,
  struct DONAU_DonationUnitPublicKey **out_keys,
  uint32_t *out_len)
{
  struct DONAU_DonationUnitPublicKey *result = NULL;
  uint32_t result_len = 0;
  struct TALER_Amount remaining, zero;
  struct DUEntry *duv = NULL;
  unsigned int n_duv = 0;
  unsigned int i = 0;

  if ( (NULL == keys) ||
       (NULL == requested_amount) ||
       (NULL == out_keys) ||
       (NULL == out_len) )
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }

  if (0 != strcasecmp (keys->currency,
                       requested_amount->currency))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }

  remaining = *requested_amount;
  TALER_amount_set_zero (keys->currency,
                         &zero);

  if (0 == TALER_amount_cmp (&remaining,
                             &zero))
  {
    *out_keys = NULL;
    *out_len = 0;
    return GNUNET_OK;
  }

  /* Build and sort (desc) the eligible units */
  for (unsigned int j = 0;
       j < keys->num_donation_unit_keys;
       j++)
  {
    const struct DONAU_DonationUnitInformation *du
      = &keys->donation_unit_keys[j];

    if (du->lost)
      continue;
    if (du->year != year)
      continue;

    GNUNET_array_grow (duv,
                       n_duv,
                       n_duv + 1);
    duv[n_duv - 1].value = du->value;
    duv[n_duv - 1].pub = &du->key;
  }

  if (0 == n_duv)
  {
    *out_keys = NULL;
    *out_len = 0;
    return GNUNET_NO;
  }

  qsort (duv,
         n_duv,
         sizeof(struct DUEntry),
         &du_amount_desc_cmp);

  while (i < n_duv)
  {
    int cmp = TALER_amount_cmp (&duv[i].value,
                                &remaining);

    if (cmp <= 0)
    {
      /* Take as many as we can of duv[i] without overshooting */
      for (;;)
      {
        struct TALER_Amount tmp;
        int rc;

        if (TALER_amount_cmp (&duv[i].value,
                              &remaining) > 0)
          break;

        GNUNET_array_append (result,
                             result_len,
                             *duv[i].pub);

        rc = TALER_amount_subtract (&tmp,
                                    &remaining,
                                    &duv[i].value);

        if (TALER_AAR_RESULT_ZERO == rc)
        {
          remaining = tmp; /* zero */
          GNUNET_free (duv);
          *out_keys = result;
          *out_len  = result_len;
          return GNUNET_OK; /* exact match */
        }
        if (TALER_AAR_RESULT_POSITIVE == rc)
        {
          remaining = tmp; /* keep taking this same value */
          continue;
        }

        /* Shouldn't happen because we guard with cmp <= 0 */
        GNUNET_break (0);
        GNUNET_free (duv);
        GNUNET_array_grow (result, result_len, 0);
        *out_keys = NULL;
        *out_len  = 0;
        return GNUNET_SYSERR;
      }
      /* current no longer fits, move to next smaller */
      i++;
      continue;
    }
    i++;
  }

  /* No exact combination found */
  GNUNET_free (duv);
  GNUNET_array_grow (result, result_len, 0);
  *out_keys = NULL;
  *out_len  = 0;
  return GNUNET_NO;
}


enum GNUNET_GenericReturnValue
DONAU_get_donation_amount_from_bkps (
  const struct DONAU_Keys *keys,
  const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps,
  size_t num_bkps,
  uint64_t year,
  struct TALER_Amount *sum_out)
{
  /* Sanity-checks */
  if (NULL == bkps)
  {
    GNUNET_break (0);
    return GNUNET_NO;
  }
  if (0 == num_bkps)
  {
    GNUNET_break (0);
    return GNUNET_NO;
  }

  TALER_amount_set_zero (keys->currency,
                         sum_out);
  if (GNUNET_YES ==
      DONAU_check_bkps_duplication (bkps,
                                    num_bkps))
  {
    GNUNET_break (0);
    return GNUNET_NO;
  }

  for (size_t i = 0; i < num_bkps; i++)
  {
    const struct DONAU_DonationUnitInformation *dui =
      DONAU_get_donation_unit_key_by_hash (keys,
                                           &bkps[i].h_donation_unit_pub);

    if (NULL == dui)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Donation unit public key unknown\n");
      return GNUNET_NO;
    }
    if (dui->year != year)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Donation sum for year %lu requested, but "
                  "donation unit is for year %lu\n",
                  (unsigned long) year,
                  (unsigned long) dui->year);
      return GNUNET_NO;
    }

    switch (TALER_amount_add (sum_out,
                              sum_out,
                              &dui->value))
    {
    case TALER_AAR_RESULT_POSITIVE:
    case TALER_AAR_RESULT_ZERO:
      /* Zero a bit strange, but let's say that it's fine to some extent */
      break;

    case TALER_AAR_INVALID_NEGATIVE_RESULT:
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Accumulation would go negative\n");
      GNUNET_break (0);
      return GNUNET_SYSERR;

    case TALER_AAR_INVALID_RESULT_OVERFLOW:
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Accumulation overflow\n");
      GNUNET_break (0);
      return GNUNET_SYSERR;

    case TALER_AAR_INVALID_NORMALIZATION_FAILED:
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Normalization failed during add\n");
      GNUNET_break (0);
      return GNUNET_SYSERR;

    case TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE:
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Currency mismatch during add\n");
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
  }
  return GNUNET_OK;
}


bool
DONAU_check_bkps_duplication (
  const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps,
  const size_t num_bkps)
{
  if ( (NULL == bkps) ||
       (num_bkps < 2) )
    return GNUNET_NO;

  for (size_t i = 0; i < num_bkps - 1; i++)
    for (size_t j = i + 1; j < num_bkps; j++)
      if (0 == GNUNET_memcmp (&bkps[i].blinded_udi,
                              &bkps[j].blinded_udi))
        return GNUNET_YES;

  return GNUNET_NO;
}


struct DONAU_Keys *
DONAU_keys_incref (struct DONAU_Keys *keys)
{
  GNUNET_assert (keys->rc < UINT_MAX);
  keys->rc++;
  return keys;
}


void
DONAU_keys_decref (struct DONAU_Keys *keys)
{
  if (NULL == keys)
    return;
  GNUNET_assert (0 < keys->rc);
  keys->rc--;
  if (0 != keys->rc)
    return;
  GNUNET_array_grow (keys->sign_keys,
                     keys->num_sign_keys,
                     0);
  for (unsigned int i = 0; i<keys->num_donation_unit_keys; i++)
    DONAU_donation_unit_pub_free (&keys->donation_unit_keys[i].key);

  GNUNET_array_grow (keys->donation_unit_keys,
                     keys->donation_unit_keys_size,
                     0);
  GNUNET_free (keys->version);
  GNUNET_free (keys->currency);
  GNUNET_free (keys->donau_url);
  GNUNET_free (keys);
}


struct DONAU_Keys *
DONAU_keys_from_json (const json_t *j)
{
  const json_t *jkeys;
  const char *url;
  uint32_t version;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_uint32 ("version",
                             &version),
    GNUNET_JSON_spec_object_const ("keys",
                                   &jkeys),
    GNUNET_JSON_spec_string ("donau_url",
                             &url),
    GNUNET_JSON_spec_end ()
  };
  struct DONAU_Keys *keys;
  enum DONAU_VersionCompatibility compat;

  if (NULL == j)
    return NULL;
  if (GNUNET_OK !=
      GNUNET_JSON_parse (j,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break_op (0);
    return NULL;
  }
  if (0 != version)
  {
    return NULL; /* unsupported version */
  }
  keys = GNUNET_new (struct DONAU_Keys);
  if (GNUNET_OK !=
      decode_keys_json (jkeys,
                        keys,
                        &compat))
  {
    GNUNET_break (0);
    return NULL;
  }
  keys->rc = 1;
  keys->donau_url = GNUNET_strdup (url);
  return keys;
}


/**
 * Data we track per donation unit group.
 */
struct GroupData
{
  /**
   * The json blob with the group meta-data and list of donation units
   */
  json_t *json;

  /**
   * Meta data for this group.
   */
  struct DONAU_DonationUnitGroup meta;
};


json_t *
DONAU_keys_to_json (const struct DONAU_Keys *kd)
{
  // --- Create the array of signkeys (unchanged) ---
  json_t *signkeys = json_array ();
  json_t *keys;
  json_t *donation_units;
  json_t *currency_spec = NULL;

  GNUNET_assert (NULL != signkeys);
  for (unsigned int i = 0; i < kd->num_sign_keys; i++)
  {
    const struct DONAU_SigningPublicKeyAndValidity *sk = &kd->sign_keys[i];
    json_t *signkey;

    signkey = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_data_auto ("key",
                                  &sk->key),
      GNUNET_JSON_pack_timestamp ("stamp_start",
                                  sk->valid_from),
      GNUNET_JSON_pack_timestamp ("stamp_expire",
                                  sk->expire_sign));
    GNUNET_assert (NULL != signkey);
    GNUNET_assert (0 ==
                   json_array_append_new (signkeys,
                                          signkey));
  }

  // --- Create the array of donation_units ---
  donation_units = json_array ();
  GNUNET_assert (NULL != donation_units);
  for (unsigned int i = 0; i < kd->num_donation_unit_keys; i++)
  {
    const struct DONAU_DonationUnitInformation *du = &kd->donation_unit_keys[i];

    // Build the JSON for one donation unit
    json_t *donation_unit_obj = GNUNET_JSON_PACK (
      DONAU_JSON_pack_donation_unit_pub ("donation_unit_pub", &du->key),
      GNUNET_JSON_pack_uint64 ("year", du->year),
      GNUNET_JSON_pack_bool ("lost", du->lost),
      TALER_JSON_pack_amount ("value", &du->value)
      );

    GNUNET_assert (NULL != donation_unit_obj);
    GNUNET_assert (0 ==
                   json_array_append_new (donation_units,
                                          donation_unit_obj));
  }

  if (NULL != kd->currency_specification.name)
  {
    currency_spec = TALER_JSON_currency_specs_to_json (
      &kd->currency_specification
      );
  }

  keys = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_string ("version",
                             kd->version),
    GNUNET_JSON_pack_string ("currency",
                             kd->currency),
    GNUNET_JSON_pack_array_steal ("signkeys",
                                  signkeys),
    GNUNET_JSON_pack_array_steal ("donation_units",
                                  donation_units)
    );

  if (NULL != currency_spec)
  {
    json_object_set_new (keys,
                         "currency_specification",
                         currency_spec);
  }

  return GNUNET_JSON_PACK (
    GNUNET_JSON_pack_uint64 ("version",
                             DONAU_SERIALIZATION_FORMAT_VERSION),
    GNUNET_JSON_pack_string ("donau_url",
                             kd->donau_url),
    GNUNET_JSON_pack_object_steal ("keys",
                                   keys));
}


/* end of donau_api_handle.c */
