LCOV - code coverage report
Current view: top level - bank-lib - bank_api_credit.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 57.2 % 159 91
Test Date: 2026-09-09 15:11:34 Functions: 100.0 % 4 4

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2017--2024 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or
       6              :   modify it under the terms of the GNU General Public License
       7              :   as published by the Free Software Foundation; either version 3,
       8              :   or (at your option) any later version.
       9              : 
      10              :   TALER is distributed in the hope that it will be useful,
      11              :   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12              :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13              :   GNU General Public License for more details.
      14              : 
      15              :   You should have received a copy of the GNU General Public
      16              :   License along with TALER; see the file COPYING.  If not,
      17              :   see <http://www.gnu.org/licenses/>
      18              : */
      19              : /**
      20              :  * @file bank-lib/bank_api_credit.c
      21              :  * @brief Implementation of the /history/incoming
      22              :  *        requests of the bank's HTTP API.
      23              :  * @author Christian Grothoff
      24              :  * @author Marcello Stanisci
      25              :  */
      26              : #include "bank_api_common.h"
      27              : #include <microhttpd.h> /* just for HTTP status codes */
      28              : #include "taler/taler_signatures.h"
      29              : 
      30              : 
      31              : /**
      32              :  * How much longer than the application-specified timeout
      33              :  * do we wait (giving the server a chance to respond)?
      34              :  */
      35              : #define GRACE_PERIOD_MS 1000
      36              : 
      37              : 
      38              : /**
      39              :  * @brief A /history/incoming Handle
      40              :  */
      41              : struct TALER_BANK_CreditHistoryHandle
      42              : {
      43              : 
      44              :   /**
      45              :    * The url for this request.
      46              :    */
      47              :   char *request_url;
      48              : 
      49              :   /**
      50              :    * Handle for the request.
      51              :    */
      52              :   struct GNUNET_CURL_Job *job;
      53              : 
      54              :   /**
      55              :    * Function to call with the result.
      56              :    */
      57              :   TALER_BANK_CreditHistoryCallback hcb;
      58              : 
      59              :   /**
      60              :    * Closure for @a cb.
      61              :    */
      62              :   void *hcb_cls;
      63              : };
      64              : 
      65              : 
      66              : /**
      67              :  * Parse history given in JSON format and invoke the callback on each item.
      68              :  *
      69              :  * @param hh handle to the account history request
      70              :  * @param history JSON array with the history
      71              :  * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
      72              :  *         were set,
      73              :  *         #GNUNET_SYSERR if there was a protocol violation in @a history
      74              :  */
      75              : static enum GNUNET_GenericReturnValue
      76          108 : parse_account_history (struct TALER_BANK_CreditHistoryHandle *hh,
      77              :                        const json_t *history)
      78              : {
      79          108 :   struct TALER_BANK_CreditHistoryResponse chr = {
      80              :     .http_status = MHD_HTTP_OK,
      81              :     .ec = TALER_EC_NONE,
      82              :     .response = history
      83              :   };
      84              :   const json_t *history_array;
      85              :   struct GNUNET_JSON_Specification spec[] = {
      86          108 :     GNUNET_JSON_spec_array_const ("incoming_transactions",
      87              :                                   &history_array),
      88          108 :     TALER_JSON_spec_full_payto_uri ("credit_account",
      89              :                                     &chr.details.ok.credit_account_uri),
      90          108 :     GNUNET_JSON_spec_end ()
      91              :   };
      92              : 
      93          108 :   if (GNUNET_OK !=
      94          108 :       GNUNET_JSON_parse (history,
      95              :                          spec,
      96              :                          NULL,
      97              :                          NULL))
      98              :   {
      99            0 :     GNUNET_break_op (0);
     100            0 :     return GNUNET_SYSERR;
     101              :   }
     102              :   {
     103          108 :     size_t len = json_array_size (history_array);
     104              :     struct TALER_BANK_CreditDetails *cd;
     105              : 
     106          108 :     GNUNET_break_op (0 != len);
     107          108 :     cd = GNUNET_new_array (GNUNET_NZL (len),
     108              :                            struct TALER_BANK_CreditDetails);
     109          216 :     for (size_t i = 0; i<len; i++)
     110              :     {
     111          108 :       struct TALER_BANK_CreditDetails *td = &cd[i];
     112              :       const char *type;
     113              :       bool no_credit_fee;
     114              :       struct GNUNET_JSON_Specification hist_spec[] = {
     115          108 :         GNUNET_JSON_spec_string ("type",
     116              :                                  &type),
     117          108 :         TALER_JSON_spec_amount_any ("amount",
     118              :                                     &td->amount),
     119          108 :         GNUNET_JSON_spec_mark_optional (
     120              :           TALER_JSON_spec_amount_any ("credit_fee",
     121              :                                       &td->credit_fee),
     122              :           &no_credit_fee),
     123          108 :         GNUNET_JSON_spec_timestamp ("date",
     124              :                                     &td->execution_date),
     125          108 :         GNUNET_JSON_spec_uint64 ("row_id",
     126              :                                  &td->serial_id),
     127          108 :         TALER_JSON_spec_full_payto_uri ("debit_account",
     128              :                                         &td->debit_account_uri),
     129          108 :         GNUNET_JSON_spec_end ()
     130              :       };
     131          108 :       json_t *transaction = json_array_get (history_array,
     132              :                                             i);
     133              : 
     134          108 :       if (GNUNET_OK !=
     135          108 :           GNUNET_JSON_parse (transaction,
     136              :                              hist_spec,
     137              :                              NULL,
     138              :                              NULL))
     139              :       {
     140            0 :         GNUNET_break_op (0);
     141            0 :         goto fail;
     142              :       }
     143          108 :       if (no_credit_fee)
     144              :       {
     145          108 :         GNUNET_assert (GNUNET_OK ==
     146              :                        TALER_amount_set_zero (td->amount.currency,
     147              :                                               &td->credit_fee));
     148              :       }
     149              :       else
     150              :       {
     151            0 :         if (GNUNET_YES !=
     152            0 :             TALER_amount_cmp_currency (&td->amount,
     153            0 :                                        &td->credit_fee))
     154              :         {
     155            0 :           GNUNET_break_op (0);
     156            0 :           goto fail;
     157              :         }
     158              :       }
     159          108 :       if (0 == strcasecmp ("RESERVE",
     160              :                            type))
     161              :       {
     162              :         struct GNUNET_JSON_Specification reserve_spec[] = {
     163           92 :           GNUNET_JSON_spec_fixed_auto ("reserve_pub",
     164              :                                        &td->details.reserve.reserve_pub),
     165           92 :           GNUNET_JSON_spec_end ()
     166              :         };
     167              : 
     168           92 :         if (GNUNET_OK !=
     169           92 :             GNUNET_JSON_parse (transaction,
     170              :                                reserve_spec,
     171              :                                NULL,
     172              :                                NULL))
     173              :         {
     174            0 :           GNUNET_break_op (0);
     175            0 :           goto fail;
     176              :         }
     177           92 :         td->type = TALER_BANK_CT_RESERVE;
     178              :       }
     179           16 :       else if (0 == strcasecmp ("KYCAUTH",
     180              :                                 type))
     181              :       {
     182              :         struct GNUNET_JSON_Specification kycauth_spec[] = {
     183           16 :           GNUNET_JSON_spec_fixed_auto ("account_pub",
     184              :                                        &td->details.kycauth.account_pub),
     185           16 :           GNUNET_JSON_spec_end ()
     186              :         };
     187              : 
     188           16 :         if (GNUNET_OK !=
     189           16 :             GNUNET_JSON_parse (transaction,
     190              :                                kycauth_spec,
     191              :                                NULL,
     192              :                                NULL))
     193              :         {
     194            0 :           GNUNET_break_op (0);
     195            0 :           goto fail;
     196              :         }
     197           16 :         td->type = TALER_BANK_CT_KYCAUTH;
     198              :       }
     199            0 :       else if (0 == strcasecmp ("WAD",
     200              :                                 type))
     201              :       {
     202              :         struct GNUNET_JSON_Specification wad_spec[] = {
     203            0 :           TALER_JSON_spec_web_url ("origin_exchange_url",
     204              :                                    &td->details.wad.origin_exchange_url),
     205            0 :           GNUNET_JSON_spec_fixed_auto ("wad_id",
     206              :                                        &td->details.wad.wad_id),
     207            0 :           GNUNET_JSON_spec_end ()
     208              :         };
     209              : 
     210            0 :         if (GNUNET_OK !=
     211            0 :             GNUNET_JSON_parse (transaction,
     212              :                                wad_spec,
     213              :                                NULL,
     214              :                                NULL))
     215              :         {
     216            0 :           GNUNET_break_op (0);
     217            0 :           goto fail;
     218              :         }
     219            0 :         td->type = TALER_BANK_CT_WAD;
     220              :       }
     221              :       else
     222              :       {
     223            0 :         GNUNET_break_op (0);
     224            0 :         goto fail;
     225              :       }
     226              :     }
     227          108 :     chr.details.ok.details_length = len;
     228          108 :     chr.details.ok.details = cd;
     229          108 :     hh->hcb (hh->hcb_cls,
     230              :              &chr);
     231          108 :     GNUNET_free (cd);
     232          108 :     return GNUNET_OK;
     233            0 : fail:
     234            0 :     GNUNET_free (cd);
     235            0 :     return GNUNET_SYSERR;
     236              :   }
     237              : }
     238              : 
     239              : 
     240              : /**
     241              :  * Function called when we're done processing the
     242              :  * HTTP /history/incoming request.
     243              :  *
     244              :  * @param cls the `struct TALER_BANK_CreditHistoryHandle`
     245              :  * @param response_code HTTP response code, 0 on error
     246              :  * @param response parsed JSON result, NULL on error
     247              :  */
     248              : static void
     249          172 : handle_credit_history_finished (void *cls,
     250              :                                 long response_code,
     251              :                                 const void *response)
     252              : {
     253          172 :   struct TALER_BANK_CreditHistoryHandle *hh = cls;
     254          172 :   struct TALER_BANK_CreditHistoryResponse chr = {
     255              :     .http_status = response_code,
     256              :     .response = response
     257              :   };
     258              : 
     259          172 :   hh->job = NULL;
     260          172 :   switch (response_code)
     261              :   {
     262            0 :   case 0:
     263            0 :     chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     264            0 :     break;
     265          108 :   case MHD_HTTP_OK:
     266          108 :     if (GNUNET_OK !=
     267          108 :         parse_account_history (hh,
     268              :                                chr.response))
     269              :     {
     270            0 :       GNUNET_break_op (0);
     271            0 :       json_dumpf (chr.response,
     272              :                   stderr,
     273              :                   JSON_INDENT (2));
     274            0 :       chr.http_status = 0;
     275            0 :       chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     276            0 :       break;
     277              :     }
     278          108 :     TALER_BANK_credit_history_cancel (hh);
     279          108 :     return;
     280           62 :   case MHD_HTTP_NO_CONTENT:
     281           62 :     break;
     282            0 :   case MHD_HTTP_BAD_REQUEST:
     283              :     /* This should never happen, either us or the bank is buggy
     284              :        (or API version conflict); just pass JSON reply to the application */
     285            0 :     GNUNET_break_op (0);
     286            0 :     chr.ec = TALER_JSON_get_error_code (chr.response);
     287            0 :     break;
     288            0 :   case MHD_HTTP_UNAUTHORIZED:
     289              :     /* Nothing really to verify, bank says the HTTP Authentication
     290              :        failed. May happen if HTTP authentication is used and the
     291              :        user supplied a wrong username/password combination. */
     292            0 :     chr.ec = TALER_JSON_get_error_code (chr.response);
     293            0 :     break;
     294            2 :   case MHD_HTTP_NOT_FOUND:
     295              :     /* Nothing really to verify: the bank is either unaware
     296              :        of the endpoint (not a bank), or of the account.
     297              :        We should pass the JSON (?) reply to the application */
     298            2 :     chr.ec = TALER_JSON_get_error_code (chr.response);
     299            2 :     break;
     300            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     301              :     /* Server had an internal issue; we should retry, but this API
     302              :        leaves this to the application */
     303            0 :     chr.ec = TALER_JSON_get_error_code (chr.response);
     304            0 :     break;
     305            0 :   default:
     306              :     /* unexpected response code */
     307            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     308              :                 "Unexpected response code %u\n",
     309              :                 (unsigned int) response_code);
     310            0 :     chr.ec = TALER_JSON_get_error_code (chr.response);
     311            0 :     break;
     312              :   }
     313           64 :   hh->hcb (hh->hcb_cls,
     314              :            &chr);
     315           64 :   TALER_BANK_credit_history_cancel (hh);
     316              : }
     317              : 
     318              : 
     319              : struct TALER_BANK_CreditHistoryHandle *
     320          172 : TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
     321              :                            const struct TALER_BANK_AuthenticationData *auth,
     322              :                            uint64_t start_row,
     323              :                            int64_t num_results,
     324              :                            struct GNUNET_TIME_Relative timeout,
     325              :                            TALER_BANK_CreditHistoryCallback hres_cb,
     326              :                            void *hres_cb_cls)
     327              : {
     328              :   char url[128];
     329              :   struct TALER_BANK_CreditHistoryHandle *hh;
     330              :   CURL *eh;
     331              :   unsigned long long tms;
     332              : 
     333          172 :   if (0 == num_results)
     334              :   {
     335            0 :     GNUNET_break (0);
     336            0 :     return NULL;
     337              :   }
     338              : 
     339          344 :   tms = (unsigned long long) (timeout.rel_value_us
     340          172 :                               / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
     341          172 :   if ( ( (UINT64_MAX == start_row) &&
     342          171 :          (0 > num_results) ) ||
     343           15 :        ( (0 == start_row) &&
     344              :          (0 < num_results) ) )
     345              :   {
     346           16 :     if ( (0 < num_results) &&
     347           15 :          (! GNUNET_TIME_relative_is_zero (timeout)) )
     348              :       /* 0 == start_row is implied, go with timeout into future */
     349            0 :       GNUNET_snprintf (url,
     350              :                        sizeof (url),
     351              :                        "history/incoming?limit=%lld&timeout_ms=%llu",
     352              :                        (long long) num_results,
     353              :                        tms);
     354              :     else
     355              :       /* Going back from current transaction or have no timeout;
     356              :          hence timeout makes no sense */
     357           16 :       GNUNET_snprintf (url,
     358              :                        sizeof (url),
     359              :                        "history/incoming?limit=%lld",
     360              :                        (long long) num_results);
     361              :   }
     362              :   else
     363              :   {
     364          156 :     if ( (0 < num_results) &&
     365          156 :          (! GNUNET_TIME_relative_is_zero (timeout)) )
     366              :       /* going forward from num_result */
     367            0 :       GNUNET_snprintf (url,
     368              :                        sizeof (url),
     369              :                        "history/incoming?limit=%lld&offset=%llu&timeout_ms=%llu",
     370              :                        (long long) num_results,
     371              :                        (unsigned long long) start_row,
     372              :                        tms);
     373              :     else
     374              :       /* going backwards or have no timeout;
     375              :          hence timeout makes no sense */
     376          156 :       GNUNET_snprintf (url,
     377              :                        sizeof (url),
     378              :                        "history/incoming?limit=%lld&offset=%llu",
     379              :                        (long long) num_results,
     380              :                        (unsigned long long) start_row);
     381              :   }
     382          172 :   hh = GNUNET_new (struct TALER_BANK_CreditHistoryHandle);
     383          172 :   hh->hcb = hres_cb;
     384          172 :   hh->hcb_cls = hres_cb_cls;
     385          172 :   hh->request_url = TALER_url_join (auth->wire_gateway_url,
     386              :                                     url,
     387              :                                     NULL);
     388          172 :   if (NULL == hh->request_url)
     389              :   {
     390            0 :     GNUNET_free (hh);
     391            0 :     GNUNET_break (0);
     392            0 :     return NULL;
     393              :   }
     394          172 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     395              :               "Requesting credit history at `%s'\n",
     396              :               hh->request_url);
     397          172 :   eh = curl_easy_init ();
     398          344 :   if ( (NULL == eh) ||
     399              :        (GNUNET_OK !=
     400          172 :         TALER_BANK_setup_auth_ (eh,
     401          172 :                                 auth)) ||
     402              :        (CURLE_OK !=
     403          172 :         curl_easy_setopt (eh,
     404              :                           CURLOPT_URL,
     405              :                           hh->request_url)) )
     406              :   {
     407            0 :     GNUNET_break (0);
     408            0 :     TALER_BANK_credit_history_cancel (hh);
     409            0 :     if (NULL != eh)
     410            0 :       curl_easy_cleanup (eh);
     411            0 :     return NULL;
     412              :   }
     413          172 :   if (0 != tms)
     414              :   {
     415            0 :     GNUNET_break (CURLE_OK ==
     416              :                   curl_easy_setopt (eh,
     417              :                                     CURLOPT_TIMEOUT_MS,
     418              :                                     (long) tms + GRACE_PERIOD_MS));
     419              :   }
     420          172 :   hh->job = GNUNET_CURL_job_add2 (ctx,
     421              :                                   eh,
     422              :                                   NULL,
     423              :                                   &handle_credit_history_finished,
     424              :                                   hh);
     425          172 :   if (NULL == hh->job)
     426              :   {
     427            0 :     GNUNET_break (0);
     428            0 :     curl_easy_cleanup (eh);
     429            0 :     TALER_BANK_credit_history_cancel (hh);
     430            0 :     return NULL;
     431              :   }
     432          172 :   return hh;
     433              : }
     434              : 
     435              : 
     436              : void
     437          172 : TALER_BANK_credit_history_cancel (struct TALER_BANK_CreditHistoryHandle *hh)
     438              : {
     439          172 :   if (NULL != hh->job)
     440              :   {
     441            0 :     GNUNET_CURL_job_cancel (hh->job);
     442            0 :     hh->job = NULL;
     443              :   }
     444          172 :   GNUNET_free (hh->request_url);
     445          172 :   GNUNET_free (hh);
     446          172 : }
     447              : 
     448              : 
     449              : /* end of bank_api_credit.c */
        

Generated by: LCOV version 2.0-1