LCOV - code coverage report
Current view: top level - lib - exchange_api_reserves_history.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 50.2 % 458 230
Test Date: 2026-01-28 06:10:56 Functions: 71.4 % 14 10

            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 2.0-1