/*
  This file is part of TALER
  Copyright (C) 2014, 2015 GNUnet e.V.
  TALER 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.
  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 Affero General Public License for more details.
  You should have received a copy of the GNU Affero General Public License along with
  TALER; see the file COPYING.  If not, If not, see 
*/
/**
 * @file taler-exchange-httpd_refresh.c
 * @brief Handle /refresh/ requests
 * @author Florian Dold
 * @author Benedikt Mueller
 * @author Christian Grothoff
 */
#include "platform.h"
#include 
#include 
#include 
#include "taler-exchange-httpd_parsing.h"
#include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_refresh.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keystate.h"
/**
 * Handle a "/refresh/melt" request after the main JSON parsing has happened.
 * We now need to validate the coins being melted and the session signature
 * and then hand things of to execute the melt operation.
 *
 * @param connection the MHD connection to handle
 * @param num_new_denoms number of coins to be created, size of y-dimension of @a commit_link array
 * @param denom_pubs array of @a num_new_denoms keys
 * @param coin_count number of coins to be melted, size of y-dimension of @a commit_coin array
 * @param coin_melt_details array with @a coin_count entries with melting details
 * @param session_hash hash over the data that the client commits to
 * @param commit_coin 2d array of coin commitments (what the exchange is to sign
 *                    once the "/refres/reveal" of cut and choose is done)
 * @param commit_link 2d array of coin link commitments (what the exchange is
 *                    to return via "/refresh/link" to enable linkage in the
 *                    future)
 * @return MHD result code
 */
static int
handle_refresh_melt_binary (struct MHD_Connection *connection,
                            unsigned int num_new_denoms,
                            const struct TALER_DenominationPublicKey *denom_pubs,
                            unsigned int coin_count,
                            const struct TMH_DB_MeltDetails *coin_melt_details,
                            const struct GNUNET_HashCode *session_hash,
                            struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin,
                            struct TALER_RefreshCommitLinkP *const* commit_link)
{
  unsigned int i;
  struct TMH_KS_StateHandle *key_state;
  struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dk;
  struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
  struct TALER_Amount cost;
  struct TALER_Amount total_cost;
  struct TALER_Amount melt;
  struct TALER_Amount value;
  struct TALER_Amount fee_withdraw;
  struct TALER_Amount fee_melt;
  struct TALER_Amount total_melt;
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TMH_exchange_currency_string,
                                        &total_cost));
  key_state = TMH_KS_acquire ();
  for (i=0;iissue;
    TALER_amount_ntoh (&value,
                       &dki->properties.value);
    TALER_amount_ntoh (&fee_withdraw,
                       &dki->properties.fee_withdraw);
    if ( (GNUNET_OK !=
          TALER_amount_add (&cost,
                            &value,
                            &fee_withdraw)) ||
         (GNUNET_OK !=
          TALER_amount_add (&total_cost,
                            &cost,
                            &total_cost)) )
    {
      GNUNET_break_op (0);
      TMH_KS_release (key_state);
      return TMH_RESPONSE_reply_internal_error (connection,
                                                "cost calculation failure");
    }
  }
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TMH_exchange_currency_string,
                                        &total_melt));
  for (i=0;iissue;
    TALER_amount_ntoh (&fee_melt,
                       &dki->properties.fee_refresh);
    if (GNUNET_OK !=
        TALER_amount_subtract (&melt,
                               &coin_melt_details->melt_amount_with_fee,
                               &fee_melt))
    {
      GNUNET_break_op (0);
      TMH_KS_release (key_state);
      return TMH_RESPONSE_reply_external_error (connection,
                                                "Melt contribution below melting fee");
    }
    if (GNUNET_OK !=
        TALER_amount_add (&total_melt,
                          &melt,
                          &total_melt))
    {
      GNUNET_break_op (0);
      TMH_KS_release (key_state);
      return TMH_RESPONSE_reply_internal_error (connection,
                                                "balance calculation failure");
    }
  }
  TMH_KS_release (key_state);
  if (0 !=
      TALER_amount_cmp (&total_cost,
                        &total_melt))
  {
    GNUNET_break_op (0);
    /* We require total value of coins being melted and
       total value of coins being generated to match! */
    return TMH_RESPONSE_reply_json_pack (connection,
                                         MHD_HTTP_BAD_REQUEST,
                                         "{s:s}",
                                         "error", "value mismatch");
  }
  return TMH_DB_execute_refresh_melt (connection,
                                      session_hash,
                                      num_new_denoms,
                                      denom_pubs,
                                      coin_count,
                                      coin_melt_details,
                                      commit_coin,
                                      commit_link);
}
/**
 * Extract public coin information from a JSON object.
 *
 * @param connection the connection to send error responses to
 * @param coin_info the JSON object to extract the coin info from
 * @param[out] r_melt_detail set to details about the coin's melting permission (if valid)
 * @return #GNUNET_YES if coin public info in JSON was valid
 *         #GNUNET_NO JSON was invalid, response was generated
 *         #GNUNET_SYSERR on internal error
 */
static int
get_coin_public_info (struct MHD_Connection *connection,
                      json_t *coin_info,
                      struct TMH_DB_MeltDetails *r_melt_detail)
{
  int ret;
  struct TALER_CoinSpendSignatureP melt_sig;
  struct TALER_DenominationSignature sig;
  struct TALER_DenominationPublicKey pk;
  struct TALER_Amount amount;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_fixed_auto ("coin_pub", &r_melt_detail->coin_info.coin_pub),
    TALER_JSON_spec_denomination_signature ("denom_sig", &sig),
    TALER_JSON_spec_denomination_public_key ("denom_pub", &pk),
    GNUNET_JSON_spec_fixed_auto ("confirm_sig", &melt_sig),
    TALER_JSON_spec_amount ("value_with_fee", &amount),
    GNUNET_JSON_spec_end ()
  };
  ret = TMH_PARSE_json_data (connection,
                             coin_info,
                             spec);
  if (GNUNET_OK != ret)
  {
    GNUNET_break_op (0);
    return ret;
  }
  /* check exchange signature on the coin */
  r_melt_detail->coin_info.denom_sig = sig;
  r_melt_detail->coin_info.denom_pub = pk;
  if (GNUNET_OK !=
      TALER_test_coin_valid (&r_melt_detail->coin_info))
  {
    GNUNET_break_op (0);
    GNUNET_JSON_parse_free (spec);
    r_melt_detail->coin_info.denom_sig.rsa_signature = NULL;
    r_melt_detail->coin_info.denom_pub.rsa_public_key = NULL;
    return (MHD_YES ==
            TMH_RESPONSE_reply_signature_invalid (connection,
                                                  "denom_sig"))
      ? GNUNET_NO : GNUNET_SYSERR;
  }
  r_melt_detail->melt_sig = melt_sig;
  r_melt_detail->melt_amount_with_fee = amount;
  return GNUNET_OK;
}
/**
 * Verify that the signature shows that this coin is to be melted into
 * the given @a session_hash melting session, and that this is a valid
 * coin (we know the denomination key and the signature on it is
 * valid).  Essentially, this does all of the per-coin checks that can
 * be done before the transaction starts.
 *
 * @param connection the connection to send error responses to
 * @param session_hash hash over refresh session the coin is melted into
 * @param[in,out] melt_detail details about the coin's melting permission,
 *                            the `melt_fee` is updated
 * @return #GNUNET_YES if coin public info in JSON was valid
 *         #GNUNET_NO JSON was invalid, response was generated
 *         #GNUNET_SYSERR on internal error
 */
static int
verify_coin_public_info (struct MHD_Connection *connection,
                         const struct GNUNET_HashCode *session_hash,
                         struct TMH_DB_MeltDetails *melt_detail)
{
  struct TALER_RefreshMeltCoinAffirmationPS body;
  struct TMH_KS_StateHandle *key_state;
  struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
  struct TALER_Amount fee_refresh;
  key_state = TMH_KS_acquire ();
  dki = TMH_KS_denomination_key_lookup (key_state,
                                        &melt_detail->coin_info.denom_pub,
					TMH_KS_DKU_DEPOSIT);
  if (NULL == dki)
  {
    TMH_KS_release (key_state);
    TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n");
    return TMH_RESPONSE_reply_arg_unknown (connection,
                                           "denom_pub");
  }
  TALER_amount_ntoh (&fee_refresh,
                     &dki->issue.properties.fee_refresh);
  melt_detail->melt_fee = fee_refresh;
  body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS));
  body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
  body.session_hash = *session_hash;
  TALER_amount_hton (&body.amount_with_fee,
                     &melt_detail->melt_amount_with_fee);
  TALER_amount_hton (&body.melt_fee,
                     &fee_refresh);
  body.coin_pub = melt_detail->coin_info.coin_pub;
  if (TALER_amount_cmp (&fee_refresh,
                        &melt_detail->melt_amount_with_fee) > 0)
  {
    GNUNET_break_op (0);
    TMH_KS_release (key_state);
    return (MHD_YES ==
            TMH_RESPONSE_reply_external_error (connection,
                                               "melt amount smaller than melting fee"))
      ? GNUNET_NO : GNUNET_SYSERR;
  }
  TMH_KS_release (key_state);
  if (GNUNET_OK !=
      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
                                  &body.purpose,
                                  &melt_detail->melt_sig.eddsa_signature,
                                  &melt_detail->coin_info.coin_pub.eddsa_pub))
  {
    GNUNET_break_op (0);
    if (MHD_YES !=
        TMH_RESPONSE_reply_signature_invalid (connection,
                                              "confirm_sig"))
      return GNUNET_SYSERR;
    return GNUNET_NO;
  }
  return GNUNET_OK;
}
/**
 * Release memory from the @a commit_coin array.
 *
 * @param commit_coin array to release
 * @param kappa size of 1st dimension
 * @param num_new_coins size of 2nd dimension
 */
static void
free_commit_coins (struct TALER_EXCHANGEDB_RefreshCommitCoin **commit_coin,
                   unsigned int kappa,
                   unsigned int num_new_coins)
{
  unsigned int i;
  unsigned int j;
  for (i=0;icoin_ev,
                                  &rcc->coin_ev_size),
        GNUNET_JSON_spec_end ()
      };
      struct GNUNET_JSON_Specification link_spec[] = {
        GNUNET_JSON_spec_varsize (NULL,
                                  (void **) &link_enc,
                                  &link_enc_size),
        GNUNET_JSON_spec_end ()
      };
      res = TMH_PARSE_json_array (connection,
                                  coin_evs,
                                  coin_spec,
                                  i, j, -1);
      if (GNUNET_OK != res)
      {
        GNUNET_break_op (0);
        res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
        goto cleanup;
      }
      GNUNET_CRYPTO_hash_context_read (hash_context,
                                       rcc->coin_ev,
                                       rcc->coin_ev_size);
      res = TMH_PARSE_json_array (connection,
                                  link_encs,
                                  link_spec,
                                  i, j, -1);
      if (GNUNET_OK != res)
      {
        GNUNET_break_op (0);
        res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
        goto cleanup;
      }
      rcc->refresh_link
        = TALER_refresh_link_encrypted_decode (link_enc,
                                               link_enc_size);
      GNUNET_CRYPTO_hash_context_read (hash_context,
                                       link_enc,
                                       link_enc_size);
      GNUNET_JSON_parse_free (link_spec);
    }
  }
  for (i = 0; i < TALER_CNC_KAPPA; i++)
  {
    commit_link[i] = GNUNET_malloc (num_oldcoins *
                                    sizeof (struct TALER_RefreshCommitLinkP));
    for (j = 0; j < num_oldcoins; j++)
    {
      struct TALER_RefreshCommitLinkP *rcl = &commit_link[i][j];
      struct GNUNET_JSON_Specification trans_spec[] = {
        GNUNET_JSON_spec_fixed_auto (NULL, &rcl->transfer_pub),
        GNUNET_JSON_spec_end ()
      };
      struct GNUNET_JSON_Specification sec_spec[] = {
        GNUNET_JSON_spec_fixed_auto (NULL, &rcl->shared_secret_enc),
        GNUNET_JSON_spec_end ()
      };
      res = TMH_PARSE_json_array (connection,
                                  transfer_pubs,
                                  trans_spec,
                                  i, j, -1);
      if (GNUNET_OK != res)
      {
        GNUNET_break_op (0);
        res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
        goto cleanup;
      }
      res = TMH_PARSE_json_array (connection,
                                  secret_encs,
                                  sec_spec,
                                  i, j, -1);
      if (GNUNET_OK != res)
      {
        GNUNET_break_op (0);
        res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
        goto cleanup;
      }
      GNUNET_CRYPTO_hash_context_read (hash_context,
                                       rcl,
                                       sizeof (struct TALER_RefreshCommitLinkP));
    }
  }
  GNUNET_CRYPTO_hash_context_finish (hash_context,
                                     &session_hash);
  hash_context = NULL;
  for (i=0;i