From 39db1ae5dbbd12c0f452cfa56119d9a95f9b1b22 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 20 Jun 2017 13:40:17 +0200 Subject: [PATCH] address #5010 for /refresh/link --- .../taler-exchange-httpd_refresh_link.c | 172 ++++++++----- src/exchangedb/plugin_exchangedb_postgres.c | 229 +++++++++++------- src/exchangedb/test_exchangedb.c | 11 +- src/include/taler_exchangedb_plugin.h | 14 +- 4 files changed, 264 insertions(+), 162 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_refresh_link.c b/src/exchange/taler-exchange-httpd_refresh_link.c index c85431f04..0be699840 100644 --- a/src/exchange/taler-exchange-httpd_refresh_link.c +++ b/src/exchange/taler-exchange-httpd_refresh_link.c @@ -55,6 +55,11 @@ struct TEH_RESPONSE_LinkSessionInfo struct HTD_Context { + /** + * Public key of the coin that we are tracing. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + /** * Session link data we collect. */ @@ -100,18 +105,18 @@ reply_refresh_link_success (struct MHD_Connection *connection, unsigned int num_sessions, const struct TEH_RESPONSE_LinkSessionInfo *sessions) { - json_t *root; json_t *mlist; int res; - unsigned int i; mlist = json_array (); - for (i=0;inext) + for (const struct TALER_EXCHANGEDB_LinkDataList *pos = sessions[i].ldl; + NULL != pos; + pos = pos->next) { json_t *obj; @@ -163,24 +168,24 @@ handle_transfer_data (void *cls, struct HTD_Context *ctx = cls; struct TALER_EXCHANGEDB_LinkDataList *ldl; struct TEH_RESPONSE_LinkSessionInfo *lsi; + enum GNUNET_DB_QueryStatus qs; - if (GNUNET_OK != ctx->status) + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ctx->status) return; - ldl = TEH_plugin->get_link_data_list (TEH_plugin->cls, - ctx->session, - session_hash); - if (NULL == ldl) + ldl = NULL; + qs = TEH_plugin->get_link_data_list (TEH_plugin->cls, + ctx->session, + session_hash, + &ldl); + if (qs <= 0) { - ctx->status = GNUNET_NO; - if (MHD_NO == - TEH_RESPONSE_reply_json_pack (ctx->connection, - MHD_HTTP_NOT_FOUND, - "{s:s}", - "error", - "link data not found (link)")) - ctx->status = GNUNET_SYSERR; + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + ctx->status = GNUNET_DB_STATUS_HARD_ERROR; + else + ctx->status = qs; return; } + GNUNET_assert (NULL != ldl); GNUNET_array_grow (ctx->sessions, ctx->num_sessions, ctx->num_sessions + 1); @@ -190,62 +195,81 @@ handle_transfer_data (void *cls, } +/** + * Free session data kept in @a ctx + * + * @param ctx context to clean up + */ +static void +purge_context (struct HTD_Context *ctx) +{ + for (unsigned int i=0;inum_sessions;i++) + TEH_plugin->free_link_data_list (TEH_plugin->cls, + ctx->sessions[i].ldl); + GNUNET_free_non_null (ctx->sessions); + ctx->sessions = NULL; + ctx->num_sessions = 0; +} + + /** * Execute a "/refresh/link". Returns the linkage information that * will allow the owner of a coin to follow the refresh trail to * the refreshed coin. * - * @param connection the MHD connection to handle - * @param coin_pub public key of the coin to link - * @return MHD result code + * If it returns a non-error code, the transaction logic MUST + * NOT queue a MHD response. IF it returns an hard error, the + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF + * it returns the soft error code, the function MAY be called again to + * retry and MUST not queue a MHD response. + * + * @param cls closure + * @param connection MHD request which triggered the transaction + * @param session database session to use + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status */ -static int -execute_refresh_link (struct MHD_Connection *connection, - const struct TALER_CoinSpendPublicKeyP *coin_pub) +static enum GNUNET_DB_QueryStatus +refresh_link_transaction (void *cls, + struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + int *mhd_ret) { - struct HTD_Context ctx; - int res; - unsigned int i; + struct HTD_Context *ctx = cls; + enum GNUNET_DB_QueryStatus qs; - if (NULL == (ctx.session = TEH_plugin->get_session (TEH_plugin->cls))) + ctx->session = session; + ctx->status = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + qs = TEH_plugin->get_transfer (TEH_plugin->cls, + session, + &ctx->coin_pub, + &handle_transfer_data, + ctx); + ctx->session = NULL; + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { - GNUNET_break (0); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_DB_SETUP_FAILED); + *mhd_ret = TEH_RESPONSE_reply_arg_unknown (connection, + TALER_EC_REFRESH_LINK_COIN_UNKNOWN, + "coin_pub"); + return GNUNET_DB_STATUS_HARD_ERROR; } - ctx.connection = connection; - ctx.num_sessions = 0; - ctx.sessions = NULL; - ctx.status = GNUNET_OK; - res = TEH_plugin->get_transfer (TEH_plugin->cls, - ctx.session, - coin_pub, - &handle_transfer_data, - &ctx); - if (GNUNET_SYSERR == ctx.status) + if (0 < qs) { - res = MHD_NO; - goto cleanup; + qs = ctx->status; + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + *mhd_ret = TEH_RESPONSE_reply_json_pack (ctx->connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", + "link data not found (link)"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + return qs; } - if (GNUNET_NO == ctx.status) - { - res = MHD_YES; - goto cleanup; - } - GNUNET_assert (GNUNET_OK == ctx.status); - if (0 == ctx.num_sessions) - return TEH_RESPONSE_reply_arg_unknown (connection, - TALER_EC_REFRESH_LINK_COIN_UNKNOWN, - "coin_pub"); - res = reply_refresh_link_success (connection, - ctx.num_sessions, - ctx.sessions); - cleanup: - for (i=0;ifree_link_data_list (TEH_plugin->cls, - ctx.sessions[i].ldl); - GNUNET_free_non_null (ctx.sessions); - return res; + purge_context (ctx); + return qs; } @@ -267,19 +291,35 @@ TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh, const char *upload_data, size_t *upload_data_size) { - struct TALER_CoinSpendPublicKeyP coin_pub; + int mhd_ret; int res; + struct HTD_Context ctx; + memset (&ctx, + 0, + sizeof (ctx)); res = TEH_PARSE_mhd_request_arg_data (connection, "coin_pub", - &coin_pub, + &ctx.coin_pub, sizeof (struct TALER_CoinSpendPublicKeyP)); if (GNUNET_SYSERR == res) return MHD_NO; if (GNUNET_OK != res) return MHD_YES; - return execute_refresh_link (connection, - &coin_pub); + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + &mhd_ret, + &refresh_link_transaction, + &ctx)) + { + purge_context (&ctx); + return mhd_ret; + } + mhd_ret = reply_refresh_link_success (connection, + ctx.num_sessions, + ctx.sessions); + purge_context (&ctx); + return mhd_ret; } diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index b029db587..b11eec16d 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -3895,45 +3895,38 @@ postgres_insert_refresh_out (void *cls, /** - * Obtain the link data of a coin, that is the encrypted link - * information, the denomination keys and the signatures. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param session_hash refresh session to get linkage data for - * @return all known link data for the session + * Closure for #add_ldl(). */ -static struct TALER_EXCHANGEDB_LinkDataList * -postgres_get_link_data_list (void *cls, - struct TALER_EXCHANGEDB_Session *session, - const struct GNUNET_HashCode *session_hash) +struct LinkDataContext { + /** + * List we are building. + */ struct TALER_EXCHANGEDB_LinkDataList *ldl; - int nrows; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (session_hash), - GNUNET_PQ_query_param_end - }; - PGresult *result; - result = GNUNET_PQ_exec_prepared (session->conn, - "get_link", - params); - ldl = NULL; - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result, session->conn); - PQclear (result); - return NULL; - } - nrows = PQntuples (result); - if (0 == nrows) - { - PQclear (result); - return NULL; - } + /** + * Status, set to #GNUNET_SYSERR on errors, + */ + int status; +}; - for (int i = nrows-1; i >= 0; i--) + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct LinkDataContext *` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +add_ldl (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct LinkDataContext *ldc = cls; + + for (int i = num_results - 1; i >= 0; i--) { struct GNUNET_CRYPTO_RsaPublicKey *denom_pub; struct GNUNET_CRYPTO_RsaSignature *sig; @@ -3954,71 +3947,98 @@ postgres_get_link_data_list (void *cls, rs, i)) { - PQclear (result); GNUNET_break (0); common_free_link_data_list (cls, - ldl); + ldc->ldl); + ldc->ldl = NULL; GNUNET_free (pos); - return NULL; + ldc->status = GNUNET_SYSERR; + return; } } - pos->next = ldl; + pos->next = ldc->ldl; pos->denom_pub.rsa_public_key = denom_pub; pos->ev_sig.rsa_signature = sig; - ldl = pos; + ldc->ldl = pos; } - PQclear (result); - return ldl; } /** - * Obtain shared secret and transfer public key from the public key of - * the coin. This information and the link information returned by - * #postgres_get_link_data_list() enable the owner of an old coin to - * determine the private keys of the new coins after the melt. + * Obtain the link data of a coin, that is the encrypted link + * information, the denomination keys and the signatures. * * @param cls the `struct PostgresClosure` with the plugin-specific state * @param session database connection - * @param coin_pub public key of the coin - * @param tdc function to call for each session the coin was melted into - * @param tdc_cls closure for @a tdc - * @return #GNUNET_OK on success, - * #GNUNET_NO on failure (not found) - * #GNUNET_SYSERR on internal failure (database issue) + * @param session_hash refresh session to get linkage data for + * @param[out] ldlp set to all known link data for the session + * @return transaction status code */ -static int -postgres_get_transfer (void *cls, - struct TALER_EXCHANGEDB_Session *session, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - TALER_EXCHANGEDB_TransferDataCallback tdc, - void *tdc_cls) +static enum GNUNET_DB_QueryStatus +postgres_get_link_data_list (void *cls, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + struct TALER_EXCHANGEDB_LinkDataList **ldlp) { + struct LinkDataContext ldc; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_auto_from_type (session_hash), GNUNET_PQ_query_param_end }; - PGresult *result; - int nrows; + enum GNUNET_DB_QueryStatus qs; - result = GNUNET_PQ_exec_prepared (session->conn, - "get_transfer", - params); - if (PGRES_TUPLES_OK != - PQresultStatus (result)) - { - BREAK_DB_ERR (result, session->conn); - PQclear (result); - return GNUNET_SYSERR; - } - nrows = PQntuples (result); - if (0 == nrows) - { - /* no matches found */ - PQclear (result); - return GNUNET_NO; - } - for (int i=0;iconn, + "get_link", + params, + &add_ldl, + &ldc); + *ldlp = ldc.ldl; + if (GNUNET_OK != ldc.status) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} + + +/** + * Closure for #add_link(). + */ +struct AddLinkContext +{ + /** + * Function to call on each result. + */ + TALER_EXCHANGEDB_TransferDataCallback tdc; + + /** + * Closure for @e tdc. + */ + void *tdc_cls; + + /** + * Status code, set to #GNUNET_SYSERR on errors. + */ + int status; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct AddLinkContext *` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +add_link (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct AddLinkContext *alc = cls; + + for (unsigned int i=0;istatus = GNUNET_SYSERR; + return; } - tdc (tdc_cls, - &session_hash, - &transfer_pub); + alc->tdc (alc->tdc_cls, + &session_hash, + &transfer_pub); } - PQclear (result); - return GNUNET_OK; +} + + +/** + * Obtain shared secret and transfer public key from the public key of + * the coin. This information and the link information returned by + * #postgres_get_link_data_list() enable the owner of an old coin to + * determine the private keys of the new coins after the melt. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub public key of the coin + * @param tdc function to call for each session the coin was melted into + * @param tdc_cls closure for @a tdc + * @return statement execution status + */ +static enum GNUNET_DB_QueryStatus +postgres_get_transfer (void *cls, + struct TALER_EXCHANGEDB_Session *session, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + TALER_EXCHANGEDB_TransferDataCallback tdc, + void *tdc_cls) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_end + }; + struct AddLinkContext al_ctx; + enum GNUNET_DB_QueryStatus qs; + + al_ctx.tdc = tdc; + al_ctx.tdc_cls = tdc_cls; + al_ctx.status = GNUNET_OK; + qs = GNUNET_PQ_eval_prepared_multi_select (session->conn, + "get_transfer", + params, + &add_link, + &al_ctx); + if (GNUNET_OK != al_ctx.status) + qs = GNUNET_DB_STATUS_HARD_ERROR; + return qs; } diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index 9a58a38ec..df730a07e 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -530,6 +530,7 @@ test_melting (struct TALER_EXCHANGEDB_Session *session) struct TALER_DenominationSignature ev_sigs[MELT_NEW_COINS]; unsigned int cnt; int ret; + enum GNUNET_DB_QueryStatus qs; ret = GNUNET_SYSERR; memset (ev_sigs, 0, sizeof (ev_sigs)); @@ -695,9 +696,11 @@ test_melting (struct TALER_EXCHANGEDB_Session *session) GNUNET_CRYPTO_rsa_signature_free (test_sig.rsa_signature); } - ldl = plugin->get_link_data_list (plugin->cls, - session, - &session_hash); + qs = plugin->get_link_data_list (plugin->cls, + session, + &session_hash, + &ldl); + FAILIF (0 >= qs); FAILIF (NULL == ldl); for (ldlp = ldl; NULL != ldlp; ldlp = ldlp->next) { @@ -742,7 +745,7 @@ test_melting (struct TALER_EXCHANGEDB_Session *session) int ok; ok = GNUNET_NO; - FAILIF (GNUNET_OK != + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != plugin->get_transfer (plugin->cls, session, &meltp->coin.coin_pub, diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 23e80c077..0406c8d19 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -1652,12 +1652,14 @@ struct TALER_EXCHANGEDB_Plugin * @param cls the @e cls of this struct with the plugin-specific state * @param session database connection * @param session_hash session to get linkage data for - * @return all known link data for the session + * @param[out] ldldp set to all known link data for the session + * @return status of the transaction */ - struct TALER_EXCHANGEDB_LinkDataList * + enum GNUNET_DB_QueryStatus (*get_link_data_list) (void *cls, struct TALER_EXCHANGEDB_Session *session, - const struct GNUNET_HashCode *session_hash); + const struct GNUNET_HashCode *session_hash, + struct TALER_EXCHANGEDB_LinkDataList **ldlp); /** @@ -1682,11 +1684,9 @@ struct TALER_EXCHANGEDB_Plugin * @param coin_pub public key of the coin * @param tdc function to call for each session the coin was melted into * @param tdc_cls closure for @a tdc - * @return #GNUNET_OK on success, - * #GNUNET_NO on failure (not found) - * #GNUNET_SYSERR on internal failure (database issue) + * @return statement execution status */ - int + enum GNUNET_DB_QueryStatus (*get_transfer) (void *cls, struct TALER_EXCHANGEDB_Session *session, const struct TALER_CoinSpendPublicKeyP *coin_pub,