donau

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

donau_api_handle.c (27145B)


      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 0
     41 
     42 /**
     43  * How many versions are we backwards compatible with?
     44  */
     45 #define DONAU_PROTOCOL_AGE 0
     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     break;
    382   case MHD_HTTP_OK:
    383     if (NULL == j)
    384     {
    385       GNUNET_break (0);
    386       response_code = 0;
    387       break;
    388     }
    389     kd = GNUNET_new (struct DONAU_Keys);
    390     kd->donau_url = GNUNET_strdup (gkh->donau_url);
    391 
    392     if (GNUNET_OK !=
    393         decode_keys_json (j,
    394                           kd,
    395                           &kresp.details.ok.compat))
    396     {
    397       TALER_LOG_ERROR ("Could not decode /keys response\n");
    398       kd->rc = 1;
    399       DONAU_keys_decref (kd);
    400       kd = NULL;
    401       kresp.hr.http_status = 0;
    402       kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    403       break;
    404     }
    405     kd->rc = 1;
    406 
    407     kresp.details.ok.keys = kd;
    408     break;
    409   case MHD_HTTP_BAD_REQUEST:
    410   case MHD_HTTP_UNAUTHORIZED:
    411   case MHD_HTTP_FORBIDDEN:
    412   case MHD_HTTP_NOT_FOUND:
    413     if (NULL == j)
    414     {
    415       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    416       kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    417     }
    418     else
    419     {
    420       kresp.hr.ec = TALER_JSON_get_error_code (j);
    421       kresp.hr.hint = TALER_JSON_get_error_hint (j);
    422     }
    423     break;
    424   default:
    425     if (NULL == j)
    426     {
    427       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    428       kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    429     }
    430     else
    431     {
    432       kresp.hr.ec = TALER_JSON_get_error_code (j);
    433       kresp.hr.hint = TALER_JSON_get_error_hint (j);
    434     }
    435     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    436                 "Unexpected response code %u/%d\n",
    437                 (unsigned int) response_code,
    438                 (int) kresp.hr.ec);
    439     break;
    440   }
    441   gkh->cert_cb (gkh->cert_cb_cls,
    442                 &kresp,
    443                 kd);
    444   DONAU_get_keys_cancel (gkh);
    445 }
    446 
    447 
    448 struct DONAU_GetKeysHandle *
    449 DONAU_get_keys (
    450   struct GNUNET_CURL_Context *ctx,
    451   const char *url,
    452   DONAU_GetKeysCallback cert_cb,
    453   void *cert_cb_cls)
    454 {
    455   struct DONAU_GetKeysHandle *gkh;
    456   CURL *eh;
    457 
    458   TALER_LOG_DEBUG ("Connecting to the donau (%s)\n",
    459                    url);
    460   gkh = GNUNET_new (struct DONAU_GetKeysHandle);
    461   gkh->donau_url = GNUNET_strdup (url);
    462   gkh->cert_cb = cert_cb;
    463   gkh->cert_cb_cls = cert_cb_cls;
    464   gkh->url = TALER_url_join (url,
    465                              "keys",
    466                              NULL);
    467   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    468               "Requesting keys with URL `%s'.\n",
    469               gkh->url);
    470   eh = DONAU_curl_easy_get_ (gkh->url);
    471   if (NULL == eh)
    472   {
    473     GNUNET_break (0);
    474     GNUNET_free (gkh->donau_url);
    475     GNUNET_free (gkh->url);
    476     GNUNET_free (gkh);
    477     return NULL;
    478   }
    479   GNUNET_break (CURLE_OK ==
    480                 curl_easy_setopt (eh,
    481                                   CURLOPT_VERBOSE,
    482                                   0));
    483   GNUNET_break (CURLE_OK ==
    484                 curl_easy_setopt (eh,
    485                                   CURLOPT_TIMEOUT,
    486                                   120 /* seconds */));
    487   gkh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
    488                                                eh,
    489                                                &keys_completed_cb,
    490                                                gkh);
    491   return gkh;
    492 }
    493 
    494 
    495 void
    496 DONAU_get_keys_cancel (
    497   struct DONAU_GetKeysHandle *gkh)
    498 {
    499   if (NULL != gkh->job)
    500   {
    501     GNUNET_CURL_job_cancel (gkh->job);
    502     gkh->job = NULL;
    503   }
    504   GNUNET_free (gkh->donau_url);
    505   GNUNET_free (gkh->url);
    506   GNUNET_free (gkh);
    507 }
    508 
    509 
    510 const struct DONAU_DonationUnitInformation *
    511 DONAU_get_donation_unit_key (
    512   const struct DONAU_Keys *keys,
    513   const struct DONAU_DonationUnitPublicKey *pk)
    514 {
    515   for (unsigned int i = 0; i<keys->num_donation_unit_keys; i++)
    516     if (0 ==
    517         DONAU_donation_unit_pub_cmp (pk,
    518                                      &keys->donation_unit_keys[i].key))
    519       return &keys->donation_unit_keys[i];
    520   return NULL;
    521 }
    522 
    523 
    524 const struct DONAU_DonationUnitInformation *
    525 DONAU_get_donation_unit_key_by_hash (
    526   const struct DONAU_Keys *keys,
    527   const struct DONAU_DonationUnitHashP *hc)
    528 {
    529   for (unsigned int i = 0; i<keys->num_donation_unit_keys; i++)
    530     // memcmp needs two pointer of the same type
    531     if (0 == GNUNET_memcmp (&hc->hash,
    532                             &keys->donation_unit_keys[i].key.bsign_pub_key->
    533                             pub_key_hash))
    534       return &keys->donation_unit_keys[i];
    535   return NULL;
    536 }
    537 
    538 
    539 bool
    540 DONAU_compute_salted_tax_id_hash (const char *donor_tax_id,
    541                                   const char *salt,
    542                                   unsigned char out_hash[512 / 8])
    543 {
    544   crypto_hash_sha512_state st;
    545 
    546   if ( (NULL == donor_tax_id) ||
    547        (NULL == salt) ||
    548        (NULL == out_hash) )
    549   {
    550     GNUNET_break (0);
    551     return false;
    552   }
    553   /* FIXME: use GNUnet wrapper */
    554   crypto_hash_sha512_init (&st);
    555   crypto_hash_sha512_update (&st,
    556                              (const unsigned char *) donor_tax_id,
    557                              strlen (donor_tax_id) + 1);
    558   crypto_hash_sha512_update (&st,
    559                              (const unsigned char *) salt,
    560                              strlen (salt) + 1);
    561   crypto_hash_sha512_final (&st,
    562                             out_hash);
    563   return true;
    564 }
    565 
    566 
    567 /**
    568  * Local helper for the #DONAU_select_donation_unit_keys_for_amount()
    569  */
    570 struct DUEntry
    571 {
    572   struct TALER_Amount value;
    573   const struct DONAU_DonationUnitPublicKey *pub; /* points into keys */
    574 };
    575 
    576 /**
    577  * Small helper function for sorting
    578  */
    579 static int
    580 du_amount_desc_cmp (const void *a,
    581                     const void *b)
    582 {
    583   const struct DUEntry *ea = a;
    584   const struct DUEntry *eb = b;
    585 
    586   int c = TALER_amount_cmp (&ea->value,
    587                             &eb->value);
    588   /* Descending */
    589   return (c < 0) ? 1 : (c > 0 ? -1 : 0);
    590 }
    591 
    592 
    593 enum GNUNET_GenericReturnValue
    594 DONAU_select_donation_unit_keys_for_amount (
    595   const struct DONAU_Keys *keys,
    596   const struct TALER_Amount *requested_amount,
    597   uint64_t year,
    598   struct DONAU_DonationUnitPublicKey **out_keys,
    599   uint32_t *out_len)
    600 {
    601   struct DONAU_DonationUnitPublicKey *result = NULL;
    602   uint32_t result_len = 0;
    603   struct TALER_Amount remaining, zero;
    604   struct DUEntry *duv = NULL;
    605   unsigned int n_duv = 0;
    606   unsigned int i = 0;
    607 
    608   if ( (NULL == keys) ||
    609        (NULL == requested_amount) ||
    610        (NULL == out_keys) ||
    611        (NULL == out_len) )
    612   {
    613     GNUNET_break (0);
    614     return GNUNET_SYSERR;
    615   }
    616 
    617   if (0 != strcasecmp (keys->currency,
    618                        requested_amount->currency))
    619   {
    620     GNUNET_break (0);
    621     return GNUNET_SYSERR;
    622   }
    623 
    624   remaining = *requested_amount;
    625   TALER_amount_set_zero (keys->currency,
    626                          &zero);
    627 
    628   if (0 == TALER_amount_cmp (&remaining,
    629                              &zero))
    630   {
    631     *out_keys = NULL;
    632     *out_len = 0;
    633     return GNUNET_OK;
    634   }
    635 
    636   /* Build and sort (desc) the eligible units */
    637   for (unsigned int j = 0;
    638        j < keys->num_donation_unit_keys;
    639        j++)
    640   {
    641     const struct DONAU_DonationUnitInformation *du
    642       = &keys->donation_unit_keys[j];
    643 
    644     if (du->lost)
    645     {
    646       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    647                   "Donation unit lost!\n");
    648       continue;
    649     }
    650     if (du->year != year)
    651     {
    652       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    653                   "Donation unit for year %u, want %u!\n",
    654                   (unsigned int) du->year,
    655                   (unsigned int) year);
    656       continue;
    657     }
    658     GNUNET_array_grow (duv,
    659                        n_duv,
    660                        n_duv + 1);
    661     duv[n_duv - 1].value = du->value;
    662     duv[n_duv - 1].pub = &du->key;
    663   }
    664 
    665   if (0 == n_duv)
    666   {
    667     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    668                 "No eligible (non-lost, correct year) donation units found\n");
    669     *out_keys = NULL;
    670     *out_len = 0;
    671     return GNUNET_NO;
    672   }
    673 
    674   qsort (duv,
    675          n_duv,
    676          sizeof(struct DUEntry),
    677          &du_amount_desc_cmp);
    678 
    679   while (i < n_duv)
    680   {
    681     int cmp = TALER_amount_cmp (&duv[i].value,
    682                                 &remaining);
    683 
    684     if (cmp <= 0)
    685     {
    686       /* Take as many as we can of duv[i] without overshooting */
    687       for (;;)
    688       {
    689         struct TALER_Amount tmp;
    690         int rc;
    691 
    692         if (TALER_amount_cmp (&duv[i].value,
    693                               &remaining) > 0)
    694           break;
    695 
    696         GNUNET_array_append (result,
    697                              result_len,
    698                              *duv[i].pub);
    699 
    700         rc = TALER_amount_subtract (&tmp,
    701                                     &remaining,
    702                                     &duv[i].value);
    703 
    704         if (TALER_AAR_RESULT_ZERO == rc)
    705         {
    706           remaining = tmp; /* zero */
    707           GNUNET_free (duv);
    708           *out_keys = result;
    709           *out_len  = result_len;
    710           return GNUNET_OK; /* exact match */
    711         }
    712         if (TALER_AAR_RESULT_POSITIVE == rc)
    713         {
    714           remaining = tmp; /* keep taking this same value */
    715           continue;
    716         }
    717 
    718         /* Shouldn't happen because we guard with cmp <= 0 */
    719         GNUNET_break (0);
    720         GNUNET_free (duv);
    721         GNUNET_array_grow (result, result_len, 0);
    722         *out_keys = NULL;
    723         *out_len  = 0;
    724         return GNUNET_SYSERR;
    725       }
    726       /* current no longer fits, move to next smaller */
    727       i++;
    728       continue;
    729     }
    730     i++;
    731   }
    732 
    733   /* No exact combination found */
    734   GNUNET_free (duv);
    735   GNUNET_array_grow (result, result_len, 0);
    736   *out_keys = NULL;
    737   *out_len  = 0;
    738   return GNUNET_NO;
    739 }
    740 
    741 
    742 enum GNUNET_GenericReturnValue
    743 DONAU_get_donation_amount_from_bkps (
    744   const struct DONAU_Keys *keys,
    745   const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps,
    746   size_t num_bkps,
    747   uint64_t year,
    748   struct TALER_Amount *sum_out)
    749 {
    750   /* Sanity-checks */
    751   if (NULL == bkps)
    752   {
    753     GNUNET_break (0);
    754     return GNUNET_NO;
    755   }
    756   if (0 == num_bkps)
    757   {
    758     GNUNET_break (0);
    759     return GNUNET_NO;
    760   }
    761 
    762   TALER_amount_set_zero (keys->currency,
    763                          sum_out);
    764   if (GNUNET_YES ==
    765       DONAU_check_bkps_duplication (bkps,
    766                                     num_bkps))
    767   {
    768     GNUNET_break (0);
    769     return GNUNET_NO;
    770   }
    771 
    772   for (size_t i = 0; i < num_bkps; i++)
    773   {
    774     const struct DONAU_DonationUnitInformation *dui =
    775       DONAU_get_donation_unit_key_by_hash (keys,
    776                                            &bkps[i].h_donation_unit_pub);
    777 
    778     if (NULL == dui)
    779     {
    780       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    781                   "Donation unit public key unknown\n");
    782       return GNUNET_NO;
    783     }
    784     if (dui->year != year)
    785     {
    786       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    787                   "Donation sum for year %lu requested, but "
    788                   "donation unit is for year %lu\n",
    789                   (unsigned long) year,
    790                   (unsigned long) dui->year);
    791       return GNUNET_NO;
    792     }
    793 
    794     switch (TALER_amount_add (sum_out,
    795                               sum_out,
    796                               &dui->value))
    797     {
    798     case TALER_AAR_RESULT_POSITIVE:
    799     case TALER_AAR_RESULT_ZERO:
    800       /* Zero a bit strange, but let's say that it's fine to some extent */
    801       break;
    802 
    803     case TALER_AAR_INVALID_NEGATIVE_RESULT:
    804       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    805                   "Accumulation would go negative\n");
    806       GNUNET_break (0);
    807       return GNUNET_SYSERR;
    808 
    809     case TALER_AAR_INVALID_RESULT_OVERFLOW:
    810       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    811                   "Accumulation overflow\n");
    812       GNUNET_break (0);
    813       return GNUNET_SYSERR;
    814 
    815     case TALER_AAR_INVALID_NORMALIZATION_FAILED:
    816       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    817                   "Normalization failed during add\n");
    818       GNUNET_break (0);
    819       return GNUNET_SYSERR;
    820 
    821     case TALER_AAR_INVALID_CURRENCIES_INCOMPATIBLE:
    822       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    823                   "Currency mismatch during add\n");
    824       GNUNET_break (0);
    825       return GNUNET_SYSERR;
    826     }
    827   }
    828   return GNUNET_OK;
    829 }
    830 
    831 
    832 bool
    833 DONAU_check_bkps_duplication (
    834   const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps,
    835   const size_t num_bkps)
    836 {
    837   if ( (NULL == bkps) ||
    838        (num_bkps < 2) )
    839     return GNUNET_NO;
    840 
    841   for (size_t i = 0; i < num_bkps - 1; i++)
    842     for (size_t j = i + 1; j < num_bkps; j++)
    843       if (0 == GNUNET_memcmp (&bkps[i].blinded_udi,
    844                               &bkps[j].blinded_udi))
    845         return GNUNET_YES;
    846 
    847   return GNUNET_NO;
    848 }
    849 
    850 
    851 struct DONAU_Keys *
    852 DONAU_keys_incref (struct DONAU_Keys *keys)
    853 {
    854   GNUNET_assert (keys->rc < UINT_MAX);
    855   keys->rc++;
    856   return keys;
    857 }
    858 
    859 
    860 void
    861 DONAU_keys_decref (struct DONAU_Keys *keys)
    862 {
    863   if (NULL == keys)
    864     return;
    865   GNUNET_assert (0 < keys->rc);
    866   keys->rc--;
    867   if (0 != keys->rc)
    868     return;
    869   GNUNET_array_grow (keys->sign_keys,
    870                      keys->num_sign_keys,
    871                      0);
    872   for (unsigned int i = 0; i<keys->num_donation_unit_keys; i++)
    873     DONAU_donation_unit_pub_free (&keys->donation_unit_keys[i].key);
    874 
    875   GNUNET_array_grow (keys->donation_unit_keys,
    876                      keys->donation_unit_keys_size,
    877                      0);
    878   GNUNET_free (keys->version);
    879   GNUNET_free (keys->currency);
    880   GNUNET_free (keys->donau_url);
    881   GNUNET_free (keys);
    882 }
    883 
    884 
    885 struct DONAU_Keys *
    886 DONAU_keys_from_json (const json_t *j)
    887 {
    888   const json_t *jkeys;
    889   const char *url;
    890   uint32_t version;
    891   struct GNUNET_JSON_Specification spec[] = {
    892     GNUNET_JSON_spec_uint32 ("version",
    893                              &version),
    894     GNUNET_JSON_spec_object_const ("keys",
    895                                    &jkeys),
    896     GNUNET_JSON_spec_string ("donau_url",
    897                              &url),
    898     GNUNET_JSON_spec_end ()
    899   };
    900   struct DONAU_Keys *keys;
    901   enum DONAU_VersionCompatibility compat;
    902 
    903   if (NULL == j)
    904     return NULL;
    905   if (GNUNET_OK !=
    906       GNUNET_JSON_parse (j,
    907                          spec,
    908                          NULL, NULL))
    909   {
    910     GNUNET_break_op (0);
    911     return NULL;
    912   }
    913   if (0 != version)
    914   {
    915     return NULL; /* unsupported version */
    916   }
    917   keys = GNUNET_new (struct DONAU_Keys);
    918   if (GNUNET_OK !=
    919       decode_keys_json (jkeys,
    920                         keys,
    921                         &compat))
    922   {
    923     GNUNET_break (0);
    924     return NULL;
    925   }
    926   keys->rc = 1;
    927   keys->donau_url = GNUNET_strdup (url);
    928   return keys;
    929 }
    930 
    931 
    932 /**
    933  * Data we track per donation unit group.
    934  */
    935 struct GroupData
    936 {
    937   /**
    938    * The json blob with the group meta-data and list of donation units
    939    */
    940   json_t *json;
    941 
    942   /**
    943    * Meta data for this group.
    944    */
    945   struct DONAU_DonationUnitGroup meta;
    946 };
    947 
    948 
    949 json_t *
    950 DONAU_keys_to_json (const struct DONAU_Keys *kd)
    951 {
    952   // --- Create the array of signkeys (unchanged) ---
    953   json_t *signkeys = json_array ();
    954   json_t *keys;
    955   json_t *donation_units;
    956   json_t *currency_spec = NULL;
    957 
    958   GNUNET_assert (NULL != signkeys);
    959   for (unsigned int i = 0; i < kd->num_sign_keys; i++)
    960   {
    961     const struct DONAU_SigningPublicKeyAndValidity *sk = &kd->sign_keys[i];
    962     json_t *signkey;
    963 
    964     signkey = GNUNET_JSON_PACK (
    965       GNUNET_JSON_pack_data_auto ("key",
    966                                   &sk->key),
    967       GNUNET_JSON_pack_timestamp ("stamp_start",
    968                                   sk->valid_from),
    969       GNUNET_JSON_pack_timestamp ("stamp_expire",
    970                                   sk->expire_sign));
    971     GNUNET_assert (NULL != signkey);
    972     GNUNET_assert (0 ==
    973                    json_array_append_new (signkeys,
    974                                           signkey));
    975   }
    976 
    977   // --- Create the array of donation_units ---
    978   donation_units = json_array ();
    979   GNUNET_assert (NULL != donation_units);
    980   for (unsigned int i = 0; i < kd->num_donation_unit_keys; i++)
    981   {
    982     const struct DONAU_DonationUnitInformation *du = &kd->donation_unit_keys[i];
    983 
    984     // Build the JSON for one donation unit
    985     json_t *donation_unit_obj = GNUNET_JSON_PACK (
    986       DONAU_JSON_pack_donation_unit_pub ("donation_unit_pub", &du->key),
    987       GNUNET_JSON_pack_uint64 ("year", du->year),
    988       GNUNET_JSON_pack_bool ("lost", du->lost),
    989       TALER_JSON_pack_amount ("value", &du->value)
    990       );
    991 
    992     GNUNET_assert (NULL != donation_unit_obj);
    993     GNUNET_assert (0 ==
    994                    json_array_append_new (donation_units,
    995                                           donation_unit_obj));
    996   }
    997 
    998   if (NULL != kd->currency_specification.name)
    999   {
   1000     currency_spec = TALER_JSON_currency_specs_to_json (
   1001       &kd->currency_specification
   1002       );
   1003   }
   1004 
   1005   keys = GNUNET_JSON_PACK (
   1006     GNUNET_JSON_pack_string ("version",
   1007                              kd->version),
   1008     GNUNET_JSON_pack_string ("currency",
   1009                              kd->currency),
   1010     GNUNET_JSON_pack_array_steal ("signkeys",
   1011                                   signkeys),
   1012     GNUNET_JSON_pack_array_steal ("donation_units",
   1013                                   donation_units)
   1014     );
   1015 
   1016   if (NULL != currency_spec)
   1017   {
   1018     json_object_set_new (keys,
   1019                          "currency_specification",
   1020                          currency_spec);
   1021   }
   1022 
   1023   return GNUNET_JSON_PACK (
   1024     GNUNET_JSON_pack_uint64 ("version",
   1025                              DONAU_SERIALIZATION_FORMAT_VERSION),
   1026     GNUNET_JSON_pack_string ("donau_url",
   1027                              kd->donau_url),
   1028     GNUNET_JSON_pack_object_steal ("keys",
   1029                                    keys));
   1030 }
   1031 
   1032 
   1033 /* end of donau_api_handle.c */