diff options
| author | Christian Grothoff <christian@grothoff.org> | 2017-11-12 15:46:52 +0100 | 
|---|---|---|
| committer | Christian Grothoff <christian@grothoff.org> | 2017-11-12 15:46:52 +0100 | 
| commit | 8440de13339a3b38d9efa18b69df409e45cde625 (patch) | |
| tree | cd4c63c5d8c58eb9ac2dac93f057d8cb31f5ffca | |
| parent | f299130c50ca79efbfc8daf7f73aaba7ffb3a258 (diff) | |
work on #5077: reserve_pub vs. wtid issue, add reject functionality to wire plugin API (with stub implementations for now)
| -rw-r--r-- | src/auditor/taler-wire-auditor.c | 80 | ||||
| -rw-r--r-- | src/exchange/taler-exchange-wirewatch.c | 110 | ||||
| -rw-r--r-- | src/include/taler_error_codes.h | 4 | ||||
| -rw-r--r-- | src/include/taler_wire_plugin.h | 71 | ||||
| -rw-r--r-- | src/wire/plugin_wire_sepa.c | 103 | ||||
| -rw-r--r-- | src/wire/plugin_wire_test.c | 202 | ||||
| -rw-r--r-- | src/wire/test_wire_plugin_transactions_test.c | 5 | 
7 files changed, 503 insertions, 72 deletions
| diff --git a/src/auditor/taler-wire-auditor.c b/src/auditor/taler-wire-auditor.c index 4ee92566..9c739902 100644 --- a/src/auditor/taler-wire-auditor.c +++ b/src/auditor/taler-wire-auditor.c @@ -70,7 +70,8 @@ static struct GNUNET_CONTAINER_MultiHashMap *in_map;  /**   * Map with information about outgoing wire transfers. - * Maps hashes of the wire offsets to `struct ReserveOutInfo`s. + * Maps hashes of the wire subjects (in binary encoding) + * to `struct ReserveOutInfo`s.   */  static struct GNUNET_CONTAINER_MultiHashMap *out_map; @@ -658,7 +659,9 @@ complain_out_not_found (void *cls,                       "row", (json_int_t) 0,                       "amount_wired", TALER_JSON_from_amount (&roi->details.amount),                       "amount_justified", TALER_JSON_from_amount (&zero), -                     "wtid", GNUNET_JSON_from_data_auto (&roi->details.reserve_pub), /* #5077 missnomer */ +                     "wtid", (NULL == roi->details.wtid_s) +                     ? GNUNET_JSON_from_data_auto (&roi->details.wtid) +                     : json_string (roi->details.wtid_s),                       "timestamp", GNUNET_STRINGS_absolute_time_to_string (roi->details.execution_date),                       "diagnostic", "justification for wire transfer not found"));    GNUNET_break (GNUNET_OK == @@ -726,6 +729,7 @@ history_debit_cb (void *cls,  		  const struct TALER_WIRE_TransferDetails *details)  {    struct ReserveOutInfo *roi; +  struct GNUNET_HashCode rowh;    if (TALER_BANK_DIRECTION_NONE == dir)    { @@ -735,13 +739,36 @@ history_debit_cb (void *cls,      check_exchange_wire_out ();      return GNUNET_OK;    } +  if (NULL != details->wtid_s) +  { +    char *diagnostic; + +    GNUNET_CRYPTO_hash (row_off, +                        row_off_size, +                        &rowh); +    GNUNET_asprintf (&diagnostic, +                     "malformed wire transfer subject `%s'", +                     details->wtid_s); +    report (report_row_inconsistencies, +            json_pack ("{s:s, s:I, s:o, s:o, s:s}", +                       "table", "bank wire log", +                       "row", (json_int_t) 0, +                       "amount", TALER_JSON_from_amount (&details->amount), +                       "wire_offset_hash", GNUNET_JSON_from_data_auto (&rowh), +                       "diagnostic", diagnostic)); +    /* TODO: report generator currently ignores 'amount' for this +       table, maybe use a different table to report this issue! */ +    /* TODO: add 'amount' to some total amount that was badly wired! */ +    GNUNET_free (diagnostic); +    return GNUNET_SYSERR; +  }    roi = GNUNET_new (struct ReserveOutInfo); -  GNUNET_CRYPTO_hash (&details->reserve_pub, /* FIXME (#5077): missnomer */ -		      sizeof (details->reserve_pub), +  GNUNET_CRYPTO_hash (&details->wtid, +		      sizeof (details->wtid),  		      &roi->subject_hash);    roi->details.amount = details->amount;    roi->details.execution_date = details->execution_date; -  roi->details.reserve_pub = details->reserve_pub; /* FIXME (#5077): missnomer & redundant */ +  roi->details.wtid = details->wtid;    roi->details.account_details = json_incref ((json_t *) details->account_details);    if (GNUNET_OK !=        GNUNET_CONTAINER_multihashmap_put (out_map, @@ -749,13 +776,25 @@ history_debit_cb (void *cls,  					 roi,  					 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))    { -    GNUNET_break_op (0); /* duplicate wire offset is not allowed! */ +    char *diagnostic; + +    GNUNET_CRYPTO_hash (row_off, +                        row_off_size, +                        &rowh); +    GNUNET_asprintf (&diagnostic, +                     "duplicate wire transfer subject `%s'", +                     TALER_B2S (&roi->subject_hash));      report (report_row_inconsistencies, -            json_pack ("{s:s, s:I, s:o, s:s}", +            json_pack ("{s:s, s:I, s:o, s:o, s:s}",                         "table", "bank wire log",                         "row", (json_int_t) 0, -                       "wire_offset_hash", GNUNET_JSON_from_data_auto (&roi->subject_hash), -                       "diagnostic", "duplicate wire offset")); +                       "amount", TALER_JSON_from_amount (&details->amount), +                       "wire_offset_hash", GNUNET_JSON_from_data_auto (&rowh), +                       "diagnostic", diagnostic)); +    /* TODO: report generator currently ignores 'amount' for this +       table, maybe use a different table to report this issue! */ +    /* TODO: add 'amount' to some total amount that was badly wired! */ +    GNUNET_free (diagnostic);      return GNUNET_SYSERR;    }    return GNUNET_OK; @@ -830,7 +869,12 @@ reserve_in_cb (void *cls,    rii->row_off_size = wire_reference_size;    rii->details.amount = *credit;    rii->details.execution_date = execution_date; -  rii->details.reserve_pub = *reserve_pub; +  /* reserve public key should be the WTID */ +  GNUNET_assert (sizeof (rii->details.wtid) == +                 sizeof (*reserve_pub)); +  memcpy (&rii->details.wtid, +          reserve_pub, +          sizeof (*reserve_pub));    rii->details.account_details = json_incref ((json_t *) sender_account_details);    rii->rowid = rowid;    if (GNUNET_OK != @@ -875,7 +919,7 @@ complain_in_not_found (void *cls,                       "row", (json_int_t) rii->rowid,                       "amount_expected", TALER_JSON_from_amount (&rii->details.amount),                       "amount_wired", TALER_JSON_from_amount (&zero), -                     "wtid", GNUNET_JSON_from_data_auto (&rii->details.reserve_pub), /* also reserve_pub, but see #5077 missnomer */ +                     "wtid", GNUNET_JSON_from_data_auto (&rii->details.wtid),                       "timestamp", GNUNET_STRINGS_absolute_time_to_string (rii->details.execution_date),                       "diagnostic", "incoming wire transfer claimed by exchange not found"));    GNUNET_break (GNUNET_OK == @@ -966,16 +1010,16 @@ history_credit_cb (void *cls,                         "diagnostic", "wire reference size missmatch"));      return GNUNET_OK;    } -  if (0 != memcmp (&details->reserve_pub, -		   &rii->details.reserve_pub, -		   sizeof (struct TALER_ReservePublicKeyP))) +  if (0 != memcmp (&details->wtid, +		   &rii->details.wtid, +		   sizeof (struct TALER_WireTransferIdentifierRawP)))    {      report (report_reserve_in_inconsistencies,              json_pack ("{s:I, s:o, s:o, s:o, s:s, s:s}",                         "row", GNUNET_JSON_from_data (row_off, row_off_size),                         "amount_exchange_expected", TALER_JSON_from_amount (&rii->details.amount),                         "amount_wired", TALER_JSON_from_amount (&zero), -                       "wtid", GNUNET_JSON_from_data_auto (&rii->details.reserve_pub), /* #5077 missnomer */ +                       "wtid", GNUNET_JSON_from_data_auto (&rii->details.wtid),                         "timestamp", GNUNET_STRINGS_absolute_time_to_string (rii->details.execution_date),                         "diagnostic", "wire subject does not match"));      GNUNET_break (GNUNET_OK == @@ -987,7 +1031,7 @@ history_credit_cb (void *cls,                         "row", GNUNET_JSON_from_data (row_off, row_off_size),                         "amount_exchange_expected", TALER_JSON_from_amount (&zero),                         "amount_wired", TALER_JSON_from_amount (&details->amount), -                       "wtid", GNUNET_JSON_from_data_auto (&details->reserve_pub), /* #5077 missnomer */ +                       "wtid", GNUNET_JSON_from_data_auto (&details->wtid),                         "timestamp", GNUNET_STRINGS_absolute_time_to_string (details->execution_date),                         "diagnostic", "wire subject does not match")); @@ -1005,7 +1049,7 @@ history_credit_cb (void *cls,                         "row", GNUNET_JSON_from_data (row_off, row_off_size),                         "amount_exchange_expected", TALER_JSON_from_amount (&rii->details.amount),                         "amount_wired", TALER_JSON_from_amount (&details->amount), -                       "wtid", GNUNET_JSON_from_data_auto (&details->reserve_pub), /* #5077 missnomer */ +                       "wtid", GNUNET_JSON_from_data_auto (&details->wtid),                         "timestamp", GNUNET_STRINGS_absolute_time_to_string (details->execution_date),                         "diagnostic", "wire amount does not match"));      if (0 < TALER_amount_cmp (&details->amount, @@ -1046,7 +1090,7 @@ history_credit_cb (void *cls,              json_pack ("{s:s, s:o, s:o}",                         "amount", TALER_JSON_from_amount (&rii->details.amount),                         "row", GNUNET_JSON_from_data (row_off, row_off_size), -                       "wtid", GNUNET_JSON_from_data_auto (&rii->details.reserve_pub))); /* FIXME #5077 missnomer */ +                       "wtid", GNUNET_JSON_from_data_auto (&rii->details.wtid)));      GNUNET_break (GNUNET_OK ==                    TALER_amount_add (&total_missattribution_in,                                      &total_missattribution_in, diff --git a/src/exchange/taler-exchange-wirewatch.c b/src/exchange/taler-exchange-wirewatch.c index 312f8ac5..ca7f3bad 100644 --- a/src/exchange/taler-exchange-wirewatch.c +++ b/src/exchange/taler-exchange-wirewatch.c @@ -37,6 +37,23 @@  /** + * Closure for #reject_cb(). + */ +struct RejectContext +{ +  /** +   * Wire transfer subject that was illformed. +   */ +  char *wtid_s; + +  /** +   * Database session that encountered the problem. +   */ +  struct TALER_EXCHANGEDB_Session *session; +}; + + +/**   * Handle to the plugin.   */  static struct TALER_WIRE_Plugin *wire_plugin; @@ -109,6 +126,11 @@ static struct GNUNET_SCHEDULER_Task *task;   */  static struct TALER_WIRE_HistoryHandle *hh; +/** + * Active request to reject a wire transfer. + */ +static struct TALER_WIRE_RejectHandle *rt; +  /**   * We're being aborted with CTRL-C (or SIGTERM). Shut down. @@ -129,6 +151,15 @@ shutdown_task (void *cls)  				     hh);      hh = NULL;    } +  if (NULL != rt) +  { +    char *wtid_s; + +    wtid_s = wire_plugin->reject_transfer_cancel (wire_plugin->cls, +                                                  rt); +    rt = NULL; +    GNUNET_free (wtid_s); +  }    TALER_EXCHANGEDB_plugin_unload (db_plugin);    db_plugin = NULL;    TALER_WIRE_plugin_unload (wire_plugin); @@ -205,6 +236,48 @@ find_transfers (void *cls);  /** + * Function called upon completion of the rejection of a wire transfer. + * + * @param cls closure with the `struct RejectContext` + * @param ec error code for the operation + */ +static void +reject_cb (void *cls, +           enum TALER_ErrorCode ec) +{ +  struct RejectContext *rtc = cls; +  enum GNUNET_DB_QueryStatus qs; + +  rt = NULL; +  if (TALER_EC_NONE != ec) +  { +    fprintf (stderr, +             "Failed to wire back transfer `%s': %d\n", +             rtc->wtid_s, +             ec); +    GNUNET_free (rtc->wtid_s); +    db_plugin->rollback (db_plugin->cls, +			 rtc->session); +    GNUNET_free (rtc); +    GNUNET_SCHEDULER_shutdown (); +    return; +  } +  GNUNET_free (rtc->wtid_s); +  qs = db_plugin->commit (db_plugin->cls, +                          rtc->session); +  GNUNET_free (rtc); +  if (0 <= qs) +  { +    GNUNET_free_non_null (start_off); +    start_off = last_row_off; +    start_off_size = last_row_off_size; +  } +  task = GNUNET_SCHEDULER_add_now (&find_transfers, +                                   NULL); +} + + +/**   * Callbacks of this type are used to serve the result of asking   * the bank for the transaction history.   * @@ -224,6 +297,7 @@ history_cb (void *cls,  {    struct TALER_EXCHANGEDB_Session *session = cls;    enum GNUNET_DB_QueryStatus qs; +  struct TALER_ReservePublicKeyP reserve_pub;    if (TALER_BANK_DIRECTION_NONE == dir)    { @@ -254,13 +328,45 @@ history_cb (void *cls,  				       NULL);      return GNUNET_OK; /* will be ignored anyway */    } +  if (NULL != details->wtid_s) +  { +    struct RejectContext *rtc; + +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "Wire transfer over %s has invalid subject `%s', sending it back!\n", +                TALER_amount2s (&details->amount), +                details->wtid_s); +    if (last_row_off_size != row_off_size) +    { +      GNUNET_free_non_null (last_row_off); +      last_row_off = GNUNET_malloc (row_off_size); +    } +    memcpy (last_row_off, +            row_off, +            row_off_size); +    rtc = GNUNET_new (struct RejectContext); +    rtc->session = session; +    rtc->wtid_s = GNUNET_strdup (details->wtid_s); +    rt = wire_plugin->reject_transfer (wire_plugin->cls, +                                       row_off, +                                       row_off_size, +                                       &reject_cb, +                                       rtc); +    return GNUNET_SYSERR; /* will continue later... */ +  } +    GNUNET_log (GNUNET_ERROR_TYPE_INFO,                "Adding wire transfer over %s with subject `%s'\n",                TALER_amount2s (&details->amount), -              TALER_B2S (&details->reserve_pub)); +              TALER_B2S (&details->wtid)); +  /* Wire transfer identifier == reserve public key */ +  GNUNET_assert (sizeof (reserve_pub) == sizeof (details->wtid)); +  memcpy (&reserve_pub, +          &details->wtid, +          sizeof (reserve_pub));    qs = db_plugin->reserves_in_insert (db_plugin->cls,  				      session, -				      &details->reserve_pub, +				      &reserve_pub,  				      &details->amount,  				      details->execution_date,  				      details->account_details, diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h index 65831f4a..931b5ee1 100644 --- a/src/include/taler_error_codes.h +++ b/src/include/taler_error_codes.h @@ -63,6 +63,10 @@ enum TALER_ErrorCode     */    TALER_EC_INTERNAL_INVARIANT_FAILURE = 5, +  /** +   * Operation timed out. +   */ +  TALER_EC_TIMEOUT = 6,    /* ********** generic error codes ************* */ diff --git a/src/include/taler_wire_plugin.h b/src/include/taler_wire_plugin.h index 969af357..6e355baf 100644 --- a/src/include/taler_wire_plugin.h +++ b/src/include/taler_wire_plugin.h @@ -59,13 +59,17 @@ struct TALER_WIRE_TransferDetails    struct GNUNET_TIME_Absolute execution_date;    /** -   * Reserve public key that was encoded in the wire transfer subject. -   * FIXME (#5077): this is incorrect for *outgoing* wire transfers. -   * Maybe use `struct TALER_WireTransferIdentifierRawP` here instead? -   * OTOH, we might want to make this even more generic in case of -   * invalid transfers, so that we can capture those as well! +   * Binary data that was encoded in the wire transfer subject, if +   * it decoded properly.  Otherwise all-zeros and @e wtid_s is set.     */ -  struct TALER_ReservePublicKeyP reserve_pub; +  struct TALER_WireTransferIdentifierRawP wtid; + +  /** +   * Wire transfer identifer as a string.  Set to NULL if the +   * identifier was properly Base32 encoded and this @e wtid could be +   * set instead. +   */ +  char *wtid_s;    /**     * The other account that was involved @@ -94,6 +98,18 @@ typedef int  /** + * Callbacks of this type are used to serve the result of asking + * the bank to reject a wire transfer. + * + * @param cls closure + * @param ec status of the operation, #TALER_EC_NONE on success + */ +typedef void +(*TALER_WIRE_RejectTransferCallback) (void *cls, +                                      enum TALER_ErrorCode ec); + + +/**   * Handle returned for cancelling a preparation step.   */  struct TALER_WIRE_PrepareHandle; @@ -308,12 +324,55 @@ struct TALER_WIRE_Plugin    /**     * Cancel going over the account's history.     * +   * @param cls plugins' closure     * @param whh operation to cancel     */    void    (*get_history_cancel) (void *cls,  			 struct TALER_WIRE_HistoryHandle *whh); + +  /** +   * Reject an incoming wire transfer that was obtained from the +   * history. This function can be used to transfer funds back to +   * the sender if the WTID was malformed (i.e. due to a typo). +   * +   * Calling `reject_transfer` twice on the same wire transfer should +   * be idempotent, i.e. not cause the funds to be wired back twice. +   * Furthermore, the transfer should henceforth be removed from the +   * results returned by @e get_history. +   * +   * @param cls plugin's closure +   * @param start_off offset of the wire transfer in plugin-specific format +   * @param start_off_len number of bytes in @a start_off +   * @param rej_cb function to call with the result of the operation +   * @param rej_cb_cls closure for @a rej_cb +   * @return handle to cancel the operation +   */ +  struct TALER_WIRE_RejectHandle * +  (*reject_transfer)(void *cls, +                     const void *start_off, +                     size_t start_off_len, +                     TALER_WIRE_RejectTransferCallback rej_cb, +                     void *rej_cb_cls); + +  /** +   * Cancel ongoing reject operation.  Note that the rejection may still +   * proceed. Basically, if this function is called, the rejection may +   * have happened or not.  This function is usually used during shutdown +   * or system upgrades.  At a later point, the application must call +   * @e reject_transfer again for this wire transfer, unless the +   * @e get_history shows that the wire transfer no longer exists. +   * +   * @param cls plugins' closure +   * @param rh operation to cancel +   * @return closure of the callback of the operation +   */ +  void * +  (*reject_transfer_cancel)(void *cls, +                            struct TALER_WIRE_RejectHandle *rh); + +  }; diff --git a/src/wire/plugin_wire_sepa.c b/src/wire/plugin_wire_sepa.c index 5de3472b..416acac7 100644 --- a/src/wire/plugin_wire_sepa.c +++ b/src/wire/plugin_wire_sepa.c @@ -791,6 +791,107 @@ sepa_get_history_cancel (void *cls,  } + +/** + * Context for a rejection operation. + */ +struct TALER_WIRE_RejectHandle +{ +  /** +   * Function to call with the result. +   */ +  TALER_WIRE_RejectTransferCallback rej_cb; + +  /** +   * Closure for @e rej_cb. +   */ +  void *rej_cb_cls; + +  /** +   * Handle to task for timeout of operation. +   */ +  struct GNUNET_SCHEDULER_Task *timeout_task; +}; + + +/** + * Rejection operation failed with timeout, notify callback + * and clean up. + * + * @param cls closure with `struct TALER_WIRE_RejectHandle` + */ +static void +timeout_reject (void *cls) +{ +  struct TALER_WIRE_RejectHandle *rh = cls; + +  rh->timeout_task = NULL; +  rh->rej_cb (rh->rej_cb_cls, +              TALER_EC_NOT_IMPLEMENTED /* in the future: TALER_EC_TIMEOUT */); +  GNUNET_free (rh); +} + + +/** + * Reject an incoming wire transfer that was obtained from the + * history. This function can be used to transfer funds back to + * the sender if the WTID was malformed (i.e. due to a typo). + * + * Calling `reject_transfer` twice on the same wire transfer should + * be idempotent, i.e. not cause the funds to be wired back twice. + * Furthermore, the transfer should henceforth be removed from the + * results returned by @e get_history. + * + * @param cls plugin's closure + * @param start_off offset of the wire transfer in plugin-specific format + * @param start_off_len number of bytes in @a start_off + * @param rej_cb function to call with the result of the operation + * @param rej_cb_cls closure for @a rej_cb + * @return handle to cancel the operation + */ +static struct TALER_WIRE_RejectHandle * +sepa_reject_transfer (void *cls, +                      const void *start_off, +                      size_t start_off_len, +                      TALER_WIRE_RejectTransferCallback rej_cb, +                      void *rej_cb_cls) +{ +  struct TALER_WIRE_RejectHandle *rh; + +  GNUNET_break (0); /* not implemented, just a stub! */ +  rh = GNUNET_new (struct TALER_WIRE_RejectHandle); +  rh->rej_cb = rej_cb; +  rh->rej_cb_cls = rej_cb_cls; +  rh->timeout_task = GNUNET_SCHEDULER_add_now (&timeout_reject, +                                               rh); +  return rh; +} + + +/** + * Cancel ongoing reject operation.  Note that the rejection may still + * proceed. Basically, if this function is called, the rejection may + * have happened or not.  This function is usually used during shutdown + * or system upgrades.  At a later point, the application must call + * @e reject_transfer again for this wire transfer, unless the + * @e get_history shows that the wire transfer no longer exists. + * + * @param cls plugins' closure + * @param rh operation to cancel + * @return closure of the callback of the operation + */ +static void * +sepa_reject_transfer_cancel (void *cls, +                             struct TALER_WIRE_RejectHandle *rh) +{ +  void *ret = rh->rej_cb_cls; + +  GNUNET_SCHEDULER_cancel (rh->timeout_task); +  GNUNET_free (rh); +  return ret; +} + +  /**   * Initialize sepa-wire subsystem.   * @@ -832,6 +933,8 @@ libtaler_plugin_wire_sepa_init (void *cls)    plugin->execute_wire_transfer_cancel = &sepa_execute_wire_transfer_cancel;    plugin->get_history = &sepa_get_history;    plugin->get_history_cancel = &sepa_get_history_cancel; +  plugin->reject_transfer = &sepa_reject_transfer; +  plugin->reject_transfer_cancel = &sepa_reject_transfer_cancel;    return plugin;  } diff --git a/src/wire/plugin_wire_test.c b/src/wire/plugin_wire_test.c index c41bd7e9..e9d5ad08 100644 --- a/src/wire/plugin_wire_test.c +++ b/src/wire/plugin_wire_test.c @@ -820,52 +820,65 @@ bhist_cb (void *cls,    uint64_t bserial_id = GNUNET_htonll (serial_id);    struct TALER_WIRE_TransferDetails wd; -  if (MHD_HTTP_OK == http_status) -  { -    char *subject; -    char *space; - -    wd.amount = details->amount; -    wd.execution_date = details->execution_date; -    subject = GNUNET_strdup (details->wire_transfer_subject); -    space = strchr (subject, (int) ' '); -    if (NULL != space) -    { -      /* Space separates the actual wire transfer subject from the -         exchange base URL (if present, expected only for outgoing -         transactions).  So we cut the string off at the space. */ -      *space = '\0'; -    } -    /* NOTE: For a real bank, the subject should include a checksum! */ -    if (GNUNET_OK != -        GNUNET_STRINGS_string_to_data (subject, -                                       strlen (subject), -                                       &wd.reserve_pub, -                                       sizeof (wd.reserve_pub))) +  switch (http_status) { +  case MHD_HTTP_OK:      { -      GNUNET_break (0); +      char *subject; +      char *space; + +      wd.amount = details->amount; +      wd.execution_date = details->execution_date; +      subject = GNUNET_strdup (details->wire_transfer_subject); +      space = strchr (subject, (int) ' '); +      if (NULL != space) +      { +        /* Space separates the actual wire transfer subject from the +           exchange base URL (if present, expected only for outgoing +           transactions).  So we cut the string off at the space. */ +        *space = '\0'; +      } +      /* NOTE: For a real bank, the subject should include a checksum! */ +      if (GNUNET_OK != +          GNUNET_STRINGS_string_to_data (subject, +                                         strlen (subject), +                                         &wd.wtid, +                                         sizeof (wd.wtid))) +      { +        /* Ill-formed wire subject, set binary version to all zeros +           and pass as a string, this time including the part after +           the space. */ +        memset (&wd.wtid, +                0, +                sizeof (wd.wtid)); +        wd.wtid_s = details->wire_transfer_subject; +      }        GNUNET_free (subject); -      /* NOTE: for a "real" bank, we would want to trigger logic to undo the -         wire transfer. However, for the "demo" bank, it should currently -         be "impossible" to do wire transfers with invalid subjects, and -         equally we thus don't need to undo them (and there is no API to do -         that nicely either right now). So we don't handle this case for now. */ -      return; +      wd.account_details = details->account_details; + +      if ( (NULL != whh->hres_cb) && +           (GNUNET_OK != +            whh->hres_cb (whh->hres_cb_cls, +                          dir, +                          &bserial_id, +                          sizeof (bserial_id), +                          &wd)) ) +        whh->hres_cb = NULL; +      break;      } -    GNUNET_free (subject); -    wd.account_details = details->account_details; - -    if ( (NULL != whh->hres_cb) && -	 (GNUNET_OK != -	  whh->hres_cb (whh->hres_cb_cls, -			dir, -			&bserial_id, -			sizeof (bserial_id), -			&wd)) ) -      whh->hres_cb = NULL; -  } -  else -  { +  case MHD_HTTP_NO_CONTENT: +    if (NULL != whh->hres_cb) +      (void) whh->hres_cb (whh->hres_cb_cls, +                           TALER_BANK_DIRECTION_NONE, +                           NULL, +                           0, +                           NULL); +    whh->hh = NULL; +    GNUNET_free (whh); +    break; +  default: +    /* FIXME: consider modifying API to pass more specific error code(s) +       back to the application. */ +    GNUNET_break (0);      if (NULL != whh->hres_cb)        (void) whh->hres_cb (whh->hres_cb_cls,                             TALER_BANK_DIRECTION_NONE, @@ -874,6 +887,7 @@ bhist_cb (void *cls,                             NULL);      whh->hh = NULL;      GNUNET_free (whh); +    break;    }  } @@ -976,6 +990,106 @@ test_get_history_cancel (void *cls,  /** + * Context for a rejection operation. + */ +struct TALER_WIRE_RejectHandle +{ +  /** +   * Function to call with the result. +   */ +  TALER_WIRE_RejectTransferCallback rej_cb; + +  /** +   * Closure for @e rej_cb. +   */ +  void *rej_cb_cls; + +  /** +   * Handle to task for timeout of operation. +   */ +  struct GNUNET_SCHEDULER_Task *timeout_task; +}; + + +/** + * Rejection operation failed with timeout, notify callback + * and clean up. + * + * @param cls closure with `struct TALER_WIRE_RejectHandle` + */ +static void +timeout_reject (void *cls) +{ +  struct TALER_WIRE_RejectHandle *rh = cls; + +  rh->timeout_task = NULL; +  rh->rej_cb (rh->rej_cb_cls, +              TALER_EC_NOT_IMPLEMENTED /* in the future: TALER_EC_TIMEOUT */); +  GNUNET_free (rh); +} + + +/** + * Reject an incoming wire transfer that was obtained from the + * history. This function can be used to transfer funds back to + * the sender if the WTID was malformed (i.e. due to a typo). + * + * Calling `reject_transfer` twice on the same wire transfer should + * be idempotent, i.e. not cause the funds to be wired back twice. + * Furthermore, the transfer should henceforth be removed from the + * results returned by @e get_history. + * + * @param cls plugin's closure + * @param start_off offset of the wire transfer in plugin-specific format + * @param start_off_len number of bytes in @a start_off + * @param rej_cb function to call with the result of the operation + * @param rej_cb_cls closure for @a rej_cb + * @return handle to cancel the operation + */ +static struct TALER_WIRE_RejectHandle * +test_reject_transfer (void *cls, +                      const void *start_off, +                      size_t start_off_len, +                      TALER_WIRE_RejectTransferCallback rej_cb, +                      void *rej_cb_cls) +{ +  struct TALER_WIRE_RejectHandle *rh; + +  GNUNET_break (0); /* not implemented, just a stub! */ +  rh = GNUNET_new (struct TALER_WIRE_RejectHandle); +  rh->rej_cb = rej_cb; +  rh->rej_cb_cls = rej_cb_cls; +  rh->timeout_task = GNUNET_SCHEDULER_add_now (&timeout_reject, +                                               rh); +  return rh; +} + + +/** + * Cancel ongoing reject operation.  Note that the rejection may still + * proceed. Basically, if this function is called, the rejection may + * have happened or not.  This function is usually used during shutdown + * or system upgrades.  At a later point, the application must call + * @e reject_transfer again for this wire transfer, unless the + * @e get_history shows that the wire transfer no longer exists. + * + * @param cls plugins' closure + * @param rh operation to cancel + * @return closure of the callback of the operation + */ +static void * +test_reject_transfer_cancel (void *cls, +                             struct TALER_WIRE_RejectHandle *rh) +{ +  void *ret = rh->rej_cb_cls; + +  GNUNET_SCHEDULER_cancel (rh->timeout_task); +  GNUNET_free (rh); +  return ret; +} + + +/**   * Initialize test-wire subsystem.   *   * @param cls a configuration instance @@ -1087,6 +1201,8 @@ libtaler_plugin_wire_test_init (void *cls)    plugin->execute_wire_transfer_cancel = &test_execute_wire_transfer_cancel;    plugin->get_history = &test_get_history;    plugin->get_history_cancel = &test_get_history_cancel; +  plugin->reject_transfer = &test_reject_transfer; +  plugin->reject_transfer_cancel = &test_reject_transfer_cancel;    return plugin;  } diff --git a/src/wire/test_wire_plugin_transactions_test.c b/src/wire/test_wire_plugin_transactions_test.c index ce31e99e..26331b5b 100644 --- a/src/wire/test_wire_plugin_transactions_test.c +++ b/src/wire/test_wire_plugin_transactions_test.c @@ -211,9 +211,8 @@ history_result_cb (void *cls,      return GNUNET_SYSERR;    }    if (0 != memcmp (&wtid, -                   &details->reserve_pub, -                   GNUNET_MIN (sizeof (struct TALER_ReservePublicKeyP), -                               sizeof (wtid)))) +                   &details->wtid, +                   sizeof (struct TALER_WireTransferIdentifierRawP)))    {      GNUNET_break (0);      global_ret = GNUNET_SYSERR; | 
