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

Generated by: LCOV version 1.16