donau

Donation authority for GNU Taler (experimental)
Log | Files | Refs | Submodules | README | LICENSE

donau_api_handle.c (27460B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it
      6   under the terms of the GNU General Public License as published
      7   by the Free Software Foundation; either version 3, or (at your
      8   option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not, see
     17   <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file lib/donau_api_handle.c
     22  * @brief Implementation of the "handle" component of the donau's HTTP API
     23  * @author Sree Harsha Totakura <sreeharsha@totakura.in>
     24  * @author Christian Grothoff
     25  * @author Lukas Matyja
     26  */
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include <taler/taler_json_lib.h>
     29 #include "donau_service.h"
     30 #include "donau_api_curl_defaults.h"
     31 #include "donau_util.h"
     32 #include "donau_json_lib.h"
     33 #include <sodium.h>
     34 
     35 
     36 /**
     37  * Which version of the Donau protocol is implemented
     38  * by this library?  Used to determine compatibility.
     39  */
     40 #define DONAU_PROTOCOL_CURRENT 1
     41 
     42 /**
     43  * How many versions are we backwards compatible with?
     44  */
     45 #define DONAU_PROTOCOL_AGE 1
     46 
     47 /**
     48  * Set to 1 for extra debug logging.
     49  */
     50 #define DEBUG 0
     51 
     52 /**
     53  * Current version for (local) JSON serialization of persisted
     54  * /keys data.
     55  */
     56 #define DONAU_SERIALIZATION_FORMAT_VERSION 0
     57 
     58 /**
     59  * How far off do we allow key lifetimes to be?
     60  */
     61 #define LIFETIME_TOLERANCE GNUNET_TIME_UNIT_HOURS
     62 
     63 /**
     64  * If the "Expire" cache control header is missing, for
     65  * how long do we assume the reply to be valid at least?
     66  */
     67 #define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS
     68 
     69 /**
     70  * If the "Expire" cache control header is missing, for
     71  * how long do we assume the reply to be valid at least?
     72  */
     73 #define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \
     74           GNUNET_TIME_UNIT_MINUTES, 2)
     75 
     76 
     77 /**
     78  * Handle for a GET /keys request.
     79  */
     80 struct DONAU_GetKeysHandle
     81 {
     82 
     83   /**
     84    * The donau base URL (i.e. "http://donau.taler.net/")
     85    */
     86   char *donau_url;
     87 
     88   /**
     89    * The url for the /keys request.
     90    */
     91   char *url;
     92 
     93   /**
     94    * Entry for this request with the `struct GNUNET_CURL_Context`.
     95    */
     96   struct GNUNET_CURL_Job *job;
     97 
     98   /**
     99    * Expiration time according to "Expire:" header.
    100    * 0 if not provided by the server.
    101    */
    102   struct GNUNET_TIME_Timestamp expire; // not used -> no expiration, always 0
    103 
    104   /**
    105    * Function to call with the donau's certification data,
    106    * NULL if this has already been done.
    107    */
    108   DONAU_GetKeysCallback cert_cb;
    109 
    110   /**
    111    * Closure to pass to @e cert_cb.
    112    */
    113   void *cert_cb_cls;
    114 
    115 };
    116 
    117 
    118 #define EXITIF(cond)                                              \
    119         do {                                                            \
    120           if (cond) { GNUNET_break (0); goto EXITIF_exit; }             \
    121         } while (0)
    122 
    123 /**
    124  * Parse a donau's signing key encoded in JSON.
    125  *
    126  * @param[out] sign_key where to return the result
    127  * @param sign_key_obj json to parse
    128  * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if @a sign_key_obj
    129  * is malformed.
    130  */
    131 static enum GNUNET_GenericReturnValue
    132 parse_json_signkey (struct DONAU_SigningPublicKeyAndValidity *sign_key,
    133                     const json_t *sign_key_obj)
    134 {
    135   struct GNUNET_JSON_Specification spec[] = {
    136     GNUNET_JSON_spec_fixed_auto ("key",
    137                                  &sign_key->key),
    138     GNUNET_JSON_spec_timestamp ("stamp_start",
    139                                 &sign_key->valid_from),
    140     GNUNET_JSON_spec_timestamp ("stamp_expire",
    141                                 &sign_key->expire_sign),
    142     GNUNET_JSON_spec_end ()
    143   };
    144 
    145   if (GNUNET_OK !=
    146       GNUNET_JSON_parse (sign_key_obj,
    147                          spec,
    148                          NULL, NULL))
    149   {
    150     GNUNET_break_op (0);
    151     return GNUNET_SYSERR;
    152   }
    153   return GNUNET_OK;
    154 }
    155 
    156 
    157 /**
    158  * Parse a donau's donation unit key encoded in JSON.
    159  *
    160  * @param[out] du where to return the result
    161  * @param donation_unit_obj json to parse
    162  * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if @a donation_unit_obj
    163  * is malformed.
    164  */
    165 static enum GNUNET_GenericReturnValue
    166 parse_json_donation_unit (struct DONAU_DonationUnitInformation *du,
    167                           const json_t *donation_unit_obj)
    168 {
    169   struct GNUNET_JSON_Specification spec[] = {
    170     DONAU_JSON_spec_donation_unit_pub ("donation_unit_pub",
    171                                        &du->key),
    172     GNUNET_JSON_spec_uint64 ("year",
    173                              &du->year),
    174     GNUNET_JSON_spec_bool ("lost",
    175                            &du->lost),
    176     TALER_JSON_spec_amount_any ("value",
    177                                 &du->value),
    178     GNUNET_JSON_spec_end ()
    179   };
    180 
    181   if (GNUNET_OK !=
    182       GNUNET_JSON_parse (donation_unit_obj,
    183                          spec,
    184                          NULL, NULL))
    185   {
    186     GNUNET_break_op (0);
    187     return GNUNET_SYSERR;
    188   }
    189 
    190   return GNUNET_OK;
    191 }
    192 
    193 
    194 /**
    195  * Decode the JSON in @a resp_obj from the /keys response
    196  * and store the data in the @a key_data.
    197  *
    198  * @param[in] resp_obj JSON object to parse
    199  * @param[out] key_data where to store the results we decoded
    200  * @param[out] vc where to store version compatibility data
    201  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
    202  * (malformed JSON)
    203  */
    204 static enum GNUNET_GenericReturnValue
    205 decode_keys_json (const json_t *resp_obj,
    206                   struct DONAU_Keys *key_data,
    207                   enum DONAU_VersionCompatibility *vc)
    208 {
    209   const json_t *sign_keys_array;
    210   const json_t *donation_units_array;
    211 
    212   if (JSON_OBJECT != json_typeof (resp_obj))
    213   {
    214     GNUNET_break_op (0);
    215     return GNUNET_SYSERR;
    216   }
    217 #if DEBUG
    218   json_dumpf (resp_obj,
    219               stderr,
    220               JSON_INDENT (2));
    221 #endif
    222   /* check the version first */
    223   {
    224     const char *ver;
    225     unsigned int age;
    226     unsigned int revision;
    227     unsigned int current;
    228     char dummy;
    229     struct GNUNET_JSON_Specification spec[] = {
    230       GNUNET_JSON_spec_string ("version",
    231                                &ver),
    232       GNUNET_JSON_spec_end ()
    233     };
    234 
    235     if (GNUNET_OK !=
    236         GNUNET_JSON_parse (resp_obj,
    237                            spec,
    238                            NULL, NULL))
    239     {
    240       GNUNET_break_op (0);
    241       return GNUNET_SYSERR;
    242     }
    243     if (3 != sscanf (ver,
    244                      "%u:%u:%u%c",
    245                      &current,
    246                      &revision,
    247                      &age,
    248                      &dummy))
    249     {
    250       GNUNET_break_op (0);
    251       return GNUNET_SYSERR;
    252     }
    253     *vc = DONAU_VC_MATCH; // 0
    254     if (DONAU_PROTOCOL_CURRENT < current)
    255     {
    256       *vc |= DONAU_VC_NEWER; // 4
    257       if (DONAU_PROTOCOL_CURRENT < current - age)
    258         *vc |= DONAU_VC_INCOMPATIBLE; // 1
    259     }
    260     if (DONAU_PROTOCOL_CURRENT > current)
    261     {
    262       *vc |= DONAU_VC_OLDER; // 2
    263       if (DONAU_PROTOCOL_CURRENT - DONAU_PROTOCOL_AGE > current)
    264         *vc |= DONAU_VC_INCOMPATIBLE; // 1
    265     }
    266     key_data->version = GNUNET_strdup (ver);
    267   }
    268 
    269   {
    270     const char *currency;
    271     struct GNUNET_JSON_Specification mspec[] = {
    272       GNUNET_JSON_spec_array_const (
    273         "signkeys",
    274         &sign_keys_array),
    275       GNUNET_JSON_spec_string (
    276         "currency",
    277         &currency),
    278       GNUNET_JSON_spec_array_const (
    279         "donation_units",
    280         &donation_units_array),
    281       GNUNET_JSON_spec_end ()
    282     };
    283     const char *emsg;
    284     unsigned int eline;
    285 
    286     if (GNUNET_OK !=
    287         GNUNET_JSON_parse (resp_obj,
    288                            mspec,
    289                            &emsg,
    290                            &eline))
    291     {
    292       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    293                   "Parsing /keys failed for `%s' (%u)\n",
    294                   emsg,
    295                   eline);
    296       EXITIF (1);
    297     }
    298 
    299     key_data->currency = GNUNET_strdup (currency);
    300   }
    301 
    302   /* parse the signing keys */
    303   key_data->num_sign_keys
    304     = json_array_size (sign_keys_array);
    305   if (0 != key_data->num_sign_keys)
    306   {
    307     json_t *sign_key_obj;
    308     unsigned int index;
    309 
    310     key_data->sign_keys
    311       = GNUNET_new_array (key_data->num_sign_keys,
    312                           struct DONAU_SigningPublicKeyAndValidity);
    313     json_array_foreach (sign_keys_array, index, sign_key_obj) {
    314       EXITIF (GNUNET_SYSERR ==
    315               parse_json_signkey (&key_data->sign_keys[index],
    316                                   sign_key_obj));
    317     }
    318   }
    319 
    320   /*
    321    * Parse the donation unit keys
    322    */
    323   key_data->num_donation_unit_keys
    324     = json_array_size (donation_units_array);
    325   if (0 != key_data->num_donation_unit_keys)
    326   {
    327     json_t *donation_unit_obj;
    328     size_t index;
    329 
    330     key_data->donation_unit_keys
    331       = GNUNET_new_array (key_data->num_donation_unit_keys,
    332                           struct DONAU_DonationUnitInformation);
    333     json_array_foreach (donation_units_array, index, donation_unit_obj) {
    334       EXITIF (GNUNET_SYSERR ==
    335               parse_json_donation_unit (&key_data->donation_unit_keys[index],
    336                                         donation_unit_obj));
    337     }
    338   }
    339 
    340   return GNUNET_OK;
    341 
    342 EXITIF_exit:
    343   *vc = DONAU_VC_PROTOCOL_ERROR;
    344   return GNUNET_SYSERR;
    345 }
    346 
    347 
    348 /**
    349  * Callback used when downloading the reply to a /keys request
    350  * is complete.
    351  *
    352  * @param cls the `struct KeysRequest`
    353  * @param response_code HTTP response code, 0 on error
    354  * @param resp_obj parsed JSON result, NULL on error
    355  */
    356 static void
    357 keys_completed_cb (void *cls,
    358                    long response_code,
    359                    const void *resp_obj)
    360 {
    361   struct DONAU_GetKeysHandle *gkh = cls;
    362   const json_t *j = resp_obj;
    363   struct DONAU_Keys *kd = NULL;
    364   struct DONAU_KeysResponse kresp = {
    365     .hr.reply = j,
    366     .hr.http_status = (unsigned int) response_code,
    367     .details.ok.compat = DONAU_VC_PROTOCOL_ERROR
    368   };
    369 
    370   gkh->job = NULL;
    371   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    372               "Received keys from URL `%s' with status %ld.\n",
    373               gkh->url,
    374               response_code);
    375   switch (response_code)
    376   {
    377   case 0:
    378     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    379                 "Failed to receive /keys response from donau %s\n",
    380                 gkh->donau_url);
    381     kresp.hr.http_status = 0;
    382     kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    383     break;
    384   case MHD_HTTP_OK:
    385     if (NULL == j)
    386     {
    387       GNUNET_break (0);
    388       response_code = 0;
    389       kresp.hr.http_status = 0;
    390       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    391       break;
    392     }
    393     kd = GNUNET_new (struct DONAU_Keys);
    394     kd->donau_url = GNUNET_strdup (gkh->donau_url);
    395 
    396     if (GNUNET_OK !=
    397         decode_keys_json (j,
    398                           kd,
    399                           &kresp.details.ok.compat))
    400     {
    401       TALER_LOG_ERROR ("Could not decode /keys response\n");
    402       kd->rc = 1;
    403       DONAU_keys_decref (kd);
    404       kd = NULL;
    405       kresp.hr.http_status = 0;
    406       kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    407       break;
    408     }
    409     kd->rc = 1;
    410 
    411     kresp.details.ok.keys = kd;
    412     break;
    413   case MHD_HTTP_BAD_REQUEST:
    414   case MHD_HTTP_UNAUTHORIZED:
    415   case MHD_HTTP_FORBIDDEN:
    416   case MHD_HTTP_NOT_FOUND:
    417     if (NULL == j)
    418     {
    419       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    420       kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    421     }
    422     else
    423     {
    424       kresp.hr.ec = TALER_JSON_get_error_code (j);
    425       kresp.hr.hint = TALER_JSON_get_error_hint (j);
    426     }
    427     break;
    428   default:
    429     if (NULL == j)
    430     {
    431       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    432       kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    433     }
    434     else
    435     {
    436       kresp.hr.ec = TALER_JSON_get_error_code (j);
    437       kresp.hr.hint = TALER_JSON_get_error_hint (j);
    438     }
    439     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    440                 "Unexpected response code %u/%d\n",
    441                 (unsigned int) response_code,
    442                 (int) kresp.hr.ec);
    443     break;
    444   }
    445   gkh->cert_cb (gkh->cert_cb_cls,
    446                 &kresp,
    447                 kd);
    448   DONAU_get_keys_cancel (gkh);
    449 }
    450 
    451 
    452 struct DONAU_GetKeysHandle *
    453 DONAU_get_keys (
    454   struct GNUNET_CURL_Context *ctx,
    455   const char *url,
    456   DONAU_GetKeysCallback cert_cb,
    457   void *cert_cb_cls)
    458 {
    459   struct DONAU_GetKeysHandle *gkh;
    460   CURL *eh;
    461 
    462   TALER_LOG_DEBUG ("Connecting to the donau (%s)\n",
    463                    url);
    464   gkh = GNUNET_new (struct DONAU_GetKeysHandle);
    465   gkh->donau_url = GNUNET_strdup (url);
    466   gkh->cert_cb = cert_cb;
    467   gkh->cert_cb_cls = cert_cb_cls;
    468   gkh->url = TALER_url_join (url,
    469                              "keys",
    470                              NULL);
    471   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    472               "Requesting keys with URL `%s'.\n",
    473               gkh->url);
    474   eh = DONAU_curl_easy_get_ (gkh->url);
    475   if (NULL == eh)
    476   {
    477     GNUNET_break (0);
    478     GNUNET_free (gkh->donau_url);
    479     GNUNET_free (gkh->url);
    480     GNUNET_free (gkh);
    481     return NULL;
    482   }
    483   GNUNET_break (CURLE_OK ==
    484                 curl_easy_setopt (eh,
    485                                   CURLOPT_VERBOSE,
    486                                   0));
    487   GNUNET_break (CURLE_OK ==
    488                 curl_easy_setopt (eh,
    489                                   CURLOPT_TIMEOUT,
    490                                   120 /* seconds */));
    491   gkh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
    492                                                eh,
    493                                                &keys_completed_cb,
    494                                                gkh);
    495   return gkh;
    496 }
    497 
    498 
    499 void
    500 DONAU_get_keys_cancel (
    501   struct DONAU_GetKeysHandle *gkh)
    502 {
    503   if (NULL != gkh->job)
    504   {
    505     GNUNET_CURL_job_cancel (gkh->job);
    506     gkh->job = NULL;
    507   }
    508   GNUNET_free (gkh->donau_url);
    509   GNUNET_free (gkh->url);
    510   GNUNET_free (gkh);
    511 }
    512 
    513 
    514 const struct DONAU_DonationUnitInformation *
    515 DONAU_get_donation_unit_key (
    516   const struct DONAU_Keys *keys,
    517   const struct DONAU_DonationUnitPublicKey *pk)
    518 {
    519   for (unsigned int i = 0; i<keys->num_donation_unit_keys; i++)
    520     if (0 ==
    521         DONAU_donation_unit_pub_cmp (pk,
    522                                      &keys->donation_unit_keys[i].key))
    523       return &keys->donation_unit_keys[i];
    524   return NULL;
    525 }
    526 
    527 
    528 const struct DONAU_DonationUnitInformation *
    529 DONAU_get_donation_unit_key_by_hash (
    530   const struct DONAU_Keys *keys,
    531   const struct DONAU_DonationUnitHashP *hc)
    532 {
    533   for (unsigned int i = 0; i<keys->num_donation_unit_keys; i++)
    534     // memcmp needs two pointer of the same type
    535     if (0 == GNUNET_memcmp (&hc->hash,
    536                             &keys->donation_unit_keys[i].key.bsign_pub_key->
    537                             pub_key_hash))
    538       return &keys->donation_unit_keys[i];
    539   return NULL;
    540 }
    541 
    542 
    543 bool
    544 DONAU_compute_salted_tax_id_hash (const char *donor_tax_id,
    545                                   const char *salt,
    546                                   unsigned char out_hash[512 / 8])
    547 {
    548   crypto_hash_sha512_state st;
    549 
    550   if ( (NULL == donor_tax_id) ||
    551        (NULL == salt) ||
    552        (NULL == out_hash) )
    553   {
    554     GNUNET_break (0);
    555     return false;
    556   }
    557   /* FIXME: use GNUnet wrapper */
    558   crypto_hash_sha512_init (&st);
    559   crypto_hash_sha512_update (&st,
    560                              (const unsigned char *) donor_tax_id,
    561                              strlen (donor_tax_id) + 1);
    562   crypto_hash_sha512_update (&st,
    563                              (const unsigned char *) salt,
    564                              strlen (salt) + 1);
    565   crypto_hash_sha512_final (&st,
    566                             out_hash);
    567   return true;
    568 }
    569 
    570 
    571 /**
    572  * Local helper for the #DONAU_select_donation_unit_keys_for_amount()
    573  */
    574 struct DUEntry
    575 {
    576   struct TALER_Amount value;
    577   const struct DONAU_DonationUnitPublicKey *pub; /* points into keys */
    578 };
    579 
    580 /**
    581  * Small helper function for sorting
    582  */
    583 static int
    584 du_amount_desc_cmp (const void *a,
    585                     const void *b)
    586 {
    587   const struct DUEntry *ea = a;
    588   const struct DUEntry *eb = b;
    589 
    590   int c = TALER_amount_cmp (&ea->value,
    591                             &eb->value);
    592   /* Descending */
    593   return (c < 0) ? 1 : (c > 0 ? -1 : 0);
    594 }
    595 
    596 
    597 enum GNUNET_GenericReturnValue
    598 DONAU_select_donation_unit_keys_for_amount (
    599   const struct DONAU_Keys *keys,
    600   const struct TALER_Amount *requested_amount,
    601   uint64_t year,
    602   struct DONAU_DonationUnitPublicKey **out_keys,
    603   size_t *out_len)
    604 {
    605   struct DONAU_DonationUnitPublicKey *result = NULL;
    606   unsigned int result_len = 0;
    607   struct TALER_Amount remaining, zero;
    608   struct DUEntry *duv = NULL;
    609   unsigned int n_duv = 0;
    610   unsigned int i = 0;
    611 
    612   if ( (NULL == keys) ||
    613        (NULL == requested_amount) ||
    614        (NULL == out_keys) ||
    615        (NULL == out_len) )
    616   {
    617     GNUNET_break (0);
    618     return GNUNET_SYSERR;
    619   }
    620 
    621   if (0 != strcasecmp (keys->currency,
    622                        requested_amount->currency))
    623   {
    624     GNUNET_break (0);
    625     return GNUNET_SYSERR;
    626   }
    627 
    628   remaining = *requested_amount;
    629   TALER_amount_set_zero (keys->currency,
    630                          &zero);
    631 
    632   if (0 == TALER_amount_cmp (&remaining,
    633                              &zero))
    634   {
    635     *out_keys = NULL;
    636     *out_len = 0;
    637     return GNUNET_OK;
    638   }
    639 
    640   /* Build and sort (desc) the eligible units */
    641   for (unsigned int j = 0;
    642        j < keys->num_donation_unit_keys;
    643        j++)
    644   {
    645     const struct DONAU_DonationUnitInformation *du
    646       = &keys->donation_unit_keys[j];
    647 
    648     if (du->lost)
    649     {
    650       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    651                   "Donation unit lost!\n");
    652       continue;
    653     }
    654     if (du->year != year)
    655     {
    656       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    657                   "Donation unit for year %u, want %u!\n",
    658                   (unsigned int) du->year,
    659                   (unsigned int) year);
    660       continue;
    661     }
    662     GNUNET_array_grow (duv,
    663                        n_duv,
    664                        n_duv + 1);
    665     duv[n_duv - 1].value = du->value;
    666     duv[n_duv - 1].pub = &du->key;
    667   }
    668 
    669   if (0 == n_duv)
    670   {
    671     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    672                 "No eligible (non-lost, correct year) donation units found\n");
    673     *out_keys = NULL;
    674     *out_len = 0;
    675     return GNUNET_NO;
    676   }
    677 
    678   qsort (duv,
    679          n_duv,
    680          sizeof(struct DUEntry),
    681          &du_amount_desc_cmp);
    682 
    683   while (i < n_duv)
    684   {
    685     int cmp = TALER_amount_cmp (&duv[i].value,
    686                                 &remaining);
    687 
    688     if (cmp <= 0)
    689     {
    690       /* Take as many as we can of duv[i] without overshooting */
    691       for (;;)
    692       {
    693         struct TALER_Amount tmp;
    694         int rc;
    695 
    696         if (TALER_amount_cmp (&duv[i].value,
    697                               &remaining) > 0)
    698           break;
    699 
    700         GNUNET_array_append (result,
    701                              result_len,
    702                              *duv[i].pub);
    703 
    704         rc = TALER_amount_subtract (&tmp,
    705                                     &remaining,
    706                                     &duv[i].value);
    707 
    708         if (TALER_AAR_RESULT_ZERO == rc)
    709         {
    710           remaining = tmp; /* zero */
    711           GNUNET_free (duv);
    712           *out_keys = result;
    713           *out_len  = result_len;
    714           return GNUNET_OK; /* exact match */
    715         }
    716         if (TALER_AAR_RESULT_POSITIVE == rc)
    717         {
    718           remaining = tmp; /* keep taking this same value */
    719           continue;
    720         }
    721 
    722         /* Shouldn't happen because we guard with cmp <= 0 */
    723         GNUNET_break (0);
    724         GNUNET_free (duv);
    725         GNUNET_array_grow (result,
    726                            result_len,
    727                            0);
    728         *out_keys = NULL;
    729         *out_len  = 0;
    730         return GNUNET_SYSERR;
    731       }
    732       /* current no longer fits, move to next smaller */
    733       i++;
    734       continue;
    735     }
    736     i++;
    737   }
    738 
    739   /* No exact combination found */
    740   GNUNET_free (duv);
    741   GNUNET_array_grow (result,
    742                      result_len,
    743                      0);
    744   *out_keys = NULL;
    745   *out_len  = 0;
    746   return GNUNET_NO;
    747 }
    748 
    749 
    750 enum GNUNET_GenericReturnValue
    751 DONAU_get_donation_amount_from_bkps (
    752   const struct DONAU_Keys *keys,
    753   const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps,
    754   size_t num_bkps,
    755   uint64_t year,
    756   struct TALER_Amount *sum_out)
    757 {
    758   /* Sanity-checks */
    759   if (NULL == bkps)
    760   {
    761     GNUNET_break (0);
    762     return GNUNET_NO;
    763   }
    764   if (0 == num_bkps)
    765   {
    766     GNUNET_break (0);
    767     return GNUNET_NO;
    768   }
    769 
    770   TALER_amount_set_zero (keys->currency,
    771                          sum_out);
    772   if (GNUNET_YES ==
    773       DONAU_check_bkps_duplication (bkps,
    774                                     num_bkps))
    775   {
    776     GNUNET_break (0);
    777     return GNUNET_NO;
    778   }
    779 
    780   for (size_t i = 0; i < num_bkps; i++)
    781   {
    782     const struct DONAU_DonationUnitInformation *dui =
    783       DONAU_get_donation_unit_key_by_hash (keys,
    784                                            &bkps[i].h_donation_unit_pub);
    785 
    786     if (NULL == dui)
    787     {
    788       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    789                   "Donation unit public key unknown\n");
    790       return GNUNET_NO;
    791     }
    792     if (dui->year != year)
    793     {
    794       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    795                   "Donation sum for year %lu requested, but "
    796                   "donation unit is for year %lu\n",
    797                   (unsigned long) year,
    798                   (unsigned long) dui->year);
    799       return GNUNET_NO;
    800     }
    801 
    802     switch (TALER_amount_add (sum_out,
    803                               sum_out,
    804                               &dui->value))
    805     {
    806     case TALER_AAR_RESULT_POSITIVE:
    807     case TALER_AAR_RESULT_ZERO:
    808       /* Zero a bit strange, but let's say that it's fine to some extent */
    809       break;
    810 
    811     case TALER_AAR_INVALID_NEGATIVE_RESULT:
    812       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    813                   "Accumulation would go negative\n");
    814       GNUNET_break (0);
    815       return GNUNET_SYSERR;
    816 
    817     case TALER_AAR_INVALID_RESULT_OVERFLOW:
    818       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    819                   "Accumulation overflow\n");
    820       GNUNET_break (0);
    821       return GNUNET_SYSERR;
    822 
    823     case TALER_AAR_INVALID_NORMALIZATION_FAILED:
    824       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    825                   "Normalization failed during add\n");
    826       GNUNET_break (0);
    827       return GNUNET_SYSERR;
    828 
    829     case TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE:
    830       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    831                   "Currency mismatch during add\n");
    832       GNUNET_break (0);
    833       return GNUNET_SYSERR;
    834     }
    835   }
    836   return GNUNET_OK;
    837 }
    838 
    839 
    840 bool
    841 DONAU_check_bkps_duplication (
    842   const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps,
    843   const size_t num_bkps)
    844 {
    845   if ( (NULL == bkps) ||
    846        (num_bkps < 2) )
    847     return GNUNET_NO;
    848 
    849   for (size_t i = 0; i < num_bkps - 1; i++)
    850     for (size_t j = i + 1; j < num_bkps; j++)
    851       if (0 == GNUNET_memcmp (&bkps[i].blinded_udi,
    852                               &bkps[j].blinded_udi))
    853         return GNUNET_YES;
    854 
    855   return GNUNET_NO;
    856 }
    857 
    858 
    859 struct DONAU_Keys *
    860 DONAU_keys_incref (struct DONAU_Keys *keys)
    861 {
    862   GNUNET_assert (keys->rc < UINT_MAX);
    863   keys->rc++;
    864   return keys;
    865 }
    866 
    867 
    868 void
    869 DONAU_keys_decref (struct DONAU_Keys *keys)
    870 {
    871   if (NULL == keys)
    872     return;
    873   GNUNET_assert (0 < keys->rc);
    874   keys->rc--;
    875   if (0 != keys->rc)
    876     return;
    877   GNUNET_array_grow (keys->sign_keys,
    878                      keys->num_sign_keys,
    879                      0);
    880   for (unsigned int i = 0; i<keys->num_donation_unit_keys; i++)
    881     DONAU_donation_unit_pub_free (&keys->donation_unit_keys[i].key);
    882 
    883   GNUNET_array_grow (keys->donation_unit_keys,
    884                      keys->num_donation_unit_keys,
    885                      0);
    886   GNUNET_free (keys->version);
    887   GNUNET_free (keys->currency);
    888   GNUNET_free (keys->donau_url);
    889   GNUNET_free (keys);
    890 }
    891 
    892 
    893 struct DONAU_Keys *
    894 DONAU_keys_from_json (const json_t *j)
    895 {
    896   const json_t *jkeys;
    897   const char *url;
    898   uint32_t version;
    899   struct GNUNET_JSON_Specification spec[] = {
    900     GNUNET_JSON_spec_uint32 ("version",
    901                              &version),
    902     GNUNET_JSON_spec_object_const ("keys",
    903                                    &jkeys),
    904     GNUNET_JSON_spec_string ("donau_url",
    905                              &url),
    906     GNUNET_JSON_spec_end ()
    907   };
    908   struct DONAU_Keys *keys;
    909   enum DONAU_VersionCompatibility compat;
    910 
    911   if (NULL == j)
    912     return NULL;
    913   if (GNUNET_OK !=
    914       GNUNET_JSON_parse (j,
    915                          spec,
    916                          NULL, NULL))
    917   {
    918     GNUNET_break_op (0);
    919     return NULL;
    920   }
    921   if (0 != version)
    922   {
    923     return NULL; /* unsupported version */
    924   }
    925   keys = GNUNET_new (struct DONAU_Keys);
    926   if (GNUNET_OK !=
    927       decode_keys_json (jkeys,
    928                         keys,
    929                         &compat))
    930   {
    931     GNUNET_break (0);
    932     keys->rc = 1;
    933     DONAU_keys_decref (keys);
    934     return NULL;
    935   }
    936   keys->rc = 1;
    937   keys->donau_url = GNUNET_strdup (url);
    938   return keys;
    939 }
    940 
    941 
    942 /**
    943  * Data we track per donation unit group.
    944  */
    945 struct GroupData
    946 {
    947   /**
    948    * The json blob with the group meta-data and list of donation units
    949    */
    950   json_t *json;
    951 
    952   /**
    953    * Meta data for this group.
    954    */
    955   struct DONAU_DonationUnitGroup meta;
    956 };
    957 
    958 
    959 json_t *
    960 DONAU_keys_to_json (const struct DONAU_Keys *kd)
    961 {
    962   // --- Create the array of signkeys (unchanged) ---
    963   json_t *signkeys = json_array ();
    964   json_t *keys;
    965   json_t *donation_units;
    966   json_t *currency_spec = NULL;
    967 
    968   GNUNET_assert (NULL != signkeys);
    969   for (unsigned int i = 0; i < kd->num_sign_keys; i++)
    970   {
    971     const struct DONAU_SigningPublicKeyAndValidity *sk = &kd->sign_keys[i];
    972     json_t *signkey;
    973 
    974     signkey = GNUNET_JSON_PACK (
    975       GNUNET_JSON_pack_data_auto ("key",
    976                                   &sk->key),
    977       GNUNET_JSON_pack_timestamp ("stamp_start",
    978                                   sk->valid_from),
    979       GNUNET_JSON_pack_timestamp ("stamp_expire",
    980                                   sk->expire_sign));
    981     GNUNET_assert (NULL != signkey);
    982     GNUNET_assert (0 ==
    983                    json_array_append_new (signkeys,
    984                                           signkey));
    985   }
    986 
    987   // --- Create the array of donation_units ---
    988   donation_units = json_array ();
    989   GNUNET_assert (NULL != donation_units);
    990   for (unsigned int i = 0; i < kd->num_donation_unit_keys; i++)
    991   {
    992     const struct DONAU_DonationUnitInformation *du = &kd->donation_unit_keys[i];
    993 
    994     // Build the JSON for one donation unit
    995     json_t *donation_unit_obj = GNUNET_JSON_PACK (
    996       DONAU_JSON_pack_donation_unit_pub ("donation_unit_pub", &du->key),
    997       GNUNET_JSON_pack_uint64 ("year", du->year),
    998       GNUNET_JSON_pack_bool ("lost", du->lost),
    999       TALER_JSON_pack_amount ("value", &du->value)
   1000       );
   1001 
   1002     GNUNET_assert (NULL != donation_unit_obj);
   1003     GNUNET_assert (0 ==
   1004                    json_array_append_new (donation_units,
   1005                                           donation_unit_obj));
   1006   }
   1007 
   1008   if (NULL != kd->currency_specification.name)
   1009   {
   1010     currency_spec = TALER_JSON_currency_specs_to_json (
   1011       &kd->currency_specification
   1012       );
   1013   }
   1014 
   1015   keys = GNUNET_JSON_PACK (
   1016     GNUNET_JSON_pack_string ("version",
   1017                              kd->version),
   1018     GNUNET_JSON_pack_string ("currency",
   1019                              kd->currency),
   1020     GNUNET_JSON_pack_array_steal ("signkeys",
   1021                                   signkeys),
   1022     GNUNET_JSON_pack_array_steal ("donation_units",
   1023                                   donation_units)
   1024     );
   1025 
   1026   if (NULL != currency_spec)
   1027   {
   1028     json_object_set_new (keys,
   1029                          "currency_specification",
   1030                          currency_spec);
   1031   }
   1032 
   1033   return GNUNET_JSON_PACK (
   1034     GNUNET_JSON_pack_uint64 ("version",
   1035                              DONAU_SERIALIZATION_FORMAT_VERSION),
   1036     GNUNET_JSON_pack_string ("donau_url",
   1037                              kd->donau_url),
   1038     GNUNET_JSON_pack_object_steal ("keys",
   1039                                    keys));
   1040 }
   1041 
   1042 
   1043 /* end of donau_api_handle.c */