Put /history[-range] logic in a dedicate file.

This commit is contained in:
Marcello Stanisci 2019-04-08 02:09:23 +02:00
parent 9fc380f7c8
commit e13a8902f0
No known key found for this signature in database
GPG Key ID: 8D526861953F4C0F
5 changed files with 768 additions and 937 deletions

1
.gitignore vendored
View File

@ -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/

View File

@ -57,6 +57,7 @@ libtalerfakebank_la_LDFLAGS = \
-no-undefined
libtalerfakebank_la_SOURCES = \
fakebank_history.c \
fakebank.c
libtalerfakebank_la_LIBADD = \

View File

@ -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,7 +603,7 @@ handle_history_new (struct TALER_FAKEBANK_Handle *h,
const char *delta;
struct Transaction *pos;
if (GNUNET_OK != parse_history_common_args (connection,
if (GNUNET_OK != TFH_parse_history_common_args (connection,
&ha))
{
GNUNET_break (0);
@ -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,
return TFH_build_history_response (connection,
pos,
&ha,
&handle_history_skip,
&handle_history_step,
&handle_history_advance);
&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,
return TFH_build_history_response (connection,
pos,
&ha,
&handle_history_range_skip,
handle_history_range_step,
&handle_history_range_advance);
&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.
*

322
src/bank-lib/fakebank.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>
*/
/**
* @file bank-lib/fakebank.h
* @brief definitions for the "/history[-range]" layer.
* @author Marcello Stanisci <stanisci.m@gmail.com>
*/
#ifndef FAKEBANK_H
#define FAKEBANK_H
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#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

View File

@ -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 <http://www.gnu.org/licenses/>
*/
/**
* @file bank-lib/fakebank_history.c
* @brief definitions for the "/history[-range]" layer.
* @author Marcello Stanisci <stanisci.m@gmail.com>
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#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;
}