LCOV - code coverage report
Current view: top level - lib - exchange_api_get-reserves-RESERVE_PUB-history.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 54.7 % 503 275
Test Date: 2026-10-29 19:05:17 Functions: 75.0 % 16 12

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

Generated by: LCOV version 2.0-1