2016-04-17 23:14:23 +02:00
|
|
|
/*
|
|
|
|
This file is part of TALER
|
2018-08-19 11:20:35 +02:00
|
|
|
(C) 2016, 2017, 2018 Inria and GNUnet e.V.
|
2016-04-17 23:14:23 +02:00
|
|
|
|
2019-04-05 16:13:56 +02:00
|
|
|
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/>
|
2016-04-17 23:14:23 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @file bank-lib/fakebank.c
|
|
|
|
* @brief library that fakes being a Taler bank for testcases
|
|
|
|
* @author Christian Grothoff <christian@grothoff.org>
|
|
|
|
*/
|
|
|
|
#include "platform.h"
|
2016-06-08 11:35:28 +02:00
|
|
|
#include "taler_fakebank_lib.h"
|
2017-05-04 23:29:08 +02:00
|
|
|
#include "taler_bank_service.h"
|
2016-04-17 23:14:23 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Maximum POST request size (for /admin/add/incoming)
|
|
|
|
*/
|
|
|
|
#define REQUEST_BUFFER_MAX (4*1024)
|
|
|
|
|
|
|
|
|
2019-04-05 16:13:56 +02:00
|
|
|
/**
|
|
|
|
* 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__)
|
2016-04-17 23:14:23 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2017-06-11 15:25:59 +02:00
|
|
|
char *subject;
|
2017-03-02 06:26:12 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Base URL of the exchange.
|
|
|
|
*/
|
|
|
|
char *exchange_base_url;
|
2017-05-04 23:29:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* When did the transaction happen?
|
|
|
|
*/
|
|
|
|
struct GNUNET_TIME_Absolute date;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Number of this transaction.
|
|
|
|
*/
|
2017-12-08 17:04:11 +01:00
|
|
|
uint64_t row_id;
|
2017-12-06 19:24:00 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Flag set if the transfer was rejected.
|
|
|
|
*/
|
|
|
|
int rejected;
|
2017-12-10 16:02:03 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Has this transaction been subjected to #TALER_FAKEBANK_check()
|
|
|
|
* and should thus no longer be counted in
|
|
|
|
* #TALER_FAKEBANK_check_empty()?
|
|
|
|
*/
|
|
|
|
int checked;
|
2016-04-17 23:14:23 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2018-12-14 00:34:35 +01:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2019-04-05 16:13:56 +02:00
|
|
|
/**
|
|
|
|
* 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
|
2019-04-05 19:31:01 +02:00
|
|
|
* negative/positive, @a start will be strictly younger/older
|
|
|
|
* of any element in the result set.
|
2019-04-05 16:13:56 +02:00
|
|
|
*/
|
|
|
|
long long count;
|
|
|
|
};
|
|
|
|
|
2019-04-05 19:31:01 +02:00
|
|
|
|
2019-04-05 16:13:56 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
};
|
|
|
|
|
2016-04-17 23:14:23 +02:00
|
|
|
/**
|
|
|
|
* Handle for the fake bank.
|
|
|
|
*/
|
2016-06-08 11:35:28 +02:00
|
|
|
struct TALER_FAKEBANK_Handle
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
|
|
|
/**
|
|
|
|
* We store transactions in a DLL.
|
|
|
|
*/
|
|
|
|
struct Transaction *transactions_head;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* We store transactions in a DLL.
|
|
|
|
*/
|
|
|
|
struct Transaction *transactions_tail;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* HTTP server we run to pretend to be the "test" bank.
|
|
|
|
*/
|
|
|
|
struct MHD_Daemon *mhd_bank;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Task running HTTP server for the "test" bank.
|
|
|
|
*/
|
|
|
|
struct GNUNET_SCHEDULER_Task *mhd_task;
|
2017-05-04 23:29:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Number of transactions.
|
|
|
|
*/
|
2017-05-07 21:11:56 +02:00
|
|
|
uint64_t serial_counter;
|
2018-08-19 11:20:35 +02:00
|
|
|
|
|
|
|
#if EPOLL_SUPPORT
|
|
|
|
/**
|
|
|
|
* Boxed @e mhd_fd.
|
|
|
|
*/
|
|
|
|
struct GNUNET_NETWORK_Handle *mhd_rfd;
|
2019-02-15 12:41:26 +01:00
|
|
|
|
2018-08-19 11:20:35 +02:00
|
|
|
/**
|
|
|
|
* File descriptor to use to wait for MHD.
|
|
|
|
*/
|
|
|
|
int mhd_fd;
|
|
|
|
#endif
|
2016-04-17 23:14:23 +02:00
|
|
|
};
|
|
|
|
|
2019-04-05 19:31:01 +02:00
|
|
|
|
2016-04-17 23:14:23 +02:00
|
|
|
/**
|
|
|
|
* Check that the @a want_amount was transferred from
|
|
|
|
* the @a want_debit to the @a want_credit account. If
|
2017-06-11 15:25:59 +02:00
|
|
|
* so, set the @a subject to the transfer identifier.
|
2016-04-17 23:14:23 +02:00
|
|
|
* If not, return #GNUNET_SYSERR.
|
|
|
|
*
|
|
|
|
* @param h bank instance
|
|
|
|
* @param want_amount transfer amount desired
|
|
|
|
* @param want_debit account that should have been debited
|
2017-04-20 07:49:56 +02:00
|
|
|
* @param want_credit account that should have been credited
|
2017-03-02 06:26:12 +01:00
|
|
|
* @param exchange_base_url expected base URL of the exchange
|
|
|
|
* i.e. "https://example.com/"; may include a port
|
2017-06-11 15:28:18 +02:00
|
|
|
* @param[out] subject set to the wire transfer identifier
|
2016-04-17 23:14:23 +02:00
|
|
|
* @return #GNUNET_OK on success
|
|
|
|
*/
|
|
|
|
int
|
2016-06-08 11:35:28 +02:00
|
|
|
TALER_FAKEBANK_check (struct TALER_FAKEBANK_Handle *h,
|
|
|
|
const struct TALER_Amount *want_amount,
|
|
|
|
uint64_t want_debit,
|
|
|
|
uint64_t want_credit,
|
2017-03-02 06:26:12 +01:00
|
|
|
const char *exchange_base_url,
|
2017-06-11 15:25:59 +02:00
|
|
|
char **subject)
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
2017-06-11 15:25:59 +02:00
|
|
|
for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next)
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
|
|
|
if ( (want_debit == t->debit_account) &&
|
|
|
|
(want_credit == t->credit_account) &&
|
|
|
|
(0 == TALER_amount_cmp (want_amount,
|
2017-03-02 06:26:12 +01:00
|
|
|
&t->amount)) &&
|
2017-12-10 16:02:03 +01:00
|
|
|
(GNUNET_NO == t->checked) &&
|
2017-03-02 06:26:12 +01:00
|
|
|
(0 == strcasecmp (exchange_base_url,
|
|
|
|
t->exchange_base_url)) )
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
2017-12-10 16:02:03 +01:00
|
|
|
*subject = GNUNET_strdup (t->subject);
|
|
|
|
t->checked = GNUNET_YES;
|
2016-04-17 23:14:23 +02:00
|
|
|
return GNUNET_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fprintf (stderr,
|
|
|
|
"Did not find matching transaction!\nI have:\n");
|
2017-06-11 15:25:59 +02:00
|
|
|
for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next)
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
2017-12-10 16:02:03 +01:00
|
|
|
if (GNUNET_YES == t->checked)
|
|
|
|
continue;
|
2016-04-17 23:14:23 +02:00
|
|
|
fprintf (stderr,
|
2017-03-02 06:26:12 +01:00
|
|
|
"%llu -> %llu (%s) from %s\n",
|
2016-04-17 23:14:23 +02:00
|
|
|
(unsigned long long) t->debit_account,
|
|
|
|
(unsigned long long) t->credit_account,
|
2017-12-10 21:19:30 +01:00
|
|
|
TALER_amount2s (&t->amount),
|
2017-03-02 06:26:12 +01:00
|
|
|
t->exchange_base_url);
|
2016-04-17 23:14:23 +02:00
|
|
|
}
|
2017-12-10 21:19:30 +01:00
|
|
|
fprintf (stderr,
|
|
|
|
"I wanted:\n%llu -> %llu (%s) from %s\n",
|
|
|
|
(unsigned long long) want_debit,
|
|
|
|
(unsigned long long) want_credit,
|
|
|
|
TALER_amount2s (want_amount),
|
|
|
|
exchange_base_url);
|
2016-04-17 23:14:23 +02:00
|
|
|
return GNUNET_SYSERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-06-11 15:25:59 +02:00
|
|
|
/**
|
|
|
|
* Tell the fakebank to create another wire transfer.
|
|
|
|
*
|
|
|
|
* @param h fake bank handle
|
|
|
|
* @param debit_account account to debit
|
|
|
|
* @param credit_account account to credit
|
|
|
|
* @param amount amount to transfer
|
|
|
|
* @param subject wire transfer subject to use
|
|
|
|
* @param exchange_base_url exchange URL
|
2017-12-08 17:04:11 +01:00
|
|
|
* @return row_id of the transfer
|
2017-06-11 15:25:59 +02:00
|
|
|
*/
|
|
|
|
uint64_t
|
|
|
|
TALER_FAKEBANK_make_transfer (struct TALER_FAKEBANK_Handle *h,
|
|
|
|
uint64_t debit_account,
|
|
|
|
uint64_t credit_account,
|
|
|
|
const struct TALER_Amount *amount,
|
|
|
|
const char *subject,
|
|
|
|
const char *exchange_base_url)
|
|
|
|
{
|
|
|
|
struct Transaction *t;
|
|
|
|
|
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
|
|
|
"Making transfer from %llu to %llu over %s and subject %s\n",
|
|
|
|
(unsigned long long) debit_account,
|
|
|
|
(unsigned long long) credit_account,
|
|
|
|
TALER_amount2s (amount),
|
|
|
|
subject);
|
|
|
|
t = GNUNET_new (struct Transaction);
|
|
|
|
t->debit_account = debit_account;
|
|
|
|
t->credit_account = credit_account;
|
|
|
|
t->amount = *amount;
|
|
|
|
t->exchange_base_url = GNUNET_strdup (exchange_base_url);
|
2017-12-08 17:04:11 +01:00
|
|
|
t->row_id = ++h->serial_counter;
|
2017-06-11 15:25:59 +02:00
|
|
|
t->date = GNUNET_TIME_absolute_get ();
|
|
|
|
t->subject = GNUNET_strdup (subject);
|
|
|
|
GNUNET_TIME_round_abs (&t->date);
|
|
|
|
GNUNET_CONTAINER_DLL_insert_tail (h->transactions_head,
|
|
|
|
h->transactions_tail,
|
|
|
|
t);
|
2017-12-08 17:04:11 +01:00
|
|
|
return t->row_id;
|
2017-06-11 15:25:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-12-06 19:24:00 +01:00
|
|
|
/**
|
|
|
|
* Reject incoming wire transfer to account @a credit_account
|
|
|
|
* as identified by @a rowid.
|
|
|
|
*
|
|
|
|
* @param h fake bank handle
|
|
|
|
* @param rowid identifies transfer to reject
|
|
|
|
* @param credit_account account number of owner of credited account
|
|
|
|
* @return #GNUNET_YES on success, #GNUNET_NO if the wire transfer was not found
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
TALER_FAKEBANK_reject_transfer (struct TALER_FAKEBANK_Handle *h,
|
|
|
|
uint64_t rowid,
|
|
|
|
uint64_t credit_account)
|
|
|
|
{
|
|
|
|
for (struct Transaction *t = h->transactions_head; NULL != t; t = t->next)
|
2017-12-08 17:04:11 +01:00
|
|
|
if ( (t->row_id == rowid) &&
|
2017-12-06 19:24:00 +01:00
|
|
|
(t->credit_account == credit_account) )
|
|
|
|
{
|
|
|
|
t->rejected = GNUNET_YES;
|
|
|
|
return GNUNET_YES;
|
|
|
|
}
|
|
|
|
return GNUNET_NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-17 23:14:23 +02:00
|
|
|
/**
|
|
|
|
* Check that no wire transfers were ordered (or at least none
|
2016-06-08 11:35:28 +02:00
|
|
|
* that have not been taken care of via #TALER_FAKEBANK_check()).
|
2016-04-17 23:14:23 +02:00
|
|
|
* If any transactions are onrecord, return #GNUNET_SYSERR.
|
|
|
|
*
|
|
|
|
* @param h bank instance
|
|
|
|
* @return #GNUNET_OK on success
|
|
|
|
*/
|
|
|
|
int
|
2016-06-08 11:35:28 +02:00
|
|
|
TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h)
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
|
|
|
struct Transaction *t;
|
|
|
|
|
2017-12-10 16:02:03 +01:00
|
|
|
t = h->transactions_head;
|
|
|
|
while (NULL != t)
|
|
|
|
{
|
|
|
|
if ( (GNUNET_YES != t->checked) &&
|
|
|
|
(GNUNET_YES != t->rejected) )
|
|
|
|
break;
|
|
|
|
t = t->next;
|
|
|
|
}
|
|
|
|
if (NULL == t)
|
2016-04-17 23:14:23 +02:00
|
|
|
return GNUNET_OK;
|
|
|
|
fprintf (stderr,
|
|
|
|
"Expected empty transaction set, but I have:\n");
|
2017-12-10 16:02:03 +01:00
|
|
|
while (NULL != t)
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
2017-12-10 16:02:03 +01:00
|
|
|
if ( (GNUNET_YES != t->checked) &&
|
|
|
|
(GNUNET_YES != t->rejected) )
|
|
|
|
{
|
|
|
|
char *s;
|
|
|
|
|
|
|
|
s = TALER_amount_to_string (&t->amount);
|
|
|
|
fprintf (stderr,
|
|
|
|
"%llu -> %llu (%s) from %s\n",
|
|
|
|
(unsigned long long) t->debit_account,
|
|
|
|
(unsigned long long) t->credit_account,
|
|
|
|
s,
|
|
|
|
t->exchange_base_url);
|
|
|
|
GNUNET_free (s);
|
|
|
|
}
|
|
|
|
t = t->next;
|
2016-04-17 23:14:23 +02:00
|
|
|
}
|
|
|
|
return GNUNET_SYSERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop running the fake bank.
|
|
|
|
*
|
|
|
|
* @param h bank to stop
|
|
|
|
*/
|
|
|
|
void
|
2016-06-08 11:35:28 +02:00
|
|
|
TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h)
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
2016-06-11 16:47:13 +02:00
|
|
|
struct Transaction *t;
|
|
|
|
|
|
|
|
while (NULL != (t = h->transactions_head))
|
|
|
|
{
|
|
|
|
GNUNET_CONTAINER_DLL_remove (h->transactions_head,
|
|
|
|
h->transactions_tail,
|
|
|
|
t);
|
2017-06-11 15:25:59 +02:00
|
|
|
GNUNET_free (t->subject);
|
2017-03-02 06:26:12 +01:00
|
|
|
GNUNET_free (t->exchange_base_url);
|
2016-06-11 16:47:13 +02:00
|
|
|
GNUNET_free (t);
|
|
|
|
}
|
2016-04-17 23:14:23 +02:00
|
|
|
if (NULL != h->mhd_task)
|
|
|
|
{
|
|
|
|
GNUNET_SCHEDULER_cancel (h->mhd_task);
|
|
|
|
h->mhd_task = NULL;
|
|
|
|
}
|
2018-08-19 11:20:35 +02:00
|
|
|
#if EPOLL_SUPPORT
|
|
|
|
GNUNET_NETWORK_socket_free_memory_only_ (h->mhd_rfd);
|
|
|
|
#endif
|
2016-04-17 23:14:23 +02:00
|
|
|
if (NULL != h->mhd_bank)
|
|
|
|
{
|
|
|
|
MHD_stop_daemon (h->mhd_bank);
|
|
|
|
h->mhd_bank = NULL;
|
|
|
|
}
|
|
|
|
GNUNET_free (h);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-12-06 19:24:00 +01:00
|
|
|
/**
|
|
|
|
* Create and queue a bank error message with the HTTP response
|
|
|
|
* code @a response_code on connection @a connection.
|
|
|
|
*
|
|
|
|
* @param connection where to queue the reply
|
|
|
|
* @param response_code http status code to use
|
|
|
|
* @param ec taler error code to use
|
|
|
|
* @param message human readable error message
|
|
|
|
* @return MHD status code
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
create_bank_error (struct MHD_Connection *connection,
|
|
|
|
unsigned int response_code,
|
|
|
|
enum TALER_ErrorCode ec,
|
|
|
|
const char *message)
|
|
|
|
{
|
|
|
|
json_t *json;
|
|
|
|
struct MHD_Response *resp;
|
|
|
|
void *json_str;
|
|
|
|
size_t json_len;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
json = json_pack ("{s:s, s:I}",
|
|
|
|
"error",
|
|
|
|
message,
|
|
|
|
"ec",
|
|
|
|
(json_int_t) ec);
|
|
|
|
json_str = json_dumps (json,
|
|
|
|
JSON_INDENT(2));
|
|
|
|
json_decref (json);
|
|
|
|
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,
|
|
|
|
response_code,
|
|
|
|
resp);
|
|
|
|
MHD_destroy_response (resp);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-17 23:14:23 +02:00
|
|
|
/**
|
|
|
|
* Function called whenever MHD is done with a request. If the
|
|
|
|
* request was a POST, we may have stored a `struct Buffer *` in the
|
|
|
|
* @a con_cls that might still need to be cleaned up. Call the
|
|
|
|
* respective function to free the memory.
|
|
|
|
*
|
|
|
|
* @param cls client-defined closure
|
|
|
|
* @param connection connection handle
|
|
|
|
* @param con_cls value as set by the last call to
|
|
|
|
* the #MHD_AccessHandlerCallback
|
|
|
|
* @param toe reason for request termination
|
|
|
|
* @see #MHD_OPTION_NOTIFY_COMPLETED
|
|
|
|
* @ingroup request
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
handle_mhd_completion_callback (void *cls,
|
|
|
|
struct MHD_Connection *connection,
|
|
|
|
void **con_cls,
|
|
|
|
enum MHD_RequestTerminationCode toe)
|
|
|
|
{
|
2016-06-08 11:35:28 +02:00
|
|
|
/* struct TALER_FAKEBANK_Handle *h = cls; */
|
2016-04-17 23:14:23 +02:00
|
|
|
|
|
|
|
GNUNET_JSON_post_parser_cleanup (*con_cls);
|
|
|
|
*con_cls = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2017-05-04 23:29:08 +02:00
|
|
|
* Handle incoming HTTP request for /admin/add/incoming.
|
2016-04-17 23:14:23 +02:00
|
|
|
*
|
2017-05-04 23:29:08 +02:00
|
|
|
* @param h the fakebank handle
|
2016-04-17 23:14:23 +02:00
|
|
|
* @param connection the connection
|
|
|
|
* @param upload_data request data
|
|
|
|
* @param upload_data_size size of @a upload_data in bytes
|
|
|
|
* @param con_cls closure for request (a `struct Buffer *`)
|
|
|
|
* @return MHD result code
|
|
|
|
*/
|
|
|
|
static int
|
2017-05-04 23:29:08 +02:00
|
|
|
handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h,
|
|
|
|
struct MHD_Connection *connection,
|
|
|
|
const char *upload_data,
|
|
|
|
size_t *upload_data_size,
|
|
|
|
void **con_cls)
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
|
|
|
enum GNUNET_JSON_PostResult pr;
|
|
|
|
json_t *json;
|
|
|
|
struct MHD_Response *resp;
|
|
|
|
int ret;
|
2017-12-08 17:04:11 +01:00
|
|
|
uint64_t row_id;
|
2016-04-17 23:14:23 +02:00
|
|
|
|
|
|
|
pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
|
|
|
|
con_cls,
|
|
|
|
upload_data,
|
|
|
|
upload_data_size,
|
|
|
|
&json);
|
|
|
|
switch (pr)
|
|
|
|
{
|
|
|
|
case GNUNET_JSON_PR_OUT_OF_MEMORY:
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
case GNUNET_JSON_PR_CONTINUE:
|
|
|
|
return MHD_YES;
|
|
|
|
case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
case GNUNET_JSON_PR_JSON_INVALID:
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
case GNUNET_JSON_PR_SUCCESS:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
{
|
2017-12-06 19:24:00 +01:00
|
|
|
const char *subject;
|
2017-06-11 15:25:59 +02:00
|
|
|
uint64_t debit_account;
|
|
|
|
uint64_t credit_account;
|
2017-03-02 06:26:12 +01:00
|
|
|
const char *base_url;
|
2017-06-11 15:25:59 +02:00
|
|
|
struct TALER_Amount amount;
|
2019-03-14 18:48:11 +01:00
|
|
|
char *amount_s;
|
2016-04-17 23:14:23 +02:00
|
|
|
struct GNUNET_JSON_Specification spec[] = {
|
2017-12-06 19:24:00 +01:00
|
|
|
GNUNET_JSON_spec_string ("subject", &subject),
|
2017-06-11 15:25:59 +02:00
|
|
|
GNUNET_JSON_spec_uint64 ("debit_account", &debit_account),
|
|
|
|
GNUNET_JSON_spec_uint64 ("credit_account", &credit_account),
|
|
|
|
TALER_JSON_spec_amount ("amount", &amount),
|
2017-03-02 06:26:12 +01:00
|
|
|
GNUNET_JSON_spec_string ("exchange_url", &base_url),
|
2016-04-17 23:14:23 +02:00
|
|
|
GNUNET_JSON_spec_end ()
|
|
|
|
};
|
|
|
|
if (GNUNET_OK !=
|
|
|
|
GNUNET_JSON_parse (json,
|
|
|
|
spec,
|
|
|
|
NULL, NULL))
|
|
|
|
{
|
|
|
|
GNUNET_break (0);
|
|
|
|
json_decref (json);
|
|
|
|
return MHD_NO;
|
|
|
|
}
|
2017-12-08 17:04:11 +01:00
|
|
|
row_id = TALER_FAKEBANK_make_transfer (h,
|
2019-03-21 18:45:50 +01:00
|
|
|
debit_account,
|
|
|
|
credit_account,
|
|
|
|
&amount,
|
|
|
|
subject,
|
|
|
|
base_url);
|
2019-03-14 18:48:11 +01:00
|
|
|
amount_s = TALER_amount_to_string (&amount);
|
2017-06-11 15:25:59 +02:00
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
2019-03-14 18:48:11 +01:00
|
|
|
"Receiving incoming wire transfer: %llu->%llu, subject: %s, amount: %s, from %s\n",
|
2017-06-11 15:25:59 +02:00
|
|
|
(unsigned long long) debit_account,
|
|
|
|
(unsigned long long) credit_account,
|
2019-03-14 18:48:11 +01:00
|
|
|
subject,
|
|
|
|
amount_s,
|
2017-06-11 15:25:59 +02:00
|
|
|
base_url);
|
2019-03-14 18:48:11 +01:00
|
|
|
GNUNET_free (amount_s);
|
2016-04-17 23:14:23 +02:00
|
|
|
}
|
|
|
|
json_decref (json);
|
2017-05-07 21:11:56 +02:00
|
|
|
|
|
|
|
/* Finally build response object */
|
|
|
|
{
|
|
|
|
void *json_str;
|
|
|
|
size_t json_len;
|
|
|
|
|
|
|
|
json = json_pack ("{s:I}",
|
2017-12-08 17:04:11 +01:00
|
|
|
"row_id",
|
|
|
|
(json_int_t) row_id);
|
2017-05-07 21:11:56 +02:00
|
|
|
json_str = json_dumps (json,
|
|
|
|
JSON_INDENT(2));
|
2017-06-04 12:07:53 +02:00
|
|
|
json_decref (json);
|
2017-05-07 21:11:56 +02:00
|
|
|
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");
|
|
|
|
}
|
2016-04-17 23:14:23 +02:00
|
|
|
ret = MHD_queue_response (connection,
|
|
|
|
MHD_HTTP_OK,
|
|
|
|
resp);
|
|
|
|
MHD_destroy_response (resp);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-12-06 19:24:00 +01:00
|
|
|
/**
|
|
|
|
* Handle incoming HTTP request for /reject.
|
|
|
|
*
|
|
|
|
* @param h the fakebank handle
|
|
|
|
* @param connection the connection
|
|
|
|
* @param upload_data request data
|
|
|
|
* @param upload_data_size size of @a upload_data in bytes
|
|
|
|
* @param con_cls closure for request (a `struct Buffer *`)
|
|
|
|
* @return MHD result code
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
handle_reject (struct TALER_FAKEBANK_Handle *h,
|
|
|
|
struct MHD_Connection *connection,
|
|
|
|
const char *upload_data,
|
|
|
|
size_t *upload_data_size,
|
|
|
|
void **con_cls)
|
|
|
|
{
|
|
|
|
enum GNUNET_JSON_PostResult pr;
|
|
|
|
json_t *json;
|
|
|
|
struct MHD_Response *resp;
|
|
|
|
int ret;
|
|
|
|
int found;
|
|
|
|
|
|
|
|
pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX,
|
|
|
|
con_cls,
|
|
|
|
upload_data,
|
|
|
|
upload_data_size,
|
|
|
|
&json);
|
|
|
|
switch (pr)
|
|
|
|
{
|
|
|
|
case GNUNET_JSON_PR_OUT_OF_MEMORY:
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
case GNUNET_JSON_PR_CONTINUE:
|
|
|
|
return MHD_YES;
|
|
|
|
case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
case GNUNET_JSON_PR_JSON_INVALID:
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
case GNUNET_JSON_PR_SUCCESS:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
{
|
2017-12-08 17:04:11 +01:00
|
|
|
uint64_t row_id;
|
2017-12-06 19:24:00 +01:00
|
|
|
uint64_t credit_account;
|
|
|
|
struct GNUNET_JSON_Specification spec[] = {
|
2017-12-08 17:04:11 +01:00
|
|
|
GNUNET_JSON_spec_uint64 ("row_id", &row_id),
|
2017-12-13 19:07:41 +01:00
|
|
|
GNUNET_JSON_spec_uint64 ("account_number", &credit_account),
|
2017-12-06 19:24:00 +01:00
|
|
|
GNUNET_JSON_spec_end ()
|
|
|
|
};
|
|
|
|
if (GNUNET_OK !=
|
|
|
|
GNUNET_JSON_parse (json,
|
|
|
|
spec,
|
|
|
|
NULL, NULL))
|
|
|
|
{
|
|
|
|
GNUNET_break (0);
|
|
|
|
json_decref (json);
|
|
|
|
return MHD_NO;
|
|
|
|
}
|
|
|
|
found = TALER_FAKEBANK_reject_transfer (h,
|
2017-12-08 17:04:11 +01:00
|
|
|
row_id,
|
2017-12-06 19:24:00 +01:00
|
|
|
credit_account);
|
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
|
|
|
"Rejected wire transfer #%llu (to %llu)\n",
|
2017-12-08 17:04:11 +01:00
|
|
|
(unsigned long long) row_id,
|
2017-12-06 19:24:00 +01:00
|
|
|
(unsigned long long) credit_account);
|
|
|
|
}
|
|
|
|
json_decref (json);
|
|
|
|
|
|
|
|
if (GNUNET_OK != found)
|
2019-04-07 23:46:33 +02:00
|
|
|
return create_bank_error
|
|
|
|
(connection,
|
|
|
|
MHD_HTTP_NOT_FOUND,
|
|
|
|
TALER_EC_BANK_REJECT_TRANSACTION_NOT_FOUND,
|
|
|
|
"transaction unknown");
|
2017-12-06 19:24:00 +01:00
|
|
|
/* finally build regular response */
|
|
|
|
resp = MHD_create_response_from_buffer (0,
|
|
|
|
NULL,
|
|
|
|
MHD_RESPMEM_PERSISTENT);
|
|
|
|
ret = MHD_queue_response (connection,
|
|
|
|
MHD_HTTP_NO_CONTENT,
|
|
|
|
resp);
|
|
|
|
MHD_destroy_response (resp);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-04-07 23:46:33 +02:00
|
|
|
/***********************************
|
|
|
|
* Serving "/history" starts here. *
|
|
|
|
***********************************/
|
2019-04-05 16:13:56 +02:00
|
|
|
|
2019-04-08 00:23:07 +02:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-04-05 16:13:56 +02:00
|
|
|
|
2019-04-05 19:31:01 +02:00
|
|
|
/**
|
|
|
|
* 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,
|
2019-04-08 00:23:07 +02:00
|
|
|
const struct Transaction *pos)
|
2019-04-05 19:31:01 +02:00
|
|
|
{
|
|
|
|
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;
|
2019-04-08 00:33:57 +02:00
|
|
|
return NULL;
|
2019-04-05 19:31:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 *
|
2019-04-07 23:46:33 +02:00
|
|
|
handle_history_step (const struct HistoryArgs *ha,
|
2019-04-05 19:31:01 +02:00
|
|
|
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;
|
|
|
|
}
|
2019-04-08 00:33:57 +02:00
|
|
|
return NULL;
|
2019-04-05 19:31:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2019-04-08 00:23:07 +02:00
|
|
|
handle_history_range_advance (const struct HistoryArgs *ha,
|
|
|
|
const struct Transaction *pos)
|
2019-04-05 19:31:01 +02:00
|
|
|
{
|
|
|
|
const struct HistoryRangeDates *hrd = ha->range;
|
2019-04-08 00:23:07 +02:00
|
|
|
|
|
|
|
if ( (NULL != pos) &&
|
|
|
|
(pos->date.abs_value_us <= hrd->end.abs_value_us) )
|
|
|
|
return GNUNET_YES;
|
|
|
|
|
|
|
|
return GNUNET_NO;
|
2019-04-05 19:31:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 *
|
2019-04-08 00:23:07 +02:00
|
|
|
handle_history_range_skip (const struct HistoryArgs *ha,
|
|
|
|
const struct Transaction *pos)
|
2019-04-05 19:31:01 +02:00
|
|
|
{
|
2019-04-08 00:23:07 +02:00
|
|
|
/* Transactions
|
|
|
|
* are stored from "head"/older to "tail"/younger. */
|
|
|
|
return pos->next;
|
2019-04-05 19:31:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterates on the "next" element to be processed. To
|
|
|
|
* be used when the current element _gets_ inserted in the result.
|
2019-04-08 00:23:07 +02:00
|
|
|
* Same implementation of the "skip" counterpart, as /history-range
|
|
|
|
* does not have the notion of count/delta.
|
2019-04-05 19:31:01 +02:00
|
|
|
*/
|
2019-04-08 00:23:07 +02:00
|
|
|
Step handle_history_range_step = handle_history_range_skip;
|
2019-04-07 23:46:33 +02:00
|
|
|
|
2019-04-05 19:31:01 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
2019-04-07 23:31:18 +02:00
|
|
|
* @return MHD_YES / MHD_NO, after having enqueued the response
|
|
|
|
* object into MHD.
|
2019-04-05 19:31:01 +02:00
|
|
|
*/
|
2019-04-07 23:46:33 +02:00
|
|
|
static int
|
2019-04-07 23:31:18 +02:00
|
|
|
build_history_response (struct MHD_Connection *connection,
|
|
|
|
struct Transaction *pos,
|
2019-04-05 19:31:01 +02:00
|
|
|
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;
|
2019-04-07 23:31:18 +02:00
|
|
|
json_t *history;
|
|
|
|
json_t *jresponse;
|
|
|
|
int ret;
|
2019-04-05 19:31:01 +02:00
|
|
|
|
|
|
|
while (GNUNET_YES == 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);
|
|
|
|
}
|
2019-04-07 23:31:18 +02:00
|
|
|
|
|
|
|
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;
|
2019-04-05 16:13:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-04-07 23:46:33 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
* @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_new (struct TALER_FAKEBANK_Handle *h,
|
|
|
|
struct MHD_Connection *connection,
|
|
|
|
void **con_cls)
|
|
|
|
{
|
|
|
|
struct HistoryArgs ha;
|
|
|
|
struct HistoryRangeIds hri;
|
|
|
|
const char *start;
|
|
|
|
const char *delta;
|
|
|
|
struct Transaction *pos;
|
|
|
|
|
|
|
|
if (GNUNET_OK != parse_history_common_args (connection,
|
|
|
|
&ha))
|
|
|
|
{
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
start = MHD_lookup_connection_value (connection,
|
|
|
|
MHD_GET_ARGUMENT_KIND,
|
|
|
|
"start");
|
|
|
|
delta = MHD_lookup_connection_value (connection,
|
|
|
|
MHD_GET_ARGUMENT_KIND,
|
|
|
|
"delta");
|
|
|
|
if ( ((NULL != start) && (1 != sscanf (start,
|
|
|
|
"%llu",
|
|
|
|
&hri.start))) ||
|
|
|
|
(NULL == delta) || (1 != sscanf (delta,
|
|
|
|
"%lld",
|
|
|
|
&hri.count)) )
|
|
|
|
{
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NULL == start)
|
|
|
|
pos = 0 > hri.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 == hri.start)
|
|
|
|
break;
|
|
|
|
if (NULL == pos)
|
|
|
|
{
|
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
|
|
|
"Invalid range specified,"
|
|
|
|
" transaction %llu not known!\n",
|
|
|
|
(unsigned long long) hri.start);
|
|
|
|
return MHD_NO;
|
|
|
|
}
|
|
|
|
/* range is exclusive, skip the matching entry */
|
|
|
|
if (hri.count > 0)
|
|
|
|
pos = pos->next;
|
|
|
|
if (hri.count < 0)
|
|
|
|
pos = pos->prev;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* list is empty */
|
|
|
|
pos = NULL;
|
|
|
|
}
|
2019-04-08 00:56:00 +02:00
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
|
|
|
"/history, start row (0 == no transactions exist): %llu\n",
|
|
|
|
NULL != pos ? pos->row_id : 0);
|
2019-04-07 23:46:33 +02:00
|
|
|
|
|
|
|
return build_history_response (connection,
|
|
|
|
pos,
|
|
|
|
&ha,
|
|
|
|
&handle_history_skip,
|
|
|
|
&handle_history_step,
|
|
|
|
&handle_history_advance);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle incoming HTTP request for /history-range.
|
|
|
|
*
|
|
|
|
* @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_range (struct TALER_FAKEBANK_Handle *h,
|
|
|
|
struct MHD_Connection *connection,
|
|
|
|
void **con_cls)
|
|
|
|
{
|
|
|
|
struct HistoryArgs ha;
|
|
|
|
struct HistoryRangeDates hrd;
|
|
|
|
const char *start;
|
|
|
|
const char *end;
|
|
|
|
long long unsigned int start_stamp;
|
|
|
|
long long unsigned int end_stamp;
|
2019-04-08 00:23:07 +02:00
|
|
|
struct Transaction *pos;
|
2019-04-07 23:46:33 +02:00
|
|
|
|
|
|
|
if (GNUNET_OK != parse_history_common_args (connection,
|
|
|
|
&ha))
|
|
|
|
{
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
}
|
|
|
|
start = MHD_lookup_connection_value (connection,
|
|
|
|
MHD_GET_ARGUMENT_KIND,
|
|
|
|
"start");
|
|
|
|
end = MHD_lookup_connection_value (connection,
|
|
|
|
MHD_GET_ARGUMENT_KIND,
|
|
|
|
"end");
|
|
|
|
|
|
|
|
if ( (NULL == start) || (1 != sscanf (start,
|
|
|
|
"%llu",
|
|
|
|
&start_stamp)) ||
|
|
|
|
(NULL == end) || (1 != sscanf (end,
|
|
|
|
"%lld",
|
|
|
|
&end_stamp)) )
|
|
|
|
{
|
|
|
|
GNUNET_break (0);
|
|
|
|
return GNUNET_NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
hrd.start.abs_value_us = start_stamp * 1000LL * 1000LL;
|
|
|
|
hrd.end.abs_value_us = end_stamp * 1000LL * 1000LL;
|
2019-04-08 00:23:07 +02:00
|
|
|
ha.range = &hrd;
|
|
|
|
|
|
|
|
pos = NULL;
|
|
|
|
/* hunt for 'pos' in the Transaction(s) LL. */
|
|
|
|
for (pos = h->transactions_head;
|
|
|
|
NULL != pos;
|
|
|
|
pos = pos->next)
|
|
|
|
{
|
|
|
|
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);
|
2019-04-07 23:46:33 +02:00
|
|
|
}
|
|
|
|
|
2017-05-04 23:29:08 +02:00
|
|
|
/**
|
|
|
|
* Handle incoming HTTP request for /history
|
|
|
|
*
|
|
|
|
* @param h the fakebank handle
|
|
|
|
* @param connection the connection
|
2017-06-04 11:30:43 +02:00
|
|
|
* @param con_cls place to store state, not used
|
2017-05-04 23:29:08 +02:00
|
|
|
* @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;
|
2017-12-06 19:24:00 +01:00
|
|
|
const char *cancelled;
|
2018-12-14 00:34:35 +01:00
|
|
|
const char *ordering;
|
2017-05-04 23:29:08 +02:00
|
|
|
unsigned long long account_number;
|
|
|
|
unsigned long long start_number;
|
|
|
|
long long count;
|
|
|
|
enum TALER_BANK_Direction direction;
|
|
|
|
struct Transaction *pos;
|
|
|
|
json_t *history;
|
2017-05-15 10:55:47 +02:00
|
|
|
json_t *jresponse;
|
2017-05-04 23:29:08 +02:00
|
|
|
int ret;
|
2018-12-14 00:34:35 +01:00
|
|
|
int ascending;
|
|
|
|
struct HistoryElement *history_results_head = NULL;
|
|
|
|
struct HistoryElement *history_results_tail = NULL;
|
|
|
|
struct HistoryElement *history_element = NULL;
|
2017-05-04 23:29:08 +02:00
|
|
|
|
|
|
|
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");
|
2017-12-06 19:24:00 +01:00
|
|
|
cancelled = MHD_lookup_connection_value (connection,
|
|
|
|
MHD_GET_ARGUMENT_KIND,
|
|
|
|
"cancelled");
|
2017-05-04 23:29:08 +02:00
|
|
|
start = MHD_lookup_connection_value (connection,
|
|
|
|
MHD_GET_ARGUMENT_KIND,
|
2017-05-15 20:33:02 +02:00
|
|
|
"start");
|
2018-12-14 00:34:35 +01:00
|
|
|
ordering = MHD_lookup_connection_value (connection,
|
|
|
|
MHD_GET_ARGUMENT_KIND,
|
|
|
|
"ordering");
|
2017-05-04 23:29:08 +02:00
|
|
|
acc = MHD_lookup_connection_value (connection,
|
|
|
|
MHD_GET_ARGUMENT_KIND,
|
|
|
|
"account_number");
|
|
|
|
if ( (NULL == auth) ||
|
2017-05-04 23:52:54 +02:00
|
|
|
(0 != strcasecmp (auth,
|
|
|
|
"basic")) ||
|
2017-05-04 23:29:08 +02:00
|
|
|
(NULL == acc) ||
|
|
|
|
(NULL == delta) )
|
|
|
|
{
|
2019-04-07 23:31:18 +02:00
|
|
|
/* Invalid request,
|
|
|
|
given that this is fakebank we impolitely just
|
2017-05-04 23:29:08 +02:00
|
|
|
kill the connection instead of returning a nice error. */
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
}
|
2019-02-15 12:41:26 +01:00
|
|
|
start_number = 0;
|
2017-05-04 23:29:08 +02:00
|
|
|
if ( (1 != sscanf (delta,
|
|
|
|
"%lld",
|
|
|
|
&count)) ||
|
|
|
|
(1 != sscanf (acc,
|
|
|
|
"%llu",
|
|
|
|
&account_number)) ||
|
|
|
|
( (NULL != start) &&
|
|
|
|
(1 != sscanf (start,
|
|
|
|
"%llu",
|
|
|
|
&start_number)) ) ||
|
2017-12-06 19:24:00 +01:00
|
|
|
(NULL == dir) ||
|
|
|
|
(NULL == cancelled) ||
|
|
|
|
( (0 != strcasecmp (cancelled,
|
|
|
|
"OMIT")) &&
|
|
|
|
(0 != strcasecmp (cancelled,
|
|
|
|
"SHOW")) ) ||
|
|
|
|
( (0 != strcasecmp (dir,
|
|
|
|
"BOTH")) &&
|
2017-05-04 23:29:08 +02:00
|
|
|
(0 != strcasecmp (dir,
|
|
|
|
"CREDIT")) &&
|
|
|
|
(0 != strcasecmp (dir,
|
|
|
|
"DEBIT")) ) )
|
|
|
|
{
|
2019-04-05 16:13:56 +02:00
|
|
|
/* Invalid request, given that this is fakebank we impolitely
|
|
|
|
* just kill the connection instead of returning a nice error.
|
|
|
|
*/
|
2017-05-04 23:29:08 +02:00
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
}
|
2017-06-11 17:04:54 +02:00
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
2018-12-14 00:34:35 +01:00
|
|
|
"Client asked for up to %lld results of type %s for account %llu starting at %llu\n",
|
2017-06-11 17:04:54 +02:00
|
|
|
count,
|
|
|
|
dir,
|
|
|
|
(unsigned long long) account_number,
|
2018-12-14 00:34:35 +01:00
|
|
|
start_number);
|
2017-12-06 19:24:00 +01:00
|
|
|
if (0 == strcasecmp (dir,
|
|
|
|
"CREDIT"))
|
|
|
|
{
|
2017-05-04 23:29:08 +02:00
|
|
|
direction = TALER_BANK_DIRECTION_CREDIT;
|
2017-12-06 19:24:00 +01:00
|
|
|
}
|
|
|
|
else if (0 == strcasecmp (dir,
|
|
|
|
"DEBIT"))
|
|
|
|
{
|
2017-05-04 23:29:08 +02:00
|
|
|
direction = TALER_BANK_DIRECTION_DEBIT;
|
2017-12-06 19:24:00 +01:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2019-03-21 18:45:50 +01:00
|
|
|
|
2017-05-04 23:29:08 +02:00
|
|
|
if (NULL == start)
|
2019-03-21 18:45:50 +01:00
|
|
|
pos = 0 > count ? h->transactions_tail : h->transactions_head;
|
|
|
|
|
2017-06-11 16:06:52 +02:00
|
|
|
else if (NULL != h->transactions_head)
|
2017-05-04 23:29:08 +02:00
|
|
|
{
|
|
|
|
for (pos = h->transactions_head;
|
2017-05-09 13:24:53 +02:00
|
|
|
NULL != pos;
|
2017-05-07 21:11:56 +02:00
|
|
|
pos = pos->next)
|
2017-12-08 17:04:11 +01:00
|
|
|
if (pos->row_id == start_number)
|
2017-05-09 13:24:53 +02:00
|
|
|
break;
|
|
|
|
if (NULL == pos)
|
2017-05-04 23:29:08 +02:00
|
|
|
{
|
2017-12-10 16:02:03 +01:00
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
|
|
|
"Invalid range specified, transaction %llu not known!\n",
|
|
|
|
(unsigned long long) start_number);
|
2017-05-09 13:24:53 +02:00
|
|
|
return MHD_NO;
|
2017-05-04 23:29:08 +02:00
|
|
|
}
|
2017-05-07 21:11:56 +02:00
|
|
|
/* range is exclusive, skip the matching entry */
|
|
|
|
if (count > 0)
|
|
|
|
pos = pos->next;
|
|
|
|
if (count < 0)
|
|
|
|
pos = pos->prev;
|
2017-05-04 23:29:08 +02:00
|
|
|
}
|
2017-06-11 16:06:52 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
/* list is empty */
|
|
|
|
pos = NULL;
|
|
|
|
}
|
2017-06-11 17:04:54 +02:00
|
|
|
|
2017-05-04 23:29:08 +02:00
|
|
|
history = json_array ();
|
2018-12-14 00:34:35 +01:00
|
|
|
if ((NULL != ordering)
|
|
|
|
&& 0 == strcmp ("ascending",
|
|
|
|
ordering))
|
|
|
|
ascending = GNUNET_YES;
|
|
|
|
else
|
|
|
|
ascending = GNUNET_NO;
|
|
|
|
|
2017-05-04 23:29:08 +02:00
|
|
|
while ( (NULL != pos) &&
|
|
|
|
(0 != count) )
|
|
|
|
{
|
|
|
|
json_t *trans;
|
|
|
|
char *subject;
|
2017-12-06 19:24:00 +01:00
|
|
|
const char *sign;
|
2017-05-04 23:29:08 +02:00
|
|
|
|
2017-06-11 17:04:54 +02:00
|
|
|
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);
|
|
|
|
|
2017-12-06 19:24:00 +01:00
|
|
|
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) ) )
|
2017-05-04 23:29:08 +02:00
|
|
|
{
|
|
|
|
if (count > 0)
|
|
|
|
pos = pos->next;
|
|
|
|
if (count < 0)
|
|
|
|
pos = pos->prev;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-06-11 15:25:59 +02:00
|
|
|
GNUNET_asprintf (&subject,
|
|
|
|
"%s %s",
|
|
|
|
pos->subject,
|
|
|
|
pos->exchange_base_url);
|
2017-12-06 19:24:00 +01:00
|
|
|
sign =
|
|
|
|
(account_number == pos->debit_account)
|
|
|
|
? (pos->rejected ? "cancel-" : "-")
|
|
|
|
: (pos->rejected ? "cancel+" : "+");
|
2017-05-04 23:29:08 +02:00
|
|
|
trans = json_pack ("{s:I, s:o, s:o, s:s, s:I, s:s}",
|
2017-12-08 17:04:11 +01:00
|
|
|
"row_id", (json_int_t) pos->row_id,
|
2017-05-04 23:29:08 +02:00
|
|
|
"date", GNUNET_JSON_from_time_abs (pos->date),
|
|
|
|
"amount", TALER_JSON_from_amount (&pos->amount),
|
2017-12-06 19:24:00 +01:00
|
|
|
"sign", sign,
|
2017-05-04 23:29:08 +02:00
|
|
|
"counterpart", (json_int_t) ( (account_number == pos->debit_account)
|
|
|
|
? pos->credit_account
|
|
|
|
: pos->debit_account),
|
|
|
|
"wt_subject", subject);
|
2017-12-10 21:19:30 +01:00
|
|
|
GNUNET_assert (NULL != trans);
|
2017-05-04 23:29:08 +02:00
|
|
|
GNUNET_free (subject);
|
2019-02-15 12:41:26 +01:00
|
|
|
|
2018-12-14 00:34:35 +01:00
|
|
|
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);
|
2017-05-04 23:29:08 +02:00
|
|
|
if (count > 0)
|
|
|
|
{
|
|
|
|
pos = pos->next;
|
|
|
|
count--;
|
|
|
|
}
|
|
|
|
if (count < 0)
|
|
|
|
{
|
|
|
|
pos = pos->prev;
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
2018-12-14 00:34:35 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2017-05-04 23:29:08 +02:00
|
|
|
if (0 == json_array_size (history))
|
|
|
|
{
|
|
|
|
struct MHD_Response *resp;
|
|
|
|
|
|
|
|
json_decref (history);
|
2017-06-11 17:04:54 +02:00
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
|
|
|
"Returning empty transaction history\n");
|
2017-05-04 23:29:08 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-05-15 10:55:47 +02:00
|
|
|
jresponse = json_pack ("{s:o}",
|
2017-12-10 21:19:30 +01:00
|
|
|
"data",
|
|
|
|
history);
|
2017-05-15 10:55:47 +02:00
|
|
|
if (NULL == jresponse)
|
|
|
|
{
|
|
|
|
GNUNET_break (0);
|
|
|
|
return MHD_NO;
|
|
|
|
}
|
|
|
|
|
2017-05-04 23:29:08 +02:00
|
|
|
/* Finally build response object */
|
|
|
|
{
|
|
|
|
struct MHD_Response *resp;
|
|
|
|
void *json_str;
|
|
|
|
size_t json_len;
|
|
|
|
|
2017-05-15 10:55:47 +02:00
|
|
|
json_str = json_dumps (jresponse,
|
2017-05-04 23:29:08 +02:00
|
|
|
JSON_INDENT(2));
|
2017-05-15 10:55:47 +02:00
|
|
|
json_decref (jresponse);
|
2017-05-04 23:29:08 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-05-07 21:11:56 +02:00
|
|
|
(void) MHD_add_response_header (resp,
|
|
|
|
MHD_HTTP_HEADER_CONTENT_TYPE,
|
|
|
|
"application/json");
|
2017-05-04 23:29:08 +02:00
|
|
|
ret = MHD_queue_response (connection,
|
|
|
|
MHD_HTTP_OK,
|
|
|
|
resp);
|
|
|
|
MHD_destroy_response (resp);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-04-07 23:46:33 +02:00
|
|
|
/***********************************
|
|
|
|
* End of /history implementation. *
|
|
|
|
***********************************/
|
2017-05-04 23:29:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle incoming HTTP request.
|
|
|
|
*
|
|
|
|
* @param cls a `struct TALER_FAKEBANK_Handle`
|
|
|
|
* @param connection the connection
|
|
|
|
* @param url the requested url
|
|
|
|
* @param method the method (POST, GET, ...)
|
|
|
|
* @param version HTTP version (ignored)
|
|
|
|
* @param upload_data request data
|
|
|
|
* @param upload_data_size size of @a upload_data in bytes
|
|
|
|
* @param con_cls closure for request (a `struct Buffer *`)
|
|
|
|
* @return MHD result code
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
handle_mhd_request (void *cls,
|
|
|
|
struct MHD_Connection *connection,
|
|
|
|
const char *url,
|
|
|
|
const char *method,
|
|
|
|
const char *version,
|
|
|
|
const char *upload_data,
|
|
|
|
size_t *upload_data_size,
|
|
|
|
void **con_cls)
|
|
|
|
{
|
|
|
|
struct TALER_FAKEBANK_Handle *h = cls;
|
|
|
|
|
|
|
|
if ( (0 == strcasecmp (url,
|
|
|
|
"/admin/add/incoming")) &&
|
|
|
|
(0 == strcasecmp (method,
|
|
|
|
MHD_HTTP_METHOD_POST)) )
|
|
|
|
return handle_admin_add_incoming (h,
|
|
|
|
connection,
|
|
|
|
upload_data,
|
|
|
|
upload_data_size,
|
|
|
|
con_cls);
|
2017-12-06 19:24:00 +01:00
|
|
|
if ( (0 == strcasecmp (url,
|
|
|
|
"/reject")) &&
|
|
|
|
(0 == strcasecmp (method,
|
|
|
|
MHD_HTTP_METHOD_POST)) )
|
|
|
|
return handle_reject (h,
|
|
|
|
connection,
|
|
|
|
upload_data,
|
|
|
|
upload_data_size,
|
|
|
|
con_cls);
|
2019-04-08 00:23:07 +02:00
|
|
|
if ( (0 == strcasecmp (url,
|
|
|
|
"/history-range")) &&
|
|
|
|
(0 == strcasecmp (method,
|
|
|
|
MHD_HTTP_METHOD_GET)) )
|
|
|
|
return handle_history_range (h,
|
|
|
|
connection,
|
|
|
|
con_cls);
|
2017-05-04 23:29:08 +02:00
|
|
|
if ( (0 == strcasecmp (url,
|
|
|
|
"/history")) &&
|
|
|
|
(0 == strcasecmp (method,
|
|
|
|
MHD_HTTP_METHOD_GET)) )
|
2019-04-08 00:33:57 +02:00
|
|
|
return handle_history_new (h,
|
|
|
|
connection,
|
|
|
|
con_cls);
|
2017-05-04 23:29:08 +02:00
|
|
|
|
2018-01-30 01:38:04 +01:00
|
|
|
/* Unexpected URL path, just close the connection. */
|
2017-05-04 23:29:08 +02:00
|
|
|
/* we're rather impolite here, but it's a testcase. */
|
2018-06-12 19:16:43 +02:00
|
|
|
TALER_LOG_ERROR ("Breaking URL: %s\n",
|
|
|
|
url);
|
2017-05-04 23:29:08 +02:00
|
|
|
GNUNET_break_op (0);
|
|
|
|
return MHD_NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-17 23:14:23 +02:00
|
|
|
/**
|
|
|
|
* Task run whenever HTTP server operations are pending.
|
|
|
|
*
|
2016-06-08 11:35:28 +02:00
|
|
|
* @param cls the `struct TALER_FAKEBANK_Handle`
|
2016-04-17 23:14:23 +02:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
run_mhd (void *cls);
|
|
|
|
|
|
|
|
|
2018-08-19 11:20:35 +02:00
|
|
|
#if EPOLL_SUPPORT
|
|
|
|
/**
|
|
|
|
* Schedule MHD. This function should be called initially when an
|
|
|
|
* MHD is first getting its client socket, and will then automatically
|
|
|
|
* always be called later whenever there is work to be done.
|
|
|
|
*
|
|
|
|
* @param h fakebank handle to schedule MHD for
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
schedule_httpd (struct TALER_FAKEBANK_Handle *h)
|
|
|
|
{
|
|
|
|
int haveto;
|
|
|
|
MHD_UNSIGNED_LONG_LONG timeout;
|
|
|
|
struct GNUNET_TIME_Relative tv;
|
|
|
|
|
|
|
|
haveto = MHD_get_timeout (h->mhd_bank,
|
|
|
|
&timeout);
|
|
|
|
if (MHD_YES == haveto)
|
|
|
|
tv.rel_value_us = (uint64_t) timeout * 1000LL;
|
|
|
|
else
|
|
|
|
tv = GNUNET_TIME_UNIT_FOREVER_REL;
|
|
|
|
if (NULL != h->mhd_task)
|
|
|
|
GNUNET_SCHEDULER_cancel (h->mhd_task);
|
|
|
|
h->mhd_task =
|
|
|
|
GNUNET_SCHEDULER_add_read_net (tv,
|
|
|
|
h->mhd_rfd,
|
|
|
|
&run_mhd,
|
|
|
|
h);
|
|
|
|
}
|
|
|
|
#else
|
2016-04-17 23:14:23 +02:00
|
|
|
/**
|
|
|
|
* Schedule MHD. This function should be called initially when an
|
|
|
|
* MHD is first getting its client socket, and will then automatically
|
|
|
|
* always be called later whenever there is work to be done.
|
2018-08-19 11:20:35 +02:00
|
|
|
*
|
|
|
|
* @param h fakebank handle to schedule MHD for
|
2016-04-17 23:14:23 +02:00
|
|
|
*/
|
|
|
|
static void
|
2016-06-08 11:35:28 +02:00
|
|
|
schedule_httpd (struct TALER_FAKEBANK_Handle *h)
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
|
|
|
fd_set rs;
|
|
|
|
fd_set ws;
|
|
|
|
fd_set es;
|
|
|
|
struct GNUNET_NETWORK_FDSet *wrs;
|
|
|
|
struct GNUNET_NETWORK_FDSet *wws;
|
|
|
|
int max;
|
|
|
|
int haveto;
|
|
|
|
MHD_UNSIGNED_LONG_LONG timeout;
|
|
|
|
struct GNUNET_TIME_Relative tv;
|
|
|
|
|
|
|
|
FD_ZERO (&rs);
|
|
|
|
FD_ZERO (&ws);
|
|
|
|
FD_ZERO (&es);
|
|
|
|
max = -1;
|
|
|
|
if (MHD_YES != MHD_get_fdset (h->mhd_bank, &rs, &ws, &es, &max))
|
|
|
|
{
|
|
|
|
GNUNET_assert (0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
haveto = MHD_get_timeout (h->mhd_bank, &timeout);
|
|
|
|
if (MHD_YES == haveto)
|
|
|
|
tv.rel_value_us = (uint64_t) timeout * 1000LL;
|
|
|
|
else
|
|
|
|
tv = GNUNET_TIME_UNIT_FOREVER_REL;
|
|
|
|
if (-1 != max)
|
|
|
|
{
|
|
|
|
wrs = GNUNET_NETWORK_fdset_create ();
|
|
|
|
wws = GNUNET_NETWORK_fdset_create ();
|
|
|
|
GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
|
|
|
|
GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
wrs = NULL;
|
|
|
|
wws = NULL;
|
|
|
|
}
|
|
|
|
if (NULL != h->mhd_task)
|
|
|
|
GNUNET_SCHEDULER_cancel (h->mhd_task);
|
|
|
|
h->mhd_task =
|
|
|
|
GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
|
|
|
|
tv,
|
|
|
|
wrs,
|
|
|
|
wws,
|
|
|
|
&run_mhd, h);
|
|
|
|
if (NULL != wrs)
|
|
|
|
GNUNET_NETWORK_fdset_destroy (wrs);
|
|
|
|
if (NULL != wws)
|
|
|
|
GNUNET_NETWORK_fdset_destroy (wws);
|
|
|
|
}
|
2018-08-19 11:20:35 +02:00
|
|
|
#endif
|
2016-04-17 23:14:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Task run whenever HTTP server operations are pending.
|
|
|
|
*
|
2016-06-08 11:35:28 +02:00
|
|
|
* @param cls the `struct TALER_FAKEBANK_Handle`
|
2016-04-17 23:14:23 +02:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
run_mhd (void *cls)
|
|
|
|
{
|
2016-06-08 11:35:28 +02:00
|
|
|
struct TALER_FAKEBANK_Handle *h = cls;
|
2016-04-17 23:14:23 +02:00
|
|
|
|
|
|
|
h->mhd_task = NULL;
|
|
|
|
MHD_run (h->mhd_bank);
|
|
|
|
schedule_httpd (h);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start the fake bank.
|
|
|
|
*
|
|
|
|
* @param port port to listen to
|
|
|
|
* @return NULL on error
|
|
|
|
*/
|
2016-06-08 11:35:28 +02:00
|
|
|
struct TALER_FAKEBANK_Handle *
|
|
|
|
TALER_FAKEBANK_start (uint16_t port)
|
2016-04-17 23:14:23 +02:00
|
|
|
{
|
2016-06-08 11:35:28 +02:00
|
|
|
struct TALER_FAKEBANK_Handle *h;
|
2016-04-17 23:14:23 +02:00
|
|
|
|
2016-06-08 11:35:28 +02:00
|
|
|
h = GNUNET_new (struct TALER_FAKEBANK_Handle);
|
2018-08-19 11:20:35 +02:00
|
|
|
h->mhd_bank = MHD_start_daemon (MHD_USE_DEBUG
|
|
|
|
#if EPOLL_SUPPORT
|
|
|
|
| MHD_USE_EPOLL
|
|
|
|
#endif
|
|
|
|
| MHD_USE_DUAL_STACK,
|
2016-04-17 23:14:23 +02:00
|
|
|
port,
|
|
|
|
NULL, NULL,
|
|
|
|
&handle_mhd_request, h,
|
|
|
|
MHD_OPTION_NOTIFY_COMPLETED,
|
|
|
|
&handle_mhd_completion_callback, h,
|
2018-08-19 11:37:24 +02:00
|
|
|
MHD_OPTION_LISTEN_BACKLOG_SIZE, (unsigned int) 1024,
|
2016-04-17 23:14:23 +02:00
|
|
|
MHD_OPTION_END);
|
|
|
|
if (NULL == h->mhd_bank)
|
|
|
|
{
|
|
|
|
GNUNET_free (h);
|
|
|
|
return NULL;
|
|
|
|
}
|
2018-08-19 11:20:35 +02:00
|
|
|
#if EPOLL_SUPPORT
|
|
|
|
h->mhd_fd = MHD_get_daemon_info (h->mhd_bank,
|
|
|
|
MHD_DAEMON_INFO_EPOLL_FD)->epoll_fd;
|
|
|
|
h->mhd_rfd = GNUNET_NETWORK_socket_box_native (h->mhd_fd);
|
|
|
|
#endif
|
2016-04-17 23:14:23 +02:00
|
|
|
schedule_httpd (h);
|
|
|
|
return h;
|
|
|
|
}
|
2017-05-04 23:29:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* end of fakebank.c */
|