/*
  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-mint-httpd_refresh.c
 * @brief Handle /refresh/ requests
 * @author Florian Dold
 * @author Benedikt Mueller
 * @author Christian Grothoff
 */
#include "platform.h"
#include 
#include 
#include 
#include "taler-mint-httpd_parsing.h"
#include "taler-mint-httpd_mhd.h"
#include "taler-mint-httpd_refresh.h"
#include "taler-mint-httpd_responses.h"
#include "taler-mint-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 mint is to sign
 *                    once the "/refres/reveal" of cut and choose is done)
 * @param commit_link 2d array of coin link commitments (what the mint 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_MINTDB_RefreshCommitCoin *const* commit_coin,
                            struct TALER_MINTDB_RefreshCommitLinkP *const* commit_link)
{
  unsigned int i;
  struct TMH_KS_StateHandle *key_state;
  struct TALER_MINTDB_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_mint_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)) )
    {
      TMH_KS_release (key_state);
      return TMH_RESPONSE_reply_internal_error (connection,
                                                "cost calculation failure");
    }
  }
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_get_zero (TMH_mint_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))
    {
      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))
    {
      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))
  {
    /* 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 TMH_PARSE_FieldSpecification spec[] = {
    TMH_PARSE_member_fixed ("coin_pub", &r_melt_detail->coin_info.coin_pub),
    TMH_PARSE_member_denomination_signature ("denom_sig", &sig),
    TMH_PARSE_member_denomination_public_key ("denom_pub", &pk),
    TMH_PARSE_member_fixed ("confirm_sig", &melt_sig),
    TMH_PARSE_member_amount ("value_with_fee", &amount),
    TMH_PARSE_MEMBER_END
  };
  ret = TMH_PARSE_json_data (connection,
                             coin_info,
                             spec);
  if (GNUNET_OK != ret)
    return ret;
  /* check mint 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))
  {
    TMH_PARSE_release_data (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;
  TMH_PARSE_release_data (spec);
  return GNUNET_OK;
}
/**
 * Verify that the signature shows that this coin is to be melted into
 * the given @a session_pub 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 melt_detail 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
verify_coin_public_info (struct MHD_Connection *connection,
                         const struct GNUNET_HashCode *session_hash,
                         const struct TMH_DB_MeltDetails *melt_detail)
{
  struct TALER_RefreshMeltCoinAffirmationPS body;
  struct TMH_KS_StateHandle *key_state;
  struct TALER_MINTDB_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");
  }
  /* FIXME: need to check if denomination key is still
     valid for issuing! (#3634) */
  TALER_amount_ntoh (&fee_refresh,
                     &dki->issue.properties.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)
  {
    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))
  {
    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_MINTDB_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);
      if (GNUNET_OK != res)
      {
        GNUNET_CRYPTO_hash_context_abort (hash_context);
        free_commit_coins (commit_coin,
                           TALER_CNC_KAPPA,
                           num_newcoins);
        return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
      }
      GNUNET_CRYPTO_hash_context_read (hash_context,
                                       rcc->coin_ev,
                                       rcc->coin_ev_size);
      res = TMH_PARSE_navigate_json (connection,
                                     link_encs,
                                     TMH_PARSE_JNC_INDEX, (int) i,
                                     TMH_PARSE_JNC_INDEX, (int) j,
                                     TMH_PARSE_JNC_RET_DATA_VAR,
                                     &link_enc,
                                     &link_enc_size);
      if (GNUNET_OK != res)
      {
        GNUNET_CRYPTO_hash_context_abort (hash_context);
        free_commit_coins (commit_coin,
                           TALER_CNC_KAPPA,
                           num_newcoins);
        GNUNET_free (link_enc);
        return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
      }
      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_free (link_enc);
    }
  }
  for (i = 0; i < TALER_CNC_KAPPA; i++)
  {
    commit_link[i] = GNUNET_malloc (num_oldcoins *
                                    sizeof (struct TALER_MINTDB_RefreshCommitLinkP));
    for (j = 0; j < num_oldcoins; j++)
    {
      struct TALER_MINTDB_RefreshCommitLinkP *rcl = &commit_link[i][j];
      res = TMH_PARSE_navigate_json (connection,
                                     transfer_pubs,
                                     TMH_PARSE_JNC_INDEX, (int) i,
                                     TMH_PARSE_JNC_INDEX, (int) j,
                                     TMH_PARSE_JNC_RET_DATA,
                                     &rcl->transfer_pub,
                                     sizeof (struct TALER_TransferPublicKeyP));
      if (GNUNET_OK != res)
      {
        GNUNET_break (GNUNET_SYSERR != res);
        GNUNET_CRYPTO_hash_context_abort (hash_context);
        free_commit_coins (commit_coin,
                           TALER_CNC_KAPPA,
                           num_newcoins);
        free_commit_links (commit_link,
                           TALER_CNC_KAPPA,
                           num_oldcoins);
        return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
      }
      res = TMH_PARSE_navigate_json (connection,
                                     secret_encs,
                                     TMH_PARSE_JNC_INDEX, (int) i,
                                     TMH_PARSE_JNC_INDEX, (int) j,
                                     TMH_PARSE_JNC_RET_DATA,
                                     &rcl->shared_secret_enc,
                                     sizeof (struct TALER_EncryptedLinkSecretP));
      if (GNUNET_OK != res)
      {
        GNUNET_break (GNUNET_SYSERR != res);
        GNUNET_CRYPTO_hash_context_abort (hash_context);
        free_commit_coins (commit_coin,
                           TALER_CNC_KAPPA,
                           num_newcoins);
        free_commit_links (commit_link,
                           TALER_CNC_KAPPA,
                           num_oldcoins);
        return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
      }
      GNUNET_CRYPTO_hash_context_read (hash_context,
                                       rcl,
                                       sizeof (struct TALER_MINTDB_RefreshCommitLinkP));
    }
  }
  GNUNET_CRYPTO_hash_context_finish (hash_context,
                                     &session_hash);
  for (i=0;i