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