commit 00cdf3f8ed2acd83208227aab90bf8652a837d44
parent 8ca128478cf6dd8524572bf4fb344abde24ea34e
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 20 Jun 2026 01:18:53 +0200
transform GET /private/kyc to use state machine, in preparation of fixing #11520
Diffstat:
1 file changed, 633 insertions(+), 502 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_get-private-kyc.c b/src/backend/taler-merchant-httpd_get-private-kyc.c
@@ -184,11 +184,6 @@ struct KycContext
struct TMH_HandlerContext *hc;
/**
- * Response to return, NULL if we don't have one yet.
- */
- struct MHD_Response *response;
-
- /**
* JSON array where we are building up the array with
* pending KYC operations.
*/
@@ -271,11 +266,22 @@ struct KycContext
enum TALER_EXCHANGE_KycLongPollTarget lpt;
/**
- * HTTP status code to use for the reply, i.e 200 for "OK".
- * Special value UINT_MAX is used to indicate hard errors
- * (no reply, return #MHD_NO).
+ * Processing phase.
*/
- unsigned int response_code;
+ enum
+ {
+ PHASE_INIT = 0,
+ PHASE_DETERMINE_LONG_POLL,
+ PHASE_DATABASE_KYC_CHECK,
+ PHASE_NO_ACCOUNTS,
+ PHASE_GENERATE_RESPONSE,
+ PHASE_IN_SHUTDOWN = 999,
+ PHASE_RETURN_YES,
+ PHASE_RETURN_NO,
+ PHASE_SUSPENDED_ON_ACCOUNT,
+ PHASE_SUSPENDED_ON_EXCHANGE,
+ } phase;
+
/**
* Output format requested by the client.
*/
@@ -287,6 +293,13 @@ struct KycContext
} format;
/**
+ * Set to true if the database notified us about a change
+ * in the account but we did not yet check the database
+ * status as we were waiting on something else.
+ */
+ bool account_signal;
+
+ /**
* True if @e h_wire was given.
*/
bool have_h_wire;
@@ -321,6 +334,8 @@ static struct KycContext *kc_head;
static struct KycContext *kc_tail;
+/* ******************* cleanup ***************** */
+
void
TMH_force_kyc_resume ()
{
@@ -331,6 +346,7 @@ TMH_force_kyc_resume ()
if (GNUNET_YES == kc->suspended)
{
kc->suspended = GNUNET_SYSERR;
+ kc->phase = PHASE_IN_SHUTDOWN;
MHD_resume_connection (kc->connection);
}
}
@@ -385,11 +401,6 @@ kyc_context_cleanup (void *cls)
TALER_MERCHANTDB_event_listen_cancel (kc->eh);
kc->eh = NULL;
}
- if (NULL != kc->response)
- {
- MHD_destroy_response (kc->response);
- kc->response = NULL;
- }
GNUNET_CONTAINER_DLL_remove (kc_head,
kc_tail,
kc);
@@ -399,172 +410,104 @@ kyc_context_cleanup (void *cls)
/**
- * We have found an exchange in status @a status. Clear any
- * long-pollers that wait for us having (or not having) this
- * status.
+ * Finish handling the connection returning @a ret to MHD
*
- * @param[in,out] kc context
- * @param status the status we encountered
+ * @param[in,out] kc connection we are handling
+ * @param mhd_ret result to return for the @a kc request
*/
static void
-clear_status (struct KycContext *kc,
- const char *status)
+finish_request (struct KycContext *kc,
+ enum MHD_Result mhd_ret)
{
- if ( (NULL != kc->lp_status) &&
- (0 == strcmp (kc->lp_status,
- status)) )
- kc->lp_status = NULL; /* satisfied! */
- if ( (NULL != kc->lp_not_status) &&
- (0 != strcmp (kc->lp_not_status,
- status) ) )
- kc->lp_not_status = NULL; /* satisfied! */
+ kc->phase = (MHD_YES == mhd_ret)
+ ? PHASE_RETURN_YES
+ : PHASE_RETURN_NO;
}
+/* ******************* phase_init ***************** */
+
+
/**
- * Resume the given KYC context and send the final response. Stores the
- * response in the @a kc and signals MHD to resume the connection. Also
- * ensures MHD runs immediately.
+ * Initialize basic data structures of the connection,
+ * finishes parsing the request.
*
- * @param kc KYC context
+ * @param[in,out] kc connection we are handling
*/
static void
-resume_kyc_with_response (struct KycContext *kc)
+phase_init (struct KycContext *kc)
{
- struct GNUNET_ShortHashCode sh;
- bool not_modified;
- char *can;
+ kc->kycs_data = json_array ();
+ GNUNET_assert (NULL != kc->kycs_data);
+ /* process 'exchange_url' argument */
+ kc->exchange_url = MHD_lookup_connection_value (
+ kc->connection,
+ MHD_GET_ARGUMENT_KIND,
+ "exchange_url");
+ if ( (NULL != kc->exchange_url) &&
+ ( (! TALER_url_valid_charset (kc->exchange_url)) ||
+ (! TALER_is_web_url (kc->exchange_url)) ) )
+ {
+ GNUNET_break_op (0);
+ finish_request (kc,
+ TALER_MHD_reply_with_error (
+ kc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "exchange_url must be a valid HTTP(s) URL"));
+ }
- if ( (! GNUNET_TIME_absolute_is_past (kc->timeout)) &&
- ( (NULL != kc->lp_not_status) ||
- (NULL != kc->lp_status) ) )
+ /* Determine desired output format from Accept header */
{
+ const char *mime;
+
+ mime = MHD_lookup_connection_value (kc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "application/json";
+ if (0 == strcmp (mime,
+ "*/*"))
+ mime = "application/json";
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Long-poll target status not reached, not returning response yet\n");
- if (GNUNET_NO == kc->suspended)
+ "KYC status requested for format %s\n",
+ mime);
+ if (0 == strcmp (mime,
+ "application/json"))
{
- MHD_suspend_connection (kc->connection);
- kc->suspended = GNUNET_YES;
+ kc->format = POF_JSON;
}
- return;
- }
- can = TALER_JSON_canonicalize (kc->kycs_data);
- GNUNET_assert (GNUNET_YES ==
- GNUNET_CRYPTO_hkdf_gnunet (&sh,
- sizeof (sh),
- "KYC-SALT",
- strlen ("KYC-SALT"),
- can,
- strlen (can)));
- not_modified = kc->have_lp_not_etag &&
- (0 == GNUNET_memcmp (&sh,
- &kc->lp_not_etag));
- if (not_modified &&
- (! GNUNET_TIME_absolute_is_past (kc->timeout)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Status unchanged, not returning response yet\n");
- if (GNUNET_NO == kc->suspended)
+ else if (0 == strcmp (mime,
+ "text/plain"))
{
- MHD_suspend_connection (kc->connection);
- kc->suspended = GNUNET_YES;
+ kc->format = POF_TEXT;
}
- GNUNET_free (can);
- return;
- }
- {
- const char *inm;
-
- inm = MHD_lookup_connection_value (kc->connection,
- MHD_GET_ARGUMENT_KIND,
- MHD_HTTP_HEADER_IF_NONE_MATCH);
- if ( (NULL == inm) ||
- ('"' != inm[0]) ||
- ('"' != inm[strlen (inm) - 1]) ||
- (0 != strncmp (inm + 1,
- can,
- strlen (can))) )
- not_modified = false; /* must return full response */
- }
- GNUNET_free (can);
- kc->response_code = not_modified
- ? MHD_HTTP_NOT_MODIFIED
- : MHD_HTTP_OK;
- switch (kc->format)
- {
- case POF_JSON:
- kc->response = TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_array_incref ("kyc_data",
- kc->kycs_data));
- break;
- case POF_TEXT:
+#if FUTURE
+ else if (0 == strcmp (mime,
+ "application/pdf"))
{
- enum GNUNET_GenericReturnValue ret;
- json_t *obj;
-
- obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_incref ("kyc_data",
- kc->kycs_data));
- ret = TALER_TEMPLATING_build (kc->connection,
- &kc->response_code,
- "kyc_text",
- kc->mi->settings.id,
- NULL,
- obj,
- &kc->response);
- json_decref (obj);
- if (GNUNET_SYSERR == ret)
- {
- /* fail hard */
- kc->suspended = GNUNET_SYSERR;
- MHD_resume_connection (kc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
- return;
- }
- if (GNUNET_OK == ret)
- {
- TALER_MHD_add_global_headers (kc->response,
- false);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (kc->response,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- "text/plain"));
- }
+ kc->format = POF_PDF;
+ }
+#endif
+ else
+ {
+ GNUNET_break_op (0);
+ finish_request (kc,
+ TALER_MHD_REPLY_JSON_PACK (
+ kc->connection,
+ MHD_HTTP_NOT_ACCEPTABLE,
+ GNUNET_JSON_pack_string ("hint",
+ mime)));
+ return;
}
- break;
- case POF_PDF:
- // not yet implemented
- GNUNET_assert (0);
- break;
- }
- {
- char *etag;
- char *qetag;
-
- etag = GNUNET_STRINGS_data_to_string_alloc (&sh,
- sizeof (sh));
- GNUNET_asprintf (&qetag,
- "\"%s\"",
- etag);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (kc->response,
- MHD_HTTP_HEADER_ETAG,
- qetag));
- GNUNET_free (qetag);
- GNUNET_free (etag);
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming /kyc handling as exchange interaction is done (%u)\n",
- MHD_HTTP_OK);
- if (GNUNET_YES == kc->suspended)
- {
- kc->suspended = GNUNET_NO;
- MHD_resume_connection (kc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
+ kc->phase++;
}
+/* ******************* phase_determine_long_poll ***************** */
+
+
/**
* Handle a DB event about an update relevant
* for the processing of the kyc request.
@@ -580,132 +523,99 @@ kyc_change_cb (void *cls,
{
struct KycContext *kc = cls;
- if (GNUNET_YES == kc->suspended)
+ if ( (GNUNET_YES == kc->suspended) &&
+ (PHASE_SUSPENDED_ON_ACCOUNT == kc->phase) )
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming KYC with gateway timeout\n");
kc->suspended = GNUNET_NO;
+ kc->phase = PHASE_DATABASE_KYC_CHECK;
MHD_resume_connection (kc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
+ else
+ {
+ /* remember for later */
+ kc->account_signal = true;
+ }
}
/**
- * Pack the given @a limit into the JSON @a limits array.
+ * Suspend @a kc until we have a change in the account status.
*
- * @param kc overall request context
- * @param limit account limit to pack
- * @param[in,out] limits JSON array to extend
+ * @param[in,out] kc request to suspend
*/
static void
-pack_limit (const struct KycContext *kc,
- const struct TALER_EXCHANGE_AccountLimit *limit,
- json_t *limits)
+wait_for_account (struct KycContext *kc)
{
- json_t *jl;
-
- jl = GNUNET_JSON_PACK (
- TALER_JSON_pack_kycte ("operation_type",
- limit->operation_type),
- GNUNET_JSON_pack_bool (
- "disallowed",
- GNUNET_TIME_relative_is_zero (limit->timeframe) ||
- TALER_amount_is_zero (&limit->threshold)),
- (POF_TEXT == kc->format)
- ? GNUNET_JSON_pack_string ("interval",
- GNUNET_TIME_relative2s (limit->timeframe,
- true))
- : GNUNET_JSON_pack_time_rel ("timeframe",
- limit->timeframe),
- TALER_JSON_pack_amount ("threshold",
- &limit->threshold),
- GNUNET_JSON_pack_bool ("soft_limit",
- limit->soft_limit)
- );
- GNUNET_assert (0 ==
- json_array_append_new (limits,
- jl));
+ GNUNET_assert (GNUNET_NO == kc->suspended);
+ if (kc->account_signal)
+ {
+ /* we got a NOTIFY earlier, handle it immediately */
+ kc->account_signal = false;
+ kc->phase = PHASE_DATABASE_KYC_CHECK;
+ return;
+ }
+ /* Wait on account notification */
+ MHD_suspend_connection (kc->connection);
+ kc->suspended = GNUNET_YES;
+ kc->phase = PHASE_SUSPENDED_ON_ACCOUNT;
}
/**
- * Return JSON array with AccountLimit objects giving
- * the current limits for this exchange.
+ * Setup long-polling for the connection, if applicable.
*
- * @param[in,out] ekr overall request context
+ * @param[in,out] kc connection we are handling
*/
-static json_t *
-get_exchange_limits (
- struct ExchangeKycRequest *ekr)
+static void
+phase_determine_long_poll (struct KycContext *kc)
{
- const struct TALER_EXCHANGE_Keys *keys = ekr->keys;
- json_t *limits;
-
- if (NULL != ekr->jlimits)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Returning custom KYC limits\n");
- return json_incref (ekr->jlimits);
- }
- if (NULL == keys)
+ if (GNUNET_TIME_absolute_is_past (kc->timeout))
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No keys, thus no default KYC limits known\n");
- return NULL;
+ kc->phase++;
+ return;
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Returning default KYC limits (%u/%u)\n",
- keys->hard_limits_length,
- keys->zero_limits_length);
- limits = json_array ();
- GNUNET_assert (NULL != limits);
- for (unsigned int i = 0; i<keys->hard_limits_length; i++)
+ if (kc->have_h_wire)
{
- const struct TALER_EXCHANGE_AccountLimit *limit
- = &keys->hard_limits[i];
+ struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP ev = {
+ .header.size = htons (sizeof (ev)),
+ .header.type = htons (
+ TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_STATUS_CHANGED
+ ),
+ .h_wire = kc->h_wire
+ };
- pack_limit (ekr->kc,
- limit,
- limits);
+ kc->eh = TALER_MERCHANTDB_event_listen (
+ TMH_db,
+ &ev.header,
+ GNUNET_TIME_absolute_get_remaining (kc->timeout),
+ &kyc_change_cb,
+ kc);
}
- for (unsigned int i = 0; i<keys->zero_limits_length; i++)
+ else
{
- const struct TALER_EXCHANGE_ZeroLimitedOperation *zlimit
- = &keys->zero_limits[i];
- json_t *jl;
- struct TALER_Amount zero;
+ struct GNUNET_DB_EventHeaderP hdr = {
+ .size = htons (sizeof (hdr)),
+ .type = htons (TALER_DBEVENT_MERCHANT_KYC_STATUS_CHANGED)
+ };
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (keys->currency,
- &zero));
- jl = GNUNET_JSON_PACK (
- TALER_JSON_pack_kycte ("operation_type",
- zlimit->operation_type),
- GNUNET_JSON_pack_bool (
- "disallowed",
- true),
- (POF_TEXT == ekr->kc->format)
- ? GNUNET_JSON_pack_string (
- "interval",
- GNUNET_TIME_relative2s (GNUNET_TIME_UNIT_ZERO,
- true))
- : GNUNET_JSON_pack_time_rel ("timeframe",
- GNUNET_TIME_UNIT_ZERO),
- TALER_JSON_pack_amount ("threshold",
- &zero),
- GNUNET_JSON_pack_bool ("soft_limit",
- true)
- );
- GNUNET_assert (0 ==
- json_array_append_new (limits,
- jl));
+ kc->eh = TALER_MERCHANTDB_event_listen (
+ TMH_db,
+ &hdr,
+ GNUNET_TIME_absolute_get_remaining (kc->timeout),
+ &kyc_change_cb,
+ kc);
}
- return limits;
+ kc->phase++;
}
-/**
+/* ***************** phase_database_kyc_check ************** */
+
+
+/**
* Maps @a ekr to a status code for clients to interpret the
* overall result.
*
@@ -847,6 +757,143 @@ map_to_status (const struct ExchangeKycRequest *ekr)
/**
+ * We have found an exchange in status @a status. Clear any
+ * long-pollers that wait for us having (or not having) this
+ * status.
+ *
+ * @param[in,out] kc context
+ * @param status the status we encountered
+ */
+static void
+clear_status (struct KycContext *kc,
+ const char *status)
+{
+ if ( (NULL != kc->lp_status) &&
+ (0 == strcmp (kc->lp_status,
+ status)) )
+ kc->lp_status = NULL; /* satisfied! */
+ if ( (NULL != kc->lp_not_status) &&
+ (0 != strcmp (kc->lp_not_status,
+ status) ) )
+ kc->lp_not_status = NULL; /* satisfied! */
+}
+
+
+/**
+ * Pack the given @a limit into the JSON @a limits array.
+ *
+ * @param kc overall request context
+ * @param limit account limit to pack
+ * @param[in,out] limits JSON array to extend
+ */
+static void
+pack_limit (const struct KycContext *kc,
+ const struct TALER_EXCHANGE_AccountLimit *limit,
+ json_t *limits)
+{
+ json_t *jl;
+
+ jl = GNUNET_JSON_PACK (
+ TALER_JSON_pack_kycte ("operation_type",
+ limit->operation_type),
+ GNUNET_JSON_pack_bool (
+ "disallowed",
+ GNUNET_TIME_relative_is_zero (limit->timeframe) ||
+ TALER_amount_is_zero (&limit->threshold)),
+ (POF_TEXT == kc->format)
+ ? GNUNET_JSON_pack_string ("interval",
+ GNUNET_TIME_relative2s (limit->timeframe,
+ true))
+ : GNUNET_JSON_pack_time_rel ("timeframe",
+ limit->timeframe),
+ TALER_JSON_pack_amount ("threshold",
+ &limit->threshold),
+ GNUNET_JSON_pack_bool ("soft_limit",
+ limit->soft_limit)
+ );
+ GNUNET_assert (0 ==
+ json_array_append_new (limits,
+ jl));
+}
+
+
+/**
+ * Return JSON array with AccountLimit objects giving
+ * the current limits for this exchange.
+ *
+ * @param[in,out] ekr overall request context
+ */
+static json_t *
+get_exchange_limits (
+ struct ExchangeKycRequest *ekr)
+{
+ const struct TALER_EXCHANGE_Keys *keys = ekr->keys;
+ json_t *limits;
+
+ if (NULL != ekr->jlimits)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning custom KYC limits\n");
+ return json_incref (ekr->jlimits);
+ }
+ if (NULL == keys)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No keys, thus no default KYC limits known\n");
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning default KYC limits (%u/%u)\n",
+ keys->hard_limits_length,
+ keys->zero_limits_length);
+ limits = json_array ();
+ GNUNET_assert (NULL != limits);
+ for (unsigned int i = 0; i<keys->hard_limits_length; i++)
+ {
+ const struct TALER_EXCHANGE_AccountLimit *limit
+ = &keys->hard_limits[i];
+
+ pack_limit (ekr->kc,
+ limit,
+ limits);
+ }
+ for (unsigned int i = 0; i<keys->zero_limits_length; i++)
+ {
+ const struct TALER_EXCHANGE_ZeroLimitedOperation *zlimit
+ = &keys->zero_limits[i];
+ json_t *jl;
+ struct TALER_Amount zero;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (keys->currency,
+ &zero));
+ jl = GNUNET_JSON_PACK (
+ TALER_JSON_pack_kycte ("operation_type",
+ zlimit->operation_type),
+ GNUNET_JSON_pack_bool (
+ "disallowed",
+ true),
+ (POF_TEXT == ekr->kc->format)
+ ? GNUNET_JSON_pack_string (
+ "interval",
+ GNUNET_TIME_relative2s (GNUNET_TIME_UNIT_ZERO,
+ true))
+ : GNUNET_JSON_pack_time_rel ("timeframe",
+ GNUNET_TIME_UNIT_ZERO),
+ TALER_JSON_pack_amount ("threshold",
+ &zero),
+ GNUNET_JSON_pack_bool ("soft_limit",
+ true)
+ );
+ GNUNET_assert (0 ==
+ json_array_append_new (limits,
+ jl));
+ }
+ return limits;
+}
+
+
+/**
* Take data from @a ekr to expand our response.
*
* @param ekr exchange we are done inspecting
@@ -974,40 +1021,6 @@ ekr_expand_response (struct ExchangeKycRequest *ekr)
/**
- * We are done with asynchronous processing, generate the
- * response for the @e kc.
- *
- * @param[in,out] kc KYC context to respond for
- */
-static void
-kc_respond (struct KycContext *kc)
-{
- if ( (! kc->return_immediately) &&
- (! GNUNET_TIME_absolute_is_past (kc->timeout)) )
- {
- if (GNUNET_NO == kc->suspended)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Suspending: long poll target %d not reached\n",
- kc->lpt);
- MHD_suspend_connection (kc->connection);
- kc->suspended = GNUNET_YES;
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Remaining suspended: long poll target %d not reached\n",
- kc->lpt);
- }
- return;
- }
- /* All exchange requests done, create final
- big response from cumulated replies */
- resume_kyc_with_response (kc);
-}
-
-
-/**
* We are done with the KYC request @a ekr. Remove it from the work list and
* check if we are done overall.
*
@@ -1024,7 +1037,11 @@ ekr_finished (struct ExchangeKycRequest *ekr)
return; /* wait for more */
if (kc->in_db)
return;
- kc_respond (kc);
+ GNUNET_assert (GNUNET_YES == kc->suspended);
+ kc->phase = PHASE_GENERATE_RESPONSE;
+ kc->suspended = GNUNET_NO;
+ MHD_resume_connection (kc->connection);
+ TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
@@ -1046,8 +1063,6 @@ determine_eligible_accounts (
char *merchant_pub_str;
struct TALER_NormalizedPayto np;
- ekr->pkaa = json_array ();
- GNUNET_assert (NULL != ekr->pkaa);
{
const struct TALER_EXCHANGE_GlobalFee *gf;
@@ -1159,6 +1174,8 @@ kyc_with_exchange (void *cls,
ekr->keys = TALER_EXCHANGE_keys_incref (keys);
if (! ekr->auth_ok)
{
+ ekr->pkaa = json_array ();
+ GNUNET_assert (NULL != ekr->pkaa);
determine_eligible_accounts (ekr);
if (0 == json_array_size (ekr->pkaa))
{
@@ -1198,6 +1215,7 @@ struct UnreachableContext
};
+
/**
* Add all trusted exchanges with "unknown" status for the
* bank account given in the context.
@@ -1349,13 +1367,7 @@ kyc_status_cb (
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Awaiting /keys from `%s'\n",
exchange_url);
-
/* Figure out wire transfer instructions */
- if (GNUNET_NO == kc->suspended)
- {
- MHD_suspend_connection (kc->connection);
- kc->suspended = GNUNET_YES;
- }
ekr->fo = TMH_EXCHANGES_keys4exchange (
exchange_url,
false,
@@ -1375,6 +1387,302 @@ kyc_status_cb (
/**
+ * Check our database for the KYC status. Determines if we then
+ * need to wait on exchange data or have no exchange and can
+ * immediately proceed to return 204.
+ *
+ * @param[in,out] kc connection we are handling
+ */
+static void
+phase_database_kyc_check (struct KycContext *kc)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking KYC status for %s (%d/%s)\n",
+ kc->mi->settings.id,
+ kc->have_h_wire,
+ kc->exchange_url);
+ /* We may run repeatedly due to long-polling; clear data
+ from previous runs first */
+ GNUNET_break (0 ==
+ json_array_clear (kc->kycs_data));
+ kc->in_db = true;
+ qs = TALER_MERCHANTDB_account_kyc_get_status (
+ TMH_db,
+ kc->mi->settings.id,
+ kc->have_h_wire
+ ? &kc->h_wire
+ : NULL,
+ kc->exchange_url,
+ &kyc_status_cb,
+ kc);
+ kc->in_db = false;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "account_kyc_get_status returned %d records\n",
+ (int) qs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* Database error */
+ GNUNET_break (0);
+ finish_request (kc,
+ TALER_MHD_reply_with_ec (
+ kc->connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "account_kyc_get_status"));
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ kc->phase = PHASE_NO_ACCOUNTS;
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* Handled below */
+ break;
+ }
+ if (NULL == kc->exchange_pending_head)
+ {
+ kc->phase = PHASE_GENERATE_RESPONSE;
+ return;
+ }
+ MHD_suspend_connection (kc->connection);
+ kc->suspended = GNUNET_YES;
+ kc->phase = PHASE_SUSPENDED_ON_EXCHANGE;
+}
+
+
+/* ********************* phase_no_accounts *********** */
+
+/**
+ * We have no accounts, return a 204 No content,
+ * or suspend if long-polling.
+ *
+ * @param[in,out] kc connection we are handling
+ */
+static void
+phase_no_accounts (struct KycContext *kc)
+{
+ /* We use an Etag of all zeros for the 204 status code */
+ static struct GNUNET_ShortHashCode zero_etag;
+ struct MHD_Response *response;
+
+ /* no matching accounts, could not have suspended */
+ GNUNET_assert (GNUNET_NO == kc->suspended);
+ if (kc->have_lp_not_etag &&
+ (0 == GNUNET_memcmp (&zero_etag,
+ &kc->lp_not_etag)) &&
+ (! GNUNET_TIME_absolute_is_past (kc->timeout)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No matching accounts, suspending to wait for this to change\n");
+ MHD_suspend_connection (kc->connection);
+ kc->suspended = GNUNET_YES;
+ kc->phase = PHASE_SUSPENDED_ON_ACCOUNT;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No matching accounts, returning empty response\n");
+ response = MHD_create_response_from_buffer_static (0,
+ NULL);
+ TALER_MHD_add_global_headers (response,
+ false);
+ {
+ char *etag;
+
+ etag = GNUNET_STRINGS_data_to_string_alloc (&zero_etag,
+ sizeof (zero_etag));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_ETAG,
+ etag));
+ GNUNET_free (etag);
+ }
+ finish_request (kc,
+ MHD_queue_response (kc->connection,
+ MHD_HTTP_NO_CONTENT,
+ response));
+ MHD_destroy_response (response);
+}
+
+
+/* ********************* phase_generate_response *********** */
+
+/**
+ * Resume the given KYC context and send the final response. Stores the
+ * response in the @a kc and signals MHD to resume the connection. Also
+ * ensures MHD runs immediately.
+ *
+ * @param kc KYC context
+ */
+static void
+resume_kyc_with_response (struct KycContext *kc)
+{
+ struct GNUNET_ShortHashCode sh;
+ bool not_modified;
+ char *can;
+ unsigned int response_code;
+ struct MHD_Response *response;
+
+ can = TALER_JSON_canonicalize (kc->kycs_data);
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_hkdf_gnunet (&sh,
+ sizeof (sh),
+ "KYC-SALT",
+ strlen ("KYC-SALT"),
+ can,
+ strlen (can)));
+ not_modified = kc->have_lp_not_etag &&
+ (0 == GNUNET_memcmp (&sh,
+ &kc->lp_not_etag));
+ if (not_modified &&
+ (! GNUNET_TIME_absolute_is_past (kc->timeout)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Status unchanged, not returning response yet\n");
+ wait_for_account (kc);
+ GNUNET_free (can);
+ return;
+ }
+ {
+ const char *inm;
+
+ inm = MHD_lookup_connection_value (kc->connection,
+ MHD_GET_ARGUMENT_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if ( (NULL == inm) ||
+ ('"' != inm[0]) ||
+ ('"' != inm[strlen (inm) - 1]) ||
+ (0 != strncmp (inm + 1,
+ can,
+ strlen (can))) )
+ not_modified = false; /* must return full response */
+ }
+ GNUNET_free (can);
+ response_code = not_modified
+ ? MHD_HTTP_NOT_MODIFIED
+ : MHD_HTTP_OK;
+ switch (kc->format)
+ {
+ case POF_JSON:
+ response = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_array_incref ("kyc_data",
+ kc->kycs_data));
+ break;
+ case POF_TEXT:
+ {
+ enum GNUNET_GenericReturnValue ret;
+ json_t *obj;
+
+ obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_incref ("kyc_data",
+ kc->kycs_data));
+ ret = TALER_TEMPLATING_build (kc->connection,
+ &response_code,
+ "kyc_text",
+ kc->mi->settings.id,
+ NULL,
+ obj,
+ &response);
+ json_decref (obj);
+ switch (ret)
+ {
+ case GNUNET_SYSERR:
+ /* failed to even produce a response */
+ GNUNET_break (0);
+ kc->phase = PHASE_RETURN_NO;
+ return;
+ case GNUNET_NO:
+ finish_request (kc,
+ MHD_queue_response (
+ kc->connection,
+ response_code,
+ response));
+ MHD_destroy_response (response);
+ return;
+ case GNUNET_OK:
+ TALER_MHD_add_global_headers (response,
+ false);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/plain"));
+ break;
+ } /* switch (ret) */
+ }
+ break;
+ case POF_PDF:
+ // not yet implemented
+ GNUNET_assert (0);
+ break;
+ }
+ {
+ char *etag;
+ char *qetag;
+
+ etag = GNUNET_STRINGS_data_to_string_alloc (&sh,
+ sizeof (sh));
+ GNUNET_asprintf (&qetag,
+ "\"%s\"",
+ etag);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_ETAG,
+ qetag));
+ GNUNET_free (qetag);
+ GNUNET_free (etag);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming /kyc handling as exchange interaction is done (%u)\n",
+ MHD_HTTP_OK);
+ finish_request (kc,
+ MHD_queue_response (
+ kc->connection,
+ response_code,
+ response));
+ MHD_destroy_response (response);
+}
+
+
+/**
+ * We are done with asynchronous processing, generate the
+ * response for the @e kc.
+ *
+ * @param[in,out] kc KYC context to respond for
+ */
+static void
+phase_generate_response (struct KycContext *kc)
+{
+ GNUNET_assert (NULL == kc->exchange_pending_head);
+ GNUNET_assert (GNUNET_NO == kc->suspended);
+ /* FIXME: mixing these two suspend conditions like this
+ does not seem sane */
+ if ( (! kc->return_immediately) &&
+ (! GNUNET_TIME_absolute_is_past (kc->timeout)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending: long poll target %d not reached\n",
+ kc->lpt);
+ wait_for_account (kc);
+ return;
+ }
+ if ( (! GNUNET_TIME_absolute_is_past (kc->timeout)) &&
+ ( (NULL != kc->lp_not_status) ||
+ (NULL != kc->lp_status) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Long-poll target status not reached, not returning response yet\n");
+ wait_for_account (kc);
+ return;
+ }
+ /* All exchange requests done, create final
+ big response from cumulated replies */
+ resume_kyc_with_response (kc);
+}
+
+
+/* ******************* main logic ***************** */
+
+/**
* Check the KYC status of an instance.
*
* @param mi instance to check KYC status of
@@ -1401,8 +1709,6 @@ get_instances_ID_kyc (
kc);
kc->connection = connection;
kc->hc = hc;
- kc->kycs_data = json_array ();
- GNUNET_assert (NULL != kc->kycs_data);
TALER_MHD_parse_request_timeout (connection,
&kc->timeout);
{
@@ -1425,22 +1731,6 @@ get_instances_ID_kyc (
}
kc->return_immediately
= (TALER_EXCHANGE_KLPT_NONE == kc->lpt);
- /* process 'exchange_url' argument */
- kc->exchange_url = MHD_lookup_connection_value (
- connection,
- MHD_GET_ARGUMENT_KIND,
- "exchange_url");
- if ( (NULL != kc->exchange_url) &&
- ( (! TALER_url_valid_charset (kc->exchange_url)) ||
- (! TALER_is_web_url (kc->exchange_url)) ) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "exchange_url must be a valid HTTP(s) URL");
- }
kc->lp_status = MHD_lookup_connection_value (
connection,
MHD_GET_ARGUMENT_KIND,
@@ -1457,203 +1747,44 @@ get_instances_ID_kyc (
"lp_not_etag",
&kc->lp_not_etag,
kc->have_lp_not_etag);
-
- /* Determine desired output format from Accept header */
- {
- const char *mime;
-
- mime = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_ACCEPT);
- if (NULL == mime)
- mime = "application/json";
- if (0 == strcmp (mime,
- "*/*"))
- mime = "application/json";
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC status requested for format %s\n",
- mime);
- if (0 == strcmp (mime,
- "application/json"))
- {
- kc->format = POF_JSON;
- }
- else if (0 == strcmp (mime,
- "text/plain"))
- {
- kc->format = POF_TEXT;
- }
-#if FUTURE
- else if (0 == strcmp (mime,
- "application/pdf"))
- {
- kc->format = POF_PDF;
- }
-#endif
- else
- {
- GNUNET_break_op (0);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_NOT_ACCEPTABLE,
- GNUNET_JSON_pack_string ("hint",
- mime));
- }
- }
-
- if (! GNUNET_TIME_absolute_is_past (kc->timeout))
- {
- if (kc->have_h_wire)
- {
- struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP ev = {
- .header.size = htons (sizeof (ev)),
- .header.type = htons (
- TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_STATUS_CHANGED
- ),
- .h_wire = kc->h_wire
- };
-
- kc->eh = TALER_MERCHANTDB_event_listen (
- TMH_db,
- &ev.header,
- GNUNET_TIME_absolute_get_remaining (kc->timeout),
- &kyc_change_cb,
- kc);
- }
- else
- {
- struct GNUNET_DB_EventHeaderP hdr = {
- .size = htons (sizeof (hdr)),
- .type = htons (TALER_DBEVENT_MERCHANT_KYC_STATUS_CHANGED)
- };
-
- kc->eh = TALER_MERCHANTDB_event_listen (
- TMH_db,
- &hdr,
- GNUNET_TIME_absolute_get_remaining (kc->timeout),
- &kyc_change_cb,
- kc);
- }
- } /* end register LISTEN hooks */
- } /* end 1st time initialization */
-
- if (GNUNET_SYSERR == kc->suspended)
- return MHD_NO; /* during shutdown, we don't generate any more replies */
- GNUNET_assert (GNUNET_NO == kc->suspended);
-
- if (NULL != kc->response)
- return MHD_queue_response (connection,
- kc->response_code,
- kc->response);
-
- /* Check our database */
+ }
+ while (1)
{
- enum GNUNET_DB_QueryStatus qs;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Checking KYC status for %s (%d/%s)\n",
- mi->settings.id,
- kc->have_h_wire,
- kc->exchange_url);
- /* We may run repeatedly due to long-polling; clear data
- from previous runs first */
- GNUNET_break (0 ==
- json_array_clear (kc->kycs_data));
- kc->in_db = true;
- qs = TALER_MERCHANTDB_account_kyc_get_status (
- TMH_db,
- mi->settings.id,
- kc->have_h_wire
- ? &kc->h_wire
- : NULL,
- kc->exchange_url,
- &kyc_status_cb,
- kc);
- kc->in_db = false;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "account_kyc_get_status returned %d records\n",
- (int) qs);
- switch (qs)
+ switch (kc->phase)
{
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- /* Database error */
- GNUNET_break (0);
- if (GNUNET_YES == kc->suspended)
- {
- /* must have suspended before DB error, resume! */
- MHD_resume_connection (connection);
- kc->suspended = GNUNET_NO;
- }
- return TALER_MHD_reply_with_ec (
- connection,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "account_kyc_get_status");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- {
- /* We use an Etag of all zeros for the 204 status code */
- static struct GNUNET_ShortHashCode zero_etag;
-
- /* no matching accounts, could not have suspended */
- GNUNET_assert (GNUNET_NO == kc->suspended);
- if (kc->have_lp_not_etag &&
- (0 == GNUNET_memcmp (&zero_etag,
- &kc->lp_not_etag)) &&
- (! GNUNET_TIME_absolute_is_past (kc->timeout)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No matching accounts, suspending to wait for this to change\n");
- MHD_suspend_connection (kc->connection);
- kc->suspended = GNUNET_YES;
- return MHD_YES;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No matching accounts, returning empty response\n");
- kc->response_code = MHD_HTTP_NO_CONTENT;
- kc->response = MHD_create_response_from_buffer_static (0,
- NULL);
- TALER_MHD_add_global_headers (kc->response,
- false);
- {
- char *etag;
-
- etag = GNUNET_STRINGS_data_to_string_alloc (&zero_etag,
- sizeof (zero_etag));
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (kc->response,
- MHD_HTTP_HEADER_ETAG,
- etag));
- GNUNET_free (etag);
- }
- return MHD_queue_response (connection,
- kc->response_code,
- kc->response);
- }
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ case PHASE_INIT:
+ phase_init (kc);
break;
- } /* end switch (qs) */
- }
-
- /* normal case, but maybe no async activity? In this case,
- respond immediately */
- if (NULL == kc->exchange_pending_head)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No asynchronous activity, responding now\n");
- kc_respond (kc);
- }
- if (GNUNET_YES == kc->suspended)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Request handling suspended, waiting for KYC status change\n");
- return MHD_YES;
+ case PHASE_DETERMINE_LONG_POLL:
+ phase_determine_long_poll (kc);
+ break;
+ case PHASE_DATABASE_KYC_CHECK:
+ phase_database_kyc_check (kc);
+ break;
+ case PHASE_NO_ACCOUNTS:
+ phase_no_accounts (kc);
+ break;
+ case PHASE_GENERATE_RESPONSE:
+ phase_generate_response (kc);
+ break;
+ case PHASE_IN_SHUTDOWN:
+ /* during shutdown, we don't generate any more replies */
+ GNUNET_assert (GNUNET_SYSERR == kc->suspended);
+ return MHD_NO;
+ case PHASE_RETURN_YES:
+ return MHD_YES;
+ case PHASE_RETURN_NO:
+ return MHD_NO;
+ case PHASE_SUSPENDED_ON_ACCOUNT:
+ /* suspended */
+ GNUNET_assert (GNUNET_YES == kc->suspended);
+ return MHD_YES;
+ case PHASE_SUSPENDED_ON_EXCHANGE:
+ /* suspended */
+ GNUNET_assert (GNUNET_YES == kc->suspended);
+ return MHD_YES;
+ }
}
-
- /* Should have generated a response */
- GNUNET_break (NULL != kc->response);
- return MHD_queue_response (connection,
- kc->response_code,
- kc->response);
}