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

Generated by: LCOV version 2.0-1