From e13a8902f07a7848d09204cd6f1b9736fe3cf885 Mon Sep 17 00:00:00 2001 From: Marcello Stanisci Date: Mon, 8 Apr 2019 02:09:23 +0200 Subject: [PATCH] Put /history[-range] logic in a dedicate file. --- .gitignore | 1 + src/bank-lib/Makefile.am | 1 + src/bank-lib/fakebank.c | 954 +------------------------------- src/bank-lib/fakebank.h | 322 +++++++++++ src/bank-lib/fakebank_history.c | 427 ++++++++++++++ 5 files changed, 768 insertions(+), 937 deletions(-) create mode 100644 src/bank-lib/fakebank.h create mode 100644 src/bank-lib/fakebank_history.c diff --git a/.gitignore b/.gitignore index 8696558d4..6f7a02583 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,4 @@ src/bank-lib/test_bank_api_twisted src/lib/test_exchange_api_new src/lib/test_auditor_api src/lib/test_exchange_api_overlapping_keys_bug +src/lib/test_exchange_api_home/.local/share/taler/exchange/revocations/ diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am index e6e2e878c..ddc4cf4b3 100644 --- a/src/bank-lib/Makefile.am +++ b/src/bank-lib/Makefile.am @@ -57,6 +57,7 @@ libtalerfakebank_la_LDFLAGS = \ -no-undefined libtalerfakebank_la_SOURCES = \ + fakebank_history.c \ fakebank.c libtalerfakebank_la_LIBADD = \ diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index f3b58fd26..37aae0423 100644 --- a/src/bank-lib/fakebank.c +++ b/src/bank-lib/fakebank.c @@ -25,6 +25,7 @@ #include "platform.h" #include "taler_fakebank_lib.h" #include "taler_bank_service.h" +#include "fakebank.h" /** * Maximum POST request size (for /admin/add/incoming) @@ -32,169 +33,6 @@ #define REQUEST_BUFFER_MAX (4*1024) -/** - * Parse URL arguments of a /history[-range] HTTP request. - * - * @param connection MHD connection object. - * @param ha @a HistoryArgs structure. - */ -#define PARSE_HISTORY_ARGS(connection, ha) \ - parse_history_args_ (connection, ha, __FUNCTION__) - -/** - * Details about a transcation we (as the simulated bank) received. - */ -struct Transaction -{ - /** - * We store transactions in a DLL. - */ - struct Transaction *next; - - /** - * We store transactions in a DLL. - */ - struct Transaction *prev; - - /** - * Amount to be transferred. - */ - struct TALER_Amount amount; - - /** - * Account to debit. - */ - uint64_t debit_account; - - /** - * Account to credit. - */ - uint64_t credit_account; - - /** - * Subject of the transfer. - */ - char *subject; - - /** - * Base URL of the exchange. - */ - char *exchange_base_url; - - /** - * When did the transaction happen? - */ - struct GNUNET_TIME_Absolute date; - - /** - * Number of this transaction. - */ - uint64_t row_id; - - /** - * Flag set if the transfer was rejected. - */ - int rejected; - - /** - * Has this transaction been subjected to #TALER_FAKEBANK_check() - * and should thus no longer be counted in - * #TALER_FAKEBANK_check_empty()? - */ - int checked; -}; - - -/** - * Needed to implement ascending/descending ordering - * of /history results. - */ -struct HistoryElement -{ - - /** - * History JSON element. - */ - json_t *element; - - /** - * Previous element. - */ - struct HistoryElement *prev; - - /** - * Next element. - */ - struct HistoryElement *next; -}; - - -/** - * Values to implement the "/history-range" range. - */ -struct HistoryRangeDates -{ - /** - * Oldest row in the results. - */ - struct GNUNET_TIME_Absolute start; - - /** - * Youngest row in the results. - */ - struct GNUNET_TIME_Absolute end; -}; - -/** - * Values to implement the "/history" range. - */ -struct HistoryRangeIds -{ - - /** - * (Exclusive) row ID for the result set. - */ - unsigned long long start; - - /** - * How many transactions we want in the result set. If - * negative/positive, @a start will be strictly younger/older - * of any element in the result set. - */ - long long count; -}; - - -/** - * This is the "base" structure for both the /history and the - * /history-range API calls. - */ -struct HistoryArgs -{ - - /** - * Direction asked by the client: CREDIT / DEBIT / BOTH / CANCEL. - */ - enum TALER_BANK_Direction direction; - - /** - * Bank account number of the requesting client. - */ - unsigned long long account_number; - - /** - * Ordering of the results. - */ - unsigned int ascending; - - /** - * Overloaded type that indicates the "range" to be returned - * in the results; this can be either a date range, or a - * starting row id + the count. - */ - void *range; -}; - /** * Handle for the fake bank. */ @@ -746,442 +584,6 @@ handle_reject (struct TALER_FAKEBANK_Handle *h, return ret; } -/*********************************** - * Serving "/history" starts here. * - ***********************************/ - -/** - * Type for a function that decides whether or not - * the history-building loop should iterate once again. - * Typically called from inside the 'while' condition. - * - * @param ha history argument. - * @param pos current position. - * @return GNUNET_YES if the iteration shuold go on. - */ -typedef int (*CheckAdvance) - (const struct HistoryArgs *ha, - const struct Transaction *pos); - -/** - * Type for a function that steps over the next element - * in the list of all transactions, after the current @a pos - * _got_ included in the result. - */ -typedef struct Transaction * (*Step) - (const struct HistoryArgs *ha, - const struct Transaction *pos); - -/* - * Type for a function that steps over the next element - * in the list of all transactions, after the current @a pos - * did _not_ get included in the result. - */ -typedef struct Transaction * (*Skip) - (const struct HistoryArgs *ha, - const struct Transaction *pos); - - - - -/** - * Decides whether the history builder will advance or not - * to the next element. - * - * @param ha history args - * @return GNUNET_YES/NO to advance/not-advance. - */ -static int -handle_history_advance (const struct HistoryArgs *ha, - const struct Transaction *pos) -{ - const struct HistoryRangeIds *hri = ha->range; - - return (NULL != pos) && (0 != hri->count); -} - - -/** - * Iterates on the "next" element to be processed. To - * be used when the current element does not get inserted in - * the result. - * - * @param ha history arguments. - * @param pos current element being processed. - * @return the next element to be processed. - */ -static struct Transaction * -handle_history_skip (const struct HistoryArgs *ha, - const struct Transaction *pos) -{ - const struct HistoryRangeIds *hri = ha->range; - - if (hri->count > 0) - return pos->next; - if (hri->count < 0) - return pos->prev; - return NULL; -} - - -/** - * Iterates on the "next" element to be processed. To - * be used when the current element _gets_ inserted in the result. - * - * @param ha history arguments. - * @param pos current element being processed. - * @return the next element to be processed. - */ -static struct Transaction * -handle_history_step (const struct HistoryArgs *ha, - const struct Transaction *pos) -{ - struct HistoryRangeIds *hri = ha->range; - - if (hri->count > 0) - { - hri->count--; - return pos->next; - } - if (hri->count < 0) - { - hri->count++; - return pos->prev; - } - return NULL; -} - - -/** - * Decides whether the history builder will advance or not - * to the next element. - * - * @param ha history args - * @return GNUNET_YES/NO to advance/not-advance. - */ -static int -handle_history_range_advance (const struct HistoryArgs *ha, - const struct Transaction *pos) -{ - const struct HistoryRangeDates *hrd = ha->range; - - if ( (NULL != pos) && - (pos->date.abs_value_us <= hrd->end.abs_value_us) ) - return GNUNET_YES; - - return GNUNET_NO; -} - - -/** - * Iterates towards the "next" element to be processed. To - * be used when the current element does not get inserted in - * the result. - * - * @param ha history arguments. - * @param pos current element being processed. - * @return the next element to be processed. - */ -static struct Transaction * -handle_history_range_skip (const struct HistoryArgs *ha, - const struct Transaction *pos) -{ - /* Transactions - * are stored from "head"/older to "tail"/younger. */ - return pos->next; -} - -/** - * Iterates on the "next" element to be processed. To - * be used when the current element _gets_ inserted in the result. - * Same implementation of the "skip" counterpart, as /history-range - * does not have the notion of count/delta. - */ -Step handle_history_range_step = handle_history_range_skip; - -/** - * Actual history response builder. - * - * @param pos first (included) element in the result set. - * @param ha history arguments. - * @param caller_name which function is building the history. - * @return MHD_YES / MHD_NO, after having enqueued the response - * object into MHD. - */ -static int -build_history_response (struct MHD_Connection *connection, - struct Transaction *pos, - struct HistoryArgs *ha, - Skip skip, - Step step, - CheckAdvance advance) -{ - - struct HistoryElement *history_results_head = NULL; - struct HistoryElement *history_results_tail = NULL; - struct HistoryElement *history_element = NULL; - json_t *history; - json_t *jresponse; - int ret; - - while (advance (ha, - pos)) - { - json_t *trans; - char *subject; - const char *sign; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Found transaction over %s from %llu to %llu\n", - TALER_amount2s (&pos->amount), - (unsigned long long) pos->debit_account, - (unsigned long long) pos->credit_account); - - if ( (! ( ( (ha->account_number == pos->debit_account) && - (0 != (ha->direction & TALER_BANK_DIRECTION_DEBIT)) ) || - ( (ha->account_number == pos->credit_account) && - (0 != (ha->direction & TALER_BANK_DIRECTION_CREDIT) ) ) ) ) || - ( (0 == (ha->direction & TALER_BANK_DIRECTION_CANCEL)) && - (GNUNET_YES == pos->rejected) ) ) - { - pos = skip (ha, - pos); - continue; - } - - GNUNET_asprintf (&subject, - "%s %s", - pos->subject, - pos->exchange_base_url); - sign = - (ha->account_number == pos->debit_account) - ? (pos->rejected ? "cancel-" : "-") - : (pos->rejected ? "cancel+" : "+"); - trans = json_pack - ("{s:I, s:o, s:o, s:s, s:I, s:s}", - "row_id", (json_int_t) pos->row_id, - "date", GNUNET_JSON_from_time_abs (pos->date), - "amount", TALER_JSON_from_amount (&pos->amount), - "sign", sign, - "counterpart", (json_int_t) - ( (ha->account_number == pos->debit_account) - ? pos->credit_account - : pos->debit_account), - "wt_subject", subject); - GNUNET_assert (NULL != trans); - GNUNET_free (subject); - - history_element = GNUNET_new (struct HistoryElement); - history_element->element = trans; - - - /* XXX: the ordering feature is missing. */ - - GNUNET_CONTAINER_DLL_insert_tail (history_results_head, - history_results_tail, - history_element); - pos = step (ha, pos); - } - - history = json_array (); - if (NULL != history_results_head) - history_element = history_results_head; - - while (NULL != history_element) - { - json_array_append_new (history, - history_element->element); - history_element = history_element->next; - if (NULL != history_element) - GNUNET_free_non_null (history_element->prev); - } - GNUNET_free_non_null (history_results_tail); - - if (0 == json_array_size (history)) - { - struct MHD_Response *resp; - - json_decref (history); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning empty transaction history\n"); - resp = MHD_create_response_from_buffer - (0, - "", - MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response (connection, - MHD_HTTP_NO_CONTENT, - resp); - MHD_destroy_response (resp); - return ret; - } - - jresponse = json_pack ("{s:o}", - "data", - history); - if (NULL == jresponse) - { - GNUNET_break (0); - return MHD_NO; - } - - /* Finally build response object */ - { - struct MHD_Response *resp; - void *json_str; - size_t json_len; - - json_str = json_dumps (jresponse, - JSON_INDENT(2)); - json_decref (jresponse); - if (NULL == json_str) - { - GNUNET_break (0); - return MHD_NO; - } - json_len = strlen (json_str); - resp = MHD_create_response_from_buffer (json_len, - json_str, - MHD_RESPMEM_MUST_FREE); - if (NULL == resp) - { - GNUNET_break (0); - free (json_str); - return MHD_NO; - } - (void) MHD_add_response_header (resp, - MHD_HTTP_HEADER_CONTENT_TYPE, - "application/json"); - ret = MHD_queue_response (connection, - MHD_HTTP_OK, - resp); - MHD_destroy_response (resp); - } - return ret; -} - - - -/** - * Parse URL history arguments, of _both_ APIs: - * /history and /history-range. - * - * @param connection MHD connection. - * @param function_name name of the caller. - * @param ha[out] will contain the parsed values. - * @return GNUNET_OK only if the parsing succeedes. - */ -static int -parse_history_common_args (struct MHD_Connection *connection, - struct HistoryArgs *ha) -{ - /** - * @variable - * Just check if given and == "basic", no need to keep around. - */ - const char *auth; - - /** - * All those will go into the structure, after parsing. - */ - const char *direction; - const char *cancelled; - const char *ordering; - const char *account_number; - - - auth = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "auth"); - direction = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "direction"); - cancelled = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "cancelled"); - ordering = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "ordering"); - account_number = MHD_lookup_connection_value - (connection, - MHD_GET_ARGUMENT_KIND, - "account_number"); - - /* Fail if one of the above failed. */ - if ( (NULL == direction) || - (NULL == cancelled) || - ( (0 != strcasecmp (cancelled, - "OMIT")) && - (0 != strcasecmp (cancelled, - "SHOW")) ) || - ( (0 != strcasecmp (direction, - "BOTH")) && - (0 != strcasecmp (direction, - "CREDIT")) && - (0 != strcasecmp (direction, - "DEBIT")) ) || - (1 != sscanf (account_number, - "%llu", - &ha->account_number)) || - ( (NULL == auth) || (0 != strcasecmp (auth, - "basic")) ) ) - { - /* Invalid request, given that this is fakebank we impolitely - * just kill the connection instead of returning a nice error. - */ - GNUNET_break (0); - return GNUNET_NO; - } - - if (0 == strcasecmp (direction, - "CREDIT")) - { - ha->direction = TALER_BANK_DIRECTION_CREDIT; - } - else if (0 == strcasecmp (direction, - "DEBIT")) - { - ha->direction = TALER_BANK_DIRECTION_DEBIT; - } - else if (0 == strcasecmp (direction, - "BOTH")) - { - ha->direction = TALER_BANK_DIRECTION_BOTH; - } - - /* Direction is invalid. */ - else - { - GNUNET_break (0); - return GNUNET_NO; - } - - if (0 == strcasecmp (cancelled, - "OMIT")) - { - /* nothing */ - } else if (0 == strcasecmp (cancelled, - "SHOW")) - { - ha->direction |= TALER_BANK_DIRECTION_CANCEL; - } - - /* Cancel-showing policy is invalid. */ - else - { - GNUNET_break (0); - return GNUNET_NO; - } - - if ((NULL != ordering) - && 0 == strcmp ("ascending", - ordering)) - ha->ascending = GNUNET_YES; - else - ha->ascending = GNUNET_NO; - - return GNUNET_OK; -} - /** * Handle incoming HTTP request for /history * @@ -1201,8 +603,8 @@ handle_history_new (struct TALER_FAKEBANK_Handle *h, const char *delta; struct Transaction *pos; - if (GNUNET_OK != parse_history_common_args (connection, - &ha)) + if (GNUNET_OK != TFH_parse_history_common_args (connection, + &ha)) { GNUNET_break (0); return MHD_NO; @@ -1259,12 +661,12 @@ handle_history_new (struct TALER_FAKEBANK_Handle *h, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "/history, start row (0 == no transactions exist): %llu\n", NULL != pos ? pos->row_id : 0); - return build_history_response (connection, - pos, - &ha, - &handle_history_skip, - &handle_history_step, - &handle_history_advance); + return TFH_build_history_response (connection, + pos, + &ha, + &TFH_handle_history_skip, + &TFH_handle_history_step, + &TFH_handle_history_advance); } /** @@ -1288,7 +690,7 @@ handle_history_range (struct TALER_FAKEBANK_Handle *h, long long unsigned int end_stamp; struct Transaction *pos; - if (GNUNET_OK != parse_history_common_args (connection, + if (GNUNET_OK != TFH_parse_history_common_args (connection, &ha)) { GNUNET_break (0); @@ -1309,7 +711,7 @@ handle_history_range (struct TALER_FAKEBANK_Handle *h, &end_stamp)) ) { GNUNET_break (0); - return GNUNET_NO; + return MHD_NO; } hrd.start.abs_value_us = start_stamp * 1000LL * 1000LL; @@ -1325,336 +727,14 @@ handle_history_range (struct TALER_FAKEBANK_Handle *h, if (hrd.start.abs_value_us <= pos->date.abs_value_us) break; } - return build_history_response (connection, - pos, - &ha, - &handle_history_range_skip, - handle_history_range_step, - &handle_history_range_advance); + return TFH_build_history_response (connection, + pos, + &ha, + &TFH_handle_history_range_skip, + TFH_handle_history_range_step, + &TFH_handle_history_range_advance); } -/** - * Handle incoming HTTP request for /history - * - * @param h the fakebank handle - * @param connection the connection - * @param con_cls place to store state, not used - * @return MHD result code - */ -static int -handle_history (struct TALER_FAKEBANK_Handle *h, - struct MHD_Connection *connection, - void **con_cls) -{ - const char *auth; - const char *delta; - const char *start; - const char *dir; - const char *acc; - const char *cancelled; - const char *ordering; - unsigned long long account_number; - unsigned long long start_number; - long long count; - enum TALER_BANK_Direction direction; - struct Transaction *pos; - json_t *history; - json_t *jresponse; - int ret; - int ascending; - struct HistoryElement *history_results_head = NULL; - struct HistoryElement *history_results_tail = NULL; - struct HistoryElement *history_element = NULL; - - auth = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "auth"); - delta = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "delta"); - dir = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "direction"); - cancelled = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "cancelled"); - start = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "start"); - ordering = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "ordering"); - acc = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "account_number"); - if ( (NULL == auth) || - (0 != strcasecmp (auth, - "basic")) || - (NULL == acc) || - (NULL == delta) ) - { - /* Invalid request, - given that this is fakebank we impolitely just - kill the connection instead of returning a nice error. */ - GNUNET_break (0); - return MHD_NO; - } - start_number = 0; - if ( (1 != sscanf (delta, - "%lld", - &count)) || - (1 != sscanf (acc, - "%llu", - &account_number)) || - ( (NULL != start) && - (1 != sscanf (start, - "%llu", - &start_number)) ) || - (NULL == dir) || - (NULL == cancelled) || - ( (0 != strcasecmp (cancelled, - "OMIT")) && - (0 != strcasecmp (cancelled, - "SHOW")) ) || - ( (0 != strcasecmp (dir, - "BOTH")) && - (0 != strcasecmp (dir, - "CREDIT")) && - (0 != strcasecmp (dir, - "DEBIT")) ) ) - { - /* Invalid request, given that this is fakebank we impolitely - * just kill the connection instead of returning a nice error. - */ - GNUNET_break (0); - return MHD_NO; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Client asked for up to %lld results of type %s for account %llu starting at %llu\n", - count, - dir, - (unsigned long long) account_number, - start_number); - if (0 == strcasecmp (dir, - "CREDIT")) - { - direction = TALER_BANK_DIRECTION_CREDIT; - } - else if (0 == strcasecmp (dir, - "DEBIT")) - { - direction = TALER_BANK_DIRECTION_DEBIT; - } - else if (0 == strcasecmp (dir, - "BOTH")) - { - direction = TALER_BANK_DIRECTION_BOTH; - } - else - { - GNUNET_assert (0); - return MHD_NO; - } - if (0 == strcasecmp (cancelled, - "OMIT")) - { - /* nothing */ - } else if (0 == strcasecmp (cancelled, - "SHOW")) - { - direction |= TALER_BANK_DIRECTION_CANCEL; - } - else - { - GNUNET_assert (0); - return MHD_NO; - } - - if (NULL == start) - pos = 0 > count ? h->transactions_tail : h->transactions_head; - - else if (NULL != h->transactions_head) - { - for (pos = h->transactions_head; - NULL != pos; - pos = pos->next) - if (pos->row_id == start_number) - break; - if (NULL == pos) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid range specified, transaction %llu not known!\n", - (unsigned long long) start_number); - return MHD_NO; - } - /* range is exclusive, skip the matching entry */ - if (count > 0) - pos = pos->next; - if (count < 0) - pos = pos->prev; - } - else - { - /* list is empty */ - pos = NULL; - } - - history = json_array (); - if ((NULL != ordering) - && 0 == strcmp ("ascending", - ordering)) - ascending = GNUNET_YES; - else - ascending = GNUNET_NO; - - while ( (NULL != pos) && - (0 != count) ) - { - json_t *trans; - char *subject; - const char *sign; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Found transaction over %s from %llu to %llu\n", - TALER_amount2s (&pos->amount), - (unsigned long long) pos->debit_account, - (unsigned long long) pos->credit_account); - - if ( (! ( ( (account_number == pos->debit_account) && - (0 != (direction & TALER_BANK_DIRECTION_DEBIT)) ) || - ( (account_number == pos->credit_account) && - (0 != (direction & TALER_BANK_DIRECTION_CREDIT) ) ) ) ) || - ( (0 == (direction & TALER_BANK_DIRECTION_CANCEL)) && - (GNUNET_YES == pos->rejected) ) ) - { - if (count > 0) - pos = pos->next; - if (count < 0) - pos = pos->prev; - continue; - } - - GNUNET_asprintf (&subject, - "%s %s", - pos->subject, - pos->exchange_base_url); - sign = - (account_number == pos->debit_account) - ? (pos->rejected ? "cancel-" : "-") - : (pos->rejected ? "cancel+" : "+"); - trans = json_pack ("{s:I, s:o, s:o, s:s, s:I, s:s}", - "row_id", (json_int_t) pos->row_id, - "date", GNUNET_JSON_from_time_abs (pos->date), - "amount", TALER_JSON_from_amount (&pos->amount), - "sign", sign, - "counterpart", (json_int_t) ( (account_number == pos->debit_account) - ? pos->credit_account - : pos->debit_account), - "wt_subject", subject); - GNUNET_assert (NULL != trans); - GNUNET_free (subject); - - history_element = GNUNET_new (struct HistoryElement); - history_element->element = trans; - - if (((0 < count) && (GNUNET_YES == ascending)) - || ((0 > count) && (GNUNET_NO == ascending))) - GNUNET_CONTAINER_DLL_insert_tail (history_results_head, - history_results_tail, - history_element); - else - GNUNET_CONTAINER_DLL_insert (history_results_head, - history_results_tail, - history_element); - if (count > 0) - { - pos = pos->next; - count--; - } - if (count < 0) - { - pos = pos->prev; - count++; - } - } - - if (NULL != history_results_head) - history_element = history_results_head; - while (NULL != history_element) - { - json_array_append_new (history, - history_element->element); - history_element = history_element->next; - if (NULL != history_element) - GNUNET_free_non_null (history_element->prev); - } - GNUNET_free_non_null (history_results_tail); - - if (0 == json_array_size (history)) - { - struct MHD_Response *resp; - - json_decref (history); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning empty transaction history\n"); - resp = MHD_create_response_from_buffer (0, - "", - MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response (connection, - MHD_HTTP_NO_CONTENT, - resp); - MHD_destroy_response (resp); - return ret; - } - - jresponse = json_pack ("{s:o}", - "data", - history); - if (NULL == jresponse) - { - GNUNET_break (0); - return MHD_NO; - } - - /* Finally build response object */ - { - struct MHD_Response *resp; - void *json_str; - size_t json_len; - - json_str = json_dumps (jresponse, - JSON_INDENT(2)); - json_decref (jresponse); - if (NULL == json_str) - { - GNUNET_break (0); - return MHD_NO; - } - json_len = strlen (json_str); - resp = MHD_create_response_from_buffer (json_len, - json_str, - MHD_RESPMEM_MUST_FREE); - if (NULL == resp) - { - GNUNET_break (0); - free (json_str); - return MHD_NO; - } - (void) MHD_add_response_header (resp, - MHD_HTTP_HEADER_CONTENT_TYPE, - "application/json"); - ret = MHD_queue_response (connection, - MHD_HTTP_OK, - resp); - MHD_destroy_response (resp); - } - return ret; -} - -/*********************************** - * End of /history implementation. * - ***********************************/ - /** * Handle incoming HTTP request. * diff --git a/src/bank-lib/fakebank.h b/src/bank-lib/fakebank.h new file mode 100644 index 000000000..4e81b3539 --- /dev/null +++ b/src/bank-lib/fakebank.h @@ -0,0 +1,322 @@ +/* + This file is part of TALER + (C) 2016, 2017, 2018 Inria and 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 +*/ + +/** + * @file bank-lib/fakebank.h + * @brief definitions for the "/history[-range]" layer. + * @author Marcello Stanisci + */ + +#ifndef FAKEBANK_H +#define FAKEBANK_H +#include "platform.h" +#include +#include "taler_bank_service.h" + +/** + * Details about a transcation we (as the simulated bank) received. + */ +struct Transaction +{ + /** + * We store transactions in a DLL. + */ + struct Transaction *next; + + /** + * We store transactions in a DLL. + */ + struct Transaction *prev; + + /** + * Amount to be transferred. + */ + struct TALER_Amount amount; + + /** + * Account to debit. + */ + uint64_t debit_account; + + /** + * Account to credit. + */ + uint64_t credit_account; + + /** + * Subject of the transfer. + */ + char *subject; + + /** + * Base URL of the exchange. + */ + char *exchange_base_url; + + /** + * When did the transaction happen? + */ + struct GNUNET_TIME_Absolute date; + + /** + * Number of this transaction. + */ + uint64_t row_id; + + /** + * Flag set if the transfer was rejected. + */ + int rejected; + + /** + * Has this transaction been subjected to #TALER_FAKEBANK_check() + * and should thus no longer be counted in + * #TALER_FAKEBANK_check_empty()? + */ + int checked; +}; + + +/****************************************** + * Definitions for "/history" start here. * + ******************************************/ + +/** + * Needed to implement ascending/descending ordering + * of /history results. + */ +struct HistoryElement +{ + + /** + * History JSON element. + */ + json_t *element; + + /** + * Previous element. + */ + struct HistoryElement *prev; + + /** + * Next element. + */ + struct HistoryElement *next; +}; + + +/** + * Values to implement the "/history-range" range. + */ +struct HistoryRangeDates +{ + /** + * Oldest row in the results. + */ + struct GNUNET_TIME_Absolute start; + + /** + * Youngest row in the results. + */ + struct GNUNET_TIME_Absolute end; +}; + +/** + * Values to implement the "/history" range. + */ +struct HistoryRangeIds +{ + + /** + * (Exclusive) row ID for the result set. + */ + unsigned long long start; + + /** + * How many transactions we want in the result set. If + * negative/positive, @a start will be strictly younger/older + * of any element in the result set. + */ + long long count; +}; + + +/** + * This is the "base" structure for both the /history and the + * /history-range API calls. + */ +struct HistoryArgs +{ + + /** + * Direction asked by the client: CREDIT / DEBIT / BOTH / CANCEL. + */ + enum TALER_BANK_Direction direction; + + /** + * Bank account number of the requesting client. + */ + unsigned long long account_number; + + /** + * Ordering of the results. + */ + unsigned int ascending; + + /** + * Overloaded type that indicates the "range" to be returned + * in the results; this can be either a date range, or a + * starting row id + the count. + */ + void *range; +}; + + + +/** + * Type for a function that decides whether or not + * the history-building loop should iterate once again. + * Typically called from inside the 'while' condition. + * + * @param ha history argument. + * @param pos current position. + * @return GNUNET_YES if the iteration shuold go on. + */ +typedef int (*CheckAdvance) + (const struct HistoryArgs *ha, + const struct Transaction *pos); + +/** + * Type for a function that steps over the next element + * in the list of all transactions, after the current @a pos + * _got_ included in the result. + */ +typedef struct Transaction * (*Step) + (const struct HistoryArgs *ha, + const struct Transaction *pos); + +/* + * Type for a function that steps over the next element + * in the list of all transactions, after the current @a pos + * did _not_ get included in the result. + */ +typedef struct Transaction * (*Skip) + (const struct HistoryArgs *ha, + const struct Transaction *pos); + +/** + * Actual history response builder. + * + * @param pos first (included) element in the result set. + * @param ha history arguments. + * @param caller_name which function is building the history. + * @return MHD_YES / MHD_NO, after having enqueued the response + * object into MHD. + */ +int +TFH_build_history_response (struct MHD_Connection *connection, + struct Transaction *pos, + struct HistoryArgs *ha, + Skip skip, + Step step, + CheckAdvance advance); + + +/** + * Parse URL history arguments, of _both_ APIs: + * /history and /history-range. + * + * @param connection MHD connection. + * @param function_name name of the caller. + * @param ha[out] will contain the parsed values. + * @return GNUNET_OK only if the parsing succeedes. + */ +int +TFH_parse_history_common_args (struct MHD_Connection *connection, + struct HistoryArgs *ha); + + +/** + * Decides whether the history builder will advance or not + * to the next element. + * + * @param ha history args + * @return GNUNET_YES/NO to advance/not-advance. + */ +int +TFH_handle_history_advance (const struct HistoryArgs *ha, + const struct Transaction *pos); + +/** + * Iterates on the "next" element to be processed. To + * be used when the current element does not get inserted in + * the result. + * + * @param ha history arguments. + * @param pos current element being processed. + * @return the next element to be processed. + */ +struct Transaction * +TFH_handle_history_skip (const struct HistoryArgs *ha, + const struct Transaction *pos); + +/** + * Iterates on the "next" element to be processed. To + * be used when the current element _gets_ inserted in the result. + * + * @param ha history arguments. + * @param pos current element being processed. + * @return the next element to be processed. + */ +struct Transaction * +TFH_handle_history_step (const struct HistoryArgs *ha, + const struct Transaction *pos); + +/** + * Decides whether the history builder will advance or not + * to the next element. + * + * @param ha history args + * @return GNUNET_YES/NO to advance/not-advance. + */ +int +TFH_handle_history_range_advance (const struct HistoryArgs *ha, + const struct Transaction *pos); + +/** + * Iterates towards the "next" element to be processed. To + * be used when the current element does not get inserted in + * the result. + * + * @param ha history arguments. + * @param pos current element being processed. + * @return the next element to be processed. + */ +struct Transaction * +TFH_handle_history_range_skip (const struct HistoryArgs *ha, + const struct Transaction *pos); + +/** + * Iterates on the "next" element to be processed. To + * be used when the current element _gets_ inserted in the result. + * Same implementation of the "skip" counterpart, as /history-range + * does not have the notion of count/delta. + */ +Step TFH_handle_history_range_step; +#endif diff --git a/src/bank-lib/fakebank_history.c b/src/bank-lib/fakebank_history.c new file mode 100644 index 000000000..17960a4e5 --- /dev/null +++ b/src/bank-lib/fakebank_history.c @@ -0,0 +1,427 @@ +/* + This file is part of TALER + (C) 2016, 2017, 2018 Inria and 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 +*/ + +/** + * @file bank-lib/fakebank_history.c + * @brief definitions for the "/history[-range]" layer. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include +#include "taler_json_lib.h" +#include "fakebank.h" + +/** + * Decides whether the history builder will advance or not + * to the next element. + * + * @param ha history args + * @return GNUNET_YES/NO to advance/not-advance. + */ +int +TFH_handle_history_advance (const struct HistoryArgs *ha, + const struct Transaction *pos) +{ + const struct HistoryRangeIds *hri = ha->range; + + return (NULL != pos) && (0 != hri->count); +} + + +/** + * Iterates on the "next" element to be processed. To + * be used when the current element does not get inserted in + * the result. + * + * @param ha history arguments. + * @param pos current element being processed. + * @return the next element to be processed. + */ +struct Transaction * +TFH_handle_history_skip (const struct HistoryArgs *ha, + const struct Transaction *pos) +{ + const struct HistoryRangeIds *hri = ha->range; + + if (hri->count > 0) + return pos->next; + if (hri->count < 0) + return pos->prev; + return NULL; +} + + +/** + * Iterates on the "next" element to be processed. To + * be used when the current element _gets_ inserted in the result. + * + * @param ha history arguments. + * @param pos current element being processed. + * @return the next element to be processed. + */ +struct Transaction * +TFH_handle_history_step (const struct HistoryArgs *ha, + const struct Transaction *pos) +{ + struct HistoryRangeIds *hri = ha->range; + + if (hri->count > 0) + { + hri->count--; + return pos->next; + } + if (hri->count < 0) + { + hri->count++; + return pos->prev; + } + return NULL; +} + + +/** + * Decides whether the history builder will advance or not + * to the next element. + * + * @param ha history args + * @return GNUNET_YES/NO to advance/not-advance. + */ +int +TFH_handle_history_range_advance (const struct HistoryArgs *ha, + const struct Transaction *pos) +{ + const struct HistoryRangeDates *hrd = ha->range; + + if ( (NULL != pos) && + (pos->date.abs_value_us <= hrd->end.abs_value_us) ) + return GNUNET_YES; + + return GNUNET_NO; +} + + +/** + * Iterates towards the "next" element to be processed. To + * be used when the current element does not get inserted in + * the result. + * + * @param ha history arguments. + * @param pos current element being processed. + * @return the next element to be processed. + */ +struct Transaction * +TFH_handle_history_range_skip (const struct HistoryArgs *ha, + const struct Transaction *pos) +{ + /* Transactions + * are stored from "head"/older to "tail"/younger. */ + return pos->next; +} + +/** + * Iterates on the "next" element to be processed. To + * be used when the current element _gets_ inserted in the result. + * Same implementation of the "skip" counterpart, as /history-range + * does not have the notion of count/delta. + */ +Step TFH_handle_history_range_step = &TFH_handle_history_range_skip; + +/** + * Actual history response builder. + * + * @param pos first (included) element in the result set. + * @param ha history arguments. + * @param caller_name which function is building the history. + * @return MHD_YES / MHD_NO, after having enqueued the response + * object into MHD. + */ +int +TFH_build_history_response (struct MHD_Connection *connection, + struct Transaction *pos, + struct HistoryArgs *ha, + Skip skip, + Step step, + CheckAdvance advance) +{ + + struct HistoryElement *history_results_head = NULL; + struct HistoryElement *history_results_tail = NULL; + struct HistoryElement *history_element = NULL; + json_t *history; + json_t *jresponse; + int ret; + + while (advance (ha, + pos)) + { + json_t *trans; + char *subject; + const char *sign; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Found transaction over %s from %llu to %llu\n", + TALER_amount2s (&pos->amount), + (unsigned long long) pos->debit_account, + (unsigned long long) pos->credit_account); + + if ( (! ( ( (ha->account_number == pos->debit_account) && + (0 != (ha->direction & TALER_BANK_DIRECTION_DEBIT)) ) || + ( (ha->account_number == pos->credit_account) && + (0 != (ha->direction & TALER_BANK_DIRECTION_CREDIT) ) ) ) ) || + ( (0 == (ha->direction & TALER_BANK_DIRECTION_CANCEL)) && + (GNUNET_YES == pos->rejected) ) ) + { + pos = skip (ha, + pos); + continue; + } + + GNUNET_asprintf (&subject, + "%s %s", + pos->subject, + pos->exchange_base_url); + sign = + (ha->account_number == pos->debit_account) + ? (pos->rejected ? "cancel-" : "-") + : (pos->rejected ? "cancel+" : "+"); + trans = json_pack + ("{s:I, s:o, s:o, s:s, s:I, s:s}", + "row_id", (json_int_t) pos->row_id, + "date", GNUNET_JSON_from_time_abs (pos->date), + "amount", TALER_JSON_from_amount (&pos->amount), + "sign", sign, + "counterpart", (json_int_t) + ( (ha->account_number == pos->debit_account) + ? pos->credit_account + : pos->debit_account), + "wt_subject", subject); + GNUNET_assert (NULL != trans); + GNUNET_free (subject); + + history_element = GNUNET_new (struct HistoryElement); + history_element->element = trans; + + + /* XXX: the ordering feature is missing. */ + + GNUNET_CONTAINER_DLL_insert_tail (history_results_head, + history_results_tail, + history_element); + pos = step (ha, pos); + } + + history = json_array (); + if (NULL != history_results_head) + history_element = history_results_head; + + while (NULL != history_element) + { + json_array_append_new (history, + history_element->element); + history_element = history_element->next; + if (NULL != history_element) + GNUNET_free_non_null (history_element->prev); + } + GNUNET_free_non_null (history_results_tail); + + if (0 == json_array_size (history)) + { + struct MHD_Response *resp; + + json_decref (history); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Returning empty transaction history\n"); + resp = MHD_create_response_from_buffer + (0, + "", + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_NO_CONTENT, + resp); + MHD_destroy_response (resp); + return ret; + } + + jresponse = json_pack ("{s:o}", + "data", + history); + if (NULL == jresponse) + { + GNUNET_break (0); + return MHD_NO; + } + + /* Finally build response object */ + { + struct MHD_Response *resp; + void *json_str; + size_t json_len; + + json_str = json_dumps (jresponse, + JSON_INDENT(2)); + json_decref (jresponse); + if (NULL == json_str) + { + GNUNET_break (0); + return MHD_NO; + } + json_len = strlen (json_str); + resp = MHD_create_response_from_buffer (json_len, + json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == resp) + { + GNUNET_break (0); + free (json_str); + return MHD_NO; + } + (void) MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json"); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + resp); + MHD_destroy_response (resp); + } + return ret; +} + +/** + * Parse URL history arguments, of _both_ APIs: + * /history and /history-range. + * + * @param connection MHD connection. + * @param function_name name of the caller. + * @param ha[out] will contain the parsed values. + * @return GNUNET_OK only if the parsing succeedes. + */ +int +TFH_parse_history_common_args (struct MHD_Connection *connection, + struct HistoryArgs *ha) +{ + /** + * @variable + * Just check if given and == "basic", no need to keep around. + */ + const char *auth; + + /** + * All those will go into the structure, after parsing. + */ + const char *direction; + const char *cancelled; + const char *ordering; + const char *account_number; + + + auth = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "auth"); + direction = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "direction"); + cancelled = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "cancelled"); + ordering = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "ordering"); + account_number = MHD_lookup_connection_value + (connection, + MHD_GET_ARGUMENT_KIND, + "account_number"); + + /* Fail if one of the above failed. */ + if ( (NULL == direction) || + (NULL == cancelled) || + ( (0 != strcasecmp (cancelled, + "OMIT")) && + (0 != strcasecmp (cancelled, + "SHOW")) ) || + ( (0 != strcasecmp (direction, + "BOTH")) && + (0 != strcasecmp (direction, + "CREDIT")) && + (0 != strcasecmp (direction, + "DEBIT")) ) || + (1 != sscanf (account_number, + "%llu", + &ha->account_number)) || + ( (NULL == auth) || (0 != strcasecmp (auth, + "basic")) ) ) + { + /* Invalid request, given that this is fakebank we impolitely + * just kill the connection instead of returning a nice error. + */ + GNUNET_break (0); + return GNUNET_NO; + } + + if (0 == strcasecmp (direction, + "CREDIT")) + { + ha->direction = TALER_BANK_DIRECTION_CREDIT; + } + else if (0 == strcasecmp (direction, + "DEBIT")) + { + ha->direction = TALER_BANK_DIRECTION_DEBIT; + } + else if (0 == strcasecmp (direction, + "BOTH")) + { + ha->direction = TALER_BANK_DIRECTION_BOTH; + } + + /* Direction is invalid. */ + else + { + GNUNET_break (0); + return GNUNET_NO; + } + + if (0 == strcasecmp (cancelled, + "OMIT")) + { + /* nothing */ + } else if (0 == strcasecmp (cancelled, + "SHOW")) + { + ha->direction |= TALER_BANK_DIRECTION_CANCEL; + } + + /* Cancel-showing policy is invalid. */ + else + { + GNUNET_break (0); + return GNUNET_NO; + } + + if ((NULL != ordering) + && 0 == strcmp ("ascending", + ordering)) + ha->ascending = GNUNET_YES; + else + ha->ascending = GNUNET_NO; + + return GNUNET_OK; +} + +