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: 50.1 % 479 240
Test Date: 2026-03-10 12:10:57 Functions: 68.8 % 16 11

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

Generated by: LCOV version 2.0-1