From ed0da1fdb3e85a0d19148b3ec9eb481e8c06c025 Mon Sep 17 00:00:00 2001 From: Marcello Stanisci Date: Mon, 8 Apr 2019 23:53:52 +0200 Subject: /history-range. Implementing the "lib" and "testing-lib" functions to use it. --- src/bank-lib/bank_api_admin.c | 4 + src/bank-lib/bank_api_history.c | 333 ++++++++++++++++++++++++--------- src/bank-lib/fakebank.c | 11 +- src/bank-lib/test_bank_interpreter.c | 2 + src/bank-lib/testing_api_cmd_history.c | 209 ++++++++++++++++++++- 5 files changed, 462 insertions(+), 97 deletions(-) (limited to 'src/bank-lib') diff --git a/src/bank-lib/bank_api_admin.c b/src/bank-lib/bank_api_admin.c index 3a1ec4efa..8eff229f5 100644 --- a/src/bank-lib/bank_api_admin.c +++ b/src/bank-lib/bank_api_admin.c @@ -79,6 +79,7 @@ handle_admin_add_incoming_finished (void *cls, { struct TALER_BANK_AdminAddIncomingHandle *aai = cls; uint64_t row_id = UINT64_MAX; + struct GNUNET_TIME_Absolute timestamp; enum TALER_ErrorCode ec; const json_t *j = response; @@ -93,6 +94,8 @@ handle_admin_add_incoming_finished (void *cls, struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_uint64 ("row_id", &row_id), + GNUNET_JSON_spec_absolute_time ("timestamp", + ×tamp), GNUNET_JSON_spec_end() }; @@ -148,6 +151,7 @@ handle_admin_add_incoming_finished (void *cls, response_code, ec, row_id, + timestamp, j); TALER_BANK_admin_add_incoming_cancel (aai); } diff --git a/src/bank-lib/bank_api_history.c b/src/bank-lib/bank_api_history.c index ab757a2d2..4b9ea3c3c 100644 --- a/src/bank-lib/bank_api_history.c +++ b/src/bank-lib/bank_api_history.c @@ -2,22 +2,26 @@ This file is part of TALER Copyright (C) 2017 GNUnet e.V. & Inria - 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 - + 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/bank_api_history.c - * @brief Implementation of the /history requests of the bank's HTTP API + * @brief Implementation of the /history[-range] + * requests of the bank's HTTP API. * @author Christian Grothoff + * @author Marcello Stanisci */ #include "platform.h" #include "bank_api_common.h" @@ -60,7 +64,23 @@ struct TALER_BANK_HistoryHandle * Closure for @a cb. */ void *hcb_cls; +}; + + +/** + * Represent a URL argument+value pair. + */ +struct HistoryArgumentURL +{ + /** + * Name of the URL argument. + */ + char argument[20]; + /** + * Value of the URL argument. + */ + char value[20]; }; @@ -248,24 +268,202 @@ handle_history_finished (void *cls, } + +/** + * Backend of both the /history[-range] requests. + * + * @param ctx curl context for the event loop + * @param bank_base_url base URL of the bank. + * @param urlargs path + URL arguments. + * @param auth authentication data to use + * @param hres_cb the callback to call with the transaction + * history + * @param hres_cb_cls closure for the above callback + * @return NULL if the inputs are invalid (i.e. zero value for + * @e num_results). In this case, the callback is not + * called. + */ +static struct TALER_BANK_HistoryHandle * +put_history_job (struct GNUNET_CURL_Context *ctx, + const char *bank_base_url, + const char *urlargs, + const struct TALER_BANK_AuthenticationData *auth, + TALER_BANK_HistoryResultCallback hres_cb, + void *hres_cb_cls) +{ + struct TALER_BANK_HistoryHandle *hh; + CURL *eh; + + hh = GNUNET_new (struct TALER_BANK_HistoryHandle); + hh->hcb = hres_cb; + hh->hcb_cls = hres_cb_cls; + hh->bank_base_url = GNUNET_strdup (bank_base_url); + hh->request_url = TALER_BANK_path_to_url_ (bank_base_url, + urlargs); + + hh->authh = TALER_BANK_make_auth_header_ (auth); + eh = curl_easy_init (); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HTTPHEADER, + hh->authh)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + hh->request_url)); + hh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_NO, + &handle_history_finished, + hh); + return hh; +} + + +/** + * Convert fixed value 'direction' into string. + * + * @param direction the value to convert. + * @return string representation of @a direction. When length + * is zero, an error occurred. + */ +static struct HistoryArgumentURL +conv_direction (enum TALER_BANK_Direction direction) +{ + struct HistoryArgumentURL ret; + + if (TALER_BANK_DIRECTION_NONE == direction) + { + /* Should just never happen. */ + GNUNET_assert (0); + return ret; + } + + if (TALER_BANK_DIRECTION_BOTH == + (TALER_BANK_DIRECTION_BOTH & direction)) + strcpy (&ret.value[0], + "both"); + else if (TALER_BANK_DIRECTION_CREDIT == + (TALER_BANK_DIRECTION_CREDIT & direction)) + strcpy (&ret.value[0], + "credit"); + else if (TALER_BANK_DIRECTION_DEBIT == + (TALER_BANK_DIRECTION_BOTH & direction)) /*why use 'both' flag?*/ + strcpy (&ret.value[0], + "debit"); + return ret; +} + + +/** + * Convert fixed value 'direction' into string representation + * of the "cancel" argument. + * + * @param direction the value to convert. + * @return string representation of @a direction. When length + * is zero, an error occurred. + */ +static struct HistoryArgumentURL +conv_cancel (enum TALER_BANK_Direction direction) +{ + struct HistoryArgumentURL ret; + + if (TALER_BANK_DIRECTION_CANCEL == + (TALER_BANK_DIRECTION_CANCEL & direction)) + strcpy (&ret.value[0], + "show"); + else + strcpy (&ret.value[0], + "omit"); + return ret; +} + +/** + * Request the wire transfer history of a bank account, + * using time stamps to narrow the results. + * + * @param ctx curl context for the event loop + * @param bank_base_url URL of the bank (used to execute this + * request) + * @param auth authentication data to use + * @param account_number which account number should we query + * @param direction what kinds of wire transfers should be + * returned + * @param ascending if GNUNET_YES, history elements will + * be returned in chronological order. + * @param start_date threshold for oldest result. + * @param end_date threshold for youngest result. + * @param hres_cb the callback to call with the transaction + * history + * @param hres_cb_cls closure for the above callback + * @return NULL if the inputs are invalid (i.e. zero value for + * @e num_results). In this case, the callback is not + * called. + */ +struct TALER_BANK_HistoryHandle * +TALER_BANK_history_range (struct GNUNET_CURL_Context *ctx, + const char *bank_base_url, + const struct TALER_BANK_AuthenticationData *auth, + uint64_t account_number, + enum TALER_BANK_Direction direction, + unsigned int ascending, + struct GNUNET_TIME_Absolute start_date, + struct GNUNET_TIME_Absolute end_date, + TALER_BANK_HistoryResultCallback hres_cb, + void *hres_cb_cls) +{ + struct TALER_BANK_HistoryHandle *hh; + char *url; + + GNUNET_TIME_round_abs (&start_date); + GNUNET_TIME_round_abs (&end_date); + + GNUNET_asprintf (&url, + "/history?auth=basic&account_number=%llu&start=%llu&end=%llu&direction=%s&cancelled=%s&ordering=%s", + (unsigned long long) account_number, + start_date.abs_value_us / 1000LL / 1000LL, + end_date.abs_value_us / 1000LL / 1000LL, + conv_direction (direction).value, + conv_cancel (direction).value, + (GNUNET_YES == ascending) ? "ascending" : "descending"); + + hh = put_history_job (ctx, + bank_base_url, + url, + auth, + hres_cb, + hres_cb_cls); + + GNUNET_free (url); + return hh; +} + + + /** * Request the wire transfer history of a bank account. * * @param ctx curl context for the event loop - * @param bank_base_url URL of the bank (used to execute this request) + * @param bank_base_url URL of the bank (used to execute this + * request) * @param auth authentication data to use * @param account_number which account number should we query - * @param direction what kinds of wire transfers should be returned - * @param ascending if GNUNET_YES, history elements will be returned in chronological order. - * @param start_row from which row on do we want to get results, use UINT64_MAX for the latest; exclusive - * @param num_results how many results do we want; negative numbers to go into the past, - * positive numbers to go into the future starting at @a start_row; - * must not be zero. - * @param hres_cb the callback to call with the transaction history + * @param direction what kinds of wire transfers should be + * returned + * @param ascending if GNUNET_YES, history elements will + * be returned in chronological order. + * @param start_row from which row on do we want to get results, + * use UINT64_MAX for the latest; exclusive + * @param num_results how many results do we want; + * negative numbers to go into the past, positive numbers + * to go into the future starting at @a start_row; + * must not be zero. + * @param hres_cb the callback to call with the transaction + * history * @param hres_cb_cls closure for the above callback - * @return NULL - * if the inputs are invalid (i.e. zero value for @e num_results). - * In this case, the callback is not called. + * @return NULL if the inputs are invalid (i.e. zero value for + * @e num_results). In this case, the callback is not + * called. */ struct TALER_BANK_HistoryHandle * TALER_BANK_history (struct GNUNET_CURL_Context *ctx, @@ -280,91 +478,44 @@ TALER_BANK_history (struct GNUNET_CURL_Context *ctx, void *hres_cb_cls) { struct TALER_BANK_HistoryHandle *hh; - CURL *eh; char *url; - const char *dir; - const char *can; if (0 == num_results) { GNUNET_break (0); return NULL; } - if (TALER_BANK_DIRECTION_NONE == direction) - { - GNUNET_break (0); - return NULL; - } - - dir = NULL; - if (TALER_BANK_DIRECTION_BOTH == (TALER_BANK_DIRECTION_BOTH & direction)) - dir = "both"; - else if (TALER_BANK_DIRECTION_CREDIT == (TALER_BANK_DIRECTION_CREDIT & direction)) - dir = "credit"; - else if (TALER_BANK_DIRECTION_DEBIT == (TALER_BANK_DIRECTION_BOTH & direction)) - dir = "debit"; - if (NULL == dir) - { - GNUNET_break (0); - return NULL; - } - if (TALER_BANK_DIRECTION_CANCEL == (TALER_BANK_DIRECTION_CANCEL & direction)) - can = "show"; - else - can = "omit"; + GNUNET_asprintf (&url, + "/history?auth=basic&account_number=%llu&delta=%lld&direction=%s&cancelled=%s&ordering=%s&start=%llu", + (unsigned long long) account_number, + (long long) num_results, + conv_direction (direction).value, + conv_cancel (direction).value, + (GNUNET_YES == ascending) ? "ascending" : "descending", + start_row); + + /* Locate and "cut" the 'start' argument, + * if the user didn't provide one. */ if (UINT64_MAX == start_row) - { - GNUNET_asprintf (&url, - "/history?auth=basic&account_number=%llu&delta=%lld&direction=%s&cancelled=%s&ordering=%s", - (unsigned long long) account_number, - (long long) num_results, - dir, - can, - (GNUNET_YES == ascending) ? "ascending" : "descending"); + *strstr (url, "&start=") = '\0'; - } - else - { - GNUNET_asprintf (&url, - "/history?auth=basic&account_number=%llu&delta=%lld&start=%llu&direction=%s&cancelled=%s&ordering=%s", - (unsigned long long) account_number, - (long long) num_results, - (unsigned long long) start_row, - dir, - can, - (GNUNET_YES == ascending) ? "ascending" : "descending"); - } + hh = put_history_job (ctx, + bank_base_url, + url, + auth, + hres_cb, + hres_cb_cls); - hh = GNUNET_new (struct TALER_BANK_HistoryHandle); - hh->hcb = hres_cb; - hh->hcb_cls = hres_cb_cls; - hh->bank_base_url = GNUNET_strdup (bank_base_url); - hh->request_url = TALER_BANK_path_to_url_ (bank_base_url, - url); GNUNET_free (url); - hh->authh = TALER_BANK_make_auth_header_ (auth); - eh = curl_easy_init (); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_HTTPHEADER, - hh->authh)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_URL, - hh->request_url)); - hh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_NO, - &handle_history_finished, - hh); return hh; } /** - * Cancel a history request. This function cannot be used on a request - * handle if a response is already served for it. + * Cancel a history request. This function cannot be + * used on a request handle if a response is already + * served for it. * * @param hh the history request handle */ diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index 37aae0423..9915516cd 100644 --- a/src/bank-lib/fakebank.c +++ b/src/bank-lib/fakebank.c @@ -160,11 +160,12 @@ TALER_FAKEBANK_make_transfer (struct TALER_FAKEBANK_Handle *h, struct Transaction *t; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Making transfer from %llu to %llu over %s and subject %s\n", + "Making transfer from %llu to %llu over %s and subject %s; for exchange: %s\n", (unsigned long long) debit_account, (unsigned long long) credit_account, TALER_amount2s (amount), - subject); + subject, + exchange_base_url); t = GNUNET_new (struct Transaction); t->debit_account = debit_account; t->credit_account = credit_account; @@ -463,9 +464,11 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, void *json_str; size_t json_len; - json = json_pack ("{s:I}", + json = json_pack ("{s:I, s:s}", "row_id", - (json_int_t) row_id); + (json_int_t) row_id, + "timestamp", "/Date(0)/"); /*dummy tmp */ + json_str = json_dumps (json, JSON_INDENT(2)); json_decref (json); diff --git a/src/bank-lib/test_bank_interpreter.c b/src/bank-lib/test_bank_interpreter.c index fc7657790..9430e84bb 100644 --- a/src/bank-lib/test_bank_interpreter.c +++ b/src/bank-lib/test_bank_interpreter.c @@ -530,6 +530,7 @@ next (struct InterpreterState *is) * 0 if the bank's reply is bogus (fails to follow the protocol) * @param ec taler status code * @param row_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error + * @param timestamp time stamp of when the transaction settled at the bank * @param json detailed response from the HTTPD, or NULL if reply was not in JSON */ static void @@ -537,6 +538,7 @@ add_incoming_cb (void *cls, unsigned int http_status, enum TALER_ErrorCode ec, uint64_t row_id, + struct GNUNET_TIME_Absolute timestamp, const json_t *json) { struct InterpreterState *is = cls; diff --git a/src/bank-lib/testing_api_cmd_history.c b/src/bank-lib/testing_api_cmd_history.c index e3b05c83a..37e15af82 100644 --- a/src/bank-lib/testing_api_cmd_history.c +++ b/src/bank-lib/testing_api_cmd_history.c @@ -90,6 +90,27 @@ struct HistoryState * chronological order. */ unsigned int ascending; + + /********************************** + * Following defs are specific to * + * the "/history-range" version. * + **********************************/ + + /** + * Last row number we want in the result. Only used + * as a trait source when using the /history-range API. + */ + const char *end_row_reference; + + /** + * Start date for /history-range. + */ + struct GNUNET_TIME_Absolute start_date; + + /** + * End date for /history-range. + */ + struct GNUNET_TIME_Absolute end_date; }; /** @@ -669,6 +690,8 @@ history_cb (void *cls, struct TALER_TESTING_Interpreter *is = cls; struct HistoryState *hs = is->commands[is->ip].cls; + /* Possibly we got the 204 status code + * as a "end of list" marker. */ if (MHD_HTTP_OK != http_status) { hs->hh = NULL; @@ -710,7 +733,7 @@ history_cb (void *cls, JSON_COMPACT); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Result %u was `%s'\n", - (unsigned int) hs->results_obtained, + (unsigned int) hs->results_obtained++, acc); if (NULL != acc) free (acc); @@ -758,7 +781,6 @@ history_run (void *cls, TALER_LOG_DEBUG ("row id (from trait) is %llu\n", (unsigned long long) row_id); - } auth = &AUTHS[hs->account_no - 1]; @@ -776,6 +798,81 @@ history_run (void *cls, } +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +history_range_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + + struct HistoryState *hs = cls; + const struct GNUNET_TIME_Absolute *start_date; + const struct GNUNET_TIME_Absolute *end_date; + struct TALER_BANK_AuthenticationData *auth; + + if (NULL != hs->start_row_reference) + { + + const struct TALER_TESTING_Command *history_cmd; + + history_cmd = TALER_TESTING_interpreter_lookup_command + (is, hs->start_row_reference); + + if (NULL == history_cmd) + TALER_TESTING_FAIL (is); + + if (GNUNET_OK != TALER_TESTING_get_trait_absolute_time + (history_cmd, 0, &start_date)) + TALER_TESTING_FAIL (is); + } + else + { + GNUNET_assert (UINT64_MAX != hs->start_date.abs_value_us); + start_date = &hs->start_date; + } + + if (NULL != hs->end_row_reference) + { + + const struct TALER_TESTING_Command *history_cmd; + + history_cmd = TALER_TESTING_interpreter_lookup_command + (is, hs->end_row_reference); + + if (NULL == history_cmd) + TALER_TESTING_FAIL (is); + + if (GNUNET_OK != TALER_TESTING_get_trait_absolute_time + (history_cmd, 0, &end_date)) + TALER_TESTING_FAIL (is); + } + else + { + GNUNET_assert (UINT64_MAX != hs->end_date.abs_value_us); + end_date = &hs->end_date; + } + + auth = &AUTHS[hs->account_no - 1]; + hs->hh = TALER_BANK_history_range (is->ctx, + hs->bank_url, + auth, + hs->account_no, + hs->direction, + hs->ascending, + *start_date, + *end_date, + &history_cb, + is); + GNUNET_assert (NULL != hs->hh); +} + + /** * Free the state from a "history" CMD, and possibly cancel * a pending operation thereof. @@ -848,4 +945,112 @@ TALER_TESTING_cmd_bank_history return cmd; } + +/** + * Make a "history-range" CMD, picking dates from traits. + * + * @param label command label. + * @param bank_url base URL of the bank offering the "history" + * operation. + * @param account_no bank account number to ask the history for. + * @param direction which direction this operation is interested. + * @param ascending if GNUNET_YES, the bank will return the rows + * in ascending (= chronological) order. + * @param start_row_reference reference to a command that can + * offer a absolute time to use as the 'start' argument + * for "/history-range". + * @param end_row_reference reference to a command that can + * offer a absolute time to use as the 'end' argument + * for "/history-range". + * @param num_result how many rows we want in the result. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_bank_history_range + (const char *label, + const char *bank_url, + uint64_t account_no, + enum TALER_BANK_Direction direction, + unsigned int ascending, + const char *start_row_reference, + const char *end_row_reference, + long long num_results) +{ + struct HistoryState *hs; + + hs = GNUNET_new (struct HistoryState); + hs->bank_url = bank_url; + hs->account_no = account_no; + hs->direction = direction; + hs->start_row_reference = start_row_reference; + hs->end_row_reference = end_row_reference; + hs->num_results = num_results; + hs->ascending = ascending; + hs->start_date = GNUNET_TIME_UNIT_FOREVER_ABS; + hs->end_date = GNUNET_TIME_UNIT_FOREVER_ABS; + + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = hs, + .run = &history_range_run, + .cleanup = &history_cleanup, + .traits = &history_traits + }; + + return cmd; +} + + +/** + * Make a "history-range" CMD, picking dates from the arguments. + * + * @param label command label. + * @param bank_url base URL of the bank offering the "history" + * operation. + * @param account_no bank account number to ask the history for. + * @param direction which direction this operation is interested. + * @param ascending if GNUNET_YES, the bank will return the rows + * in ascending (= chronological) order. + * @param start_date value for the 'start' argument + * of "/history-range". + * @param end_date value for the 'end' argument + * of "/history-range". + * @param num_result how many rows we want in the result. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_bank_history_range_with_dates + (const char *label, + const char *bank_url, + uint64_t account_no, + enum TALER_BANK_Direction direction, + unsigned int ascending, + struct GNUNET_TIME_Absolute start_date, + struct GNUNET_TIME_Absolute end_date, + long long num_results) +{ + struct HistoryState *hs; + + hs = GNUNET_new (struct HistoryState); + hs->bank_url = bank_url; + hs->account_no = account_no; + hs->direction = direction; + hs->num_results = num_results; + hs->ascending = ascending; + hs->start_date = start_date; + hs->end_date = start_date; + + struct TALER_TESTING_Command cmd = { + .label = label, + .cls = hs, + .run = &history_range_run, + .cleanup = &history_cleanup, + .traits = &history_traits + }; + + return cmd; +} + /* end of testing_api_cmd_history.c */ -- cgit v1.2.3