LCOV - code coverage report
Current view: top level - lib - exchange_api_get-keys.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 67.4 % 190 128
Test Date: 2026-03-10 12:10:57 Functions: 100.0 % 7 7

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2014-2026 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see
      15              :   <http://www.gnu.org/licenses/>
      16              : */
      17              : /**
      18              :  * @file lib/exchange_api_get-keys.c
      19              :  * @brief Implementation of GET /keys
      20              :  * @author Sree Harsha Totakura <sreeharsha@totakura.in>
      21              :  * @author Christian Grothoff
      22              :  */
      23              : #include "taler/platform.h"
      24              : #include <microhttpd.h>
      25              : #include <gnunet/gnunet_curl_lib.h>
      26              : #include "taler/taler_json_lib.h"
      27              : #include "taler/taler_exchange_service.h"
      28              : #include "taler/taler_signatures.h"
      29              : #include "exchange_api_handle.h"
      30              : #include "exchange_api_curl_defaults.h"
      31              : #include "taler/taler_curl_lib.h"
      32              : 
      33              : /**
      34              :  * If the "Expire" cache control header is missing, for
      35              :  * how long do we assume the reply to be valid at least?
      36              :  */
      37              : #define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS
      38              : 
      39              : /**
      40              :  * If the "Expire" cache control header is missing, for
      41              :  * how long do we assume the reply to be valid at least?
      42              :  */
      43              : #define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \
      44              :           GNUNET_TIME_UNIT_MINUTES, 2)
      45              : 
      46              : /**
      47              :  * Define a max length for the HTTP "Expire:" header
      48              :  */
      49              : #define MAX_DATE_LINE_LEN 32
      50              : 
      51              : 
      52              : /**
      53              :  * Handle for a GET /keys request.
      54              :  */
      55              : struct TALER_EXCHANGE_GetKeysHandle
      56              : {
      57              : 
      58              :   /**
      59              :    * The exchange base URL (i.e. "https://exchange.demo.taler.net/")
      60              :    */
      61              :   char *exchange_url;
      62              : 
      63              :   /**
      64              :    * The url for the /keys request, set during _start.
      65              :    */
      66              :   char *url;
      67              : 
      68              :   /**
      69              :    * Previous /keys response, NULL for none.
      70              :    */
      71              :   struct TALER_EXCHANGE_Keys *prev_keys;
      72              : 
      73              :   /**
      74              :    * Entry for this request with the `struct GNUNET_CURL_Context`.
      75              :    */
      76              :   struct GNUNET_CURL_Job *job;
      77              : 
      78              :   /**
      79              :    * Expiration time according to "Expire:" header.
      80              :    * 0 if not provided by the server.
      81              :    */
      82              :   struct GNUNET_TIME_Timestamp expire;
      83              : 
      84              :   /**
      85              :    * Function to call with the exchange's certification data,
      86              :    * NULL if this has already been done.
      87              :    */
      88              :   TALER_EXCHANGE_GetKeysCallback cert_cb;
      89              : 
      90              :   /**
      91              :    * Closure to pass to @e cert_cb.
      92              :    */
      93              :   TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE *cert_cb_cls;
      94              : 
      95              :   /**
      96              :    * Reference to the execution context.
      97              :    */
      98              :   struct GNUNET_CURL_Context *ctx;
      99              : 
     100              : };
     101              : 
     102              : 
     103              : /**
     104              :  * Parse HTTP timestamp.
     105              :  *
     106              :  * @param dateline header to parse header
     107              :  * @param[out] at where to write the result
     108              :  * @return #GNUNET_OK on success
     109              :  */
     110              : static enum GNUNET_GenericReturnValue
     111           34 : parse_date_string (const char *dateline,
     112              :                    struct GNUNET_TIME_Timestamp *at)
     113              : {
     114              :   static const char *MONTHS[] =
     115              :   { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
     116              :     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
     117              :   int year;
     118              :   int mon;
     119              :   int day;
     120              :   int hour;
     121              :   int min;
     122              :   int sec;
     123              :   char month[4];
     124              :   struct tm tm;
     125              :   time_t t;
     126              : 
     127              :   /* We recognize the three formats in RFC2616, section 3.3.1.  Month
     128              :      names are always in English.  The formats are:
     129              :       Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
     130              :       Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
     131              :       Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
     132              :      Note that the first is preferred.
     133              :    */
     134              : 
     135           34 :   if (strlen (dateline) > MAX_DATE_LINE_LEN)
     136              :   {
     137            0 :     GNUNET_break_op (0);
     138            0 :     return GNUNET_SYSERR;
     139              :   }
     140           34 :   while (*dateline == ' ')
     141            0 :     ++dateline;
     142          170 :   while (*dateline && *dateline != ' ')
     143          136 :     ++dateline;
     144           68 :   while (*dateline == ' ')
     145           34 :     ++dateline;
     146              :   /* We just skipped over the day of the week. Now we have:*/
     147           34 :   if ( (sscanf (dateline,
     148              :                 "%d %3s %d %d:%d:%d",
     149            0 :                 &day, month, &year, &hour, &min, &sec) != 6) &&
     150            0 :        (sscanf (dateline,
     151              :                 "%d-%3s-%d %d:%d:%d",
     152            0 :                 &day, month, &year, &hour, &min, &sec) != 6) &&
     153            0 :        (sscanf (dateline,
     154              :                 "%3s %d %d:%d:%d %d",
     155              :                 month, &day, &hour, &min, &sec, &year) != 6) )
     156              :   {
     157            0 :     GNUNET_break (0);
     158            0 :     return GNUNET_SYSERR;
     159              :   }
     160              :   /* Two digit dates are defined to be relative to 1900; all other dates
     161              :    * are supposed to be represented as four digits. */
     162           34 :   if (year < 100)
     163            0 :     year += 1900;
     164              : 
     165          102 :   for (mon = 0; ; mon++)
     166              :   {
     167          102 :     if (! MONTHS[mon])
     168              :     {
     169            0 :       GNUNET_break_op (0);
     170            0 :       return GNUNET_SYSERR;
     171              :     }
     172          102 :     if (0 == strcasecmp (month,
     173              :                          MONTHS[mon]))
     174           34 :       break;
     175              :   }
     176              : 
     177           34 :   memset (&tm, 0, sizeof(tm));
     178           34 :   tm.tm_year = year - 1900;
     179           34 :   tm.tm_mon = mon;
     180           34 :   tm.tm_mday = day;
     181           34 :   tm.tm_hour = hour;
     182           34 :   tm.tm_min = min;
     183           34 :   tm.tm_sec = sec;
     184              : 
     185           34 :   t = mktime (&tm);
     186           34 :   if (((time_t) -1) == t)
     187              :   {
     188            0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     189              :                          "mktime");
     190            0 :     return GNUNET_SYSERR;
     191              :   }
     192           34 :   if (t < 0)
     193            0 :     t = 0; /* can happen due to timezone issues if date was 1.1.1970 */
     194           34 :   *at = GNUNET_TIME_timestamp_from_s (t);
     195           34 :   return GNUNET_OK;
     196              : }
     197              : 
     198              : 
     199              : /**
     200              :  * Function called for each header in the HTTP /keys response.
     201              :  * Finds the "Expire:" header and parses it, storing the result
     202              :  * in the "expire" field of the keys request.
     203              :  *
     204              :  * @param buffer header data received
     205              :  * @param size size of an item in @a buffer
     206              :  * @param nitems number of items in @a buffer
     207              :  * @param userdata the `struct TALER_EXCHANGE_GetKeysHandle`
     208              :  * @return `size * nitems` on success (everything else aborts)
     209              :  */
     210              : static size_t
     211          442 : header_cb (char *buffer,
     212              :            size_t size,
     213              :            size_t nitems,
     214              :            void *userdata)
     215              : {
     216          442 :   struct TALER_EXCHANGE_GetKeysHandle *kr = userdata;
     217          442 :   size_t total = size * nitems;
     218              :   char *val;
     219              : 
     220          442 :   if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": "))
     221           34 :     return total;
     222          408 :   if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ",
     223              :                         buffer,
     224              :                         strlen (MHD_HTTP_HEADER_EXPIRES ": ")))
     225          374 :     return total;
     226           34 :   val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
     227              :                         total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
     228           34 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     229              :               "Found %s header `%s'\n",
     230              :               MHD_HTTP_HEADER_EXPIRES,
     231              :               val);
     232           34 :   if (GNUNET_OK !=
     233           34 :       parse_date_string (val,
     234              :                          &kr->expire))
     235              :   {
     236            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     237              :                 "Failed to parse %s-header `%s'\n",
     238              :                 MHD_HTTP_HEADER_EXPIRES,
     239              :                 val);
     240            0 :     kr->expire = GNUNET_TIME_UNIT_ZERO_TS;
     241              :   }
     242           34 :   GNUNET_free (val);
     243           34 :   return total;
     244              : }
     245              : 
     246              : 
     247              : /**
     248              :  * Callback used when downloading the reply to a /keys request
     249              :  * is complete.
     250              :  *
     251              :  * @param cls the `struct TALER_EXCHANGE_GetKeysHandle`
     252              :  * @param response_code HTTP response code, 0 on error
     253              :  * @param resp_obj parsed JSON result, NULL on error
     254              :  */
     255              : static void
     256           34 : keys_completed_cb (void *cls,
     257              :                    long response_code,
     258              :                    const void *resp_obj)
     259              : {
     260           34 :   struct TALER_EXCHANGE_GetKeysHandle *gkh = cls;
     261           34 :   const json_t *j = resp_obj;
     262           34 :   struct TALER_EXCHANGE_Keys *kd = NULL;
     263           34 :   struct TALER_EXCHANGE_KeysResponse kresp = {
     264              :     .hr.reply = j,
     265           34 :     .hr.http_status = (unsigned int) response_code,
     266              :     .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR,
     267              :   };
     268              : 
     269           34 :   gkh->job = NULL;
     270           34 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     271              :               "Received keys from URL `%s' with status %ld and expiration %s.\n",
     272              :               gkh->url,
     273              :               response_code,
     274              :               GNUNET_TIME_timestamp2s (gkh->expire));
     275           34 :   if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time))
     276              :   {
     277            0 :     if (MHD_HTTP_OK == response_code)
     278            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     279              :                   "Exchange failed to give expiration time, assuming in %s\n",
     280              :                   GNUNET_TIME_relative2s (DEFAULT_EXPIRATION,
     281              :                                           true));
     282              :     gkh->expire
     283            0 :       = GNUNET_TIME_absolute_to_timestamp (
     284              :           GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
     285              :   }
     286           34 :   switch (response_code)
     287              :   {
     288            0 :   case 0:
     289            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     290              :                 "Failed to receive /keys response from exchange %s\n",
     291              :                 gkh->exchange_url);
     292            0 :     break;
     293           34 :   case MHD_HTTP_OK:
     294           34 :     if (NULL == j)
     295              :     {
     296            0 :       GNUNET_break (0);
     297            0 :       response_code = 0;
     298            0 :       break;
     299              :     }
     300           34 :     kd = GNUNET_new (struct TALER_EXCHANGE_Keys);
     301           34 :     kd->exchange_url = GNUNET_strdup (gkh->exchange_url);
     302           34 :     if (NULL != gkh->prev_keys)
     303              :     {
     304            8 :       const struct TALER_EXCHANGE_Keys *kd_old = gkh->prev_keys;
     305              : 
     306              :       /* We keep the denomination keys and auditor signatures from the
     307              :          previous iteration (/keys cherry picking) */
     308              :       kd->num_denom_keys
     309            8 :         = kd_old->num_denom_keys;
     310              :       kd->last_denom_issue_date
     311            8 :         = kd_old->last_denom_issue_date;
     312            8 :       GNUNET_array_grow (kd->denom_keys,
     313              :                          kd->denom_keys_size,
     314              :                          kd->num_denom_keys);
     315              :       /* First make a shallow copy, we then need another pass for the RSA key... */
     316            8 :       GNUNET_memcpy (kd->denom_keys,
     317              :                      kd_old->denom_keys,
     318              :                      kd_old->num_denom_keys
     319              :                      * sizeof (struct TALER_EXCHANGE_DenomPublicKey));
     320          113 :       for (unsigned int i = 0; i<kd_old->num_denom_keys; i++)
     321          105 :         TALER_denom_pub_copy (&kd->denom_keys[i].key,
     322          105 :                               &kd_old->denom_keys[i].key);
     323            8 :       kd->num_auditors = kd_old->num_auditors;
     324              :       kd->auditors
     325            8 :         = GNUNET_new_array (kd->num_auditors,
     326              :                             struct TALER_EXCHANGE_AuditorInformation);
     327              :       /* Now the necessary deep copy... */
     328           10 :       for (unsigned int i = 0; i<kd_old->num_auditors; i++)
     329              :       {
     330            2 :         const struct TALER_EXCHANGE_AuditorInformation *aold =
     331            2 :           &kd_old->auditors[i];
     332            2 :         struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i];
     333              : 
     334            2 :         anew->auditor_pub = aold->auditor_pub;
     335            2 :         anew->auditor_url = GNUNET_strdup (aold->auditor_url);
     336            2 :         anew->auditor_name = GNUNET_strdup (aold->auditor_name);
     337            2 :         GNUNET_array_grow (anew->denom_keys,
     338              :                            anew->num_denom_keys,
     339              :                            aold->num_denom_keys);
     340            2 :         GNUNET_memcpy (
     341              :           anew->denom_keys,
     342              :           aold->denom_keys,
     343              :           aold->num_denom_keys
     344              :           * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
     345              :       }
     346              :     }
     347              :     /* Now decode fresh /keys response */
     348           34 :     if (GNUNET_OK !=
     349           34 :         TALER_EXCHANGE_decode_keys_json_ (j,
     350              :                                           true,
     351              :                                           kd,
     352              :                                           &kresp.details.ok.compat))
     353              :     {
     354            0 :       TALER_LOG_ERROR ("Could not decode /keys response\n");
     355            0 :       kd->rc = 1;
     356            0 :       TALER_EXCHANGE_keys_decref (kd);
     357            0 :       kd = NULL;
     358            0 :       kresp.hr.http_status = 0;
     359            0 :       kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     360            0 :       break;
     361              :     }
     362           34 :     kd->rc = 1;
     363           34 :     kd->key_data_expiration = gkh->expire;
     364           34 :     if (GNUNET_TIME_relative_cmp (
     365              :           GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time),
     366              :           <,
     367              :           MINIMUM_EXPIRATION))
     368              :     {
     369            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     370              :                   "Exchange returned keys with expiration time below %s. Compensating.\n",
     371              :                   GNUNET_TIME_relative2s (MINIMUM_EXPIRATION,
     372              :                                           true));
     373              :       kd->key_data_expiration
     374            0 :         = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION);
     375              :     }
     376              : 
     377           34 :     kresp.details.ok.keys = kd;
     378           34 :     break;
     379            0 :   case MHD_HTTP_BAD_REQUEST:
     380              :   case MHD_HTTP_UNAUTHORIZED:
     381              :   case MHD_HTTP_FORBIDDEN:
     382              :   case MHD_HTTP_NOT_FOUND:
     383            0 :     if (NULL == j)
     384              :     {
     385            0 :       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     386            0 :       kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
     387              :     }
     388              :     else
     389              :     {
     390            0 :       kresp.hr.ec = TALER_JSON_get_error_code (j);
     391            0 :       kresp.hr.hint = TALER_JSON_get_error_hint (j);
     392              :     }
     393            0 :     break;
     394            0 :   default:
     395            0 :     if (NULL == j)
     396              :     {
     397            0 :       kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     398            0 :       kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
     399              :     }
     400              :     else
     401              :     {
     402            0 :       kresp.hr.ec = TALER_JSON_get_error_code (j);
     403            0 :       kresp.hr.hint = TALER_JSON_get_error_hint (j);
     404              :     }
     405            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     406              :                 "Unexpected response code %u/%d\n",
     407              :                 (unsigned int) response_code,
     408              :                 (int) kresp.hr.ec);
     409            0 :     break;
     410              :   }
     411           34 :   gkh->cert_cb (gkh->cert_cb_cls,
     412              :                 &kresp,
     413              :                 kd);
     414           34 :   TALER_EXCHANGE_get_keys_cancel (gkh);
     415           34 : }
     416              : 
     417              : 
     418              : struct TALER_EXCHANGE_GetKeysHandle *
     419           34 : TALER_EXCHANGE_get_keys_create (
     420              :   struct GNUNET_CURL_Context *ctx,
     421              :   const char *url)
     422              : {
     423              :   struct TALER_EXCHANGE_GetKeysHandle *gkh;
     424              : 
     425           34 :   gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle);
     426           34 :   gkh->ctx = ctx;
     427           34 :   gkh->exchange_url = GNUNET_strdup (url);
     428           34 :   return gkh;
     429              : }
     430              : 
     431              : 
     432              : enum GNUNET_GenericReturnValue
     433            8 : TALER_EXCHANGE_get_keys_set_options_ (
     434              :   struct TALER_EXCHANGE_GetKeysHandle *gkh,
     435              :   unsigned int num_options,
     436              :   const struct TALER_EXCHANGE_GetKeysOptionValue *options)
     437              : {
     438           16 :   for (unsigned int i = 0; i < num_options; i++)
     439              :   {
     440           16 :     const struct TALER_EXCHANGE_GetKeysOptionValue *opt = &options[i];
     441              : 
     442           16 :     switch (opt->option)
     443              :     {
     444            8 :     case TALER_EXCHANGE_GET_KEYS_OPTION_END:
     445            8 :       return GNUNET_OK;
     446            8 :     case TALER_EXCHANGE_GET_KEYS_OPTION_LAST_KEYS:
     447            8 :       TALER_EXCHANGE_keys_decref (gkh->prev_keys);
     448            8 :       gkh->prev_keys = NULL;
     449            8 :       if (NULL != opt->details.last_keys)
     450              :         gkh->prev_keys
     451            8 :           = TALER_EXCHANGE_keys_incref (opt->details.last_keys);
     452            8 :       break;
     453              :     }
     454              :   }
     455            0 :   return GNUNET_OK;
     456              : }
     457              : 
     458              : 
     459              : enum TALER_ErrorCode
     460           34 : TALER_EXCHANGE_get_keys_start (
     461              :   struct TALER_EXCHANGE_GetKeysHandle *gkh,
     462              :   TALER_EXCHANGE_GetKeysCallback cert_cb,
     463              :   TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE *cert_cb_cls)
     464              : {
     465              :   CURL *eh;
     466           34 :   char last_date[80] = { 0 };
     467              : 
     468           34 :   gkh->cert_cb = cert_cb;
     469           34 :   gkh->cert_cb_cls = cert_cb_cls;
     470           34 :   if (NULL != gkh->prev_keys)
     471              :   {
     472            8 :     TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
     473              :                      GNUNET_TIME_timestamp2s (
     474              :                        gkh->prev_keys->last_denom_issue_date));
     475            8 :     GNUNET_snprintf (last_date,
     476              :                      sizeof (last_date),
     477              :                      "%llu",
     478              :                      (unsigned long long)
     479            8 :                      gkh->prev_keys->last_denom_issue_date.abs_time.abs_value_us
     480              :                      / 1000000LLU);
     481              :   }
     482           68 :   gkh->url = TALER_url_join (gkh->exchange_url,
     483              :                              "keys",
     484           34 :                              (NULL != gkh->prev_keys)
     485              :                              ? "last_issue_date"
     486              :                              : NULL,
     487           34 :                              (NULL != gkh->prev_keys)
     488              :                              ? last_date
     489              :                              : NULL,
     490              :                              NULL);
     491           34 :   if (NULL == gkh->url)
     492              :   {
     493            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     494              :                 "Could not construct request URL.\n");
     495            0 :     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
     496              :   }
     497           34 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     498              :               "Requesting keys with URL `%s'.\n",
     499              :               gkh->url);
     500           34 :   eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
     501           34 :   if (NULL == eh)
     502              :   {
     503            0 :     GNUNET_break (0);
     504            0 :     GNUNET_free (gkh->url);
     505            0 :     gkh->url = NULL;
     506            0 :     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     507              :   }
     508           34 :   GNUNET_break (CURLE_OK ==
     509              :                 curl_easy_setopt (eh,
     510              :                                   CURLOPT_VERBOSE,
     511              :                                   0));
     512           34 :   GNUNET_break (CURLE_OK ==
     513              :                 curl_easy_setopt (eh,
     514              :                                   CURLOPT_TIMEOUT,
     515              :                                   120 /* seconds */));
     516           34 :   GNUNET_assert (CURLE_OK ==
     517              :                  curl_easy_setopt (eh,
     518              :                                    CURLOPT_HEADERFUNCTION,
     519              :                                    &header_cb));
     520           34 :   GNUNET_assert (CURLE_OK ==
     521              :                  curl_easy_setopt (eh,
     522              :                                    CURLOPT_HEADERDATA,
     523              :                                    gkh));
     524           34 :   gkh->job = GNUNET_CURL_job_add_with_ct_json (gkh->ctx,
     525              :                                                eh,
     526              :                                                &keys_completed_cb,
     527              :                                                gkh);
     528           34 :   if (NULL == gkh->job)
     529              :   {
     530            0 :     GNUNET_free (gkh->url);
     531            0 :     gkh->url = NULL;
     532            0 :     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     533              :   }
     534           34 :   return TALER_EC_NONE;
     535              : }
     536              : 
     537              : 
     538              : void
     539           34 : TALER_EXCHANGE_get_keys_cancel (
     540              :   struct TALER_EXCHANGE_GetKeysHandle *gkh)
     541              : {
     542           34 :   if (NULL != gkh->job)
     543              :   {
     544            0 :     GNUNET_CURL_job_cancel (gkh->job);
     545            0 :     gkh->job = NULL;
     546              :   }
     547           34 :   TALER_EXCHANGE_keys_decref (gkh->prev_keys);
     548           34 :   GNUNET_free (gkh->exchange_url);
     549           34 :   GNUNET_free (gkh->url);
     550           34 :   GNUNET_free (gkh);
     551           34 : }
     552              : 
     553              : 
     554              : /* end of exchange_api_get-keys.c */
        

Generated by: LCOV version 2.0-1