/*
  This file is part of TALER
  Copyright (C) 2014-2026 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see
  <http://www.gnu.org/licenses/>
*/
/**
 * @file lib/exchange_api_get-keys.c
 * @brief Implementation of GET /keys
 * @author Sree Harsha Totakura <sreeharsha@totakura.in>
 * @author Christian Grothoff
 */
#include "platform.h"  /* UNNECESSARY? */
#include <microhttpd.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler/taler_json_lib.h"
#include "taler/taler_exchange_service.h"  /* UNNECESSARY? */
#include "taler/taler_signatures.h"
#include "exchange_api_handle.h"
#include "exchange_api_curl_defaults.h"
#include "taler/taler_curl_lib.h"

/**
 * If the "Expire" cache control header is missing, for
 * how long do we assume the reply to be valid at least?
 */
#define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS

/**
 * If the "Expire" cache control header is missing, for
 * how long do we assume the reply to be valid at least?
 */
#define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \
          GNUNET_TIME_UNIT_MINUTES, 2)

/**
 * Define a max length for the HTTP "Expire:" header
 */
#define MAX_DATE_LINE_LEN 32


/**
 * Handle for a GET /keys request.
 */
struct TALER_EXCHANGE_GetKeysHandle
{

  /**
   * The exchange base URL (i.e. "https://exchange.demo.taler.net/")
   */
  char *exchange_url;

  /**
   * The url for the /keys request, set during _start.
   */
  char *url;

  /**
   * Previous /keys response, NULL for none.
   */
  struct TALER_EXCHANGE_Keys *prev_keys;

  /**
   * Entry for this request with the `struct GNUNET_CURL_Context`.
   */
  struct GNUNET_CURL_Job *job;

  /**
   * Expiration time according to "Expire:" header.
   * 0 if not provided by the server.
   */
  struct GNUNET_TIME_Timestamp expire;

  /**
   * Function to call with the exchange's certification data,
   * NULL if this has already been done.
   */
  TALER_EXCHANGE_GetKeysCallback cert_cb;

  /**
   * Closure to pass to @e cert_cb.
   */
  TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE *cert_cb_cls;

  /**
   * Reference to the execution context.
   */
  struct GNUNET_CURL_Context *ctx;

};


/**
 * Parse HTTP timestamp.
 *
 * @param dateline header to parse header
 * @param[out] at where to write the result
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
parse_date_string (const char *dateline,
                   struct GNUNET_TIME_Timestamp *at)
{
  static const char *MONTHS[] =
  { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
  int year;
  int mon;
  int day;
  int hour;
  int min;
  int sec;
  char month[4];
  struct tm tm;
  time_t t;

  /* We recognize the three formats in RFC2616, section 3.3.1.  Month
     names are always in English.  The formats are:
      Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
      Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
      Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
     Note that the first is preferred.
   */

  if (strlen (dateline) > MAX_DATE_LINE_LEN)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  while (*dateline == ' ')
    ++dateline;
  while (*dateline && *dateline != ' ')
    ++dateline;
  while (*dateline == ' ')
    ++dateline;
  /* We just skipped over the day of the week. Now we have:*/
  if ( (sscanf (dateline,
                "%d %3s %d %d:%d:%d",
                &day, month, &year, &hour, &min, &sec) != 6) &&
       (sscanf (dateline,
                "%d-%3s-%d %d:%d:%d",
                &day, month, &year, &hour, &min, &sec) != 6) &&
       (sscanf (dateline,
                "%3s %d %d:%d:%d %d",
                month, &day, &hour, &min, &sec, &year) != 6) )
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  /* Two digit dates are defined to be relative to 1900; all other dates
   * are supposed to be represented as four digits. */
  if (year < 100)
    year += 1900;

  for (mon = 0; ; mon++)
  {
    if (! MONTHS[mon])
    {
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    if (0 == strcasecmp (month,
                         MONTHS[mon]))
      break;
  }

  memset (&tm, 0, sizeof(tm));
  tm.tm_year = year - 1900;
  tm.tm_mon = mon;
  tm.tm_mday = day;
  tm.tm_hour = hour;
  tm.tm_min = min;
  tm.tm_sec = sec;

  t = mktime (&tm);
  if (((time_t) -1) == t)
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "mktime");
    return GNUNET_SYSERR;
  }
  if (t < 0)
    t = 0; /* can happen due to timezone issues if date was 1.1.1970 */
  *at = GNUNET_TIME_timestamp_from_s (t);
  return GNUNET_OK;
}


/**
 * Function called for each header in the HTTP /keys response.
 * Finds the "Expire:" header and parses it, storing the result
 * in the "expire" field of the keys request.
 *
 * @param buffer header data received
 * @param size size of an item in @a buffer
 * @param nitems number of items in @a buffer
 * @param userdata the `struct TALER_EXCHANGE_GetKeysHandle`
 * @return `size * nitems` on success (everything else aborts)
 */
static size_t
header_cb (char *buffer,
           size_t size,
           size_t nitems,
           void *userdata)
{
  struct TALER_EXCHANGE_GetKeysHandle *kr = userdata;
  size_t total = size * nitems;
  char *val;

  if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": "))
    return total;
  if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ",
                        buffer,
                        strlen (MHD_HTTP_HEADER_EXPIRES ": ")))
    return total;
  val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
                        total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Found %s header `%s'\n",
              MHD_HTTP_HEADER_EXPIRES,
              val);
  if (GNUNET_OK !=
      parse_date_string (val,
                         &kr->expire))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Failed to parse %s-header `%s'\n",
                MHD_HTTP_HEADER_EXPIRES,
                val);
    kr->expire = GNUNET_TIME_UNIT_ZERO_TS;
  }
  GNUNET_free (val);
  return total;
}


/**
 * Callback used when downloading the reply to a /keys request
 * is complete.
 *
 * @param cls the `struct TALER_EXCHANGE_GetKeysHandle`
 * @param response_code HTTP response code, 0 on error
 * @param resp_obj parsed JSON result, NULL on error
 */
static void
keys_completed_cb (void *cls,
                   long response_code,
                   const void *resp_obj)
{
  struct TALER_EXCHANGE_GetKeysHandle *gkh = cls;
  const json_t *j = resp_obj;
  struct TALER_EXCHANGE_Keys *kd = NULL;
  struct TALER_EXCHANGE_KeysResponse kresp = {
    .hr.reply = j,
    .hr.http_status = (unsigned int) response_code,
    .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR,
  };

  gkh->job = NULL;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Received keys from URL `%s' with status %ld and expiration %s.\n",
              gkh->url,
              response_code,
              GNUNET_TIME_timestamp2s (gkh->expire));
  if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time))
  {
    if (MHD_HTTP_OK == response_code)
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Exchange failed to give expiration time, assuming in %s\n",
                  GNUNET_TIME_relative2s (DEFAULT_EXPIRATION,
                                          true));
    gkh->expire
      = GNUNET_TIME_absolute_to_timestamp (
          GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
  }
  switch (response_code)
  {
  case 0:
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Failed to receive /keys response from exchange %s\n",
                gkh->exchange_url);
    break;
  case MHD_HTTP_OK:
    if (NULL == j)
    {
      GNUNET_break (0);
      kresp.hr.http_status = 0;
      break;
    }
    kd = GNUNET_new (struct TALER_EXCHANGE_Keys);
    kd->exchange_url = GNUNET_strdup (gkh->exchange_url);
    if (NULL != gkh->prev_keys)
    {
      const struct TALER_EXCHANGE_Keys *kd_old = gkh->prev_keys;

      /* We keep the denomination keys and auditor signatures from the
         previous iteration (/keys cherry picking) */
      kd->num_denom_keys
        = kd_old->num_denom_keys;
      kd->last_denom_issue_date
        = kd_old->last_denom_issue_date;
      GNUNET_array_grow (kd->denom_keys,
                         kd->denom_keys_size,
                         kd->num_denom_keys);
      /* First make a shallow copy, we then need another pass for the RSA key... */
      GNUNET_memcpy (kd->denom_keys,
                     kd_old->denom_keys,
                     kd_old->num_denom_keys
                     * sizeof (struct TALER_EXCHANGE_DenomPublicKey));
      for (unsigned int i = 0; i<kd_old->num_denom_keys; i++)
        TALER_denom_pub_copy (&kd->denom_keys[i].key,
                              &kd_old->denom_keys[i].key);
      kd->num_auditors = kd_old->num_auditors;
      kd->auditors
        = GNUNET_new_array (kd->num_auditors,
                            struct TALER_EXCHANGE_AuditorInformation);
      /* Now the necessary deep copy... */
      for (unsigned int i = 0; i<kd_old->num_auditors; i++)
      {
        const struct TALER_EXCHANGE_AuditorInformation *aold =
          &kd_old->auditors[i];
        struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i];

        anew->auditor_pub = aold->auditor_pub;
        anew->auditor_url = GNUNET_strdup (aold->auditor_url);
        anew->auditor_name = GNUNET_strdup (aold->auditor_name);
        GNUNET_array_grow (anew->denom_keys,
                           anew->num_denom_keys,
                           aold->num_denom_keys);
        GNUNET_memcpy (
          anew->denom_keys,
          aold->denom_keys,
          aold->num_denom_keys
          * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
      }
    }
    /* Now decode fresh /keys response */
    if (GNUNET_OK !=
        TALER_EXCHANGE_decode_keys_json_ (j,
                                          true,
                                          kd,
                                          &kresp.details.ok.compat))
    {
      TALER_LOG_ERROR ("Could not decode /keys response\n");
      kd->rc = 1;
      TALER_EXCHANGE_keys_decref (kd);
      kd = NULL;
      kresp.hr.http_status = 0;
      kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
      break;
    }
    kd->rc = 1;
    kd->key_data_expiration = gkh->expire;
    if (GNUNET_TIME_relative_cmp (
          GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time),
          <,
          MINIMUM_EXPIRATION))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Exchange returned keys with expiration time below %s. Compensating.\n",
                  GNUNET_TIME_relative2s (MINIMUM_EXPIRATION,
                                          true));
      kd->key_data_expiration
        = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION);
    }

    kresp.details.ok.keys = kd;
    break;
  case MHD_HTTP_BAD_REQUEST:
  case MHD_HTTP_UNAUTHORIZED:
  case MHD_HTTP_FORBIDDEN:
  case MHD_HTTP_NOT_FOUND:
    if (NULL == j)
    {
      kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
      kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    }
    else
    {
      kresp.hr.ec = TALER_JSON_get_error_code (j);
      kresp.hr.hint = TALER_JSON_get_error_hint (j);
    }
    break;
  default:
    if (NULL == j)
    {
      kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
      kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
    }
    else
    {
      kresp.hr.ec = TALER_JSON_get_error_code (j);
      kresp.hr.hint = TALER_JSON_get_error_hint (j);
    }
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u/%d\n",
                (unsigned int) response_code,
                (int) kresp.hr.ec);
    break;
  }
  gkh->cert_cb (gkh->cert_cb_cls,
                &kresp,
                kd);
  TALER_EXCHANGE_get_keys_cancel (gkh);
}


struct TALER_EXCHANGE_GetKeysHandle *
TALER_EXCHANGE_get_keys_create (
  struct GNUNET_CURL_Context *ctx,
  const char *url)
{
  struct TALER_EXCHANGE_GetKeysHandle *gkh;

  gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle);
  gkh->ctx = ctx;
  gkh->exchange_url = GNUNET_strdup (url);
  return gkh;
}


enum GNUNET_GenericReturnValue
TALER_EXCHANGE_get_keys_set_options_ (
  struct TALER_EXCHANGE_GetKeysHandle *gkh,
  unsigned int num_options,
  const struct TALER_EXCHANGE_GetKeysOptionValue *options)
{
  for (unsigned int i = 0; i < num_options; i++)
  {
    const struct TALER_EXCHANGE_GetKeysOptionValue *opt = &options[i];

    switch (opt->option)
    {
    case TALER_EXCHANGE_GET_KEYS_OPTION_END:
      return GNUNET_OK;
    case TALER_EXCHANGE_GET_KEYS_OPTION_LAST_KEYS:
      TALER_EXCHANGE_keys_decref (gkh->prev_keys);
      gkh->prev_keys = NULL;
      if (NULL != opt->details.last_keys)
        gkh->prev_keys
          = TALER_EXCHANGE_keys_incref (opt->details.last_keys);
      break;
    }
  }
  return GNUNET_OK;
}


enum TALER_ErrorCode
TALER_EXCHANGE_get_keys_start (
  struct TALER_EXCHANGE_GetKeysHandle *gkh,
  TALER_EXCHANGE_GetKeysCallback cert_cb,
  TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE *cert_cb_cls)
{
  CURL *eh;
  char last_date[80] = { 0 };

  gkh->cert_cb = cert_cb;
  gkh->cert_cb_cls = cert_cb_cls;
  if (NULL != gkh->prev_keys)
  {
    TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
                     GNUNET_TIME_timestamp2s (
                       gkh->prev_keys->last_denom_issue_date));
    GNUNET_snprintf (last_date,
                     sizeof (last_date),
                     "%llu",
                     (unsigned long long)
                     gkh->prev_keys->last_denom_issue_date.abs_time.abs_value_us
                     / 1000000LLU);
  }
  gkh->url = TALER_url_join (gkh->exchange_url,
                             "keys",
                             (NULL != gkh->prev_keys)
                             ? "last_issue_date"
                             : NULL,
                             (NULL != gkh->prev_keys)
                             ? last_date
                             : NULL,
                             NULL);
  if (NULL == gkh->url)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Could not construct request URL.\n");
    return TALER_EC_GENERIC_CONFIGURATION_INVALID;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Requesting keys with URL `%s'.\n",
              gkh->url);
  eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
  if (NULL == eh)
  {
    GNUNET_break (0);
    GNUNET_free (gkh->url);
    gkh->url = NULL;
    return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
  }
  GNUNET_break (CURLE_OK ==
                curl_easy_setopt (eh,
                                  CURLOPT_VERBOSE,
                                  0));
  GNUNET_break (CURLE_OK ==
                curl_easy_setopt (eh,
                                  CURLOPT_TIMEOUT,
                                  120 /* seconds */));
  GNUNET_assert (CURLE_OK ==
                 curl_easy_setopt (eh,
                                   CURLOPT_HEADERFUNCTION,
                                   &header_cb));
  GNUNET_assert (CURLE_OK ==
                 curl_easy_setopt (eh,
                                   CURLOPT_HEADERDATA,
                                   gkh));
  gkh->job = GNUNET_CURL_job_add_with_ct_json (gkh->ctx,
                                               eh,
                                               &keys_completed_cb,
                                               gkh);
  if (NULL == gkh->job)
  {
    GNUNET_free (gkh->url);
    gkh->url = NULL;
    return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
  }
  return TALER_EC_NONE;
}


void
TALER_EXCHANGE_get_keys_cancel (
  struct TALER_EXCHANGE_GetKeysHandle *gkh)
{
  if (NULL != gkh->job)
  {
    GNUNET_CURL_job_cancel (gkh->job);
    gkh->job = NULL;
  }
  TALER_EXCHANGE_keys_decref (gkh->prev_keys);
  GNUNET_free (gkh->exchange_url);
  GNUNET_free (gkh->url);
  GNUNET_free (gkh);
}


/* end of exchange_api_get-keys.c */
