LCOV - code coverage report
Current view: top level - exchange-lib - exchange_api_reserve.c (source / functions) Hit Total Coverage
Test: rcoverage.info Lines: 224 361 62.0 %
Date: 2017-09-17 17:24:28 Functions: 10 10 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014, 2015 GNUnet e.V.
       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 exchange-lib/exchange_api_reserve.c
      19             :  * @brief Implementation of the /reserve requests of the exchange's HTTP API
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <curl/curl.h>
      24             : #include <jansson.h>
      25             : #include <microhttpd.h> /* just for HTTP status codes */
      26             : #include <gnunet/gnunet_util_lib.h>
      27             : #include <gnunet/gnunet_json_lib.h>
      28             : #include <gnunet/gnunet_curl_lib.h>
      29             : #include "taler_exchange_service.h"
      30             : #include "taler_json_lib.h"
      31             : #include "exchange_api_handle.h"
      32             : #include "taler_signatures.h"
      33             : 
      34             : 
      35             : /* ********************** /reserve/status ********************** */
      36             : 
      37             : /**
      38             :  * @brief A Withdraw Status Handle
      39             :  */
      40             : struct TALER_EXCHANGE_ReserveStatusHandle
      41             : {
      42             : 
      43             :   /**
      44             :    * The connection to exchange this request handle will use
      45             :    */
      46             :   struct TALER_EXCHANGE_Handle *exchange;
      47             : 
      48             :   /**
      49             :    * The url for this request.
      50             :    */
      51             :   char *url;
      52             : 
      53             :   /**
      54             :    * Handle for the request.
      55             :    */
      56             :   struct GNUNET_CURL_Job *job;
      57             : 
      58             :   /**
      59             :    * Function to call with the result.
      60             :    */
      61             :   TALER_EXCHANGE_ReserveStatusResultCallback cb;
      62             : 
      63             :   /**
      64             :    * Public key of the reserve we are querying.
      65             :    */
      66             :   struct TALER_ReservePublicKeyP reserve_pub;
      67             : 
      68             :   /**
      69             :    * Closure for @a cb.
      70             :    */
      71             :   void *cb_cls;
      72             : 
      73             : };
      74             : 
      75             : 
      76             : /**
      77             :  * Parse history given in JSON format and return it in binary
      78             :  * format.
      79             :  *
      80             :  * @param exchange connection to the exchange we can use
      81             :  * @param history JSON array with the history
      82             :  * @param reserve_pub public key of the reserve to inspect
      83             :  * @param currency currency we expect the balance to be in
      84             :  * @param[out] balance final balance
      85             :  * @param history_length number of entries in @a history
      86             :  * @param[out] rhistory array of length @a history_length, set to the
      87             :  *             parsed history entries
      88             :  * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
      89             :  *         were set,
      90             :  *         #GNUNET_SYSERR if there was a protocol violation in @a history
      91             :  */
      92             : static int
      93           3 : parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange,
      94             :                        const json_t *history,
      95             :                        const struct TALER_ReservePublicKeyP *reserve_pub,
      96             :                        const char *currency,
      97             :                        struct TALER_Amount *balance,
      98             :                        unsigned int history_length,
      99             :                        struct TALER_EXCHANGE_ReserveHistory *rhistory)
     100           3 : {
     101           3 :   struct GNUNET_HashCode uuid[history_length];
     102             :   unsigned int uuid_off;
     103             :   struct TALER_Amount total_in;
     104             :   struct TALER_Amount total_out;
     105             :   size_t off;
     106             : 
     107           3 :   TALER_amount_get_zero (currency,
     108             :                          &total_in);
     109           3 :   TALER_amount_get_zero (currency,
     110             :                          &total_out);
     111           3 :   uuid_off = 0;
     112          20 :   for (off=0;off<history_length;off++)
     113             :   {
     114             :     json_t *transaction;
     115             :     struct TALER_Amount amount;
     116             :     const char *type;
     117           7 :     struct GNUNET_JSON_Specification hist_spec[] = {
     118             :       GNUNET_JSON_spec_string ("type", &type),
     119             :       TALER_JSON_spec_amount ("amount",
     120             :                               &amount),
     121             :       /* 'wire' and 'signature' are optional depending on 'type'! */
     122             :       GNUNET_JSON_spec_end()
     123             :     };
     124             : 
     125           7 :     transaction = json_array_get (history,
     126             :                                   off);
     127           7 :     if (GNUNET_OK !=
     128           7 :         GNUNET_JSON_parse (transaction,
     129             :                            hist_spec,
     130             :                            NULL, NULL))
     131             :     {
     132           0 :       GNUNET_break_op (0);
     133           0 :       return GNUNET_SYSERR;
     134             :     }
     135           7 :     rhistory[off].amount = amount;
     136             : 
     137           7 :     if (0 == strcasecmp (type,
     138             :                          "DEPOSIT"))
     139             :     {
     140             :       json_t *wire_account;
     141             :       void *wire_reference;
     142             :       size_t wire_reference_size;
     143             : 
     144           3 :       struct GNUNET_JSON_Specification withdraw_spec[] = {
     145             :         GNUNET_JSON_spec_varsize ("wire_reference",
     146             :                                   &wire_reference,
     147             :                                   &wire_reference_size),
     148             :         GNUNET_JSON_spec_json ("sender_account_details",
     149             :                                &wire_account),
     150             :         GNUNET_JSON_spec_end()
     151             :       };
     152             : 
     153           3 :       rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT;
     154           3 :       if (GNUNET_OK !=
     155           3 :           TALER_amount_add (&total_in,
     156             :                             &total_in,
     157             :                             &amount))
     158             :       {
     159             :         /* overflow in history already!? inconceivable! Bad exchange! */
     160           0 :         GNUNET_break_op (0);
     161           0 :         return GNUNET_SYSERR;
     162             :       }
     163           3 :       if (GNUNET_OK !=
     164           3 :           GNUNET_JSON_parse (transaction,
     165             :                              withdraw_spec,
     166             :                              NULL, NULL))
     167             :       {
     168           0 :         GNUNET_break_op (0);
     169           0 :         return GNUNET_SYSERR;
     170             :       }
     171           3 :       rhistory[off].details.in_details.sender_account_details = wire_account;
     172           3 :       rhistory[off].details.in_details.wire_reference = wire_reference;
     173           3 :       rhistory[off].details.in_details.wire_reference_size = wire_reference_size;
     174             :       /* end type==DEPOSIT */
     175             :     }
     176           4 :     else if (0 == strcasecmp (type,
     177             :                               "WITHDRAW"))
     178             :     {
     179             :       struct TALER_ReserveSignatureP sig;
     180             :       struct TALER_WithdrawRequestPS withdraw_purpose;
     181           3 :       struct GNUNET_JSON_Specification withdraw_spec[] = {
     182             :         GNUNET_JSON_spec_fixed_auto ("reserve_sig",
     183             :                                      &sig),
     184             :         TALER_JSON_spec_amount_nbo ("withdraw_fee",
     185             :                                     &withdraw_purpose.withdraw_fee),
     186             :         GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
     187             :                                      &withdraw_purpose.h_denomination_pub),
     188             :         GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
     189             :                                      &withdraw_purpose.h_coin_envelope),
     190             :         GNUNET_JSON_spec_end()
     191             :       };
     192             :       unsigned int i;
     193             : 
     194           3 :       rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL;
     195           3 :       if (GNUNET_OK !=
     196           3 :           GNUNET_JSON_parse (transaction,
     197             :                              withdraw_spec,
     198             :                              NULL, NULL))
     199             :       {
     200           0 :         GNUNET_break_op (0);
     201           0 :         return GNUNET_SYSERR;
     202             :       }
     203             :       withdraw_purpose.purpose.size
     204           3 :         = htonl (sizeof (withdraw_purpose));
     205             :       withdraw_purpose.purpose.purpose
     206           3 :         = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
     207           3 :       withdraw_purpose.reserve_pub = *reserve_pub;
     208           3 :       TALER_amount_hton (&withdraw_purpose.amount_with_fee,
     209             :                          &amount);
     210             :       /* Check that the signature is a valid withdraw request */
     211           3 :       if (GNUNET_OK !=
     212           3 :           GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
     213             :                                       &withdraw_purpose.purpose,
     214             :                                       &sig.eddsa_signature,
     215             :                                       &reserve_pub->eddsa_pub))
     216             :       {
     217           0 :         GNUNET_break_op (0);
     218           0 :         GNUNET_JSON_parse_free (withdraw_spec);
     219           0 :         return GNUNET_SYSERR;
     220             :       }
     221             :       /* TODO: check that withdraw fee matches expectations! */
     222           3 :       rhistory[off].details.out_authorization_sig
     223           3 :         = json_object_get (transaction,
     224             :                            "signature");
     225             :       /* Check check that the same withdraw transaction
     226             :          isn't listed twice by the exchange. We use the
     227             :          "uuid" array to remember the hashes of all
     228             :          purposes, and compare the hashes to find
     229             :          duplicates. */
     230           6 :       GNUNET_CRYPTO_hash (&withdraw_purpose,
     231           3 :                           ntohl (withdraw_purpose.purpose.size),
     232             :                           &uuid[uuid_off]);
     233           3 :       for (i=0;i<uuid_off;i++)
     234             :       {
     235           0 :         if (0 == memcmp (&uuid[uuid_off],
     236           0 :                          &uuid[i],
     237             :                          sizeof (struct GNUNET_HashCode)))
     238             :         {
     239           0 :           GNUNET_break_op (0);
     240           0 :           GNUNET_JSON_parse_free (withdraw_spec);
     241           0 :           return GNUNET_SYSERR;
     242             :         }
     243             :       }
     244           3 :       uuid_off++;
     245             : 
     246           3 :       if (GNUNET_OK !=
     247           3 :           TALER_amount_add (&total_out,
     248             :                             &total_out,
     249             :                             &amount))
     250             :       {
     251             :         /* overflow in history already!? inconceivable! Bad exchange! */
     252           0 :         GNUNET_break_op (0);
     253           0 :         GNUNET_JSON_parse_free (withdraw_spec);
     254           0 :         return GNUNET_SYSERR;
     255             :       }
     256             :       /* end type==WITHDRAW */
     257             :     }
     258           1 :     else if (0 == strcasecmp (type,
     259             :                               "PAYBACK"))
     260             :     {
     261             :       struct TALER_PaybackConfirmationPS pc;
     262             :       struct GNUNET_TIME_Absolute timestamp;
     263             :       const struct TALER_EXCHANGE_Keys *key_state;
     264           3 :       struct GNUNET_JSON_Specification payback_spec[] = {
     265             :         GNUNET_JSON_spec_fixed_auto ("coin_pub",
     266             :                                      &pc.coin_pub),
     267           1 :         GNUNET_JSON_spec_fixed_auto ("exchange_sig",
     268             :                                      &rhistory[off].details.payback_details.exchange_sig),
     269           1 :         GNUNET_JSON_spec_fixed_auto ("exchange_pub",
     270             :                                      &rhistory[off].details.payback_details.exchange_pub),
     271             :         GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
     272             :                                             &pc.timestamp),
     273             :         GNUNET_JSON_spec_end()
     274             :       };
     275             : 
     276           1 :       rhistory[off].type = TALER_EXCHANGE_RTT_PAYBACK;
     277           1 :       rhistory[off].amount = amount;
     278           1 :       if (GNUNET_OK !=
     279           1 :           GNUNET_JSON_parse (transaction,
     280             :                              payback_spec,
     281             :                              NULL, NULL))
     282             :       {
     283           0 :         GNUNET_break_op (0);
     284           0 :         return GNUNET_SYSERR;
     285             :       }
     286           1 :       rhistory[off].details.payback_details.coin_pub = pc.coin_pub;
     287           1 :       TALER_amount_hton (&pc.payback_amount,
     288             :                          &amount);
     289           1 :       pc.purpose.size = htonl (sizeof (pc));
     290           1 :       pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK);
     291           1 :       pc.reserve_pub = *reserve_pub;
     292           1 :       timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp);
     293           1 :       rhistory[off].details.payback_details.timestamp = timestamp;
     294             : 
     295           1 :       key_state = TALER_EXCHANGE_get_keys (exchange);
     296           1 :       if (GNUNET_OK !=
     297           1 :           TALER_EXCHANGE_test_signing_key (key_state,
     298           1 :                                            &rhistory[off].details.payback_details.exchange_pub))
     299             :       {
     300           0 :         GNUNET_break_op (0);
     301           0 :         return GNUNET_SYSERR;
     302             :       }
     303           1 :       if (GNUNET_OK !=
     304           1 :           GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK,
     305             :                                       &pc.purpose,
     306           1 :                                       &rhistory[off].details.payback_details.exchange_sig.eddsa_signature,
     307           1 :                                       &rhistory[off].details.payback_details.exchange_pub.eddsa_pub))
     308             :       {
     309           0 :         GNUNET_break_op (0);
     310           0 :         return GNUNET_SYSERR;
     311             :       }
     312           1 :       if (GNUNET_OK !=
     313           1 :           TALER_amount_add (&total_in,
     314             :                             &total_in,
     315           1 :                             &rhistory[off].amount))
     316             :       {
     317             :         /* overflow in history already!? inconceivable! Bad exchange! */
     318           0 :         GNUNET_break_op (0);
     319           0 :         return GNUNET_SYSERR;
     320             :       }
     321             :       /* end type==PAYBACK */
     322             :     }
     323           0 :     else if (0 == strcasecmp (type,
     324             :                               "CLOSING"))
     325             :     {
     326             :       const struct TALER_EXCHANGE_Keys *key_state;
     327             :       struct TALER_ReserveCloseConfirmationPS rcc;
     328             :       struct GNUNET_TIME_Absolute timestamp;
     329           0 :       struct GNUNET_JSON_Specification closing_spec[] = {
     330           0 :         GNUNET_JSON_spec_json ("receiver_account_details",
     331           0 :                                &rhistory[off].details.close_details.receiver_account_details),
     332           0 :         GNUNET_JSON_spec_fixed_auto ("wtid",
     333             :                                      &rhistory[off].details.close_details.wtid),
     334           0 :         GNUNET_JSON_spec_fixed_auto ("exchange_sig",
     335             :                                      &rhistory[off].details.close_details.exchange_sig),
     336           0 :         GNUNET_JSON_spec_fixed_auto ("exchange_pub",
     337             :                                      &rhistory[off].details.close_details.exchange_pub),
     338             :         TALER_JSON_spec_amount_nbo ("closing_fee",
     339             :                                     &rcc.closing_fee),
     340             :         GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
     341             :                                             &rcc.timestamp),
     342             :         GNUNET_JSON_spec_end()
     343             :       };
     344             : 
     345           0 :       rhistory[off].type = TALER_EXCHANGE_RTT_CLOSE;
     346           0 :       rhistory[off].amount = amount;
     347           0 :       if (GNUNET_OK !=
     348           0 :           GNUNET_JSON_parse (transaction,
     349             :                              closing_spec,
     350             :                              NULL, NULL))
     351             :       {
     352           0 :         GNUNET_break_op (0);
     353           0 :         return GNUNET_SYSERR;
     354             :       }
     355           0 :       TALER_amount_hton (&rcc.closing_amount,
     356             :                          &amount);
     357           0 :       TALER_JSON_hash (rhistory[off].details.close_details.receiver_account_details,
     358             :                        &rcc.h_wire);
     359           0 :       rcc.wtid = rhistory[off].details.close_details.wtid;
     360           0 :       rcc.purpose.size = htonl (sizeof (rcc));
     361           0 :       rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED);
     362           0 :       rcc.reserve_pub = *reserve_pub;
     363           0 :       timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp);
     364           0 :       rhistory[off].details.close_details.timestamp = timestamp;
     365             : 
     366           0 :       key_state = TALER_EXCHANGE_get_keys (exchange);
     367           0 :       if (GNUNET_OK !=
     368           0 :           TALER_EXCHANGE_test_signing_key (key_state,
     369           0 :                                            &rhistory[off].details.payback_details.exchange_pub))
     370             :       {
     371           0 :         GNUNET_break_op (0);
     372           0 :         return GNUNET_SYSERR;
     373             :       }
     374           0 :       if (GNUNET_OK !=
     375           0 :           GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED,
     376             :                                       &rcc.purpose,
     377           0 :                                       &rhistory[off].details.payback_details.exchange_sig.eddsa_signature,
     378           0 :                                       &rhistory[off].details.payback_details.exchange_pub.eddsa_pub))
     379             :       {
     380           0 :         GNUNET_break_op (0);
     381           0 :         return GNUNET_SYSERR;
     382             :       }
     383           0 :       if (GNUNET_OK !=
     384           0 :           TALER_amount_add (&total_out,
     385             :                             &total_out,
     386           0 :                             &rhistory[off].amount))
     387             :       {
     388             :         /* overflow in history already!? inconceivable! Bad exchange! */
     389           0 :         GNUNET_break_op (0);
     390           0 :         return GNUNET_SYSERR;
     391             :       }
     392             :       /* end type==CLOSING */
     393             :     }
     394             :     else
     395             :     {
     396             :       /* unexpected 'type', protocol incompatibility, complain! */
     397           0 :       GNUNET_break_op (0);
     398           0 :       return GNUNET_SYSERR;
     399             :     }
     400             :   }
     401             : 
     402             :   /* check balance = total_in - total_out < withdraw-amount */
     403           3 :   if (GNUNET_SYSERR ==
     404           3 :       TALER_amount_subtract (balance,
     405             :                              &total_in,
     406             :                              &total_out))
     407             :   {
     408             :     /* total_in < total_out, why did the exchange ever allow this!? */
     409           0 :     GNUNET_break_op (0);
     410           0 :     return GNUNET_SYSERR;
     411             :   }
     412           3 :   return GNUNET_OK;
     413             : }
     414             : 
     415             : 
     416             : /**
     417             :  * Free memory (potentially) allocated by #parse_reserve_history().
     418             :  *
     419             :  * @param rhistory result to free
     420             :  * @param len number of entries in @a rhistory
     421             :  */
     422             : static void
     423           3 : free_rhistory (struct TALER_EXCHANGE_ReserveHistory *rhistory,
     424             :                unsigned int len)
     425             : {
     426          10 :   for (unsigned int i=0;i<len;i++)
     427             :   {
     428           7 :     switch (rhistory[i].type)
     429             :     {
     430             :     case TALER_EXCHANGE_RTT_DEPOSIT:
     431           3 :       GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference);
     432           3 :       if (NULL != rhistory[i].details.in_details.sender_account_details)
     433           3 :         json_decref (rhistory[i].details.in_details.sender_account_details);
     434           3 :       break;
     435             :     case TALER_EXCHANGE_RTT_WITHDRAWAL:
     436           3 :       break;
     437             :     case TALER_EXCHANGE_RTT_PAYBACK:
     438           1 :       break;
     439             :     case TALER_EXCHANGE_RTT_CLOSE:
     440           0 :       if (NULL != rhistory[i].details.close_details.receiver_account_details)
     441           0 :         json_decref (rhistory[i].details.close_details.receiver_account_details);
     442           0 :       break;
     443             :     }
     444             :   }
     445           3 : }
     446             : 
     447             : 
     448             : /**
     449             :  * Function called when we're done processing the
     450             :  * HTTP /reserve/status request.
     451             :  *
     452             :  * @param cls the `struct TALER_EXCHANGE_ReserveStatusHandle`
     453             :  * @param response_code HTTP response code, 0 on error
     454             :  * @param json parsed JSON result, NULL on error
     455             :  */
     456             : static void
     457           2 : handle_reserve_status_finished (void *cls,
     458             :                                 long response_code,
     459             :                                 const json_t *json)
     460             : {
     461           2 :   struct TALER_EXCHANGE_ReserveStatusHandle *rsh = cls;
     462             : 
     463           2 :   rsh->job = NULL;
     464           2 :   switch (response_code)
     465             :   {
     466             :   case 0:
     467           0 :     break;
     468             :   case MHD_HTTP_OK:
     469             :     {
     470             :       /* TODO: move into separate function... */
     471             :       json_t *history;
     472             :       unsigned int len;
     473             :       struct TALER_Amount balance;
     474             :       struct TALER_Amount balance_from_history;
     475           2 :       struct GNUNET_JSON_Specification spec[] = {
     476             :         TALER_JSON_spec_amount ("balance", &balance),
     477             :         GNUNET_JSON_spec_end()
     478             :       };
     479             : 
     480           2 :       if (GNUNET_OK !=
     481           2 :           GNUNET_JSON_parse (json,
     482             :                              spec,
     483             :                              NULL,
     484             :                              NULL))
     485             :       {
     486           0 :         GNUNET_break_op (0);
     487           0 :         response_code = 0;
     488           0 :         break;
     489             :       }
     490           2 :       history = json_object_get (json,
     491             :                                  "history");
     492           2 :       if (NULL == history)
     493             :       {
     494           0 :         GNUNET_break_op (0);
     495           0 :         response_code = 0;
     496           0 :         break;
     497             :       }
     498           2 :       len = json_array_size (history);
     499           2 :       {
     500           2 :         struct TALER_EXCHANGE_ReserveHistory rhistory[len];
     501             : 
     502           2 :         memset (rhistory, 0, sizeof (rhistory));
     503           2 :         if (GNUNET_OK !=
     504           4 :             parse_reserve_history (rsh->exchange,
     505             :                                    history,
     506           2 :                                    &rsh->reserve_pub,
     507             :                                    balance.currency,
     508             :                                    &balance_from_history,
     509             :                                    len,
     510             :                                    rhistory))
     511             :         {
     512           0 :           GNUNET_break_op (0);
     513           0 :           response_code = 0;
     514             :         }
     515           4 :         if ( (0 != response_code) &&
     516             :              (0 !=
     517           2 :               TALER_amount_cmp (&balance_from_history,
     518             :                                 &balance)) )
     519             :         {
     520             :           /* exchange cannot add up balances!? */
     521           0 :           GNUNET_break_op (0);
     522           0 :           response_code = 0;
     523             :         }
     524           2 :         if (0 != response_code)
     525             :         {
     526           2 :           rsh->cb (rsh->cb_cls,
     527             :                    response_code,
     528             :                    TALER_EC_NONE,
     529             :                    json,
     530             :                    &balance,
     531             :                    len,
     532             :                    rhistory);
     533           2 :           rsh->cb = NULL;
     534             :         }
     535           2 :         free_rhistory (rhistory,
     536             :                        len);
     537             :       }
     538             :     }
     539           2 :     break;
     540             :   case MHD_HTTP_BAD_REQUEST:
     541             :     /* This should never happen, either us or the exchange is buggy
     542             :        (or API version conflict); just pass JSON reply to the application */
     543           0 :     break;
     544             :   case MHD_HTTP_NOT_FOUND:
     545             :     /* Nothing really to verify, this should never
     546             :        happen, we should pass the JSON reply to the application */
     547           0 :     break;
     548             :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     549             :     /* Server had an internal issue; we should retry, but this API
     550             :        leaves this to the application */
     551           0 :     break;
     552             :   default:
     553             :     /* unexpected response code */
     554           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     555             :                 "Unexpected response code %u\n",
     556             :                 (unsigned int) response_code);
     557           0 :     GNUNET_break (0);
     558           0 :     response_code = 0;
     559           0 :     break;
     560             :   }
     561           2 :   if (NULL != rsh->cb)
     562           0 :     rsh->cb (rsh->cb_cls,
     563             :              response_code,
     564             :              TALER_JSON_get_error_code (json),
     565             :              json,
     566             :              NULL,
     567             :              0, NULL);
     568           2 :   TALER_EXCHANGE_reserve_status_cancel (rsh);
     569           2 : }
     570             : 
     571             : 
     572             : /**
     573             :  * Submit a request to obtain the transaction history of a reserve
     574             :  * from the exchange.  Note that while we return the full response to the
     575             :  * caller for further processing, we do already verify that the
     576             :  * response is well-formed (i.e. that signatures included in the
     577             :  * response are all valid and add up to the balance).  If the exchange's
     578             :  * reply is not well-formed, we return an HTTP status code of zero to
     579             :  * @a cb.
     580             :  *
     581             :  * @param exchange the exchange handle; the exchange must be ready to operate
     582             :  * @param reserve_pub public key of the reserve to inspect
     583             :  * @param cb the callback to call when a reply for this request is available
     584             :  * @param cb_cls closure for the above callback
     585             :  * @return a handle for this request; NULL if the inputs are invalid (i.e.
     586             :  *         signatures fail to verify).  In this case, the callback is not called.
     587             :  */
     588             : struct TALER_EXCHANGE_ReserveStatusHandle *
     589           2 : TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange,
     590             :                                const struct TALER_ReservePublicKeyP *reserve_pub,
     591             :                                TALER_EXCHANGE_ReserveStatusResultCallback cb,
     592             :                                void *cb_cls)
     593             : {
     594             :   struct TALER_EXCHANGE_ReserveStatusHandle *rsh;
     595             :   struct GNUNET_CURL_Context *ctx;
     596             :   CURL *eh;
     597             :   char *pub_str;
     598             :   char *arg_str;
     599             : 
     600           2 :   if (GNUNET_YES !=
     601           2 :       MAH_handle_is_ready (exchange))
     602             :   {
     603           0 :     GNUNET_break (0);
     604           0 :     return NULL;
     605             :   }
     606           2 :   pub_str = GNUNET_STRINGS_data_to_string_alloc (reserve_pub,
     607             :                                                  sizeof (struct TALER_ReservePublicKeyP));
     608           2 :   GNUNET_asprintf (&arg_str,
     609             :                    "/reserve/status?reserve_pub=%s",
     610             :                    pub_str);
     611           2 :   GNUNET_free (pub_str);
     612           2 :   rsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle);
     613           2 :   rsh->exchange = exchange;
     614           2 :   rsh->cb = cb;
     615           2 :   rsh->cb_cls = cb_cls;
     616           2 :   rsh->reserve_pub = *reserve_pub;
     617           2 :   rsh->url = MAH_path_to_url (exchange,
     618             :                               arg_str);
     619           2 :   GNUNET_free (arg_str);
     620             : 
     621           2 :   eh = curl_easy_init ();
     622           2 :   GNUNET_assert (CURLE_OK ==
     623             :                  curl_easy_setopt (eh,
     624             :                                    CURLOPT_URL,
     625             :                                    rsh->url));
     626           2 :   ctx = MAH_handle_to_context (exchange);
     627           2 :   rsh->job = GNUNET_CURL_job_add (ctx,
     628             :                           eh,
     629             :                           GNUNET_NO,
     630             :                           &handle_reserve_status_finished,
     631             :                           rsh);
     632           2 :   return rsh;
     633             : }
     634             : 
     635             : 
     636             : /**
     637             :  * Cancel a reserve status request.  This function cannot be used
     638             :  * on a request handle if a response is already served for it.
     639             :  *
     640             :  * @param rsh the reserve status request handle
     641             :  */
     642             : void
     643           2 : TALER_EXCHANGE_reserve_status_cancel (struct TALER_EXCHANGE_ReserveStatusHandle *rsh)
     644             : {
     645           2 :   if (NULL != rsh->job)
     646             :   {
     647           0 :     GNUNET_CURL_job_cancel (rsh->job);
     648           0 :     rsh->job = NULL;
     649             :   }
     650           2 :   GNUNET_free (rsh->url);
     651           2 :   GNUNET_free (rsh);
     652           2 : }
     653             : 
     654             : 
     655             : /* ********************** /reserve/withdraw ********************** */
     656             : 
     657             : /**
     658             :  * @brief A Withdraw Sign Handle
     659             :  */
     660             : struct TALER_EXCHANGE_ReserveWithdrawHandle
     661             : {
     662             : 
     663             :   /**
     664             :    * The connection to exchange this request handle will use
     665             :    */
     666             :   struct TALER_EXCHANGE_Handle *exchange;
     667             : 
     668             :   /**
     669             :    * The url for this request.
     670             :    */
     671             :   char *url;
     672             : 
     673             :   /**
     674             :    * JSON encoding of the request to POST.
     675             :    */
     676             :   char *json_enc;
     677             : 
     678             :   /**
     679             :    * Handle for the request.
     680             :    */
     681             :   struct GNUNET_CURL_Job *job;
     682             : 
     683             :   /**
     684             :    * Function to call with the result.
     685             :    */
     686             :   TALER_EXCHANGE_ReserveWithdrawResultCallback cb;
     687             : 
     688             :   /**
     689             :    * Key used to blind the value.
     690             :    */
     691             :   struct TALER_DenominationBlindingKeyP blinding_key;
     692             : 
     693             :   /**
     694             :    * Denomination key we are withdrawing.
     695             :    */
     696             :   const struct TALER_EXCHANGE_DenomPublicKey *pk;
     697             : 
     698             :   /**
     699             :    * Closure for @a cb.
     700             :    */
     701             :   void *cb_cls;
     702             : 
     703             :   /**
     704             :    * Hash of the public key of the coin we are signing.
     705             :    */
     706             :   struct GNUNET_HashCode c_hash;
     707             : 
     708             :   /**
     709             :    * Public key of the reserve we are withdrawing from.
     710             :    */
     711             :   struct TALER_ReservePublicKeyP reserve_pub;
     712             : 
     713             : };
     714             : 
     715             : 
     716             : /**
     717             :  * We got a 200 OK response for the /reserve/withdraw operation.
     718             :  * Extract the coin's signature and return it to the caller.
     719             :  * The signature we get from the exchange is for the blinded value.
     720             :  * Thus, we first must unblind it and then should verify its
     721             :  * validity against our coin's hash.
     722             :  *
     723             :  * If everything checks out, we return the unblinded signature
     724             :  * to the application via the callback.
     725             :  *
     726             :  * @param wsh operation handle
     727             :  * @param json reply from the exchange
     728             :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
     729             :  */
     730             : static int
     731           6 : reserve_withdraw_ok (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh,
     732             :                      const json_t *json)
     733             : {
     734             :   struct GNUNET_CRYPTO_RsaSignature *blind_sig;
     735             :   struct GNUNET_CRYPTO_RsaSignature *sig;
     736             :   struct TALER_DenominationSignature dsig;
     737           6 :   struct GNUNET_JSON_Specification spec[] = {
     738             :     GNUNET_JSON_spec_rsa_signature ("ev_sig", &blind_sig),
     739             :     GNUNET_JSON_spec_end()
     740             :   };
     741             : 
     742           6 :   if (GNUNET_OK !=
     743           6 :       GNUNET_JSON_parse (json,
     744             :                          spec,
     745             :                          NULL, NULL))
     746             :   {
     747           0 :     GNUNET_break_op (0);
     748           0 :     return GNUNET_SYSERR;
     749             :   }
     750          12 :   sig = GNUNET_CRYPTO_rsa_unblind (blind_sig,
     751           6 :                                    &wsh->blinding_key.bks,
     752           6 :                                    wsh->pk->key.rsa_public_key);
     753           6 :   GNUNET_CRYPTO_rsa_signature_free (blind_sig);
     754           6 :   if (GNUNET_OK !=
     755           6 :       GNUNET_CRYPTO_rsa_verify (&wsh->c_hash,
     756             :                                 sig,
     757           6 :                                 wsh->pk->key.rsa_public_key))
     758             :   {
     759           0 :     GNUNET_break_op (0);
     760           0 :     GNUNET_CRYPTO_rsa_signature_free (sig);
     761           0 :     return GNUNET_SYSERR;
     762             :   }
     763             :   /* signature is valid, return it to the application */
     764           6 :   dsig.rsa_signature = sig;
     765           6 :   wsh->cb (wsh->cb_cls,
     766             :            MHD_HTTP_OK,
     767             :            TALER_EC_NONE,
     768             :            &dsig,
     769             :            json);
     770             :   /* make sure callback isn't called again after return */
     771           6 :   wsh->cb = NULL;
     772           6 :   GNUNET_CRYPTO_rsa_signature_free (sig);
     773           6 :   return GNUNET_OK;
     774             : }
     775             : 
     776             : 
     777             : /**
     778             :  * We got a 403 FORBIDDEN response for the /reserve/withdraw operation.
     779             :  * Check the signatures on the withdraw transactions in the provided
     780             :  * history and that the balances add up.  We don't do anything directly
     781             :  * with the information, as the JSON will be returned to the application.
     782             :  * However, our job is ensuring that the exchange followed the protocol, and
     783             :  * this in particular means checking all of the signatures in the history.
     784             :  *
     785             :  * @param wsh operation handle
     786             :  * @param json reply from the exchange
     787             :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
     788             :  */
     789             : static int
     790           1 : reserve_withdraw_payment_required (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh,
     791             :                                    const json_t *json)
     792             : {
     793             :   struct TALER_Amount balance;
     794             :   struct TALER_Amount balance_from_history;
     795             :   struct TALER_Amount requested_amount;
     796             :   json_t *history;
     797             :   size_t len;
     798           1 :   struct GNUNET_JSON_Specification spec[] = {
     799             :     TALER_JSON_spec_amount ("balance", &balance),
     800             :     GNUNET_JSON_spec_end()
     801             :   };
     802             : 
     803           1 :   if (GNUNET_OK !=
     804           1 :       GNUNET_JSON_parse (json,
     805             :                          spec,
     806             :                          NULL, NULL))
     807             :   {
     808           0 :     GNUNET_break_op (0);
     809           0 :     return GNUNET_SYSERR;
     810             :   }
     811           1 :   history = json_object_get (json,
     812             :                              "history");
     813           1 :   if (NULL == history)
     814             :   {
     815           0 :     GNUNET_break_op (0);
     816           0 :     return GNUNET_SYSERR;
     817             :   }
     818             : 
     819             :   /* go over transaction history and compute
     820             :      total incoming and outgoing amounts */
     821           1 :   len = json_array_size (history);
     822           1 :   {
     823           1 :     struct TALER_EXCHANGE_ReserveHistory rhistory[len];
     824             : 
     825           1 :     if (GNUNET_OK !=
     826           2 :         parse_reserve_history (wsh->exchange,
     827             :                                history,
     828           1 :                                &wsh->reserve_pub,
     829             :                                balance.currency,
     830             :                                &balance_from_history,
     831             :                                len,
     832             :                                rhistory))
     833             :     {
     834           0 :       GNUNET_break_op (0);
     835           0 :       free_rhistory (rhistory,
     836             :                      len);
     837           0 :       return GNUNET_SYSERR;
     838             :     }
     839           1 :     free_rhistory (rhistory,
     840             :                    len);
     841             :   }
     842             : 
     843           1 :   if (0 !=
     844           1 :       TALER_amount_cmp (&balance_from_history,
     845             :                         &balance))
     846             :   {
     847             :     /* exchange cannot add up balances!? */
     848           0 :     GNUNET_break_op (0);
     849           0 :     return GNUNET_SYSERR;
     850             :   }
     851             :   /* Compute how much we expected to charge to the reserve */
     852           1 :   if (GNUNET_OK !=
     853           2 :       TALER_amount_add (&requested_amount,
     854           1 :                         &wsh->pk->value,
     855           1 :                         &wsh->pk->fee_withdraw))
     856             :   {
     857             :     /* Overflow here? Very strange, our CPU must be fried... */
     858           0 :     GNUNET_break (0);
     859           0 :     return GNUNET_SYSERR;
     860             :   }
     861             :   /* Check that funds were really insufficient */
     862           1 :   if (0 >= TALER_amount_cmp (&requested_amount,
     863             :                              &balance))
     864             :   {
     865             :     /* Requested amount is smaller or equal to reported balance,
     866             :        so this should not have failed. */
     867           0 :     GNUNET_break_op (0);
     868           0 :     return GNUNET_SYSERR;
     869             :   }
     870           1 :   return GNUNET_OK;
     871             : }
     872             : 
     873             : 
     874             : /**
     875             :  * Function called when we're done processing the
     876             :  * HTTP /reserve/withdraw request.
     877             :  *
     878             :  * @param cls the `struct TALER_EXCHANGE_ReserveWithdrawHandle`
     879             :  * @param response_code HTTP response code, 0 on error
     880             :  * @param json parsed JSON result, NULL on error
     881             :  */
     882             : static void
     883           8 : handle_reserve_withdraw_finished (void *cls,
     884             :                                   long response_code,
     885             :                                   const json_t *json)
     886             : {
     887           8 :   struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh = cls;
     888             : 
     889           8 :   wsh->job = NULL;
     890           8 :   switch (response_code)
     891             :   {
     892             :   case 0:
     893           0 :     break;
     894             :   case MHD_HTTP_OK:
     895           6 :     if (GNUNET_OK !=
     896           6 :         reserve_withdraw_ok (wsh,
     897             :                              json))
     898             :     {
     899           0 :       GNUNET_break_op (0);
     900           0 :       response_code = 0;
     901             :     }
     902           6 :     break;
     903             :   case MHD_HTTP_BAD_REQUEST:
     904             :     /* This should never happen, either us or the exchange is buggy
     905             :        (or API version conflict); just pass JSON reply to the application */
     906           0 :     break;
     907             :   case MHD_HTTP_FORBIDDEN:
     908             :     /* The exchange says that the reserve has insufficient funds;
     909             :        check the signatures in the history... */
     910           1 :     if (GNUNET_OK !=
     911           1 :         reserve_withdraw_payment_required (wsh,
     912             :                                         json))
     913             :     {
     914           0 :       GNUNET_break_op (0);
     915           0 :       response_code = 0;
     916             :     }
     917           1 :     break;
     918             :   case MHD_HTTP_UNAUTHORIZED:
     919           0 :     GNUNET_break (0);
     920             :     /* Nothing really to verify, exchange says one of the signatures is
     921             :        invalid; as we checked them, this should never happen, we
     922             :        should pass the JSON reply to the application */
     923           0 :     break;
     924             :   case MHD_HTTP_NOT_FOUND:
     925             :     /* Nothing really to verify, the exchange basically just says
     926             :        that it doesn't know this reserve.  Can happen if we
     927             :        query before the wire transfer went through.
     928             :        We should simply pass the JSON reply to the application. */
     929           1 :     break;
     930             :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     931             :     /* Server had an internal issue; we should retry, but this API
     932             :        leaves this to the application */
     933           0 :     break;
     934             :   default:
     935             :     /* unexpected response code */
     936           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     937             :                 "Unexpected response code %u\n",
     938             :                 (unsigned int) response_code);
     939           0 :     GNUNET_break (0);
     940           0 :     response_code = 0;
     941           0 :     break;
     942             :   }
     943           8 :   if (NULL != wsh->cb)
     944           2 :     wsh->cb (wsh->cb_cls,
     945             :              response_code,
     946             :              TALER_JSON_get_error_code (json),
     947             :              NULL,
     948             :              json);
     949           8 :   TALER_EXCHANGE_reserve_withdraw_cancel (wsh);
     950           8 : }
     951             : 
     952             : 
     953             : /**
     954             :  * Withdraw a coin from the exchange using a /reserve/withdraw request.  Note
     955             :  * that to ensure that no money is lost in case of hardware failures,
     956             :  * the caller must have committed (most of) the arguments to disk
     957             :  * before calling, and be ready to repeat the request with the same
     958             :  * arguments in case of failures.
     959             :  *
     960             :  * @param exchange the exchange handle; the exchange must be ready to operate
     961             :  * @param pk kind of coin to create
     962             :  * @param reserve_priv private key of the reserve to withdraw from
     963             :  * @param coin_priv where to fetch the coin's private key,
     964             :  *        caller must have committed this value to disk before the call (with @a pk)
     965             :  * @param blinding_key where to fetch the coin's blinding key
     966             :  *        caller must have committed this value to disk before the call (with @a pk)
     967             :  * @param res_cb the callback to call when the final result for this request is available
     968             :  * @param res_cb_cls closure for the above callback
     969             :  * @return handle for the operation on success, NULL on error, i.e.
     970             :  *         if the inputs are invalid (i.e. denomination key not with this exchange).
     971             :  *         In this case, the callback is not called.
     972             :  */
     973             : struct TALER_EXCHANGE_ReserveWithdrawHandle *
     974           8 : TALER_EXCHANGE_reserve_withdraw (struct TALER_EXCHANGE_Handle *exchange,
     975             :                                  const struct TALER_EXCHANGE_DenomPublicKey *pk,
     976             :                                  const struct TALER_ReservePrivateKeyP *reserve_priv,
     977             :                                  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
     978             :                                  const struct TALER_DenominationBlindingKeyP *blinding_key,
     979             :                                  TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb,
     980             :                                  void *res_cb_cls)
     981             : {
     982             :   struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh;
     983             :   struct TALER_WithdrawRequestPS req;
     984             :   struct TALER_ReserveSignatureP reserve_sig;
     985             :   struct TALER_CoinSpendPublicKeyP coin_pub;
     986             :   struct GNUNET_CURL_Context *ctx;
     987             :   struct TALER_Amount amount_with_fee;
     988             :   char *coin_ev;
     989             :   size_t coin_ev_size;
     990             :   json_t *withdraw_obj;
     991             :   CURL *eh;
     992             : 
     993           8 :   wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveWithdrawHandle);
     994           8 :   wsh->exchange = exchange;
     995           8 :   wsh->cb = res_cb;
     996           8 :   wsh->cb_cls = res_cb_cls;
     997           8 :   wsh->pk = pk;
     998             : 
     999           8 :   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
    1000             :                                       &coin_pub.eddsa_pub);
    1001           8 :   GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub,
    1002             :                       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
    1003             :                       &wsh->c_hash);
    1004           8 :   if (GNUNET_YES !=
    1005           8 :       GNUNET_CRYPTO_rsa_blind (&wsh->c_hash,
    1006             :                                &blinding_key->bks,
    1007             :                                pk->key.rsa_public_key,
    1008             :                                &coin_ev,
    1009             :                                &coin_ev_size))
    1010             :   {
    1011           0 :     GNUNET_break_op (0);
    1012           0 :     GNUNET_free (wsh);
    1013           0 :     return NULL;
    1014             :   }
    1015           8 :   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
    1016             :                                       &wsh->reserve_pub.eddsa_pub);
    1017           8 :   req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS));
    1018           8 :   req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
    1019           8 :   req.reserve_pub = wsh->reserve_pub;
    1020           8 :   if (GNUNET_OK !=
    1021           8 :       TALER_amount_add (&amount_with_fee,
    1022             :                         &pk->fee_withdraw,
    1023             :                         &pk->value))
    1024             :   {
    1025             :     /* exchange gave us denomination keys that overflow like this!? */
    1026           0 :     GNUNET_break_op (0);
    1027           0 :     GNUNET_free (coin_ev);
    1028           0 :     GNUNET_free (wsh);
    1029           0 :     return NULL;
    1030             :   }
    1031           8 :   TALER_amount_hton (&req.amount_with_fee,
    1032             :                      &amount_with_fee);
    1033           8 :   TALER_amount_hton (&req.withdraw_fee,
    1034             :                      &pk->fee_withdraw);
    1035           8 :   GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key,
    1036             :                                      &req.h_denomination_pub);
    1037           8 :   GNUNET_CRYPTO_hash (coin_ev,
    1038             :                       coin_ev_size,
    1039             :                       &req.h_coin_envelope);
    1040           8 :   GNUNET_assert (GNUNET_OK ==
    1041             :                  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
    1042             :                                            &req.purpose,
    1043             :                                            &reserve_sig.eddsa_signature));
    1044          24 :   withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub and coin_ev */
    1045             :                             " s:o, s:o}",/* reserve_pub and reserve_sig */
    1046           8 :                             "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key),
    1047             :                             "coin_ev", GNUNET_JSON_from_data (coin_ev,
    1048             :                                                               coin_ev_size),
    1049           8 :                             "reserve_pub", GNUNET_JSON_from_data_auto (&wsh->reserve_pub),
    1050             :                             "reserve_sig", GNUNET_JSON_from_data_auto (&reserve_sig));
    1051           8 :   GNUNET_free (coin_ev);
    1052           8 :   if (NULL == withdraw_obj)
    1053             :   {
    1054           0 :     GNUNET_break (0);
    1055           0 :     return NULL;
    1056             :   }
    1057             : 
    1058             : 
    1059           8 :   wsh->blinding_key = *blinding_key;
    1060           8 :   wsh->url = MAH_path_to_url (exchange, "/reserve/withdraw");
    1061             : 
    1062           8 :   eh = curl_easy_init ();
    1063           8 :   GNUNET_assert (NULL != (wsh->json_enc =
    1064             :                           json_dumps (withdraw_obj,
    1065             :                                       JSON_COMPACT)));
    1066           8 :   json_decref (withdraw_obj);
    1067           8 :   GNUNET_assert (CURLE_OK ==
    1068             :                  curl_easy_setopt (eh,
    1069             :                                    CURLOPT_URL,
    1070             :                                    wsh->url));
    1071           8 :   GNUNET_assert (CURLE_OK ==
    1072             :                  curl_easy_setopt (eh,
    1073             :                                    CURLOPT_POSTFIELDS,
    1074             :                                    wsh->json_enc));
    1075           8 :   GNUNET_assert (CURLE_OK ==
    1076             :                  curl_easy_setopt (eh,
    1077             :                                    CURLOPT_POSTFIELDSIZE,
    1078             :                                    strlen (wsh->json_enc)));
    1079           8 :   ctx = MAH_handle_to_context (exchange);
    1080           8 :   wsh->job = GNUNET_CURL_job_add (ctx,
    1081             :                           eh,
    1082             :                           GNUNET_YES,
    1083             :                           &handle_reserve_withdraw_finished,
    1084             :                           wsh);
    1085           8 :   return wsh;
    1086             : }
    1087             : 
    1088             : 
    1089             : /**
    1090             :  * Cancel a withdraw status request.  This function cannot be used
    1091             :  * on a request handle if a response is already served for it.
    1092             :  *
    1093             :  * @param sign the withdraw sign request handle
    1094             :  */
    1095             : void
    1096           8 : TALER_EXCHANGE_reserve_withdraw_cancel (struct TALER_EXCHANGE_ReserveWithdrawHandle *sign)
    1097             : {
    1098           8 :   if (NULL != sign->job)
    1099             :   {
    1100           0 :     GNUNET_CURL_job_cancel (sign->job);
    1101           0 :     sign->job = NULL;
    1102             :   }
    1103           8 :   GNUNET_free (sign->url);
    1104           8 :   GNUNET_free (sign->json_enc);
    1105           8 :   GNUNET_free (sign);
    1106           8 : }
    1107             : 
    1108             : 
    1109             : /* end of exchange_api_reserve.c */

Generated by: LCOV version 1.13