more code refactoring to separate parsing, db and response generation nicely
This commit is contained in:
parent
f9347d2395
commit
ed51946442
@ -27,8 +27,7 @@
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a TALER amount to a JSON
|
* Convert a TALER amount to a JSON object.
|
||||||
* object.
|
|
||||||
*
|
*
|
||||||
* @param amount the amount
|
* @param amount the amount
|
||||||
* @return a json object describing the amount
|
* @return a json object describing the amount
|
||||||
@ -47,6 +46,17 @@ json_t *
|
|||||||
TALER_JSON_from_abs (struct GNUNET_TIME_Absolute stamp);
|
TALER_JSON_from_abs (struct GNUNET_TIME_Absolute stamp);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a signature (with purpose) to a JSON object representation.
|
||||||
|
*
|
||||||
|
* @param purpose purpose of the signature
|
||||||
|
* @param signature the signature
|
||||||
|
* @return the JSON reporesentation of the signature with purpose
|
||||||
|
*/
|
||||||
|
json_t *
|
||||||
|
TALER_JSON_from_sig (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
|
||||||
|
const struct GNUNET_CRYPTO_EddsaSignature *signature);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert binary data to a JSON string
|
* Convert binary data to a JSON string
|
||||||
@ -65,7 +75,7 @@ TALER_JSON_from_data (const void *data, size_t size);
|
|||||||
*
|
*
|
||||||
* @param json the json object representing Amount
|
* @param json the json object representing Amount
|
||||||
* @param r_amount where the amount has to be written
|
* @param r_amount where the amount has to be written
|
||||||
* @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error
|
* @return #GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
TALER_JSON_to_amount (json_t *json,
|
TALER_JSON_to_amount (json_t *json,
|
||||||
@ -76,7 +86,7 @@ TALER_JSON_to_amount (json_t *json,
|
|||||||
*
|
*
|
||||||
* @param json the json object representing absolute time in seconds
|
* @param json the json object representing absolute time in seconds
|
||||||
* @param r_abs where the time has to be written
|
* @param r_abs where the time has to be written
|
||||||
* @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error
|
* @return #GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
TALER_JSON_to_abs (json_t *json,
|
TALER_JSON_to_abs (json_t *json,
|
||||||
@ -88,7 +98,7 @@ TALER_JSON_to_abs (json_t *json,
|
|||||||
* @param json the json object representing data
|
* @param json the json object representing data
|
||||||
* @param out the pointer to hold the parsed data.
|
* @param out the pointer to hold the parsed data.
|
||||||
* @param out_size the size of r_data.
|
* @param out_size the size of r_data.
|
||||||
* @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error
|
* @return #GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
TALER_JSON_to_data (json_t *json,
|
TALER_JSON_to_data (json_t *json,
|
||||||
|
192
src/mint/mint.h
192
src/mint/mint.h
@ -13,14 +13,15 @@
|
|||||||
You should have received a copy of the GNU General Public License along with
|
You should have received a copy of the GNU General Public License along with
|
||||||
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file taler_mint.h
|
* @file mint.h
|
||||||
* @brief Common functionality for the mint
|
* @brief Common functionality for the mint
|
||||||
* @author Florian Dold
|
* @author Florian Dold
|
||||||
* @author Benedikt Mueller
|
* @author Benedikt Mueller
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* - revisit and document `struct Deposit` members.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _MINT_H
|
#ifndef _MINT_H
|
||||||
#define _MINT_H
|
#define _MINT_H
|
||||||
|
|
||||||
@ -55,11 +56,195 @@ struct TALER_MINT_DenomKeyIssuePriv
|
|||||||
* not available.
|
* not available.
|
||||||
*/
|
*/
|
||||||
struct TALER_RSA_PrivateKey *denom_priv;
|
struct TALER_RSA_PrivateKey *denom_priv;
|
||||||
|
|
||||||
struct TALER_MINT_DenomKeyIssue issue;
|
struct TALER_MINT_DenomKeyIssue issue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public information about a coin.
|
||||||
|
*/
|
||||||
|
struct TALER_CoinPublicInfo
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The coin's public key.
|
||||||
|
*/
|
||||||
|
struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The public key signifying the coin's denomination.
|
||||||
|
*/
|
||||||
|
struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signature over coin_pub by denom_pub.
|
||||||
|
*/
|
||||||
|
struct TALER_RSA_Signature denom_sig;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct CollectableBlindcoin
|
||||||
|
{
|
||||||
|
struct TALER_RSA_BlindedSignaturePurpose ev;
|
||||||
|
struct TALER_RSA_Signature ev_sig;
|
||||||
|
struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
|
||||||
|
struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
|
||||||
|
struct GNUNET_CRYPTO_EddsaSignature reserve_sig;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct RefreshSession
|
||||||
|
{
|
||||||
|
int has_commit_sig;
|
||||||
|
struct GNUNET_CRYPTO_EddsaSignature commit_sig;
|
||||||
|
struct GNUNET_CRYPTO_EddsaPublicKey session_pub;
|
||||||
|
uint16_t num_oldcoins;
|
||||||
|
uint16_t num_newcoins;
|
||||||
|
uint16_t kappa;
|
||||||
|
uint16_t noreveal_index;
|
||||||
|
uint8_t reveal_ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define TALER_REFRESH_SHARED_SECRET_LENGTH (sizeof (struct GNUNET_HashCode))
|
||||||
|
#define TALER_REFRESH_LINK_LENGTH (sizeof (struct LinkData))
|
||||||
|
|
||||||
|
struct RefreshCommitLink
|
||||||
|
{
|
||||||
|
struct GNUNET_CRYPTO_EddsaPublicKey session_pub;
|
||||||
|
struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub;
|
||||||
|
uint16_t cnc_index;
|
||||||
|
uint16_t oldcoin_index;
|
||||||
|
char shared_secret_enc[sizeof (struct GNUNET_HashCode)];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LinkData
|
||||||
|
{
|
||||||
|
struct GNUNET_CRYPTO_EcdsaPrivateKey coin_priv;
|
||||||
|
struct TALER_RSA_BlindingKeyBinaryEncoded bkey_enc;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
GNUNET_NETWORK_STRUCT_BEGIN
|
||||||
|
|
||||||
|
struct SharedSecretEnc
|
||||||
|
{
|
||||||
|
char data[TALER_REFRESH_SHARED_SECRET_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct LinkDataEnc
|
||||||
|
{
|
||||||
|
char data[sizeof (struct LinkData)];
|
||||||
|
};
|
||||||
|
|
||||||
|
GNUNET_NETWORK_STRUCT_END
|
||||||
|
|
||||||
|
struct RefreshCommitCoin
|
||||||
|
{
|
||||||
|
struct GNUNET_CRYPTO_EddsaPublicKey session_pub;
|
||||||
|
struct TALER_RSA_BlindedSignaturePurpose coin_ev;
|
||||||
|
uint16_t cnc_index;
|
||||||
|
uint16_t newcoin_index;
|
||||||
|
char link_enc[sizeof (struct LinkData)];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct KnownCoin
|
||||||
|
{
|
||||||
|
struct TALER_CoinPublicInfo public_info;
|
||||||
|
struct TALER_Amount expended_balance;
|
||||||
|
int is_refreshed;
|
||||||
|
/**
|
||||||
|
* Refreshing session, only valid if
|
||||||
|
* is_refreshed==1.
|
||||||
|
*/
|
||||||
|
struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specification for a /deposit operation.
|
||||||
|
*/
|
||||||
|
struct Deposit
|
||||||
|
{
|
||||||
|
/* FIXME: should be TALER_CoinPublicInfo */
|
||||||
|
struct GNUNET_CRYPTO_EddsaPublicKey coin_pub;
|
||||||
|
|
||||||
|
struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
|
||||||
|
|
||||||
|
struct TALER_RSA_Signature coin_sig;
|
||||||
|
|
||||||
|
struct TALER_RSA_Signature ubsig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of the deposit (also purpose of the signature). Either
|
||||||
|
* #TALER_SIGNATURE_DEPOSIT or #TALER_SIGNATURE_INCREMENTAL_DEPOSIT.
|
||||||
|
*/
|
||||||
|
struct TALER_RSA_SignaturePurpose purpose;
|
||||||
|
|
||||||
|
uint64_t transaction_id;
|
||||||
|
|
||||||
|
struct TALER_AmountNBO amount;
|
||||||
|
|
||||||
|
struct GNUNET_CRYPTO_EddsaPublicKey merchant_pub;
|
||||||
|
|
||||||
|
struct GNUNET_HashCode h_contract;
|
||||||
|
|
||||||
|
struct GNUNET_HashCode h_wire;
|
||||||
|
|
||||||
|
/* TODO: uint16_t wire_size */
|
||||||
|
char wire[]; /* string encoded wire JSON object */
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserve row. Corresponds to table 'reserves' in the mint's
|
||||||
|
* database. FIXME: not sure this is how we want to store this
|
||||||
|
* information. Also, may currently used in different ways in the
|
||||||
|
* code, so we might need to separate the struct into different ones
|
||||||
|
* depending on the context it is used in.
|
||||||
|
*/
|
||||||
|
struct Reserve
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Signature over the purse.
|
||||||
|
* Only valid if (blind_session_missing==GNUNET_YES).
|
||||||
|
*/
|
||||||
|
struct GNUNET_CRYPTO_EddsaSignature status_sig;
|
||||||
|
/**
|
||||||
|
* Signature with purpose TALER_SIGNATURE_PURSE.
|
||||||
|
* Only valid if (blind_session_missing==GNUNET_YES).
|
||||||
|
*/
|
||||||
|
struct GNUNET_CRYPTO_EccSignaturePurpose status_sig_purpose;
|
||||||
|
/**
|
||||||
|
* Signing key used to sign the purse.
|
||||||
|
* Only valid if (blind_session_missing==GNUNET_YES).
|
||||||
|
*/
|
||||||
|
struct GNUNET_CRYPTO_EddsaPublicKey status_sign_pub;
|
||||||
|
/**
|
||||||
|
* Withdraw public key, identifies the purse.
|
||||||
|
* Only the customer knows the corresponding private key.
|
||||||
|
*/
|
||||||
|
struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
|
||||||
|
/**
|
||||||
|
* Remaining balance in the purse.
|
||||||
|
*/
|
||||||
|
struct TALER_AmountNBO balance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expiration date for the purse.
|
||||||
|
*/
|
||||||
|
struct GNUNET_TIME_AbsoluteNBO expiration;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -153,4 +338,3 @@ TALER_TALER_DB_extract_amount_nbo (PGresult *result, unsigned int row,
|
|||||||
int indices[3], struct TALER_AmountNBO *denom_nbo);
|
int indices[3], struct TALER_AmountNBO *denom_nbo);
|
||||||
|
|
||||||
#endif /* _MINT_H */
|
#endif /* _MINT_H */
|
||||||
|
|
||||||
|
@ -29,149 +29,8 @@
|
|||||||
#include "taler_util.h"
|
#include "taler_util.h"
|
||||||
#include "taler_rsa.h"
|
#include "taler_rsa.h"
|
||||||
#include "taler-mint-httpd_db.h"
|
#include "taler-mint-httpd_db.h"
|
||||||
|
#include "mint.h"
|
||||||
|
|
||||||
/**
|
|
||||||
* Public information about a coin.
|
|
||||||
*/
|
|
||||||
struct TALER_CoinPublicInfo
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The coin's public key.
|
|
||||||
*/
|
|
||||||
struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The public key signifying the coin's denomination.
|
|
||||||
*/
|
|
||||||
struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signature over coin_pub by denom_pub.
|
|
||||||
*/
|
|
||||||
struct TALER_RSA_Signature denom_sig;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reserve row. Corresponds to table 'reserves' in
|
|
||||||
* the mint's database.
|
|
||||||
*/
|
|
||||||
struct Reserve
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Signature over the purse.
|
|
||||||
* Only valid if (blind_session_missing==GNUNET_YES).
|
|
||||||
*/
|
|
||||||
struct GNUNET_CRYPTO_EddsaSignature status_sig;
|
|
||||||
/**
|
|
||||||
* Signature with purpose TALER_SIGNATURE_PURSE.
|
|
||||||
* Only valid if (blind_session_missing==GNUNET_YES).
|
|
||||||
*/
|
|
||||||
struct GNUNET_CRYPTO_EccSignaturePurpose status_sig_purpose;
|
|
||||||
/**
|
|
||||||
* Signing key used to sign the purse.
|
|
||||||
* Only valid if (blind_session_missing==GNUNET_YES).
|
|
||||||
*/
|
|
||||||
struct GNUNET_CRYPTO_EddsaPublicKey status_sign_pub;
|
|
||||||
/**
|
|
||||||
* Withdraw public key, identifies the purse.
|
|
||||||
* Only the customer knows the corresponding private key.
|
|
||||||
*/
|
|
||||||
struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
|
|
||||||
/**
|
|
||||||
* Remaining balance in the purse.
|
|
||||||
*/
|
|
||||||
struct TALER_AmountNBO balance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expiration date for the purse.
|
|
||||||
*/
|
|
||||||
struct GNUNET_TIME_AbsoluteNBO expiration;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct CollectableBlindcoin
|
|
||||||
{
|
|
||||||
struct TALER_RSA_BlindedSignaturePurpose ev;
|
|
||||||
struct TALER_RSA_Signature ev_sig;
|
|
||||||
struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
|
|
||||||
struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
|
|
||||||
struct GNUNET_CRYPTO_EddsaSignature reserve_sig;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct RefreshSession
|
|
||||||
{
|
|
||||||
int has_commit_sig;
|
|
||||||
struct GNUNET_CRYPTO_EddsaSignature commit_sig;
|
|
||||||
struct GNUNET_CRYPTO_EddsaPublicKey session_pub;
|
|
||||||
uint16_t num_oldcoins;
|
|
||||||
uint16_t num_newcoins;
|
|
||||||
uint16_t kappa;
|
|
||||||
uint16_t noreveal_index;
|
|
||||||
uint8_t reveal_ok;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#define TALER_REFRESH_SHARED_SECRET_LENGTH (sizeof (struct GNUNET_HashCode))
|
|
||||||
#define TALER_REFRESH_LINK_LENGTH (sizeof (struct LinkData))
|
|
||||||
|
|
||||||
struct RefreshCommitLink
|
|
||||||
{
|
|
||||||
struct GNUNET_CRYPTO_EddsaPublicKey session_pub;
|
|
||||||
struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub;
|
|
||||||
uint16_t cnc_index;
|
|
||||||
uint16_t oldcoin_index;
|
|
||||||
char shared_secret_enc[sizeof (struct GNUNET_HashCode)];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LinkData
|
|
||||||
{
|
|
||||||
struct GNUNET_CRYPTO_EcdsaPrivateKey coin_priv;
|
|
||||||
struct TALER_RSA_BlindingKeyBinaryEncoded bkey_enc;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
GNUNET_NETWORK_STRUCT_BEGIN
|
|
||||||
|
|
||||||
struct SharedSecretEnc
|
|
||||||
{
|
|
||||||
char data[TALER_REFRESH_SHARED_SECRET_LENGTH];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct LinkDataEnc
|
|
||||||
{
|
|
||||||
char data[sizeof (struct LinkData)];
|
|
||||||
};
|
|
||||||
|
|
||||||
GNUNET_NETWORK_STRUCT_END
|
|
||||||
|
|
||||||
struct RefreshCommitCoin
|
|
||||||
{
|
|
||||||
struct GNUNET_CRYPTO_EddsaPublicKey session_pub;
|
|
||||||
struct TALER_RSA_BlindedSignaturePurpose coin_ev;
|
|
||||||
uint16_t cnc_index;
|
|
||||||
uint16_t newcoin_index;
|
|
||||||
char link_enc[sizeof (struct LinkData)];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct KnownCoin
|
|
||||||
{
|
|
||||||
struct TALER_CoinPublicInfo public_info;
|
|
||||||
struct TALER_Amount expended_balance;
|
|
||||||
int is_refreshed;
|
|
||||||
/**
|
|
||||||
* Refreshing session, only valid if
|
|
||||||
* is_refreshed==1.
|
|
||||||
*/
|
|
||||||
struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub;
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
int
|
||||||
TALER_MINT_DB_prepare (PGconn *db_conn);
|
TALER_MINT_DB_prepare (PGconn *db_conn);
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
#include <libpq-fe.h>
|
#include <libpq-fe.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include "mint.h"
|
#include "mint.h"
|
||||||
#include "mint_db.h"
|
|
||||||
#include "taler_signatures.h"
|
#include "taler_signatures.h"
|
||||||
#include "taler_rsa.h"
|
#include "taler_rsa.h"
|
||||||
#include "taler_json_lib.h"
|
#include "taler_json_lib.h"
|
||||||
@ -38,6 +37,7 @@
|
|||||||
#include "taler-mint-httpd_deposit.h"
|
#include "taler-mint-httpd_deposit.h"
|
||||||
#include "taler-mint-httpd_withdraw.h"
|
#include "taler-mint-httpd_withdraw.h"
|
||||||
#include "taler-mint-httpd_refresh.h"
|
#include "taler-mint-httpd_refresh.h"
|
||||||
|
#include "mint_db.h"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,12 +25,15 @@
|
|||||||
* - /deposit: check for leaks
|
* - /deposit: check for leaks
|
||||||
*/
|
*/
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <jansson.h>
|
||||||
#include "taler-mint-httpd_db.h"
|
#include "taler-mint-httpd_db.h"
|
||||||
#include "taler_signatures.h"
|
#include "taler_signatures.h"
|
||||||
|
#include "taler-mint-httpd_keys.h"
|
||||||
#include "taler-mint-httpd_responses.h"
|
#include "taler-mint-httpd_responses.h"
|
||||||
#include "mint_db.h"
|
#include "mint_db.h"
|
||||||
#include "mint.h"
|
#include "mint.h"
|
||||||
#include <pthread.h>
|
#include "taler_json_lib.h"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,18 +51,15 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection,
|
|||||||
const struct Deposit *deposit)
|
const struct Deposit *deposit)
|
||||||
{
|
{
|
||||||
PGconn *db_conn;
|
PGconn *db_conn;
|
||||||
|
struct Deposit *existing_deposit;
|
||||||
|
int res;
|
||||||
|
|
||||||
if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
|
if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
|
||||||
{
|
{
|
||||||
GNUNET_break (0);
|
GNUNET_break (0);
|
||||||
return TALER_MINT_reply_internal_error (connection,
|
return TALER_MINT_reply_internal_error (connection,
|
||||||
"Failed to connect to database\n");
|
"Failed to connect to database");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
struct Deposit *existing_deposit;
|
|
||||||
int res;
|
|
||||||
|
|
||||||
res = TALER_MINT_DB_get_deposit (db_conn,
|
res = TALER_MINT_DB_get_deposit (db_conn,
|
||||||
&deposit->coin_pub,
|
&deposit->coin_pub,
|
||||||
&existing_deposit);
|
&existing_deposit);
|
||||||
@ -85,7 +85,6 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection,
|
|||||||
/* FIXME: return error message to client via MHD! */
|
/* FIXME: return error message to client via MHD! */
|
||||||
return MHD_NO;
|
return MHD_NO;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
struct KnownCoin known_coin;
|
struct KnownCoin known_coin;
|
||||||
@ -133,3 +132,228 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection,
|
|||||||
TALER_MINT_DB_commit (db_conn);
|
TALER_MINT_DB_commit (db_conn);
|
||||||
return TALER_MINT_reply_deposit_success (connection, deposit);
|
return TALER_MINT_reply_deposit_success (connection, deposit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign a reserve's status with the current signing key.
|
||||||
|
* FIXME: not sure why we do this. Should just return
|
||||||
|
* existing list of operations on the reserve.
|
||||||
|
*
|
||||||
|
* @param reserve the reserve to sign
|
||||||
|
* @param key_state the key state containing the current
|
||||||
|
* signing private key
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
sign_reserve (struct Reserve *reserve,
|
||||||
|
struct MintKeyState *key_state)
|
||||||
|
{
|
||||||
|
reserve->status_sign_pub = key_state->current_sign_key_issue.issue.signkey_pub;
|
||||||
|
reserve->status_sig_purpose.purpose = htonl (TALER_SIGNATURE_RESERVE_STATUS);
|
||||||
|
reserve->status_sig_purpose.size = htonl (sizeof (struct Reserve) -
|
||||||
|
offsetof (struct Reserve, status_sig_purpose));
|
||||||
|
GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv,
|
||||||
|
&reserve->status_sig_purpose,
|
||||||
|
&reserve->status_sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a /withdraw/status.
|
||||||
|
*
|
||||||
|
* @param connection the MHD connection to handle
|
||||||
|
* @param reserve_pub public key of the reserve to check
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
TALER_MINT_db_execute_withdraw_status (struct MHD_Connection *connection,
|
||||||
|
const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub)
|
||||||
|
{
|
||||||
|
PGconn *db_conn;
|
||||||
|
int res;
|
||||||
|
struct Reserve reserve;
|
||||||
|
struct MintKeyState *key_state;
|
||||||
|
int must_update = GNUNET_NO;
|
||||||
|
|
||||||
|
|
||||||
|
if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
return TALER_MINT_reply_internal_error (connection,
|
||||||
|
"Failed to connect to database");
|
||||||
|
}
|
||||||
|
res = TALER_MINT_DB_get_reserve (db_conn,
|
||||||
|
reserve_pub,
|
||||||
|
&reserve);
|
||||||
|
/* check if these are really the matching error codes,
|
||||||
|
seems odd... */
|
||||||
|
if (GNUNET_SYSERR == res)
|
||||||
|
return TALER_MINT_reply_json_pack (connection,
|
||||||
|
MHD_HTTP_NOT_FOUND,
|
||||||
|
"{s:s}",
|
||||||
|
"error",
|
||||||
|
"Reserve not found");
|
||||||
|
if (GNUNET_OK != res)
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
return TALER_MINT_reply_internal_error (connection,
|
||||||
|
"Internal error");
|
||||||
|
}
|
||||||
|
key_state = TALER_MINT_key_state_acquire ();
|
||||||
|
if (0 != memcmp (&key_state->current_sign_key_issue.issue.signkey_pub,
|
||||||
|
&reserve.status_sign_pub,
|
||||||
|
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))
|
||||||
|
{
|
||||||
|
sign_reserve (&reserve, key_state);
|
||||||
|
must_update = GNUNET_YES;
|
||||||
|
}
|
||||||
|
if ((GNUNET_YES == must_update) &&
|
||||||
|
(GNUNET_OK != TALER_MINT_DB_update_reserve (db_conn, &reserve, !must_update)))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
return MHD_YES;
|
||||||
|
}
|
||||||
|
return TALER_MINT_reply_withdraw_status_success (connection,
|
||||||
|
&reserve);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a /withdraw/sign.
|
||||||
|
*
|
||||||
|
* @param connection the MHD connection to handle
|
||||||
|
* @param wsrd_ro details about the withdraw request
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection,
|
||||||
|
const struct TALER_WithdrawRequest *wsrd_ro)
|
||||||
|
{
|
||||||
|
PGconn *db_conn;
|
||||||
|
struct Reserve reserve;
|
||||||
|
struct MintKeyState *key_state;
|
||||||
|
struct CollectableBlindcoin collectable;
|
||||||
|
struct TALER_MINT_DenomKeyIssuePriv *dki;
|
||||||
|
struct TALER_RSA_Signature ev_sig;
|
||||||
|
struct TALER_Amount amount_required;
|
||||||
|
/* FIXME: the fact that we do this here is a sign that we
|
||||||
|
need to have different versions of this struct for
|
||||||
|
the different places it is used! */
|
||||||
|
struct TALER_WithdrawRequest wsrd = *wsrd_ro;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
|
||||||
|
{
|
||||||
|
// FIXME: return 'internal error'?
|
||||||
|
GNUNET_break (0);
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
res = TALER_MINT_DB_get_collectable_blindcoin (db_conn,
|
||||||
|
&wsrd.coin_envelope,
|
||||||
|
&collectable);
|
||||||
|
if (GNUNET_SYSERR == res)
|
||||||
|
{
|
||||||
|
// FIXME: return 'internal error'
|
||||||
|
GNUNET_break (0);
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't sign again if we have already signed the coin */
|
||||||
|
if (GNUNET_YES == res)
|
||||||
|
return TALER_MINT_reply_withdraw_sign_success (connection,
|
||||||
|
&collectable);
|
||||||
|
GNUNET_assert (GNUNET_NO == res);
|
||||||
|
res = TALER_MINT_DB_get_reserve (db_conn,
|
||||||
|
&wsrd.reserve_pub,
|
||||||
|
&reserve);
|
||||||
|
if (GNUNET_SYSERR == res)
|
||||||
|
{
|
||||||
|
// FIXME: return 'internal error'
|
||||||
|
GNUNET_break (0);
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
if (GNUNET_NO == res)
|
||||||
|
return TALER_MINT_reply_json_pack (connection,
|
||||||
|
MHD_HTTP_NOT_FOUND,
|
||||||
|
"{s:s}",
|
||||||
|
"error",
|
||||||
|
"Reserve not found");
|
||||||
|
|
||||||
|
// fill out all the missing info in the request before
|
||||||
|
// we can check the signature on the request
|
||||||
|
|
||||||
|
wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WITHDRAW);
|
||||||
|
wsrd.purpose.size = htonl (sizeof (struct TALER_WithdrawRequest) -
|
||||||
|
offsetof (struct TALER_WithdrawRequest, purpose));
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WITHDRAW,
|
||||||
|
&wsrd.purpose,
|
||||||
|
&wsrd.sig,
|
||||||
|
&wsrd.reserve_pub))
|
||||||
|
return TALER_MINT_reply_json_pack (connection,
|
||||||
|
MHD_HTTP_UNAUTHORIZED,
|
||||||
|
"{s:s}",
|
||||||
|
"error", "Invalid Signature");
|
||||||
|
|
||||||
|
key_state = TALER_MINT_key_state_acquire ();
|
||||||
|
dki = TALER_MINT_get_denom_key (key_state,
|
||||||
|
&wsrd.denomination_pub);
|
||||||
|
TALER_MINT_key_state_release (key_state);
|
||||||
|
if (NULL == dki)
|
||||||
|
return TALER_MINT_reply_json_pack (connection,
|
||||||
|
MHD_HTTP_NOT_FOUND,
|
||||||
|
"{s:s}",
|
||||||
|
"error",
|
||||||
|
"Denomination not found");
|
||||||
|
|
||||||
|
amount_required = TALER_amount_ntoh (dki->issue.value);
|
||||||
|
amount_required = TALER_amount_add (amount_required,
|
||||||
|
TALER_amount_ntoh (dki->issue.fee_withdraw));
|
||||||
|
|
||||||
|
if (0 < TALER_amount_cmp (amount_required,
|
||||||
|
TALER_amount_ntoh (reserve.balance)))
|
||||||
|
return TALER_MINT_reply_json_pack (connection,
|
||||||
|
MHD_HTTP_PAYMENT_REQUIRED,
|
||||||
|
"{s:s}",
|
||||||
|
"error",
|
||||||
|
"Insufficient funds");
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
TALER_RSA_sign (dki->denom_priv,
|
||||||
|
&wsrd.coin_envelope,
|
||||||
|
sizeof (struct TALER_RSA_BlindedSignaturePurpose),
|
||||||
|
&ev_sig))
|
||||||
|
{
|
||||||
|
// FIXME: return 'internal error'
|
||||||
|
GNUNET_break (0);
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
reserve.balance = TALER_amount_hton (TALER_amount_subtract (TALER_amount_ntoh (reserve.balance),
|
||||||
|
amount_required));
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
TALER_MINT_DB_update_reserve (db_conn,
|
||||||
|
&reserve,
|
||||||
|
GNUNET_YES))
|
||||||
|
{
|
||||||
|
// FIXME: return 'internal error'
|
||||||
|
GNUNET_break (0);
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
collectable.ev = wsrd.coin_envelope;
|
||||||
|
collectable.ev_sig = ev_sig;
|
||||||
|
collectable.reserve_pub = wsrd.reserve_pub;
|
||||||
|
collectable.reserve_sig = wsrd.sig;
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
TALER_MINT_DB_insert_collectable_blindcoin (db_conn,
|
||||||
|
&collectable))
|
||||||
|
{
|
||||||
|
// FIXME: return 'internal error'
|
||||||
|
GNUNET_break (0);
|
||||||
|
return GNUNET_NO;;
|
||||||
|
}
|
||||||
|
return TALER_MINT_reply_withdraw_sign_success (connection,
|
||||||
|
&collectable);
|
||||||
|
}
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
* @file mint/taler-mint_httpd_db.h
|
* @file mint/taler-mint_httpd_db.h
|
||||||
* @brief Mint-specific database access
|
* @brief Mint-specific database access
|
||||||
* @author Chrisitan Grothoff
|
* @author Chrisitan Grothoff
|
||||||
*
|
|
||||||
* TODO:
|
|
||||||
* - revisit and document `struct Deposit` members.
|
|
||||||
*/
|
*/
|
||||||
#ifndef TALER_MINT_HTTPD_DB_H
|
#ifndef TALER_MINT_HTTPD_DB_H
|
||||||
#define TALER_MINT_HTTPD_DB_H
|
#define TALER_MINT_HTTPD_DB_H
|
||||||
@ -29,46 +26,13 @@
|
|||||||
#include <gnunet/gnunet_util_lib.h>
|
#include <gnunet/gnunet_util_lib.h>
|
||||||
#include "taler_util.h"
|
#include "taler_util.h"
|
||||||
#include "taler_rsa.h"
|
#include "taler_rsa.h"
|
||||||
|
#include "taler-mint-httpd_keys.h"
|
||||||
|
#include "mint.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specification for a /deposit operation.
|
* Execute a /deposit. The validity of the coin and signature
|
||||||
*/
|
|
||||||
struct Deposit
|
|
||||||
{
|
|
||||||
/* FIXME: should be TALER_CoinPublicInfo */
|
|
||||||
struct GNUNET_CRYPTO_EddsaPublicKey coin_pub;
|
|
||||||
|
|
||||||
struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
|
|
||||||
|
|
||||||
struct TALER_RSA_Signature coin_sig;
|
|
||||||
|
|
||||||
struct TALER_RSA_Signature ubsig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of the deposit (also purpose of the signature). Either
|
|
||||||
* #TALER_SIGNATURE_DEPOSIT or #TALER_SIGNATURE_INCREMENTAL_DEPOSIT.
|
|
||||||
*/
|
|
||||||
struct TALER_RSA_SignaturePurpose purpose;
|
|
||||||
|
|
||||||
uint64_t transaction_id;
|
|
||||||
|
|
||||||
struct TALER_AmountNBO amount;
|
|
||||||
|
|
||||||
struct GNUNET_CRYPTO_EddsaPublicKey merchant_pub;
|
|
||||||
|
|
||||||
struct GNUNET_HashCode h_contract;
|
|
||||||
|
|
||||||
struct GNUNET_HashCode h_wire;
|
|
||||||
|
|
||||||
/* TODO: uint16_t wire_size */
|
|
||||||
char wire[]; /* string encoded wire JSON object */
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a deposit. The validity of the coin and signature
|
|
||||||
* have already been checked. The database must now check that
|
* have already been checked. The database must now check that
|
||||||
* the coin is not (double or over) spent, and execute the
|
* the coin is not (double or over) spent, and execute the
|
||||||
* transaction (record details, generate success or failure response).
|
* transaction (record details, generate success or failure response).
|
||||||
@ -82,4 +46,28 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection,
|
|||||||
const struct Deposit *deposit);
|
const struct Deposit *deposit);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a /withdraw/status.
|
||||||
|
*
|
||||||
|
* @param connection the MHD connection to handle
|
||||||
|
* @param reserve_pub public key of the reserve to check
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
TALER_MINT_db_execute_withdraw_status (struct MHD_Connection *connection,
|
||||||
|
const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a /withdraw/sign.
|
||||||
|
*
|
||||||
|
* @param connection the MHD connection to handle
|
||||||
|
* @param wsrd details about the withdraw request
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection,
|
||||||
|
const struct TALER_WithdrawRequest *wsrd);
|
||||||
|
|
||||||
|
|
||||||
#endif /* _NEURO_MINT_DB_H */
|
#endif /* _NEURO_MINT_DB_H */
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
#include <libpq-fe.h>
|
#include <libpq-fe.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include "mint.h"
|
#include "mint.h"
|
||||||
#include "mint_db.h"
|
|
||||||
#include "taler_signatures.h"
|
#include "taler_signatures.h"
|
||||||
#include "taler_rsa.h"
|
#include "taler_rsa.h"
|
||||||
#include "taler_json_lib.h"
|
#include "taler_json_lib.h"
|
||||||
@ -35,6 +34,7 @@
|
|||||||
#include "taler-mint-httpd_keys.h"
|
#include "taler-mint-httpd_keys.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mint key state. Never use directly, instead access via
|
* Mint key state. Never use directly, instead access via
|
||||||
* #TALER_MINT_key_state_acquire and #TALER_MINT_key_state_release.
|
* #TALER_MINT_key_state_acquire and #TALER_MINT_key_state_release.
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
#include <gnunet/gnunet_util_lib.h>
|
#include <gnunet/gnunet_util_lib.h>
|
||||||
#include <microhttpd.h>
|
#include <microhttpd.h>
|
||||||
#include "taler-mint-httpd.h"
|
#include "taler-mint-httpd.h"
|
||||||
|
#include "mint.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Snapshot of the (coin and signing)
|
* Snapshot of the (coin and signing)
|
||||||
|
@ -19,6 +19,9 @@
|
|||||||
* @author Florian Dold
|
* @author Florian Dold
|
||||||
* @author Benedikt Mueller
|
* @author Benedikt Mueller
|
||||||
* @author Christian Grothoff
|
* @author Christian Grothoff
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* - split properly into parsing, DB-ops and response generation
|
||||||
*/
|
*/
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include <gnunet/gnunet_util_lib.h>
|
#include <gnunet/gnunet_util_lib.h>
|
||||||
@ -70,6 +73,9 @@ sign_as_json (struct GNUNET_CRYPTO_EccSignaturePurpose *purpose)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME: document!
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
link_iter (void *cls,
|
link_iter (void *cls,
|
||||||
const struct LinkDataEnc *link_data_enc,
|
const struct LinkDataEnc *link_data_enc,
|
||||||
@ -246,9 +252,9 @@ check_confirm_signature (struct MHD_Connection *connection,
|
|||||||
*
|
*
|
||||||
* @param connection the connection to send error responses to
|
* @param connection the connection to send error responses to
|
||||||
* @param root the JSON object to extract the coin info from
|
* @param root the JSON object to extract the coin info from
|
||||||
* @return GNUNET_YES if coin public info in JSON was valid
|
* @return #GNUNET_YES if coin public info in JSON was valid
|
||||||
* GNUNET_NO otherwise
|
* #GNUNET_NO otherwise
|
||||||
* GNUNET_SYSERR on internal error
|
* #GNUNET_SYSERR on internal error
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
request_json_require_coin_public_info (struct MHD_Connection *connection,
|
request_json_require_coin_public_info (struct MHD_Connection *connection,
|
||||||
@ -298,9 +304,9 @@ request_json_require_coin_public_info (struct MHD_Connection *connection,
|
|||||||
* @param root the JSON object
|
* @param root the JSON object
|
||||||
* @param hash_context the hash context that will receive
|
* @param hash_context the hash context that will receive
|
||||||
* the coin public keys of the melted coin
|
* the coin public keys of the melted coin
|
||||||
* @return a GNUnet result code, GNUNET_OK on success,
|
* @return #GNUNET_OK on success,
|
||||||
* GNUNET_NO if an error message was generated,
|
* #GNUNET_NO if an error message was generated,
|
||||||
* GNUNET_SYSERR on internal errors (no response generated)
|
* #GNUNET_SYSERR on internal errors (no response generated)
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
refresh_accept_melts (struct MHD_Connection *connection,
|
refresh_accept_melts (struct MHD_Connection *connection,
|
||||||
|
@ -26,9 +26,14 @@
|
|||||||
* TODO:
|
* TODO:
|
||||||
* - when generating /deposit reply, do include signature of mint
|
* - when generating /deposit reply, do include signature of mint
|
||||||
* to say that we accepted it (check reply format)
|
* to say that we accepted it (check reply format)
|
||||||
|
* - when generating /withdraw/status reply, which signature do
|
||||||
|
* we use there? Might want to instead return *all* signatures on the
|
||||||
|
* existig withdraw operations, instead of Mint's signature
|
||||||
|
* (check reply format, adjust `struct Reserve` if needed)
|
||||||
*/
|
*/
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "taler-mint-httpd_responses.h"
|
#include "taler-mint-httpd_responses.h"
|
||||||
|
#include "taler_json_lib.h"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -229,4 +234,61 @@ TALER_MINT_reply_deposit_success (struct MHD_Connection *connection,
|
|||||||
"DEPOSIT_OK");
|
"DEPOSIT_OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send reserve status information to client.
|
||||||
|
*
|
||||||
|
* @param connection connection to the client
|
||||||
|
* @param reserve reserve status information to return
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
TALER_MINT_reply_withdraw_status_success (struct MHD_Connection *connection,
|
||||||
|
const struct Reserve *reserve)
|
||||||
|
{
|
||||||
|
json_t *json;
|
||||||
|
|
||||||
|
/* Convert the public information of a reserve (i.e.
|
||||||
|
excluding private key) to a JSON object. */
|
||||||
|
json = json_object ();
|
||||||
|
json_object_set_new (json,
|
||||||
|
"balance",
|
||||||
|
TALER_JSON_from_amount (TALER_amount_ntoh (reserve->balance)));
|
||||||
|
json_object_set_new (json,
|
||||||
|
"expiration",
|
||||||
|
TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (reserve->expiration)));
|
||||||
|
json_object_set_new (json,
|
||||||
|
"signature",
|
||||||
|
TALER_JSON_from_sig (&reserve->status_sig_purpose,
|
||||||
|
&reserve->status_sig));
|
||||||
|
|
||||||
|
return TALER_MINT_reply_json (connection,
|
||||||
|
json,
|
||||||
|
MHD_HTTP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send blinded coin information to client.
|
||||||
|
*
|
||||||
|
* @param connection connection to the client
|
||||||
|
* @param collectable blinded coin to return
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
TALER_MINT_reply_withdraw_sign_success (struct MHD_Connection *connection,
|
||||||
|
const struct CollectableBlindcoin *collectable)
|
||||||
|
{
|
||||||
|
json_t *root = json_object ();
|
||||||
|
|
||||||
|
json_object_set_new (root, "ev_sig",
|
||||||
|
TALER_JSON_from_data (&collectable->ev_sig,
|
||||||
|
sizeof (struct TALER_RSA_Signature)));
|
||||||
|
return TALER_MINT_reply_json (connection,
|
||||||
|
root,
|
||||||
|
MHD_HTTP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* end of taler-mint-httpd_responses.c */
|
/* end of taler-mint-httpd_responses.c */
|
||||||
|
@ -31,8 +31,8 @@
|
|||||||
#include <libpq-fe.h>
|
#include <libpq-fe.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include "taler-mint-httpd.h"
|
#include "taler-mint-httpd.h"
|
||||||
|
#include "taler-mint-httpd_db.h"
|
||||||
#include "taler-mint-httpd_mhd.h"
|
#include "taler-mint-httpd_mhd.h"
|
||||||
#include "mint_db.h"
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,5 +135,28 @@ TALER_MINT_reply_deposit_success (struct MHD_Connection *connection,
|
|||||||
const struct Deposit *deposit);
|
const struct Deposit *deposit);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send reserve status information to client.
|
||||||
|
*
|
||||||
|
* @param connection connection to the client
|
||||||
|
* @param reserve reserve status information to return
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
TALER_MINT_reply_withdraw_status_success (struct MHD_Connection *connection,
|
||||||
|
const struct Reserve *reserve);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send blinded coin information to client.
|
||||||
|
*
|
||||||
|
* @param connection connection to the client
|
||||||
|
* @param collectable blinded coin to return
|
||||||
|
* @return MHD result code
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
TALER_MINT_reply_withdraw_sign_success (struct MHD_Connection *connection,
|
||||||
|
const struct CollectableBlindcoin *collectable);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -19,6 +19,9 @@
|
|||||||
* @author Florian Dold
|
* @author Florian Dold
|
||||||
* @author Benedikt Mueller
|
* @author Benedikt Mueller
|
||||||
* @author Christian Grothoff
|
* @author Christian Grothoff
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* - support variable-size RSA keys
|
||||||
*/
|
*/
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include <gnunet/gnunet_util_lib.h>
|
#include <gnunet/gnunet_util_lib.h>
|
||||||
@ -33,62 +36,12 @@
|
|||||||
#include "taler_json_lib.h"
|
#include "taler_json_lib.h"
|
||||||
#include "taler-mint-httpd_parsing.h"
|
#include "taler-mint-httpd_parsing.h"
|
||||||
#include "taler-mint-httpd_keys.h"
|
#include "taler-mint-httpd_keys.h"
|
||||||
|
#include "taler-mint-httpd_db.h"
|
||||||
#include "taler-mint-httpd_mhd.h"
|
#include "taler-mint-httpd_mhd.h"
|
||||||
#include "taler-mint-httpd_withdraw.h"
|
#include "taler-mint-httpd_withdraw.h"
|
||||||
#include "taler-mint-httpd_responses.h"
|
#include "taler-mint-httpd_responses.h"
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a signature (with purpose) to
|
|
||||||
* a JSON object representation.
|
|
||||||
*
|
|
||||||
* @param purpose purpose of the signature
|
|
||||||
* @param signature the signature
|
|
||||||
* @return the JSON reporesentation of the signature with purpose
|
|
||||||
*/
|
|
||||||
static json_t *
|
|
||||||
sig_to_json (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
|
|
||||||
const struct GNUNET_CRYPTO_EddsaSignature *signature)
|
|
||||||
{
|
|
||||||
json_t *root;
|
|
||||||
json_t *el;
|
|
||||||
|
|
||||||
root = json_object ();
|
|
||||||
|
|
||||||
el = json_integer ((json_int_t) ntohl (purpose->size));
|
|
||||||
json_object_set_new (root, "size", el);
|
|
||||||
|
|
||||||
el = json_integer ((json_int_t) ntohl (purpose->purpose));
|
|
||||||
json_object_set_new (root, "purpose", el);
|
|
||||||
|
|
||||||
el = TALER_JSON_from_data (signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature));
|
|
||||||
json_object_set_new (root, "sig", el);
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sign a reserve's status with the current signing key.
|
|
||||||
*
|
|
||||||
* @param reserve the reserve to sign
|
|
||||||
* @param key_state the key state containing the current
|
|
||||||
* signing private key
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
sign_reserve (struct Reserve *reserve,
|
|
||||||
struct MintKeyState *key_state)
|
|
||||||
{
|
|
||||||
reserve->status_sign_pub = key_state->current_sign_key_issue.issue.signkey_pub;
|
|
||||||
reserve->status_sig_purpose.purpose = htonl (TALER_SIGNATURE_RESERVE_STATUS);
|
|
||||||
reserve->status_sig_purpose.size = htonl (sizeof (struct Reserve) -
|
|
||||||
offsetof (struct Reserve, status_sig_purpose));
|
|
||||||
GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv,
|
|
||||||
&reserve->status_sig_purpose,
|
|
||||||
&reserve->status_sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a "/withdraw/status" request
|
* Handle a "/withdraw/status" request
|
||||||
*
|
*
|
||||||
@ -107,100 +60,18 @@ TALER_MINT_handler_withdraw_status (struct RequestHandler *rh,
|
|||||||
size_t *upload_data_size)
|
size_t *upload_data_size)
|
||||||
{
|
{
|
||||||
struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
|
struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
|
||||||
PGconn *db_conn;
|
|
||||||
int res;
|
int res;
|
||||||
struct Reserve reserve;
|
|
||||||
struct MintKeyState *key_state;
|
|
||||||
int must_update = GNUNET_NO;
|
|
||||||
json_t *json;
|
|
||||||
|
|
||||||
res = TALER_MINT_mhd_request_arg_data (connection,
|
res = TALER_MINT_mhd_request_arg_data (connection,
|
||||||
"reserve_pub",
|
"reserve_pub",
|
||||||
&reserve_pub,
|
&reserve_pub,
|
||||||
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
|
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
|
||||||
if (GNUNET_SYSERR == res)
|
if (GNUNET_SYSERR == res)
|
||||||
{
|
return MHD_NO; /* internal error */
|
||||||
// FIXME: return 'internal error'
|
if (GNUNET_NO == res)
|
||||||
GNUNET_break (0);
|
return MHD_YES; /* parse error */
|
||||||
return MHD_NO;
|
return TALER_MINT_db_execute_withdraw_status (connection,
|
||||||
}
|
&reserve_pub);
|
||||||
if (GNUNET_OK != res)
|
|
||||||
return MHD_YES;
|
|
||||||
if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
|
|
||||||
{
|
|
||||||
// FIXME: return 'internal error'?
|
|
||||||
GNUNET_break (0);
|
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
res = TALER_MINT_DB_get_reserve (db_conn,
|
|
||||||
&reserve_pub,
|
|
||||||
&reserve);
|
|
||||||
if (GNUNET_SYSERR == res)
|
|
||||||
return TALER_MINT_reply_json_pack (connection,
|
|
||||||
MHD_HTTP_NOT_FOUND,
|
|
||||||
"{s:s}",
|
|
||||||
"error",
|
|
||||||
"Reserve not found");
|
|
||||||
if (GNUNET_OK != res)
|
|
||||||
{
|
|
||||||
// FIXME: return 'internal error'?
|
|
||||||
GNUNET_break (0);
|
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
key_state = TALER_MINT_key_state_acquire ();
|
|
||||||
if (0 != memcmp (&key_state->current_sign_key_issue.issue.signkey_pub,
|
|
||||||
&reserve.status_sign_pub,
|
|
||||||
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))
|
|
||||||
{
|
|
||||||
sign_reserve (&reserve, key_state);
|
|
||||||
must_update = GNUNET_YES;
|
|
||||||
}
|
|
||||||
if ((GNUNET_YES == must_update) &&
|
|
||||||
(GNUNET_OK != TALER_MINT_DB_update_reserve (db_conn, &reserve, !must_update)))
|
|
||||||
{
|
|
||||||
GNUNET_break (0);
|
|
||||||
return MHD_YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Convert the public information of a reserve (i.e.
|
|
||||||
excluding private key) to a JSON object. */
|
|
||||||
json = json_object ();
|
|
||||||
json_object_set_new (json,
|
|
||||||
"balance",
|
|
||||||
TALER_JSON_from_amount (TALER_amount_ntoh (reserve.balance)));
|
|
||||||
json_object_set_new (json,
|
|
||||||
"expiration",
|
|
||||||
TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (reserve.expiration)));
|
|
||||||
json_object_set_new (json,
|
|
||||||
"signature",
|
|
||||||
sig_to_json (&reserve.status_sig_purpose,
|
|
||||||
&reserve.status_sig));
|
|
||||||
|
|
||||||
return TALER_MINT_reply_json (connection,
|
|
||||||
json,
|
|
||||||
MHD_HTTP_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send positive, normal response for "/withdraw/sign".
|
|
||||||
*
|
|
||||||
* @param connection the connection to send the response to
|
|
||||||
* @param collectable the collectable blindcoin (i.e. the blindly signed coin)
|
|
||||||
* @return a MHD result code
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
helper_withdraw_sign_send_reply (struct MHD_Connection *connection,
|
|
||||||
const struct CollectableBlindcoin *collectable)
|
|
||||||
{
|
|
||||||
json_t *root = json_object ();
|
|
||||||
|
|
||||||
json_object_set_new (root, "ev_sig",
|
|
||||||
TALER_JSON_from_data (&collectable->ev_sig,
|
|
||||||
sizeof (struct TALER_RSA_Signature)));
|
|
||||||
return TALER_MINT_reply_json (connection,
|
|
||||||
root,
|
|
||||||
MHD_HTTP_OK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -223,180 +94,44 @@ TALER_MINT_handler_withdraw_sign (struct RequestHandler *rh,
|
|||||||
{
|
{
|
||||||
struct TALER_WithdrawRequest wsrd;
|
struct TALER_WithdrawRequest wsrd;
|
||||||
int res;
|
int res;
|
||||||
PGconn *db_conn;
|
|
||||||
struct Reserve reserve;
|
|
||||||
struct MintKeyState *key_state;
|
|
||||||
struct CollectableBlindcoin collectable;
|
|
||||||
struct TALER_MINT_DenomKeyIssuePriv *dki;
|
|
||||||
struct TALER_RSA_Signature ev_sig;
|
|
||||||
struct TALER_Amount amount_required;
|
|
||||||
|
|
||||||
memset (&wsrd,
|
|
||||||
0,
|
|
||||||
sizeof (struct TALER_WithdrawRequest));
|
|
||||||
res = TALER_MINT_mhd_request_arg_data (connection,
|
res = TALER_MINT_mhd_request_arg_data (connection,
|
||||||
"reserve_pub",
|
"reserve_pub",
|
||||||
&wsrd.reserve_pub,
|
&wsrd.reserve_pub,
|
||||||
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
|
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
|
||||||
if (GNUNET_SYSERR == res)
|
if (GNUNET_SYSERR == res)
|
||||||
{
|
return MHD_NO; /* internal error */
|
||||||
// FIXME: return 'internal error'?
|
if (GNUNET_NO == res)
|
||||||
GNUNET_break (0);
|
return MHD_YES; /* invalid request */
|
||||||
return MHD_NO;
|
|
||||||
}
|
/* FIXME: handle variable-size signing keys! */
|
||||||
if (GNUNET_OK != res)
|
|
||||||
return MHD_YES;
|
|
||||||
res = TALER_MINT_mhd_request_arg_data (connection,
|
res = TALER_MINT_mhd_request_arg_data (connection,
|
||||||
"denom_pub",
|
"denom_pub",
|
||||||
&wsrd.denomination_pub,
|
&wsrd.denomination_pub,
|
||||||
sizeof (struct TALER_RSA_PublicKeyBinaryEncoded));
|
sizeof (struct TALER_RSA_PublicKeyBinaryEncoded));
|
||||||
if (GNUNET_SYSERR == res)
|
if (GNUNET_SYSERR == res)
|
||||||
{
|
return MHD_NO; /* internal error */
|
||||||
// FIXME: return 'internal error'?
|
if (GNUNET_NO == res)
|
||||||
GNUNET_break (0);
|
return MHD_YES; /* invalid request */
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
if (GNUNET_OK != res)
|
|
||||||
return MHD_YES;
|
|
||||||
res = TALER_MINT_mhd_request_arg_data (connection,
|
res = TALER_MINT_mhd_request_arg_data (connection,
|
||||||
"coin_ev",
|
"coin_ev",
|
||||||
&wsrd.coin_envelope,
|
&wsrd.coin_envelope,
|
||||||
sizeof (struct TALER_RSA_Signature));
|
sizeof (struct TALER_RSA_Signature));
|
||||||
if (GNUNET_SYSERR == res)
|
if (GNUNET_SYSERR == res)
|
||||||
{
|
return MHD_NO; /* internal error */
|
||||||
// FIXME: return 'internal error'?
|
if (GNUNET_NO == res)
|
||||||
GNUNET_break (0);
|
return MHD_YES; /* invalid request */
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
if (GNUNET_OK != res)
|
|
||||||
return MHD_YES;
|
|
||||||
res = TALER_MINT_mhd_request_arg_data (connection,
|
res = TALER_MINT_mhd_request_arg_data (connection,
|
||||||
"reserve_sig",
|
"reserve_sig",
|
||||||
&wsrd.sig,
|
&wsrd.sig,
|
||||||
sizeof (struct GNUNET_CRYPTO_EddsaSignature));
|
sizeof (struct GNUNET_CRYPTO_EddsaSignature));
|
||||||
if (GNUNET_SYSERR == res)
|
if (GNUNET_SYSERR == res)
|
||||||
{
|
return MHD_NO; /* internal error */
|
||||||
// FIXME: return 'internal error'?
|
|
||||||
GNUNET_break (0);
|
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
if (GNUNET_OK != res)
|
|
||||||
return MHD_YES;
|
|
||||||
|
|
||||||
if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
|
|
||||||
{
|
|
||||||
// FIXME: return 'internal error'?
|
|
||||||
GNUNET_break (0);
|
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
res = TALER_MINT_DB_get_collectable_blindcoin (db_conn,
|
|
||||||
&wsrd.coin_envelope,
|
|
||||||
&collectable);
|
|
||||||
if (GNUNET_SYSERR == res)
|
|
||||||
{
|
|
||||||
// FIXME: return 'internal error'
|
|
||||||
GNUNET_break (0);
|
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Don't sign again if we have already signed the coin */
|
|
||||||
if (GNUNET_YES == res)
|
|
||||||
return helper_withdraw_sign_send_reply (connection,
|
|
||||||
&collectable);
|
|
||||||
GNUNET_assert (GNUNET_NO == res);
|
|
||||||
res = TALER_MINT_DB_get_reserve (db_conn,
|
|
||||||
&wsrd.reserve_pub,
|
|
||||||
&reserve);
|
|
||||||
if (GNUNET_SYSERR == res)
|
|
||||||
{
|
|
||||||
// FIXME: return 'internal error'
|
|
||||||
GNUNET_break (0);
|
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
if (GNUNET_NO == res)
|
if (GNUNET_NO == res)
|
||||||
return TALER_MINT_reply_json_pack (connection,
|
return MHD_YES; /* invalid request */
|
||||||
MHD_HTTP_NOT_FOUND,
|
|
||||||
"{s:s}",
|
|
||||||
"error",
|
|
||||||
"Reserve not found");
|
|
||||||
|
|
||||||
// fill out all the missing info in the request before
|
return TALER_MINT_db_execute_withdraw_sign (connection,
|
||||||
// we can check the signature on the request
|
&wsrd);
|
||||||
|
|
||||||
wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WITHDRAW);
|
|
||||||
wsrd.purpose.size = htonl (sizeof (struct TALER_WithdrawRequest) -
|
|
||||||
offsetof (struct TALER_WithdrawRequest, purpose));
|
|
||||||
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WITHDRAW,
|
|
||||||
&wsrd.purpose,
|
|
||||||
&wsrd.sig,
|
|
||||||
&wsrd.reserve_pub))
|
|
||||||
return TALER_MINT_reply_json_pack (connection,
|
|
||||||
MHD_HTTP_UNAUTHORIZED,
|
|
||||||
"{s:s}",
|
|
||||||
"error", "Invalid Signature");
|
|
||||||
|
|
||||||
key_state = TALER_MINT_key_state_acquire ();
|
|
||||||
dki = TALER_MINT_get_denom_key (key_state,
|
|
||||||
&wsrd.denomination_pub);
|
|
||||||
TALER_MINT_key_state_release (key_state);
|
|
||||||
if (NULL == dki)
|
|
||||||
return TALER_MINT_reply_json_pack (connection,
|
|
||||||
MHD_HTTP_NOT_FOUND,
|
|
||||||
"{s:s}",
|
|
||||||
"error",
|
|
||||||
"Denomination not found");
|
|
||||||
|
|
||||||
amount_required = TALER_amount_ntoh (dki->issue.value);
|
|
||||||
amount_required = TALER_amount_add (amount_required,
|
|
||||||
TALER_amount_ntoh (dki->issue.fee_withdraw));
|
|
||||||
|
|
||||||
if (0 < TALER_amount_cmp (amount_required,
|
|
||||||
TALER_amount_ntoh (reserve.balance)))
|
|
||||||
return TALER_MINT_reply_json_pack (connection,
|
|
||||||
MHD_HTTP_PAYMENT_REQUIRED,
|
|
||||||
"{s:s}",
|
|
||||||
"error",
|
|
||||||
"Insufficient funds");
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
TALER_RSA_sign (dki->denom_priv,
|
|
||||||
&wsrd.coin_envelope,
|
|
||||||
sizeof (struct TALER_RSA_BlindedSignaturePurpose),
|
|
||||||
&ev_sig))
|
|
||||||
{
|
|
||||||
// FIXME: return 'internal error'
|
|
||||||
GNUNET_break (0);
|
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
reserve.balance = TALER_amount_hton (TALER_amount_subtract (TALER_amount_ntoh (reserve.balance),
|
|
||||||
amount_required));
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
TALER_MINT_DB_update_reserve (db_conn,
|
|
||||||
&reserve,
|
|
||||||
GNUNET_YES))
|
|
||||||
{
|
|
||||||
// FIXME: return 'internal error'
|
|
||||||
GNUNET_break (0);
|
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
collectable.ev = wsrd.coin_envelope;
|
|
||||||
collectable.ev_sig = ev_sig;
|
|
||||||
collectable.reserve_pub = wsrd.reserve_pub;
|
|
||||||
collectable.reserve_sig = wsrd.sig;
|
|
||||||
if (GNUNET_OK !=
|
|
||||||
TALER_MINT_DB_insert_collectable_blindcoin (db_conn,
|
|
||||||
&collectable))
|
|
||||||
{
|
|
||||||
// FIXME: return 'internal error'
|
|
||||||
GNUNET_break (0);
|
|
||||||
return GNUNET_NO;;
|
|
||||||
}
|
|
||||||
return helper_withdraw_sign_send_reply (connection,
|
|
||||||
&collectable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* end of taler-mint-httpd_withdraw.c */
|
/* end of taler-mint-httpd_withdraw.c */
|
||||||
|
@ -90,6 +90,34 @@ TALER_JSON_from_abs (struct GNUNET_TIME_Absolute stamp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a signature (with purpose) to a JSON object representation.
|
||||||
|
*
|
||||||
|
* @param purpose purpose of the signature
|
||||||
|
* @param signature the signature
|
||||||
|
* @return the JSON reporesentation of the signature with purpose
|
||||||
|
*/
|
||||||
|
json_t *
|
||||||
|
TALER_JSON_from_sig (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
|
||||||
|
const struct GNUNET_CRYPTO_EddsaSignature *signature)
|
||||||
|
{
|
||||||
|
json_t *root;
|
||||||
|
json_t *el;
|
||||||
|
|
||||||
|
root = json_object ();
|
||||||
|
|
||||||
|
el = json_integer ((json_int_t) ntohl (purpose->size));
|
||||||
|
json_object_set_new (root, "size", el);
|
||||||
|
|
||||||
|
el = json_integer ((json_int_t) ntohl (purpose->purpose));
|
||||||
|
json_object_set_new (root, "purpose", el);
|
||||||
|
|
||||||
|
el = TALER_JSON_from_data (signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature));
|
||||||
|
json_object_set_new (root, "sig", el);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert binary data to a JSON string
|
* Convert binary data to a JSON string
|
||||||
|
Loading…
Reference in New Issue
Block a user