LCOV - code coverage report
Current view: top level - lib - exchange_api_get-reserves-RESERVE_PUB-history.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 49.3 % 499 246
Test Date: 2026-09-09 15:11:34 Functions: 68.8 % 16 11

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

Generated by: LCOV version 2.0-1