From 72cbf663952bc95888aa2187894da78725e7590c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Sun, 2 Oct 2022 22:51:54 +0200 Subject: [PATCH] WiP: added auction_brandt as extension - added an extension to handle auctions via libbrandt - /extensions/auction_brandt GET and POST handler defined - initial parsing of transcript.json implemented, yet WiP - multiple cleanups and changes to extension handling --- src/exchange-tools/taler-exchange-offline.c | 6 +- src/exchange/taler-exchange-httpd.c | 43 +++ .../taler-exchange-httpd_extensions.c | 17 +- src/exchange/taler-exchange-httpd_keys.c | 13 +- .../extension_age_restriction.c | 9 +- .../auction_brandt/extension_auction_brandt.c | 309 +++++++++++++++++- src/include/taler_extensions.h | 13 +- src/lib/exchange_api_handle.c | 3 + src/testing/testing_api_cmd_batch_withdraw.c | 2 +- src/testing/testing_api_cmd_withdraw.c | 2 +- 10 files changed, 380 insertions(+), 37 deletions(-) diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index e7cb94b58..3d980c270 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -4255,13 +4255,17 @@ do_extensions_show (char *const *args) GNUNET_assert (NULL != exts); for (it = TALER_extensions_get_head (); - NULL != it; + NULL != it && NULL != it->extension; it = it->next) + { + const struct TALER_Extension *extension = it->extension; + GNUNET_assert (0 == json_object_set_new (exts, it->extension->name, it->extension->config_to_json ( it->extension))); + } obj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_object_steal ("extensions", exts)); diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index d7651d792..68fc9fb72 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -1040,6 +1040,42 @@ handle_post_auditors (struct TEH_RequestContext *rc, root); } +/** + * Handle GET "/extensions/..." requests. + * + * @param rc request context + * @param args array of additional options + * @return MHD result code + */ +static MHD_RESULT +handle_get_extensions (struct TEH_RequestContext *rc, + const char *const args[]) +{ + const struct TALER_Extension *ext = NULL; + + if (NULL == args[0]) + { + GNUNET_break_op (0); + return r404 (rc->connection, + "/extensions/$EXTENSION"); + } + + ext = TALER_extensions_get_by_name (args[0]); + if (NULL == ext) + { + GNUNET_break_op (0); + return r404 (rc->connection, + "/extensions/$EXTENSION unknown"); + } + + if (NULL == ext->http_get_handler) + return MHD_HTTP_NOT_IMPLEMENTED; + + return ext->http_get_handler ( + rc->connection, + &args[1]); +} + /** * Handle POST "/extensions/..." requests. @@ -1299,6 +1335,13 @@ handle_mhd_request (void *cls, .nargs_is_upper_bound = true }, /* extensions endpoints */ + { + .url = "extensions", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &handle_get_extensions, + .nargs = 4, /* Arbitrary upper bound */ + .nargs_is_upper_bound = true, + }, { .url = "extensions", .method = MHD_HTTP_METHOD_POST, diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c index 2a99d7a2f..2aee1b5c5 100644 --- a/src/exchange/taler-exchange-httpd_extensions.c +++ b/src/exchange/taler-exchange-httpd_extensions.c @@ -78,6 +78,7 @@ extension_update_event_cb (void *cls, } // Get the config from the database as string + if (extension->has_config) { char *config_str = NULL; enum GNUNET_DB_QueryStatus qs; @@ -117,26 +118,26 @@ extension_update_event_cb (void *cls, err.text, err.source); GNUNET_break (0); - free(config_str); + free (config_str); return; } // Call the parser for the extension ret = extension->load_json_config ( (struct TALER_Extension *) extension, - json_object_get(config, "config")); + json_object_get (config, "config")); if (GNUNET_OK != ret) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Couldn't parse configuration for extension %s from the database: %s\n", extension->name, - config_str); + config_str); GNUNET_break (0); } - free(config_str); - json_decref(config); + free (config_str); + json_decref (config); } /* Special case age restriction: Update global flag and mask */ @@ -190,12 +191,16 @@ TEH_extensions_init () it = it->next) { const struct TALER_Extension *ext = it->extension; + uint32_t typ = htonl (ext->type); char *conf = json_dumps (ext->config_to_json (ext), JSON_COMPACT); TEH_plugin->set_extension_config (TEH_plugin->cls, ext->name, conf); - extension_update_event_cb (NULL, &ext->type, sizeof(ext->type)); + + extension_update_event_cb (NULL, + &typ, + sizeof(typ)); free (conf); } diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 4fb8a717f..4761f3142 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -1916,18 +1916,23 @@ create_krd (struct TEH_KeyStateHandle *ksh, /* flag our findings so far */ has_extensions = true; - GNUNET_assert (NULL != extension->config_json); ext = GNUNET_JSON_PACK ( GNUNET_JSON_pack_bool ("critical", extension->critical), GNUNET_JSON_pack_string ("version", - extension->version), - GNUNET_JSON_pack_object_incref ("config", - extension->config_json) + extension->version) ); GNUNET_assert (NULL != ext); + if (extension->has_config) + { + GNUNET_assert (extension->config_json); + json_object_set_new (ext, + "config", + extension->config_json); + } + r = json_object_set_new ( extensions, extension->name, diff --git a/src/extensions/age_restriction/extension_age_restriction.c b/src/extensions/age_restriction/extension_age_restriction.c index 1399fbc7a..697d066f2 100644 --- a/src/extensions/age_restriction/extension_age_restriction.c +++ b/src/extensions/age_restriction/extension_age_restriction.c @@ -105,8 +105,8 @@ age_restriction_load_json_config ( json_decref (ext->config_json); ext->enabled = true; - ext->config_json = json_copy(jconfig); - json_decref(jconfig); + ext->config_json = json_copy (jconfig); + json_decref (jconfig); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "loaded new age restriction config with age groups: %s\n", @@ -155,7 +155,7 @@ age_restriction_config_to_json ( GNUNET_JSON_pack_string ("age_groups", mask_str) ); - free(mask_str); + free (mask_str); return GNUNET_JSON_PACK ( GNUNET_JSON_pack_bool ("critical", ext->critical), @@ -188,6 +188,7 @@ struct TALER_Extension TE_extension_age_restriction = { .critical = false, .version = "1", .enabled = false, /* disabled per default */ + .has_config = true, /* we need to store configuration */ .config = NULL, .config_json = NULL, .disable = &age_restriction_disable, @@ -264,7 +265,7 @@ libtaler_extension_age_restriction_init (void *arg) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "[age restriction] setting age mask to %s with #groups: %d\n", - TALER_age_mask_to_string(&AR_config.mask), + TALER_age_mask_to_string (&AR_config.mask), __builtin_popcount (AR_config.mask.bits) - 1); TE_extension_age_restriction.config = &AR_config; diff --git a/src/extensions/auction_brandt/extension_auction_brandt.c b/src/extensions/auction_brandt/extension_auction_brandt.c index a6efd94b9..a5f3af8c1 100644 --- a/src/extensions/auction_brandt/extension_auction_brandt.c +++ b/src/extensions/auction_brandt/extension_auction_brandt.c @@ -26,11 +26,245 @@ #include "stdint.h" #include -#define EXTENSION_NAME "auction_brandt" +#define AUCTION_BRANDT "auction_brandt" /* Path to the replay program. */ static char *replay_program; +/* This is basically BRANDT_Result with an extra string field */ +struct result +{ + uint16_t bidder; + uint16_t price_idx; + const char *price; +}; + +/* + * TODO: Transcript information + */ +struct transcript +{ + // All fields from json come here. + uint16_t n; // #bidders + 1 + uint16_t k; // #prices + uint16_t m; // type of auction + struct GNUNET_TIME_Absolute time_start; + struct GNUNET_TIME_Relative time_round; + bool public; + char **prices; // Must be of length k. We do not parse those + // struct msg *msgs; // Array must be of length 4*n + + // struct BRANDT_Auction *auction; + + struct result *results; + size_t results_len; + struct result *expected; + size_t expected_len; + uint16_t id; + struct GNUNET_CRYPTO_EccDlogContext *edc; +}; + +/** + * @brief returns an JSON with the error + */ +static enum GNUNET_GenericReturnValue +json_error (json_t **output, + char *error) +{ + GNUNET_assert (error); + + *output = json_pack ("{s:s}", "error", error); + GNUNET_assert (*output); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "[auction_brandt] got error: %s\n", + error); + + return GNUNET_SYSERR; +}; + +/** + * @brief returns an JSON with the result + */ +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; +} + + +/* + * @brief Parses a given json as transcript. + * + * @param[in] jtr JSON input + * @param[out] transcript Parsed transcript data + * @param[out] jresult JSON output, both, for results or errors + * @return GNUNET_OK on succes + */ +static enum GNUNET_GenericReturnValue +parse_transcript (const json_t *jtr, + struct transcript *tr, + json_t **output) +{ + // TODO: json_error_t jerror; + // TODO: struct GNUNET_CRYPTO_EddsaSignature sig; + + GNUNET_assert (jtr); + GNUNET_assert (tr); + + { + json_t *auc; + 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_end () + }; + + auc = json_object_get (jtr, "auction"); + if (NULL == auc) + return json_error (output, "no auction found in transcript"); + + if (GNUNET_OK != + GNUNET_JSON_parse (auc, + au_spec, + (const char **) &perr, + &eline)) + return json_error (output, perr); + + // Prices... + { + json_t *prices = json_object_get (auc, "prices"); + size_t idx; + json_t *val; + + + if (! json_is_array (prices)) + // TODO: leak!? + return json_error (output, "no prices found"); + + tr->k = json_array_size (prices); + + tr->prices = GNUNET_new_array (tr->k, char *); + json_array_foreach (prices, idx, val) + { + if (! json_is_string (val)) + // TODO: leak!_ + return json_error (output, "prices not strings"); + + tr->prices[idx] = (char *) json_string_value (val); + + // TODO: parse prices + } + } + } + + // Bidders + { + json_t *bidders; + + bidders = json_object_get (jtr, "bidders"); + if (! bidders || ! json_is_array (bidders)) + // TODO: leak!_ + return json_error (output, "bidders not found"); + + // TODO: parse bidders as pub keys; + tr->n = json_array_size (bidders); + } + + + // Messages + { + json_t *messages; + size_t nm; + + messages = json_object_get (jtr, "transcript"); + if (! json_is_array (messages)) + // TODO: leak! + return json_error (output, "no messages found"); + + + nm = json_array_size (messages); + + if (nm != (4 * tr->n)) + // TODO: leak! + return json_error (output, "not the right no. of messages found"); + } + + // Winners + { + json_t *winners; + size_t idx; + json_t *val; + + winners = json_object_get (jtr, "winners"); + + if (! json_is_array (winners)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "winners not provided, continuing without\n"); + goto CONT; + } + + tr->expected_len = json_array_size (winners); + tr->expected = GNUNET_new_array (tr->expected_len, + struct result); + + json_array_foreach (winners, idx, val) { + char *error; + + 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, + (const char**) &error, + NULL)) + // TODO: leak! + return json_error (output, "couldn't parse winners"); + } + +CONT: + } + return json_result (output, tr); +} + + /** * @brief implements the TALER_Extension.disable interface. * @@ -70,7 +304,9 @@ auction_config_to_json ( const struct TALER_Extension *ext) { /* This extension has no configuration */ - return json_null (); + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_bool ("critical", ext->critical), + GNUNET_JSON_pack_string ("version", ext->version)); } @@ -91,36 +327,68 @@ auction_load_json_config ( } +/** + * @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, + "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 */ - 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 *result; + + ret = parse_transcript (root, &tr, &result); + /* TODO */ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "auction_http_post_handler not implemented yet"); - return MHD_HTTP_NOT_IMPLEMENTED; + "auction_http_post_handler not implemented yet\n"); + + return TALER_MHD_reply_json_steal (connection, + result, + 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 = 0x0815, /* TODO: where do we get this from? */ - .name = EXTENSION_NAME, + .type = TALER_Extension_AuctionBrandt, + .name = AUCTION_BRANDT, .critical = false, .version = "0", .enabled = false, /* disabled per default */ + .has_config = false, /* This extension has no configuration */ .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, }; @@ -141,25 +409,31 @@ struct TALER_Extension TE_auction_brandt = { * @return Pointer to TE_auction_brandt */ struct TALER_Extension * -init (void *arg) +libtaler_extension_auction_brandt_init (void *arg) { const struct GNUNET_CONFIGURATION_Handle *cfg = arg; if (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_string (cfg, - "extension_" EXTENSION_NAME, - "replay_program", + TALER_EXTENSION_SECTION_PREFIX + AUCTION_BRANDT, + "REPLAY_PROGRAM", &replay_program)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "extension_" EXTENSION_NAME, - "replay_program"); + TALER_EXTENSION_SECTION_PREFIX AUCTION_BRANDT, + "REPLAY_PROGRAM"); return NULL; } + /* TODO: check if replay_program is actually executable */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "[auction_brandt] loading... using replay_program '%s'\n", + replay_program); return &TE_auction_brandt; } + /** * @brief Tear-down function for the extension. * Will be called by GNUNET_PLUGIN_unload. @@ -168,15 +442,14 @@ init (void *arg) * @return null */ void * -done (void *arg) +libtaler_extension_auction_brandt_done (void *arg) { - auction_disable(&TE_auction_brandt); - GNUNET_free(replay_program); - replay_program=NULL; + auction_disable (&TE_auction_brandt); + GNUNET_free (replay_program); + replay_program = NULL; - return NULL; + return NULL; } - /* end of extension_auction_brandt.c */ diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h index 40b14402f..1ae8ad1ed 100644 --- a/src/include/taler_extensions.h +++ b/src/include/taler_extensions.h @@ -31,8 +31,12 @@ enum TALER_Extension_Type { - TALER_Extension_AgeRestriction = 0, - TALER_Extension_MaxPredefined = 1 // Must be last of the predefined + TALER_Extension_Refund = 0, + TALER_Extension_AgeRestriction = 1, + TALER_Extension_P2P = 2, + TALER_Extension_AuctionBrandt = 3, + TALER_Extension_Escrow = 4, + TALER_Extension_MaxPredefined = 5 // Must be last of the predefined }; @@ -59,6 +63,7 @@ struct TALER_Extension char *version; void *config; bool enabled; + bool has_config; /* some extension might not have a configuration */ json_t *config_json; void (*disable)(struct TALER_Extension *ext); @@ -78,6 +83,10 @@ struct TALER_Extension const json_t *root, const char *const args[]); + MHD_RESULT (*http_get_handler)( + struct MHD_Connection *connection, + const char *const args[]); + }; /** diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index add37a80f..e92e43858 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -931,6 +931,9 @@ decode_keys_json (const json_t *resp_obj, if (! no_extensions && ! no_signature) { + GNUNET_log(GNUNET_ERROR_TYPE_INFO, + "DEBUG: got extensions object %s\n", + json_dumps(extensions, JSON_INDENT(2))); /* 2. We have an extensions object. Verify its signature. */ EXITIF (GNUNET_OK != TALER_extensions_verify_json_config_signature ( diff --git a/src/testing/testing_api_cmd_batch_withdraw.c b/src/testing/testing_api_cmd_batch_withdraw.c index 3b32b5ada..a5229ae9c 100644 --- a/src/testing/testing_api_cmd_batch_withdraw.c +++ b/src/testing/testing_api_cmd_batch_withdraw.c @@ -490,7 +490,7 @@ TALER_TESTING_cmd_batch_withdraw (const char *label, acp = GNUNET_new (struct TALER_AgeCommitmentProof); hac = GNUNET_new (struct TALER_AgeCommitmentHash); - mask = TALER_extensions_get_age_restriction_mask(); + mask = TALER_extensions_get_age_restriction_mask (); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, &seed, sizeof(seed)); diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index 3e2f45c4b..7a81d1c33 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -590,7 +590,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label, acp = GNUNET_new (struct TALER_AgeCommitmentProof); hac = GNUNET_new (struct TALER_AgeCommitmentHash); - mask = TALER_extensions_get_age_restriction_mask(); + mask = TALER_extensions_get_age_restriction_mask (); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, &seed, sizeof(seed));