/* This file is part of TALER Copyright (C) 2015-2021 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, see */ /** * @file taler-exchange-httpd_wire.c * @brief Handle /wire requests * @author Christian Grothoff */ #include "platform.h" #include #include "taler_dbevents.h" #include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_wire.h" #include "taler_json_lib.h" #include "taler_mhd_lib.h" #include /** * Stores the latest generation of our wire response. */ static struct WireStateHandle *wire_state; /** * Handler listening for wire updates by other exchange * services. */ static struct GNUNET_DB_EventHandler *wire_eh; /** * Counter incremented whenever we have a reason to re-build the #wire_state * because something external changed. */ static uint64_t wire_generation; /** * State we keep per thread to cache the /wire response. */ struct WireStateHandle { /** * Cached JSON for /wire response. */ json_t *wire_reply; /** * For which (global) wire_generation was this data structure created? * Used to check when we are outdated and need to be re-generated. */ uint64_t wire_generation; /** * HTTP status to return with this response. */ unsigned int http_status; }; /** * Free memory associated with @a wsh * * @param[in] wsh wire state to destroy */ static void destroy_wire_state (struct WireStateHandle *wsh) { json_decref (wsh->wire_reply); GNUNET_free (wsh); } /** * Function called whenever another exchange process has updated * the wire data in the database. * * @param cls NULL * @param extra unused * @param extra_size number of bytes in @a extra unused */ static void wire_update_event_cb (void *cls, const void *extra, size_t extra_size) { (void) cls; (void) extra; (void) extra_size; wire_generation++; } int TEH_wire_init () { struct GNUNET_DB_EventHeaderP es = { .size = htons (sizeof (es)), .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED), }; wire_eh = TEH_plugin->event_listen (TEH_plugin->cls, GNUNET_TIME_UNIT_FOREVER_REL, &es, &wire_update_event_cb, NULL); if (NULL == wire_eh) { GNUNET_break (0); return GNUNET_SYSERR; } return GNUNET_OK; } void TEH_WIRE_done () { if (NULL != wire_state) { destroy_wire_state (wire_state); wire_state = NULL; } if (NULL != wire_eh) { TEH_plugin->event_listen_cancel (TEH_plugin->cls, wire_eh); wire_eh = NULL; } } /** * Create standard JSON response format using * @param ec and @a detail * * @param ec error code to return * @param detail optional detail text to return, can be NULL * @return JSON response */ static json_t * make_ec_reply (enum TALER_ErrorCode ec, const char *detail) { return GNUNET_JSON_PACK ( GNUNET_JSON_pack_uint64 ("code", ec), GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("detail", detail))); } /** * Add information about a wire account to @a cls. * * @param cls a `json_t *` object to expand with wire account details * @param payto_uri the exchange bank account URI to add * @param master_sig master key signature affirming that this is a bank * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS) */ static void add_wire_account (void *cls, const char *payto_uri, const struct TALER_MasterSignatureP *master_sig) { json_t *a = cls; if (0 != json_array_append_new ( a, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("payto_uri", payto_uri), GNUNET_JSON_pack_data_auto ("master_sig", master_sig)))) { GNUNET_break (0); /* out of memory!? */ return; } } /** * Add information about a wire account to @a cls. * * @param cls a `json_t *` array to expand with wire account details * @param wire_fee the wire fee we charge * @param closing_fee the closing fee we charge * @param start_date from when are these fees valid (start date) * @param end_date until when are these fees valid (end date, exclusive) * @param master_sig master key signature affirming that this is the correct * fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES) */ static void add_wire_fee (void *cls, const struct TALER_Amount *wire_fee, const struct TALER_Amount *closing_fee, struct GNUNET_TIME_Absolute start_date, struct GNUNET_TIME_Absolute end_date, const struct TALER_MasterSignatureP *master_sig) { json_t *a = cls; if (0 != json_array_append_new ( a, GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("wire_fee", wire_fee), TALER_JSON_pack_amount ("closing_fee", closing_fee), GNUNET_JSON_pack_time_abs ("start_date", start_date), GNUNET_JSON_pack_time_abs ("end_date", end_date), GNUNET_JSON_pack_data_auto ("sig", master_sig)))) { GNUNET_break (0); /* out of memory!? */ return; } } /** * Create the /wire response from our database state. * * @return NULL on error */ static struct WireStateHandle * build_wire_state (void) { json_t *wire_accounts_array; json_t *wire_fee_object; uint64_t wg = wire_generation; /* must be obtained FIRST */ enum GNUNET_DB_QueryStatus qs; struct WireStateHandle *wsh; wsh = GNUNET_new (struct WireStateHandle); wsh->wire_generation = wg; wire_accounts_array = json_array (); GNUNET_assert (NULL != wire_accounts_array); qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls, &add_wire_account, wire_accounts_array); if (0 > qs) { GNUNET_break (0); json_decref (wire_accounts_array); wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; wsh->wire_reply = make_ec_reply (TALER_EC_GENERIC_DB_FETCH_FAILED, "get_wire_accounts"); return wsh; } if (0 == json_array_size (wire_accounts_array)) { json_decref (wire_accounts_array); wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; wsh->wire_reply = make_ec_reply (TALER_EC_EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED, NULL); return wsh; } wire_fee_object = json_object (); GNUNET_assert (NULL != wire_fee_object); { json_t *account; size_t index; json_array_foreach (wire_accounts_array, index, account) { char *wire_method; const char *payto_uri = json_string_value (json_object_get (account, "payto_uri")); GNUNET_assert (NULL != payto_uri); wire_method = TALER_payto_get_method (payto_uri); if (NULL == wire_method) { wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; wsh->wire_reply = make_ec_reply (TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED, payto_uri); json_decref (wire_accounts_array); json_decref (wire_fee_object); return wsh; } if (NULL == json_object_get (wire_fee_object, wire_method)) { json_t *a = json_array (); GNUNET_assert (NULL != a); qs = TEH_plugin->get_wire_fees (TEH_plugin->cls, wire_method, &add_wire_fee, a); if (0 > qs) { GNUNET_break (0); json_decref (a); json_decref (wire_fee_object); json_decref (wire_accounts_array); GNUNET_free (wire_method); wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; wsh->wire_reply = make_ec_reply (TALER_EC_GENERIC_DB_FETCH_FAILED, "get_wire_fees"); return wsh; } if (0 == json_array_size (a)) { json_decref (a); json_decref (wire_accounts_array); json_decref (wire_fee_object); wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; wsh->wire_reply = make_ec_reply (TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED, wire_method); GNUNET_free (wire_method); return wsh; } GNUNET_assert (0 == json_object_set_new (wire_fee_object, wire_method, a)); } GNUNET_free (wire_method); } } wsh->wire_reply = GNUNET_JSON_PACK ( GNUNET_JSON_pack_array_steal ("accounts", wire_accounts_array), GNUNET_JSON_pack_object_steal ("fees", wire_fee_object), GNUNET_JSON_pack_data_auto ("master_public_key", &TEH_master_public_key)); wsh->http_status = MHD_HTTP_OK; return wsh; } void TEH_wire_update_state (void) { struct GNUNET_DB_EventHeaderP es = { .size = htons (sizeof (es)), .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED), }; TEH_plugin->event_notify (TEH_plugin->cls, &es, NULL, 0); wire_generation++; } /** * Return the current key state for this thread. Possibly * re-builds the key state if we have reason to believe * that something changed. * * @return NULL on error */ struct WireStateHandle * get_wire_state (void) { struct WireStateHandle *old_wsh; old_wsh = wire_state; if ( (NULL == old_wsh) || (old_wsh->wire_generation < wire_generation) ) { struct WireStateHandle *wsh; wsh = build_wire_state (); wire_state = wsh; if (NULL != old_wsh) destroy_wire_state (old_wsh); return wsh; } return old_wsh; } MHD_RESULT TEH_handler_wire (struct TEH_RequestContext *rc, const char *const args[]) { struct WireStateHandle *wsh; (void) args; wsh = get_wire_state (); if (NULL == wsh) return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, NULL); return TALER_MHD_reply_json (rc->connection, wsh->wire_reply, wsh->http_status); } /* end of taler-exchange-httpd_wire.c */