LCOV - code coverage report
Current view: top level - lib - exchange_api_reserves_history.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 230 458 50.2 %
Date: 2025-06-22 12:09:43 Functions: 10 14 71.4 %

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

Generated by: LCOV version 1.16