734 lines
19 KiB
C
734 lines
19 KiB
C
/*
|
|
This file is part of TALER
|
|
Copyright (C) 2021-2022 Taler Systems SA
|
|
|
|
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 extension_auction_brandt.c
|
|
* @brief Extension for replay of auctions of type brandt
|
|
* @author Özgür Kesim
|
|
*/
|
|
#include "platform.h"
|
|
#include "taler_util.h"
|
|
#include "taler_extensions.h"
|
|
#include "../../exchange/taler-exchange-httpd.h"
|
|
#include "taler_mhd_lib.h"
|
|
#include "stdint.h"
|
|
#include <microhttpd.h>
|
|
|
|
#define AUCTION_BRANDT "auction_brandt"
|
|
#define LOG_PREFIX "[auction_brandt] "
|
|
#define MAX_RESULT_SIZE 10 * 1024
|
|
|
|
/* Path to the replay program. */
|
|
static char *replay_program;
|
|
|
|
/* supported currency */
|
|
static char *currency;
|
|
|
|
/* This is basically BRANDT_Result with an extra string field */
|
|
struct result
|
|
{
|
|
uint16_t bidder;
|
|
uint16_t price_idx;
|
|
const char *price;
|
|
};
|
|
|
|
/*
|
|
* @brief Transcript information
|
|
*
|
|
*/
|
|
struct transcript
|
|
{
|
|
/*
|
|
* The first couple of fields are from a JSON transcript
|
|
*/
|
|
|
|
/* Public key of seller */
|
|
struct GNUNET_CRYPTO_EddsaPublicKey seller_pub;
|
|
|
|
/* Payto URL */
|
|
const char *payto;
|
|
|
|
/* Number of bidders + 1 (for seller) */
|
|
uint16_t n;
|
|
|
|
/* (n-1) public keys of bidders */
|
|
struct GNUNET_CRYPTO_EddsaPublicKey *bidder_pub;
|
|
|
|
/* Type of auction, see libbrandt */
|
|
uint16_t m;
|
|
|
|
/* Auction public outcome? */
|
|
bool public;
|
|
|
|
/* Start date of the auction */
|
|
struct GNUNET_TIME_Timestamp time_start;
|
|
|
|
/* End date of the auction */
|
|
struct GNUNET_TIME_Relative time_round;
|
|
|
|
/* Number of prices */
|
|
uint16_t k;
|
|
|
|
/* Prices, must be length k */
|
|
struct TALER_Amount *prices;
|
|
|
|
/* Expected winner(s), maybe NULL */
|
|
struct result *expected;
|
|
size_t expected_len;
|
|
|
|
/*
|
|
* These are the results from the replay via the external program.
|
|
*/
|
|
struct result *results;
|
|
size_t results_len;
|
|
};
|
|
|
|
/**
|
|
* @brief returns an JSON with the error
|
|
*/
|
|
static enum GNUNET_GenericReturnValue
|
|
json_error (json_t **output,
|
|
char *error, ...)
|
|
{
|
|
va_list ap;
|
|
int n = 0;
|
|
char buf[4096];
|
|
GNUNET_assert (error);
|
|
|
|
va_start (ap, error);
|
|
n = vsprintf (buf, error, ap);
|
|
va_end (ap);
|
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
|
LOG_PREFIX "got error: %s\n",
|
|
n < 0 ? error: buf);
|
|
|
|
*output = json_pack ("{s:s}",
|
|
"error",
|
|
n < 0 ? error : buf);
|
|
GNUNET_assert (*output);
|
|
|
|
return GNUNET_SYSERR;
|
|
};
|
|
|
|
/**
|
|
* @brief returns an JSON with the result
|
|
*/
|
|
#if 0
|
|
static enum GNUNET_GenericReturnValue
|
|
json_result (json_t **output,
|
|
const struct transcript *tr)
|
|
{
|
|
json_t *results;
|
|
|
|
GNUNET_assert (NULL != tr);
|
|
|
|
*output = json_object ();
|
|
results = json_array ();
|
|
GNUNET_assert (*output);
|
|
GNUNET_assert (results);
|
|
|
|
for (size_t i = 0; i < tr->results_len; i++)
|
|
{
|
|
json_t *result = json_pack ("{s:i, s:s}",
|
|
"bidder", tr->results[i].bidder,
|
|
"price", tr->results[i].price);
|
|
GNUNET_assert (result);
|
|
|
|
GNUNET_assert (-1 !=
|
|
json_array_append_new (results, result));
|
|
}
|
|
|
|
GNUNET_assert (-1 !=
|
|
json_object_set_new (*output,
|
|
"winners",
|
|
results));
|
|
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
/*
|
|
* @brief Parses a given json as transcript.
|
|
*
|
|
* @param[in] jtr JSON input
|
|
* @param[out] tr Parsed transcript data
|
|
* @param[out] jerror JSON output for errors
|
|
* @return GNUNET_OK on succes
|
|
*
|
|
* TODO:
|
|
* - parse and verify signatures
|
|
*/
|
|
static enum GNUNET_GenericReturnValue
|
|
parse_transcript (const json_t *jtr,
|
|
struct transcript *tr,
|
|
json_t **jerror)
|
|
{
|
|
json_t *auc;
|
|
|
|
// TODO: struct GNUNET_CRYPTO_EddsaSignature sig;
|
|
|
|
GNUNET_assert (jtr);
|
|
GNUNET_assert (tr);
|
|
|
|
// Parse auction
|
|
{
|
|
char *perr;
|
|
unsigned int eline;
|
|
struct GNUNET_JSON_Specification au_spec[] = {
|
|
GNUNET_JSON_spec_bool ("public", &tr->public),
|
|
GNUNET_JSON_spec_uint16 ("type", &tr->m),
|
|
GNUNET_JSON_spec_fixed_auto ("pubkey", &tr->seller_pub),
|
|
GNUNET_JSON_spec_timestamp ("time_start", &tr->time_start),
|
|
GNUNET_JSON_spec_relative_time ("time_round", &tr->time_round),
|
|
GNUNET_JSON_spec_string ("payto", &tr->payto),
|
|
GNUNET_JSON_spec_end ()
|
|
};
|
|
|
|
auc = json_object_get (jtr, "auction");
|
|
if (NULL == auc)
|
|
return json_error (jerror,
|
|
"no auction found in transcript");
|
|
|
|
if (GNUNET_OK !=
|
|
GNUNET_JSON_parse (auc,
|
|
au_spec,
|
|
(const char **) &perr,
|
|
&eline))
|
|
return json_error (jerror,
|
|
perr);
|
|
|
|
// Prices...
|
|
{
|
|
size_t idx;
|
|
json_t *val;
|
|
json_t *prices;
|
|
|
|
prices = json_object_get (auc, "prices");
|
|
if (! json_is_array (prices))
|
|
return json_error (jerror,
|
|
"no prices found");
|
|
|
|
tr->k = json_array_size (prices);
|
|
|
|
tr->prices = GNUNET_new_array (tr->k, struct TALER_Amount);
|
|
json_array_foreach (prices, idx, val)
|
|
{
|
|
struct GNUNET_JSON_Specification spec[] = {
|
|
TALER_JSON_spec_amount (NULL,
|
|
currency,
|
|
&(tr->prices[idx])),
|
|
GNUNET_JSON_spec_end (),
|
|
};
|
|
|
|
if (GNUNET_OK !=
|
|
GNUNET_JSON_parse (val,
|
|
spec,
|
|
NULL,
|
|
NULL))
|
|
return json_error (jerror,
|
|
"price no. %ld couldn't be parsed",
|
|
idx + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Bidders
|
|
{
|
|
size_t idx;
|
|
json_t *val;
|
|
json_t *bidders;
|
|
|
|
bidders = json_object_get (jtr, "bidders");
|
|
if (! bidders || ! json_is_array (bidders))
|
|
return json_error (jerror,
|
|
"no bidders found");
|
|
|
|
tr->n = json_array_size (bidders);
|
|
|
|
tr->bidder_pub = GNUNET_new_array (tr->n, struct
|
|
GNUNET_CRYPTO_EddsaPublicKey);
|
|
json_array_foreach (bidders, idx, val)
|
|
{
|
|
struct GNUNET_JSON_Specification spec[] = {
|
|
GNUNET_JSON_spec_fixed_auto (NULL,
|
|
&(tr->bidder_pub[idx])),
|
|
GNUNET_JSON_spec_end (),
|
|
};
|
|
if (GNUNET_OK !=
|
|
GNUNET_JSON_parse (val,
|
|
spec,
|
|
NULL,
|
|
NULL))
|
|
return json_error (jerror,
|
|
"bidder no %ld public key couldn't be parsed",
|
|
idx + 1);
|
|
}
|
|
}
|
|
|
|
// TODO: parse and verify signatures from bidders of the auction
|
|
|
|
|
|
// Messages
|
|
{
|
|
size_t nm;
|
|
json_t *messages = json_object_get (jtr, "transcript");
|
|
|
|
if (! json_is_array (messages))
|
|
return json_error (jerror,
|
|
"no messages found");
|
|
|
|
|
|
nm = json_array_size (messages);
|
|
|
|
if (nm != (4 * tr->n))
|
|
return json_error (jerror,
|
|
"not the right no. of messages found");
|
|
|
|
/* TODO: parse and evaluate signatures */
|
|
}
|
|
|
|
// Winners
|
|
{
|
|
size_t idx;
|
|
json_t *val;
|
|
json_t *winners = json_object_get (jtr, "winners");
|
|
|
|
if (! json_is_array (winners))
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
|
|
LOG_PREFIX "winners not provided, continuing without\n");
|
|
goto DONE;
|
|
}
|
|
|
|
tr->expected_len = json_array_size (winners);
|
|
tr->expected = GNUNET_new_array (tr->expected_len,
|
|
struct result);
|
|
|
|
json_array_foreach (winners, idx, val) {
|
|
struct GNUNET_JSON_Specification spec[] = {
|
|
GNUNET_JSON_spec_uint16 ("bidder",
|
|
&(tr->expected[idx].bidder)),
|
|
GNUNET_JSON_spec_uint16 ("price_idx",
|
|
&(tr->expected[idx].price_idx)),
|
|
GNUNET_JSON_spec_string ("price",
|
|
&(tr->expected[idx].price)),
|
|
GNUNET_JSON_spec_end ()
|
|
};
|
|
|
|
if (GNUNET_OK !=
|
|
GNUNET_JSON_parse (val,
|
|
spec,
|
|
NULL,
|
|
NULL))
|
|
return json_error (jerror,
|
|
"couldn't parse winner no. %ld",
|
|
idx + 1);
|
|
}
|
|
}
|
|
|
|
// TODO: parse and evalue sig of seller
|
|
|
|
// TODO: check for max values
|
|
|
|
DONE:
|
|
|
|
*jerror = NULL;
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief replay an auction using the external program
|
|
*
|
|
* @param[in] root The original JSON transcript
|
|
* @param[in] transcript The transcript object parsed so far
|
|
* @param[out] result The JSON result from the program
|
|
* @return GNUNET_OK on success
|
|
*
|
|
* TODO: Make this resumable
|
|
*/
|
|
static enum GNUNET_GenericReturnValue
|
|
replay_transcript (const json_t*root,
|
|
struct transcript *tr,
|
|
json_t **result)
|
|
{
|
|
struct GNUNET_DISK_PipeHandle *pi;
|
|
struct GNUNET_DISK_PipeHandle *po;
|
|
const struct GNUNET_DISK_FileHandle *fd;
|
|
struct GNUNET_OS_Process *proc;
|
|
|
|
pi = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
|
|
po = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
|
|
proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
|
|
pi, po, NULL,
|
|
replay_program,
|
|
replay_program,
|
|
NULL);
|
|
if (NULL == proc)
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
|
LOG_PREFIX "couldn't create auction replay program '%s'\n",
|
|
replay_program);
|
|
|
|
return json_error (result, "internal error");
|
|
}
|
|
|
|
// Write original transcript JSON to stdin
|
|
{
|
|
ssize_t sz;
|
|
char *str;
|
|
size_t str_len;
|
|
|
|
|
|
fd = GNUNET_DISK_pipe_handle (pi, GNUNET_DISK_PIPE_END_WRITE);
|
|
str = json_dumps (root, JSON_COMPACT);
|
|
str_len = strlen (str);
|
|
sz = GNUNET_DISK_file_write (fd,
|
|
str,
|
|
str_len);
|
|
free (str);
|
|
if (sz != str_len)
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
|
LOG_PREFIX "couldn't write all data to replay_program\n");
|
|
}
|
|
}
|
|
|
|
// Read output from stdout
|
|
{
|
|
ssize_t sz;
|
|
char buf[MAX_RESULT_SIZE];
|
|
json_error_t error;
|
|
json_t *res;
|
|
|
|
fd = GNUNET_DISK_pipe_handle (po, GNUNET_DISK_PIPE_END_READ);
|
|
|
|
sz = GNUNET_DISK_file_read (fd,
|
|
buf,
|
|
sizeof(buf));
|
|
if (GNUNET_SYSERR == sz)
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
|
LOG_PREFIX "couldn't read data from replay_program\n");
|
|
return json_error (result, "internal error");
|
|
}
|
|
|
|
buf[sz] = 0;
|
|
res = json_loads (buf,
|
|
JSON_DECODE_ANY,
|
|
&error);
|
|
|
|
if (! res)
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
|
LOG_PREFIX
|
|
"couldn't parse response from replay_program: %s (for '%s')\n",
|
|
error.text,
|
|
buf);
|
|
return json_error (result, error.text);
|
|
}
|
|
|
|
// Handle error case first
|
|
{
|
|
json_t *err = json_object_get (res,
|
|
"error");
|
|
if (NULL != err)
|
|
{
|
|
*result = json_copy (res);
|
|
json_decref (res);
|
|
return GNUNET_SYSERR;
|
|
}
|
|
}
|
|
|
|
// Parse the result
|
|
{
|
|
json_t *winners = json_object_get (res,
|
|
"winners");
|
|
if ((NULL == winners) ||
|
|
(! json_is_array (winners)))
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
|
LOG_PREFIX
|
|
"replay program didn't return a known result type, instead: '%s'\n",
|
|
json_dumps (res, JSON_INDENT (2)));
|
|
return json_error (result, "internal error");
|
|
}
|
|
|
|
{
|
|
// TODO: check each winner with tr->expected, if applicable
|
|
json_object_set (res, "exchange_sig", json_string (
|
|
"sig(priv_E, winners)"));
|
|
|
|
}
|
|
|
|
// TODO: return own result object.
|
|
*result = json_copy (res);
|
|
json_decref (res);
|
|
}
|
|
|
|
}
|
|
|
|
if (GNUNET_OK != GNUNET_OS_process_wait (proc))
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
|
LOG_PREFIX "error while launching auction replay program '%s'\n",
|
|
replay_program);
|
|
|
|
json_object_clear (*result);
|
|
return json_error (result, "internal error");
|
|
}
|
|
|
|
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief implements the TALER_Extension.disable interface.
|
|
*
|
|
* @param ext Pointer to the current extension
|
|
*/
|
|
static void
|
|
auction_disable (
|
|
struct TALER_Extension *ext)
|
|
{
|
|
/* TODO: cleanup configuration */
|
|
ext->enabled = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief implements the TALER_Extension.test_json_config interface.
|
|
*
|
|
* @param config configuration as json_t* to test
|
|
* @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise.
|
|
*/
|
|
static enum GNUNET_GenericReturnValue
|
|
auction_test_json_config (
|
|
const json_t *config)
|
|
{
|
|
/* This extension has no configuration */
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief implements the TALER_Extension.config_to_json interface.
|
|
*
|
|
* @param ext if NULL, only tests the configuration
|
|
* @return configuration as json_t* object, maybe NULL
|
|
*/
|
|
static json_t *
|
|
auction_config_to_json (
|
|
const struct TALER_Extension *ext)
|
|
{
|
|
/* TODO: add configuration */
|
|
return GNUNET_JSON_PACK (
|
|
GNUNET_JSON_pack_bool ("critical", ext->critical),
|
|
GNUNET_JSON_pack_string ("version", ext->version));
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief implements the TALER_Extension.load_json_config interface.
|
|
*
|
|
* @param ext if NULL, only tests the configuration
|
|
* @param jconfig the configuration as json
|
|
*/
|
|
static enum GNUNET_GenericReturnValue
|
|
auction_load_json_config (
|
|
struct TALER_Extension *ext,
|
|
json_t *jconfig)
|
|
{
|
|
/* TODO: add configuration */
|
|
ext->enabled = true;
|
|
return GNUNET_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief implements the TALER_Extension.http_get_handler
|
|
*/
|
|
static MHD_RESULT
|
|
auction_http_get_handler (
|
|
struct MHD_Connection *connection,
|
|
const char *const args[])
|
|
{
|
|
/* TODO: return some meta-data about supported version, limits, etc.*/
|
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
|
|
LOG_PREFIX "auction_http_get_handler not implemented yet\n");
|
|
|
|
return TALER_MHD_reply_with_error (connection,
|
|
MHD_HTTP_NOT_IMPLEMENTED,
|
|
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
|
|
"auction_http_get_handler not implemented yet\n");
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief implements the TALER_Extension.http_post_handler
|
|
*
|
|
* TODO: make this non-blocking
|
|
*/
|
|
static MHD_RESULT
|
|
auction_http_post_handler (
|
|
struct MHD_Connection *connection,
|
|
const json_t *root,
|
|
const char *const args[])
|
|
{
|
|
struct transcript tr = {};
|
|
enum GNUNET_GenericReturnValue ret;
|
|
json_t *result1;
|
|
json_t *result2;
|
|
|
|
ret = parse_transcript (root,
|
|
&tr,
|
|
&result1);
|
|
if (GNUNET_OK != ret)
|
|
return TALER_MHD_reply_json_steal (connection,
|
|
result1,
|
|
MHD_HTTP_BAD_REQUEST);
|
|
GNUNET_assert (NULL == result1);
|
|
|
|
ret = replay_transcript (root,
|
|
&tr,
|
|
&result2);
|
|
|
|
return TALER_MHD_reply_json_steal (connection,
|
|
result2,
|
|
GNUNET_OK == ret?
|
|
MHD_HTTP_OK :
|
|
MHD_HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
|
|
/* The extension struct for auctions of brandt-style */
|
|
struct TALER_Extension TE_auction_brandt = {
|
|
.type = TALER_Extension_AuctionBrandt,
|
|
.name = AUCTION_BRANDT,
|
|
.critical = false,
|
|
.version = "0",
|
|
.enabled = false, /* disabled per default */
|
|
.has_config = true,
|
|
.config = NULL,
|
|
.config_json = NULL,
|
|
.disable = &auction_disable,
|
|
.test_json_config = &auction_test_json_config,
|
|
.load_json_config = &auction_load_json_config,
|
|
.config_to_json = &auction_config_to_json,
|
|
.http_get_handler = &auction_http_get_handler,
|
|
.http_post_handler = &auction_http_post_handler,
|
|
};
|
|
|
|
|
|
/* TODO: sql handler */
|
|
|
|
/**
|
|
* ===========================================
|
|
* Handler for GNUNET_PLUGIN_load and _unload
|
|
* ===========================================
|
|
*/
|
|
|
|
/**
|
|
* @brief Initialization function for the extension.
|
|
* Will be called by GNUNET_PLUGIN_load.
|
|
*
|
|
* @param arg Configuration - ptr to GNUNET_CONFIGURATION_Handle
|
|
* @return Pointer to TE_auction_brandt
|
|
*/
|
|
struct TALER_Extension *
|
|
libtaler_extension_auction_brandt_init (void *arg)
|
|
{
|
|
const struct GNUNET_CONFIGURATION_Handle *cfg = arg;
|
|
|
|
|
|
if (GNUNET_OK !=
|
|
TALER_config_get_currency (cfg,
|
|
¤cy))
|
|
return NULL;
|
|
|
|
if (GNUNET_SYSERR ==
|
|
GNUNET_CONFIGURATION_get_value_string (cfg,
|
|
TALER_EXTENSION_SECTION_PREFIX
|
|
AUCTION_BRANDT,
|
|
"REPLAY_PROGRAM",
|
|
&replay_program))
|
|
{
|
|
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
|
|
TALER_EXTENSION_SECTION_PREFIX AUCTION_BRANDT,
|
|
"REPLAY_PROGRAM");
|
|
return NULL;
|
|
}
|
|
|
|
/* check if replay_program is actually an executable */
|
|
{
|
|
struct stat sb;
|
|
|
|
if (0 != stat (replay_program, &sb))
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
|
LOG_PREFIX "replay_program '%s' not found\n",
|
|
replay_program);
|
|
return NULL;
|
|
}
|
|
|
|
if ( (sb.st_mode & S_IFDIR) ||
|
|
! (sb.st_mode & S_IXUSR))
|
|
{
|
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
|
LOG_PREFIX "replay_program '%s' is not an executable\n",
|
|
replay_program);
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
|
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
|
LOG_PREFIX "loading... using replay_program '%s'\n",
|
|
replay_program);
|
|
|
|
/* TODO: read other config parameters and generate configuration */
|
|
|
|
|
|
return &TE_auction_brandt;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Tear-down function for the extension.
|
|
* Will be called by GNUNET_PLUGIN_unload.
|
|
*
|
|
* @param ignored
|
|
* @return null
|
|
*/
|
|
void *
|
|
libtaler_extension_auction_brandt_done (void *arg)
|
|
{
|
|
auction_disable (&TE_auction_brandt);
|
|
GNUNET_free (replay_program);
|
|
replay_program = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* end of extension_auction_brandt.c */
|