/*
  This file is part of TALER
  Copyright (C) 2023-2025 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 testing/testing_api_cmd_age_withdraw.c
 * @brief implements the withdraw command for age-restricted coins
 * @author Özgür Kesim
 */

#include "platform.h"
#include "taler_exchange_service.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_common.h>
#include <microhttpd.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_signatures.h"
#include "taler_extensions.h"
#include "taler_testing_lib.h"

/*
 * The output state of coin
 */
struct CoinOutputState
{

  /**
   * The calculated details during "withdraw", for the selected coin.
   */
  struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details;

  /**
   * The (wanted) value of the coin, MUST be the same as input.denom_pub.value;
   */
  struct TALER_Amount amount;

};

/**
 * State for a "age withdraw" CMD:
 */

struct AgeWithdrawState
{

  /**
   * Interpreter state (during command)
   */
  struct TALER_TESTING_Interpreter *is;

  /**
   * The age-withdraw handle
   */
  struct TALER_EXCHANGE_WithdrawHandle *handle;

  /**
   * Exchange base URL.  Only used as offered trait.
   */
  char *exchange_url;

  /**
   * URI of the reserve we are withdrawing from.
   */
  struct TALER_NormalizedPayto reserve_payto_uri;

  /**
   * Private key of the reserve we are withdrawing from.
   */
  struct TALER_ReservePrivateKeyP reserve_priv;

  /**
   * Public key of the reserve we are withdrawing from.
   */
  struct TALER_ReservePublicKeyP reserve_pub;

  /**
   * Which reserve should we withdraw from?
   */
  const char *reserve_reference;

  /**
   * Expected HTTP response code to the request.
   */
  unsigned int expected_response_code;

  /**
   * Age mask
   */
  struct TALER_AgeMask mask;

  /**
   * The maximum age we commit to
   */
  uint8_t max_age;

  /**
   * Number of coins to withdraw
   */
  size_t num_coins;

  /**
   * The @e num_coins denomination public keys that are provided
   * to the `TALER_EXCHANGE_withdraw_with_age_proof` API.
   */
  struct TALER_EXCHANGE_DenomPublicKey *denoms_pub;


  /**
   * The master seed from which all the other seeds are derived from
   */
  struct TALER_WithdrawMasterSeedP seed;

  /**
   * The #TALER_CNC_KAPPA seeds derived from @e seed
   */
  struct TALER_KappaWithdrawMasterSeedP kappa_seed;

  /**
   * The master seed from which all the other seeds are derived from
   */
  struct TALER_BlindingMasterSeedP blinding_seed;

  /**
   * The output state of @e num_coins coins, calculated during the
   * "age-withdraw" operation.
   */
  struct CoinOutputState *coin_outputs;

  /**
   * The index returned by the exchange for the "age-withdraw" operation,
   * of the kappa coin candidates that we do not disclose and keep.
   */
  uint8_t noreveal_index;

  /**
   * The hash of the commitment, needed for the reveal step.
   */
  struct TALER_HashBlindedPlanchetsP planchets_h;

  /**
     *  The hash of the selected blinded planchets
     */
  struct TALER_HashBlindedPlanchetsP selected_h;

  /**
   * Set to the KYC requirement payto hash *if* the exchange replied with a
   * request for KYC.
   */
  struct TALER_NormalizedPaytoHashP h_payto;

  /**
   * Set to the KYC requirement row *if* the exchange replied with
   * a request for KYC.
   */
  uint64_t requirement_row;

  /**
   * Reserve history entry that corresponds to this withdraw.
   * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
   */
  struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
};

/**
 * Callback for the "age-withdraw" operation;  It checks that the response
 * code is expected and store the exchange signature in the state.
 *
 * @param cls Closure of type `struct AgeWithdrawState *`
 * @param response Response details
 */
static void
age_withdraw_cb (
  void *cls,
  const struct TALER_EXCHANGE_WithdrawResponse *response)
{
  struct AgeWithdrawState *aws = cls;
  struct TALER_TESTING_Interpreter *is = aws->is;

  aws->handle = NULL;
  if (aws->expected_response_code != response->hr.http_status)
  {
    TALER_TESTING_unexpected_status_with_body (is,
                                               response->hr.http_status,
                                               aws->expected_response_code,
                                               response->hr.reply);
    return;
  }

  switch (response->hr.http_status)
  {
  case MHD_HTTP_CREATED:
    aws->noreveal_index = response->details.created.noreveal_index;
    aws->planchets_h = response->details.created.planchets_h;
    aws->selected_h = response->details.created.selected_h;
    aws->reserve_history.details.withdraw.planchets_h = aws->planchets_h;
    aws->reserve_history.details.withdraw.selected_h = aws->selected_h;
    aws->reserve_history.details.withdraw.noreveal_index = aws->noreveal_index;
    aws->kappa_seed = response->details.created.kappa_seed;

    GNUNET_assert (aws->num_coins == response->details.created.num_coins);
    for (size_t n = 0; n < aws->num_coins; n++)
    {
      aws->coin_outputs[n].details = response->details.created.coin_details[n];
      TALER_age_commitment_proof_deep_copy (
        &aws->coin_outputs[n].details.age_commitment_proof,
        &response->details.created.coin_details[n].age_commitment_proof);
      TALER_denom_ewv_copy (
        &aws->coin_outputs[n].details.blinding_values,
        &response->details.created.coin_details[n].blinding_values);
    }
    break;
  case MHD_HTTP_FORBIDDEN:
  case MHD_HTTP_NOT_FOUND:
  case MHD_HTTP_GONE:
    /* nothing to check */
    break;
  case MHD_HTTP_CONFLICT:
    /* FIXME[oec]: Add this to the response-type and handle it here */
    break;
  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
  default:
    /* Unsupported status code (by test harness) */
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "test command for age-withdraw not support status code %u, body:\n"
                ">>%s<<\n",
                response->hr.http_status,
                json_dumps (response->hr.reply, JSON_INDENT (2)));
    GNUNET_break (0);
    break;
  }

  /* We are done with this command, pick the next one */
  TALER_TESTING_interpreter_next (is);
}


/**
 * Run the command for age-withdraw.
 */
static void
age_withdraw_run (
  void *cls,
  const struct TALER_TESTING_Command *cmd,
  struct TALER_TESTING_Interpreter *is)
{
  struct AgeWithdrawState *aws = cls;
  struct TALER_EXCHANGE_Keys *keys = TALER_TESTING_get_keys (is);
  const struct TALER_ReservePrivateKeyP *rp;
  const struct TALER_TESTING_Command *create_reserve;
  const struct TALER_EXCHANGE_DenomPublicKey *dpk;

  aws->is = is;

  /* Prepare the reserve related data */
  create_reserve
    = TALER_TESTING_interpreter_lookup_command (
        is,
        aws->reserve_reference);

  if (NULL == create_reserve)
  {
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
    return;
  }
  if (GNUNET_OK !=
      TALER_TESTING_get_trait_reserve_priv (create_reserve,
                                            &rp))
  {
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
    return;
  }
  if (NULL == aws->exchange_url)
    aws->exchange_url
      = GNUNET_strdup (TALER_TESTING_get_exchange_url (is));
  aws->reserve_priv = *rp;
  GNUNET_CRYPTO_eddsa_key_get_public (&aws->reserve_priv.eddsa_priv,
                                      &aws->reserve_pub.eddsa_pub);
  aws->reserve_payto_uri
    = TALER_reserve_make_payto (aws->exchange_url,
                                &aws->reserve_pub);

  aws->denoms_pub = GNUNET_new_array (aws->num_coins,
                                      struct TALER_EXCHANGE_DenomPublicKey);

  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                              &aws->seed,
                              sizeof(aws->seed));

  for (unsigned int i = 0; i<aws->num_coins; i++)
  {
    struct TALER_EXCHANGE_DenomPublicKey *denom_pub = &aws->denoms_pub[i];
    struct CoinOutputState *cos = &aws->coin_outputs[i];

    /* Find denomination */
    dpk = TALER_TESTING_find_pk (keys,
                                 &cos->amount,
                                 true); /* _always_ use denominations with age-striction */
    if (NULL == dpk)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed to determine denomination key for amount at %s\n",
                  (NULL != cmd) ? cmd->label : "<retried command>");
      GNUNET_break (0);
      TALER_TESTING_interpreter_fail (is);
      return;
    }

    /* We copy the denomination key, as re-querying /keys
     * would free the old one. */
    *denom_pub = *dpk;
    TALER_denom_pub_copy (&denom_pub->key,
                          &dpk->key);

    /* Accumulate the expected total amount and fee for the history */
    GNUNET_assert (0 <=
                   TALER_amount_add (&aws->reserve_history.amount,
                                     &cos->amount,
                                     &denom_pub->fees.withdraw));
    if (i == 0)
      GNUNET_assert (GNUNET_OK ==
                     TALER_amount_set_zero (
                       denom_pub->fees.withdraw.currency,
                       &aws->reserve_history.details.withdraw.fee));

    GNUNET_assert (0 <=
                   TALER_amount_add (&aws->reserve_history.details.withdraw.fee,
                                     &aws->reserve_history.details.withdraw.fee,
                                     &denom_pub->fees.withdraw));

  }
  /* Save the expected history entry */
  aws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
  aws->reserve_history.details.withdraw.age_restricted = true;
  aws->reserve_history.details.withdraw.max_age = aws->max_age;


  /* Execute the age-restricted variant of withdraw protocol */
  aws->handle =
    TALER_EXCHANGE_withdraw_with_age_proof (
      TALER_TESTING_interpreter_get_context (is),
      keys,
      TALER_TESTING_get_exchange_url (is),
      rp,
      aws->num_coins,
      aws->denoms_pub,
      &aws->seed,
      aws->max_age,
      &age_withdraw_cb,
      aws);

  if (NULL == aws->handle)
  {
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
    return;
  }
}


/**
 * Free the state of a "age withdraw" CMD, and possibly cancel a
 * pending operation thereof
 *
 * @param cls Closure of type `struct AgeWithdrawState`
 * @param cmd The command being freed.
 */
static void
age_withdraw_cleanup (
  void *cls,
  const struct TALER_TESTING_Command *cmd)
{
  struct AgeWithdrawState *aws = cls;

  if (NULL != aws->handle)
  {
    TALER_TESTING_command_incomplete (aws->is,
                                      cmd->label);
    TALER_EXCHANGE_withdraw_cancel (aws->handle);
    aws->handle = NULL;
  }

  if (NULL != aws->denoms_pub)
  {
    for (size_t n = 0; n < aws->num_coins; n++)
      TALER_denom_pub_free (&aws->denoms_pub[n].key);

    GNUNET_free (aws->denoms_pub);
    aws->denoms_pub = NULL;
  }

  if (NULL != aws->coin_outputs)
  {
    for (size_t n = 0; n < aws->num_coins; n++)
    {
      struct CoinOutputState *out = &aws->coin_outputs[n];
      TALER_age_commitment_proof_free (&out->details.age_commitment_proof);
      TALER_denom_ewv_free (&out->details.blinding_values);
    }
    GNUNET_free (aws->coin_outputs);
    aws->coin_outputs = NULL;
  }

  GNUNET_free (aws->exchange_url);
  aws->exchange_url  = NULL;
  GNUNET_free (aws->reserve_payto_uri.normalized_payto);
  aws->reserve_payto_uri.normalized_payto = NULL;
  GNUNET_free (aws);
}


/**
 * Offer internal data of a "age withdraw" CMD state to other commands.
 *
 * @param cls Closure of type `struct AgeWithdrawState`
 * @param[out] ret result (could be anything)
 * @param trait name of the trait
 * @param idx index number of the object to offer.
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
age_withdraw_traits (
  void *cls,
  const void **ret,
  const char *trait,
  unsigned int idx)
{
  struct AgeWithdrawState *aws = cls;
  struct CoinOutputState *out = &aws->coin_outputs[idx];
  struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *details =
    &aws->coin_outputs[idx].details;
  struct TALER_TESTING_Trait traits[] = {
    /* history entry MUST be first due to response code logic below! */
    TALER_TESTING_make_trait_reserve_history (idx,
                                              &aws->reserve_history),
    TALER_TESTING_make_trait_denom_pub (idx,
                                        &aws->denoms_pub[idx]),
    TALER_TESTING_make_trait_reserve_priv (&aws->reserve_priv),
    TALER_TESTING_make_trait_reserve_pub (&aws->reserve_pub),
    TALER_TESTING_make_trait_withdraw_commitment (&aws->planchets_h),
    TALER_TESTING_make_trait_amounts (idx,
                                      &out->amount),
    /* FIXME[oec]: add legal requirement to response and handle it here, as well
    TALER_TESTING_make_trait_legi_requirement_row (&aws->requirement_row),
    TALER_TESTING_make_trait_h_payto (&aws->h_payto),
    */
    TALER_TESTING_make_trait_normalized_payto_uri (&aws->reserve_payto_uri),
    TALER_TESTING_make_trait_exchange_url (aws->exchange_url),
    TALER_TESTING_make_trait_coin_priv (idx,
                                        &details->coin_priv),
    TALER_TESTING_make_trait_withdraw_seed (&aws->seed),
    /* FIXME[oec]: needed!?
    TALER_TESTING_make_trait_planchet_secrets (idx,
                                               &aws->secrets[k][idx]),
    */
    TALER_TESTING_make_trait_blinding_key (idx,
                                           &details->blinding_key),
    TALER_TESTING_make_trait_exchange_blinding_values (idx,
                                                       &details->blinding_values),
    TALER_TESTING_make_trait_age_commitment_proof (
      idx,
      &details->age_commitment_proof),
    TALER_TESTING_make_trait_h_age_commitment (
      idx,
      &details->h_age_commitment),
    TALER_TESTING_trait_end ()
  };

  if (idx >= aws->num_coins)
    return GNUNET_NO;

  return TALER_TESTING_get_trait ((aws->expected_response_code == MHD_HTTP_OK)
                                  ? &traits[0] /* we have reserve history */
                                  : &traits[1], /* skip reserve history */
                                  ret,
                                  trait,
                                  idx);
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_with_age_proof (const char *label,
                                           const char *reserve_reference,
                                           uint8_t max_age,
                                           unsigned int
                                           expected_response_code,
                                           const char *amount,
                                           ...)
{
  struct AgeWithdrawState *aws;
  unsigned int cnt;
  va_list ap;

  aws = GNUNET_new (struct AgeWithdrawState);
  aws->reserve_reference = reserve_reference;
  aws->expected_response_code = expected_response_code;
  aws->mask = TALER_extensions_get_age_restriction_mask ();
  aws->max_age = TALER_get_lowest_age (&aws->mask,
                                       max_age);
  cnt = 1;
  va_start (ap, amount);
  while (NULL != (va_arg (ap, const char *)))
    cnt++;
  aws->num_coins = cnt;
  aws->coin_outputs = GNUNET_new_array (cnt,
                                        struct CoinOutputState);
  va_end (ap);
  va_start (ap, amount);

  for (unsigned int i = 0; i<aws->num_coins; i++)
  {
    struct CoinOutputState *out = &aws->coin_outputs[i];
    if (GNUNET_OK !=
        TALER_string_to_amount (amount,
                                &out->amount))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed to parse amount `%s' at %s\n",
                  amount,
                  label);
      GNUNET_assert (0);
    }
    /* move on to next vararg! */
    amount = va_arg (ap, const char *);
  }

  GNUNET_assert (NULL == amount);
  va_end (ap);

  {
    struct TALER_TESTING_Command cmd = {
      .cls = aws,
      .label = label,
      .run = &age_withdraw_run,
      .cleanup = &age_withdraw_cleanup,
      .traits = &age_withdraw_traits,
    };

    return cmd;
  }
}


/**
 * The state for the age-withdraw-reveal operation
 */
struct AgeRevealWithdrawState
{
  /**
   * The reference to the CMD resembling the previous call to age-withdraw
   */
  const char *age_withdraw_reference;

  /**
   * The state to the previous age-withdraw command
   */
  const struct AgeWithdrawState *aws;

  /**
   * The expected response code from the call to the
   * age-withdraw-reveal operation
   */
  unsigned int expected_response_code;

  /**
   * Interpreter state (during command)
   */
  struct TALER_TESTING_Interpreter *is;

  /**
   * The handle to the reveal-operation
   */
  struct TALER_EXCHANGE_RevealWithdrawHandle *handle;


  /**
   * Number of coins, extracted form the age withdraw command
   */
  size_t num_coins;

  /**
   * The signatures of the @e num_coins coins returned
   */
  struct TALER_DenominationSignature *denom_sigs;

};


/**
 * Callback for the reveal response
 *
 * @param cls Closure of type `struct AgeRevealWithdrawState`
 * @param response The response
 */
static void
age_reveal_withdraw_cb (
  void *cls,
  const struct TALER_EXCHANGE_RevealWithdrawResponse *response)
{
  struct AgeRevealWithdrawState *awrs = cls;
  struct TALER_TESTING_Interpreter *is = awrs->is;

  awrs->handle = NULL;
  if (awrs->expected_response_code != response->hr.http_status)
  {
    TALER_TESTING_unexpected_status_with_body (is,
                                               response->hr.http_status,
                                               awrs->expected_response_code,
                                               response->hr.reply);
    return;
  }
  switch (response->hr.http_status)
  {
  case MHD_HTTP_OK:
    {
      const struct AgeWithdrawState *aws = awrs->aws;
      GNUNET_assert (awrs->num_coins == response->details.ok.num_sigs);
      awrs->denom_sigs = GNUNET_new_array (awrs->num_coins,
                                           struct TALER_DenominationSignature);
      for (size_t n = 0; n < awrs->num_coins; n++)
      {
        GNUNET_assert (GNUNET_OK ==
                       TALER_denom_sig_unblind (
                         &awrs->denom_sigs[n],
                         &response->details.ok.blinded_denom_sigs[n],
                         &aws->coin_outputs[n].details.blinding_key,
                         &aws->coin_outputs[n].details.h_coin_pub,
                         &aws->coin_outputs[n].details.blinding_values,
                         &aws->denoms_pub[n].key));
        TALER_denom_sig_free (&awrs->denom_sigs[n]);
      }

      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "age-withdraw reveal success!\n");
      GNUNET_free (awrs->denom_sigs);
    }
    break;
  case MHD_HTTP_NOT_FOUND:
  case MHD_HTTP_FORBIDDEN:
    /* nothing to check */
    break;
  /* FIXME[oec]: handle more cases !? */
  default:
    /* Unsupported status code (by test harness) */
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Age withdraw reveal test command does not support status code %u\n",
                response->hr.http_status);
    GNUNET_break (0);
    break;
  }

  /* We are done with this command, pick the next one */
  TALER_TESTING_interpreter_next (is);
}


/**
 * Run the command for age-withdraw-reveal
 */
static void
age_reveal_withdraw_run (
  void *cls,
  const struct TALER_TESTING_Command *cmd,
  struct TALER_TESTING_Interpreter *is)
{
  struct AgeRevealWithdrawState *awrs = cls;
  const struct TALER_TESTING_Command *age_withdraw_cmd;
  const struct AgeWithdrawState *aws;

  (void) cmd;
  awrs->is = is;

  /*
   * Get the command and state for the previous call to "age witdraw"
   */
  age_withdraw_cmd  =
    TALER_TESTING_interpreter_lookup_command (is,
                                              awrs->age_withdraw_reference);
  if (NULL == age_withdraw_cmd)
  {
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
    return;
  }
  GNUNET_assert (age_withdraw_cmd->run == age_withdraw_run);
  aws = age_withdraw_cmd->cls;
  awrs->aws = aws;
  awrs->num_coins = aws->num_coins;

  {
    struct TALER_RevealWithdrawMasterSeedsP revealed_seeds;
    size_t j = 0;
    for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
    {
      if (aws->noreveal_index == k)
        continue;

      revealed_seeds.tuple[j] = aws->kappa_seed.tuple[k];
      j++;
    }

    awrs->handle =
      TALER_EXCHANGE_reveal_withdraw (
        TALER_TESTING_interpreter_get_context (is),
        TALER_TESTING_get_exchange_url (is),
        aws->num_coins,
        &aws->planchets_h,
        &revealed_seeds,
        age_reveal_withdraw_cb,
        awrs);
  }
}


/**
 * Free the state of a "age-withdraw-reveal" CMD, and possibly
 * cancel a pending operation thereof
 *
 * @param cls Closure of type `struct AgeRevealWithdrawState`
 * @param cmd The command being freed.
 */
static void
age_reveal_withdraw_cleanup (
  void *cls,
  const struct TALER_TESTING_Command *cmd)
{
  struct AgeRevealWithdrawState *awrs = cls;

  if (NULL != awrs->handle)
  {
    TALER_TESTING_command_incomplete (awrs->is,
                                      cmd->label);
    TALER_EXCHANGE_reveal_withdraw_cancel (awrs->handle);
    awrs->handle = NULL;
  }
  GNUNET_free (awrs->denom_sigs);
  awrs->denom_sigs = NULL;
  GNUNET_free (awrs);
}


/**
 * Offer internal data of a "age withdraw reveal" CMD state to other commands.
 *
 * @param cls Closure of they `struct AgeRevealWithdrawState`
 * @param[out] ret result (could be anything)
 * @param trait name of the trait
 * @param idx index number of the object to offer.
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
age_reveal_withdraw_traits (
  void *cls,
  const void **ret,
  const char *trait,
  unsigned int idx)
{
  struct AgeRevealWithdrawState *awrs = cls;
  struct TALER_TESTING_Trait traits[] = {
    TALER_TESTING_make_trait_denom_sig (idx,
                                        &awrs->denom_sigs[idx]),
    /* FIXME: shall we provide the traits from the previous
     * call to "age withdraw" as well? */
    TALER_TESTING_trait_end ()
  };

  if (idx >= awrs->num_coins)
    return GNUNET_NO;

  return TALER_TESTING_get_trait (traits,
                                  ret,
                                  trait,
                                  idx);
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_reveal_age_proof (
  const char *label,
  const char *age_withdraw_reference,
  unsigned int expected_response_code)
{
  struct AgeRevealWithdrawState *awrs =
    GNUNET_new (struct AgeRevealWithdrawState);

  awrs->age_withdraw_reference = age_withdraw_reference;
  awrs->expected_response_code = expected_response_code;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = awrs,
      .label = label,
      .run = age_reveal_withdraw_run,
      .cleanup = age_reveal_withdraw_cleanup,
      .traits = age_reveal_withdraw_traits,
    };

    return cmd;
  }
}


/* end of testing_api_cmd_age_withdraw.c */
