exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_api_reserves_history.c (37132B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2023 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file lib/exchange_api_reserves_history.c
     19  * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <jansson.h>
     24 #include <microhttpd.h> /* just for HTTP history codes */
     25 #include <gnunet/gnunet_util_lib.h>
     26 #include <gnunet/gnunet_json_lib.h>
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include "taler/taler_exchange_service.h"
     29 #include "taler/taler_json_lib.h"
     30 #include "exchange_api_handle.h"
     31 #include "taler/taler_signatures.h"
     32 #include "exchange_api_curl_defaults.h"
     33 
     34 
     35 /**
     36  * @brief A /reserves/$RID/history Handle
     37  */
     38 struct TALER_EXCHANGE_ReservesHistoryHandle
     39 {
     40 
     41   /**
     42    * The keys of the exchange this request handle will use
     43    */
     44   struct TALER_EXCHANGE_Keys *keys;
     45 
     46   /**
     47    * The url for this request.
     48    */
     49   char *url;
     50 
     51   /**
     52    * Handle for the request.
     53    */
     54   struct GNUNET_CURL_Job *job;
     55 
     56   /**
     57    * Context for #TEH_curl_easy_post(). Keeps the data that must
     58    * persist for Curl to make the upload.
     59    */
     60   struct TALER_CURL_PostContext post_ctx;
     61 
     62   /**
     63    * Function to call with the result.
     64    */
     65   TALER_EXCHANGE_ReservesHistoryCallback cb;
     66 
     67   /**
     68    * Public key of the reserve we are querying.
     69    */
     70   struct TALER_ReservePublicKeyP reserve_pub;
     71 
     72   /**
     73    * Closure for @a cb.
     74    */
     75   void *cb_cls;
     76 
     77   /**
     78    * Where to store the etag (if any).
     79    */
     80   uint64_t etag;
     81 
     82 };
     83 
     84 
     85 /**
     86  * Context for history entry helpers.
     87  */
     88 struct HistoryParseContext
     89 {
     90 
     91   /**
     92    * Keys of the exchange we use.
     93    */
     94   const struct TALER_EXCHANGE_Keys *keys;
     95 
     96   /**
     97    * Our reserve public key.
     98    */
     99   const struct TALER_ReservePublicKeyP *reserve_pub;
    100 
    101   /**
    102    * Array of UUIDs.
    103    */
    104   struct GNUNET_HashCode *uuids;
    105 
    106   /**
    107    * Where to sum up total inbound amounts.
    108    */
    109   struct TALER_Amount *total_in;
    110 
    111   /**
    112    * Where to sum up total outbound amounts.
    113    */
    114   struct TALER_Amount *total_out;
    115 
    116   /**
    117    * Number of entries already used in @e uuids.
    118    */
    119   unsigned int uuid_off;
    120 };
    121 
    122 
    123 /**
    124  * Type of a function called to parse a reserve history
    125  * entry @a rh.
    126  *
    127  * @param[in,out] rh where to write the result
    128  * @param[in,out] uc UUID context for duplicate detection
    129  * @param transaction the transaction to parse
    130  * @return #GNUNET_OK on success
    131  */
    132 typedef enum GNUNET_GenericReturnValue
    133 (*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    134                struct HistoryParseContext *uc,
    135                const json_t *transaction);
    136 
    137 
    138 /**
    139  * Parse "credit" reserve history entry.
    140  *
    141  * @param[in,out] rh entry to parse
    142  * @param uc our context
    143  * @param transaction the transaction to parse
    144  * @return #GNUNET_OK on success
    145  */
    146 static enum GNUNET_GenericReturnValue
    147 parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    148               struct HistoryParseContext *uc,
    149               const json_t *transaction)
    150 {
    151   struct TALER_FullPayto wire_uri;
    152   uint64_t wire_reference;
    153   struct GNUNET_TIME_Timestamp timestamp;
    154   struct GNUNET_JSON_Specification withdraw_spec[] = {
    155     GNUNET_JSON_spec_uint64 ("wire_reference",
    156                              &wire_reference),
    157     GNUNET_JSON_spec_timestamp ("timestamp",
    158                                 &timestamp),
    159     TALER_JSON_spec_full_payto_uri ("sender_account_url",
    160                                     &wire_uri),
    161     GNUNET_JSON_spec_end ()
    162   };
    163 
    164   rh->type = TALER_EXCHANGE_RTT_CREDIT;
    165   if (0 >
    166       TALER_amount_add (uc->total_in,
    167                         uc->total_in,
    168                         &rh->amount))
    169   {
    170     /* overflow in history already!? inconceivable! Bad exchange! */
    171     GNUNET_break_op (0);
    172     return GNUNET_SYSERR;
    173   }
    174   if (GNUNET_OK !=
    175       GNUNET_JSON_parse (transaction,
    176                          withdraw_spec,
    177                          NULL, NULL))
    178   {
    179     GNUNET_break_op (0);
    180     return GNUNET_SYSERR;
    181   }
    182   rh->details.in_details.sender_url.full_payto
    183     = GNUNET_strdup (wire_uri.full_payto);
    184   rh->details.in_details.wire_reference = wire_reference;
    185   rh->details.in_details.timestamp = timestamp;
    186   return GNUNET_OK;
    187 }
    188 
    189 
    190 /**
    191  * Parse "withdraw" reserve history entry.
    192  *
    193  * @param[in,out] rh entry to parse
    194  * @param uc our context
    195  * @param transaction the transaction to parse
    196  * @return #GNUNET_OK on success
    197  */
    198 static enum GNUNET_GenericReturnValue
    199 parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    200                 struct HistoryParseContext *uc,
    201                 const json_t *transaction)
    202 {
    203   uint16_t num_coins;
    204   struct TALER_Amount withdraw_fee;
    205   struct TALER_Amount withdraw_amount;
    206   struct TALER_Amount amount_without_fee;
    207   uint8_t max_age = 0;
    208   uint8_t noreveal_index = 0;
    209   struct TALER_HashBlindedPlanchetsP planchets_h;
    210   struct TALER_HashBlindedPlanchetsP selected_h;
    211   struct TALER_ReserveSignatureP reserve_sig;
    212   struct TALER_BlindingMasterSeedP blinding_seed;
    213   struct TALER_DenominationHashP *denom_pub_hashes;
    214   size_t num_denom_pub_hashes;
    215   bool no_max_age;
    216   bool no_noreveal_index;
    217   bool no_blinding_seed;
    218   bool no_selected_h;
    219   struct GNUNET_JSON_Specification withdraw_spec[] = {
    220     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    221                                  &reserve_sig),
    222     GNUNET_JSON_spec_uint16 ("num_coins",
    223                              &num_coins),
    224     GNUNET_JSON_spec_fixed_auto ("planchets_h",
    225                                  &planchets_h),
    226     TALER_JSON_spec_amount_any ("amount",
    227                                 &withdraw_amount),
    228     TALER_JSON_spec_amount_any ("withdraw_fee",
    229                                 &withdraw_fee),
    230     TALER_JSON_spec_array_of_denom_pub_h ("denom_pub_hashes",
    231                                           &num_denom_pub_hashes,
    232                                           &denom_pub_hashes),
    233     GNUNET_JSON_spec_mark_optional (
    234       GNUNET_JSON_spec_fixed_auto ("selected_h",
    235                                    &selected_h),
    236       &no_selected_h),
    237     GNUNET_JSON_spec_mark_optional (
    238       GNUNET_JSON_spec_uint8 ("max_age",
    239                               &max_age),
    240       &no_max_age),
    241     GNUNET_JSON_spec_mark_optional (
    242       GNUNET_JSON_spec_fixed_auto ("blinding_seed",
    243                                    &blinding_seed),
    244       &no_blinding_seed),
    245     GNUNET_JSON_spec_mark_optional (
    246       GNUNET_JSON_spec_uint8 ("noreveal_index",
    247                               &noreveal_index),
    248       &no_noreveal_index),
    249     GNUNET_JSON_spec_end ()
    250   };
    251 
    252   rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
    253   if (GNUNET_OK !=
    254       GNUNET_JSON_parse (transaction,
    255                          withdraw_spec,
    256                          NULL, NULL))
    257   {
    258     GNUNET_break_op (0);
    259     return GNUNET_SYSERR;
    260   }
    261 
    262   if ((no_max_age != no_noreveal_index) ||
    263       (no_max_age != no_selected_h))
    264   {
    265     GNUNET_break_op (0);
    266     GNUNET_JSON_parse_free (withdraw_spec);
    267     return GNUNET_SYSERR;
    268   }
    269   rh->details.withdraw.age_restricted = ! no_max_age;
    270 
    271   if (num_coins != num_denom_pub_hashes)
    272   {
    273     GNUNET_break_op (0);
    274     GNUNET_JSON_parse_free (withdraw_spec);
    275     return GNUNET_SYSERR;
    276   }
    277 
    278   /* Check that the signature is a valid withdraw request */
    279   if (0>TALER_amount_subtract (
    280         &amount_without_fee,
    281         &withdraw_amount,
    282         &withdraw_fee))
    283   {
    284     GNUNET_break_op (0);
    285     GNUNET_JSON_parse_free (withdraw_spec);
    286     return GNUNET_SYSERR;
    287   }
    288 
    289   if (GNUNET_OK !=
    290       TALER_wallet_withdraw_verify (
    291         &amount_without_fee,
    292         &withdraw_fee,
    293         &planchets_h,
    294         no_blinding_seed ? NULL : &blinding_seed,
    295         no_max_age ? NULL : &uc->keys->age_mask,
    296         no_max_age ? 0 : max_age,
    297         uc->reserve_pub,
    298         &reserve_sig))
    299   {
    300     GNUNET_break_op (0);
    301     GNUNET_JSON_parse_free (withdraw_spec);
    302     return GNUNET_SYSERR;
    303   }
    304 
    305   rh->details.withdraw.num_coins = num_coins;
    306   rh->details.withdraw.age_restricted = ! no_max_age;
    307   rh->details.withdraw.max_age = max_age;
    308   rh->details.withdraw.planchets_h = planchets_h;
    309   rh->details.withdraw.selected_h = selected_h;
    310   rh->details.withdraw.noreveal_index = noreveal_index;
    311   rh->details.withdraw.no_blinding_seed = no_blinding_seed;
    312   if (! no_blinding_seed)
    313     rh->details.withdraw.blinding_seed = blinding_seed;
    314 
    315   /* check that withdraw fee matches expectations! */
    316   {
    317     const struct TALER_EXCHANGE_Keys *key_state;
    318     struct TALER_Amount fee_acc;
    319     struct TALER_Amount amount_acc;
    320 
    321     GNUNET_assert (GNUNET_OK ==
    322                    TALER_amount_set_zero (withdraw_amount.currency,
    323                                           &fee_acc));
    324     GNUNET_assert (GNUNET_OK ==
    325                    TALER_amount_set_zero (withdraw_amount.currency,
    326                                           &amount_acc));
    327 
    328     key_state = uc->keys;
    329 
    330     /* accumulate the withdraw fees */
    331     for (size_t i=0; i < num_coins; i++)
    332     {
    333       const struct TALER_EXCHANGE_DenomPublicKey *dki;
    334 
    335       dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
    336                                                          &denom_pub_hashes[i]);
    337       if (NULL == dki)
    338       {
    339         GNUNET_break_op (0);
    340         GNUNET_JSON_parse_free (withdraw_spec);
    341         return GNUNET_SYSERR;
    342       }
    343       GNUNET_assert (0 <=
    344                      TALER_amount_add (&fee_acc,
    345                                        &fee_acc,
    346                                        &dki->fees.withdraw));
    347       GNUNET_assert (0 <=
    348                      TALER_amount_add (&amount_acc,
    349                                        &amount_acc,
    350                                        &dki->value));
    351     }
    352 
    353     if ( (GNUNET_YES !=
    354           TALER_amount_cmp_currency (&fee_acc,
    355                                      &withdraw_fee)) ||
    356          (0 !=
    357           TALER_amount_cmp (&amount_acc,
    358                             &amount_without_fee)) )
    359     {
    360       GNUNET_break_op (0);
    361       GNUNET_JSON_parse_free (withdraw_spec);
    362       return GNUNET_SYSERR;
    363     }
    364     rh->details.withdraw.fee = withdraw_fee;
    365   }
    366 
    367   #pragma message "is out_authorization_sig still needed? Not set anywhere"
    368   rh->details.withdraw.out_authorization_sig
    369     = json_object_get (transaction,
    370                        "signature");
    371   /* Check check that the same withdraw transaction
    372        isn't listed twice by the exchange. We use the
    373        "uuid" array to remember the hashes of all
    374        signatures, and compare the hashes to find
    375        duplicates. */
    376   GNUNET_CRYPTO_hash (&reserve_sig,
    377                       sizeof (reserve_sig),
    378                       &uc->uuids[uc->uuid_off]);
    379   for (unsigned int i = 0; i<uc->uuid_off; i++)
    380   {
    381     if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
    382                             &uc->uuids[i]))
    383     {
    384       GNUNET_break_op (0);
    385       GNUNET_JSON_parse_free (withdraw_spec);
    386       return GNUNET_SYSERR;
    387     }
    388   }
    389   uc->uuid_off++;
    390 
    391   if (0 >
    392       TALER_amount_add (uc->total_out,
    393                         uc->total_out,
    394                         &rh->amount))
    395   {
    396     /* overflow in history already!? inconceivable! Bad exchange! */
    397     GNUNET_break_op (0);
    398     GNUNET_JSON_parse_free (withdraw_spec);
    399     return GNUNET_SYSERR;
    400   }
    401   GNUNET_JSON_parse_free (withdraw_spec);
    402   return GNUNET_OK;
    403 }
    404 
    405 
    406 /**
    407  * Parse "recoup" reserve history entry.
    408  *
    409  * @param[in,out] rh entry to parse
    410  * @param uc our context
    411  * @param transaction the transaction to parse
    412  * @return #GNUNET_OK on success
    413  */
    414 static enum GNUNET_GenericReturnValue
    415 parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    416               struct HistoryParseContext *uc,
    417               const json_t *transaction)
    418 {
    419   const struct TALER_EXCHANGE_Keys *key_state;
    420   struct GNUNET_JSON_Specification recoup_spec[] = {
    421     GNUNET_JSON_spec_fixed_auto ("coin_pub",
    422                                  &rh->details.recoup_details.coin_pub),
    423     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    424                                  &rh->details.recoup_details.exchange_sig),
    425     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    426                                  &rh->details.recoup_details.exchange_pub),
    427     GNUNET_JSON_spec_timestamp ("timestamp",
    428                                 &rh->details.recoup_details.timestamp),
    429     GNUNET_JSON_spec_end ()
    430   };
    431 
    432   rh->type = TALER_EXCHANGE_RTT_RECOUP;
    433   if (GNUNET_OK !=
    434       GNUNET_JSON_parse (transaction,
    435                          recoup_spec,
    436                          NULL, NULL))
    437   {
    438     GNUNET_break_op (0);
    439     return GNUNET_SYSERR;
    440   }
    441   key_state = uc->keys;
    442   if (GNUNET_OK !=
    443       TALER_EXCHANGE_test_signing_key (key_state,
    444                                        &rh->details.
    445                                        recoup_details.exchange_pub))
    446   {
    447     GNUNET_break_op (0);
    448     return GNUNET_SYSERR;
    449   }
    450   if (GNUNET_OK !=
    451       TALER_exchange_online_confirm_recoup_verify (
    452         rh->details.recoup_details.timestamp,
    453         &rh->amount,
    454         &rh->details.recoup_details.coin_pub,
    455         uc->reserve_pub,
    456         &rh->details.recoup_details.exchange_pub,
    457         &rh->details.recoup_details.exchange_sig))
    458   {
    459     GNUNET_break_op (0);
    460     return GNUNET_SYSERR;
    461   }
    462   if (0 >
    463       TALER_amount_add (uc->total_in,
    464                         uc->total_in,
    465                         &rh->amount))
    466   {
    467     /* overflow in history already!? inconceivable! Bad exchange! */
    468     GNUNET_break_op (0);
    469     return GNUNET_SYSERR;
    470   }
    471   return GNUNET_OK;
    472 }
    473 
    474 
    475 /**
    476  * Parse "closing" reserve history entry.
    477  *
    478  * @param[in,out] rh entry to parse
    479  * @param uc our context
    480  * @param transaction the transaction to parse
    481  * @return #GNUNET_OK on success
    482  */
    483 static enum GNUNET_GenericReturnValue
    484 parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    485                struct HistoryParseContext *uc,
    486                const json_t *transaction)
    487 {
    488   const struct TALER_EXCHANGE_Keys *key_state;
    489   struct TALER_FullPayto receiver_uri;
    490   struct GNUNET_JSON_Specification closing_spec[] = {
    491     TALER_JSON_spec_full_payto_uri (
    492       "receiver_account_details",
    493       &receiver_uri),
    494     GNUNET_JSON_spec_fixed_auto ("wtid",
    495                                  &rh->details.close_details.wtid),
    496     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    497                                  &rh->details.close_details.exchange_sig),
    498     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    499                                  &rh->details.close_details.exchange_pub),
    500     TALER_JSON_spec_amount_any ("closing_fee",
    501                                 &rh->details.close_details.fee),
    502     GNUNET_JSON_spec_timestamp ("timestamp",
    503                                 &rh->details.close_details.timestamp),
    504     GNUNET_JSON_spec_end ()
    505   };
    506 
    507   rh->type = TALER_EXCHANGE_RTT_CLOSING;
    508   if (GNUNET_OK !=
    509       GNUNET_JSON_parse (transaction,
    510                          closing_spec,
    511                          NULL, NULL))
    512   {
    513     GNUNET_break_op (0);
    514     return GNUNET_SYSERR;
    515   }
    516   key_state = uc->keys;
    517   if (GNUNET_OK !=
    518       TALER_EXCHANGE_test_signing_key (
    519         key_state,
    520         &rh->details.close_details.exchange_pub))
    521   {
    522     GNUNET_break_op (0);
    523     return GNUNET_SYSERR;
    524   }
    525   if (GNUNET_OK !=
    526       TALER_exchange_online_reserve_closed_verify (
    527         rh->details.close_details.timestamp,
    528         &rh->amount,
    529         &rh->details.close_details.fee,
    530         receiver_uri,
    531         &rh->details.close_details.wtid,
    532         uc->reserve_pub,
    533         &rh->details.close_details.exchange_pub,
    534         &rh->details.close_details.exchange_sig))
    535   {
    536     GNUNET_break_op (0);
    537     return GNUNET_SYSERR;
    538   }
    539   if (0 >
    540       TALER_amount_add (uc->total_out,
    541                         uc->total_out,
    542                         &rh->amount))
    543   {
    544     /* overflow in history already!? inconceivable! Bad exchange! */
    545     GNUNET_break_op (0);
    546     return GNUNET_SYSERR;
    547   }
    548   rh->details.close_details.receiver_account_details.full_payto
    549     = GNUNET_strdup (receiver_uri.full_payto);
    550   return GNUNET_OK;
    551 }
    552 
    553 
    554 /**
    555  * Parse "merge" reserve history entry.
    556  *
    557  * @param[in,out] rh entry to parse
    558  * @param uc our context
    559  * @param transaction the transaction to parse
    560  * @return #GNUNET_OK on success
    561  */
    562 static enum GNUNET_GenericReturnValue
    563 parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    564              struct HistoryParseContext *uc,
    565              const json_t *transaction)
    566 {
    567   uint32_t flags32;
    568   struct GNUNET_JSON_Specification merge_spec[] = {
    569     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    570                                  &rh->details.merge_details.h_contract_terms),
    571     GNUNET_JSON_spec_fixed_auto ("merge_pub",
    572                                  &rh->details.merge_details.merge_pub),
    573     GNUNET_JSON_spec_fixed_auto ("purse_pub",
    574                                  &rh->details.merge_details.purse_pub),
    575     GNUNET_JSON_spec_uint32 ("min_age",
    576                              &rh->details.merge_details.min_age),
    577     GNUNET_JSON_spec_uint32 ("flags",
    578                              &flags32),
    579     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    580                                  &rh->details.merge_details.reserve_sig),
    581     TALER_JSON_spec_amount_any ("purse_fee",
    582                                 &rh->details.merge_details.purse_fee),
    583     GNUNET_JSON_spec_timestamp ("merge_timestamp",
    584                                 &rh->details.merge_details.merge_timestamp),
    585     GNUNET_JSON_spec_timestamp ("purse_expiration",
    586                                 &rh->details.merge_details.purse_expiration),
    587     GNUNET_JSON_spec_bool ("merged",
    588                            &rh->details.merge_details.merged),
    589     GNUNET_JSON_spec_end ()
    590   };
    591 
    592   rh->type = TALER_EXCHANGE_RTT_MERGE;
    593   if (GNUNET_OK !=
    594       GNUNET_JSON_parse (transaction,
    595                          merge_spec,
    596                          NULL, NULL))
    597   {
    598     GNUNET_break_op (0);
    599     return GNUNET_SYSERR;
    600   }
    601   rh->details.merge_details.flags =
    602     (enum TALER_WalletAccountMergeFlags) flags32;
    603   if (GNUNET_OK !=
    604       TALER_wallet_account_merge_verify (
    605         rh->details.merge_details.merge_timestamp,
    606         &rh->details.merge_details.purse_pub,
    607         rh->details.merge_details.purse_expiration,
    608         &rh->details.merge_details.h_contract_terms,
    609         &rh->amount,
    610         &rh->details.merge_details.purse_fee,
    611         rh->details.merge_details.min_age,
    612         rh->details.merge_details.flags,
    613         uc->reserve_pub,
    614         &rh->details.merge_details.reserve_sig))
    615   {
    616     GNUNET_break_op (0);
    617     return GNUNET_SYSERR;
    618   }
    619   if (rh->details.merge_details.merged)
    620   {
    621     if (0 >
    622         TALER_amount_add (uc->total_in,
    623                           uc->total_in,
    624                           &rh->amount))
    625     {
    626       /* overflow in history already!? inconceivable! Bad exchange! */
    627       GNUNET_break_op (0);
    628       return GNUNET_SYSERR;
    629     }
    630   }
    631   else
    632   {
    633     if (0 >
    634         TALER_amount_add (uc->total_out,
    635                           uc->total_out,
    636                           &rh->details.merge_details.purse_fee))
    637     {
    638       /* overflow in history already!? inconceivable! Bad exchange! */
    639       GNUNET_break_op (0);
    640       return GNUNET_SYSERR;
    641     }
    642   }
    643   return GNUNET_OK;
    644 }
    645 
    646 
    647 /**
    648  * Parse "open" reserve open entry.
    649  *
    650  * @param[in,out] rh entry to parse
    651  * @param uc our context
    652  * @param transaction the transaction to parse
    653  * @return #GNUNET_OK on success
    654  */
    655 static enum GNUNET_GenericReturnValue
    656 parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    657             struct HistoryParseContext *uc,
    658             const json_t *transaction)
    659 {
    660   struct GNUNET_JSON_Specification open_spec[] = {
    661     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    662                                  &rh->details.open_request.reserve_sig),
    663     TALER_JSON_spec_amount_any ("open_payment",
    664                                 &rh->details.open_request.reserve_payment),
    665     GNUNET_JSON_spec_uint32 ("requested_min_purses",
    666                              &rh->details.open_request.purse_limit),
    667     GNUNET_JSON_spec_timestamp ("request_timestamp",
    668                                 &rh->details.open_request.request_timestamp),
    669     GNUNET_JSON_spec_timestamp ("requested_expiration",
    670                                 &rh->details.open_request.reserve_expiration),
    671     GNUNET_JSON_spec_end ()
    672   };
    673 
    674   rh->type = TALER_EXCHANGE_RTT_OPEN;
    675   if (GNUNET_OK !=
    676       GNUNET_JSON_parse (transaction,
    677                          open_spec,
    678                          NULL, NULL))
    679   {
    680     GNUNET_break_op (0);
    681     return GNUNET_SYSERR;
    682   }
    683   if (GNUNET_OK !=
    684       TALER_wallet_reserve_open_verify (
    685         &rh->amount,
    686         rh->details.open_request.request_timestamp,
    687         rh->details.open_request.reserve_expiration,
    688         rh->details.open_request.purse_limit,
    689         uc->reserve_pub,
    690         &rh->details.open_request.reserve_sig))
    691   {
    692     GNUNET_break_op (0);
    693     return GNUNET_SYSERR;
    694   }
    695   if (0 >
    696       TALER_amount_add (uc->total_out,
    697                         uc->total_out,
    698                         &rh->amount))
    699   {
    700     /* overflow in history already!? inconceivable! Bad exchange! */
    701     GNUNET_break_op (0);
    702     return GNUNET_SYSERR;
    703   }
    704   return GNUNET_OK;
    705 }
    706 
    707 
    708 /**
    709  * Parse "close" reserve close entry.
    710  *
    711  * @param[in,out] rh entry to parse
    712  * @param uc our context
    713  * @param transaction the transaction to parse
    714  * @return #GNUNET_OK on success
    715  */
    716 static enum GNUNET_GenericReturnValue
    717 parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    718              struct HistoryParseContext *uc,
    719              const json_t *transaction)
    720 {
    721   struct GNUNET_JSON_Specification close_spec[] = {
    722     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    723                                  &rh->details.close_request.reserve_sig),
    724     GNUNET_JSON_spec_mark_optional (
    725       GNUNET_JSON_spec_fixed_auto ("h_payto",
    726                                    &rh->details.close_request.
    727                                    target_account_h_payto),
    728       NULL),
    729     GNUNET_JSON_spec_timestamp ("request_timestamp",
    730                                 &rh->details.close_request.request_timestamp),
    731     GNUNET_JSON_spec_end ()
    732   };
    733 
    734   rh->type = TALER_EXCHANGE_RTT_CLOSE;
    735   if (GNUNET_OK !=
    736       GNUNET_JSON_parse (transaction,
    737                          close_spec,
    738                          NULL, NULL))
    739   {
    740     GNUNET_break_op (0);
    741     return GNUNET_SYSERR;
    742   }
    743   /* force amount to invalid */
    744   memset (&rh->amount,
    745           0,
    746           sizeof (rh->amount));
    747   if (GNUNET_OK !=
    748       TALER_wallet_reserve_close_verify (
    749         rh->details.close_request.request_timestamp,
    750         &rh->details.close_request.target_account_h_payto,
    751         uc->reserve_pub,
    752         &rh->details.close_request.reserve_sig))
    753   {
    754     GNUNET_break_op (0);
    755     return GNUNET_SYSERR;
    756   }
    757   return GNUNET_OK;
    758 }
    759 
    760 
    761 static void
    762 free_reserve_history (
    763   unsigned int len,
    764   struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
    765 {
    766   for (unsigned int i = 0; i<len; i++)
    767   {
    768     struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i];
    769 
    770     switch (rhi->type)
    771     {
    772     case TALER_EXCHANGE_RTT_CREDIT:
    773       GNUNET_free (rhi->details.in_details.sender_url.full_payto);
    774       break;
    775     case TALER_EXCHANGE_RTT_WITHDRAWAL:
    776       break;
    777     case TALER_EXCHANGE_RTT_RECOUP:
    778       break;
    779     case TALER_EXCHANGE_RTT_CLOSING:
    780       break;
    781     case TALER_EXCHANGE_RTT_MERGE:
    782       break;
    783     case TALER_EXCHANGE_RTT_OPEN:
    784       break;
    785     case TALER_EXCHANGE_RTT_CLOSE:
    786       GNUNET_free (rhi->details.close_details
    787                    .receiver_account_details.full_payto);
    788       break;
    789     }
    790   }
    791   GNUNET_free (rhistory);
    792 }
    793 
    794 
    795 /**
    796  * Parse history given in JSON format and return it in binary
    797  * format.
    798  *
    799  * @param keys exchange keys
    800  * @param history JSON array with the history
    801  * @param reserve_pub public key of the reserve to inspect
    802  * @param currency currency we expect the balance to be in
    803  * @param[out] total_in set to value of credits to reserve
    804  * @param[out] total_out set to value of debits from reserve
    805  * @param history_length number of entries in @a history
    806  * @param[out] rhistory array of length @a history_length, set to the
    807  *             parsed history entries
    808  * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
    809  *         were set,
    810  *         #GNUNET_SYSERR if there was a protocol violation in @a history
    811  */
    812 static enum GNUNET_GenericReturnValue
    813 parse_reserve_history (
    814   const struct TALER_EXCHANGE_Keys *keys,
    815   const json_t *history,
    816   const struct TALER_ReservePublicKeyP *reserve_pub,
    817   const char *currency,
    818   struct TALER_Amount *total_in,
    819   struct TALER_Amount *total_out,
    820   unsigned int history_length,
    821   struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
    822 {
    823   const struct
    824   {
    825     const char *type;
    826     ParseHelper helper;
    827   } map[] = {
    828     { "CREDIT", &parse_credit },
    829     { "WITHDRAW", &parse_withdraw },
    830     { "RECOUP", &parse_recoup },
    831     { "MERGE", &parse_merge },
    832     { "CLOSING", &parse_closing },
    833     { "OPEN", &parse_open },
    834     { "CLOSE", &parse_close },
    835     { NULL, NULL }
    836   };
    837   struct GNUNET_HashCode uuid[history_length];
    838   struct HistoryParseContext uc = {
    839     .keys = keys,
    840     .reserve_pub = reserve_pub,
    841     .uuids = uuid,
    842     .total_in = total_in,
    843     .total_out = total_out
    844   };
    845 
    846   GNUNET_assert (GNUNET_OK ==
    847                  TALER_amount_set_zero (currency,
    848                                         total_in));
    849   GNUNET_assert (GNUNET_OK ==
    850                  TALER_amount_set_zero (currency,
    851                                         total_out));
    852   for (unsigned int off = 0; off<history_length; off++)
    853   {
    854     struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
    855     json_t *transaction;
    856     struct TALER_Amount amount;
    857     const char *type;
    858     struct GNUNET_JSON_Specification hist_spec[] = {
    859       GNUNET_JSON_spec_string ("type",
    860                                &type),
    861       TALER_JSON_spec_amount_any ("amount",
    862                                   &amount),
    863       /* 'wire' and 'signature' are optional depending on 'type'! */
    864       GNUNET_JSON_spec_end ()
    865     };
    866     bool found = false;
    867 
    868     transaction = json_array_get (history,
    869                                   off);
    870     if (GNUNET_OK !=
    871         GNUNET_JSON_parse (transaction,
    872                            hist_spec,
    873                            NULL, NULL))
    874     {
    875       GNUNET_break_op (0);
    876       json_dumpf (transaction,
    877                   stderr,
    878                   JSON_INDENT (2));
    879       return GNUNET_SYSERR;
    880     }
    881     rh->amount = amount;
    882     if (GNUNET_YES !=
    883         TALER_amount_cmp_currency (&amount,
    884                                    total_in))
    885     {
    886       GNUNET_break_op (0);
    887       return GNUNET_SYSERR;
    888     }
    889     for (unsigned int i = 0; NULL != map[i].type; i++)
    890     {
    891       if (0 == strcasecmp (map[i].type,
    892                            type))
    893       {
    894         found = true;
    895         if (GNUNET_OK !=
    896             map[i].helper (rh,
    897                            &uc,
    898                            transaction))
    899         {
    900           GNUNET_break_op (0);
    901           return GNUNET_SYSERR;
    902         }
    903         break;
    904       }
    905     }
    906     if (! found)
    907     {
    908       /* unexpected 'type', protocol incompatibility, complain! */
    909       GNUNET_break_op (0);
    910       return GNUNET_SYSERR;
    911     }
    912   }
    913   return GNUNET_OK;
    914 }
    915 
    916 
    917 /**
    918  * Handle HTTP header received by curl.
    919  *
    920  * @param buffer one line of HTTP header data
    921  * @param size size of an item
    922  * @param nitems number of items passed
    923  * @param userdata our `struct TALER_EXCHANGE_ReservesHistoryHandle *`
    924  * @return `size * nitems`
    925  */
    926 static size_t
    927 handle_header (char *buffer,
    928                size_t size,
    929                size_t nitems,
    930                void *userdata)
    931 {
    932   struct TALER_EXCHANGE_ReservesHistoryHandle *rhh = userdata;
    933   size_t total = size * nitems;
    934   char *ndup;
    935   const char *hdr_type;
    936   char *hdr_val;
    937   char *sp;
    938 
    939   ndup = GNUNET_strndup (buffer,
    940                          total);
    941   hdr_type = strtok_r (ndup,
    942                        ":",
    943                        &sp);
    944   if (NULL == hdr_type)
    945   {
    946     GNUNET_free (ndup);
    947     return total;
    948   }
    949   hdr_val = strtok_r (NULL,
    950                       "\n\r",
    951                       &sp);
    952   if (NULL == hdr_val)
    953   {
    954     GNUNET_free (ndup);
    955     return total;
    956   }
    957   if (' ' == *hdr_val)
    958     hdr_val++;
    959   if (0 == strcasecmp (hdr_type,
    960                        MHD_HTTP_HEADER_ETAG))
    961   {
    962     unsigned long long tval;
    963     char dummy;
    964 
    965     if (1 !=
    966         sscanf (hdr_val,
    967                 "\"%llu\"%c",
    968                 &tval,
    969                 &dummy))
    970     {
    971       GNUNET_break_op (0);
    972       GNUNET_free (ndup);
    973       return 0;
    974     }
    975     rhh->etag = (uint64_t) tval;
    976   }
    977   GNUNET_free (ndup);
    978   return total;
    979 }
    980 
    981 
    982 /**
    983  * We received an #MHD_HTTP_OK history code. Handle the JSON
    984  * response.
    985  *
    986  * @param rsh handle of the request
    987  * @param j JSON response
    988  * @return #GNUNET_OK on success
    989  */
    990 static enum GNUNET_GenericReturnValue
    991 handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
    992                             const json_t *j)
    993 {
    994   const json_t *history;
    995   unsigned int len;
    996   struct TALER_EXCHANGE_ReserveHistory rs = {
    997     .hr.reply = j,
    998     .hr.http_status = MHD_HTTP_OK,
    999     .details.ok.etag = rsh->etag
   1000   };
   1001   struct GNUNET_JSON_Specification spec[] = {
   1002     TALER_JSON_spec_amount_any ("balance",
   1003                                 &rs.details.ok.balance),
   1004     GNUNET_JSON_spec_array_const ("history",
   1005                                   &history),
   1006     GNUNET_JSON_spec_end ()
   1007   };
   1008 
   1009   if (GNUNET_OK !=
   1010       GNUNET_JSON_parse (j,
   1011                          spec,
   1012                          NULL,
   1013                          NULL))
   1014   {
   1015     GNUNET_break_op (0);
   1016     return GNUNET_SYSERR;
   1017   }
   1018   len = json_array_size (history);
   1019   {
   1020     struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
   1021 
   1022     rhistory = GNUNET_new_array (len,
   1023                                  struct TALER_EXCHANGE_ReserveHistoryEntry);
   1024     if (GNUNET_OK !=
   1025         parse_reserve_history (rsh->keys,
   1026                                history,
   1027                                &rsh->reserve_pub,
   1028                                rs.details.ok.balance.currency,
   1029                                &rs.details.ok.total_in,
   1030                                &rs.details.ok.total_out,
   1031                                len,
   1032                                rhistory))
   1033     {
   1034       GNUNET_break_op (0);
   1035       free_reserve_history (len,
   1036                             rhistory);
   1037       GNUNET_JSON_parse_free (spec);
   1038       return GNUNET_SYSERR;
   1039     }
   1040     if (NULL != rsh->cb)
   1041     {
   1042       rs.details.ok.history = rhistory;
   1043       rs.details.ok.history_len = len;
   1044       rsh->cb (rsh->cb_cls,
   1045                &rs);
   1046       rsh->cb = NULL;
   1047     }
   1048     free_reserve_history (len,
   1049                           rhistory);
   1050   }
   1051   return GNUNET_OK;
   1052 }
   1053 
   1054 
   1055 /**
   1056  * Function called when we're done processing the
   1057  * HTTP /reserves/$RID/history request.
   1058  *
   1059  * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle`
   1060  * @param response_code HTTP response code, 0 on error
   1061  * @param response parsed JSON result, NULL on error
   1062  */
   1063 static void
   1064 handle_reserves_history_finished (void *cls,
   1065                                   long response_code,
   1066                                   const void *response)
   1067 {
   1068   struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls;
   1069   const json_t *j = response;
   1070   struct TALER_EXCHANGE_ReserveHistory rs = {
   1071     .hr.reply = j,
   1072     .hr.http_status = (unsigned int) response_code
   1073   };
   1074 
   1075   rsh->job = NULL;
   1076   switch (response_code)
   1077   {
   1078   case 0:
   1079     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
   1080     break;
   1081   case MHD_HTTP_OK:
   1082     if (GNUNET_OK !=
   1083         handle_reserves_history_ok (rsh,
   1084                                     j))
   1085     {
   1086       rs.hr.http_status = 0;
   1087       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
   1088     }
   1089     break;
   1090   case MHD_HTTP_BAD_REQUEST:
   1091     /* This should never happen, either us or the exchange is buggy
   1092        (or API version conflict); just pass JSON reply to the application */
   1093     GNUNET_break (0);
   1094     rs.hr.ec = TALER_JSON_get_error_code (j);
   1095     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1096     break;
   1097   case MHD_HTTP_FORBIDDEN:
   1098     /* This should never happen, either us or the exchange is buggy
   1099        (or API version conflict); just pass JSON reply to the application */
   1100     GNUNET_break (0);
   1101     rs.hr.ec = TALER_JSON_get_error_code (j);
   1102     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1103     break;
   1104   case MHD_HTTP_NOT_FOUND:
   1105     /* Nothing really to verify, this should never
   1106        happen, we should pass the JSON reply to the application */
   1107     rs.hr.ec = TALER_JSON_get_error_code (j);
   1108     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1109     break;
   1110   case MHD_HTTP_INTERNAL_SERVER_ERROR:
   1111     /* Server had an internal issue; we should retry, but this API
   1112        leaves this to the application */
   1113     rs.hr.ec = TALER_JSON_get_error_code (j);
   1114     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1115     break;
   1116   default:
   1117     /* unexpected response code */
   1118     GNUNET_break_op (0);
   1119     rs.hr.ec = TALER_JSON_get_error_code (j);
   1120     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1121     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1122                 "Unexpected response code %u/%d for reserves history\n",
   1123                 (unsigned int) response_code,
   1124                 (int) rs.hr.ec);
   1125     break;
   1126   }
   1127   if (NULL != rsh->cb)
   1128   {
   1129     rsh->cb (rsh->cb_cls,
   1130              &rs);
   1131     rsh->cb = NULL;
   1132   }
   1133   TALER_EXCHANGE_reserves_history_cancel (rsh);
   1134 }
   1135 
   1136 
   1137 struct TALER_EXCHANGE_ReservesHistoryHandle *
   1138 TALER_EXCHANGE_reserves_history (
   1139   struct GNUNET_CURL_Context *ctx,
   1140   const char *url,
   1141   struct TALER_EXCHANGE_Keys *keys,
   1142   const struct TALER_ReservePrivateKeyP *reserve_priv,
   1143   uint64_t start_off,
   1144   TALER_EXCHANGE_ReservesHistoryCallback cb,
   1145   void *cb_cls)
   1146 {
   1147   struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
   1148   CURL *eh;
   1149   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64];
   1150   struct curl_slist *job_headers;
   1151 
   1152   rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
   1153   rsh->cb = cb;
   1154   rsh->cb_cls = cb_cls;
   1155   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
   1156                                       &rsh->reserve_pub.eddsa_pub);
   1157   {
   1158     char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
   1159     char *end;
   1160 
   1161     end = GNUNET_STRINGS_data_to_string (
   1162       &rsh->reserve_pub,
   1163       sizeof (rsh->reserve_pub),
   1164       pub_str,
   1165       sizeof (pub_str));
   1166     *end = '\0';
   1167     if (0 != start_off)
   1168       GNUNET_snprintf (arg_str,
   1169                        sizeof (arg_str),
   1170                        "reserves/%s/history?start=%llu",
   1171                        pub_str,
   1172                        (unsigned long long) start_off);
   1173     else
   1174       GNUNET_snprintf (arg_str,
   1175                        sizeof (arg_str),
   1176                        "reserves/%s/history",
   1177                        pub_str);
   1178   }
   1179   rsh->url = TALER_url_join (url,
   1180                              arg_str,
   1181                              NULL);
   1182   if (NULL == rsh->url)
   1183   {
   1184     GNUNET_free (rsh);
   1185     return NULL;
   1186   }
   1187   eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
   1188   if (NULL == eh)
   1189   {
   1190     GNUNET_break (0);
   1191     GNUNET_free (rsh->url);
   1192     GNUNET_free (rsh);
   1193     return NULL;
   1194   }
   1195   GNUNET_assert (CURLE_OK ==
   1196                  curl_easy_setopt (eh,
   1197                                    CURLOPT_HEADERFUNCTION,
   1198                                    &handle_header));
   1199   GNUNET_assert (CURLE_OK ==
   1200                  curl_easy_setopt (eh,
   1201                                    CURLOPT_HEADERDATA,
   1202                                    rsh));
   1203   {
   1204     struct TALER_ReserveSignatureP reserve_sig;
   1205     char *sig_hdr;
   1206     char *hdr;
   1207 
   1208     TALER_wallet_reserve_history_sign (start_off,
   1209                                        reserve_priv,
   1210                                        &reserve_sig);
   1211 
   1212     sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
   1213       &reserve_sig,
   1214       sizeof (reserve_sig));
   1215     GNUNET_asprintf (&hdr,
   1216                      "%s: %s",
   1217                      TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
   1218                      sig_hdr);
   1219     GNUNET_free (sig_hdr);
   1220     job_headers = curl_slist_append (NULL,
   1221                                      hdr);
   1222     GNUNET_free (hdr);
   1223     if (NULL == job_headers)
   1224     {
   1225       GNUNET_break (0);
   1226       curl_easy_cleanup (eh);
   1227       return NULL;
   1228     }
   1229   }
   1230 
   1231   rsh->keys = TALER_EXCHANGE_keys_incref (keys);
   1232   rsh->job = GNUNET_CURL_job_add2 (ctx,
   1233                                    eh,
   1234                                    job_headers,
   1235                                    &handle_reserves_history_finished,
   1236                                    rsh);
   1237   curl_slist_free_all (job_headers);
   1238   return rsh;
   1239 }
   1240 
   1241 
   1242 void
   1243 TALER_EXCHANGE_reserves_history_cancel (
   1244   struct TALER_EXCHANGE_ReservesHistoryHandle *rsh)
   1245 {
   1246   if (NULL != rsh->job)
   1247   {
   1248     GNUNET_CURL_job_cancel (rsh->job);
   1249     rsh->job = NULL;
   1250   }
   1251   TALER_curl_easy_post_finished (&rsh->post_ctx);
   1252   GNUNET_free (rsh->url);
   1253   TALER_EXCHANGE_keys_decref (rsh->keys);
   1254   GNUNET_free (rsh);
   1255 }
   1256 
   1257 
   1258 /* end of exchange_api_reserves_history.c */