LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_bank_admin_add_incoming.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 85 160 53.1 %
Date: 2021-08-30 06:43:37 Functions: 8 10 80.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2018-2020 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it
       6             :   under the terms of the GNU General Public License as published by
       7             :   the Free Software Foundation; either version 3, or (at your
       8             :   option) any later version.
       9             : 
      10             :   TALER is distributed in the hope that it will be useful, but
      11             :   WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13             :   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, see
      17             :   <http://www.gnu.org/licenses/>
      18             : */
      19             : /**
      20             :  * @file testing/testing_api_cmd_bank_admin_add_incoming.c
      21             :  * @brief implementation of a bank /admin/add-incoming command
      22             :  * @author Christian Grothoff
      23             :  * @author Marcello Stanisci
      24             :  */
      25             : #include "platform.h"
      26             : #include "backoff.h"
      27             : #include "taler_json_lib.h"
      28             : #include <gnunet/gnunet_curl_lib.h>
      29             : #include "taler_bank_service.h"
      30             : #include "taler_fakebank_lib.h"
      31             : #include "taler_signatures.h"
      32             : #include "taler_testing_lib.h"
      33             : 
      34             : /**
      35             :  * How long do we wait AT MOST when retrying?
      36             :  */
      37             : #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
      38             :     GNUNET_TIME_UNIT_MILLISECONDS, 100)
      39             : 
      40             : 
      41             : /**
      42             :  * How often do we retry before giving up?
      43             :  */
      44             : #define NUM_RETRIES 5
      45             : 
      46             : 
      47             : /**
      48             :  * State for a "fakebank transfer" CMD.
      49             :  */
      50             : struct AdminAddIncomingState
      51             : {
      52             : 
      53             :   /**
      54             :    * Label of any command that can trait-offer a reserve priv.
      55             :    */
      56             :   const char *reserve_reference;
      57             : 
      58             :   /**
      59             :    * Wire transfer amount.
      60             :    */
      61             :   struct TALER_Amount amount;
      62             : 
      63             :   /**
      64             :    * Base URL of the credited account.
      65             :    */
      66             :   const char *exchange_credit_url;
      67             : 
      68             :   /**
      69             :    * Money sender payto URL.
      70             :    */
      71             :   const char *payto_debit_account;
      72             : 
      73             :   /**
      74             :    * Username to use for authentication.
      75             :    */
      76             :   struct TALER_BANK_AuthenticationData auth;
      77             : 
      78             :   /**
      79             :    * Set (by the interpreter) to the reserve's private key
      80             :    * we used to make a wire transfer subject line with.
      81             :    */
      82             :   struct TALER_ReservePrivateKeyP reserve_priv;
      83             : 
      84             :   /**
      85             :    * Whether we know the private key or not.
      86             :    */
      87             :   bool reserve_priv_known;
      88             : 
      89             :   /**
      90             :    * Reserve public key matching @e reserve_priv.
      91             :    */
      92             :   struct TALER_ReservePublicKeyP reserve_pub;
      93             : 
      94             :   /**
      95             :    * Handle to the pending request at the fakebank.
      96             :    */
      97             :   struct TALER_BANK_AdminAddIncomingHandle *aih;
      98             : 
      99             :   /**
     100             :    * Interpreter state.
     101             :    */
     102             :   struct TALER_TESTING_Interpreter *is;
     103             : 
     104             :   /**
     105             :    * Reserve history entry that corresponds to this operation.
     106             :    * Will be of type #TALER_EXCHANGE_RTT_CREDIT.  Note that
     107             :    * the "sender_url" field is set to a 'const char *' and
     108             :    * MUST NOT be free()'ed.
     109             :    */
     110             :   struct TALER_EXCHANGE_ReserveHistory reserve_history;
     111             : 
     112             :   /**
     113             :    * Set to the wire transfer's unique ID.
     114             :    */
     115             :   uint64_t serial_id;
     116             : 
     117             :   /**
     118             :    * Timestamp of the transaction (as returned from the bank).
     119             :    */
     120             :   struct GNUNET_TIME_Absolute timestamp;
     121             : 
     122             :   /**
     123             :    * Merchant instance.  Sometimes used to get the tip reserve
     124             :    * private key by reading the appropriate config section.
     125             :    */
     126             :   const char *instance;
     127             : 
     128             :   /**
     129             :    * Configuration filename.  Used to get the tip reserve key
     130             :    * filename (used to obtain a public key to write in the
     131             :    * transfer subject).
     132             :    */
     133             :   const char *config_filename;
     134             : 
     135             :   /**
     136             :    * Task scheduled to try later.
     137             :    */
     138             :   struct GNUNET_SCHEDULER_Task *retry_task;
     139             : 
     140             :   /**
     141             :    * How long do we wait until we retry?
     142             :    */
     143             :   struct GNUNET_TIME_Relative backoff;
     144             : 
     145             :   /**
     146             :    * Was this command modified via
     147             :    * #TALER_TESTING_cmd_admin_add_incoming_with_retry to
     148             :    * enable retries? If so, how often should we still retry?
     149             :    */
     150             :   unsigned int do_retry;
     151             : 
     152             :   /**
     153             :    * Expected HTTP status code.
     154             :    */
     155             :   unsigned int expected_http_status;
     156             : };
     157             : 
     158             : 
     159             : /**
     160             :  * Run the "fakebank transfer" CMD.
     161             :  *
     162             :  * @param cls closure.
     163             :  * @param cmd CMD being run.
     164             :  * @param is interpreter state.
     165             :  */
     166             : static void
     167             : admin_add_incoming_run (void *cls,
     168             :                         const struct TALER_TESTING_Command *cmd,
     169             :                         struct TALER_TESTING_Interpreter *is);
     170             : 
     171             : 
     172             : /**
     173             :  * Task scheduled to re-try #admin_add_incoming_run.
     174             :  *
     175             :  * @param cls a `struct AdminAddIncomingState`
     176             :  */
     177             : static void
     178           0 : do_retry (void *cls)
     179             : {
     180           0 :   struct AdminAddIncomingState *fts = cls;
     181             : 
     182           0 :   fts->retry_task = NULL;
     183           0 :   fts->is->commands[fts->is->ip].last_req_time
     184           0 :     = GNUNET_TIME_absolute_get ();
     185           0 :   admin_add_incoming_run (fts,
     186             :                           NULL,
     187             :                           fts->is);
     188           0 : }
     189             : 
     190             : 
     191             : /**
     192             :  * This callback will process the fakebank response to the wire
     193             :  * transfer.  It just checks whether the HTTP response code is
     194             :  * acceptable.
     195             :  *
     196             :  * @param cls closure with the interpreter state
     197             :  * @param http_status HTTP response code, #MHD_HTTP_OK (200) for
     198             :  *        successful status request; 0 if the exchange's reply is
     199             :  *        bogus (fails to follow the protocol)
     200             :  * @param ec taler-specific error code, #TALER_EC_NONE on success
     201             :  * @param serial_id unique ID of the wire transfer
     202             :  * @param timestamp time stamp of the transaction made.
     203             :  * @param json raw response
     204             :  */
     205             : static void
     206          32 : confirmation_cb (void *cls,
     207             :                  unsigned int http_status,
     208             :                  enum TALER_ErrorCode ec,
     209             :                  uint64_t serial_id,
     210             :                  struct GNUNET_TIME_Absolute timestamp,
     211             :                  const json_t *json)
     212             : {
     213          32 :   struct AdminAddIncomingState *fts = cls;
     214          32 :   struct TALER_TESTING_Interpreter *is = fts->is;
     215             : 
     216             :   (void) json;
     217          32 :   fts->reserve_history.details.in_details.timestamp = timestamp;
     218          32 :   fts->reserve_history.details.in_details.wire_reference = serial_id;
     219          32 :   fts->aih = NULL;
     220          32 :   switch (http_status)
     221             :   {
     222          30 :   case MHD_HTTP_OK:
     223          30 :     if (fts->expected_http_status !=
     224             :         MHD_HTTP_OK)
     225             :     {
     226           0 :       GNUNET_break (0);
     227           0 :       TALER_TESTING_interpreter_fail (is);
     228           0 :       return;
     229             :     }
     230          30 :     fts->serial_id = serial_id;
     231          30 :     fts->timestamp = timestamp;
     232          30 :     TALER_TESTING_interpreter_next (is);
     233          30 :     return;
     234           0 :   case MHD_HTTP_UNAUTHORIZED:
     235           0 :     switch (fts->auth.method)
     236             :     {
     237           0 :     case TALER_BANK_AUTH_NONE:
     238           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     239             :                   "Authentication required, but none configure.\n");
     240           0 :       break;
     241           0 :     case TALER_BANK_AUTH_BASIC:
     242           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     243             :                   "Basic authentication (%s) failed.\n",
     244             :                   fts->auth.details.basic.username);
     245           0 :       break;
     246             :     }
     247           0 :     break;
     248           2 :   case MHD_HTTP_CONFLICT:
     249           2 :     if (fts->expected_http_status !=
     250             :         MHD_HTTP_CONFLICT)
     251             :     {
     252           0 :       GNUNET_break (0);
     253           0 :       TALER_TESTING_interpreter_fail (is);
     254           0 :       return;
     255             :     }
     256           2 :     TALER_TESTING_interpreter_next (is);
     257           2 :     return;
     258           0 :   default:
     259           0 :     if (0 != fts->do_retry)
     260             :     {
     261           0 :       fts->do_retry--;
     262           0 :       if ( (0 == http_status) ||
     263           0 :            (TALER_EC_GENERIC_DB_SOFT_FAILURE == ec) ||
     264             :            (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) )
     265             :       {
     266           0 :         GNUNET_log (
     267             :           GNUNET_ERROR_TYPE_INFO,
     268             :           "Retrying fakebank transfer failed with %u/%d\n",
     269             :           http_status,
     270             :           (int) ec);
     271             :         /* on DB conflicts, do not use backoff */
     272           0 :         if (TALER_EC_GENERIC_DB_SOFT_FAILURE == ec)
     273           0 :           fts->backoff = GNUNET_TIME_UNIT_ZERO;
     274             :         else
     275           0 :           fts->backoff = GNUNET_TIME_randomized_backoff (fts->backoff,
     276             :                                                          MAX_BACKOFF);
     277           0 :         fts->is->commands[fts->is->ip].num_tries++;
     278           0 :         fts->retry_task = GNUNET_SCHEDULER_add_delayed (
     279             :           fts->backoff,
     280             :           &do_retry,
     281             :           fts);
     282           0 :         return;
     283             :       }
     284             :     }
     285           0 :     break;
     286             :   }
     287           0 :   GNUNET_break (0);
     288           0 :   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     289             :               "Fakebank returned HTTP status %u/%d\n",
     290             :               http_status,
     291             :               (int) ec);
     292           0 :   TALER_TESTING_interpreter_fail (is);
     293             : }
     294             : 
     295             : 
     296             : /**
     297             :  * Run the "fakebank transfer" CMD.
     298             :  *
     299             :  * @param cls closure.
     300             :  * @param cmd CMD being run.
     301             :  * @param is interpreter state.
     302             :  */
     303             : static void
     304          32 : admin_add_incoming_run (void *cls,
     305             :                         const struct TALER_TESTING_Command *cmd,
     306             :                         struct TALER_TESTING_Interpreter *is)
     307             : {
     308          32 :   struct AdminAddIncomingState *fts = cls;
     309          32 :   bool have_public = false;
     310             : 
     311             :   (void) cmd;
     312             :   /* Use reserve public key as subject */
     313          32 :   if (NULL != fts->reserve_reference)
     314             :   {
     315             :     const struct TALER_TESTING_Command *ref;
     316             :     const struct TALER_ReservePrivateKeyP *reserve_priv;
     317             :     const struct TALER_ReservePublicKeyP *reserve_pub;
     318             : 
     319           2 :     ref = TALER_TESTING_interpreter_lookup_command
     320             :             (is, fts->reserve_reference);
     321           2 :     if (NULL == ref)
     322             :     {
     323           0 :       GNUNET_break (0);
     324           0 :       TALER_TESTING_interpreter_fail (is);
     325           0 :       return;
     326             :     }
     327           2 :     if (GNUNET_OK !=
     328           2 :         TALER_TESTING_get_trait_reserve_priv (ref,
     329             :                                               0,
     330             :                                               &reserve_priv))
     331             :     {
     332           0 :       if (GNUNET_OK != TALER_TESTING_get_trait_reserve_pub (ref,
     333             :                                                             0,
     334             :                                                             &reserve_pub))
     335             :       {
     336           0 :         GNUNET_break (0);
     337           0 :         TALER_TESTING_interpreter_fail (is);
     338           0 :         return;
     339             :       }
     340           0 :       have_public = true;
     341           0 :       fts->reserve_pub.eddsa_pub = reserve_pub->eddsa_pub;
     342           0 :       fts->reserve_priv_known = false;
     343             :     }
     344             :     else
     345             :     {
     346           2 :       fts->reserve_priv.eddsa_priv = reserve_priv->eddsa_priv;
     347           2 :       fts->reserve_priv_known = true;
     348             :     }
     349             :   }
     350             :   else
     351             :   {
     352             :     /* No referenced reserve, no instance to take priv
     353             :      * from, no explicit subject given: create new key! */
     354          30 :     GNUNET_CRYPTO_eddsa_key_create (&fts->reserve_priv.eddsa_priv);
     355          30 :     fts->reserve_priv_known = true;
     356             :   }
     357          32 :   if (! have_public)
     358          32 :     GNUNET_CRYPTO_eddsa_key_get_public (&fts->reserve_priv.eddsa_priv,
     359             :                                         &fts->reserve_pub.eddsa_pub);
     360          32 :   fts->reserve_history.type = TALER_EXCHANGE_RTT_CREDIT;
     361          32 :   fts->reserve_history.amount = fts->amount;
     362             :   fts->reserve_history.details.in_details.sender_url
     363          32 :     = (char *) fts->payto_debit_account; /* remember to NOT free this one... */
     364          32 :   fts->is = is;
     365             :   fts->aih
     366          32 :     = TALER_BANK_admin_add_incoming (
     367             :         TALER_TESTING_interpreter_get_context (is),
     368          32 :         &fts->auth,
     369          32 :         &fts->reserve_pub,
     370          32 :         &fts->amount,
     371             :         fts->payto_debit_account,
     372             :         &confirmation_cb,
     373             :         fts);
     374          32 :   if (NULL == fts->aih)
     375             :   {
     376           0 :     GNUNET_break (0);
     377           0 :     TALER_TESTING_interpreter_fail (is);
     378           0 :     return;
     379             :   }
     380             : }
     381             : 
     382             : 
     383             : /**
     384             :  * Free the state of a "/admin/add-incoming" CMD, and possibly
     385             :  * cancel a pending operation thereof.
     386             :  *
     387             :  * @param cls closure
     388             :  * @param cmd current CMD being cleaned up.
     389             :  */
     390             : static void
     391          32 : admin_add_incoming_cleanup (void *cls,
     392             :                             const struct TALER_TESTING_Command *cmd)
     393             : {
     394          32 :   struct AdminAddIncomingState *fts = cls;
     395             : 
     396          32 :   if (NULL != fts->aih)
     397             :   {
     398           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     399             :                 "Command %s did not complete\n",
     400             :                 cmd->label);
     401           0 :     TALER_BANK_admin_add_incoming_cancel (fts->aih);
     402           0 :     fts->aih = NULL;
     403             :   }
     404          32 :   if (NULL != fts->retry_task)
     405             :   {
     406           0 :     GNUNET_SCHEDULER_cancel (fts->retry_task);
     407           0 :     fts->retry_task = NULL;
     408             :   }
     409          32 :   GNUNET_free (fts);
     410          32 : }
     411             : 
     412             : 
     413             : /**
     414             :  * Offer internal data from a "/admin/add-incoming" CMD to other
     415             :  * commands.
     416             :  *
     417             :  * @param cls closure.
     418             :  * @param[out] ret result
     419             :  * @param trait name of the trait.
     420             :  * @param index index number of the object to offer.
     421             :  * @return #GNUNET_OK on success.
     422             :  */
     423             : static int
     424        1102 : admin_add_incoming_traits (void *cls,
     425             :                            const void **ret,
     426             :                            const char *trait,
     427             :                            unsigned int index)
     428             : {
     429        1102 :   struct AdminAddIncomingState *fts = cls;
     430             : 
     431        1102 :   if (MHD_HTTP_OK !=
     432        1102 :       fts->expected_http_status)
     433           6 :     return GNUNET_NO; /* requests that failed generate no history */
     434        1096 :   if (fts->reserve_priv_known)
     435             :   {
     436             :     struct TALER_TESTING_Trait traits[] = {
     437        1096 :       TALER_TESTING_make_trait_bank_row (&fts->serial_id),
     438        1096 :       TALER_TESTING_make_trait_payto (TALER_TESTING_PT_DEBIT,
     439             :                                       fts->payto_debit_account),
     440             :       /* Used as a marker, content does not matter */
     441        1096 :       TALER_TESTING_make_trait_payto (TALER_TESTING_PT_CREDIT,
     442             :                                       "payto://void/the-exchange"),
     443        1096 :       TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BANK_ACCOUNT_URL,
     444             :                                     fts->exchange_credit_url),
     445        1096 :       TALER_TESTING_make_trait_amount_obj (0, &fts->amount),
     446        1096 :       TALER_TESTING_make_trait_absolute_time (0, &fts->timestamp),
     447        1096 :       TALER_TESTING_make_trait_reserve_priv (0,
     448        1096 :                                              &fts->reserve_priv),
     449        1096 :       TALER_TESTING_make_trait_reserve_pub (0,
     450        1096 :                                             &fts->reserve_pub),
     451        1096 :       TALER_TESTING_make_trait_reserve_history (0,
     452        1096 :                                                 &fts->reserve_history),
     453        1096 :       TALER_TESTING_trait_end ()
     454             :     };
     455             : 
     456        1096 :     return TALER_TESTING_get_trait (traits,
     457             :                                     ret,
     458             :                                     trait,
     459             :                                     index);
     460             :   }
     461             :   else
     462             :   {
     463             :     struct TALER_TESTING_Trait traits[] = {
     464           0 :       TALER_TESTING_make_trait_bank_row (&fts->serial_id),
     465           0 :       TALER_TESTING_make_trait_payto (TALER_TESTING_PT_DEBIT,
     466             :                                       fts->payto_debit_account),
     467             :       /* Used as a marker, content does not matter */
     468           0 :       TALER_TESTING_make_trait_payto (TALER_TESTING_PT_CREDIT,
     469             :                                       "payto://void/the-exchange"),
     470           0 :       TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BANK_ACCOUNT_URL,
     471             :                                     fts->exchange_credit_url),
     472           0 :       TALER_TESTING_make_trait_amount_obj (0, &fts->amount),
     473           0 :       TALER_TESTING_make_trait_absolute_time (0, &fts->timestamp),
     474           0 :       TALER_TESTING_make_trait_reserve_pub (0,
     475           0 :                                             &fts->reserve_pub),
     476           0 :       TALER_TESTING_make_trait_reserve_history (0,
     477           0 :                                                 &fts->reserve_history),
     478           0 :       TALER_TESTING_trait_end ()
     479             :     };
     480             : 
     481           0 :     return TALER_TESTING_get_trait (traits,
     482             :                                     ret,
     483             :                                     trait,
     484             :                                     index);
     485             :   }
     486             : }
     487             : 
     488             : 
     489             : /**
     490             :  * Create internal state for "/admin/add-incoming" CMD.
     491             :  *
     492             :  * @param amount the amount to transfer.
     493             :  * @param payto_debit_account which account sends money
     494             :  * @param auth authentication data
     495             :  * @return the internal state
     496             :  */
     497             : static struct AdminAddIncomingState *
     498          32 : make_fts (const char *amount,
     499             :           const struct TALER_BANK_AuthenticationData *auth,
     500             :           const char *payto_debit_account)
     501             : {
     502             :   struct AdminAddIncomingState *fts;
     503             : 
     504          32 :   fts = GNUNET_new (struct AdminAddIncomingState);
     505          32 :   fts->exchange_credit_url = auth->wire_gateway_url;
     506          32 :   fts->payto_debit_account = payto_debit_account;
     507          32 :   fts->auth = *auth;
     508          32 :   fts->expected_http_status = MHD_HTTP_OK;
     509          32 :   if (GNUNET_OK !=
     510          32 :       TALER_string_to_amount (amount,
     511             :                               &fts->amount))
     512             :   {
     513           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     514             :                 "Failed to parse amount `%s'\n",
     515             :                 amount);
     516           0 :     GNUNET_assert (0);
     517             :   }
     518          32 :   return fts;
     519             : }
     520             : 
     521             : 
     522             : /**
     523             :  * Helper function to create admin/add-incoming command.
     524             :  *
     525             :  * @param label command label.
     526             :  * @param fts internal state to use
     527             :  * @return the command.
     528             :  */
     529             : static struct TALER_TESTING_Command
     530          32 : make_command (const char *label,
     531             :               struct AdminAddIncomingState *fts)
     532             : {
     533          32 :   struct TALER_TESTING_Command cmd = {
     534             :     .cls = fts,
     535             :     .label = label,
     536             :     .run = &admin_add_incoming_run,
     537             :     .cleanup = &admin_add_incoming_cleanup,
     538             :     .traits = &admin_add_incoming_traits
     539             :   };
     540             : 
     541          32 :   return cmd;
     542             : }
     543             : 
     544             : 
     545             : struct TALER_TESTING_Command
     546          30 : TALER_TESTING_cmd_admin_add_incoming (const char *label,
     547             :                                       const char *amount,
     548             :                                       const struct
     549             :                                       TALER_BANK_AuthenticationData *auth,
     550             :                                       const char *payto_debit_account)
     551             : {
     552          30 :   return make_command (label,
     553             :                        make_fts (amount,
     554             :                                  auth,
     555             :                                  payto_debit_account));
     556             : }
     557             : 
     558             : 
     559             : struct TALER_TESTING_Command
     560           2 : TALER_TESTING_cmd_admin_add_incoming_with_ref (
     561             :   const char *label,
     562             :   const char *amount,
     563             :   const struct TALER_BANK_AuthenticationData *auth,
     564             :   const char *payto_debit_account,
     565             :   const char *ref,
     566             :   unsigned int http_status)
     567             : {
     568             :   struct AdminAddIncomingState *fts;
     569             : 
     570           2 :   fts = make_fts (amount,
     571             :                   auth,
     572             :                   payto_debit_account);
     573           2 :   fts->reserve_reference = ref;
     574           2 :   fts->expected_http_status = http_status;
     575           2 :   return make_command (label,
     576             :                        fts);
     577             : }
     578             : 
     579             : 
     580             : /**
     581             :  * Modify a fakebank transfer command to enable retries when the
     582             :  * reserve is not yet full or we get other transient errors from the
     583             :  * fakebank.
     584             :  *
     585             :  * @param cmd a fakebank transfer command
     586             :  * @return the command with retries enabled
     587             :  */
     588             : struct TALER_TESTING_Command
     589           0 : TALER_TESTING_cmd_admin_add_incoming_retry (struct TALER_TESTING_Command cmd)
     590             : {
     591             :   struct AdminAddIncomingState *fts;
     592             : 
     593           0 :   GNUNET_assert (&admin_add_incoming_run == cmd.run);
     594           0 :   fts = cmd.cls;
     595           0 :   fts->do_retry = NUM_RETRIES;
     596           0 :   return cmd;
     597             : }
     598             : 
     599             : 
     600             : /* end of testing_api_cmd_bank_admin_add_incoming.c */

Generated by: LCOV version 1.14