summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/Makefile.am3
-rw-r--r--src/backend/taler-merchant-httpd.c7
-rw-r--r--src/backend/taler-merchant-httpd.h2
-rw-r--r--src/backend/taler-merchant-httpd_contract.c10
-rw-r--r--src/backend/taler-merchant-httpd_map.c246
-rw-r--r--src/backend/taler-merchant-httpd_map.h66
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c142
-rw-r--r--src/backenddb/test_merchantdb.c19
-rw-r--r--src/include/taler_merchant_service.h54
-rw-r--r--src/include/taler_merchantdb_plugin.h28
-rw-r--r--src/lib/Makefile.am3
-rw-r--r--src/lib/merchant_api_contract.c64
-rw-r--r--src/lib/merchant_api_history.c3
-rw-r--r--src/lib/merchant_api_map.c230
-rw-r--r--src/lib/merchant_api_pay.c1
-rw-r--r--src/lib/merchant_api_track_transaction.c2
-rw-r--r--src/lib/merchant_api_track_transfer.c2
-rw-r--r--src/lib/test_merchant_api.c230
-rw-r--r--src/merchant-tools/Makefile.am16
-rwxr-xr-xsrc/merchant-tools/taler-merchant-dbinit228
-rw-r--r--src/merchant-tools/taler-merchant-dbinit.c115
21 files changed, 1374 insertions, 97 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index a1aaf540..f7fc4c88 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -23,7 +23,8 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_pay.c taler-merchant-httpd_pay.h \
taler-merchant-httpd_history.c taler-merchant-httpd_history.h \
taler-merchant-httpd_track-transaction.c taler-merchant-httpd_track-transaction.h \
- taler-merchant-httpd_track-transfer.c taler-merchant-httpd_track-transfer.h
+ taler-merchant-httpd_track-transfer.c taler-merchant-httpd_track-transfer.h \
+ taler-merchant-httpd_map.c taler-merchant-httpd_map.h
taler_merchant_httpd_LDADD = \
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index c5ebe21e..95ac4b32 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -40,6 +40,7 @@
#include "taler-merchant-httpd_track-transaction.h"
#include "taler-merchant-httpd_track-transfer.h"
#include "taler-merchant-httpd_history.h"
+#include "taler-merchant-httpd_map.h"
/**
* Backlog for listen operation on unix-domain sockets.
@@ -191,6 +192,12 @@ url_handler (void *cls,
{ "/history", MHD_HTTP_METHOD_GET, "text/plain",
"Only GET is allowed", 0,
&MH_handler_history, MHD_HTTP_OK},
+ { "/map/in", MHD_HTTP_METHOD_POST, NULL,
+ "Only POST is allowed", 0,
+ &MH_handler_map_in, MHD_HTTP_OK},
+ { "/map/out", MHD_HTTP_METHOD_GET, "text/plain",
+ "Only GET is allowed", 0,
+ &MH_handler_map_out, MHD_HTTP_OK},
{NULL, NULL, NULL, NULL, 0, 0 }
};
static struct TMH_RequestHandler h404 =
diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h
index 9d45ab67..f5adeac9 100644
--- a/src/backend/taler-merchant-httpd.h
+++ b/src/backend/taler-merchant-httpd.h
@@ -190,7 +190,7 @@ typedef void
* Each MHD response handler that sets the "connection_cls" to a
* non-NULL value must use a struct that has this struct as its first
* member. This struct contains a single callback, which will be
- * invoked to clean up the memory when the contection is completed.
+ * invoked to clean up the memory when the connection is completed.
*/
struct TM_HandlerContext
{
diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c
index f47e5ba9..e9ebda49 100644
--- a/src/backend/taler-merchant-httpd_contract.c
+++ b/src/backend/taler-merchant-httpd_contract.c
@@ -81,7 +81,7 @@ check_products (json_t *products)
/**
- * Information we keep for an individual calls
+ * Information we keep for individual calls
* to requests that parse JSON, but keep no other state.
*/
struct TMH_JsonParseContext
@@ -169,7 +169,7 @@ MH_handler_contract (struct TMH_RequestHandler *rh,
GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp),
GNUNET_JSON_spec_absolute_time ("refund_deadline", &refund_deadline),
GNUNET_JSON_spec_absolute_time ("pay_deadline", &pay_deadline),
- GNUNET_JSON_spec_end()
+ GNUNET_JSON_spec_end ()
};
if (NULL == *connection_cls)
@@ -215,9 +215,9 @@ MH_handler_contract (struct TMH_RequestHandler *rh,
if (GNUNET_SYSERR == res)
{
json_decref (root);
- return TMH_RESPONSE_reply_arg_invalid (connection,
- TALER_EC_PARAMETER_MALFORMED,
- "contract");
+ return TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_NONE,
+ "Impossible to parse contract");
}
/* check contract is well-formed */
if (GNUNET_OK != check_products (products))
diff --git a/src/backend/taler-merchant-httpd_map.c b/src/backend/taler-merchant-httpd_map.c
new file mode 100644
index 00000000..a41968a0
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_map.c
@@ -0,0 +1,246 @@
+/*
+ This file is part of TALER
+ (C) 2014, 2015, 2016 INRIA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU 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 backend/taler-merchant-httpd_map.c
+ * @brief Provides the frontend the mean to store plain contracts in database
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_responses.h"
+#include "taler-merchant-httpd_parsing.h"
+
+/**
+ * Information we keep for individual calls
+ * to requests that parse JSON, but keep no other state.
+ */
+struct TMH_JsonParseContext
+{
+
+ /**
+ * This field MUST be first.
+ * FIXME: Explain why!
+ */
+ struct TM_HandlerContext hc;
+
+ /**
+ * Placeholder for #TMH_PARSE_post_json() to keep its internal state.
+ */
+ void *json_parse_context;
+};
+
+/**
+ * Custom cleanup routine for a `struct TMH_JsonParseContext`.
+ *
+ * @param hc the `struct TMH_JsonParseContext` to clean up.
+ */
+static void
+json_parse_cleanup (struct TM_HandlerContext *hc)
+{
+ struct TMH_JsonParseContext *jpc = (struct TMH_JsonParseContext *) hc;
+
+ TMH_PARSE_post_cleanup_callback (jpc->json_parse_context);
+ GNUNET_free (jpc);
+}
+
+
+/**
+ * Manage a /map/in request. Store in db a plain text contract
+ * and its hashcode.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+MH_handler_map_in (struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+
+ int res;
+ json_t *root;
+ json_t *contract;
+ struct GNUNET_HashCode h_contract;
+ struct GNUNET_HashCode tmp;
+ struct TMH_JsonParseContext *ctx;
+
+/* Fetch body */
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("contract", &contract),
+ GNUNET_JSON_spec_fixed_auto ("h_contract", &h_contract),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == *connection_cls)
+ {
+ ctx = GNUNET_new (struct TMH_JsonParseContext);
+ ctx->hc.cc = &json_parse_cleanup;
+ *connection_cls = ctx;
+ }
+ else
+ {
+ ctx = *connection_cls;
+ }
+
+ res = TMH_PARSE_post_json (connection,
+ &ctx->json_parse_context,
+ upload_data,
+ upload_data_size,
+ &root);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO;
+ /* the POST's body has to be further fetched */
+ if ((GNUNET_NO == res) || (NULL == root))
+ return MHD_YES;
+
+ res = TMH_PARSE_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_NO == res)
+ {
+ json_decref (root);
+ return MHD_YES;
+ }
+ if (GNUNET_SYSERR == res)
+ {
+ json_decref (root);
+ return TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_NONE,
+ "Impossible to parse JSON");
+ }
+
+ /* Sanity checks */
+ if (GNUNET_SYSERR ==
+ TALER_JSON_hash (contract,
+ &tmp))
+ return TMH_RESPONSE_reply_invalid_json (connection);
+
+ /**
+ * Check hashes match. This check does NOT detect invalid
+ * contracts though.
+ */
+
+ if (0 != memcmp (&tmp,
+ &h_contract,
+ sizeof (struct GNUNET_HashCode)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "'h_contract' does not match 'contract'\n");
+ return TMH_RESPONSE_reply_json_pack
+ (connection,
+ MHD_HTTP_UNPROCESSABLE_ENTITY,
+ "{s:I, s:s}",
+ "code", (json_int_t) TALER_EC_MAP_IN_UNMATCHED_HASH,
+ "error", "field 'h_contract' is not hash of 'contract'");
+ }
+
+ /* Store body */
+ if (GNUNET_OK != db->store_map (db->cls,
+ &h_contract,
+ contract))
+ {
+ return TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_MAP_IN_STORE_DB_ERROR,
+ "Could not store data into db");
+ }
+
+ /* Test */
+ json_t *hello;
+
+ hello = json_pack ("{s:s}", "ok", "computer");
+ return TMH_RESPONSE_reply_json (connection,
+ hello,
+ MHD_HTTP_OK);
+
+
+}
+
+
+/**
+ * Manage a /map/out request. Query the db and returns a plain
+ * text contract associated with the hashcode given as input
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+MH_handler_map_out (struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ const char *h_contract_enc;
+ struct GNUNET_HashCode h_contract;
+ int res;
+ json_t *contract;
+
+ h_contract_enc = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "h_contract");
+ if (NULL == h_contract_enc)
+ return TMH_RESPONSE_reply_arg_missing (connection,
+ TALER_EC_PARAMETER_MISSING,
+ "h_contract");
+
+ if (GNUNET_OK != GNUNET_STRINGS_string_to_data (h_contract_enc,
+ strlen (h_contract_enc),
+ &h_contract,
+ sizeof (h_contract)))
+ {
+ GNUNET_break_op (0);
+ return TMH_RESPONSE_reply_bad_request (connection,
+ TALER_EC_PARAMETER_MALFORMED,
+ "Could not decode hashcode into binary form");
+ }
+
+ res = db->find_contract (db->cls,
+ &contract,
+ &h_contract);
+
+ if (GNUNET_SYSERR == res)
+ {
+ return TMH_RESPONSE_reply_internal_error (connection,
+ TALER_EC_MAP_OUT_GET_FROM_DB_ERROR,
+ "Could not retrieve data from db");
+ }
+
+ if (GNUNET_NO == res)
+ {
+ return TMH_RESPONSE_reply_not_found (connection,
+ TALER_EC_MAP_OUT_CONTRACT_UNKNOWN,
+ "contract");
+ }
+
+ return TMH_RESPONSE_reply_json (connection,
+ contract,
+ MHD_HTTP_OK);
+}
+/* end of taler-merchant-httpd_history.c */
diff --git a/src/backend/taler-merchant-httpd_map.h b/src/backend/taler-merchant-httpd_map.h
new file mode 100644
index 00000000..de9150a3
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_map.h
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ (C) 2014, 2015, 2016 INRIA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU 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 backend/taler-merchant-httpd_contract.c
+ * @brief HTTP serving layer mainly intended to communicate with the frontend
+ * @author Marcello Stanisci
+ */
+
+#ifndef TALER_MERCHANT_HTTPD_MAP_H
+#define TALER_MERCHANT_HTTPD_MAP_H
+#include <microhttpd.h>
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Manage a /map/in request. Store in db a plain text contract
+ * and its hashcode.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+MH_handler_map_in (struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+/**
+ * Manage a /map/out request. Query the db and returns a plain
+ * text contract associated with the hashcode given as input
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+MH_handler_map_out (struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+/* end of taler-merchant-httpd_history.c */
+#endif
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
index c6d8ab95..8fbed465 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -41,6 +41,14 @@ struct PostgresClosure
};
+/**
+ * Extract error code.
+ *
+ * @param res postgres result object with error details
+ */
+#define EXTRACT_DB_ERROR(res) \
+ PQresultErrorField(res, PG_DIAG_SQLSTATE)
+
/**
* Log error from PostGres.
@@ -137,6 +145,7 @@ postgres_drop_tables (void *cls)
PG_EXEC_INDEX (pg, "DROP TABLE merchant_deposits;");
PG_EXEC_INDEX (pg, "DROP TABLE merchant_transactions;");
PG_EXEC_INDEX (pg, "DROP TABLE merchant_proofs;");
+ PG_EXEC_INDEX (pg, "DROP TABLE merchant_contract_maps;");
return GNUNET_OK;
}
@@ -154,6 +163,13 @@ postgres_initialize (void *cls)
/* Setup tables */
PG_EXEC (pg,
+ "CREATE TABLE IF NOT EXISTS merchant_contract_maps ("
+ "h_contract BYTEA NOT NULL CHECK (LENGTH(h_contract)=64)"
+ ",plain_contract BYTEA NOT NULL"
+ ",PRIMARY KEY (h_contract)"
+ ");");
+
+ PG_EXEC (pg,
"CREATE TABLE IF NOT EXISTS merchant_transactions ("
" transaction_id INT8"
",exchange_uri VARCHAR NOT NULL"
@@ -262,6 +278,22 @@ postgres_initialize (void *cls)
5);
PG_PREPARE (pg,
+ "insert_map",
+ "INSERT INTO merchant_contract_maps"
+ "(h_contract"
+ ",plain_contract)"
+ " VALUES "
+ "($1, $2)",
+ 2);
+
+ PG_PREPARE (pg,
+ "find_contract",
+ "SELECT plain_contract FROM merchant_contract_maps"
+ " WHERE"
+ " h_contract=$1",
+ 1);
+
+ PG_PREPARE (pg,
"find_transactions_by_date",
"SELECT"
" transaction_id"
@@ -365,6 +397,114 @@ postgres_initialize (void *cls)
return GNUNET_OK;
}
+/**
+ * Retrieve plain contract given its hashcode
+ *
+ * @param cls closure
+ * @param h_contract hashcode of the contract to retrieve
+ * @param contract where to store the retrieved contract
+ * @return #GNUNET_OK on success, #GNUNET_NO if no contract is
+ * found, #GNUNET_SYSERR upon error
+ */
+static int
+postgres_find_contract (void *cls,
+ json_t **contract,
+ struct GNUNET_HashCode *h_contract)
+{
+ struct PostgresClosure *pg = cls;
+ PGresult *result;
+ unsigned int i;
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_contract),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (pg->conn,
+ "find_contract",
+ params);
+ i = PQntuples (result);
+ if (1 < i)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Mupltiple contracts share the same hashcode.\n");
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "i, %d\n", i);
+
+ if (0 == i)
+ return GNUNET_NO;
+
+ /* FIXME, figure out how to pass back json_t's */
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_json ("plain_contract",
+ contract),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ 0))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Insert a hash to contract map into the database
+ *
+ * @param cls closure
+ * @param h_contract hashcode of @a contract
+ * @param contract contract to store
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error
+ */
+static int
+postgres_store_map (void *cls,
+ struct GNUNET_HashCode *h_contract,
+ const json_t *contract)
+{
+ struct PostgresClosure *pg = cls;
+ PGresult *result;
+ int ret;
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_contract),
+ TALER_PQ_query_param_json (contract),
+ GNUNET_PQ_query_param_end
+ };
+
+ result = GNUNET_PQ_exec_prepared (pg->conn,
+ "insert_map",
+ params);
+
+ /**
+ * We don't treat a unique_violation (code '23505') error as
+ * an actual error, since there is no problem if a frontend tries
+ * to store twice the same contract. That is especially needed
+ * when DB-less frontends perform replayed payments.
+ */
+ if (PGRES_COMMAND_OK != PQresultStatus (result)
+ && (0 != memcmp ("23505",
+ EXTRACT_DB_ERROR (result),
+ 5)))
+ {
+ ret = GNUNET_SYSERR;
+ BREAK_DB_ERR (result);
+ }
+ else
+ {
+ ret = GNUNET_OK;
+ }
+ PQclear (result);
+ return ret;
+}
/**
* Insert transaction data into the database.
@@ -1229,6 +1369,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
plugin->find_transfers_by_id = &postgres_find_transfers_by_id;
plugin->find_deposits_by_wtid = &postgres_find_deposits_by_wtid;
plugin->find_proof_by_wtid = &postgres_find_proof_by_wtid;
+ plugin->store_map = &postgres_store_map;
+ plugin->find_contract = &postgres_find_contract;
return plugin;
}
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index f67b0f7d..d30b7f5e 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -130,6 +130,11 @@ static json_t *deposit_proof;
*/
static json_t *transfer_proof;
+/**
+ * A mock contract, not need to be well-formed
+ */
+static json_t *contract;
+
/**
* Function called with information about a transaction.
@@ -347,6 +352,20 @@ run (void *cls)
json_object_set_new (transfer_proof,
"test",
json_string ("backenddb test B")));
+ contract = json_object ();
+
+ FAILIF (GNUNET_OK !=
+ plugin->store_map (plugin->cls,
+ &h_contract,
+ contract));
+
+ json_t *out;
+
+ FAILIF (GNUNET_OK !=
+ plugin->find_contract (plugin->cls,
+ &out,
+ &h_contract));
+
FAILIF (GNUNET_OK !=
plugin->store_transaction (plugin->cls,
transaction_id,
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
index eaf85caf..0ed6856e 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -28,6 +28,60 @@
#include <jansson.h>
+/* ********************* /map/{in,out} *********************** */
+
+struct TALER_MERCHANT_MapOutOperation;
+
+typedef void
+(*TALER_MERCHANT_MapOperationCallback) (void *cls,
+ unsigned int http_status,
+ const json_t *body);
+
+/**
+ * Issue a /map/out request to the backend.
+ *
+ * @param ctx execution context
+ * @param backend_uri base URL of the merchant backend
+ * @param h_contract hashcode of `contract`
+ * @param map_in_cb callback which will work the response gotten from the backend
+ * @param map_in_cb_cls closure to pass to @a history_cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_MapOperation *
+TALER_MERCHANT_map_out (struct GNUNET_CURL_Context *ctx,
+ const char *backend_uri,
+ const struct GNUNET_HashCode *h_contract,
+ TALER_MERCHANT_MapOperationCallback map_cb,
+ void *map_cb_cls);
+
+/**
+ * Issue a /map/in request to the backend.
+ *
+ * @param ctx execution context
+ * @param backend_uri base URL of the merchant backend
+ * @param contract contract to store
+ * @param h_contract hashcode of `contract`
+ * @param map_in_cb callback which will work the response gotten from the backend
+ * @param map_in_cb_cls closure to pass to @a history_cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_MapOperation *
+TALER_MERCHANT_map_in (struct GNUNET_CURL_Context *ctx,
+ const char *backend_uri,
+ const json_t *contract,
+ const struct GNUNET_HashCode *h_contract,
+ TALER_MERCHANT_MapOperationCallback map_cb,
+ void *map_cb_cls);
+
+/**
+ * Cancel a /map/in request.
+ *
+ * @param mio handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_map_cancel (struct TALER_MERCHANT_MapOperation *mo);
+
+
/* ********************* /contract *********************** */
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index 92375fb2..3923fde1 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -150,6 +150,34 @@ struct TALER_MERCHANTDB_Plugin
/**
+ * Insert a hash to contract map into the database
+ *
+ * @param cls closure
+ * @param h_contract hashcode of @a contract
+ * @param contract contract to store
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error
+ */
+ int
+ (*store_map) (void *cls,
+ struct GNUNET_HashCode *h_contract,
+ const json_t *contract);
+
+
+ /**
+ * Retrieve plain contract given its hashcode
+ *
+ * @param cls closure
+ * @param h_contract hashcode of the contract to retrieve
+ * @param contract where to store the retrieved contract
+ * @return #GNUNET_OK on success, #GNUNET_NO if no contract is
+ * found, #GNUNET_SYSERR upon error
+ */
+ int
+ (*find_contract) (void *cls,
+ json_t **contract,
+ struct GNUNET_HashCode *h_contract);
+
+ /**
* Insert transaction data into the database.
*
* @param cls closure
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index d36eec22..d862365e 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -18,7 +18,8 @@ libtalermerchant_la_SOURCES = \
merchant_api_pay.c \
merchant_api_track_transaction.c \
merchant_api_track_transfer.c \
- merchant_api_history.c
+ merchant_api_history.c \
+ merchant_api_map.c
libtalermerchant_la_LIBADD = \
-ltalerexchange \
diff --git a/src/lib/merchant_api_contract.c b/src/lib/merchant_api_contract.c
index 469539bf..c5db8d3b 100644
--- a/src/lib/merchant_api_contract.c
+++ b/src/lib/merchant_api_contract.c
@@ -94,9 +94,9 @@ handle_contract_finished (void *cls,
h_contractp = NULL;
switch (response_code)
{
- case 0:
- break;
- case MHD_HTTP_OK:
+ case 0:
+ break;
+ case MHD_HTTP_OK:
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("contract", &contract),
@@ -104,7 +104,7 @@ handle_contract_finished (void *cls,
GNUNET_JSON_spec_fixed_auto ("H_contract", &h_contract),
GNUNET_JSON_spec_end()
};
-
+
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
spec,
@@ -117,34 +117,33 @@ handle_contract_finished (void *cls,
h_contractp = &h_contract;
sigp = &sig;
}
- break;
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the merchant is buggy
- (or API version conflict); just pass JSON reply to the application */
- break;
- case MHD_HTTP_FORBIDDEN:
- break;
- case MHD_HTTP_UNAUTHORIZED:
- /* Nothing really to verify, merchant says one of the signatures is
- invalid; as we checked them, this should never happen, we
- should pass the JSON reply to the application */
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- break;
- default:
- /* unexpected response code */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- GNUNET_break (0);
- response_code = 0;
- break;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the merchant is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Nothing really to verify, merchant says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
}
co->cb (co->cb_cls,
response_code,
@@ -155,7 +154,6 @@ handle_contract_finished (void *cls,
h_contractp);
if (NULL != contract)
json_decref (contract);
- TALER_MERCHANT_contract_sign_cancel (co);
}
diff --git a/src/lib/merchant_api_history.c b/src/lib/merchant_api_history.c
index bf7505f4..39f9bd82 100644
--- a/src/lib/merchant_api_history.c
+++ b/src/lib/merchant_api_history.c
@@ -128,7 +128,6 @@ history_raw_cb (void *cls,
response_code,
TALER_JSON_get_error_code (json),
json);
- TALER_MERCHANT_history_cancel (ho);
}
@@ -171,8 +170,6 @@ TALER_MERCHANT_history (struct GNUNET_CURL_Context *ctx,
return NULL;
}
-
-
if (NULL == (ho->job = GNUNET_CURL_job_add (ctx,
eh,
GNUNET_YES,
diff --git a/src/lib/merchant_api_map.c b/src/lib/merchant_api_map.c
new file mode 100644
index 00000000..d1aaf5a1
--- /dev/null
+++ b/src/lib/merchant_api_map.c
@@ -0,0 +1,230 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V. and INRIA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/merchant_api_map.c
+ * @brief Implementation of the /map/{in,out} request of the merchant's HTTP API
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * This structure acts like a "handle" for both /map/in and
+ * /map/out operations, as they only differ about the 'json_enc'
+ * field (which is just left NULL when not needed).
+ */
+struct TALER_MERCHANT_MapOperation
+{
+ /**
+ * Full URI, includes "/map/in".
+ */
+ char *url;
+
+ /**
+ * Request's body. Left NULL in case of /map/out.
+ */
+ char *json_enc;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_MapOperationCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Cancel a /map/{in,out} request.
+ *
+ * @param mio handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_map_cancel (struct TALER_MERCHANT_MapOperation *mo)
+{
+ if (NULL != mo->job)
+ {
+ GNUNET_CURL_job_cancel (mo->job);
+ mo->job = NULL;
+ }
+ GNUNET_free (mo->url);
+ GNUNET_free_non_null (mo->json_enc);
+ GNUNET_free (mo);
+}
+
+
+/**
+ * Function called when we're done processing the HTTP /map/{in,out} request.
+ *
+ * @param cls the `struct TALER_MERCHANT_MapInOperation`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, should be NULL
+ */
+static void
+handle_map_finished (void *cls,
+ long response_code,
+ const json_t *json)
+{
+ struct TALER_MERCHANT_MapOperation *mo = cls;
+
+ /**
+ * As no data is supposed to be extracted from this
+ * call, we just invoke the provided callback from here.
+ */
+ mo->cb (mo->cb_cls,
+ response_code,
+ json);
+}
+
+/**
+ * Issue a /map/out request to the backend.
+ *
+ * @param ctx execution context
+ * @param backend_uri base URL of the merchant backend
+ * @param h_contract hashcode of `contract`
+ * @param map_in_cb callback which will work the response gotten from the backend
+ * @param map_in_cb_cls closure to pass to @a history_cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_MapOperation *
+TALER_MERCHANT_map_out (struct GNUNET_CURL_Context *ctx,
+ const char *backend_uri,
+ const struct GNUNET_HashCode *h_contract,
+ TALER_MERCHANT_MapOperationCallback map_cb,
+ void *map_cb_cls)
+{
+ struct TALER_MERCHANT_MapOperation *mo;
+ CURL *eh;
+ char *hash_enc;
+
+ mo = GNUNET_new (struct TALER_MERCHANT_MapOperation);
+ mo->ctx = ctx;
+ mo->cb = map_cb;
+ mo->cb_cls = map_cb_cls;
+
+ hash_enc = GNUNET_STRINGS_data_to_string_alloc (h_contract,
+ sizeof (struct GNUNET_HashCode));
+ GNUNET_asprintf (&mo->url,
+ "%s/map/out?h_contract=%s",
+ backend_uri,
+ hash_enc);
+ eh = curl_easy_init ();
+ if (CURLE_OK != curl_easy_setopt (eh,
+ CURLOPT_URL,
+ mo->url))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ if (NULL == (mo->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_map_finished,
+ mo)))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return mo;
+}
+
+/**
+ * Issue a /map/in request to the backend.
+ *
+ * @param ctx execution context
+ * @param backend_uri base URL of the merchant backend
+ * @param contract contract to store
+ * @param h_contract hashcode of `contract`
+ * @param map_in_cb callback which will work the response gotten from the backend
+ * @param map_in_cb_cls closure to pass to @a history_cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_MapOperation *
+TALER_MERCHANT_map_in (struct GNUNET_CURL_Context *ctx,
+ const char *backend_uri,
+ const json_t *contract,
+ const struct GNUNET_HashCode *h_contract,
+ TALER_MERCHANT_MapOperationCallback map_cb,
+ void *map_cb_cls)
+{
+ struct TALER_MERCHANT_MapOperation *mo;
+ CURL *eh;
+ json_t *req;
+
+ mo = GNUNET_new (struct TALER_MERCHANT_MapOperation);
+ mo->ctx = ctx;
+ mo->cb = map_cb;
+ mo->cb_cls = map_cb_cls;
+
+ GNUNET_asprintf (&mo->url,
+ "%s%s",
+ backend_uri,
+ "/map/in");
+
+ // build final json
+ req = json_pack ("{s:o, s:o}",
+ "contract", contract,
+ "h_contract", GNUNET_JSON_from_data_auto (h_contract));
+
+ GNUNET_assert (NULL !=
+ (mo->json_enc = json_dumps (req, JSON_COMPACT))
+ );
+
+ json_decref (req);
+ eh = curl_easy_init ();
+
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ mo->url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ mo->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (mo->json_enc)));
+ mo->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_map_finished,
+ mo);
+ return mo;
+}
diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c
index 0c06326a..5475ec70 100644
--- a/src/lib/merchant_api_pay.c
+++ b/src/lib/merchant_api_pay.c
@@ -243,7 +243,6 @@ handle_pay_finished (void *cls,
response_code,
TALER_JSON_get_error_code (json),
json);
- TALER_MERCHANT_pay_cancel (ph);
}
diff --git a/src/lib/merchant_api_track_transaction.c b/src/lib/merchant_api_track_transaction.c
index 4aed3c68..cb864410 100644
--- a/src/lib/merchant_api_track_transaction.c
+++ b/src/lib/merchant_api_track_transaction.c
@@ -161,7 +161,6 @@ parse_track_transaction_ok (struct TALER_MERCHANT_TrackTransactionHandle *tdo,
transfers);
free_transfers (num_transfers,
transfers);
- TALER_MERCHANT_track_transaction_cancel (tdo);
return GNUNET_OK;
}
@@ -223,7 +222,6 @@ handle_track_transaction_finished (void *cls,
json,
0,
NULL);
- TALER_MERCHANT_track_transaction_cancel (tdo);
}
diff --git a/src/lib/merchant_api_track_transfer.c b/src/lib/merchant_api_track_transfer.c
index e7a1fdc3..ee6c979c 100644
--- a/src/lib/merchant_api_track_transfer.c
+++ b/src/lib/merchant_api_track_transfer.c
@@ -147,7 +147,6 @@ check_track_transfer_response_ok (struct TALER_MERCHANT_TrackTransferHandle *wdh
details);
}
GNUNET_JSON_parse_free (inner_spec);
- TALER_MERCHANT_track_transfer_cancel (wdh);
return GNUNET_OK;
}
@@ -206,7 +205,6 @@ handle_track_transfer_finished (void *cls,
NULL,
0,
NULL);
- TALER_MERCHANT_track_transfer_cancel (tdo);
}
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
index 45f17a64..f2081c2d 100644
--- a/src/lib/test_merchant_api.c
+++ b/src/lib/test_merchant_api.c
@@ -55,6 +55,20 @@
*/
#define CONTRACT_MAX_SIZE 1000
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+
+/**
+ * Handle to database.
+ */
+struct TALER_MERCHANTDB_Plugin *db;
+
+/**
+ * Configuration handle.
+ */
+struct GNUNET_CONFIGURATION_Handle *cfg;
+
/**
* Handle to access the exchange.
*/
@@ -118,6 +132,18 @@ enum OpCode
OC_END = 0,
/**
+ * Ask the backend to store a contract and its hashcode into
+ * the database.
+ */
+ OC_MAP_IN,
+
+ /**
+ * Ask the backend to retrieve a contract from the database, according
+ * to its hashcode.
+ */
+ OC_MAP_OUT,
+
+ /**
* Add funds to a reserve by (faking) incoming wire transfer.
*/
OC_ADMIN_ADD_INCOMING,
@@ -292,6 +318,21 @@ struct Command
} admin_add_incoming;
/**
+ * Information for both #OC_MAP_{IN,OUT} command.
+ */
+ struct
+ {
+
+ /**
+ * Reference to a contract we need to hash and store.
+ */
+ const char *contract_reference;
+
+ struct TALER_MERCHANT_MapOperation *mo;
+
+ } map;
+
+ /**
* Information for a #OC_WITHDRAW_STATUS command.
*/
struct
@@ -611,47 +652,6 @@ fail (struct InterpreterState *is)
GNUNET_SCHEDULER_shutdown ();
}
-/**
- * Get a new contract proposal for each OC_CONTRACT
- * in the list of command. It's used when we run multiple
- * instances beacuse we can't have the same transaction_id
- * for two instances.
- *
- * @param cmds the list of commands
- */
-void
-get_new_contracts (struct Command *cmds)
-{
- unsigned int i;
- unsigned int d = 0;
- struct Command *cmd;
- #define DELTA 1000
-
- for (i=0;OC_END != (cmd = &cmds[i])->oc;i++)
- if ( (NULL != cmd->label) &&
- (OC_CONTRACT == cmd->oc) )
- {
-
- if (0 == strcmp (cmd->label, "create-contract-2"))
- d = DELTA;
-
- snprintf (cmd->details.contract.proposal,
- CONTRACT_MAX_SIZE,
- "{\
- \"max_fee\":\
- {\"currency\":\"EUR\", \"value\":0, \"fraction\":50000000},\
- \"transaction_id\":%d,\
- \"timestamp\":\"\\/Date(42)\\/\",\
- \"refund_deadline\":\"\\/Date(0)\\/\",\
- \"expiry\":\"\\/Date(999999999)\\/\",\
- \"amount\":{\"currency\":\"EUR\", \"value\":5, \"fraction\":0},\
- \"products\":\
- [ {\"description\":\"ice cream\", \"value\":\"{EUR:5}\"} ]\
- }",
- instance_idx + d);
- }
-}
-
/**
* Find a command by label.
@@ -1273,6 +1273,28 @@ track_transfer_cb (void *cls,
next_command (is);
}
+/**
+ * Callback for /map/in issued at backend. Just check
+ * whether response code is as expected.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code we got
+ */
+static void
+map_cb (void *cls,
+ unsigned int http_status,
+ const json_t *json)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+
+ cmd->details.map.mo = NULL;
+
+ if (cmd->expected_response_code != http_status)
+ fail (is);
+
+ next_command (is);
+}
/**
* Function called with detailed wire transfer data.
@@ -1472,10 +1494,60 @@ interpreter_run (void *cls)
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Switching instance: '%s'\n",
instance);
- /*get_new_contracts(is->commands);*/
+
is->task = GNUNET_SCHEDULER_add_now (interpreter_run,
is);
return;
+
+ case OC_MAP_IN:
+ case OC_MAP_OUT:
+ {
+ struct GNUNET_HashCode h_proposal;
+ json_error_t error;
+ json_t *proposal;
+
+ GNUNET_assert (NULL != cmd->details.map.contract_reference);
+ ref = find_command (is, cmd->details.map.contract_reference);
+ GNUNET_assert (NULL != ref);
+
+ /**
+ * WARNING, make sure what is hashed here, is exactly the same
+ * contract hashed then by /map/in handler.
+ */
+ proposal = json_loads (ref->details.contract.proposal,
+ JSON_REJECT_DUPLICATES,
+ &error);
+
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_JSON_hash (proposal, &h_proposal));
+
+ if (OC_MAP_IN == cmd->oc)
+ {
+
+ if (MHD_HTTP_UNPROCESSABLE_ENTITY == cmd->expected_response_code)
+ RND_BLK (&h_proposal);
+
+ GNUNET_assert (NULL !=
+ (cmd->details.map.mo
+ = TALER_MERCHANT_map_in (ctx,
+ MERCHANT_URI,
+ proposal,
+ &h_proposal,
+ map_cb,
+ is)));
+ }
+ else
+ GNUNET_assert (NULL !=
+ (cmd->details.map.mo
+ = TALER_MERCHANT_map_out (ctx,
+ MERCHANT_URI,
+ &h_proposal,
+ map_cb,
+ is)));
+
+ }
+ return;
+
case OC_ADMIN_ADD_INCOMING:
if (NULL !=
cmd->details.admin_add_incoming.reserve_reference)
@@ -1961,6 +2033,18 @@ do_shutdown (void *cls)
case OC_END:
GNUNET_assert (0);
break;
+ case OC_MAP_IN:
+ case OC_MAP_OUT:
+ if (NULL != cmd->details.map.mo)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ i,
+ cmd->label);
+ TALER_MERCHANT_map_cancel (cmd->details.map.mo);
+ }
+ break;
+
case OC_ADMIN_ADD_INCOMING:
if (NULL != cmd->details.admin_add_incoming.aih)
{
@@ -2102,6 +2186,10 @@ do_shutdown (void *cls)
}
TALER_FAKEBANK_stop (fakebank);
fakebank = NULL;
+
+ db->drop_tables (db->cls);
+ TALER_MERCHANTDB_plugin_unload (db);
+ GNUNET_CONFIGURATION_destroy (cfg);
}
@@ -2212,8 +2300,15 @@ run (void *cls)
.details.pay.coin_ref = "withdraw-coin-1",
.details.pay.amount_with_fee = "EUR:5",
.details.pay.amount_without_fee = "EUR:4.99" },
- /* Create another contract */
+ /* Store contract-1 */
+ {
+ .oc = OC_MAP_IN,
+ .label = "store-contract-1",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.map.contract_reference = "create-contract-1" },
+
+ /* Create another contract */
{ .oc = OC_CONTRACT,
.label = "create-contract-2",
.expected_response_code = MHD_HTTP_OK,
@@ -2227,6 +2322,7 @@ run (void *cls)
\"amount\":{\"currency\":\"EUR\", \"value\":5, \"fraction\":0},\
\"products\":\
[ {\"description\":\"ice cream\", \"value\":\"{EUR:5}\"} ] }" },
+
/* Try to double-spend the 5 EUR coin at the same merchant (but different
transaction ID) */
{ .oc = OC_PAY,
@@ -2245,6 +2341,14 @@ run (void *cls)
.details.admin_add_incoming.transfer_details = "{ \"uuid\": 2}",
.details.admin_add_incoming.amount = "EUR:1" },
+ /* Store contract-1 */
+ {
+ .oc = OC_MAP_IN,
+ .label = "store-contract-2",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.map.contract_reference = "create-contract-2",
+ },
+
/* Add another 4.01 EUR to reserve #2 */
{ .oc = OC_ADMIN_ADD_INCOMING,
.label = "create-reserve-2b",
@@ -2261,6 +2365,13 @@ run (void *cls)
.details.reserve_withdraw.reserve_reference = "create-reserve-2",
.details.reserve_withdraw.amount = "EUR:5" },
+ /* Fetch contract-1 */
+ {
+ .oc = OC_MAP_OUT,
+ .label = "fetch-contract-2",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.map.contract_reference = "create-contract-2" },
+
/* Check nothing happened on the bank side so far */
{ .oc = OC_CHECK_BANK_TRANSFERS_EMPTY,
.label = "check_bank_empty" },
@@ -2390,6 +2501,34 @@ run (void *cls)
.details.history.date.abs_value_us = 43 * 1000LL * 1000LL,
.details.history.nresult = 0
},
+ /* Retrieve via /map/out a contract NOT stored previously. */
+ {
+ .oc = OC_MAP_OUT,
+ .label = "fetch-contract-not-found",
+ .expected_response_code = MHD_HTTP_NOT_FOUND,
+ .details.map.contract_reference = "create-contract-3" },
+
+ /* Create another contract, NOT to be stored. */
+ { .oc = OC_CONTRACT,
+ .label = "create-contract-3",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.contract.proposal = "{\
+ \"max_fee\":\
+ {\"currency\":\"EUR\", \"value\":0, \"fraction\":10000},\
+ \"transaction_id\":3,\
+ \"timestamp\":\"\\/Date(42)\\/\",\
+ \"refund_deadline\":\"\\/Date(0)\\/\",\
+ \"pay_deadline\":\"\\/Date(0)\\/\",\
+ \"amount\":{\"currency\":\"EUR\", \"value\":1, \"fraction\":0},\
+ \"products\":\
+ [ {\"description\":\"bogus\", \"value\":\"{EUR:1}\"} ] }" },
+
+ /* Try to store a contract passing a bogus hashcode. */
+ {
+ .oc = OC_MAP_IN,
+ .label = "store-contract-bogus",
+ .expected_response_code = MHD_HTTP_UNPROCESSABLE_ENTITY,
+ .details.map.contract_reference = "create-contract-3" },
/* end of testcase */
{ .oc = OC_END }
};
@@ -2440,8 +2579,6 @@ main (int argc,
struct GNUNET_OS_Process *proc;
struct GNUNET_OS_Process *exchanged;
struct GNUNET_OS_Process *merchantd;
- struct TALER_MERCHANTDB_Plugin *db;
- struct GNUNET_CONFIGURATION_Handle *cfg;
unsigned int cnt;
struct GNUNET_SIGNAL_Context *shc_chld;
@@ -2478,9 +2615,6 @@ main (int argc,
GNUNET_CONFIGURATION_destroy (cfg);
return 77;
}
- TALER_MERCHANTDB_plugin_unload (db);
- GNUNET_CONFIGURATION_destroy (cfg);
-
proc = GNUNET_OS_start_process (GNUNET_NO,
GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
diff --git a/src/merchant-tools/Makefile.am b/src/merchant-tools/Makefile.am
new file mode 100644
index 00000000..6b8162e9
--- /dev/null
+++ b/src/merchant-tools/Makefile.am
@@ -0,0 +1,16 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+bin_PROGRAMS = \
+ taler-merchant-dbinit
+
+taler_merchant_dbinit_SOURCES = \
+ taler-merchant-dbinit.c
+
+
+taler_merchant_dbinit_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/backenddb/libtalermerchantdb.la \
+ -lgnunetutil \
+ -ltalerutil \
+ -ltalerpq
diff --git a/src/merchant-tools/taler-merchant-dbinit b/src/merchant-tools/taler-merchant-dbinit
new file mode 100755
index 00000000..99ac8fbd
--- /dev/null
+++ b/src/merchant-tools/taler-merchant-dbinit
@@ -0,0 +1,228 @@
+#! /bin/bash
+
+# taler-merchant-dbinit - temporary wrapper script for .libs/taler-merchant-dbinit
+# Generated by libtool (GNU libtool) 2.4.2 Debian-2.4.2-1.11
+#
+# The taler-merchant-dbinit program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s/\([`"$\\]\)/\\\1/g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command="(cd /home/marcello/merchant/src/merchant-tools; { test -z \"\${LIBRARY_PATH+set}\" || unset LIBRARY_PATH || { LIBRARY_PATH=; export LIBRARY_PATH; }; }; { test -z \"\${COMPILER_PATH+set}\" || unset COMPILER_PATH || { COMPILER_PATH=; export COMPILER_PATH; }; }; { test -z \"\${GCC_EXEC_PREFIX+set}\" || unset GCC_EXEC_PREFIX || { GCC_EXEC_PREFIX=; export GCC_EXEC_PREFIX; }; }; { test -z \"\${LD_RUN_PATH+set}\" || unset LD_RUN_PATH || { LD_RUN_PATH=; export LD_RUN_PATH; }; }; LD_LIBRARY_PATH=/home/marcello/tmp/lib/; export LD_LIBRARY_PATH; PATH=/home/marcello/local/bin:/home/marcello/.local/bin:/home/marcello/local/bin:/home/marcello/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games; export PATH; gcc -std=gnu99 -Wall -Wall -O0 -g -o \$progdir/\$file taler-merchant-dbinit.o -L/home/marcello/local/lib -L/usr/local/lib /home/marcello/local/lib/libgcrypt.so -L/usr/lib/i386-linux-gnu /home/marcello/local/lib/libgpg-error.so ../../src/backenddb/.libs/libtalermerchantdb.so /home/marcello/local/lib/libgnunetutil.so /home/marcello/local/lib/libtalerutil.so /home/marcello/local/lib/libtalerpq.so -Wl,-rpath -Wl,/home/marcello/local/lib -Wl,-rpath -Wl,/home/marcello/merchant/src/backenddb/.libs -Wl,-rpath -Wl,/home/marcello/local/lib)"
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.2'
+ notinst_deplibs=' ../../src/backenddb/libtalermerchantdb.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ which is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options which match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "taler-merchant-dbinit:taler-merchant-dbinit:${LINENO}: libtool wrapper (GNU libtool) 2.4.2 Debian-2.4.2-1.11" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "taler-merchant-dbinit:taler-merchant-dbinit:${LINENO}: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "taler-merchant-dbinit:taler-merchant-dbinit:${LINENO}: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program=lt-'taler-merchant-dbinit'
+ progdir="$thisdir/.libs"
+
+ if test ! -f "$progdir/$program" ||
+ { file=`ls -1dt "$progdir/$program" "$progdir/../$program" 2>/dev/null | /bin/sed 1q`; \
+ test "X$file" != "X$progdir/$program"; }; then
+
+ file="$$-$program"
+
+ if test ! -d "$progdir"; then
+ mkdir "$progdir"
+ else
+ rm -f "$progdir/$file"
+ fi
+
+ # relink executable if necessary
+ if test -n "$relink_command"; then
+ if relink_command_output=`eval $relink_command 2>&1`; then :
+ else
+ printf %s\n "$relink_command_output" >&2
+ rm -f "$progdir/$file"
+ exit 1
+ fi
+ fi
+
+ mv -f "$progdir/$file" "$progdir/$program" 2>/dev/null ||
+ { rm -f "$progdir/$program";
+ mv -f "$progdir/$file" "$progdir/$program"; }
+ rm -f "$progdir/$file"
+ fi
+
+ if test -f "$progdir/$program"; then
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: \`$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/merchant-tools/taler-merchant-dbinit.c b/src/merchant-tools/taler-merchant-dbinit.c
new file mode 100644
index 00000000..5bffe9d1
--- /dev/null
+++ b/src/merchant-tools/taler-merchant-dbinit.c
@@ -0,0 +1,115 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 merchant-tools/taler-merchant-dbinit.c
+ * @brief Create tables for the merchant database.
+ * @author Florian Dold
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * -r option: do full DB reset
+ */
+static int reset_db;
+
+/**
+ * Main function that will be run.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param cfg configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct TALER_MERCHANTDB_Plugin *plugin;
+
+ if (NULL ==
+ (plugin = TALER_MERCHANTDB_plugin_load (cfg)))
+ {
+ fprintf (stderr,
+ "Failed to initialize database plugin.\n");
+ global_ret = 1;
+ return;
+ }
+ if (reset_db)
+ (void) plugin->drop_tables (plugin->cls);
+ if (GNUNET_OK !=
+ plugin->initialize (plugin->cls))
+ {
+ fprintf (stderr,
+ "Failed to initialize database.\n");
+ TALER_MERCHANTDB_plugin_unload (plugin);
+ global_ret = 1;
+ return;
+ }
+
+ TALER_MERCHANTDB_plugin_unload (plugin);
+}
+
+
+/**
+ * The main function of the database initialization tool.
+ * Used to initialize the Taler Exchange's database.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ {'r', "reset", NULL,
+ "reset database (DANGEROUS: all existing data is lost!)", 0,
+ &GNUNET_GETOPT_set_one, &reset_db},
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ /* force linker to link against libtalerutil; if we do
+ not do this, the linker may "optimize" libtalerutil
+ away and skip #TALER_OS_init(), which we do need */
+ (void) TALER_project_data_default ();
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_log_setup ("taler-merchant-dbinit",
+ "INFO",
+ NULL));
+ if (GNUNET_OK !=
+ GNUNET_PROGRAM_run (argc, argv,
+ "taler-merchant-dbinit",
+ "Initialize Taler merchant database",
+ options,
+ &run, NULL))
+ return 1;
+ return global_ret;
+}
+
+
+/* end of taler-exchange-dbinit.c */