/*
  This file is part of Anastasis
  Copyright (C) 2019, 2021 Anastasis SARL

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

  Anastasis 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 Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License along with
  Anastasis; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file anastasis-httpd_truth_upload.c
 * @brief functions to handle incoming POST request on /truth
 * @author Dennis Neufeld
 * @author Dominik Meister
 * @author Christian Grothoff
 */
#include "platform.h"
struct TruthUploadContext;
#define TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE struct \
        TruthUploadContext
#define TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE struct \
        TruthUploadContext
#include "anastasis-httpd.h"
#include "anastasis_service.h"
#include "anastasis-httpd_truth.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_rest_lib.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_merchant_service.h>
#include <taler/taler_signatures.h>
#include "anastasis_authorization_lib.h"
#include <taler/merchant/post-private-orders.h>
#include <taler/merchant/get-private-orders-ORDER_ID.h>


/**
 * Information we track per truth upload.
 */
struct TruthUploadContext
{

  /**
   * UUID of the truth object we are processing.
   */
  struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;

  /**
   * Kept in DLL for shutdown handling while suspended.
   */
  struct TruthUploadContext *next;

  /**
   * Kept in DLL for shutdown handling while suspended.
   */
  struct TruthUploadContext *prev;

  /**
   * Used while we are awaiting proposal creation.
   */
  struct TALER_MERCHANT_PostPrivateOrdersHandle *po;

  /**
   * Used while we are waiting payment.
   */
  struct TALER_MERCHANT_GetPrivateOrderHandle *cpo;

  /**
   * Post parser context.
   */
  void *post_ctx;

  /**
   * Handle to the client request.
   */
  struct MHD_Connection *connection;

  /**
   * Incoming JSON, NULL if not yet available.
   */
  json_t *json;

  /**
   * HTTP response code to use on resume, if non-NULL.
   */
  struct MHD_Response *resp;

  /**
   * When should this request time out?
   */
  struct GNUNET_TIME_Absolute timeout;

  /**
   * Fee that is to be paid for this upload.
   */
  struct TALER_Amount upload_fee;

  /**
   * HTTP response code to use on resume, if resp is set.
   */
  unsigned int response_code;

  /**
   * For how many years must the customer still pay?
   */
  unsigned int years_to_pay;

};


/**
 * Head of linked list over all truth upload processes
 */
static struct TruthUploadContext *tuc_head;

/**
 * Tail of linked list over all truth upload processes
 */
static struct TruthUploadContext *tuc_tail;


void
AH_truth_upload_shutdown (void)
{
  struct TruthUploadContext *tuc;

  while (NULL != (tuc = tuc_head))
  {
    GNUNET_CONTAINER_DLL_remove (tuc_head,
                                 tuc_tail,
                                 tuc);
    if (NULL != tuc->cpo)
    {
      TALER_MERCHANT_get_private_order_cancel (tuc->cpo);
      tuc->cpo = NULL;
    }
    if (NULL != tuc->po)
    {
      TALER_MERCHANT_post_private_orders_cancel (tuc->po);
      tuc->po = NULL;
    }
    MHD_resume_connection (tuc->connection);
  }
}


/**
 * Function called to clean up a `struct TruthUploadContext`.
 *
 * @param hc general handler context
 */
static void
cleanup_truth_post (struct TM_HandlerContext *hc)
{
  struct TruthUploadContext *tuc = hc->ctx;

  TALER_MHD_parse_post_cleanup_callback (tuc->post_ctx);
  if (NULL != tuc->po)
    TALER_MERCHANT_post_private_orders_cancel (tuc->po);
  if (NULL != tuc->cpo)
    TALER_MERCHANT_get_private_order_cancel (tuc->cpo);
  if (NULL != tuc->resp)
    MHD_destroy_response (tuc->resp);
  if (NULL != tuc->json)
    json_decref (tuc->json);
  GNUNET_free (tuc);
}


/**
 * Transmit a payment request for @a tuc.
 *
 * @param tuc upload context to generate payment request for
 */
static void
make_payment_request (struct TruthUploadContext *tuc)
{
  struct MHD_Response *resp;

  /* request payment via Taler */
  resp = MHD_create_response_from_buffer (0,
                                          NULL,
                                          MHD_RESPMEM_PERSISTENT);
  GNUNET_assert (NULL != resp);
  TALER_MHD_add_global_headers (resp,
                                false);
  {
    char *hdr;
    const char *pfx;
    const char *hn;

    if (0 == strncasecmp ("https://",
                          AH_backend_url,
                          strlen ("https://")))
    {
      pfx = "taler://";
      hn = &AH_backend_url[strlen ("https://")];
    }
    else if (0 == strncasecmp ("http://",
                               AH_backend_url,
                               strlen ("http://")))
    {
      pfx = "taler+http://";
      hn = &AH_backend_url[strlen ("http://")];
    }
    else
    {
      /* This invariant holds as per check in anastasis-httpd.c */
      GNUNET_assert (0);
    }
    /* This invariant holds as per check in anastasis-httpd.c */
    GNUNET_assert (0 != strlen (hn));
    {
      char *order_id;

      order_id = GNUNET_STRINGS_data_to_string_alloc (
        &tuc->truth_uuid,
        sizeof (tuc->truth_uuid));
      GNUNET_asprintf (&hdr,
                       "%spay/%s%s/",
                       pfx,
                       hn,
                       order_id);
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Returning %u %s\n",
                  MHD_HTTP_PAYMENT_REQUIRED,
                  order_id);
      GNUNET_free (order_id);
    }
    GNUNET_break (MHD_YES ==
                  MHD_add_response_header (resp,
                                           ANASTASIS_HTTP_HEADER_TALER,
                                           hdr));
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "TRUTH payment request made: %s\n",
                hdr);
    GNUNET_free (hdr);
  }
  tuc->resp = resp;
  tuc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
}


/**
 * Callbacks of this type are used to serve the result of submitting a
 * POST /private/orders request to a merchant.
 *
 * @param cls our `struct TruthUploadContext`
 * @param por response details
 */
static void
proposal_cb (struct TruthUploadContext *tuc,
             const struct TALER_MERCHANT_PostPrivateOrdersResponse *por)
{

  tuc->po = NULL;
  GNUNET_CONTAINER_DLL_remove (tuc_head,
                               tuc_tail,
                               tuc);
  MHD_resume_connection (tuc->connection);
  AH_trigger_daemon (NULL);
  if (MHD_HTTP_OK != por->hr.http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Backend returned status %u/%d\n",
                por->hr.http_status,
                (int) por->hr.ec);
    GNUNET_break (0);
    tuc->resp = TALER_MHD_MAKE_JSON_PACK (
      GNUNET_JSON_pack_uint64 ("code",
                               TALER_EC_ANASTASIS_GENERIC_ORDER_CREATE_BACKEND_ERROR),
      GNUNET_JSON_pack_string ("hint",
                               "Failed to setup order with merchant backend"),
      GNUNET_JSON_pack_uint64 ("backend-ec",
                               por->hr.ec),
      GNUNET_JSON_pack_uint64 ("backend-http-status",
                               por->hr.http_status),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_object_incref ("backend-reply",
                                        (json_t *) por->hr.reply)));
    tuc->response_code = MHD_HTTP_BAD_GATEWAY;
    return;
  }
  make_payment_request (tuc);
}


/**
 * Callback to process a GET /check-payment request
 *
 * @param cls our `struct PolicyUploadContext`
 * @param osr order status
 */
static void
check_payment_cb (struct TruthUploadContext *tuc,
                  const struct TALER_MERCHANT_GetPrivateOrderResponse *osr)
{
  const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr;

  tuc->cpo = NULL;
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Checking backend order status returned %u\n",
              hr->http_status);
  switch (hr->http_status)
  {
  case 0:
    /* Likely timeout, complain! */
    tuc->response_code = MHD_HTTP_GATEWAY_TIMEOUT;
    tuc->resp = TALER_MHD_make_error (
      TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT,
      NULL);
    break;
  case MHD_HTTP_OK:
    switch (osr->details.ok.status)
    {
    case TALER_MERCHANT_OSC_PAID:
      {
        enum GNUNET_DB_QueryStatus qs;
        unsigned int years;
        struct GNUNET_TIME_Relative paid_until;
        const json_t *contract;
        struct TALER_Amount amount;
        struct GNUNET_JSON_Specification cspec[] = {
          TALER_JSON_spec_amount_any ("amount",
                                      &amount),
          GNUNET_JSON_spec_end ()
        };

        contract = osr->details.ok.details.paid.contract_terms;
        if (GNUNET_OK !=
            GNUNET_JSON_parse (contract,
                               cspec,
                               NULL, NULL))
        {
          GNUNET_break (0);
          tuc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
          tuc->resp = TALER_MHD_make_error (
            TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
            "contract terms in database are malformed");
          break;
        }
        years = TALER_amount_divide2 (&amount,
                                      &AH_truth_upload_fee);
        paid_until = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
                                                    years);
        /* add 1 week grace period, otherwise if a user
           wants to pay for 1 year, the first seconds
           would have passed between making the payment
           and our subsequent check if +1 year was
           paid... So we actually say 1 year = 52 weeks
           on the server, while the client calculates
           with 365 days. */
        paid_until = GNUNET_TIME_relative_add (paid_until,
                                               GNUNET_TIME_UNIT_WEEKS);
        qs = ANASTASIS_DB_insert_truth_payment (
          &tuc->truth_uuid,
          &osr->details.ok.details.paid.deposit_total,
          paid_until);
        if (qs <= 0)
        {
          GNUNET_break (0);
          tuc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
          tuc->resp = TALER_MHD_make_error (
            TALER_EC_GENERIC_DB_STORE_FAILED,
            "insert_truth_payment");
          break;
        }
      }
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Payment confirmed, resuming upload\n");
      break;
    case TALER_MERCHANT_OSC_UNPAID:
    case TALER_MERCHANT_OSC_CLAIMED:
      make_payment_request (tuc);
      break;
    }
    break;
  case MHD_HTTP_UNAUTHORIZED:
    /* Configuration issue, complain! */
    tuc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    tuc->resp = TALER_MHD_MAKE_JSON_PACK (
      GNUNET_JSON_pack_uint64 ("code",
                               TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED),
      GNUNET_JSON_pack_string ("hint",
                               TALER_ErrorCode_get_hint (
                                 TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED)),
      GNUNET_JSON_pack_uint64 ("backend-ec",
                               hr->ec),
      GNUNET_JSON_pack_uint64 ("backend-http-status",
                               hr->http_status),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_object_incref ("backend-reply",
                                        (json_t *) hr->reply)));
    GNUNET_assert (NULL != tuc->resp);
    break;
  case MHD_HTTP_NOT_FOUND:
    /* Setup fresh order */
    {
      char *order_id;
      json_t *order;

      order_id = GNUNET_STRINGS_data_to_string_alloc (
        &tuc->truth_uuid,
        sizeof(tuc->truth_uuid));
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "%u, setting up fresh order %s\n",
                  MHD_HTTP_NOT_FOUND,
                  order_id);
      order = json_pack ("{s:o, s:s, s:[{s:s,s:I,s:s}], s:s}",
                         "amount",
                         TALER_JSON_from_amount (&tuc->upload_fee),
                         "summary",
                         "Anastasis challenge storage fee",
                         "products",
                         "description", "challenge storage fee",
                         "quantity", (json_int_t) tuc->years_to_pay,
                         "unit", "years",
                         "order_id",
                         order_id);
      GNUNET_free (order_id);
      if (NULL == order)
      {
        GNUNET_break (0);
        tuc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
        tuc->resp = TALER_MHD_make_error (
          TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
          "could not create order");
        break;
      }
      tuc->po = TALER_MERCHANT_post_private_orders_create (AH_ctx,
                                                           AH_backend_url,
                                                           order);
      GNUNET_assert (NULL != tuc->po);
      GNUNET_assert (
        GNUNET_OK ==
        TALER_MERCHANT_post_private_orders_set_options (
          tuc->po,
          TALER_MERCHANT_post_private_orders_option_create_token (false)));
      GNUNET_assert (TALER_EC_NONE ==
                     TALER_MERCHANT_post_private_orders_start (tuc->po,
                                                               &proposal_cb,
                                                               tuc));
      AH_trigger_curl ();
      json_decref (order);
      return;
    }
  default:
    /* Unexpected backend response */
    tuc->response_code = MHD_HTTP_BAD_GATEWAY;
    tuc->resp = TALER_MHD_MAKE_JSON_PACK (
      GNUNET_JSON_pack_uint64 ("code",
                               TALER_EC_ANASTASIS_GENERIC_BACKEND_ERROR),
      GNUNET_JSON_pack_string ("hint",
                               TALER_ErrorCode_get_hint (
                                 TALER_EC_ANASTASIS_GENERIC_BACKEND_ERROR)),
      GNUNET_JSON_pack_uint64 ("backend-ec",
                               (json_int_t) hr->ec),
      GNUNET_JSON_pack_uint64 ("backend-http-status",
                               (json_int_t) hr->http_status),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_object_incref ("backend-reply",
                                        (json_t *) hr->reply)));
    break;
  }
  GNUNET_CONTAINER_DLL_remove (tuc_head,
                               tuc_tail,
                               tuc);
  MHD_resume_connection (tuc->connection);
  AH_trigger_daemon (NULL);
}


/**
 * Helper function used to ask our backend to begin processing a
 * payment for the truth upload.  May perform asynchronous operations
 * by suspending the connection if required.
 *
 * @param tuc context to begin payment for.
 * @return MHD status code
 */
static enum MHD_Result
begin_payment (struct TruthUploadContext *tuc)
{
  char *order_id;
  struct GNUNET_TIME_Relative timeout;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Checking backend order status...\n");
  timeout = GNUNET_TIME_absolute_get_remaining (tuc->timeout);
  order_id = GNUNET_STRINGS_data_to_string_alloc (
    &tuc->truth_uuid,
    sizeof (tuc->truth_uuid));
  tuc->cpo = TALER_MERCHANT_get_private_order_create (AH_ctx,
                                                      AH_backend_url,
                                                      order_id);
  GNUNET_free (order_id);
  if (NULL == tuc->cpo)
  {
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (tuc->connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_START_FAILED,
                                       "Could not check order status");
  }
  GNUNET_assert (GNUNET_OK ==
                 TALER_MERCHANT_get_private_order_set_options (
                   tuc->cpo,
                   TALER_MERCHANT_get_private_order_option_timeout (timeout)));
  if (TALER_EC_NONE !=
      TALER_MERCHANT_get_private_order_start (tuc->cpo,
                                              &check_payment_cb,
                                              tuc))
  {
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (tuc->connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_START_FAILED,
                                       "Could not check order status");
  }
  GNUNET_CONTAINER_DLL_insert (tuc_head,
                               tuc_tail,
                               tuc);
  MHD_suspend_connection (tuc->connection);
  return MHD_YES;
}


enum MHD_Result
AH_handler_truth_post (
  struct MHD_Connection *connection,
  struct TM_HandlerContext *hc,
  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
  const char *truth_data,
  size_t *truth_data_size)
{
  struct TruthUploadContext *tuc = hc->ctx;
  enum MHD_Result ret;
  int res;
  struct ANASTASIS_CRYPTO_EncryptedKeyShareP key_share_data;
  void *encrypted_truth;
  size_t encrypted_truth_size;
  const char *truth_mime = NULL;
  const char *type;
  uint32_t storage_years;
  struct GNUNET_TIME_Timestamp paid_until
    = GNUNET_TIME_UNIT_ZERO_TS;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_fixed_auto ("key_share_data",
                                 &key_share_data),
    GNUNET_JSON_spec_string ("type",
                             &type),
    GNUNET_JSON_spec_varsize ("encrypted_truth",
                              &encrypted_truth,
                              &encrypted_truth_size),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("truth_mime",
                               &truth_mime),
      NULL),
    GNUNET_JSON_spec_uint32 ("storage_duration_years",
                             &storage_years),
    GNUNET_JSON_spec_end ()
  };

  if (NULL == tuc)
  {
    tuc = GNUNET_new (struct TruthUploadContext);
    tuc->connection = connection;
    tuc->truth_uuid = *truth_uuid;
    hc->ctx = tuc;
    hc->cc = &cleanup_truth_post;
    TALER_MHD_check_content_length (connection,
                                    AH_upload_limit_mb * 1024LLU * 1024LLU);
    tuc->timeout = GNUNET_TIME_relative_to_absolute (
      GNUNET_TIME_UNIT_SECONDS);
    TALER_MHD_parse_request_timeout (connection,
                                     &tuc->timeout);
  } /* end 'if (NULL == tuc)' */

  if (NULL != tuc->resp)
  {
    /* We generated a response asynchronously, queue that */
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Returning asynchronously generated response with HTTP status %u\n",
                tuc->response_code);
    ret = MHD_queue_response (connection,
                              tuc->response_code,
                              tuc->resp);
    GNUNET_break (MHD_YES == ret);
    MHD_destroy_response (tuc->resp);
    tuc->resp = NULL;
    return ret;
  }

  if (NULL == tuc->json)
  {
    res = TALER_MHD_parse_post_json (connection,
                                     &tuc->post_ctx,
                                     truth_data,
                                     truth_data_size,
                                     &tuc->json);
    if (GNUNET_SYSERR == res)
    {
      GNUNET_break (0);
      return MHD_NO;
    }
    if ( (GNUNET_NO == res) ||
         (NULL == tuc->json) )
      return MHD_YES;
  }
  res = TALER_MHD_parse_json_data (connection,
                                   tuc->json,
                                   spec);
  if (GNUNET_SYSERR == res)
  {
    GNUNET_break (0);
    return MHD_NO;   /* hard failure */
  }
  if (GNUNET_NO == res)
  {
    GNUNET_break_op (0);
    return MHD_YES;   /* failure */
  }

  /* check method is supported */
  if ( (0 != strcmp ("question",
                     type)) &&
       (NULL ==
        ANASTASIS_authorization_plugin_load (type,
                                             AH_cfg)) )
  {
    GNUNET_JSON_parse_free (spec);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_BAD_REQUEST,
                                       TALER_EC_ANASTASIS_TRUTH_UPLOAD_METHOD_NOT_SUPPORTED,
                                       type);
  }

  if (storage_years > ANASTASIS_MAX_YEARS_STORAGE)
  {
    GNUNET_break_op (0);
    GNUNET_JSON_parse_free (spec);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_BAD_REQUEST,
                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                       "storage_duration_years");
  }
  if (0 == storage_years)
    storage_years = 1;

  if (! TALER_amount_is_zero (&AH_truth_upload_fee))
  {
    struct GNUNET_TIME_Timestamp desired_until;
    enum GNUNET_DB_QueryStatus qs;

    desired_until
      = GNUNET_TIME_relative_to_timestamp (
          GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
                                         storage_years));
    qs = ANASTASIS_DB_get_truth_payment (
      truth_uuid,
      &paid_until);
    if (qs < 0)
    {
      GNUNET_JSON_parse_free (spec);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
                                         NULL);
    }
    if ( (0 == qs) ||
         (GNUNET_TIME_timestamp_cmp (paid_until,
                                     <,
                                     desired_until) ) )
    {
      struct GNUNET_TIME_Relative rem;

      if (GNUNET_TIME_absolute_is_past (paid_until.abs_time))
        paid_until = GNUNET_TIME_timestamp_get ();
      rem = GNUNET_TIME_absolute_get_difference (paid_until.abs_time,
                                                 desired_until.abs_time);
      tuc->years_to_pay = rem.rel_value_us
                          / GNUNET_TIME_UNIT_YEARS.rel_value_us;
      if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us))
        tuc->years_to_pay++;
      if (0 >
          TALER_amount_multiply (&tuc->upload_fee,
                                 &AH_truth_upload_fee,
                                 tuc->years_to_pay))
      {
        GNUNET_break_op (0);
        GNUNET_JSON_parse_free (spec);
        return TALER_MHD_reply_with_error (connection,
                                           MHD_HTTP_BAD_REQUEST,
                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                           "storage_duration_years");
      }
      if (! TALER_amount_is_zero (&tuc->upload_fee))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                    "Truth upload payment required (%d)!\n",
                    qs);
        /* `spec' is re-parsed from tuc->json when MHD resumes us, and
           begin_payment() only touches `tuc', so release it now. */
        GNUNET_JSON_parse_free (spec);
        return begin_payment (tuc);
      }
    }
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "TRUTH paid until %s (%d)!\n",
                GNUNET_TIME_relative2s (
                  GNUNET_TIME_absolute_get_remaining (
                    paid_until.abs_time),
                  GNUNET_YES),
                qs);
  }
  else
  {
    paid_until
      = GNUNET_TIME_relative_to_timestamp (
          GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
                                         ANASTASIS_MAX_YEARS_STORAGE));
  }


  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Storing truth %s until %s!\n",
              TALER_B2S (truth_uuid),
              GNUNET_TIME_timestamp2s (paid_until));
  {
    enum GNUNET_DB_QueryStatus qs;

    qs = ANASTASIS_DB_insert_truth (
      truth_uuid,
      &key_share_data,
      (NULL == truth_mime)
                        ? ""
                        : truth_mime,
      encrypted_truth,
      encrypted_truth_size,
      type,
      GNUNET_TIME_absolute_get_remaining (
        paid_until.abs_time));
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
    case GNUNET_DB_STATUS_SOFT_ERROR:
      GNUNET_break (0);
      GNUNET_JSON_parse_free (spec);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
                                         "insert_truth");
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      {
        void *xtruth;
        size_t xtruth_size;
        char *xtruth_mime;
        char *xmethod;
        bool ok = false;

        if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
            ANASTASIS_DB_get_truth (
              truth_uuid,
              &xtruth,
              &xtruth_size,
              &xtruth_mime,
              &xmethod))
        {
          ok = ( (xtruth_size == encrypted_truth_size) &&
                 (0 == strcmp (xmethod,
                               type)) &&
                 (0 == strcmp (((NULL == truth_mime) ? "" : truth_mime),
                               ((NULL == xtruth_mime) ? "" : xtruth_mime))) &&
                 (0 == memcmp (xtruth,
                               encrypted_truth,
                               xtruth_size)) );
          /* NOTE: do NOT free encrypted_truth here; it is owned by `spec`
             and released by GNUNET_JSON_parse_free (spec) below.  Freeing it
             here caused a double free on the idempotent fall-through. */
          GNUNET_free (xtruth);
          GNUNET_free (xtruth_mime);
          GNUNET_free (xmethod);
        }
        if (! ok)
        {
          GNUNET_JSON_parse_free (spec);

          return TALER_MHD_reply_with_error (connection,
                                             MHD_HTTP_CONFLICT,
                                             TALER_EC_ANASTASIS_TRUTH_UPLOAD_UUID_EXISTS,
                                             NULL);
        }
        /* idempotency detected, intentional fall through! */
      }
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      {
        struct MHD_Response *resp;

        GNUNET_JSON_parse_free (spec);
        resp = MHD_create_response_from_buffer (0,
                                                NULL,
                                                MHD_RESPMEM_PERSISTENT);
        TALER_MHD_add_global_headers (resp,
                                      false);
        ret = MHD_queue_response (connection,
                                  MHD_HTTP_NO_CONTENT,
                                  resp);
        MHD_destroy_response (resp);
        GNUNET_break (MHD_YES == ret);
        return ret;
      }
    }
  }
  GNUNET_JSON_parse_free (spec);
  GNUNET_break (0);
  return MHD_NO;
}
