From b219366cdf2252ebbe8a6b0246eb4d6262ad37fb Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 23 Apr 2023 22:43:04 +0200 Subject: [PATCH 01/53] update for API change as per #6363 --- contrib/gana | 2 +- src/auditor/batch.sh | 2 +- src/auditor/generate-auditor-basedb.sh | 2 +- src/auditor/generate-revoke-basedb.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/gana b/contrib/gana index bf43b20a0..bd4e73b2e 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit bf43b20a0362ac19bcf1bab9c33215e55d8d9f36 +Subproject commit bd4e73b2ed06269fdee42eaad21acb5be8be9302 diff --git a/src/auditor/batch.sh b/src/auditor/batch.sh index ec9ed8309..1f8896c42 100755 --- a/src/auditor/batch.sh +++ b/src/auditor/batch.sh @@ -193,7 +193,7 @@ echo " DONE" echo -n "Setting up merchant" -curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances +curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"accounts":[{"payto_uri":"payto://x-taler-bank/localhost/43"}],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances echo " DONE" diff --git a/src/auditor/generate-auditor-basedb.sh b/src/auditor/generate-auditor-basedb.sh index 2ab403a83..95fc2216c 100755 --- a/src/auditor/generate-auditor-basedb.sh +++ b/src/auditor/generate-auditor-basedb.sh @@ -398,7 +398,7 @@ echo " DONE" echo -n "Setting up merchant" -curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"payto_uris":["payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances +curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"accounts":[{"payto_uri":"payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"}],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances echo " DONE" diff --git a/src/auditor/generate-revoke-basedb.sh b/src/auditor/generate-revoke-basedb.sh index 88d8e562c..745b96b72 100755 --- a/src/auditor/generate-revoke-basedb.sh +++ b/src/auditor/generate-revoke-basedb.sh @@ -400,7 +400,7 @@ echo " DONE" # Setup merchant echo -n "Setting up merchant" -curl -H "Content-Type: application/json" -X POST -d '{"auth": {"method": "external"}, "payto_uris":["payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances +curl -H "Content-Type: application/json" -X POST -d '{"auth": {"method": "external"}, "accounts":[{"payto_uri":"payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"}],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances # run wallet CLI From 505170ce1f3d0512ef57da042f656a53cc24912a Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 24 Apr 2023 20:37:53 +0200 Subject: [PATCH 02/53] fix filename --- ...t_exchange_api_twisted.conf => test_exchange_api-twisted.conf} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/testing/{test_exchange_api_twisted.conf => test_exchange_api-twisted.conf} (100%) diff --git a/src/testing/test_exchange_api_twisted.conf b/src/testing/test_exchange_api-twisted.conf similarity index 100% rename from src/testing/test_exchange_api_twisted.conf rename to src/testing/test_exchange_api-twisted.conf From 1a3dbf8c98cd5faf784deb2cfc75a1ba1988a3c5 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 24 Apr 2023 21:15:04 +0200 Subject: [PATCH 03/53] -fix port --- src/testing/test_exchange_api-twisted.conf | 4 - src/testing/test_exchange_api_twisted.c | 166 +++++++++++---------- 2 files changed, 90 insertions(+), 80 deletions(-) diff --git a/src/testing/test_exchange_api-twisted.conf b/src/testing/test_exchange_api-twisted.conf index 17f8833e7..536d36ee4 100644 --- a/src/testing/test_exchange_api-twisted.conf +++ b/src/testing/test_exchange_api-twisted.conf @@ -8,9 +8,6 @@ # only seeks the exchange/BASE_URL URL to connect to the exchange. BASE_URL = "http://localhost:8888/" -[bank] -HTTP_PORT = 8082 - [twister] # HTTP listen port for twister HTTP_PORT = 8888 @@ -30,4 +27,3 @@ ACCEPT_FROM6 = ::1; UNIXPATH = /tmp/taler-service-twister.sock UNIX_MATCH_UID = NO UNIX_MATCH_GID = YES - diff --git a/src/testing/test_exchange_api_twisted.c b/src/testing/test_exchange_api_twisted.c index e89df4647..388c064aa 100644 --- a/src/testing/test_exchange_api_twisted.c +++ b/src/testing/test_exchange_api_twisted.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -112,8 +112,9 @@ run (void *cls, * response from a refresh-reveal operation. */ struct TALER_TESTING_Command refresh_409_conflict[] = { - CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve", - "EUR:5.01"), + CMD_TRANSFER_TO_EXCHANGE ( + "refresh-create-reserve", + "EUR:5.01"), /** * Make previous command effective. */ @@ -121,34 +122,38 @@ run (void *cls, /** * Withdraw EUR:5. */ - TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin", - "refresh-create-reserve", - "EUR:5", - 0, /* age restriction off */ - MHD_HTTP_OK), - TALER_TESTING_cmd_deposit ("refresh-deposit-partial", - "refresh-withdraw-coin", - 0, - bc.user42_payto, - "{\"items\":[{\"name\":\"ice cream\",\ - \"value\":\"EUR:1\"}]}", - GNUNET_TIME_UNIT_ZERO, - "EUR:1", - MHD_HTTP_OK), + TALER_TESTING_cmd_withdraw_amount ( + "refresh-withdraw-coin", + "refresh-create-reserve", + "EUR:5", + 0, /* age restriction off */ + MHD_HTTP_OK), + TALER_TESTING_cmd_deposit ( + "refresh-deposit-partial", + "refresh-withdraw-coin", + 0, + bc.user42_payto, + "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:1\"}]}", + GNUNET_TIME_UNIT_ZERO, + "EUR:1", + MHD_HTTP_OK), /** * Melt the rest of the coin's value * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ - TALER_TESTING_cmd_melt ("refresh-melt", - "refresh-withdraw-coin", - MHD_HTTP_OK, - NULL), + TALER_TESTING_cmd_melt ( + "refresh-melt", + "refresh-withdraw-coin", + MHD_HTTP_OK, + NULL), /* Trigger 409 Conflict. */ - TALER_TESTING_cmd_flip_upload ("flip-upload", - config_file, - "transfer_privs.0"), - TALER_TESTING_cmd_refresh_reveal ("refresh-(flipped-)reveal", - "refresh-melt", - MHD_HTTP_CONFLICT), + TALER_TESTING_cmd_flip_upload ( + "flip-upload", + config_file, + "transfer_privs.0"), + TALER_TESTING_cmd_refresh_reveal ( + "refresh-(flipped-)reveal", + "refresh-melt", + MHD_HTTP_CONFLICT), TALER_TESTING_cmd_end () }; @@ -159,23 +164,25 @@ run (void *cls, * lib test suite. */ struct TALER_TESTING_Command refund[] = { - CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r1", - "EUR:5.01"), + CMD_TRANSFER_TO_EXCHANGE ( + "create-reserve-r1", + "EUR:5.01"), CMD_EXEC_WIREWATCH ("wirewatch-r1"), - TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1", - "create-reserve-r1", - "EUR:5", - 0, /* age restriction off */ - MHD_HTTP_OK), - TALER_TESTING_cmd_deposit ("deposit-refund-1", - "withdraw-coin-r1", - 0, - bc.user42_payto, - "{\"items\":[{\"name\":\"ice cream\"," - "\"value\":\"EUR:5\"}]}", - GNUNET_TIME_UNIT_MINUTES, - "EUR:5", - MHD_HTTP_OK), + TALER_TESTING_cmd_withdraw_amount ( + "withdraw-coin-r1", + "create-reserve-r1", + "EUR:5", + 0, /* age restriction off */ + MHD_HTTP_OK), + TALER_TESTING_cmd_deposit ( + "deposit-refund-1", + "withdraw-coin-r1", + 0, + bc.user42_payto, + "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:5\"}]}", + GNUNET_TIME_UNIT_MINUTES, + "EUR:5", + MHD_HTTP_OK), TALER_TESTING_cmd_refund ("refund-currency-mismatch", MHD_HTTP_BAD_REQUEST, "USD:5", @@ -190,18 +197,18 @@ run (void *cls, /* This next deposit CMD is only used to provide a * good merchant signature to the next (failing) refund * operations. */ - TALER_TESTING_cmd_deposit ("deposit-refund-to-fail", - "withdraw-coin-r1", - 0, /* coin index. */ - bc.user42_payto, - /* This parameter will make any comparison about - h_contract_terms fail, when /refund will be handled. - So in other words, this is h_contract mismatch. */ - "{\"items\":[{\"name\":\"ice skate\"," - "\"value\":\"EUR:5\"}]}", - GNUNET_TIME_UNIT_MINUTES, - "EUR:5", - MHD_HTTP_CONFLICT), + TALER_TESTING_cmd_deposit ( + "deposit-refund-to-fail", + "withdraw-coin-r1", + 0, /* coin index. */ + bc.user42_payto, + /* This parameter will make any comparison about + h_contract_terms fail, when /refund will be handled. + So in other words, this is h_contract mismatch. */ + "{\"items\":[{\"name\":\"ice skate\",\"value\":\"EUR:5\"}]}", + GNUNET_TIME_UNIT_MINUTES, + "EUR:5", + MHD_HTTP_CONFLICT), TALER_TESTING_cmd_refund ("refund-deposit-not-found", MHD_HTTP_NOT_FOUND, "EUR:5", @@ -219,10 +226,11 @@ run (void *cls, * are out of date. */ struct TALER_TESTING_Command expired_keys[] = { - TALER_TESTING_cmd_modify_header_dl ("modify-expiration", - config_file, - MHD_HTTP_HEADER_EXPIRES, - "Wed, 19 Jan 586524 08:01:49 GMT"), + TALER_TESTING_cmd_modify_header_dl ( + "modify-expiration", + config_file, + MHD_HTTP_HEADER_EXPIRES, + "Wed, 19 Jan 586524 08:01:49 GMT"), TALER_TESTING_cmd_check_keys_pull_all_keys ( "check-keys-expiration-0", 2), @@ -232,28 +240,34 @@ run (void *cls, CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r2", "EUR:55.01"), CMD_EXEC_WIREWATCH ("wirewatch-r2"), - TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r2", - "create-reserve-r2", - "EUR:5", - 0, /* age restriction off */ - MHD_HTTP_OK), + TALER_TESTING_cmd_withdraw_amount ( + "withdraw-coin-r2", + "create-reserve-r2", + "EUR:5", + 0, /* age restriction off */ + MHD_HTTP_OK), TALER_TESTING_cmd_end () }; #endif struct TALER_TESTING_Command commands[] = { - TALER_TESTING_cmd_wire_add ("add-wire-account", - "payto://x-taler-bank/localhost/2?receiver-name=2", - MHD_HTTP_NO_CONTENT, - false), - TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys", - config_file), - TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys", - 1), - TALER_TESTING_cmd_batch ("refresh-reveal-409-conflict", - refresh_409_conflict), - TALER_TESTING_cmd_batch ("refund", - refund), + TALER_TESTING_cmd_wire_add ( + "add-wire-account", + "payto://x-taler-bank/localhost/2?receiver-name=2", + MHD_HTTP_NO_CONTENT, + false), + TALER_TESTING_cmd_exec_offline_sign_keys ( + "offline-sign-future-keys", + config_file), + TALER_TESTING_cmd_check_keys_pull_all_keys ( + "refetch /keys", + 1), + TALER_TESTING_cmd_batch ( + "refresh-reveal-409-conflict", + refresh_409_conflict), + TALER_TESTING_cmd_batch ( + "refund", + refund), #if 0 TALER_TESTING_cmd_batch ("expired-keys", expired_keys), From 487f23502f00cc0ec54e7cec043f41582030613d Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 25 Apr 2023 23:06:58 +0200 Subject: [PATCH 04/53] add nexus-fetch-transactions --- src/include/taler_testing_lib.h | 30 +++ src/testing/Makefile.am | 2 + src/testing/test_bank_api.c | 9 + src/testing/testing_api_cmd_exec_wget.c | 158 +++++++++++++++ ...testing_api_cmd_nexus_fetch_transactions.c | 186 ++++++++++++++++++ 5 files changed, 385 insertions(+) create mode 100644 src/testing/testing_api_cmd_exec_wget.c create mode 100644 src/testing/testing_api_cmd_nexus_fetch_transactions.c diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 37d347c30..5fc930a86 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -1242,6 +1242,36 @@ TALER_TESTING_cmd_exec_wirewatch (const char *label, const char *config_filename); +/** + * Request URL via "wget". + * + * @param label command label. + * @param url URL to fetch + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_wget (const char *label, + const char *url); + + +/** + * Request fetch-transactions via "wget". + * + * @param label command label. + * @param username username to use + * @param password password to use + * @param bank_base_url base URL of the nexus + * @param account_id account to fetch transactions for + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_nexus_fetch_transactions (const char *label, + const char *username, + const char *password, + const char *bank_base_url, + const char *account_id); + + /** * Make a "expire" CMD. * diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index f0dcecc9e..86cb029c7 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -68,11 +68,13 @@ libtalertesting_la_SOURCES = \ testing_api_cmd_exec_expire.c \ testing_api_cmd_exec_router.c \ testing_api_cmd_exec_transfer.c \ + testing_api_cmd_exec_wget.c \ testing_api_cmd_exec_wirewatch.c \ testing_api_cmd_insert_deposit.c \ testing_api_cmd_kyc_check_get.c \ testing_api_cmd_kyc_proof.c \ testing_api_cmd_kyc_wallet_get.c \ + testing_api_cmd_nexus_fetch_transactions.c \ testing_api_cmd_oauth.c \ testing_api_cmd_offline_sign_global_fees.c \ testing_api_cmd_offline_sign_wire_fees.c \ diff --git a/src/testing/test_bank_api.c b/src/testing/test_bank_api.c index 8cd1ca319..ed2d00353 100644 --- a/src/testing/test_bank_api.c +++ b/src/testing/test_bank_api.c @@ -129,6 +129,15 @@ run (void *cls, TALER_TESTING_cmd_sleep ("Waiting 5s for 'debit-1' to settle", 5), + with_libeufin + ? TALER_TESTING_cmd_nexus_fetch_transactions ( + "fetch-transactions-at-nexus", + "exchange", /* from taler-nexus-prepare */ + "x", /* from taler-nexus-prepare */ + "http://localhost:5001", + "my-bank-account") /* from taler-nexus-prepare */ + : TALER_TESTING_cmd_sleep ("nop", + 0), TALER_TESTING_cmd_bank_debits ("history-2b", &bc.exchange_auth, NULL, diff --git a/src/testing/testing_api_cmd_exec_wget.c b/src/testing/testing_api_cmd_exec_wget.c new file mode 100644 index 000000000..67aceca0a --- /dev/null +++ b/src/testing/testing_api_cmd_exec_wget.c @@ -0,0 +1,158 @@ +/* + This file is part of TALER + Copyright (C) 2023 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 testing/testing_api_cmd_exec_wget.c + * @brief run a wget command + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "wget" CMD. + */ +struct WgetState +{ + /** + * Process for the wgeter. + */ + struct GNUNET_OS_Process *wget_proc; + + /** + * URL to used by the wget. + */ + const char *url; +}; + + +/** + * Run the command; use the `wget' program. + * + * @param cls closure. + * @param cmd command currently being executed. + * @param is interpreter state. + */ +static void +wget_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct WgetState *ws = cls; + + (void) cmd; + ws->wget_proc + = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "wget", + "wget", + ws->url, + NULL); + if (NULL == ws->wget_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "wget" CMD, and possibly + * kills its process if it did not terminate regularly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +wget_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct WgetState *ws = cls; + + (void) cmd; + if (NULL != ws->wget_proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (ws->wget_proc, + SIGKILL)); + GNUNET_OS_process_wait (ws->wget_proc); + GNUNET_OS_process_destroy (ws->wget_proc); + ws->wget_proc = NULL; + } + GNUNET_free (ws); +} + + +/** + * Offer "wget" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static enum GNUNET_GenericReturnValue +wget_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct WgetState *ws = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_process (&ws->wget_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_wget (const char *label, + const char *url) +{ + struct WgetState *ws; + + ws = GNUNET_new (struct WgetState); + ws->url = url; + + { + struct TALER_TESTING_Command cmd = { + .cls = ws, + .label = label, + .run = &wget_run, + .cleanup = &wget_cleanup, + .traits = &wget_traits + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_exec_wget.c */ diff --git a/src/testing/testing_api_cmd_nexus_fetch_transactions.c b/src/testing/testing_api_cmd_nexus_fetch_transactions.c new file mode 100644 index 000000000..11bcee06c --- /dev/null +++ b/src/testing/testing_api_cmd_nexus_fetch_transactions.c @@ -0,0 +1,186 @@ +/* + This file is part of TALER + Copyright (C) 2023 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 testing/testing_api_cmd_exec_nft.c + * @brief run a nft command + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "nft" CMD. + */ +struct NftState +{ + /** + * Process for the nfter. + */ + struct GNUNET_OS_Process *nft_proc; + + const char *username; + const char *password; + const char *bank_base_url; + const char *account_id; +}; + + +/** + * Run the command; use the `nft' program. + * + * @param cls closure. + * @param cmd command currently being executed. + * @param is interpreter state. + */ +static void +nft_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct NftState *ws = cls; + char *url; + char *user; + char *pass; + + (void) cmd; + GNUNET_asprintf (&url, + "%s/bank-accounts/%s/fetch-transactions", + ws->bank_base_url, + ws->account_id); + GNUNET_asprintf (&user, + "--user=%s", + ws->username); + GNUNET_asprintf (&pass, + "--password=%s", + ws->password); + ws->nft_proc + = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "wget", + "wget", + "--header=Content-Type:application/json", + "--auth-no-challenge", + "--post-data={\"level\":\"all\",\"rangeType\":\"latest\"}", + user, + pass, + url, + NULL); + GNUNET_free (url); + GNUNET_free (user); + GNUNET_free (pass); + if (NULL == ws->nft_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "nft" CMD, and possibly + * kills its process if it did not terminate regularly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +nft_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct NftState *ws = cls; + + (void) cmd; + if (NULL != ws->nft_proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (ws->nft_proc, + SIGKILL)); + GNUNET_OS_process_wait (ws->nft_proc); + GNUNET_OS_process_destroy (ws->nft_proc); + ws->nft_proc = NULL; + } + GNUNET_free (ws); +} + + +/** + * Offer "nft" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static enum GNUNET_GenericReturnValue +nft_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct NftState *ws = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_process (&ws->nft_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_nexus_fetch_transactions ( + const char *label, + const char *username, + const char *password, + const char *bank_base_url, + const char *account_id) +{ + struct NftState *ws; + + ws = GNUNET_new (struct NftState); + ws->username = username; + ws->password = password; + ws->bank_base_url = bank_base_url; + ws->account_id = account_id; + + { + struct TALER_TESTING_Command cmd = { + .cls = ws, + .label = label, + .run = &nft_run, + .cleanup = &nft_cleanup, + .traits = &nft_traits + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_nexus_fetch_transactions.c */ From e1439e64017dc6c5f1f5a81361a44afe1d8b6157 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 25 Apr 2023 23:12:33 +0200 Subject: [PATCH 05/53] -fix doxygen --- src/testing/testing_api_cmd_nexus_fetch_transactions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testing/testing_api_cmd_nexus_fetch_transactions.c b/src/testing/testing_api_cmd_nexus_fetch_transactions.c index 11bcee06c..f4c7883c0 100644 --- a/src/testing/testing_api_cmd_nexus_fetch_transactions.c +++ b/src/testing/testing_api_cmd_nexus_fetch_transactions.c @@ -17,7 +17,7 @@ */ /** - * @file testing/testing_api_cmd_exec_nft.c + * @file testing/testing_api_cmd_exec_nexus_fetch_transactions.c * @brief run a nft command * @author Christian Grothoff */ From af3c92f9d5a9e0d674ab2b1d8028268552ab9f5b Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 25 Apr 2023 23:19:18 +0200 Subject: [PATCH 06/53] -dox --- src/testing/testing_api_cmd_nexus_fetch_transactions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testing/testing_api_cmd_nexus_fetch_transactions.c b/src/testing/testing_api_cmd_nexus_fetch_transactions.c index f4c7883c0..152647963 100644 --- a/src/testing/testing_api_cmd_nexus_fetch_transactions.c +++ b/src/testing/testing_api_cmd_nexus_fetch_transactions.c @@ -17,7 +17,7 @@ */ /** - * @file testing/testing_api_cmd_exec_nexus_fetch_transactions.c + * @file testing/testing_api_cmd_nexus_fetch_transactions.c * @brief run a nft command * @author Christian Grothoff */ From 1a63275d9804762f0bcdc1cd23060c2e9b364fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Mon, 1 May 2023 14:05:58 +0200 Subject: [PATCH 07/53] WiP: age-withdraw, finished reveal-request, 10/n - /age-withdraw/$ACH/reveal handler now fully implemented - for consistency with api: rename of tables from withdraw_age_... to age_withdraw --- ...taler-exchange-httpd_age-withdraw_reveal.c | 163 ++++++++++++++++-- src/exchange/taler-exchange-httpd_metrics.h | 3 +- .../taler-exchange-httpd_refreshes_reveal.c | 10 +- ....sql => 0003-age_withdraw_commitments.sql} | 66 ++++--- ...eals.sql => 0003-age_withdraw_reveals.sql} | 33 ++-- src/exchangedb/exchange-0003.sql.in | 2 + src/exchangedb/pg_get_age_withdraw_info.c | 2 +- .../pg_insert_age_withdraw_reveal.c | 4 +- src/exchangedb/pg_insert_records_by_table.c | 60 +++---- src/exchangedb/pg_lookup_records_by_table.c | 58 +++---- src/exchangedb/pg_lookup_serial_by_table.c | 20 +-- src/include/taler_exchangedb_plugin.h | 46 +++-- 12 files changed, 304 insertions(+), 163 deletions(-) rename src/exchangedb/{0003-withdraw_age_commitments.sql => 0003-age_withdraw_commitments.sql} (68%) rename src/exchangedb/{0003-withdraw_age_reveals.sql => 0003-age_withdraw_reveals.sql} (79%) diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c index 5cee7277a..931463cbd 100644 --- a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c +++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c @@ -22,6 +22,8 @@ #include #include #include +#include "taler-exchange-httpd_metrics.h" +#include "taler_exchangedb_plugin.h" #include "taler_mhd_lib.h" #include "taler-exchange-httpd_mhd.h" #include "taler-exchange-httpd_age-withdraw_reveal.h" @@ -387,12 +389,10 @@ denomination_is_valid ( struct TEH_DenominationKey *dks, MHD_RESULT *result) { - dks = TEH_keys_denomination_by_hash2 ( - ksh, - denom_h, - connection, - result); - + dks = TEH_keys_denomination_by_hash2 (ksh, + denom_h, + connection, + result); if (NULL == dks) { /* The denomination doesn't exist */ @@ -784,6 +784,43 @@ verify_commitment_and_max_age ( } +/** + * @brief Send a response for "/age-withdraw/$RCH/reveal" + * + * @param connection The http connection to the client to send the reponse to + * @param num_coins Number of new coins with age restriction for which we reveal data + * @param awrcs array of @a num_coins signatures revealed + * @return a MHD result code + */ +static MHD_RESULT +reply_age_withdraw_reveal_success ( + struct MHD_Connection *connection, + unsigned int num_coins, + const struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin *awrcs) +{ + json_t *list = json_array (); + GNUNET_assert (NULL != list); + + for (unsigned int index = 0; + index < num_coins; + index++) + { + json_t *obj = GNUNET_JSON_PACK ( + TALER_JSON_pack_blinded_denom_sig ("ev_sig", + &awrcs[index].coin_sig)); + GNUNET_assert (0 == + json_array_append_new (list, + obj)); + } + + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_array_steal ("ev_sigs", + list)); +} + + /** * @brief Signs and persists the undisclosed coins * @@ -796,7 +833,7 @@ verify_commitment_and_max_age ( * @return GNUNET_OK on success, GNUNET_SYSERR otherwise */ static enum GNUNET_GenericReturnValue -finalize_age_withdraw_and_sign ( +sign_and_finalize_age_withdraw ( struct MHD_Connection *connection, const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, const uint32_t num_coins, @@ -806,7 +843,9 @@ finalize_age_withdraw_and_sign ( { enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; struct TEH_CoinSignData csds[num_coins]; - struct TALER_BlindedDenominationSignature bss[num_coins]; + struct TALER_BlindedDenominationSignature bds[num_coins]; + struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin awrcs[num_coins]; + enum GNUNET_DB_QueryStatus qs; for (uint32_t i = 0; istart (TEH_plugin->cls, + "insert_age_withdraw_reveal batch")) + { + GNUNET_break (0); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_START_FAILED, + NULL); + goto cleanup; + } + + qs = TEH_plugin->insert_age_withdraw_reveal (TEH_plugin->cls, + h_commitment, + num_coins, + awrcs); + + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + TEH_plugin->rollback (TEH_plugin->cls); + continue; + } + else if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + TEH_plugin->rollback (TEH_plugin->cls); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert_age_withdraw_reveal"); + goto cleanup; + } + + changed = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); + + /* Commit the transaction */ + qs = TEH_plugin->commit (TEH_plugin->cls); + if (qs >= 0) + { + if (changed) + TEH_METRICS_num_success[TEH_MT_SUCCESS_AGE_WITHDRAW_REVEAL]++; + + break; /* success */ + + } + else if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + TEH_plugin->rollback (TEH_plugin->cls); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + NULL); + goto cleanup; + } + else + { + TEH_plugin->rollback (TEH_plugin->cls); + } + } /* end of retry */ + + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + GNUNET_break (0); + TEH_plugin->rollback (TEH_plugin->cls); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + NULL); + goto cleanup; + } + + /* Generate final (positive) response */ + ret = reply_age_withdraw_reveal_success (connection, + num_coins, + awrcs); +cleanup: + // TODO[oec]: handle error cases + // TODO[oec]: cleanup! return ret; } @@ -922,7 +1053,7 @@ TEH_handler_age_withdraw_reveal ( break; /* Finally, sign and persist the coins */ - if (GNUNET_OK != finalize_age_withdraw_and_sign ( + if (GNUNET_OK != sign_and_finalize_age_withdraw ( rc->connection, &actx.commitment.h_commitment, actx.num_coins, diff --git a/src/exchange/taler-exchange-httpd_metrics.h b/src/exchange/taler-exchange-httpd_metrics.h index 8f6804355..318113c1f 100644 --- a/src/exchange/taler-exchange-httpd_metrics.h +++ b/src/exchange/taler-exchange-httpd_metrics.h @@ -61,7 +61,8 @@ enum TEH_MetricTypeSuccess TEH_MT_SUCCESS_BATCH_WITHDRAW = 3, TEH_MT_SUCCESS_MELT = 4, TEH_MT_SUCCESS_REFRESH_REVEAL = 5, - TEH_MT_SUCCESS_COUNT = 6 /* MUST BE LAST! */ + TEH_MT_SUCCESS_AGE_WITHDRAW_REVEAL = 6, + TEH_MT_SUCCESS_COUNT = 7 /* MUST BE LAST! */ }; /** diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c index 08a85265c..89bdf2724 100644 --- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c +++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c @@ -773,12 +773,17 @@ clean_age: NULL); goto cleanup; } + for (unsigned int i = 0; inum_fresh_coins; i++) + { rrcs[i].coin_sig = bss[i]; + rrcs[i].blinded_planchet = rcds[i].blinded_planchet; + } } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Signatures ready, starting DB interaction\n"); + for (unsigned int r = 0; rnum_fresh_coins; i++) - { - struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i]; - rrc->blinded_planchet = rcds[i].blinded_planchet; - } qs = TEH_plugin->insert_refresh_reveal ( TEH_plugin->cls, melt_serial_id, diff --git a/src/exchangedb/0003-withdraw_age_commitments.sql b/src/exchangedb/0003-age_withdraw_commitments.sql similarity index 68% rename from src/exchangedb/0003-withdraw_age_commitments.sql rename to src/exchangedb/0003-age_withdraw_commitments.sql index b8451129a..d74a697c3 100644 --- a/src/exchangedb/0003-withdraw_age_commitments.sql +++ b/src/exchangedb/0003-age_withdraw_commitments.sql @@ -14,24 +14,24 @@ -- TALER; see the file COPYING. If not, see -- -CREATE FUNCTION create_table_withdraw_age_commitments( +CREATE FUNCTION create_table_age_withdraw_commitments( IN partition_suffix VARCHAR DEFAULT NULL ) RETURNS VOID LANGUAGE plpgsql AS $$ DECLARE - table_name VARCHAR DEFAULT 'withdraw_age_commitments'; + table_name VARCHAR DEFAULT 'age_withdraw_commitments'; BEGIN PERFORM create_partitioned_table( 'CREATE TABLE %I' - '(withdraw_age_commitment_id BIGINT GENERATED BY DEFAULT AS IDENTITY' - ',h_commitment BYTEA PRIMARY KEY CHECK (LENGTH(h_commitment)=64)' + '(age_withdraw_commitment_id BIGINT GENERATED BY DEFAULT AS IDENTITY' + ',h_commitment BYTEA CHECK (LENGTH(h_commitment)=64)' ',amount_with_fee_val INT8 NOT NULL' ',amount_with_fee_frac INT4 NOT NULL' ',max_age INT2 NOT NULL' - ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)' - ',reserve_sig BYTEA CHECK (LENGTH(reserve_sig)=64)' + ',reserve_pub BYTEA CHECK (LENGTH(reserve_pub)=32)' + ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)' ',noreveal_index INT4 NOT NULL' ') %s ;' ,table_name @@ -77,66 +77,58 @@ END $$; -CREATE FUNCTION constrain_table_withdraw_age_commitments( +CREATE FUNCTION constrain_table_age_withdraw_commitments( IN partition_suffix VARCHAR ) RETURNS void LANGUAGE plpgsql AS $$ DECLARE - table_name VARCHAR DEFAULT 'withdraw_age_commitments'; + table_name VARCHAR DEFAULT 'age_withdraw_commitments'; BEGIN table_name = concat_ws('_', table_name, partition_suffix); - EXECUTE FORMAT ( 'ALTER TABLE ' || table_name || - ' ADD PRIMARY KEY (h_commitment, reserve_pub);' + ' ADD PRIMARY KEY (h_commitment);' ); EXECUTE FORMAT ( 'ALTER TABLE ' || table_name || - ' ADD CONSTRAINT ' || table_name || '_withdraw_age_commitment_id_key' - ' UNIQUE (withdraw_age_commitment_id);' + ' ADD CONSTRAINT ' || table_name || '_h_commitment_reserve_pub_key' + ' UNIQUE (h_commitment, reserve_pub);' + ); + EXECUTE FORMAT ( + 'ALTER TABLE ' || table_name || + ' ADD CONSTRAINT ' || table_name || '_age_withdraw_commitment_id_key' + ' UNIQUE (age_withdraw_commitment_id);' ); END $$; -CREATE FUNCTION foreign_table_withdraw_age_commitments() +CREATE FUNCTION foreign_table_age_withdraw_commitments() RETURNS void LANGUAGE plpgsql AS $$ DECLARE - table_name VARCHAR DEFAULT 'withdraw_age_commitments'; + table_name VARCHAR DEFAULT 'age_withdraw_commitments'; BEGIN EXECUTE FORMAT ( 'ALTER TABLE ' || table_name || ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub' ' FOREIGN KEY (reserve_pub)' - ' REFERENCES reserves (reserve_pub) ON DELETE CASCADE;' + ' REFERENCES reserves(reserve_pub) ON DELETE CASCADE;' ); END $$; -INSERT INTO exchange_tables - (name - ,version - ,action - ,partitioned - ,by_range) - VALUES - ('withdraw_age_commitments' - ,'exchange-0003' - ,'create' - ,TRUE - ,FALSE), - ('withdraw_age_commitments' - ,'exchange-0003' - ,'constrain' - ,TRUE - ,FALSE), - ('withdraw_age_commitments' - ,'exchange-0003' - ,'foreign' - ,TRUE - ,FALSE); +INSERT INTO exchange_tables + (name + ,version + ,action + ,partitioned + ,by_range) +VALUES + ('age_withdraw_commitments', 'exchange-0003', 'create', TRUE ,FALSE), + ('age_withdraw_commitments', 'exchange-0003', 'constrain',TRUE ,FALSE), + ('age_withdraw_commitments', 'exchange-0003', 'foreign', TRUE ,FALSE); diff --git a/src/exchangedb/0003-withdraw_age_reveals.sql b/src/exchangedb/0003-age_withdraw_reveals.sql similarity index 79% rename from src/exchangedb/0003-withdraw_age_reveals.sql rename to src/exchangedb/0003-age_withdraw_reveals.sql index af66eab75..1c55fb190 100644 --- a/src/exchangedb/0003-withdraw_age_reveals.sql +++ b/src/exchangedb/0003-age_withdraw_reveals.sql @@ -14,25 +14,24 @@ -- TALER; see the file COPYING. If not, see -- -CREATE FUNCTION create_table_withdraw_age_revealed_coins( +CREATE FUNCTION create_table_age_withdraw_revealed_coins( IN partition_suffix VARCHAR DEFAULT NULL ) RETURNS VOID LANGUAGE plpgsql AS $$ DECLARE - table_name VARCHAR DEFAULT 'withdraw_age_revealed_coins'; + table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins'; BEGIN PERFORM create_partitioned_table( 'CREATE TABLE %I' - '(withdraw_age_revealed_coins_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE + '(age_withdraw_revealed_coins_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE ',h_commitment BYTEA NOT NULL CHECK (LENGTH(h_commitment)=64)' ',freshcoin_index INT4 NOT NULL' ',denominations_serial INT8 NOT NULL' ',coin_ev BYTEA NOT NULL' ',h_coin_ev BYTEA CHECK (LENGTH(h_coin_ev)=64)' ',ev_sig BYTEA NOT NULL' - ',ewv BYTEA NOT NULL' ') %s ;' ,table_name ,'PARTITION BY HASH (h_commitment)' @@ -79,30 +78,24 @@ BEGIN ,table_name ,partition_suffix ); - PERFORM comment_partitioned_column( - 'Exchange contributed values in the creation of the fresh coin (see /csr)' - ,'ewv' - ,table_name - ,partition_suffix - ); END $$; -CREATE FUNCTION constrain_table_withdraw_age_revealed_coins( +CREATE FUNCTION constrain_table_age_withdraw_revealed_coins( IN partition_suffix VARCHAR ) RETURNS void LANGUAGE plpgsql AS $$ DECLARE - table_name VARCHAR DEFAULT 'withdraw_age_revealed_coins'; + table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins'; BEGIN table_name = concat_ws('_', table_name, partition_suffix); EXECUTE FORMAT ( 'ALTER TABLE ' || table_name || - ' ADD CONSTRAINT ' || table_name || '_withdraw_age_revealed_coins_id_key' - ' UNIQUE (withdraw_age_revealed_coins_id);' + ' ADD CONSTRAINT ' || table_name || '_age_withdraw_revealed_coins_id_key' + ' UNIQUE (age_withdraw_revealed_coins_id);' ); EXECUTE FORMAT ( 'ALTER TABLE ' || table_name || @@ -112,18 +105,18 @@ BEGIN END $$; -CREATE FUNCTION foreign_table_withdraw_age_revealed_coins() +CREATE FUNCTION foreign_table_age_withdraw_revealed_coins() RETURNS void LANGUAGE plpgsql AS $$ DECLARE - table_name VARCHAR DEFAULT 'withdraw_age_revealed_coins'; + table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins'; BEGIN EXECUTE FORMAT ( 'ALTER TABLE ' || table_name || ' ADD CONSTRAINT ' || table_name || '_foreign_h_commitment' ' FOREIGN KEY (h_commitment)' - ' REFERENCES withdraw_age_commitments (h_commitment) ON DELETE CASCADE;' + ' REFERENCES age_withdraw_commitments (h_commitment) ON DELETE CASCADE;' ); EXECUTE FORMAT ( 'ALTER TABLE ' || table_name || @@ -142,17 +135,17 @@ INSERT INTO exchange_tables ,partitioned ,by_range) VALUES - ('withdraw_age_revealed_coins' + ('age_withdraw_revealed_coins' ,'exchange-0003' ,'create' ,TRUE ,FALSE), - ('withdraw_age_revealed_coins' + ('age_withdraw_revealed_coins' ,'exchange-0003' ,'constrain' ,TRUE ,FALSE), - ('withdraw_age_revealed_coins' + ('age_withdraw_revealed_coins' ,'exchange-0003' ,'foreign' ,TRUE diff --git a/src/exchangedb/exchange-0003.sql.in b/src/exchangedb/exchange-0003.sql.in index 5461c0dd3..01733ea24 100644 --- a/src/exchangedb/exchange-0003.sql.in +++ b/src/exchangedb/exchange-0003.sql.in @@ -25,6 +25,8 @@ SET search_path TO exchange; #include "0003-aml_status.sql" #include "0003-aml_staff.sql" #include "0003-aml_history.sql" +#include "0003-age_withdraw_commitments.sql" +#include "0003-age_withdraw_reveals.sql" COMMIT; diff --git a/src/exchangedb/pg_get_age_withdraw_info.c b/src/exchangedb/pg_get_age_withdraw_info.c index 754b572c9..f4a68b377 100644 --- a/src/exchangedb/pg_get_age_withdraw_info.c +++ b/src/exchangedb/pg_get_age_withdraw_info.c @@ -69,7 +69,7 @@ TEH_PG_get_age_withdraw_info ( ",amount_with_fee_val" ",amount_with_fee_frac" ",noreveal_index" - " FROM withdraw_age_commitments" + " FROM age_withdraw_commitments" " WHERE reserve_pub=$1 and h_commitment=$2;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "get_age_withdraw_info", diff --git a/src/exchangedb/pg_insert_age_withdraw_reveal.c b/src/exchangedb/pg_insert_age_withdraw_reveal.c index 336ed384f..ebba7ebbc 100644 --- a/src/exchangedb/pg_insert_age_withdraw_reveal.c +++ b/src/exchangedb/pg_insert_age_withdraw_reveal.c @@ -42,8 +42,8 @@ TEH_PG_insert_age_withdraw_reveal ( /* TODO */ #if 0 PREPARE (pg, - "insert_withdraw_age_revealed_coin", - "INSERT INTO withdraw_age_reveals " + "insert_age_withdraw_revealed_coin", + "INSERT INTO age_withdraw_reveals " "(h_commitment " ",freshcoin_index " ",denominations_serial " diff --git a/src/exchangedb/pg_insert_records_by_table.c b/src/exchangedb/pg_insert_records_by_table.c index 3ec9c77cc..e597f2bf5 100644 --- a/src/exchangedb/pg_insert_records_by_table.c +++ b/src/exchangedb/pg_insert_records_by_table.c @@ -2062,39 +2062,39 @@ irbt_cb_table_purse_deletion (struct PostgresClosure *pg, /** - * Function called with withdraw_age_commitments records to insert into table. + * Function called with age_withdraw_commitments records to insert into table. * * @param pg plugin context * @param td record to insert */ static enum GNUNET_DB_QueryStatus -irbt_cb_table_withdraw_age_commitments (struct PostgresClosure *pg, +irbt_cb_table_age_withdraw_commitments (struct PostgresClosure *pg, const struct TALER_EXCHANGEDB_TableData *td) { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&td->serial), GNUNET_PQ_query_param_auto_from_type ( - &td->details.withdraw_age_commitments.h_commitment), + &td->details.age_withdraw_commitments.h_commitment), TALER_PQ_query_param_amount ( - &td->details.withdraw_age_commitments.amount_with_fee), + &td->details.age_withdraw_commitments.amount_with_fee), GNUNET_PQ_query_param_uint16 ( - &td->details.withdraw_age_commitments.max_age), + &td->details.age_withdraw_commitments.max_age), GNUNET_PQ_query_param_auto_from_type ( - &td->details.withdraw_age_commitments.reserve_pub), + &td->details.age_withdraw_commitments.reserve_pub), GNUNET_PQ_query_param_auto_from_type ( - &td->details.withdraw_age_commitments.reserve_sig), + &td->details.age_withdraw_commitments.reserve_sig), GNUNET_PQ_query_param_uint32 ( - &td->details.withdraw_age_commitments.noreveal_index), + &td->details.age_withdraw_commitments.noreveal_index), GNUNET_PQ_query_param_absolute_time ( - &td->details.withdraw_age_commitments.timestamp), + &td->details.age_withdraw_commitments.timestamp), GNUNET_PQ_query_param_end }; PREPARE (pg, - "insert_into_table_withdraw_age_commitments", - "INSERT INTO withdraw_age_commitments" - "(withdraw_age_commitment_id" + "insert_into_table_age_withdraw_commitments", + "INSERT INTO age_withdraw_commitments" + "(age_withdraw_commitment_id" ",h_commitment" ",amount_with_fee_val" ",amount_with_fee_frac" @@ -2106,19 +2106,19 @@ irbt_cb_table_withdraw_age_commitments (struct PostgresClosure *pg, ") VALUES " "($1, $2, $3, $4, $5, $6, $7, $8, $9);"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_into_table_withdraw_age_commitments", + "insert_into_table_age_withdraw_commitments", params); } /** - * Function called with withdraw_age_revealed_coins records to insert into table. + * Function called with age_withdraw_revealed_coins records to insert into table. * * @param pg plugin context * @param td record to insert */ static enum GNUNET_DB_QueryStatus -irbt_cb_table_withdraw_age_revealed_coins (struct PostgresClosure *pg, +irbt_cb_table_age_withdraw_revealed_coins (struct PostgresClosure *pg, const struct TALER_EXCHANGEDB_TableData *td) { @@ -2126,26 +2126,26 @@ irbt_cb_table_withdraw_age_revealed_coins (struct PostgresClosure *pg, struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&td->serial), GNUNET_PQ_query_param_auto_from_type ( - &td->details.withdraw_age_revealed_coins.h_commitment), + &td->details.age_withdraw_revealed_coins.h_commitment), GNUNET_PQ_query_param_uint32 ( - &td->details.withdraw_age_revealed_coins.freshcoin_index), + &td->details.age_withdraw_revealed_coins.freshcoin_index), GNUNET_PQ_query_param_uint64 ( - &td->details.withdraw_age_revealed_coins.denominations_serial), + &td->details.age_withdraw_revealed_coins.denominations_serial), GNUNET_PQ_query_param_fixed_size ( - td->details.withdraw_age_revealed_coins.coin_ev, - td->details.withdraw_age_revealed_coins.coin_ev_size), + td->details.age_withdraw_revealed_coins.coin_ev, + td->details.age_withdraw_revealed_coins.coin_ev_size), GNUNET_PQ_query_param_auto_from_type (&h_coin_ev), TALER_PQ_query_param_blinded_denom_sig ( - &td->details.withdraw_age_revealed_coins.ev_sig), + &td->details.age_withdraw_revealed_coins.ev_sig), TALER_PQ_query_param_exchange_withdraw_values ( - &td->details.withdraw_age_revealed_coins.ewv), + &td->details.age_withdraw_revealed_coins.ewv), GNUNET_PQ_query_param_end }; PREPARE (pg, - "insert_into_table_withdraw_age_revealed_coins", - "INSERT INTO withdraw_age_revealed_coins" - "(withdraw_age_revealed_coins_id" + "insert_into_table_age_withdraw_revealed_coins", + "INSERT INTO age_withdraw_revealed_coins" + "(age_withdraw_revealed_coins_id" ",h_commitment" ",freshcoin_index" ",denominations_serial" @@ -2156,12 +2156,12 @@ irbt_cb_table_withdraw_age_revealed_coins (struct PostgresClosure *pg, ") VALUES " "($1, $2, $3, $4, $5, $6, $7, $8);"); - GNUNET_CRYPTO_hash (td->details.withdraw_age_revealed_coins.coin_ev, - td->details.withdraw_age_revealed_coins.coin_ev_size, + GNUNET_CRYPTO_hash (td->details.age_withdraw_revealed_coins.coin_ev, + td->details.age_withdraw_revealed_coins.coin_ev_size, &h_coin_ev); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_into_table_withdraw_age_revealed_coins", + "insert_into_table_age_withdraw_revealed_coins", params); } @@ -2314,10 +2314,10 @@ TEH_PG_insert_records_by_table (void *cls, rh = &irbt_cb_table_purse_deletion; break; case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_COMMITMENTS: - rh = &irbt_cb_table_withdraw_age_commitments; + rh = &irbt_cb_table_age_withdraw_commitments; break; case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_REVEALED_COINS: - rh = &irbt_cb_table_withdraw_age_revealed_coins; + rh = &irbt_cb_table_age_withdraw_revealed_coins; break; } if (NULL == rh) diff --git a/src/exchangedb/pg_lookup_records_by_table.c b/src/exchangedb/pg_lookup_records_by_table.c index 2e157360f..efa0fec54 100644 --- a/src/exchangedb/pg_lookup_records_by_table.c +++ b/src/exchangedb/pg_lookup_records_by_table.c @@ -2767,14 +2767,14 @@ lrbt_cb_table_purse_deletion (void *cls, /** - * Function called with withdraw_age_commitments table entries. + * Function called with age_withdraw_commitments table entries. * * @param cls closure * @param result the postgres result * @param num_results the number of results in @a result */ static void -lrbt_cb_table_withdraw_age_commitments (void *cls, +lrbt_cb_table_age_withdraw_commitments (void *cls, PGresult *result, unsigned int num_results) { @@ -2788,26 +2788,26 @@ lrbt_cb_table_withdraw_age_commitments (void *cls, { struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ( - "withdraw_age_commitment_id", + "age_withdraw_commitment_id", &td.serial), GNUNET_PQ_result_spec_auto_from_type ( "h_commitment", - &td.details.withdraw_age_commitments.h_commitment), + &td.details.age_withdraw_commitments.h_commitment), GNUNET_PQ_result_spec_uint16 ( "max_age", - &td.details.withdraw_age_commitments.max_age), + &td.details.age_withdraw_commitments.max_age), TALER_PQ_RESULT_SPEC_AMOUNT ( "amount_with_fee", - &td.details.withdraw_age_commitments.amount_with_fee), + &td.details.age_withdraw_commitments.amount_with_fee), GNUNET_PQ_result_spec_auto_from_type ( "reserve_pub", - &td.details.withdraw_age_commitments.reserve_pub), + &td.details.age_withdraw_commitments.reserve_pub), GNUNET_PQ_result_spec_auto_from_type ( "reserve_sig", - &td.details.withdraw_age_commitments.reserve_sig), + &td.details.age_withdraw_commitments.reserve_sig), GNUNET_PQ_result_spec_uint32 ( "noreveal_index", - &td.details.withdraw_age_commitments.noreveal_index), + &td.details.age_withdraw_commitments.noreveal_index), GNUNET_PQ_result_spec_end }; @@ -2828,14 +2828,14 @@ lrbt_cb_table_withdraw_age_commitments (void *cls, /** - * Function called with withdraw_age_revealed_coins table entries. + * Function called with age_withdraw_revealed_coins table entries. * * @param cls closure * @param result the postgres result * @param num_results the number of results in @a result */ static void -lrbt_cb_table_withdraw_age_revealed_coins (void *cls, +lrbt_cb_table_age_withdraw_revealed_coins (void *cls, PGresult *result, unsigned int num_results) { @@ -2848,22 +2848,22 @@ lrbt_cb_table_withdraw_age_revealed_coins (void *cls, { struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ( - "withdraw_age_revealed_coins_id", + "age_withdraw_revealed_coins_id", &td.serial), GNUNET_PQ_result_spec_auto_from_type ( "h_commitment", - &td.details.withdraw_age_revealed_coins.h_commitment), + &td.details.age_withdraw_revealed_coins.h_commitment), GNUNET_PQ_result_spec_uint32 ( "freshcoin_index", - &td.details.withdraw_age_revealed_coins.freshcoin_index), + &td.details.age_withdraw_revealed_coins.freshcoin_index), GNUNET_PQ_result_spec_uint64 ( "denominations_serial", - &td.details.withdraw_age_revealed_coins.denominations_serial), + &td.details.age_withdraw_revealed_coins.denominations_serial), /* Note: h_coin_ev is recalculated */ GNUNET_PQ_result_spec_variable_size ( "coin_ev", - (void **) &td.details.withdraw_age_revealed_coins.coin_ev, - &td.details.withdraw_age_revealed_coins.coin_ev_size), + (void **) &td.details.age_withdraw_revealed_coins.coin_ev, + &td.details.age_withdraw_revealed_coins.coin_ev_size), TALER_PQ_result_spec_blinded_denom_sig ( "ev_sig", &td.details.refresh_revealed_coins.ev_sig), @@ -3598,9 +3598,9 @@ TEH_PG_lookup_records_by_table (void *cls, rh = &lrbt_cb_table_purse_deletion; break; case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_COMMITMENTS: - XPREPARE ("select_above_serial_by_table_withdraw_age_commitments", + XPREPARE ("select_above_serial_by_table_age_withdraw_commitments", "SELECT" - " withdraw_age_commitment_id" + " age_withdraw_commitment_id" ",h_commitment" ",amount_with_fee_val" ",amount_with_fee_frac" @@ -3608,15 +3608,15 @@ TEH_PG_lookup_records_by_table (void *cls, ",reserve_pub" ",reserve_sig" ",noreveal_index" - " FROM withdraw_age_commitments" - " WHERE withdraw_age_commitment_id > $1" - " ORDER BY withdraw_age_commitment_id ASC;"); - rh = &lrbt_cb_table_withdraw_age_commitments; + " FROM age_withdraw_commitments" + " WHERE age_withdraw_commitment_id > $1" + " ORDER BY age_withdraw_commitment_id ASC;"); + rh = &lrbt_cb_table_age_withdraw_commitments; break; case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_REVEALED_COINS: - XPREPARE ("select_above_serial_by_table_withdraw_age_revealed_coins", + XPREPARE ("select_above_serial_by_table_age_withdraw_revealed_coins", "SELECT" - " withdraw_age_revealed_coins_serial_id" + " age_withdraw_revealed_coins_serial_id" ",h_commitment" ",freshcoin_index" ",denominations_serial" @@ -3624,10 +3624,10 @@ TEH_PG_lookup_records_by_table (void *cls, ",h_coin_ev" ",ev_sig" ",ewv" - " FROM withdraw_age_revealed_coins" - " WHERE withdraw_age_revealed_coins_serial_id > $1" - " ORDER BY withdraw_age_revealed_coins_serial_id ASC;"); - rh = &lrbt_cb_table_withdraw_age_revealed_coins; + " FROM age_withdraw_revealed_coins" + " WHERE age_withdraw_revealed_coins_serial_id > $1" + " ORDER BY age_withdraw_revealed_coins_serial_id ASC;"); + rh = &lrbt_cb_table_age_withdraw_revealed_coins; break; } if (NULL == rh) diff --git a/src/exchangedb/pg_lookup_serial_by_table.c b/src/exchangedb/pg_lookup_serial_by_table.c index c98b4539e..2e3b41304 100644 --- a/src/exchangedb/pg_lookup_serial_by_table.c +++ b/src/exchangedb/pg_lookup_serial_by_table.c @@ -427,22 +427,22 @@ TEH_PG_lookup_serial_by_table (void *cls, statement = "select_serial_by_table_purse_deletion"; break; case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_COMMITMENTS: - XPREPARE ("select_serial_by_table_withdraw_age_commitments", + XPREPARE ("select_serial_by_table_age_withdraw_commitments", "SELECT" - " withdraw_age_commitment_id AS serial" - " FROM withdraw_age_commitments" - " ORDER BY withdraw_age_commitment_id DESC" + " age_withdraw_commitment_id AS serial" + " FROM age_withdraw_commitments" + " ORDER BY age_withdraw_commitment_id DESC" " LIMIT 1;"); - statement = "select_serial_by_table_withdraw_age_commitments"; + statement = "select_serial_by_table_age_withdraw_commitments"; break; case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_REVEALED_COINS: - XPREPARE ("select_serial_by_table_withdraw_age_revealed_coins", + XPREPARE ("select_serial_by_table_age_withdraw_revealed_coins", "SELECT" - " withdraw_age_revealed_coins_id AS serial" - " FROM withdraw_age_revealed_coins" - " ORDER BY withdraw_age_revealed_coins_id DESC" + " age_withdraw_revealed_coins_id AS serial" + " FROM age_withdraw_revealed_coins" + " ORDER BY age_withdraw_revealed_coins_id DESC" " LIMIT 1;"); - statement = "select_serial_by_table_withdraw_age_revealed_coins"; + statement = "select_serial_by_table_age_withdraw_revealed_coins"; break; } if (NULL == statement) diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 43f6b73e8..e4c9a28be 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -760,7 +760,7 @@ struct TALER_EXCHANGEDB_TableData struct TALER_ReserveSignatureP reserve_sig; uint32_t noreveal_index; struct GNUNET_TIME_Absolute timestamp; - } withdraw_age_commitments; + } age_withdraw_commitments; struct { @@ -772,7 +772,7 @@ struct TALER_EXCHANGEDB_TableData struct TALER_ExchangeWithdrawValues ewv; // h_coin_ev omitted, to be recomputed! struct TALER_BlindedDenominationSignature ev_sig; - } withdraw_age_revealed_coins; + } age_withdraw_revealed_coins; } details; @@ -1200,8 +1200,9 @@ struct TALER_EXCHANGEDB_AgeWithdrawCommitment struct TALER_ReservePublicKeyP reserve_pub; /** - * Signature confirming the age withdrawal, matching @e reserve_pub, @e - * maximum_age_group and @e h_commitment and @e total_amount_with_fee. + * Signature confirming the age withdrawal commitment, matching @e + * reserve_pub, @e maximum_age_group and @e h_commitment and @e + * total_amount_with_fee. */ struct TALER_ReserveSignatureP reserve_sig; @@ -2737,6 +2738,28 @@ struct TALER_EXCHANGEDB_CsRevealFreshCoinData uint32_t coin_off; }; +/** + * Information about a coin that was revealed to the exchange + * during reveal. + */ +struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin +{ + /** + * Hash of the public denomination key of the coin. + */ + struct TALER_DenominationHashP h_denom_pub; + + /** + * Signature generated by the exchange over the coin (in blinded format). + */ + struct TALER_BlindedDenominationSignature coin_sig; + + /** + * Blinded hash of the new coin + */ + struct TALER_BlindedCoinHashP h_coin_ev; +}; + /** * Generic KYC status for some operation. @@ -3810,19 +3833,18 @@ struct TALER_EXCHANGEDB_Plugin * age restriction enabled in a given age-withdraw operation and the relevant * information we learned or created in the reveal steop * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param h_commitment The hash of the original age-withdraw commitment, which is a key into the withdraw_age_commitments table - * @param num_coins number of coins to generate, size of the @a coin_evs array - * TODO: oec + * @param cls The `struct PostgresClosure` with the plugin-specific state + * @param h_commitment The hash of the original age-withdraw commitment, which is a key into the age_withdraw_commitments table + * @param num_awrcs Number of coins to generate, size of the @a coin_evs array + * @param awrcs Array of @a num_awrcs information about coins to be created * @return query execution status */ enum GNUNET_DB_QueryStatus (*insert_age_withdraw_reveal)( void *cls, - uint64_t h_commitment, - uint32_t num_coins - /* TODO: oec */ - ); + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + uint32_t num_awrcs, + const struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin *awrcs); /** * Lookup in the database for the fresh coins with age-restriction that From 153a078ca50f8d4a17e82b161a4e03cadb0b7c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Mon, 1 May 2023 14:12:38 +0200 Subject: [PATCH 08/53] -typo in comment --- src/exchange/taler-exchange-httpd_age-withdraw_reveal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c index 931463cbd..aba15776a 100644 --- a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c +++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c @@ -787,7 +787,7 @@ verify_commitment_and_max_age ( /** * @brief Send a response for "/age-withdraw/$RCH/reveal" * - * @param connection The http connection to the client to send the reponse to + * @param connection The http connection to the client to send the response to * @param num_coins Number of new coins with age restriction for which we reveal data * @param awrcs array of @a num_coins signatures revealed * @return a MHD result code From e68d9f9b75fc6869b9be12637db2fc22706dd217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Mon, 1 May 2023 14:22:18 +0200 Subject: [PATCH 09/53] -cleanup signatures --- src/exchange/taler-exchange-httpd_age-withdraw_reveal.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c index aba15776a..67e5facdc 100644 --- a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c +++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c @@ -635,7 +635,7 @@ verify_commitment_and_max_age ( } else { - /* FIXME:oec: Refactor this block out into its own function */ + /* FIXME[oec] Refactor this block out into its own function */ size_t j = (TALER_CNC_KAPPA - 1) * c + k; /* Index into disclosed_coin_secrets[] */ const struct TALER_PlanchetMasterSecretP *secret; @@ -966,8 +966,11 @@ sign_and_finalize_age_withdraw ( num_coins, awrcs); cleanup: - // TODO[oec]: handle error cases - // TODO[oec]: cleanup! + GNUNET_break (MHD_NO != ret); + + /* Free resources */ + for (unsigned int i = 0; i Date: Mon, 1 May 2023 20:19:51 +0200 Subject: [PATCH 10/53] -typos --- src/exchange/taler-exchange-httpd_age-withdraw_reveal.c | 2 +- src/exchangedb/pg_insert_records_by_table.c | 2 -- src/include/taler_exchangedb_plugin.h | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c index 67e5facdc..776c0d8d9 100644 --- a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c +++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c @@ -966,7 +966,7 @@ sign_and_finalize_age_withdraw ( num_coins, awrcs); cleanup: - GNUNET_break (MHD_NO != ret); + GNUNET_break (GNUNET_OK != ret); /* Free resources */ for (unsigned int i = 0; idetails.age_withdraw_revealed_coins.ev_sig), - TALER_PQ_query_param_exchange_withdraw_values ( - &td->details.age_withdraw_revealed_coins.ewv), GNUNET_PQ_query_param_end }; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index e4c9a28be..7c1757642 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -769,7 +769,6 @@ struct TALER_EXCHANGEDB_TableData uint64_t denominations_serial; void *coin_ev; size_t coin_ev_size; - struct TALER_ExchangeWithdrawValues ewv; // h_coin_ev omitted, to be recomputed! struct TALER_BlindedDenominationSignature ev_sig; } age_withdraw_revealed_coins; From 5ee567d1bacf1422745bb0867a1022fd695bc5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Mon, 1 May 2023 20:59:03 +0200 Subject: [PATCH 11/53] -fix memory leak --- src/exchange/taler-exchange-httpd_keys.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index d73283509..fa87a3b84 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -2377,9 +2377,10 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) } GNUNET_CONTAINER_multihashmap_iterator_destroy (iter); - GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group); } + + GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group); } GNUNET_CONTAINER_heap_destroy (heap); From 75f75c4a51c4700da9bde18cc9a9b5d9df1e8457 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 30 Apr 2023 16:21:07 +0200 Subject: [PATCH 12/53] breaking protocol changes towards fixing #7810 (incomplete, taler-exchange-offline still unfinished) --- contrib/gana | 2 +- src/auditor/taler-helper-auditor-wire.c | 8 +- src/bank-lib/bank_api_credit.c | 4 +- src/bank-lib/bank_api_debit.c | 4 +- .../taler-exchange-wire-gateway-client.c | 8 +- src/exchange-tools/taler-auditor-offline.c | 23 +- src/exchange-tools/taler-exchange-offline.c | 92 ++- src/exchange/taler-exchange-httpd_config.h | 2 +- ...r-exchange-httpd_management_wire_disable.c | 5 +- ...er-exchange-httpd_management_wire_enable.c | 46 +- src/exchange/taler-exchange-httpd_wire.c | 17 +- src/exchange/taler-exchange-wirewatch.c | 4 +- src/exchangedb/.gitignore | 3 +- src/exchangedb/0004-wire_accounts.sql | 26 + src/exchangedb/Makefile.am | 13 +- src/exchangedb/drop.sql | 1 + src/exchangedb/exchange-0004.sql.in | 24 + src/exchangedb/pg_get_wire_accounts.c | 32 +- src/exchangedb/pg_insert_wire.c | 15 +- src/exchangedb/pg_insert_wire.h | 6 + src/exchangedb/pg_update_wire.c | 20 +- src/exchangedb/pg_update_wire.h | 8 + src/include/taler_bank_service.h | 6 +- src/include/taler_crypto_lib.h | 24 + src/include/taler_exchange_service.h | 672 +++++++++++++++--- src/include/taler_exchangedb_plugin.h | 18 + src/include/taler_json_lib.h | 27 - src/include/taler_testing_lib.h | 8 +- src/json/Makefile.am | 16 +- src/json/json_wire.c | 74 -- src/json/test_json_wire.c | 80 --- src/lib/exchange_api_batch_deposit.c | 12 +- src/lib/exchange_api_batch_withdraw.c | 37 +- src/lib/exchange_api_batch_withdraw2.c | 68 +- src/lib/exchange_api_contracts_get.c | 8 +- src/lib/exchange_api_csr_melt.c | 4 +- src/lib/exchange_api_csr_withdraw.c | 2 +- src/lib/exchange_api_deposit.c | 8 +- src/lib/exchange_api_deposits_get.c | 22 +- src/lib/exchange_api_handle.c | 88 ++- src/lib/exchange_api_kyc_check.c | 20 +- src/lib/exchange_api_link.c | 4 +- src/lib/exchange_api_lookup_aml_decision.c | 12 +- src/lib/exchange_api_lookup_aml_decisions.c | 6 +- .../exchange_api_management_drain_profits.c | 28 +- src/lib/exchange_api_management_get_keys.c | 78 +- .../exchange_api_management_post_extensions.c | 22 +- src/lib/exchange_api_management_post_keys.c | 26 +- ...e_api_management_revoke_denomination_key.c | 22 +- ...change_api_management_revoke_signing_key.c | 22 +- ...change_api_management_update_aml_officer.c | 26 +- .../exchange_api_management_wire_disable.c | 30 +- src/lib/exchange_api_management_wire_enable.c | 38 +- src/lib/exchange_api_melt.c | 10 +- src/lib/exchange_api_purse_deposit.c | 16 +- src/lib/exchange_api_purse_merge.c | 14 +- src/lib/exchange_api_purses_get.c | 14 +- src/lib/exchange_api_recoup.c | 69 +- src/lib/exchange_api_recoup_refresh.c | 67 +- src/lib/exchange_api_refreshes_reveal.c | 4 +- src/lib/exchange_api_refund.c | 75 +- src/lib/exchange_api_transfers_get.c | 105 +-- src/lib/exchange_api_wire.c | 385 ++++++---- src/lib/exchange_api_withdraw.c | 28 +- src/lib/exchange_api_withdraw2.c | 69 +- .../testing_api_cmd_bank_history_credit.c | 4 +- .../testing_api_cmd_bank_history_debit.c | 4 +- src/testing/testing_api_cmd_batch_deposit.c | 10 +- src/testing/testing_api_cmd_batch_withdraw.c | 2 +- .../testing_api_cmd_check_aml_decision.c | 4 +- src/testing/testing_api_cmd_contract_get.c | 10 +- src/testing/testing_api_cmd_deposit.c | 6 +- src/testing/testing_api_cmd_deposits_get.c | 4 +- ...testing_api_cmd_nexus_fetch_transactions.c | 1 + .../testing_api_cmd_purse_create_deposit.c | 4 +- src/testing/testing_api_cmd_purse_deposit.c | 10 +- src/testing/testing_api_cmd_purse_get.c | 4 +- src/testing/testing_api_cmd_recoup.c | 17 +- src/testing/testing_api_cmd_recoup_refresh.c | 9 +- src/testing/testing_api_cmd_refresh.c | 34 +- src/testing/testing_api_cmd_refund.c | 13 +- .../testing_api_cmd_revoke_denom_key.c | 5 +- src/testing/testing_api_cmd_revoke_sign_key.c | 7 +- src/testing/testing_api_cmd_set_officer.c | 6 +- src/testing/testing_api_cmd_transfer_get.c | 251 +++---- src/testing/testing_api_cmd_wire.c | 75 +- src/testing/testing_api_cmd_wire_add.c | 22 +- src/testing/testing_api_cmd_wire_del.c | 7 +- src/testing/testing_api_cmd_withdraw.c | 8 +- src/testing/testing_api_loop.c | 57 +- src/util/offline_signatures.c | 78 +- 91 files changed, 2077 insertions(+), 1302 deletions(-) create mode 100644 src/exchangedb/0004-wire_accounts.sql create mode 100644 src/exchangedb/exchange-0004.sql.in delete mode 100644 src/json/test_json_wire.c diff --git a/contrib/gana b/contrib/gana index bd4e73b2e..5cfe18c5b 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit bd4e73b2ed06269fdee42eaad21acb5be8be9302 +Subproject commit 5cfe18c5bbfd404a5f7cf27a78577c881ddb9ebd diff --git a/src/auditor/taler-helper-auditor-wire.c b/src/auditor/taler-helper-auditor-wire.c index e3e3764a7..8615c439e 100644 --- a/src/auditor/taler-helper-auditor-wire.c +++ b/src/auditor/taler-helper-auditor-wire.c @@ -1483,10 +1483,10 @@ history_debit_cb (void *cls, switch (dhr->http_status) { case MHD_HTTP_OK: - for (unsigned int i = 0; idetails.success.details_length; i++) + for (unsigned int i = 0; idetails.ok.details_length; i++) { const struct TALER_BANK_DebitDetails *dd - = &dhr->details.success.details[i]; + = &dhr->details.ok.details[i]; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Analyzing bank DEBIT at %s of %s with WTID %s\n", GNUNET_TIME_timestamp2s (dd->execution_date), @@ -1978,10 +1978,10 @@ history_credit_cb (void *cls, switch (chr->http_status) { case MHD_HTTP_OK: - for (unsigned int i = 0; idetails.success.details_length; i++) + for (unsigned int i = 0; idetails.ok.details_length; i++) { const struct TALER_BANK_CreditDetails *cd - = &chr->details.success.details[i]; + = &chr->details.ok.details[i]; if (! analyze_credit (wa, cd)) diff --git a/src/bank-lib/bank_api_credit.c b/src/bank-lib/bank_api_credit.c index 4842de969..14ddcd729 100644 --- a/src/bank-lib/bank_api_credit.c +++ b/src/bank-lib/bank_api_credit.c @@ -131,8 +131,8 @@ parse_account_history (struct TALER_BANK_CreditHistoryHandle *hh, return GNUNET_SYSERR; } } - chr.details.success.details_length = len; - chr.details.success.details = cd; + chr.details.ok.details_length = len; + chr.details.ok.details = cd; hh->hcb (hh->hcb_cls, &chr); } diff --git a/src/bank-lib/bank_api_debit.c b/src/bank-lib/bank_api_debit.c index bd9b11941..e40156ce7 100644 --- a/src/bank-lib/bank_api_debit.c +++ b/src/bank-lib/bank_api_debit.c @@ -133,8 +133,8 @@ parse_account_history (struct TALER_BANK_DebitHistoryHandle *hh, return GNUNET_SYSERR; } } - dhr.details.success.details_length = len; - dhr.details.success.details = dd; + dhr.details.ok.details_length = len; + dhr.details.ok.details = dd; hh->hcb (hh->hcb_cls, &dhr); } diff --git a/src/bank-lib/taler-exchange-wire-gateway-client.c b/src/bank-lib/taler-exchange-wire-gateway-client.c index 5bfd9311d..a972bcfd2 100644 --- a/src/bank-lib/taler-exchange-wire-gateway-client.c +++ b/src/bank-lib/taler-exchange-wire-gateway-client.c @@ -179,10 +179,10 @@ credit_history_cb (void *cls, global_ret = 0; break; case MHD_HTTP_OK: - for (unsigned int i = 0; idetails.success.details_length; i++) + for (unsigned int i = 0; idetails.ok.details_length; i++) { const struct TALER_BANK_CreditDetails *cd = - &reply->details.success.details[i]; + &reply->details.ok.details[i]; /* If credit/debit accounts were specified, use as a filter */ if ( (NULL != credit_account) && @@ -279,10 +279,10 @@ debit_history_cb (void *cls, global_ret = 0; break; case MHD_HTTP_OK: - for (unsigned int i = 0; idetails.success.details_length; i++) + for (unsigned int i = 0; idetails.ok.details_length; i++) { const struct TALER_BANK_DebitDetails *dd = - &reply->details.success.details[i]; + &reply->details.ok.details[i]; /* If credit/debit accounts were specified, use as a filter */ if ( (NULL != credit_account) && diff --git a/src/exchange-tools/taler-auditor-offline.c b/src/exchange-tools/taler-auditor-offline.c index 53135d9fa..f239c11a9 100644 --- a/src/exchange-tools/taler-auditor-offline.c +++ b/src/exchange-tools/taler-auditor-offline.c @@ -644,26 +644,19 @@ do_upload (char *const *args) * a particular exchange and what keys the exchange is using. * * @param cls closure with the `char **` remaining args - * @param hr HTTP response data - * @param keys information about the various keys used - * by the exchange, NULL if /keys failed - * @param compat protocol compatibility information + * @param kr response data */ static void keys_cb ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_Keys *keys, - enum TALER_EXCHANGE_VersionCompatibility compat) + const struct TALER_EXCHANGE_KeysResponse *kr) { char *const *args = cls; - (void) keys; - (void) compat; - switch (hr->http_status) + switch (kr->hr.http_status) { case MHD_HTTP_OK: - if (! json_is_object (hr->reply)) + if (! json_is_object (kr->hr.reply)) { GNUNET_break (0); TALER_EXCHANGE_disconnect (exchange); @@ -676,9 +669,9 @@ keys_cb ( default: fprintf (stderr, "Failed to download keys: %s (HTTP status: %u/%u)\n", - hr->hint, - hr->http_status, - (unsigned int) hr->ec); + kr->hr.hint, + kr->hr.http_status, + (unsigned int) kr->hr.ec); TALER_EXCHANGE_disconnect (exchange); exchange = NULL; test_shutdown (); @@ -689,7 +682,7 @@ keys_cb ( GNUNET_JSON_pack_string ("operation", OP_INPUT_KEYS), GNUNET_JSON_pack_object_incref ("arguments", - (json_t *) hr->reply)); + (json_t *) kr->hr.reply)); if (NULL == args[0]) { json_dumpf (in, diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index 660864b77..ec48ec831 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -1119,14 +1119,15 @@ load_offline_key (int do_create) * Function called with information about the post revocation operation result. * * @param cls closure with a `struct DenomRevocationRequest` - * @param hr HTTP response data + * @param dr response data */ static void denom_revocation_cb ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *dr) { struct DenomRevocationRequest *drr = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &dr->hr; if (MHD_HTTP_NO_CONTENT != hr->http_status) { @@ -1208,14 +1209,15 @@ upload_denom_revocation (const char *exchange_url, * Function called with information about the post revocation operation result. * * @param cls closure with a `struct SignkeyRevocationRequest` - * @param hr HTTP response data + * @param sr response data */ static void signkey_revocation_cb ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *sr) { struct SignkeyRevocationRequest *srr = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &sr->hr; if (MHD_HTTP_NO_CONTENT != hr->http_status) { @@ -1489,13 +1491,14 @@ upload_auditor_del (const char *exchange_url, * Function called with information about the post wire add operation result. * * @param cls closure with a `struct WireAddRequest` - * @param hr HTTP response data + * @param wer response data */ static void wire_add_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementWireEnableResponse *wer) { struct WireAddRequest *war = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &wer->hr; if (MHD_HTTP_NO_CONTENT != hr->http_status) { @@ -1533,10 +1536,21 @@ upload_wire_add (const char *exchange_url, struct GNUNET_TIME_Timestamp start_time; struct WireAddRequest *war; const char *err_name; + const char *conversion_url = NULL; + json_t *debit_restrictions; + json_t *credit_restrictions; unsigned int err_line; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("payto_uri", &payto_uri), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("conversion_url", + &conversion_url), + NULL), + GNUNET_JSON_spec_json ("debit_restrictions", + &debit_restrictions), + GNUNET_JSON_spec_json ("credit_restrictions", + &credit_restrictions), GNUNET_JSON_spec_timestamp ("validity_start", &start_time), GNUNET_JSON_spec_fixed_auto ("master_sig_add", @@ -1561,6 +1575,7 @@ upload_wire_add (const char *exchange_url, stderr, JSON_INDENT (2)); global_ret = EXIT_FAILURE; + GNUNET_JSON_parse_free (spec); test_shutdown (); return; } @@ -1574,6 +1589,7 @@ upload_wire_add (const char *exchange_url, "payto:// URI `%s' is malformed\n", payto_uri); global_ret = EXIT_FAILURE; + GNUNET_JSON_parse_free (spec); test_shutdown (); return; } @@ -1588,6 +1604,7 @@ upload_wire_add (const char *exchange_url, "payto URI is malformed: %s\n", msg); GNUNET_free (msg); + GNUNET_JSON_parse_free (spec); test_shutdown (); global_ret = EXIT_INVALIDARGUMENT; return; @@ -1599,6 +1616,9 @@ upload_wire_add (const char *exchange_url, TALER_EXCHANGE_management_enable_wire (ctx, exchange_url, payto_uri, + conversion_url, + debit_restrictions, + credit_restrictions, start_time, &master_sig_add, &master_sig_wire, @@ -1607,6 +1627,7 @@ upload_wire_add (const char *exchange_url, GNUNET_CONTAINER_DLL_insert (war_head, war_tail, war); + GNUNET_JSON_parse_free (spec); } @@ -1614,13 +1635,14 @@ upload_wire_add (const char *exchange_url, * Function called with information about the post wire del operation result. * * @param cls closure with a `struct WireDelRequest` - * @param hr HTTP response data + * @param wdres response data */ static void wire_del_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementWireDisableResponse *wdres) { struct WireDelRequest *wdr = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &wdres->hr; if (MHD_HTTP_NO_CONTENT != hr->http_status) { @@ -1927,14 +1949,15 @@ upload_global_fee (const char *exchange_url, * Function called with information about the drain profits operation. * * @param cls closure with a `struct DrainProfitsRequest` - * @param hr HTTP response data + * @param mdr response data */ static void drain_profits_cb ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementDrainResponse *mdr) { struct DrainProfitsRequest *dpr = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &mdr->hr; if (MHD_HTTP_NO_CONTENT != hr->http_status) { @@ -2033,14 +2056,15 @@ upload_drain (const char *exchange_url, * Function called with information about the post upload keys operation result. * * @param cls closure with a `struct UploadKeysRequest` - * @param hr HTTP response data + * @param mr response data */ static void keys_cb ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementPostKeysResponse *mr) { struct UploadKeysRequest *ukr = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &mr->hr; if (MHD_HTTP_NO_CONTENT != hr->http_status) { @@ -2206,14 +2230,15 @@ upload_keys (const char *exchange_url, * Function called with information about the post upload extensions operation result. * * @param cls closure with a `struct UploadExtensionsRequest` - * @param hr HTTP response data + * @param er response data */ static void extensions_cb ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementPostExtensionsResponse *er) { struct UploadExtensionsRequest *uer = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &er->hr; if (MHD_HTTP_NO_CONTENT != hr->http_status) { @@ -2447,14 +2472,15 @@ add_partner (const char *exchange_url, * Function called with information about the AML officer update operation. * * @param cls closure with a `struct AmlStaffRequest` - * @param hr HTTP response data + * @param ar response data */ static void update_aml_officer_cb ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse *ar) { struct AmlStaffRequest *asr = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &ar->hr; if (MHD_HTTP_NO_CONTENT != hr->http_status) { @@ -2961,6 +2987,10 @@ do_add_wire (char *const *args) struct TALER_MasterSignatureP master_sig_add; struct TALER_MasterSignatureP master_sig_wire; struct GNUNET_TIME_Timestamp now; + const char *conversion_url = NULL; + json_t *debit_restrictions; + json_t *credit_restrictions; + unsigned int num_args = 1; if (NULL != in) { @@ -3011,24 +3041,43 @@ do_add_wire (char *const *args) } GNUNET_free (wire_method); } + // FIXME: init new args properly! + debit_restrictions = json_array (); + GNUNET_assert (NULL != debit_restrictions); + credit_restrictions = json_array (); + GNUNET_assert (NULL != credit_restrictions); + TALER_exchange_offline_wire_add_sign (args[0], + conversion_url, + debit_restrictions, + credit_restrictions, now, &master_priv, &master_sig_add); TALER_exchange_wire_signature_make (args[0], + conversion_url, + debit_restrictions, + credit_restrictions, &master_priv, &master_sig_wire); output_operation (OP_ENABLE_WIRE, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("payto_uri", args[0]), + GNUNET_JSON_pack_array_steal ("debit_restrictions", + debit_restrictions), + GNUNET_JSON_pack_array_steal ("credit_restrictions", + credit_restrictions), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + conversion_url)), GNUNET_JSON_pack_timestamp ("validity_start", now), GNUNET_JSON_pack_data_auto ("master_sig_add", &master_sig_add), GNUNET_JSON_pack_data_auto ("master_sig_wire", &master_sig_wire))); - next (args + 1); + next (args + num_args); } @@ -3643,18 +3692,15 @@ enable_aml_staff (char *const *args) * whether there are subsequent commands). * * @param cls closure with the `char **` remaining args - * @param hr HTTP response data - * @param keys information about the various keys used - * by the exchange, NULL if /management/keys failed + * @param mgr response data */ static void download_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_FutureKeys *keys) + const struct TALER_EXCHANGE_ManagementGetKeysResponse *mgr) { char *const *args = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &mgr->hr; - (void) keys; mgkh = NULL; switch (hr->http_status) { diff --git a/src/exchange/taler-exchange-httpd_config.h b/src/exchange/taler-exchange-httpd_config.h index 7763cdeb5..95380e0a8 100644 --- a/src/exchange/taler-exchange-httpd_config.h +++ b/src/exchange/taler-exchange-httpd_config.h @@ -41,7 +41,7 @@ * * Returned via both /config and /keys endpoints. */ -#define EXCHANGE_PROTOCOL_VERSION "14:0:2" +#define EXCHANGE_PROTOCOL_VERSION "15:0:0" /** diff --git a/src/exchange/taler-exchange-httpd_management_wire_disable.c b/src/exchange/taler-exchange-httpd_management_wire_disable.c index 34825eda3..077a56b25 100644 --- a/src/exchange/taler-exchange-httpd_management_wire_disable.c +++ b/src/exchange/taler-exchange-httpd_management_wire_disable.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020-2023 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 @@ -114,6 +114,9 @@ del_wire (void *cls, } qs = TEH_plugin->update_wire (TEH_plugin->cls, awc->payto_uri, + NULL, + NULL, + NULL, awc->validity_end, false); if (qs < 0) diff --git a/src/exchange/taler-exchange-httpd_management_wire_enable.c b/src/exchange/taler-exchange-httpd_management_wire_enable.c index 25ee0eeac..6743b485c 100644 --- a/src/exchange/taler-exchange-httpd_management_wire_enable.c +++ b/src/exchange/taler-exchange-httpd_management_wire_enable.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020-2023 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 @@ -54,6 +54,21 @@ struct AddWireContext */ const char *payto_uri; + /** + * (optional) address of a conversion service for this account. + */ + const char *conversion_url; + + /** + * Restrictions imposed when crediting this account. + */ + json_t *credit_restrictions; + + /** + * Restrictions imposed when debiting this account. + */ + json_t *debit_restrictions; + /** * Timestamp for checking against replay attacks. */ @@ -114,11 +129,17 @@ add_wire (void *cls, if (0 == qs) qs = TEH_plugin->insert_wire (TEH_plugin->cls, awc->payto_uri, + awc->conversion_url, + awc->debit_restrictions, + awc->credit_restrictions, awc->validity_start, &awc->master_sig_wire); else qs = TEH_plugin->update_wire (TEH_plugin->cls, awc->payto_uri, + awc->conversion_url, + awc->debit_restrictions, + awc->credit_restrictions, awc->validity_start, true); if (qs < 0) @@ -141,7 +162,9 @@ TEH_handler_management_post_wire ( struct MHD_Connection *connection, const json_t *root) { - struct AddWireContext awc; + struct AddWireContext awc = { + .conversion_url = NULL + }; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("master_sig_wire", &awc.master_sig_wire), @@ -149,6 +172,14 @@ TEH_handler_management_post_wire ( &awc.master_sig_add), GNUNET_JSON_spec_string ("payto_uri", &awc.payto_uri), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("conversion_url", + &awc.conversion_url), + NULL), + GNUNET_JSON_spec_json ("credit_restrictions", + &awc.credit_restrictions), + GNUNET_JSON_spec_json ("debit_restrictions", + &awc.debit_restrictions), GNUNET_JSON_spec_timestamp ("validity_start", &awc.validity_start), GNUNET_JSON_spec_end () @@ -179,17 +210,22 @@ TEH_handler_management_post_wire ( MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PAYTO_URI_MALFORMED, msg); + GNUNET_JSON_parse_free (spec); GNUNET_free (msg); return ret; } } if (GNUNET_OK != TALER_exchange_offline_wire_add_verify (awc.payto_uri, + awc.conversion_url, + awc.debit_restrictions, + awc.credit_restrictions, awc.validity_start, &TEH_master_public_key, &awc.master_sig_add)) { GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_FORBIDDEN, @@ -199,10 +235,14 @@ TEH_handler_management_post_wire ( TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; if (GNUNET_OK != TALER_exchange_wire_signature_check (awc.payto_uri, + awc.conversion_url, + awc.debit_restrictions, + awc.credit_restrictions, &TEH_master_public_key, &awc.master_sig_wire)) { GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_FORBIDDEN, @@ -218,6 +258,7 @@ TEH_handler_management_post_wire ( GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "payto:// URI `%s' is malformed\n", awc.payto_uri); + GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_BAD_REQUEST, @@ -237,6 +278,7 @@ TEH_handler_management_post_wire ( &ret, &add_wire, &awc); + GNUNET_JSON_parse_free (spec); if (GNUNET_SYSERR == res) return ret; } diff --git a/src/exchange/taler-exchange-httpd_wire.c b/src/exchange/taler-exchange-httpd_wire.c index 34010462d..17875a720 100644 --- a/src/exchange/taler-exchange-httpd_wire.c +++ b/src/exchange/taler-exchange-httpd_wire.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015-2022 Taler Systems SA + Copyright (C) 2015-2023 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 @@ -224,12 +224,18 @@ TEH_wire_done () * * @param cls a `json_t *` object to expand with wire account details * @param payto_uri the exchange bank account URI to add + * @param conversion_url URL of a conversion service, NULL if there is no conversion + * @param debit_restrictions JSON array with debit restrictions on the account + * @param credit_restrictions JSON array with credit restrictions on the account * @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 char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, const struct TALER_MasterSignatureP *master_sig) { json_t *a = cls; @@ -240,6 +246,13 @@ add_wire_account (void *cls, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("payto_uri", payto_uri), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + conversion_url)), + GNUNET_JSON_pack_array_incref ("debit_restrictions", + (json_t *) debit_restrictions), + GNUNET_JSON_pack_array_incref ("credit_restrictions", + (json_t *) credit_restrictions), GNUNET_JSON_pack_data_auto ("master_sig", master_sig)))) { @@ -462,6 +475,8 @@ build_wire_state (void) wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK ( GNUNET_JSON_pack_array_steal ("accounts", wire_accounts_array), + GNUNET_JSON_pack_array_steal ("wads", /* #7271 */ + json_array ()), GNUNET_JSON_pack_object_steal ("fees", wire_fee_object), GNUNET_JSON_pack_data_auto ("master_public_key", diff --git a/src/exchange/taler-exchange-wirewatch.c b/src/exchange/taler-exchange-wirewatch.c index 235c0153f..047042423 100644 --- a/src/exchange/taler-exchange-wirewatch.c +++ b/src/exchange/taler-exchange-wirewatch.c @@ -731,8 +731,8 @@ history_cb (void *cls, { case MHD_HTTP_OK: process_reply (wrap_size, - reply->details.success.details, - reply->details.success.details_length); + reply->details.ok.details, + reply->details.ok.details_length); return; case MHD_HTTP_NO_CONTENT: transaction_completed (); diff --git a/src/exchangedb/.gitignore b/src/exchangedb/.gitignore index fa833d81f..6e67fadb1 100644 --- a/src/exchangedb/.gitignore +++ b/src/exchangedb/.gitignore @@ -12,4 +12,5 @@ test-exchangedb-batch-reserves-in-insert-postgres test-exchangedb-by-j-postgres test-exchangedb-populate-link-data-postgres test-exchangedb-populate-ready-deposit-postgres -test-exchangedb-populate-select-refunds-by-coin-postgres \ No newline at end of file +test-exchangedb-populate-select-refunds-by-coin-postgres +exchange-0004.sql diff --git a/src/exchangedb/0004-wire_accounts.sql b/src/exchangedb/0004-wire_accounts.sql new file mode 100644 index 000000000..6114c821a --- /dev/null +++ b/src/exchangedb/0004-wire_accounts.sql @@ -0,0 +1,26 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2023 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 +-- + +ALTER TABLE wire_accounts + ADD COLUMN conversion_url VARCHAR DEFAULT (NULL), + ADD COLUMN debit_restrictions VARCHAR DEFAULT (NULL), + ADD COLUMN credit_restrictions VARCHAR DEFAULT (NULL); +COMMENT ON COLUMN wire_accounts.conversion_url + IS 'URL of a currency conversion service if conversion is needed when this account is used; NULL if there is no conversion.'; +COMMENT ON COLUMN wire_accounts.debit_restrictions + IS 'JSON array describing restrictions imposed when debiting this account. Empty for no restrictions, NULL if account was migrated from previous database revision or account is disabled.'; +COMMENT ON COLUMN wire_accounts.credit_restrictions + IS 'JSON array describing restrictions imposed when crediting this account. Empty for no restrictions, NULL if account was migrated from previous database revision or account is disabled.'; diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index de76997cb..ee78b87f7 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -20,7 +20,9 @@ sqlinputs = \ 0002-*.sql \ exchange-0002.sql.in \ 0003-*.sql \ - exchange-0003.sql.in + exchange-0003.sql.in \ + 0004-*.sql \ + exchange-0004.sql.in sql_DATA = \ benchmark-0001.sql \ @@ -28,6 +30,7 @@ sql_DATA = \ exchange-0001.sql \ exchange-0002.sql \ exchange-0003.sql \ + exchange-0004.sql \ drop.sql \ procedures.sql @@ -39,7 +42,8 @@ BUILT_SOURCES = \ CLEANFILES = \ exchange-0002.sql \ - exchange-0003.sql + exchange-0003.sql \ + exchange-0004.sql procedures.sql: procedures.sql.in exchange_do_*.sql chmod +w $@ || true @@ -56,6 +60,11 @@ exchange-0003.sql: exchange-0003.sql.in 0003-*.sql gcc -E -P -undef - < exchange-0003.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@ chmod ugo-w $@ +exchange-0004.sql: exchange-0004.sql.in 0004-*.sql + chmod +w $@ || true + gcc -E -P -undef - < exchange-0004.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@ + chmod ugo-w $@ + EXTRA_DIST = \ exchangedb.conf \ exchangedb-postgres.conf \ diff --git a/src/exchangedb/drop.sql b/src/exchangedb/drop.sql index ecebde6f5..843cda8ec 100644 --- a/src/exchangedb/drop.sql +++ b/src/exchangedb/drop.sql @@ -21,6 +21,7 @@ BEGIN; SELECT _v.unregister_patch('exchange-0001'); SELECT _v.unregister_patch('exchange-0002'); SELECT _v.unregister_patch('exchange-0003'); +SELECT _v.unregister_patch('exchange-0004'); DROP SCHEMA exchange CASCADE; diff --git a/src/exchangedb/exchange-0004.sql.in b/src/exchangedb/exchange-0004.sql.in new file mode 100644 index 000000000..00979e193 --- /dev/null +++ b/src/exchangedb/exchange-0004.sql.in @@ -0,0 +1,24 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2014--2023 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 +-- + +BEGIN; + +SELECT _v.register_patch('exchange-0004', NULL, NULL); +SET search_path TO exchange; + +#include "0004-wire_accounts.sql" + +COMMIT; diff --git a/src/exchangedb/pg_get_wire_accounts.c b/src/exchangedb/pg_get_wire_accounts.c index 43590acf8..23b939046 100644 --- a/src/exchangedb/pg_get_wire_accounts.c +++ b/src/exchangedb/pg_get_wire_accounts.c @@ -66,10 +66,25 @@ get_wire_accounts_cb (void *cls, for (unsigned int i = 0; i < num_results; i++) { char *payto_uri; + char *conversion_url = NULL; + json_t *debit_restrictions = NULL; + json_t *credit_restrictions = NULL; struct TALER_MasterSignatureP master_sig; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("payto_uri", &payto_uri), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_string ("conversion_url", + &conversion_url), + NULL), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_json ("debit_restrictions", + &debit_restrictions), + NULL), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_json ("credit_restrictions", + &credit_restrictions), + NULL), GNUNET_PQ_result_spec_auto_from_type ("master_sig", &master_sig), GNUNET_PQ_result_spec_end @@ -84,8 +99,21 @@ get_wire_accounts_cb (void *cls, ctx->status = GNUNET_SYSERR; return; } + if (NULL == debit_restrictions) + { + debit_restrictions = json_array (); + GNUNET_assert (NULL != debit_restrictions); + } + if (NULL == credit_restrictions) + { + credit_restrictions = json_array (); + GNUNET_assert (NULL != credit_restrictions); + } ctx->cb (ctx->cb_cls, payto_uri, + conversion_url, + debit_restrictions, + credit_restrictions, &master_sig); GNUNET_PQ_cleanup_result (rs); } @@ -112,6 +140,9 @@ TEH_PG_get_wire_accounts (void *cls, "get_wire_accounts", "SELECT" " payto_uri" + ",conversion_url" + ",debit_restrictions" + ",credit_restrictions" ",master_sig" " FROM wire_accounts" " WHERE is_active"); @@ -123,5 +154,4 @@ TEH_PG_get_wire_accounts (void *cls, if (GNUNET_OK != ctx.status) return GNUNET_DB_STATUS_HARD_ERROR; return qs; - } diff --git a/src/exchangedb/pg_insert_wire.c b/src/exchangedb/pg_insert_wire.c index 75323b6fc..8329a04af 100644 --- a/src/exchangedb/pg_insert_wire.c +++ b/src/exchangedb/pg_insert_wire.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 2023 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 @@ -29,12 +29,20 @@ enum GNUNET_DB_QueryStatus TEH_PG_insert_wire (void *cls, const char *payto_uri, + const char *conversion_url, + json_t *debit_restrictions, + json_t *credit_restrictions, struct GNUNET_TIME_Timestamp start_date, const struct TALER_MasterSignatureP *master_sig) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (payto_uri), + NULL == conversion_url + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (conversion_url), + TALER_PQ_query_param_json (debit_restrictions), + TALER_PQ_query_param_json (credit_restrictions), GNUNET_PQ_query_param_auto_from_type (master_sig), GNUNET_PQ_query_param_timestamp (&start_date), GNUNET_PQ_query_param_end @@ -44,11 +52,14 @@ TEH_PG_insert_wire (void *cls, "insert_wire", "INSERT INTO wire_accounts " "(payto_uri" + ",conversion_url" + ",debit_restrictions" + ",credit_restrictions" ",master_sig" ",is_active" ",last_change" ") VALUES " - "($1, $2, true, $3);"); + "($1, $2, $3, $4, $5, true, $6);"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_wire", params); diff --git a/src/exchangedb/pg_insert_wire.h b/src/exchangedb/pg_insert_wire.h index 670928d7c..c949327d7 100644 --- a/src/exchangedb/pg_insert_wire.h +++ b/src/exchangedb/pg_insert_wire.h @@ -29,6 +29,9 @@ * * @param cls closure * @param payto_uri wire account of the exchange + * @param conversion_url URL of a conversion service, NULL if there is no conversion + * @param debit_restrictions JSON array with debit restrictions on the account + * @param credit_restrictions JSON array with credit restrictions on the account * @param start_date date when the account was added by the offline system * (only to be used for replay detection) * @param master_sig public signature affirming the existence of the account, @@ -38,6 +41,9 @@ enum GNUNET_DB_QueryStatus TEH_PG_insert_wire (void *cls, const char *payto_uri, + const char *conversion_url, + json_t *debit_restrictions, + json_t *credit_restrictions, struct GNUNET_TIME_Timestamp start_date, const struct TALER_MasterSignatureP *master_sig); diff --git a/src/exchangedb/pg_update_wire.c b/src/exchangedb/pg_update_wire.c index 4059348c9..0c4ec7b58 100644 --- a/src/exchangedb/pg_update_wire.c +++ b/src/exchangedb/pg_update_wire.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 2023 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 @@ -29,6 +29,9 @@ enum GNUNET_DB_QueryStatus TEH_PG_update_wire (void *cls, const char *payto_uri, + const char *conversion_url, + json_t *debit_restrictions, + json_t *credit_restrictions, struct GNUNET_TIME_Timestamp change_date, bool enabled) { @@ -36,17 +39,28 @@ TEH_PG_update_wire (void *cls, struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (payto_uri), GNUNET_PQ_query_param_bool (enabled), + NULL == conversion_url + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (conversion_url), + enabled + ? TALER_PQ_query_param_json (debit_restrictions) + : GNUNET_PQ_query_param_null (), + enabled + ? TALER_PQ_query_param_json (credit_restrictions) + : GNUNET_PQ_query_param_null (), GNUNET_PQ_query_param_timestamp (&change_date), GNUNET_PQ_query_param_end }; - /* used in #postgres_update_wire() */ PREPARE (pg, "update_wire", "UPDATE wire_accounts" " SET" " is_active=$2" - " ,last_change=$3" + " ,conversion_url=$3" + " ,debit_restrictions=$4" + " ,credit_restrictions=$5" + " ,last_change=$6" " WHERE payto_uri=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "update_wire", diff --git a/src/exchangedb/pg_update_wire.h b/src/exchangedb/pg_update_wire.h index 67038b580..360b8845a 100644 --- a/src/exchangedb/pg_update_wire.h +++ b/src/exchangedb/pg_update_wire.h @@ -24,11 +24,16 @@ #include "taler_util.h" #include "taler_json_lib.h" #include "taler_exchangedb_plugin.h" + + /** * Update information about a wire account of the exchange. * * @param cls closure * @param payto_uri account the update is about + * @param conversion_url URL of a conversion service, NULL if there is no conversion + * @param debit_restrictions JSON array with debit restrictions on the account; NULL allowed if not @a enabled + * @param credit_restrictions JSON array with credit restrictions on the account; NULL allowed if not @a enabled * @param change_date date when the account status was last changed * (only to be used for replay detection) * @param enabled true to enable, false to disable (the actual change) @@ -37,6 +42,9 @@ enum GNUNET_DB_QueryStatus TEH_PG_update_wire (void *cls, const char *payto_uri, + const char *conversion_url, + json_t *debit_restrictions, + json_t *credit_restrictions, struct GNUNET_TIME_Timestamp change_date, bool enabled); diff --git a/src/include/taler_bank_service.h b/src/include/taler_bank_service.h index b5d4dcceb..5ce5e254d 100644 --- a/src/include/taler_bank_service.h +++ b/src/include/taler_bank_service.h @@ -111,6 +111,7 @@ struct TALER_BANK_AdminAddIncomingHandle; * @param timestamp time when the transaction was made. * @param json detailed response from the HTTPD, or NULL if reply was not in JSON */ +// FIXME: bad API typedef void (*TALER_BANK_AdminAddIncomingCallback) ( void *cls, @@ -199,6 +200,7 @@ struct TALER_BANK_TransferHandle; * @param row_id unique ID of the wire transfer in the bank's records * @param timestamp when did the transaction go into effect */ +// FIXME: bad API typedef void (*TALER_BANK_TransferCallback)( void *cls, @@ -337,7 +339,7 @@ struct TALER_BANK_CreditHistoryResponse */ unsigned int details_length; - } success; + } ok; } details; @@ -493,7 +495,7 @@ struct TALER_BANK_DebitHistoryResponse */ unsigned int details_length; - } success; + } ok; } details; diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index 61d92483f..166440764 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -5512,6 +5512,9 @@ TALER_exchange_offline_global_fee_verify ( * Create wire account addition signature. * * @param payto_uri bank account + * @param conversion_url URL of the conversion service, or NULL if none + * @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec + * @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec * @param now timestamp to use for the signature (rounded) * @param master_priv private key to sign with * @param[out] master_sig where to write the signature @@ -5519,6 +5522,9 @@ TALER_exchange_offline_global_fee_verify ( void TALER_exchange_offline_wire_add_sign ( const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, struct GNUNET_TIME_Timestamp now, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig); @@ -5528,6 +5534,9 @@ TALER_exchange_offline_wire_add_sign ( * Verify wire account addition signature. * * @param payto_uri bank account + * @param conversion_url URL of the conversion service, or NULL if none + * @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec + * @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec * @param sign_time timestamp when signature was created * @param master_pub public key to verify against * @param master_sig the signature the signature @@ -5536,6 +5545,9 @@ TALER_exchange_offline_wire_add_sign ( enum GNUNET_GenericReturnValue TALER_exchange_offline_wire_add_verify ( const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, struct GNUNET_TIME_Timestamp sign_time, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig); @@ -5578,6 +5590,9 @@ TALER_exchange_offline_wire_del_verify ( * Check the signature in @a master_sig. * * @param payto_uri URI that is signed + * @param conversion_url URL of the conversion service, or NULL if none + * @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec + * @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec * @param master_pub master public key of the exchange * @param master_sig signature of the exchange * @return #GNUNET_OK if signature is valid @@ -5585,6 +5600,9 @@ TALER_exchange_offline_wire_del_verify ( enum GNUNET_GenericReturnValue TALER_exchange_wire_signature_check ( const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig); @@ -5593,12 +5611,18 @@ TALER_exchange_wire_signature_check ( * Create a signed wire statement for the given account. * * @param payto_uri account specification + * @param conversion_url URL of the conversion service, or NULL if none + * @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec + * @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec * @param master_priv private key to sign with * @param[out] master_sig where to write the signature */ void TALER_exchange_wire_signature_make ( const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig); diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 4099d6bb0..f9330ec5c 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -461,22 +461,53 @@ struct TALER_EXCHANGE_HttpResponse }; +/** + * Response from /keys. + */ +struct TALER_EXCHANGE_KeysResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Details depending on the HTTP status code. + */ + union + { + + /** + * Details on #MHD_HTTP_OK. + */ + struct + { + /** + * Information about the various keys used by the exchange. + */ + const struct TALER_EXCHANGE_Keys *keys; + + /** + * Protocol compatibility information + */ + enum TALER_EXCHANGE_VersionCompatibility compat; + } ok; + } details; + +}; + + /** * Function called with information about who is auditing * a particular exchange and what keys the exchange is using. * * @param cls closure - * @param hr HTTP response data - * @param keys information about the various keys used - * by the exchange, NULL if /keys failed - * @param compat protocol compatibility information + * @param kr response from /keys */ typedef void (*TALER_EXCHANGE_CertificationCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_Keys *keys, - enum TALER_EXCHANGE_VersionCompatibility compat); + const struct TALER_EXCHANGE_KeysResponse *kr); /** @@ -712,7 +743,7 @@ TALER_EXCHANGE_get_signing_key_info ( /** - * Sorted list of fees to be paid for aggregate wire transfers. + * List sorted by @a start_date with fees to be paid for aggregate wire transfers. */ struct TALER_EXCHANGE_WireAggregateFees { @@ -743,6 +774,95 @@ struct TALER_EXCHANGE_WireAggregateFees }; +/** + * Information about wire fees by wire method. + */ +struct TALER_EXCHANGE_WireFeesByMethod +{ + /** + * Wire method with the given @e fees. + */ + const char *method; + + /** + * Linked list of wire fees the exchange charges for + * accounts of the wire @e method. + */ + struct TALER_EXCHANGE_WireAggregateFees *fees_head; + +}; + + +/** + * Type of an account restriction. + */ +enum TALER_EXCHANGE_AccountRestrictionType +{ + /** + * Invalid restriction. + */ + TALER_EXCHANGE_AR_INVALID = 0, + + /** + * Account must not be used for this operation. + */ + TALER_EXCHANGE_AR_DENY = 1, + + /** + * Other account must match given regular expression. + */ + TALER_EXCHANGE_AR_REGEX = 2 +}; + +/** + * Restrictions that apply to using a given exchange bank account. + */ +struct TALER_EXCHANGE_AccountRestriction +{ + + /** + * Type of the account restriction. + */ + enum TALER_EXCHANGE_AccountRestrictionType type; + + /** + * Restriction details depending on @e type. + */ + union + { + /** + * Details if type is #TALER_EXCHANGE_AR_REGEX. + */ + struct + { + /** + * Regular expression that the payto://-URI of the partner account must + * follow. The regular expression should follow posix-egrep, but + * without support for character classes, GNU extensions, + * back-references or intervals. See + * https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html + * for a description of the posix-egrep syntax. Applications may support + * regexes with additional features, but exchanges must not use such + * regexes. + */ + const char *posix_egrep; + + /** + * Hint for a human to understand the restriction. + */ + const char *human_hint; + + /** + * Internationalizations for the @e human_hint. Map from IETF BCP 47 + * language tax to localized human hints. + */ + const json_t *human_hint_i18n; + } regex; + } details; + +}; + + /** * Information about a wire account of the exchange. */ @@ -753,38 +873,104 @@ struct TALER_EXCHANGE_WireAccount */ const char *payto_uri; + /** + * URL of a conversion service in case using this account is subject to + * currency conversion. NULL for no conversion needed. + */ + const char *conversion_url; + + /** + * Array of restrictions that apply when crediting + * this account. + */ + struct TALER_EXCHANGE_AccountRestriction *credit_restrictions; + + /** + * Array of restrictions that apply when debiting + * this account. + */ + struct TALER_EXCHANGE_AccountRestriction *debit_restrictions; + + /** + * Length of the @e credit_restrictions array. + */ + unsigned int credit_restrictions_length; + + /** + * Length of the @e debit_restrictions array. + */ + unsigned int debit_restrictions_length; + /** * Signature of the exchange over the account (was checked by the API). */ struct TALER_MasterSignatureP master_sig; - /** - * Linked list of wire fees the exchange charges for - * accounts of the wire method matching @e payto_uri. - */ - const struct TALER_EXCHANGE_WireAggregateFees *fees; - }; /** - * Callbacks of this type are used to serve the result of submitting a - * wire format inquiry request to a exchange. + * Response to a /wire request. + */ +struct TALER_EXCHANGE_WireResponse +{ + /** + * HTTP response details. + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Response details depending on status. + */ + union + { + + /** + * Details for #MHD_HTTP_OK. + */ + struct + { + + /** + * Array of accounts of the exchange. + */ + const struct TALER_EXCHANGE_WireAccount *accounts; + + /** + * Array of wire fees by wire method. + */ + const struct TALER_EXCHANGE_WireFeesByMethod *fees; + + /** + * Length of @e accounts array. + */ + unsigned int accounts_len; + + /** + * Length of @e fees array. + */ + unsigned int fees_len; + + } ok; + + } details; +}; + + +/** + * Callbacks of this type are used to serve the result of submitting a wire + * format inquiry request to a exchange. * * If the request fails to generate a valid response from the - * exchange, @a http_status will also be zero. + * exchange, the http_status will also be zero. * * @param cls closure - * @param hr HTTP response data - * @param accounts_len length of the @a accounts array - * @param accounts list of wire accounts of the exchange, NULL on error + * @param wr response data */ typedef void (*TALER_EXCHANGE_WireCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - unsigned int accounts_len, - const struct TALER_EXCHANGE_WireAccount *accounts); + const struct TALER_EXCHANGE_WireResponse *wr); /** @@ -976,7 +1162,7 @@ struct TALER_EXCHANGE_DepositResult */ const char *transaction_base_url; - } success; + } ok; /** * Information returned if the HTTP status is @@ -1110,7 +1296,7 @@ struct TALER_EXCHANGE_BatchDepositResult */ unsigned int num_signatures; - } success; + } ok; /** * Information returned if the HTTP status is @@ -1202,23 +1388,51 @@ TALER_EXCHANGE_batch_deposit_cancel ( */ struct TALER_EXCHANGE_RefundHandle; +/** + * Response from the /refund API. + */ +struct TALER_EXCHANGE_RefundResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Response details depending on the HTTP status code. + */ + union + { + /** + * Details on #MHD_HTTP_OK. + */ + struct + { + /** + * Exchange key used to sign. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * The actual signature + */ + struct TALER_ExchangeSignatureP exchange_sig; + } ok; + } details; +}; + /** * Callbacks of this type are used to serve the result of submitting a * refund request to an exchange. * * @param cls closure - * @param hr HTTP response data - * @param sign_key exchange key used to sign @a obj, or NULL - * @param signature the actual signature, or NULL on error + * @param rr refund response */ typedef void (*TALER_EXCHANGE_RefundCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_ExchangePublicKeyP *sign_key, - const struct TALER_ExchangeSignatureP *signature); - + const struct TALER_EXCHANGE_RefundResponse *rr); /** * Submit a refund request to the exchange and get the exchange's response. @@ -1311,7 +1525,7 @@ struct TALER_EXCHANGE_CsRMeltResponse * respective coin's withdraw operation. */ const struct TALER_ExchangeWithdrawValues *alg_values; - } success; + } ok; /** * Details if the status is #MHD_HTTP_GONE. @@ -1423,7 +1637,7 @@ struct TALER_EXCHANGE_CsRWithdrawResponse * respective coin's withdraw operation. */ struct TALER_ExchangeWithdrawValues alg_values; - } success; + } ok; /** * Details if the status is #MHD_HTTP_GONE. @@ -2209,7 +2423,7 @@ struct TALER_EXCHANGE_WithdrawResponse /** * Details if the status is #MHD_HTTP_OK. */ - struct TALER_EXCHANGE_PrivateCoinDetails success; + struct TALER_EXCHANGE_PrivateCoinDetails ok; /** * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS. @@ -2336,7 +2550,7 @@ struct TALER_EXCHANGE_BatchWithdrawResponse * Length of the @e coins array. */ unsigned int num_coins; - } success; + } ok; /** * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS. @@ -2430,19 +2644,46 @@ TALER_EXCHANGE_batch_withdraw_cancel ( struct TALER_EXCHANGE_BatchWithdrawHandle *wh); +/** + * Response from a withdraw2 request. + */ +struct TALER_EXCHANGE_Withdraw2Response +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Response details depending on the HTTP status. + */ + union + { + /** + * Details if HTTP status is #MHD_HTTP_OK. + */ + struct + { + /** + * blind signature over the coin + */ + struct TALER_BlindedDenominationSignature blind_sig; + } ok; + } details; + +}; + /** * Callbacks of this type are used to serve the result of submitting a * withdraw request to a exchange without the (un)blinding factor. * * @param cls closure - * @param hr HTTP response data - * @param blind_sig blind signature over the coin, NULL on error + * @param w2r response data */ typedef void (*TALER_EXCHANGE_Withdraw2Callback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_BlindedDenominationSignature *blind_sig); + const struct TALER_EXCHANGE_Withdraw2Response *w2r); /** @@ -2492,21 +2733,53 @@ void TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh); +/** + * Response from a batch-withdraw request (2nd variant). + */ +struct TALER_EXCHANGE_BatchWithdraw2Response +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Response details depending on the HTTP status. + */ + union + { + /** + * Details if HTTP status is #MHD_HTTP_OK. + */ + struct + { + /** + * array of blind signatures over the coins. + */ + const struct TALER_BlindedDenominationSignature *blind_sigs; + + /** + * length of @e blind_sigs + */ + unsigned int blind_sigs_length; + + } ok; + } details; + +}; + + /** * Callbacks of this type are used to serve the result of submitting a batch * withdraw request to a exchange without the (un)blinding factor. * * @param cls closure - * @param hr HTTP response data - * @param blind_sigs array of blind signatures over the coins, NULL on error - * @param blind_sigs_length length of @a blind_sigs + * @param bw2r response data */ typedef void (*TALER_EXCHANGE_BatchWithdraw2Callback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_BlindedDenominationSignature *blind_sigs, - unsigned int blind_sigs_length); + const struct TALER_EXCHANGE_BatchWithdraw2Response *bw2r); /** @@ -2675,7 +2948,7 @@ struct TALER_EXCHANGE_MeltResponse * Gamma value chosen by the exchange. */ uint32_t noreveal_index; - } success; + } ok; } details; }; @@ -2801,7 +3074,7 @@ struct TALER_EXCHANGE_RevealResult * Number of coins returned. */ unsigned int num_coins; - } success; + } ok; } details; @@ -2947,7 +3220,7 @@ struct TALER_EXCHANGE_LinkResult * Number of coins returned. */ unsigned int num_coins; - } success; + } ok; } details; @@ -3059,19 +3332,44 @@ struct TALER_EXCHANGE_TransferData }; +/** + * Response for a GET /transfers request. + */ +struct TALER_EXCHANGE_TransfersGetResponse +{ + /** + * HTTP response. + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Details depending on HTTP status code. + */ + union + { + /** + * Details if status code is #MHD_HTTP_OK. + */ + struct + { + struct TALER_EXCHANGE_TransferData td; + } ok; + + } details; +}; + + /** * Function called with detailed wire transfer data, including all * of the coin transactions that were combined into the wire transfer. * * @param cls closure - * @param hr HTTP response data - * @param ta transfer data, (set only if @a http_status is #MHD_HTTP_OK, otherwise NULL) + * @param tgr response data */ typedef void (*TALER_EXCHANGE_TransfersGetCallback)( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_TransferData *ta); + const struct TALER_EXCHANGE_TransfersGetResponse *tgr); /** @@ -3163,7 +3461,7 @@ struct TALER_EXCHANGE_GetDepositResponse */ struct TALER_Amount coin_contribution; - } success; + } ok; /** * Response if the status was #MHD_HTTP_ACCEPTED @@ -3315,6 +3613,37 @@ TALER_EXCHANGE_free_reserve_history ( struct TALER_EXCHANGE_RecoupHandle; +/** + * Response from a recoup request. + */ +struct TALER_EXCHANGE_RecoupResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Response details depending on the HTTP status. + */ + union + { + /** + * Details if HTTP status is #MHD_HTTP_OK. + */ + struct + { + /** + * public key of the reserve receiving the recoup + */ + struct TALER_ReservePublicKeyP reserve_pub; + + } ok; + } details; + +}; + + /** * Callbacks of this type are used to return the final result of * submitting a recoup request to a exchange. If the operation was @@ -3322,14 +3651,12 @@ struct TALER_EXCHANGE_RecoupHandle; * reserve that was credited. * * @param cls closure - * @param hr HTTP response data - * @param reserve_pub public key of the reserve receiving the recoup + * @param rr response data */ typedef void (*TALER_EXCHANGE_RecoupResultCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_ReservePublicKeyP *reserve_pub); + const struct TALER_EXCHANGE_RecoupResponse *rr); /** @@ -3377,19 +3704,48 @@ TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph); struct TALER_EXCHANGE_RecoupRefreshHandle; +/** + * Response from a /recoup-refresh request. + */ +struct TALER_EXCHANGE_RecoupRefreshResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Response details depending on the HTTP status. + */ + union + { + /** + * Details if HTTP status is #MHD_HTTP_OK. + */ + struct + { + /** + * public key of the dirty coin that was credited + */ + struct TALER_CoinSpendPublicKeyP old_coin_pub; + + } ok; + } details; + +}; + + /** * Callbacks of this type are used to return the final result of * submitting a recoup-refresh request to a exchange. * * @param cls closure - * @param hr HTTP response data - * @param old_coin_pub public key of the dirty coin that was credited + * @param rrr response data */ typedef void (*TALER_EXCHANGE_RecoupRefreshResultCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_CoinSpendPublicKeyP *old_coin_pub); + const struct TALER_EXCHANGE_RecoupRefreshResponse *rrr); /** @@ -3495,7 +3851,7 @@ struct TALER_EXCHANGE_KycStatus */ enum TALER_AmlDecisionState aml_status; - } success; + } ok; /** * KYC is required. @@ -3898,19 +4254,48 @@ struct TALER_EXCHANGE_FutureKeys }; +/** + * Response from a /management/keys request. + */ +struct TALER_EXCHANGE_ManagementGetKeysResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Response details depending on the HTTP status. + */ + union + { + /** + * Details if HTTP status is #MHD_HTTP_OK. + */ + struct + { + /** + * information about the various keys used + * by the exchange + */ + struct TALER_EXCHANGE_FutureKeys keys; + + } ok; + } details; + +}; + + /** * Function called with information about future keys. * * @param cls closure - * @param hr HTTP response data - * @param keys information about the various keys used - * by the exchange, NULL if /management/keys failed + * @param mgr HTTP response data */ typedef void (*TALER_EXCHANGE_ManagementGetKeysCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_FutureKeys *keys); + const struct TALER_EXCHANGE_ManagementGetKeysResponse *mgr); /** @@ -4012,16 +4397,29 @@ struct TALER_EXCHANGE_ManagementPostKeysData }; +/** + * Response from a POST /management/keys request. + */ +struct TALER_EXCHANGE_ManagementPostKeysResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + +}; + + /** * Function called with information about the post keys operation result. * * @param cls closure - * @param hr HTTP response data + * @param mr response data */ typedef void (*TALER_EXCHANGE_ManagementPostKeysCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr); + const struct TALER_EXCHANGE_ManagementPostKeysResponse *mr); /** @@ -4071,6 +4469,20 @@ struct TALER_EXCHANGE_ManagementPostExtensionsData struct TALER_MasterSignatureP extensions_sig; }; + +/** + * Response from a POST /management/extensions request. + */ +struct TALER_EXCHANGE_ManagementPostExtensionsResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + +}; + + /** * Function called with information about the post extensions operation result. * @@ -4080,7 +4492,7 @@ struct TALER_EXCHANGE_ManagementPostExtensionsData typedef void (*TALER_EXCHANGE_ManagementPostExtensionsCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr); + const struct TALER_EXCHANGE_ManagementPostExtensionsResponse *hr); /** * @brief Handle for a POST /management/extensions request. @@ -4118,6 +4530,19 @@ TALER_EXCHANGE_management_post_extensions_cancel ( struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph); +/** + * Response from a POST /management/drain request. + */ +struct TALER_EXCHANGE_ManagementDrainResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + +}; + + /** * Function called with information about the drain profits result. * @@ -4127,7 +4552,7 @@ TALER_EXCHANGE_management_post_extensions_cancel ( typedef void (*TALER_EXCHANGE_ManagementDrainProfitsCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr); + const struct TALER_EXCHANGE_ManagementDrainResponse *hr); /** @@ -4175,6 +4600,19 @@ TALER_EXCHANGE_management_drain_profits_cancel ( struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp); +/** + * Response from a POST /management/denominations/$DENOM/revoke request. + */ +struct TALER_EXCHANGE_ManagementRevokeDenominationResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + +}; + + /** * Function called with information about the post revocation operation result. * @@ -4184,7 +4622,7 @@ TALER_EXCHANGE_management_drain_profits_cancel ( typedef void (*TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr); + const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *hr); /** @@ -4224,6 +4662,18 @@ TALER_EXCHANGE_management_revoke_denomination_key_cancel ( struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh); +/** + * Response from a POST /management/signkeys/$SK/revoke request. + */ +struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + +}; + /** * Function called with information about the post revocation operation result. * @@ -4233,7 +4683,7 @@ TALER_EXCHANGE_management_revoke_denomination_key_cancel ( typedef void (*TALER_EXCHANGE_ManagementRevokeSigningKeyCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr); + const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *hr); /** @@ -4273,6 +4723,18 @@ TALER_EXCHANGE_management_revoke_signing_key_cancel ( struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh); +/** + * Response from a POST /management/aml-officers request. + */ +struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + +}; + /** * Function called with information about the change to * an AML officer status. @@ -4283,7 +4745,7 @@ TALER_EXCHANGE_management_revoke_signing_key_cancel ( typedef void (*TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr); + const struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse *hr); /** @@ -4390,7 +4852,7 @@ struct TALER_EXCHANGE_AmlDecisionsResponse */ unsigned int decisions_length; - } success; + } ok; } details; }; @@ -4547,7 +5009,7 @@ struct TALER_EXCHANGE_AmlDecisionResponse */ unsigned int kyc_attributes_length; - } success; + } ok; } details; }; @@ -4617,6 +5079,7 @@ struct TALER_EXCHANGE_AddAmlDecision; * @param cls closure * @param hr HTTP response data */ +// FIXME: bad API typedef void (*TALER_EXCHANGE_AddAmlDecisionCallback) ( void *cls, @@ -4672,6 +5135,7 @@ TALER_EXCHANGE_add_aml_decision_cancel ( * @param cls closure * @param hr HTTP response data */ +// FIXME: bad API typedef void (*TALER_EXCHANGE_ManagementAddPartnerCallback) ( void *cls, @@ -4732,6 +5196,7 @@ TALER_EXCHANGE_management_add_partner_cancel ( * @param cls closure * @param hr HTTP response data */ +// FIXME: bad API typedef void (*TALER_EXCHANGE_ManagementAuditorEnableCallback) ( void *cls, @@ -4787,6 +5252,7 @@ TALER_EXCHANGE_management_enable_auditor_cancel ( * @param cls closure * @param hr HTTP response data */ +// FIXME: bad API typedef void (*TALER_EXCHANGE_ManagementAuditorDisableCallback) ( void *cls, @@ -4832,16 +5298,28 @@ TALER_EXCHANGE_management_disable_auditor_cancel ( struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah); +/** + * Response from an exchange account/enable operation. + */ +struct TALER_EXCHANGE_ManagementWireEnableResponse +{ + /** + * HTTP response data. + */ + struct TALER_EXCHANGE_HttpResponse hr; +}; + + /** * Function called with information about the wire enable operation result. * * @param cls closure - * @param hr HTTP response data + * @param wer HTTP response data */ typedef void (*TALER_EXCHANGE_ManagementWireEnableCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr); + const struct TALER_EXCHANGE_ManagementWireEnableResponse *wer); /** @@ -4856,6 +5334,9 @@ struct TALER_EXCHANGE_ManagementWireEnableHandle; * @param ctx the context * @param url HTTP base URL for the exchange * @param payto_uri RFC 8905 URI of the exchange's bank account + * @param conversion_url URL of the conversion service, or NULL if none + * @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec + * @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec * @param validity_start when was this decided? * @param master_sig1 signature affirming the wire addition * of purpose #TALER_SIGNATURE_MASTER_ADD_WIRE @@ -4870,6 +5351,9 @@ TALER_EXCHANGE_management_enable_wire ( struct GNUNET_CURL_Context *ctx, const char *url, const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, struct GNUNET_TIME_Timestamp validity_start, const struct TALER_MasterSignatureP *master_sig1, const struct TALER_MasterSignatureP *master_sig2, @@ -4887,16 +5371,27 @@ TALER_EXCHANGE_management_enable_wire_cancel ( struct TALER_EXCHANGE_ManagementWireEnableHandle *wh); +/** + * Response from an exchange account/disable operation. + */ +struct TALER_EXCHANGE_ManagementWireDisableResponse +{ + /** + * HTTP response data. + */ + struct TALER_EXCHANGE_HttpResponse hr; +}; + /** * Function called with information about the wire disable operation result. * * @param cls closure - * @param hr HTTP response data + * @param wdr response data */ typedef void (*TALER_EXCHANGE_ManagementWireDisableCallback) ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr); + const struct TALER_EXCHANGE_ManagementWireDisableResponse *wdr); /** @@ -4945,6 +5440,7 @@ TALER_EXCHANGE_management_disable_wire_cancel ( * @param cls closure * @param hr HTTP response data */ +// FIXME: bad API typedef void (*TALER_EXCHANGE_ManagementSetWireFeeCallback) ( void *cls, @@ -5001,6 +5497,7 @@ TALER_EXCHANGE_management_set_wire_fees_cancel ( * @param cls closure * @param hr HTTP response data */ +// FIXME: bad API typedef void (*TALER_EXCHANGE_ManagementSetGlobalFeeCallback) ( void *cls, @@ -5062,6 +5559,7 @@ TALER_EXCHANGE_management_set_global_fees_cancel ( * @param cls closure * @param hr HTTP response data */ +// FIXME: bad API typedef void (*TALER_EXCHANGE_AuditorAddDenominationCallback) ( void *cls, @@ -5147,7 +5645,7 @@ struct TALER_EXCHANGE_ContractGetResponse */ size_t econtract_size; - } success; + } ok; } details; @@ -5243,7 +5741,7 @@ struct TALER_EXCHANGE_PurseGetResponse */ struct GNUNET_TIME_Timestamp purse_expiration; - } success; + } ok; } details; @@ -5333,7 +5831,7 @@ struct TALER_EXCHANGE_PurseCreateDepositResponse struct TALER_ExchangeSignatureP exchange_sig; - } success; + } ok; } details; @@ -5532,7 +6030,7 @@ struct TALER_EXCHANGE_AccountMergeResponse */ struct GNUNET_TIME_Timestamp etime; - } success; + } ok; /** * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS. @@ -5640,7 +6138,7 @@ struct TALER_EXCHANGE_PurseCreateMergeResponse struct { - } success; + } ok; /** * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS. @@ -5760,7 +6258,7 @@ struct TALER_EXCHANGE_PurseDepositResponse */ struct TALER_PrivateContractHashP h_contract_terms; - } success; + } ok; } details; }; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 7c1757642..d025b5327 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -2881,6 +2881,9 @@ typedef enum GNUNET_GenericReturnValue * * @param cls closure * @param payto_uri the exchange bank account URI + * @param conversion_url URL of a conversion service, NULL if there is no conversion + * @param debit_restrictions JSON array with debit restrictions on the account + * @param credit_restrictions JSON array with credit restrictions on the account * @param master_sig master key signature affirming that this is a bank * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS) */ @@ -2888,6 +2891,9 @@ typedef void (*TALER_EXCHANGEDB_WireAccountCallback)( void *cls, const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, const struct TALER_MasterSignatureP *master_sig); @@ -5544,6 +5550,9 @@ struct TALER_EXCHANGEDB_Plugin * * @param cls closure * @param payto_uri wire account of the exchange + * @param conversion_url URL of a conversion service, NULL if there is no conversion + * @param debit_restrictions JSON array with debit restrictions on the account + * @param credit_restrictions JSON array with credit restrictions on the account * @param start_date date when the account was added by the offline system * (only to be used for replay detection) * @param master_sig public signature affirming the existence of the account, @@ -5553,6 +5562,9 @@ struct TALER_EXCHANGEDB_Plugin enum GNUNET_DB_QueryStatus (*insert_wire)(void *cls, const char *payto_uri, + const char *conversion_url, + json_t *debit_restrictions, + json_t *credit_restrictions, struct GNUNET_TIME_Timestamp start_date, const struct TALER_MasterSignatureP *master_sig); @@ -5562,6 +5574,9 @@ struct TALER_EXCHANGEDB_Plugin * * @param cls closure * @param payto_uri account the update is about + * @param conversion_url URL of a conversion service, NULL if there is no conversion + * @param debit_restrictions JSON array with debit restrictions on the account; NULL allowed if not @a enabled + * @param credit_restrictions JSON array with credit restrictions on the account; NULL allowed if not @a enabled * @param change_date date when the account status was last changed * (only to be used for replay detection) * @param enabled true to enable, false to disable (the actual change) @@ -5570,6 +5585,9 @@ struct TALER_EXCHANGEDB_Plugin enum GNUNET_DB_QueryStatus (*update_wire)(void *cls, const char *payto_uri, + const char *conversion_url, + json_t *debit_restrictions, + json_t *credit_restrictions, struct GNUNET_TIME_Timestamp change_date, bool enabled); diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index 7f17df030..d0749808d 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -674,33 +674,6 @@ TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s, struct TALER_MerchantWireHashP *hc); -/** - * Check the signature in @a wire_s. Also performs rudimentary - * checks on the account data *if* supported. - * - * @param wire_s signed wire information of an exchange - * @param master_pub master public key of the exchange - * @return #GNUNET_OK if signature is valid - */ -enum GNUNET_GenericReturnValue -TALER_JSON_exchange_wire_signature_check ( - const json_t *wire_s, - const struct TALER_MasterPublicKeyP *master_pub); - - -/** - * Create a signed wire statement for the given account. - * - * @param payto_uri account specification - * @param master_priv private key to sign with - * @return NULL if @a payto_uri is malformed - */ -json_t * -TALER_JSON_exchange_wire_signature_make ( - const char *payto_uri, - const struct TALER_MasterPrivateKeyP *master_priv); - - /** * Extract a string from @a object under the field @a field, but respecting * the Taler i18n rules and the language preferences expressed in @a diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 5fc930a86..664db6ccd 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -147,15 +147,11 @@ TALER_TESTING_prepare_exchange (const char *config_filename, * * @param cls closure, typically, the "run" method containing * all the commands to be run, and a closure for it. - * @param hr http response details - * @param keys the exchange's keys. - * @param compat protocol compatibility information. + * @param kr response details */ void TALER_TESTING_cert_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_Keys *keys, - enum TALER_EXCHANGE_VersionCompatibility compat); + const struct TALER_EXCHANGE_KeysResponse *kr); /** diff --git a/src/json/Makefile.am b/src/json/Makefile.am index 2f5ec3f17..6bd5b464c 100644 --- a/src/json/Makefile.am +++ b/src/json/Makefile.am @@ -28,12 +28,10 @@ libtalerjson_la_LIBADD = \ $(XLIB) TESTS = \ - test_json \ - test_json_wire + test_json check_PROGRAMS= \ - test_json \ - test_json_wire + test_json test_json_SOURCES = \ test_json.c @@ -43,13 +41,3 @@ test_json_LDADD = \ $(top_builddir)/src/util/libtalerutil.la \ -lgnunetutil \ -ljansson - - -test_json_wire_SOURCES = \ - test_json_wire.c -test_json_wire_LDADD = \ - $(top_builddir)/src/json/libtalerjson.la \ - -lgnunetjson \ - $(top_builddir)/src/util/libtalerutil.la \ - -lgnunetutil \ - -ljansson diff --git a/src/json/json_wire.c b/src/json/json_wire.c index 544b56453..9d22d28ea 100644 --- a/src/json/json_wire.c +++ b/src/json/json_wire.c @@ -70,80 +70,6 @@ TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s, } -enum GNUNET_GenericReturnValue -TALER_JSON_exchange_wire_signature_check ( - const json_t *wire_s, - const struct TALER_MasterPublicKeyP *master_pub) -{ - const char *payto_uri; - struct TALER_MasterSignatureP master_sig; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("payto_uri", - &payto_uri), - GNUNET_JSON_spec_fixed_auto ("master_sig", - &master_sig), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (wire_s, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - { - char *err; - - err = TALER_payto_validate (payto_uri); - if (NULL != err) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "URI `%s' ill-formed: %s\n", - payto_uri, - err); - GNUNET_free (err); - return GNUNET_SYSERR; - } - } - - return TALER_exchange_wire_signature_check (payto_uri, - master_pub, - &master_sig); -} - - -json_t * -TALER_JSON_exchange_wire_signature_make ( - const char *payto_uri, - const struct TALER_MasterPrivateKeyP *master_priv) -{ - struct TALER_MasterSignatureP master_sig; - char *err; - - if (NULL != - (err = TALER_payto_validate (payto_uri))) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid payto URI `%s': %s\n", - payto_uri, - err); - GNUNET_free (err); - return NULL; - } - TALER_exchange_wire_signature_make (payto_uri, - master_priv, - &master_sig); - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("payto_uri", - payto_uri), - GNUNET_JSON_pack_data_auto ("master_sig", - &master_sig)); -} - - char * TALER_JSON_wire_to_payto (const json_t *wire_s) { diff --git a/src/json/test_json_wire.c b/src/json/test_json_wire.c deleted file mode 100644 index b417b25fe..000000000 --- a/src/json/test_json_wire.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - This file is part of TALER - (C) 2015, 2016 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 json/test_json_wire.c - * @brief Tests for Taler-specific crypto logic - * @author Christian Grothoff - */ -#include "platform.h" -#include "taler_util.h" -#include "taler_json_lib.h" - - -int -main (int argc, - const char *const argv[]) -{ - struct TALER_MasterPublicKeyP master_pub; - struct TALER_MasterPrivateKeyP master_priv; - json_t *wire_xtalerbank; - json_t *wire_iban; - const char *payto_xtalerbank = "payto://x-taler-bank/42"; - const char *payto_iban = - "payto://iban/BIC-TO-BE-SKIPPED/DE89370400440532013000?receiver-name=Test"; - char *p_xtalerbank; - char *p_iban; - - (void) argc; - (void) argv; - GNUNET_log_setup ("test-json-wire", - "WARNING", - NULL); - GNUNET_CRYPTO_eddsa_key_create (&master_priv.eddsa_priv); - GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv, - &master_pub.eddsa_pub); - wire_xtalerbank = TALER_JSON_exchange_wire_signature_make (payto_xtalerbank, - &master_priv); - wire_iban = TALER_JSON_exchange_wire_signature_make (payto_iban, - &master_priv); - if (NULL == wire_iban) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not parse payto/IBAN (%s) into 'wire object'\n", - payto_iban); - return 1; - } - p_xtalerbank = TALER_JSON_wire_to_payto (wire_xtalerbank); - p_iban = TALER_JSON_wire_to_payto (wire_iban); - GNUNET_assert (0 == strcmp (p_xtalerbank, payto_xtalerbank)); - GNUNET_assert (0 == strcmp (p_iban, payto_iban)); - GNUNET_free (p_xtalerbank); - GNUNET_free (p_iban); - - GNUNET_assert (GNUNET_OK == - TALER_JSON_exchange_wire_signature_check (wire_xtalerbank, - &master_pub)); - GNUNET_assert (GNUNET_OK == - TALER_JSON_exchange_wire_signature_check (wire_iban, - &master_pub)); - json_decref (wire_xtalerbank); - json_decref (wire_iban); - - return 0; -} - - -/* end of test_json_wire.c */ diff --git a/src/lib/exchange_api_batch_deposit.c b/src/lib/exchange_api_batch_deposit.c index 4665908d2..c583d5add 100644 --- a/src/lib/exchange_api_batch_deposit.c +++ b/src/lib/exchange_api_batch_deposit.c @@ -247,7 +247,7 @@ handle_deposit_finished (void *cls, &dh->exchange_pub), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("transaction_base_url", - &dr.details.success.transaction_base_url), + &dr.details.ok.transaction_base_url), NULL), GNUNET_JSON_spec_timestamp ("exchange_timestamp", &dh->exchange_timestamp), @@ -332,7 +332,7 @@ handle_deposit_finished (void *cls, GNUNET_break_op (0); dr.hr.http_status = 0; dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE; - GNUNET_JSON_parse_free (spec); + GNUNET_JSON_parse_free (spec); break; } } @@ -341,10 +341,10 @@ handle_deposit_finished (void *cls, dh); GNUNET_JSON_parse_free (spec); } - dr.details.success.exchange_sigs = dh->exchange_sigs; - dr.details.success.exchange_pub = &dh->exchange_pub; - dr.details.success.deposit_timestamp = dh->exchange_timestamp; - dr.details.success.num_signatures = dh->num_cdds; + dr.details.ok.exchange_sigs = dh->exchange_sigs; + dr.details.ok.exchange_pub = &dh->exchange_pub; + dr.details.ok.deposit_timestamp = dh->exchange_timestamp; + dr.details.ok.num_signatures = dh->num_cdds; break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy diff --git a/src/lib/exchange_api_batch_withdraw.c b/src/lib/exchange_api_batch_withdraw.c index 9bb158f87..4817ae403 100644 --- a/src/lib/exchange_api_batch_withdraw.c +++ b/src/lib/exchange_api_batch_withdraw.c @@ -144,37 +144,34 @@ struct TALER_EXCHANGE_BatchWithdrawHandle * HTTP /reserves/$RESERVE_PUB/batch-withdraw request. * * @param cls the `struct TALER_EXCHANGE_BatchWithdrawHandle` - * @param hr HTTP response data - * @param blind_sigs array of blind signatures over the coins, NULL on error - * @param blind_sigs_length length of the @a blind_sigs array + * @param bw2r response data */ static void handle_reserve_batch_withdraw_finished ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_BlindedDenominationSignature *blind_sigs, - unsigned int blind_sigs_length) + const struct TALER_EXCHANGE_BatchWithdraw2Response *bw2r) { struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cls; struct TALER_EXCHANGE_BatchWithdrawResponse wr = { - .hr = *hr + .hr = bw2r->hr }; - struct TALER_EXCHANGE_PrivateCoinDetails coins[wh->num_coins]; + struct TALER_EXCHANGE_PrivateCoinDetails coins[GNUNET_NZL (wh->num_coins)]; wh->wh2 = NULL; memset (coins, 0, sizeof (coins)); - if (blind_sigs_length != wh->num_coins) - { - GNUNET_break_op (0); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - switch (hr->http_status) + switch (bw2r->hr.http_status) { case MHD_HTTP_OK: { + if (bw2r->details.ok.blind_sigs_length != wh->num_coins) + { + GNUNET_break_op (0); + wr.hr.http_status = 0; + wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } for (unsigned int i = 0; inum_coins; i++) { struct CoinData *cd = &wh->coins[i]; @@ -183,7 +180,7 @@ handle_reserve_batch_withdraw_finished ( if (GNUNET_OK != TALER_planchet_to_coin (&cd->pk.key, - &blind_sigs[i], + &bw2r->details.ok.blind_sigs[i], &cd->bks, &cd->priv, cd->ach, @@ -200,8 +197,8 @@ handle_reserve_batch_withdraw_finished ( coin->sig = fc.sig; coin->exchange_vals = cd->alg_values; } - wr.details.success.coins = coins; - wr.details.success.num_coins = wh->num_coins; + wr.details.ok.coins = coins; + wr.details.ok.num_coins = wh->num_coins; break; } case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: @@ -217,7 +214,7 @@ handle_reserve_batch_withdraw_finished ( }; if (GNUNET_OK != - GNUNET_JSON_parse (hr->reply, + GNUNET_JSON_parse (bw2r->hr.reply, spec, NULL, NULL)) { @@ -289,7 +286,7 @@ withdraw_cs_stage_two_callback ( switch (csrr->hr.http_status) { case MHD_HTTP_OK: - cd->alg_values = csrr->details.success.alg_values; + cd->alg_values = csrr->details.ok.alg_values; TALER_planchet_setup_coin_priv (&cd->ps, &cd->alg_values, &cd->priv); diff --git a/src/lib/exchange_api_batch_withdraw2.c b/src/lib/exchange_api_batch_withdraw2.c index e14ed92fd..04c2c0100 100644 --- a/src/lib/exchange_api_batch_withdraw2.c +++ b/src/lib/exchange_api_batch_withdraw2.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -108,9 +108,9 @@ reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh, "ev_sigs"); const json_t *j; unsigned int index; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK + struct TALER_EXCHANGE_BatchWithdraw2Response bwr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK }; if ( (NULL == ja) || @@ -141,10 +141,10 @@ reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh, } /* signature is valid, return it to the application */ + bwr.details.ok.blind_sigs = blind_sigs; + bwr.details.ok.blind_sigs_length = wh->num_coins; wh->cb (wh->cb_cls, - &hr, - blind_sigs, - wh->num_coins); + &bwr); /* make sure callback isn't called again after return */ wh->cb = NULL; for (unsigned int i = 0; inum_coins; i++) @@ -264,16 +264,16 @@ handle_reserve_batch_withdraw_finished (void *cls, { struct TALER_EXCHANGE_BatchWithdraw2Handle *wh = cls; const json_t *j = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code + struct TALER_EXCHANGE_BatchWithdraw2Response bwr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code }; wh->job = NULL; switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + bwr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: if (GNUNET_OK != @@ -281,8 +281,8 @@ handle_reserve_batch_withdraw_finished (void *cls, j)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + bwr.hr.http_status = 0; + bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } GNUNET_assert (NULL == wh->cb); @@ -304,8 +304,8 @@ handle_reserve_batch_withdraw_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + bwr.hr.http_status = 0; + bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } } @@ -313,24 +313,24 @@ handle_reserve_batch_withdraw_finished (void *cls, case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + bwr.hr.ec = TALER_JSON_get_error_code (j); + bwr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_FORBIDDEN: GNUNET_break_op (0); /* Nothing really to verify, exchange says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + bwr.hr.ec = TALER_JSON_get_error_code (j); + bwr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, the exchange basically just says that it doesn't know this reserve. Can happen if we query before the wire transfer went through. We should simply pass the JSON reply to the application. */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + bwr.hr.ec = TALER_JSON_get_error_code (j); + bwr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_CONFLICT: /* The exchange says that the reserve has insufficient funds; @@ -340,13 +340,13 @@ handle_reserve_batch_withdraw_finished (void *cls, j)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + bwr.hr.http_status = 0; + bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; } else { - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + bwr.hr.ec = TALER_JSON_get_error_code (j); + bwr.hr.hint = TALER_JSON_get_error_hint (j); } break; case MHD_HTTP_GONE: @@ -354,32 +354,30 @@ handle_reserve_batch_withdraw_finished (void *cls, /* Note: one might want to check /keys for revocation signature here, alas tricky in case our /keys is outdated => left to clients */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + bwr.hr.ec = TALER_JSON_get_error_code (j); + bwr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + bwr.hr.ec = TALER_JSON_get_error_code (j); + bwr.hr.hint = TALER_JSON_get_error_hint (j); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + bwr.hr.ec = TALER_JSON_get_error_code (j); + bwr.hr.hint = TALER_JSON_get_error_hint (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange batch withdraw\n", (unsigned int) response_code, - (int) hr.ec); + (int) bwr.hr.ec); break; } if (NULL != wh->cb) { wh->cb (wh->cb_cls, - &hr, - NULL, - 0); + &bwr); wh->cb = NULL; } TALER_EXCHANGE_batch_withdraw2_cancel (wh); diff --git a/src/lib/exchange_api_contracts_get.c b/src/lib/exchange_api_contracts_get.c index 263a0cbe9..8fd4ba1e7 100644 --- a/src/lib/exchange_api_contracts_get.c +++ b/src/lib/exchange_api_contracts_get.c @@ -109,7 +109,7 @@ handle_contract_get_finished (void *cls, struct TALER_PurseContractSignatureP econtract_sig; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("purse_pub", - &dr.details.success.purse_pub), + &dr.details.ok.purse_pub), GNUNET_JSON_spec_fixed_auto ("econtract_sig", &econtract_sig), GNUNET_JSON_spec_varsize ("econtract", @@ -133,7 +133,7 @@ handle_contract_get_finished (void *cls, econtract, econtract_size, &cgh->cpub, - &dr.details.success.purse_pub, + &dr.details.ok.purse_pub, &econtract_sig)) { GNUNET_break (0); @@ -142,8 +142,8 @@ handle_contract_get_finished (void *cls, GNUNET_JSON_parse_free (spec); break; } - dr.details.success.econtract = econtract; - dr.details.success.econtract_size = econtract_size; + dr.details.ok.econtract = econtract; + dr.details.ok.econtract_size = econtract_size; cgh->cb (cgh->cb_cls, &dr); GNUNET_JSON_parse_free (spec); diff --git a/src/lib/exchange_api_csr_melt.c b/src/lib/exchange_api_csr_melt.c index 9de8cd8d9..67b1a9b7e 100644 --- a/src/lib/exchange_api_csr_melt.c +++ b/src/lib/exchange_api_csr_melt.c @@ -94,8 +94,8 @@ csr_ok (struct TALER_EXCHANGE_CsRMeltHandle *csrh, struct TALER_ExchangeWithdrawValues alg_values[GNUNET_NZL (alen)]; struct TALER_EXCHANGE_CsRMeltResponse csrr = { .hr = *hr, - .details.success.alg_values_len = alen, - .details.success.alg_values = alg_values + .details.ok.alg_values_len = alen, + .details.ok.alg_values = alg_values }; for (unsigned int i = 0; iexchange_pub), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("transaction_base_url", - &dr.details.success.transaction_base_url), + &dr.details.ok.transaction_base_url), NULL), GNUNET_JSON_spec_timestamp ("exchange_timestamp", &dh->exchange_timestamp), @@ -297,9 +297,9 @@ handle_deposit_finished (void *cls, &auditor_cb, dh); } - dr.details.success.exchange_sig = &dh->exchange_sig; - dr.details.success.exchange_pub = &dh->exchange_pub; - dr.details.success.deposit_timestamp = dh->exchange_timestamp; + dr.details.ok.exchange_sig = &dh->exchange_sig; + dr.details.ok.exchange_pub = &dh->exchange_pub; + dr.details.ok.deposit_timestamp = dh->exchange_timestamp; break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy diff --git a/src/lib/exchange_api_deposits_get.c b/src/lib/exchange_api_deposits_get.c index 2e8a5e5e8..bd5f2f653 100644 --- a/src/lib/exchange_api_deposits_get.c +++ b/src/lib/exchange_api_deposits_get.c @@ -118,15 +118,15 @@ handle_deposit_wtid_finished (void *cls, { struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("wtid", - &dr.details.success.wtid), + &dr.details.ok.wtid), GNUNET_JSON_spec_timestamp ("execution_time", - &dr.details.success.execution_time), + &dr.details.ok.execution_time), TALER_JSON_spec_amount_any ("coin_contribution", - &dr.details.success.coin_contribution), + &dr.details.ok.coin_contribution), GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &dr.details.success.exchange_sig), + &dr.details.ok.exchange_sig), GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &dr.details.success.exchange_pub), + &dr.details.ok.exchange_pub), GNUNET_JSON_spec_end () }; const struct TALER_EXCHANGE_Keys *key_state; @@ -145,7 +145,7 @@ handle_deposit_wtid_finished (void *cls, } if (GNUNET_OK != TALER_EXCHANGE_test_signing_key (key_state, - &dr.details.success.exchange_pub)) + &dr.details.ok.exchange_pub)) { GNUNET_break_op (0); dr.hr.http_status = 0; @@ -156,12 +156,12 @@ handle_deposit_wtid_finished (void *cls, TALER_exchange_online_confirm_wire_verify ( &dwh->h_wire, &dwh->h_contract_terms, - &dr.details.success.wtid, + &dr.details.ok.wtid, &dwh->coin_pub, - dr.details.success.execution_time, - &dr.details.success.coin_contribution, - &dr.details.success.exchange_pub, - &dr.details.success.exchange_sig)) + dr.details.ok.execution_time, + &dr.details.ok.coin_contribution, + &dr.details.ok.exchange_pub, + &dr.details.ok.exchange_sig)) { GNUNET_break_op (0); dr.hr.http_status = 0; diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index 8501ed11d..f6a5e979d 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -40,7 +40,7 @@ * Which version of the Taler protocol is implemented * by this library? Used to determine compatibility. */ -#define EXCHANGE_PROTOCOL_CURRENT 14 +#define EXCHANGE_PROTOCOL_CURRENT 15 /** * How many versions are we backwards compatible with? @@ -1305,15 +1305,18 @@ keys_completed_cb (void *cls, { struct KeysRequest *kr = cls; struct TALER_EXCHANGE_Handle *exchange = kr->exchange; - struct TALER_EXCHANGE_Keys kd; struct TALER_EXCHANGE_Keys kd_old; - enum TALER_EXCHANGE_VersionCompatibility vc; const json_t *j = resp_obj; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code + struct TALER_EXCHANGE_Keys kd; + struct TALER_EXCHANGE_KeysResponse kresp = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code, + .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR, }; + memset (&kd, + 0, + sizeof (kd)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received keys from URL `%s' with status %ld and expiration %s.\n", kr->url, @@ -1330,10 +1333,6 @@ keys_completed_cb (void *cls, GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION)); } kd_old = exchange->key_data; - memset (&kd, - 0, - sizeof (struct TALER_EXCHANGE_Keys)); - vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR; switch (response_code) { case 0: @@ -1358,8 +1357,10 @@ keys_completed_cb (void *cls, } /* We keep the denomination keys and auditor signatures from the previous iteration (/keys cherry picking) */ - kd.num_denom_keys = kd_old.num_denom_keys; - kd.last_denom_issue_date = kd_old.last_denom_issue_date; + kd.num_denom_keys + = kd_old.num_denom_keys; + kd.last_denom_issue_date + = kd_old.last_denom_issue_date; GNUNET_array_grow (kd.denom_keys, kd.denom_keys_size, kd.num_denom_keys); @@ -1401,11 +1402,11 @@ keys_completed_cb (void *cls, decode_keys_json (j, true, &kd, - &vc)) + &kresp.details.ok.compat)) { TALER_LOG_ERROR ("Could not decode /keys response\n"); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + kresp.hr.http_status = 0; + kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; for (unsigned int i = 0; ikeys_error_count++; if (NULL == j) { - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = TALER_ErrorCode_get_hint (hr.ec); + kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec); } else { - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + kresp.hr.ec = TALER_JSON_get_error_code (j); + kresp.hr.hint = TALER_JSON_get_error_hint (j); } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); + (int) kresp.hr.ec); break; } exchange->key_data = kd; @@ -1491,9 +1492,7 @@ keys_completed_cb (void *cls, free_key_data (&kd_old); /* notify application that we failed */ exchange->cert_cb (exchange->cert_cb_cls, - &hr, - NULL, - vc); + &kresp); return; } @@ -1504,11 +1503,11 @@ keys_completed_cb (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Successfully downloaded exchange's keys\n"); update_auditors (exchange); + kresp.details.ok.keys = &exchange->key_data; + /* notify application about the key information */ exchange->cert_cb (exchange->cert_cb_cls, - &hr, - &exchange->key_data, - vc); + &kresp); free_key_data (&kd_old); } @@ -1702,7 +1701,6 @@ static void deserialize_data (struct TALER_EXCHANGE_Handle *exchange, const json_t *data) { - enum TALER_EXCHANGE_VersionCompatibility vc; json_t *keys; const char *url; uint32_t version; @@ -1719,10 +1717,11 @@ deserialize_data (struct TALER_EXCHANGE_Handle *exchange, GNUNET_JSON_spec_end () }; struct TALER_EXCHANGE_Keys key_data; - struct TALER_EXCHANGE_HttpResponse hr = { - .ec = TALER_EC_NONE, - .http_status = MHD_HTTP_OK, - .reply = data + struct TALER_EXCHANGE_KeysResponse kresp = { + .hr.ec = TALER_EC_NONE, + .hr.http_status = MHD_HTTP_OK, + .hr.reply = data, + .details.ok.keys = &exchange->key_data }; if (NULL == data) @@ -1754,7 +1753,7 @@ deserialize_data (struct TALER_EXCHANGE_Handle *exchange, decode_keys_json (keys, false, &key_data, - &vc)) + &kresp.details.ok.compat)) { GNUNET_break (0); GNUNET_JSON_parse_free (spec); @@ -1771,9 +1770,7 @@ deserialize_data (struct TALER_EXCHANGE_Handle *exchange, update_auditors (exchange); /* notify application about the key information */ exchange->cert_cb (exchange->cert_cb_cls, - &hr, - &exchange->key_data, - vc); + &kresp); GNUNET_JSON_parse_free (spec); } @@ -2056,17 +2053,18 @@ request_keys (void *cls) url); if (NULL == kr->url) { - struct TALER_EXCHANGE_HttpResponse hr = { - .ec = TALER_EC_GENERIC_CONFIGURATION_INVALID + struct TALER_EXCHANGE_KeysResponse kresp = { + .hr.ec = TALER_EC_GENERIC_CONFIGURATION_INVALID, + /* Next line is technically unnecessary, as the + http status we set is 0 */ + .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR }; GNUNET_free (kr); exchange->keys_error_count++; exchange->state = MHS_FAILED; exchange->cert_cb (exchange->cert_cb_cls, - &hr, - NULL, - TALER_EXCHANGE_VC_PROTOCOL_ERROR); + &kresp); return; } diff --git a/src/lib/exchange_api_kyc_check.c b/src/lib/exchange_api_kyc_check.c index 2f03730a8..d1580e76c 100644 --- a/src/lib/exchange_api_kyc_check.c +++ b/src/lib/exchange_api_kyc_check.c @@ -100,11 +100,11 @@ handle_kyc_check_finished (void *cls, uint32_t status; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &ks.details.success.exchange_sig), + &ks.details.ok.exchange_sig), GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &ks.details.success.exchange_pub), + &ks.details.ok.exchange_pub), GNUNET_JSON_spec_timestamp ("now", - &ks.details.success.timestamp), + &ks.details.ok.timestamp), GNUNET_JSON_spec_json ("kyc_details", &kyc_details), GNUNET_JSON_spec_uint32 ("aml_status", @@ -123,13 +123,13 @@ handle_kyc_check_finished (void *cls, ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } - ks.details.success.kyc_details = kyc_details; - ks.details.success.aml_status + ks.details.ok.kyc_details = kyc_details; + ks.details.ok.aml_status = (enum TALER_AmlDecisionState) status; key_state = TALER_EXCHANGE_get_keys (kch->exchange); if (GNUNET_OK != TALER_EXCHANGE_test_signing_key (key_state, - &ks.details.success.exchange_pub)) + &ks.details.ok.exchange_pub)) { GNUNET_break_op (0); ks.http_status = 0; @@ -141,10 +141,10 @@ handle_kyc_check_finished (void *cls, if (GNUNET_OK != TALER_exchange_online_account_setup_success_verify ( &kch->h_payto, - ks.details.success.kyc_details, - ks.details.success.timestamp, - &ks.details.success.exchange_pub, - &ks.details.success.exchange_sig)) + ks.details.ok.kyc_details, + ks.details.ok.timestamp, + &ks.details.ok.exchange_pub, + &ks.details.ok.exchange_sig)) { GNUNET_break_op (0); ks.http_status = 0; diff --git a/src/lib/exchange_api_link.c b/src/lib/exchange_api_link.c index 9e8625ed5..3b998f23a 100644 --- a/src/lib/exchange_api_link.c +++ b/src/lib/exchange_api_link.c @@ -365,8 +365,8 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh, if (off_coin == num_coins) { - lr.details.success.num_coins = num_coins; - lr.details.success.coins = lcis; + lr.details.ok.num_coins = num_coins; + lr.details.ok.coins = lcis; lh->link_cb (lh->link_cb_cls, &lr); lh->link_cb = NULL; diff --git a/src/lib/exchange_api_lookup_aml_decision.c b/src/lib/exchange_api_lookup_aml_decision.c index 897a21f10..fc1a8a8e4 100644 --- a/src/lib/exchange_api_lookup_aml_decision.c +++ b/src/lib/exchange_api_lookup_aml_decision.c @@ -190,17 +190,17 @@ parse_decision_ok (struct TALER_EXCHANGE_LookupAmlDecision *lh, GNUNET_break_op (0); return GNUNET_SYSERR; } - lr.details.success.aml_history_length = json_array_size (aml_history); - lr.details.success.kyc_attributes_length = json_array_size (kyc_attributes); + lr.details.ok.aml_history_length = json_array_size (aml_history); + lr.details.ok.kyc_attributes_length = json_array_size (kyc_attributes); { struct TALER_EXCHANGE_AmlDecisionDetail aml_history_ar[ - GNUNET_NZL (lr.details.success.aml_history_length)]; + GNUNET_NZL (lr.details.ok.aml_history_length)]; struct TALER_EXCHANGE_KycHistoryDetail kyc_attributes_ar[ - lr.details.success.kyc_attributes_length]; + lr.details.ok.kyc_attributes_length]; enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; - lr.details.success.aml_history = aml_history_ar; - lr.details.success.kyc_attributes = kyc_attributes_ar; + lr.details.ok.aml_history = aml_history_ar; + lr.details.ok.kyc_attributes = kyc_attributes_ar; ret = parse_aml_history (aml_history, aml_history_ar); if (GNUNET_OK == ret) diff --git a/src/lib/exchange_api_lookup_aml_decisions.c b/src/lib/exchange_api_lookup_aml_decisions.c index f8a1d7fa3..403acb54a 100644 --- a/src/lib/exchange_api_lookup_aml_decisions.c +++ b/src/lib/exchange_api_lookup_aml_decisions.c @@ -139,13 +139,13 @@ parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh, GNUNET_break_op (0); return GNUNET_SYSERR; } - lr.details.success.decisions_length = json_array_size (records); + lr.details.ok.decisions_length = json_array_size (records); { struct TALER_EXCHANGE_AmlDecisionSummary decisions[ - GNUNET_NZL (lr.details.success.decisions_length)]; + GNUNET_NZL (lr.details.ok.decisions_length)]; enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; - lr.details.success.decisions = decisions; + lr.details.ok.decisions = decisions; ret = parse_aml_decisions (records, decisions); if (GNUNET_OK == ret) diff --git a/src/lib/exchange_api_management_drain_profits.c b/src/lib/exchange_api_management_drain_profits.c index 9cf1af85e..bc7232b87 100644 --- a/src/lib/exchange_api_management_drain_profits.c +++ b/src/lib/exchange_api_management_drain_profits.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020-2022 Taler Systems SA + Copyright (C) 2020-2023 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 @@ -79,9 +79,9 @@ handle_drain_profits_finished (void *cls, { struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp = cls; const json_t *json = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_EXCHANGE_ManagementDrainResponse dr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; dp->job = NULL; @@ -90,32 +90,32 @@ handle_drain_profits_finished (void *cls, case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + dr.hr.ec = TALER_JSON_get_error_code (json); + dr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + dr.hr.ec = TALER_JSON_get_error_code (json); + dr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_PRECONDITION_FAILED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + dr.hr.ec = TALER_JSON_get_error_code (json); + dr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + dr.hr.ec = TALER_JSON_get_error_code (json); + dr.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange management drain profits\n", (unsigned int) response_code, - (int) hr.ec); + (int) dr.hr.ec); break; } if (NULL != dp->cb) { dp->cb (dp->cb_cls, - &hr); + &dr); dp->cb = NULL; } TALER_EXCHANGE_management_drain_profits_cancel (dp); diff --git a/src/lib/exchange_api_management_get_keys.c b/src/lib/exchange_api_management_get_keys.c index 80c471441..c649f397f 100644 --- a/src/lib/exchange_api_management_get_keys.c +++ b/src/lib/exchange_api_management_get_keys.c @@ -75,11 +75,16 @@ struct TALER_EXCHANGE_ManagementGetKeysHandle * @param response the response * @return #GNUNET_OK if the response was well-formed */ -static int +static enum GNUNET_GenericReturnValue handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh, const json_t *response) { - struct TALER_EXCHANGE_FutureKeys fk; + struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = { + .hr.http_status = MHD_HTTP_OK, + .hr.reply = response, + }; + struct TALER_EXCHANGE_FutureKeys *fk + = &gkr.details.ok.keys; json_t *sk; json_t *dk; bool ok; @@ -89,13 +94,13 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh, GNUNET_JSON_spec_json ("future_signkeys", &sk), GNUNET_JSON_spec_fixed_auto ("master_pub", - &fk.master_pub), + &fk->master_pub), GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key", - &fk.denom_secmod_public_key), + &fk->denom_secmod_public_key), GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key", - &fk.denom_secmod_cs_public_key), + &fk->denom_secmod_cs_public_key), GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key", - &fk.signkey_secmod_public_key), + &fk->signkey_secmod_public_key), GNUNET_JSON_spec_end () }; @@ -107,21 +112,21 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh, GNUNET_break_op (0); return GNUNET_SYSERR; } - fk.num_sign_keys = json_array_size (sk); - fk.num_denom_keys = json_array_size (dk); - fk.sign_keys = GNUNET_new_array ( - fk.num_sign_keys, + fk->num_sign_keys = json_array_size (sk); + fk->num_denom_keys = json_array_size (dk); + fk->sign_keys = GNUNET_new_array ( + fk->num_sign_keys, struct TALER_EXCHANGE_FutureSigningPublicKey); - fk.denom_keys = GNUNET_new_array ( - fk.num_denom_keys, + fk->denom_keys = GNUNET_new_array ( + fk->num_denom_keys, struct TALER_EXCHANGE_FutureDenomPublicKey); ok = true; - for (unsigned int i = 0; inum_sign_keys; i++) { json_t *j = json_array_get (sk, i); struct TALER_EXCHANGE_FutureSigningPublicKey *sign_key - = &fk.sign_keys[i]; + = &fk->sign_keys[i]; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("key", &sign_key->key), @@ -155,7 +160,7 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh, &sign_key->key, sign_key->valid_from, duration, - &fk.signkey_secmod_public_key, + &fk->signkey_secmod_public_key, &sign_key->signkey_secmod_sig)) { GNUNET_break_op (0); @@ -164,12 +169,12 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh, } } } - for (unsigned int i = 0; inum_denom_keys; i++) { json_t *j = json_array_get (dk, i); struct TALER_EXCHANGE_FutureDenomPublicKey *denom_key - = &fk.denom_keys[i]; + = &fk->denom_keys[i]; const char *section_name; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_amount_any ("value", @@ -236,7 +241,7 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh, section_name, denom_key->valid_from, duration, - &fk.denom_secmod_public_key, + &fk->denom_secmod_public_key, &denom_key->denom_secmod_sig)) { GNUNET_break_op (0); @@ -256,7 +261,7 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh, section_name, denom_key->valid_from, duration, - &fk.denom_secmod_cs_public_key, + &fk->denom_secmod_cs_public_key, &denom_key->denom_secmod_sig)) { GNUNET_break_op (0); @@ -277,19 +282,13 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh, } if (ok) { - struct TALER_EXCHANGE_HttpResponse hr = { - .http_status = MHD_HTTP_OK, - .reply = response - }; - gh->cb (gh->cb_cls, - &hr, - &fk); + &gkr); } - for (unsigned int i = 0; inum_denom_keys; i++) + TALER_denom_pub_free (&fk->denom_keys[i].key); + GNUNET_free (fk->sign_keys); + GNUNET_free (fk->denom_keys); GNUNET_JSON_parse_free (spec); return (ok) ? GNUNET_OK : GNUNET_SYSERR; } @@ -310,9 +309,9 @@ handle_get_keys_finished (void *cls, { struct TALER_EXCHANGE_ManagementGetKeysHandle *gh = cls; const json_t *json = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; gh->job = NULL; @@ -334,25 +333,24 @@ handle_get_keys_finished (void *cls, /* unexpected response code */ if (NULL != json) { - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + gkr.hr.ec = TALER_JSON_get_error_code (json); + gkr.hr.hint = TALER_JSON_get_error_hint (json); } else { - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = TALER_ErrorCode_get_hint (hr.ec); + gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec); } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange management get keys\n", (unsigned int) response_code, - (int) hr.ec); + (int) gkr.hr.ec); break; } if (NULL != gh->cb) { gh->cb (gh->cb_cls, - &hr, - NULL); + &gkr); gh->cb = NULL; } TALER_EXCHANGE_get_management_keys_cancel (gh); diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c index abec4ef09..b9721a982 100644 --- a/src/lib/exchange_api_management_post_extensions.c +++ b/src/lib/exchange_api_management_post_extensions.c @@ -83,9 +83,9 @@ handle_post_extensions_finished (void *cls, { struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph = cls; const json_t *json = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_EXCHANGE_ManagementPostExtensionsResponse per = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; ph->job = NULL; @@ -94,28 +94,28 @@ handle_post_extensions_finished (void *cls, case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + per.hr.ec = TALER_JSON_get_error_code (json); + per.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + per.hr.ec = TALER_JSON_get_error_code (json); + per.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + per.hr.ec = TALER_JSON_get_error_code (json); + per.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange management post extensions\n", (unsigned int) response_code, - (int) hr.ec); + (int) per.hr.ec); break; } if (NULL != ph->cb) { ph->cb (ph->cb_cls, - &hr); + &per); ph->cb = NULL; } TALER_EXCHANGE_management_post_extensions_cancel (ph); diff --git a/src/lib/exchange_api_management_post_keys.c b/src/lib/exchange_api_management_post_keys.c index 291c0ac02..a46124d90 100644 --- a/src/lib/exchange_api_management_post_keys.c +++ b/src/lib/exchange_api_management_post_keys.c @@ -82,9 +82,9 @@ handle_post_keys_finished (void *cls, { struct TALER_EXCHANGE_ManagementPostKeysHandle *ph = cls; const json_t *json = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_EXCHANGE_ManagementPostKeysResponse pkr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; ph->job = NULL; @@ -93,32 +93,32 @@ handle_post_keys_finished (void *cls, case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pkr.hr.ec = TALER_JSON_get_error_code (json); + pkr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pkr.hr.ec = TALER_JSON_get_error_code (json); + pkr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_REQUEST_ENTITY_TOO_LARGE: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pkr.hr.ec = TALER_JSON_get_error_code (json); + pkr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pkr.hr.ec = TALER_JSON_get_error_code (json); + pkr.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange management post keys\n", (unsigned int) response_code, - (int) hr.ec); + (int) pkr.hr.ec); break; } if (NULL != ph->cb) { ph->cb (ph->cb_cls, - &hr); + &pkr); ph->cb = NULL; } TALER_EXCHANGE_post_management_keys_cancel (ph); diff --git a/src/lib/exchange_api_management_revoke_denomination_key.c b/src/lib/exchange_api_management_revoke_denomination_key.c index 8d65b6451..aa4d527a7 100644 --- a/src/lib/exchange_api_management_revoke_denomination_key.c +++ b/src/lib/exchange_api_management_revoke_denomination_key.c @@ -82,9 +82,9 @@ handle_revoke_denomination_finished (void *cls, { struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh = cls; const json_t *json = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_EXCHANGE_ManagementRevokeDenominationResponse rdr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; rh->job = NULL; @@ -92,30 +92,30 @@ handle_revoke_denomination_finished (void *cls, { case 0: /* no reply */ - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = "server offline?"; + rdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + rdr.hr.hint = "server offline?"; break; case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + rdr.hr.ec = TALER_JSON_get_error_code (json); + rdr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + rdr.hr.ec = TALER_JSON_get_error_code (json); + rdr.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange management revoke denomination\n", (unsigned int) response_code, - (int) hr.ec); + (int) rdr.hr.ec); break; } if (NULL != rh->cb) { rh->cb (rh->cb_cls, - &hr); + &rdr); rh->cb = NULL; } TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh); diff --git a/src/lib/exchange_api_management_revoke_signing_key.c b/src/lib/exchange_api_management_revoke_signing_key.c index aac27678f..c4d634248 100644 --- a/src/lib/exchange_api_management_revoke_signing_key.c +++ b/src/lib/exchange_api_management_revoke_signing_key.c @@ -79,9 +79,9 @@ handle_revoke_signing_finished (void *cls, { struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh = cls; const json_t *json = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse rsr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; rh->job = NULL; @@ -89,30 +89,30 @@ handle_revoke_signing_finished (void *cls, { case 0: /* no reply */ - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = "server offline?"; + rsr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + rsr.hr.hint = "server offline?"; break; case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + rsr.hr.ec = TALER_JSON_get_error_code (json); + rsr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + rsr.hr.ec = TALER_JSON_get_error_code (json); + rsr.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange management revoke signkey\n", (unsigned int) response_code, - (int) hr.ec); + (int) rsr.hr.ec); break; } if (NULL != rh->cb) { rh->cb (rh->cb_cls, - &hr); + &rsr); rh->cb = NULL; } TALER_EXCHANGE_management_revoke_signing_key_cancel (rh); diff --git a/src/lib/exchange_api_management_update_aml_officer.c b/src/lib/exchange_api_management_update_aml_officer.c index 6e1669465..0033a1308 100644 --- a/src/lib/exchange_api_management_update_aml_officer.c +++ b/src/lib/exchange_api_management_update_aml_officer.c @@ -79,9 +79,9 @@ handle_update_aml_officer_finished (void *cls, { struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh = cls; const json_t *json = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse uar = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; wh->job = NULL; @@ -89,34 +89,34 @@ handle_update_aml_officer_finished (void *cls, { case 0: /* no reply */ - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = "server offline?"; + uar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + uar.hr.hint = "server offline?"; break; case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + uar.hr.ec = TALER_JSON_get_error_code (json); + uar.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + uar.hr.ec = TALER_JSON_get_error_code (json); + uar.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + uar.hr.ec = TALER_JSON_get_error_code (json); + uar.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange management update AML officer\n", (unsigned int) response_code, - (int) hr.ec); + (int) uar.hr.ec); break; } if (NULL != wh->cb) { wh->cb (wh->cb_cls, - &hr); + &uar); wh->cb = NULL; } TALER_EXCHANGE_management_update_aml_officer_cancel (wh); diff --git a/src/lib/exchange_api_management_wire_disable.c b/src/lib/exchange_api_management_wire_disable.c index 5d97eef75..30a010762 100644 --- a/src/lib/exchange_api_management_wire_disable.c +++ b/src/lib/exchange_api_management_wire_disable.c @@ -79,9 +79,9 @@ handle_auditor_disable_finished (void *cls, { struct TALER_EXCHANGE_ManagementWireDisableHandle *wh = cls; const json_t *json = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_EXCHANGE_ManagementWireDisableResponse wdr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; wh->job = NULL; @@ -89,38 +89,38 @@ handle_auditor_disable_finished (void *cls, { case 0: /* no reply */ - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = "server offline?"; + wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + wdr.hr.hint = "server offline?"; break; case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + wdr.hr.ec = TALER_JSON_get_error_code (json); + wdr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + wdr.hr.ec = TALER_JSON_get_error_code (json); + wdr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + wdr.hr.ec = TALER_JSON_get_error_code (json); + wdr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + wdr.hr.ec = TALER_JSON_get_error_code (json); + wdr.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d exchange management disable wire\n", (unsigned int) response_code, - (int) hr.ec); + (int) wdr.hr.ec); break; } if (NULL != wh->cb) { wh->cb (wh->cb_cls, - &hr); + &wdr); wh->cb = NULL; } TALER_EXCHANGE_management_disable_wire_cancel (wh); diff --git a/src/lib/exchange_api_management_wire_enable.c b/src/lib/exchange_api_management_wire_enable.c index 55480474e..23a98b153 100644 --- a/src/lib/exchange_api_management_wire_enable.c +++ b/src/lib/exchange_api_management_wire_enable.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015-2021 Taler Systems SA + Copyright (C) 2015-2023 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 @@ -79,9 +79,9 @@ handle_auditor_enable_finished (void *cls, { struct TALER_EXCHANGE_ManagementWireEnableHandle *wh = cls; const json_t *json = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_EXCHANGE_ManagementWireEnableResponse wer = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; wh->job = NULL; @@ -89,34 +89,34 @@ handle_auditor_enable_finished (void *cls, { case 0: /* no reply */ - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = "server offline?"; + wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + wer.hr.hint = "server offline?"; break; case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + wer.hr.ec = TALER_JSON_get_error_code (json); + wer.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + wer.hr.ec = TALER_JSON_get_error_code (json); + wer.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + wer.hr.ec = TALER_JSON_get_error_code (json); + wer.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange management enable wire\n", (unsigned int) response_code, - (int) hr.ec); + (int) wer.hr.ec); break; } if (NULL != wh->cb) { wh->cb (wh->cb_cls, - &hr); + &wer); wh->cb = NULL; } TALER_EXCHANGE_management_enable_wire_cancel (wh); @@ -128,6 +128,9 @@ TALER_EXCHANGE_management_enable_wire ( struct GNUNET_CURL_Context *ctx, const char *url, const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, struct GNUNET_TIME_Timestamp validity_start, const struct TALER_MasterSignatureP *master_sig1, const struct TALER_MasterSignatureP *master_sig2, @@ -167,6 +170,13 @@ TALER_EXCHANGE_management_enable_wire ( body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("payto_uri", payto_uri), + GNUNET_JSON_pack_array_incref ("debit_restrictions", + (json_t *) debit_restrictions), + GNUNET_JSON_pack_array_incref ("credit_restrictions", + (json_t *) credit_restrictions), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + conversion_url)), GNUNET_JSON_pack_data_auto ("master_sig_add", master_sig1), GNUNET_JSON_pack_data_auto ("master_sig_wire", diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c index feeef4001..3a8144a3c 100644 --- a/src/lib/exchange_api_melt.c +++ b/src/lib/exchange_api_melt.c @@ -221,16 +221,16 @@ handle_melt_finished (void *cls, if (GNUNET_OK != verify_melt_signature_ok (mh, j, - &mr.details.success.sign_key)) + &mr.details.ok.sign_key)) { GNUNET_break_op (0); mr.hr.http_status = 0; mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE; break; } - mr.details.success.noreveal_index = mh->noreveal_index; - mr.details.success.num_mbds = mh->rd->fresh_pks_len; - mr.details.success.mbds = mh->mbds; + mr.details.ok.noreveal_index = mh->noreveal_index; + mr.details.ok.num_mbds = mh->rd->fresh_pks_len; + mr.details.ok.mbds = mh->mbds; mh->melt_cb (mh->melt_cb_cls, &mr); mh->melt_cb = NULL; @@ -478,7 +478,7 @@ csr_cb (void *cls, break; case TALER_DENOMINATION_CS: GNUNET_assert (TALER_DENOMINATION_CS == wv->cipher); - *wv = csrr->details.success.alg_values[nks_off]; + *wv = csrr->details.ok.alg_values[nks_off]; nks_off++; break; } diff --git a/src/lib/exchange_api_purse_deposit.c b/src/lib/exchange_api_purse_deposit.c index f5a503b0b..f88d23299 100644 --- a/src/lib/exchange_api_purse_deposit.c +++ b/src/lib/exchange_api_purse_deposit.c @@ -164,17 +164,17 @@ handle_purse_deposit_finished (void *cls, GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub), GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &dr.details.success.h_contract_terms), + &dr.details.ok.h_contract_terms), GNUNET_JSON_spec_timestamp ("exchange_timestamp", &etime), GNUNET_JSON_spec_timestamp ("purse_expiration", - &dr.details.success.purse_expiration), + &dr.details.ok.purse_expiration), TALER_JSON_spec_amount ("total_deposited", keys->currency, - &dr.details.success.total_deposited), + &dr.details.ok.total_deposited), TALER_JSON_spec_amount ("purse_value_after_fees", keys->currency, - &dr.details.success.purse_value_after_fees), + &dr.details.ok.purse_value_after_fees), GNUNET_JSON_spec_end () }; @@ -200,11 +200,11 @@ handle_purse_deposit_finished (void *cls, if (GNUNET_OK != TALER_exchange_online_purse_created_verify ( etime, - dr.details.success.purse_expiration, - &dr.details.success.purse_value_after_fees, - &dr.details.success.total_deposited, + dr.details.ok.purse_expiration, + &dr.details.ok.purse_value_after_fees, + &dr.details.ok.total_deposited, &pch->purse_pub, - &dr.details.success.h_contract_terms, + &dr.details.ok.h_contract_terms, &exchange_pub, &exchange_sig)) { diff --git a/src/lib/exchange_api_purse_merge.c b/src/lib/exchange_api_purse_merge.c index fd11978d9..6e1995e0d 100644 --- a/src/lib/exchange_api_purse_merge.c +++ b/src/lib/exchange_api_purse_merge.c @@ -152,11 +152,11 @@ handle_purse_merge_finished (void *cls, struct TALER_Amount total_deposited; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &dr.details.success.exchange_sig), + &dr.details.ok.exchange_sig), GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &dr.details.success.exchange_pub), + &dr.details.ok.exchange_pub), GNUNET_JSON_spec_timestamp ("exchange_timestamp", - &dr.details.success.etime), + &dr.details.ok.etime), TALER_JSON_spec_amount ("merge_amount", pch->purse_value_after_fees.currency, &total_deposited), @@ -176,7 +176,7 @@ handle_purse_merge_finished (void *cls, key_state = TALER_EXCHANGE_get_keys (pch->exchange); if (GNUNET_OK != TALER_EXCHANGE_test_signing_key (key_state, - &dr.details.success.exchange_pub)) + &dr.details.ok.exchange_pub)) { GNUNET_break_op (0); dr.hr.http_status = 0; @@ -185,15 +185,15 @@ handle_purse_merge_finished (void *cls, } if (GNUNET_OK != TALER_exchange_online_purse_merged_verify ( - dr.details.success.etime, + dr.details.ok.etime, pch->purse_expiration, &pch->purse_value_after_fees, &pch->purse_pub, &pch->h_contract_terms, &pch->reserve_pub, pch->provider_url, - &dr.details.success.exchange_pub, - &dr.details.success.exchange_sig)) + &dr.details.ok.exchange_pub, + &dr.details.ok.exchange_sig)) { GNUNET_break_op (0); dr.hr.http_status = 0; diff --git a/src/lib/exchange_api_purses_get.c b/src/lib/exchange_api_purses_get.c index 5d63ecf13..4c2fdd79c 100644 --- a/src/lib/exchange_api_purses_get.c +++ b/src/lib/exchange_api_purses_get.c @@ -101,16 +101,16 @@ handle_purse_get_finished (void *cls, struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_timestamp ("merge_timestamp", - &dr.details.success.merge_timestamp), + &dr.details.ok.merge_timestamp), &no_merge), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_timestamp ("deposit_timestamp", - &dr.details.success.deposit_timestamp), + &dr.details.ok.deposit_timestamp), &no_deposit), TALER_JSON_spec_amount_any ("balance", - &dr.details.success.balance), + &dr.details.ok.balance), GNUNET_JSON_spec_timestamp ("purse_expiration", - &dr.details.success.purse_expiration), + &dr.details.ok.purse_expiration), GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub), GNUNET_JSON_spec_fixed_auto ("exchange_sig", @@ -142,9 +142,9 @@ handle_purse_get_finished (void *cls, } if (GNUNET_OK != TALER_exchange_online_purse_status_verify ( - dr.details.success.merge_timestamp, - dr.details.success.deposit_timestamp, - &dr.details.success.balance, + dr.details.ok.merge_timestamp, + dr.details.ok.deposit_timestamp, + &dr.details.ok.balance, &exchange_pub, &exchange_sig)) { diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c index 8de4da123..b89bda8b3 100644 --- a/src/lib/exchange_api_recoup.c +++ b/src/lib/exchange_api_recoup.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2017-2022 Taler Systems SA + Copyright (C) 2017-2023 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 @@ -100,16 +100,15 @@ static enum GNUNET_GenericReturnValue process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph, const json_t *json) { - struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_EXCHANGE_RecoupResponse rr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; struct GNUNET_JSON_Specification spec_withdraw[] = { GNUNET_JSON_spec_fixed_auto ("reserve_pub", - &reserve_pub), + &rr.details.ok.reserve_pub), GNUNET_JSON_spec_end () }; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK - }; if (GNUNET_OK != GNUNET_JSON_parse (json, @@ -120,8 +119,7 @@ process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph, return GNUNET_SYSERR; } ph->cb (ph->cb_cls, - &hr, - &reserve_pub); + &rr); return GNUNET_OK; } @@ -141,9 +139,9 @@ handle_recoup_finished (void *cls, { struct TALER_EXCHANGE_RecoupHandle *ph = cls; const json_t *j = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code + struct TALER_EXCHANGE_RecoupResponse rr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code }; const struct TALER_EXCHANGE_Keys *keys; @@ -152,7 +150,7 @@ handle_recoup_finished (void *cls, switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: if (GNUNET_OK != @@ -160,8 +158,8 @@ handle_recoup_finished (void *cls, j)) { GNUNET_break_op (0); - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - hr.http_status = 0; + rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + rr.hr.http_status = 0; break; } TALER_EXCHANGE_recoup_cancel (ph); @@ -169,22 +167,22 @@ handle_recoup_finished (void *cls, case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_CONFLICT: { struct TALER_Amount min_key; - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); if (GNUNET_OK != TALER_EXCHANGE_get_min_denomination_ (keys, &min_key)) { GNUNET_break (0); - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - hr.http_status = 0; + rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + rr.hr.http_status = 0; break; } if (GNUNET_OK != @@ -197,8 +195,8 @@ handle_recoup_finished (void *cls, &min_key)) { GNUNET_break (0); - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - hr.http_status = 0; + rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + rr.hr.http_status = 0; break; } break; @@ -207,41 +205,40 @@ handle_recoup_finished (void *cls, /* Nothing really to verify, exchange says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_GONE: /* Kind of normal: the money was already sent to the merchant (it was too late for the refund). */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; default: /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange recoup\n", (unsigned int) response_code, - (int) hr.ec); + (int) rr.hr.ec); GNUNET_break (0); break; } ph->cb (ph->cb_cls, - &hr, - NULL); + &rr); TALER_EXCHANGE_recoup_cancel (ph); } diff --git a/src/lib/exchange_api_recoup_refresh.c b/src/lib/exchange_api_recoup_refresh.c index 00eeca070..7b42aa7eb 100644 --- a/src/lib/exchange_api_recoup_refresh.c +++ b/src/lib/exchange_api_recoup_refresh.c @@ -101,16 +101,15 @@ process_recoup_response ( const struct TALER_EXCHANGE_RecoupRefreshHandle *ph, const json_t *json) { - struct TALER_CoinSpendPublicKeyP old_coin_pub; + struct TALER_EXCHANGE_RecoupRefreshResponse rrr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; struct GNUNET_JSON_Specification spec_refresh[] = { GNUNET_JSON_spec_fixed_auto ("old_coin_pub", - &old_coin_pub), + &rrr.details.ok.old_coin_pub), GNUNET_JSON_spec_end () }; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK - }; if (GNUNET_OK != GNUNET_JSON_parse (json, @@ -121,8 +120,7 @@ process_recoup_response ( return GNUNET_SYSERR; } ph->cb (ph->cb_cls, - &hr, - &old_coin_pub); + &rrr); return GNUNET_OK; } @@ -142,9 +140,9 @@ handle_recoup_refresh_finished (void *cls, { struct TALER_EXCHANGE_RecoupRefreshHandle *ph = cls; const json_t *j = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code + struct TALER_EXCHANGE_RecoupRefreshResponse rrr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code }; const struct TALER_EXCHANGE_Keys *keys; @@ -153,7 +151,7 @@ handle_recoup_refresh_finished (void *cls, switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + rrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: if (GNUNET_OK != @@ -161,8 +159,8 @@ handle_recoup_refresh_finished (void *cls, j)) { GNUNET_break_op (0); - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - hr.http_status = 0; + rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + rrr.hr.http_status = 0; break; } TALER_EXCHANGE_recoup_refresh_cancel (ph); @@ -170,35 +168,35 @@ handle_recoup_refresh_finished (void *cls, case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_FORBIDDEN: /* Nothing really to verify, exchange says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_CONFLICT: { struct TALER_Amount min_key; - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); if (GNUNET_OK != TALER_EXCHANGE_get_min_denomination_ (keys, &min_key)) { GNUNET_break (0); - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - hr.http_status = 0; + rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + rrr.hr.http_status = 0; break; } if (GNUNET_OK != @@ -211,8 +209,8 @@ handle_recoup_refresh_finished (void *cls, &min_key)) { GNUNET_break (0); - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - hr.http_status = 0; + rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + rrr.hr.http_status = 0; break; } break; @@ -220,29 +218,28 @@ handle_recoup_refresh_finished (void *cls, case MHD_HTTP_GONE: /* Kind of normal: the money was already sent to the merchant (it was too late for the refund). */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); break; default: /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange recoup\n", (unsigned int) response_code, - (int) hr.ec); + (int) rrr.hr.ec); GNUNET_break (0); break; } ph->cb (ph->cb_cls, - &hr, - NULL); + &rrr); TALER_EXCHANGE_recoup_refresh_cancel (ph); } diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c index cd2a1d1f4..3bdef2020 100644 --- a/src/lib/exchange_api_refreshes_reveal.c +++ b/src/lib/exchange_api_refreshes_reveal.c @@ -266,8 +266,8 @@ handle_refresh_reveal_finished (void *cls, else { GNUNET_assert (rrh->noreveal_index < TALER_CNC_KAPPA); - rr.details.success.num_coins = rrh->md.num_fresh_coins; - rr.details.success.coins = rcis; + rr.details.ok.num_coins = rrh->md.num_fresh_coins; + rr.details.ok.coins = rcis; rrh->reveal_cb (rrh->reveal_cb_cls, &rr); rrh->reveal_cb = NULL; diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c index 855b4fcc7..7c9616ea5 100644 --- a/src/lib/exchange_api_refund.c +++ b/src/lib/exchange_api_refund.c @@ -585,37 +585,28 @@ handle_refund_finished (void *cls, const void *response) { struct TALER_EXCHANGE_RefundHandle *rh = cls; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_ExchangePublicKeyP *ep = NULL; - struct TALER_ExchangeSignatureP *es = NULL; const json_t *j = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code + struct TALER_EXCHANGE_RefundResponse rr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code }; rh->job = NULL; switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: if (GNUNET_OK != verify_refund_signature_ok (rh, j, - &exchange_pub, - &exchange_sig)) + &rr.details.ok.exchange_pub, + &rr.details.ok.exchange_sig)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE; - } - else - { - ep = &exchange_pub; - es = &exchange_sig; + rr.hr.http_status = 0; + rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE; } break; case MHD_HTTP_BAD_REQUEST: @@ -623,21 +614,21 @@ handle_refund_finished (void *cls, (or API version conflict); also can happen if the currency differs (which we should obviously never support). Just pass JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_FORBIDDEN: /* Nothing really to verify, exchange says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_CONFLICT: /* Requested total refunds exceed deposited amount */ @@ -649,19 +640,19 @@ handle_refund_finished (void *cls, json_dumpf (j, stderr, JSON_INDENT (2)); - hr.http_status = 0; - hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE; - hr.hint = "conflict information provided by exchange is invalid"; + rr.hr.http_status = 0; + rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE; + rr.hr.hint = "conflict information provided by exchange is invalid"; break; } - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_GONE: /* Kind of normal: the money was already sent to the merchant (it was too late for the refund). */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_PRECONDITION_FAILED: if (GNUNET_OK != @@ -669,37 +660,35 @@ handle_refund_finished (void *cls, j)) { GNUNET_break (0); - hr.http_status = 0; - hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE; - hr.hint = "failed precondition proof returned by exchange is invalid"; + rr.hr.http_status = 0; + rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE; + rr.hr.hint = "failed precondition proof returned by exchange is invalid"; break; } /* Two different refund requests were made about the same deposit, but carrying identical refund transaction ids. */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange refund\n", (unsigned int) response_code, - hr.ec); + rr.hr.ec); break; } rh->cb (rh->cb_cls, - &hr, - ep, - es); + &rr); TALER_EXCHANGE_refund_cancel (rh); } diff --git a/src/lib/exchange_api_transfers_get.c b/src/lib/exchange_api_transfers_get.c index 06465618c..c2a1c0b15 100644 --- a/src/lib/exchange_api_transfers_get.c +++ b/src/lib/exchange_api_transfers_get.c @@ -85,23 +85,32 @@ check_transfers_get_response_ok ( const json_t *json) { json_t *details_j; - struct TALER_EXCHANGE_TransferData td; struct TALER_Amount total_expected; struct TALER_MerchantPublicKeyP merchant_pub; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("total", &td.total_amount), - TALER_JSON_spec_amount_any ("wire_fee", &td.wire_fee), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), - GNUNET_JSON_spec_fixed_auto ("h_payto", &td.h_payto), - GNUNET_JSON_spec_timestamp ("execution_time", &td.execution_time), - GNUNET_JSON_spec_json ("deposits", &details_j), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", &td.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", &td.exchange_pub), - GNUNET_JSON_spec_end () + struct TALER_EXCHANGE_TransfersGetResponse tgr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK }; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK + struct TALER_EXCHANGE_TransferData *td + = &tgr.details.ok.td; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("total", + &td->total_amount), + TALER_JSON_spec_amount_any ("wire_fee", + &td->wire_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &merchant_pub), + GNUNET_JSON_spec_fixed_auto ("h_payto", + &td->h_payto), + GNUNET_JSON_spec_timestamp ("execution_time", + &td->execution_time), + GNUNET_JSON_spec_json ("deposits", + &details_j), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &td->exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &td->exchange_pub), + GNUNET_JSON_spec_end () }; if (GNUNET_OK != @@ -113,7 +122,7 @@ check_transfers_get_response_ok ( return GNUNET_SYSERR; } if (GNUNET_OK != - TALER_amount_set_zero (td.total_amount.currency, + TALER_amount_set_zero (td->total_amount.currency, &total_expected)) { GNUNET_break_op (0); @@ -123,22 +132,22 @@ check_transfers_get_response_ok ( if (GNUNET_OK != TALER_EXCHANGE_test_signing_key ( TALER_EXCHANGE_get_keys (wdh->exchange), - &td.exchange_pub)) + &td->exchange_pub)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } - td.details_length = json_array_size (details_j); + td->details_length = json_array_size (details_j); { struct GNUNET_HashContext *hash_context; struct TALER_TrackTransferDetails *details; - details = GNUNET_new_array (td.details_length, + details = GNUNET_new_array (td->details_length, struct TALER_TrackTransferDetails); - td.details = details; + td->details = details; hash_context = GNUNET_CRYPTO_hash_context_start (); - for (unsigned int i = 0; idetails_length; i++) { struct TALER_TrackTransferDetails *detail = &details[i]; struct json_t *detail_j = json_array_get (details_j, i); @@ -180,7 +189,7 @@ check_transfers_get_response_ok ( TALER_exchange_online_wire_deposit_append ( hash_context, &detail->h_contract_terms, - td.execution_time, + td->execution_time, &detail->coin_pub, &detail->coin_value, &detail->coin_fee); @@ -193,13 +202,13 @@ check_transfers_get_response_ok ( &h_details); if (GNUNET_OK != TALER_exchange_online_wire_deposit_verify ( - &td.total_amount, - &td.wire_fee, + &td->total_amount, + &td->wire_fee, &merchant_pub, - &td.h_payto, + &td->h_payto, &h_details, - &td.exchange_pub, - &td.exchange_sig)) + &td->exchange_pub, + &td->exchange_sig)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); @@ -211,7 +220,7 @@ check_transfers_get_response_ok ( if (0 > TALER_amount_subtract (&total_expected, &total_expected, - &td.wire_fee)) + &td->wire_fee)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); @@ -220,7 +229,7 @@ check_transfers_get_response_ok ( } if (0 != TALER_amount_cmp (&total_expected, - &td.total_amount)) + &td->total_amount)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); @@ -228,8 +237,7 @@ check_transfers_get_response_ok ( return GNUNET_SYSERR; } wdh->cb (wdh->cb_cls, - &hr, - &td); + &tgr); GNUNET_free (details); } GNUNET_JSON_parse_free (spec); @@ -253,16 +261,16 @@ handle_transfers_get_finished (void *cls, { struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls; const json_t *j = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code + struct TALER_EXCHANGE_TransfersGetResponse tgr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code }; wdh->job = NULL; switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: if (GNUNET_OK == @@ -270,48 +278,47 @@ handle_transfers_get_finished (void *cls, j)) return; GNUNET_break_op (0); - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + tgr.hr.http_status = 0; break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + tgr.hr.ec = TALER_JSON_get_error_code (j); + tgr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_FORBIDDEN: /* Nothing really to verify, exchange says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + tgr.hr.ec = TALER_JSON_get_error_code (j); + tgr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_NOT_FOUND: /* Exchange does not know about transaction; we should pass the reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + tgr.hr.ec = TALER_JSON_get_error_code (j); + tgr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + tgr.hr.ec = TALER_JSON_get_error_code (j); + tgr.hr.hint = TALER_JSON_get_error_hint (j); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + tgr.hr.ec = TALER_JSON_get_error_code (j); + tgr.hr.hint = TALER_JSON_get_error_hint (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for transfers get\n", (unsigned int) response_code, - (int) hr.ec); + (int) tgr.hr.ec); break; } wdh->cb (wdh->cb_cls, - &hr, - NULL); + &tgr); TALER_EXCHANGE_transfers_get_cancel (wdh); } diff --git a/src/lib/exchange_api_wire.c b/src/lib/exchange_api_wire.c index c23ea62d8..498265282 100644 --- a/src/lib/exchange_api_wire.c +++ b/src/lib/exchange_api_wire.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -66,82 +66,65 @@ struct TALER_EXCHANGE_WireHandle /** - * List of wire fees by method. - */ -struct FeeMap -{ - /** - * Next entry in list. - */ - struct FeeMap *next; - - /** - * Wire method this fee structure is for. - */ - char *method; - - /** - * Array of wire fees, also linked list, but allocated - * only once. - */ - struct TALER_EXCHANGE_WireAggregateFees *fee_list; -}; - - -/** - * Frees @a fm. + * Frees @a wfm array. * - * @param fm memory to release + * @param wfm fee array to release + * @param wfm_len length of the @a wfm array */ static void -free_fees (struct FeeMap *fm) +free_fees (struct TALER_EXCHANGE_WireFeesByMethod *wfm, + unsigned int wfm_len) { - while (NULL != fm) + for (unsigned int i = 0; inext; + struct TALER_EXCHANGE_WireFeesByMethod *wfmi = &wfm[i]; - GNUNET_free (fm->fee_list); - GNUNET_free (fm->method); - GNUNET_free (fm); - fm = fe; + while (NULL != wfmi->fees_head) + { + struct TALER_EXCHANGE_WireAggregateFees *fe + = wfmi->fees_head; + + wfmi->fees_head = fe->next; + GNUNET_free (fe); + } } + GNUNET_free (wfm); } /** - * Parse wire @a fees and return map. + * Parse wire @a fees and return array. * * @param master_pub master public key to use to check signatures * @param fees json AggregateTransferFee to parse + * @param[out] fees_len set to length of returned array * @return NULL on error */ -static struct FeeMap * +static struct TALER_EXCHANGE_WireFeesByMethod * parse_fees (const struct TALER_MasterPublicKeyP *master_pub, - json_t *fees) + const json_t *fees, + unsigned int *fees_len) { - struct FeeMap *fm = NULL; + struct TALER_EXCHANGE_WireFeesByMethod *fbm; + unsigned int fbml = json_object_size (fees); + unsigned int i = 0; const char *key; - json_t *fee_array; + const json_t *fee_array; - json_object_foreach (fees, key, fee_array) { - struct FeeMap *fe = GNUNET_new (struct FeeMap); - unsigned int len; + fbm = GNUNET_new_array (fbml, + struct TALER_EXCHANGE_WireFeesByMethod); + *fees_len = fbml; + json_object_foreach ((json_t *) fees, key, fee_array) { + struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++]; unsigned int idx; json_t *fee; - if (0 == (len = json_array_size (fee_array))) - { - GNUNET_free (fe); - continue; /* skip */ - } - fe->method = GNUNET_strdup (key); - fe->next = fm; - fe->fee_list = GNUNET_new_array (len, - struct TALER_EXCHANGE_WireAggregateFees); - fm = fe; + fe->method = key; + fe->fees_head = NULL; json_array_foreach (fee_array, idx, fee) { - struct TALER_EXCHANGE_WireAggregateFees *wa = &fe->fee_list[idx]; + struct TALER_EXCHANGE_WireAggregateFees *wa + = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees); struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("sig", &wa->master_sig), @@ -156,6 +139,8 @@ parse_fees (const struct TALER_MasterPublicKeyP *master_pub, GNUNET_JSON_spec_end () }; + wa->next = fe->fees_head; + fe->fees_head = wa; if (GNUNET_OK != GNUNET_JSON_parse (fee, spec, @@ -163,7 +148,8 @@ parse_fees (const struct TALER_MasterPublicKeyP *master_pub, NULL)) { GNUNET_break_op (0); - free_fees (fm); + free_fees (fbm, + i); return NULL; } if (GNUNET_OK != @@ -176,35 +162,122 @@ parse_fees (const struct TALER_MasterPublicKeyP *master_pub, &wa->master_sig)) { GNUNET_break_op (0); - free_fees (fm); + free_fees (fbm, + i); return NULL; } - if (idx + 1 < len) - wa->next = &fe->fee_list[idx + 1]; - else - wa->next = NULL; - } - } - return fm; + } /* for all fees over time */ + } /* for all methods */ + GNUNET_assert (i == fbml); + return fbm; } /** - * Find fee by @a method. + * Parse account restriction in @a jrest into @a rest. * - * @param fm map to look in - * @param method key to look for - * @return NULL if fee is not specified in @a fm + * @param jrest array of account restrictions in JSON + * @param[out] resta_len set to length of @a resta + * @param[out] resta account restriction array to set + * @return #GNUNET_OK on success */ -static const struct TALER_EXCHANGE_WireAggregateFees * -lookup_fee (const struct FeeMap *fm, - const char *method) +static enum GNUNET_GenericReturnValue +parse_restrictions (const json_t *jresta, + unsigned int *resta_len, + struct TALER_EXCHANGE_AccountRestriction **resta) { - for (; NULL != fm; fm = fm->next) - if (0 == strcasecmp (fm->method, - method)) - return fm->fee_list; - return NULL; + if (! json_is_array (jresta)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *resta_len = json_array_size (jresta); + if (0 == *resta_len) + { + /* no restrictions, perfectly OK */ + *resta = NULL; + return GNUNET_OK; + } + *resta = GNUNET_new_array (*resta_len, + struct TALER_EXCHANGE_AccountRestriction); + for (unsigned int i = 0; i<*resta_len; i++) + { + const json_t *jr = json_array_get (jresta, + i); + struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i]; + const char *type = json_string_value (json_object_get (jr, + "type")); + + if (NULL == type) + { + GNUNET_break (0); + goto fail; + } + if (0 == strcmp (type, + "deny")) + { + ar->type = TALER_EXCHANGE_AR_DENY; + continue; + } + if (0 == strcmp (type, + "regex")) + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ( + "payto_regex", + &ar->details.regex.posix_egrep), + GNUNET_JSON_spec_string ( + "human_hint", + &ar->details.regex.human_hint), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ( + "human_hint_i18n", + &ar->details.regex.human_hint_i18n), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jr, + spec, + NULL, NULL)) + { + /* bogus reply */ + GNUNET_break_op (0); + goto fail; + } + ar->type = TALER_EXCHANGE_AR_REGEX; + continue; + } + /* unsupported type */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +fail: + GNUNET_free (*resta); + *resta_len = 0; + return GNUNET_SYSERR; +} + + +/** + * Free data within @a was, but not @a was itself. + * + * @param was array of wire account data + * @param was_len length of the @a was array + */ +static void +free_accounts (struct TALER_EXCHANGE_WireAccount *was, + unsigned int was_len) +{ + for (unsigned int i = 0; icredit_restrictions); + GNUNET_free (wa->debit_restrictions); + } } @@ -223,9 +296,9 @@ handle_wire_finished (void *cls, { struct TALER_EXCHANGE_WireHandle *wh = cls; const json_t *j = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code + struct TALER_EXCHANGE_WireResponse wr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code }; TALER_LOG_DEBUG ("Checking raw /wire response\n"); @@ -233,28 +306,29 @@ handle_wire_finished (void *cls, switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + wr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; wh->exchange->wire_error_count++; break; case MHD_HTTP_OK: { - json_t *accounts; - json_t *fees; - unsigned int num_accounts; - struct FeeMap *fm; + const json_t *accounts; + const json_t *fees; + const json_t *wads; + struct TALER_EXCHANGE_WireFeesByMethod *fbm; struct TALER_MasterPublicKeyP master_pub; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("master_public_key", &master_pub), - GNUNET_JSON_spec_json ("accounts", - &accounts), - GNUNET_JSON_spec_json ("fees", - &fees), + GNUNET_JSON_spec_array_const ("accounts", + &accounts), + GNUNET_JSON_spec_object_const ("fees", + &fees), + GNUNET_JSON_spec_array_const ("wads", + &wads), GNUNET_JSON_spec_end () }; wh->exchange->wire_error_count = 0; - if (GNUNET_OK != GNUNET_JSON_parse (j, spec, @@ -262,8 +336,8 @@ handle_wire_finished (void *cls, { /* bogus reply */ GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wr.hr.http_status = 0; + wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } { @@ -275,61 +349,70 @@ handle_wire_finished (void *cls, { /* bogus reply: master public key in /wire differs from that in /keys */ GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wr.hr.http_status = 0; + wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } } - if (0 == (num_accounts = json_array_size (accounts))) + wr.details.ok.accounts_len + = json_array_size (accounts); + if (0 == wr.details.ok.accounts_len) { /* bogus reply */ GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wr.hr.http_status = 0; + wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - if (NULL == (fm = parse_fees (&master_pub, - fees))) + fbm = parse_fees (&master_pub, + fees, + &wr.details.ok.fees_len); + if (NULL == fbm) { /* bogus reply */ GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wr.hr.http_status = 0; + wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } /* parse accounts */ { - struct TALER_EXCHANGE_WireAccount was[num_accounts]; + struct TALER_EXCHANGE_WireAccount was[wr.details.ok.accounts_len]; - for (unsigned int i = 0; ipayto_uri), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("conversion_url", + &wa->conversion_url), + NULL), + GNUNET_JSON_spec_json ("credit_restrictions", + &credit_restrictions), + GNUNET_JSON_spec_json ("debit_restrictions", + &debit_restrictions), GNUNET_JSON_spec_fixed_auto ("master_sig", &wa->master_sig), GNUNET_JSON_spec_end () }; - char *method; + json_t *account; account = json_array_get (accounts, i); - if (GNUNET_OK != - TALER_JSON_exchange_wire_signature_check (account, - &master_pub)) - { - /* bogus reply */ - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_EXCHANGE_WIRE_SIGNATURE_INVALID; - break; - } if (GNUNET_OK != GNUNET_JSON_parse (account, spec_account, @@ -337,80 +420,104 @@ handle_wire_finished (void *cls, { /* bogus reply */ GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wr.hr.http_status = 0; + wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - if (NULL == (method = TALER_payto_get_method (wa->payto_uri))) + { + char *err; + + err = TALER_payto_validate (wa->payto_uri); + if (NULL != err) + { + GNUNET_break_op (0); + wr.hr.http_status = 0; + wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + GNUNET_free (err); + break; + } + } + + if (GNUNET_OK != + TALER_exchange_wire_signature_check (wa->payto_uri, + wa->conversion_url, + debit_restrictions, + credit_restrictions, + &master_pub, + &wa->master_sig)) { /* bogus reply */ GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wr.hr.http_status = 0; + wr.hr.ec = TALER_EC_EXCHANGE_WIRE_SIGNATURE_INVALID; break; } - if (NULL == (wa->fees = lookup_fee (fm, - method))) + if ( (GNUNET_OK != + parse_restrictions (credit_restrictions, + &wa->credit_restrictions_length, + &wa->credit_restrictions)) || + (GNUNET_OK != + parse_restrictions (debit_restrictions, + &wa->debit_restrictions_length, + &wa->debit_restrictions)) ) { /* bogus reply */ GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - GNUNET_free (method); + wr.hr.http_status = 0; + wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - GNUNET_free (method); + GNUNET_JSON_parse_free (spec_account); } /* end 'for all accounts */ - if ( (0 != response_code) && + if ( (0 != wr.hr.http_status) && (NULL != wh->cb) ) { wh->cb (wh->cb_cls, - &hr, - num_accounts, - was); + &wr); wh->cb = NULL; } + free_accounts (was, + wr.details.ok.accounts_len); } /* end of 'parse accounts */ - free_fees (fm); + free_fees (fbm, + wr.details.ok.fees_len); GNUNET_JSON_parse_free (spec); } /* end of MHD_HTTP_OK */ break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + wr.hr.ec = TALER_JSON_get_error_code (j); + wr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + wr.hr.ec = TALER_JSON_get_error_code (j); + wr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + wr.hr.ec = TALER_JSON_get_error_code (j); + wr.hr.hint = TALER_JSON_get_error_hint (j); break; default: /* unexpected response code */ if (MHD_HTTP_GATEWAY_TIMEOUT == response_code) wh->exchange->wire_error_count++; GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + wr.hr.ec = TALER_JSON_get_error_code (j); + wr.hr.hint = TALER_JSON_get_error_hint (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange wire\n", (unsigned int) response_code, - (int) hr.ec); + (int) wr.hr.ec); break; } if (NULL != wh->cb) wh->cb (wh->cb_cls, - &hr, - 0, - NULL); + &wr); TALER_EXCHANGE_wire_cancel (wh); } diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c index 62c042039..2a3c095a6 100644 --- a/src/lib/exchange_api_withdraw.c +++ b/src/lib/exchange_api_withdraw.c @@ -116,22 +116,20 @@ struct TALER_EXCHANGE_WithdrawHandle * HTTP /reserves/$RESERVE_PUB/withdraw request. * * @param cls the `struct TALER_EXCHANGE_WithdrawHandle` - * @param hr HTTP response data - * @param blind_sig blind signature over the coin, NULL on error + * @param w2r response data */ static void handle_reserve_withdraw_finished ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_BlindedDenominationSignature *blind_sig) + const struct TALER_EXCHANGE_Withdraw2Response *w2r) { struct TALER_EXCHANGE_WithdrawHandle *wh = cls; struct TALER_EXCHANGE_WithdrawResponse wr = { - .hr = *hr + .hr = w2r->hr }; wh->wh2 = NULL; - switch (hr->http_status) + switch (w2r->hr.http_status) { case MHD_HTTP_OK: { @@ -139,7 +137,7 @@ handle_reserve_withdraw_finished ( if (GNUNET_OK != TALER_planchet_to_coin (&wh->pk.key, - blind_sig, + &w2r->details.ok.blind_sig, &wh->bks, &wh->priv, wh->ach, @@ -151,10 +149,10 @@ handle_reserve_withdraw_finished ( wr.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE; break; } - wr.details.success.coin_priv = wh->priv; - wr.details.success.bks = wh->bks; - wr.details.success.sig = fc.sig; - wr.details.success.exchange_vals = wh->alg_values; + wr.details.ok.coin_priv = wh->priv; + wr.details.ok.bks = wh->bks; + wr.details.ok.sig = fc.sig; + wr.details.ok.exchange_vals = wh->alg_values; break; } case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: @@ -170,7 +168,7 @@ handle_reserve_withdraw_finished ( }; if (GNUNET_OK != - GNUNET_JSON_parse (hr->reply, + GNUNET_JSON_parse (w2r->hr.reply, spec, NULL, NULL)) { @@ -186,8 +184,8 @@ handle_reserve_withdraw_finished ( } wh->cb (wh->cb_cls, &wr); - if (MHD_HTTP_OK == hr->http_status) - TALER_denom_sig_free (&wr.details.success.sig); + if (MHD_HTTP_OK == w2r->hr.http_status) + TALER_denom_sig_free (&wr.details.ok.sig); TALER_EXCHANGE_withdraw_cancel (wh); } @@ -213,7 +211,7 @@ withdraw_cs_stage_two_callback ( switch (csrr->hr.http_status) { case MHD_HTTP_OK: - wh->alg_values = csrr->details.success.alg_values; + wh->alg_values = csrr->details.ok.alg_values; TALER_planchet_setup_coin_priv (&wh->ps, &wh->alg_values, &wh->priv); diff --git a/src/lib/exchange_api_withdraw2.c b/src/lib/exchange_api_withdraw2.c index 04881804a..bf2bd0237 100644 --- a/src/lib/exchange_api_withdraw2.c +++ b/src/lib/exchange_api_withdraw2.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -99,16 +99,15 @@ static enum GNUNET_GenericReturnValue reserve_withdraw_ok (struct TALER_EXCHANGE_Withdraw2Handle *wh, const json_t *json) { - struct TALER_BlindedDenominationSignature blind_sig; + struct TALER_EXCHANGE_Withdraw2Response w2r = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_blinded_denom_sig ("ev_sig", - &blind_sig), + &w2r.details.ok.blind_sig), GNUNET_JSON_spec_end () }; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK - }; if (GNUNET_OK != GNUNET_JSON_parse (json, @@ -121,8 +120,7 @@ reserve_withdraw_ok (struct TALER_EXCHANGE_Withdraw2Handle *wh, /* signature is valid, return it to the application */ wh->cb (wh->cb_cls, - &hr, - &blind_sig); + &w2r); /* make sure callback isn't called again after return */ wh->cb = NULL; GNUNET_JSON_parse_free (spec); @@ -240,16 +238,16 @@ handle_reserve_withdraw_finished (void *cls, { struct TALER_EXCHANGE_Withdraw2Handle *wh = cls; const json_t *j = response; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code + struct TALER_EXCHANGE_Withdraw2Response w2r = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code }; wh->job = NULL; switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + w2r.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: if (GNUNET_OK != @@ -257,8 +255,8 @@ handle_reserve_withdraw_finished (void *cls, j)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + w2r.hr.http_status = 0; + w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } GNUNET_assert (NULL == wh->cb); @@ -267,24 +265,24 @@ handle_reserve_withdraw_finished (void *cls, case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + w2r.hr.ec = TALER_JSON_get_error_code (j); + w2r.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_FORBIDDEN: GNUNET_break_op (0); /* Nothing really to verify, exchange says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + w2r.hr.ec = TALER_JSON_get_error_code (j); + w2r.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, the exchange basically just says that it doesn't know this reserve. Can happen if we query before the wire transfer went through. We should simply pass the JSON reply to the application. */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + w2r.hr.ec = TALER_JSON_get_error_code (j); + w2r.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_CONFLICT: /* The exchange says that the reserve has insufficient funds; @@ -294,13 +292,13 @@ handle_reserve_withdraw_finished (void *cls, j)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + w2r.hr.http_status = 0; + w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; } else { - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + w2r.hr.ec = TALER_JSON_get_error_code (j); + w2r.hr.hint = TALER_JSON_get_error_hint (j); } break; case MHD_HTTP_GONE: @@ -308,8 +306,8 @@ handle_reserve_withdraw_finished (void *cls, /* Note: one might want to check /keys for revocation signature here, alas tricky in case our /keys is outdated => left to clients */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + w2r.hr.ec = TALER_JSON_get_error_code (j); + w2r.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: /* only validate reply is well-formed */ @@ -327,8 +325,8 @@ handle_reserve_withdraw_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + w2r.hr.http_status = 0; + w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } } @@ -336,25 +334,24 @@ handle_reserve_withdraw_finished (void *cls, case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + w2r.hr.ec = TALER_JSON_get_error_code (j); + w2r.hr.hint = TALER_JSON_get_error_hint (j); break; default: /* unexpected response code */ GNUNET_break_op (0); - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + w2r.hr.ec = TALER_JSON_get_error_code (j); + w2r.hr.hint = TALER_JSON_get_error_hint (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d for exchange withdraw\n", (unsigned int) response_code, - (int) hr.ec); + (int) w2r.hr.ec); break; } if (NULL != wh->cb) { wh->cb (wh->cb_cls, - &hr, - NULL); + &w2r); wh->cb = NULL; } TALER_EXCHANGE_withdraw2_cancel (wh); diff --git a/src/testing/testing_api_cmd_bank_history_credit.c b/src/testing/testing_api_cmd_bank_history_credit.c index 9a61d6d53..119c5d86c 100644 --- a/src/testing/testing_api_cmd_bank_history_credit.c +++ b/src/testing/testing_api_cmd_bank_history_credit.c @@ -386,10 +386,10 @@ history_cb (void *cls, GNUNET_break (0); goto error; case MHD_HTTP_OK: - for (unsigned int i = 0; idetails.success.details_length; i++) + for (unsigned int i = 0; idetails.ok.details_length; i++) { const struct TALER_BANK_CreditDetails *cd = - &chr->details.success.details[i]; + &chr->details.ok.details[i]; /* check current element */ if (GNUNET_OK != diff --git a/src/testing/testing_api_cmd_bank_history_debit.c b/src/testing/testing_api_cmd_bank_history_debit.c index 33b212ad0..fd1e81994 100644 --- a/src/testing/testing_api_cmd_bank_history_debit.c +++ b/src/testing/testing_api_cmd_bank_history_debit.c @@ -378,10 +378,10 @@ history_cb (void *cls, GNUNET_break (0); goto error; case MHD_HTTP_OK: - for (unsigned int i = 0; idetails.success.details_length; i++) + for (unsigned int i = 0; idetails.ok.details_length; i++) { const struct TALER_BANK_DebitDetails *dd = - &dhr->details.success.details[i]; + &dhr->details.ok.details[i]; /* check current element */ if (GNUNET_OK != diff --git a/src/testing/testing_api_cmd_batch_deposit.c b/src/testing/testing_api_cmd_batch_deposit.c index 54a20cdb2..d3275875d 100644 --- a/src/testing/testing_api_cmd_batch_deposit.c +++ b/src/testing/testing_api_cmd_batch_deposit.c @@ -212,17 +212,17 @@ batch_deposit_cb (void *cls, } if (MHD_HTTP_OK == dr->hr.http_status) { - if (ds->num_coins != dr->details.success.num_signatures) + if (ds->num_coins != dr->details.ok.num_signatures) { GNUNET_break (0); TALER_TESTING_interpreter_fail (ds->is); return; } ds->deposit_succeeded = GNUNET_YES; - ds->exchange_timestamp = dr->details.success.deposit_timestamp; - ds->exchange_pub = *dr->details.success.exchange_pub; - ds->exchange_sigs = GNUNET_memdup (dr->details.success.exchange_sigs, - dr->details.success.num_signatures + ds->exchange_timestamp = dr->details.ok.deposit_timestamp; + ds->exchange_pub = *dr->details.ok.exchange_pub; + ds->exchange_sigs = GNUNET_memdup (dr->details.ok.exchange_sigs, + dr->details.ok.num_signatures * sizeof (struct TALER_ExchangeSignatureP)); } diff --git a/src/testing/testing_api_cmd_batch_withdraw.c b/src/testing/testing_api_cmd_batch_withdraw.c index a5229ae9c..2aa549c0f 100644 --- a/src/testing/testing_api_cmd_batch_withdraw.c +++ b/src/testing/testing_api_cmd_batch_withdraw.c @@ -210,7 +210,7 @@ reserve_batch_withdraw_cb (void *cls, { struct CoinState *cs = &ws->coins[i]; const struct TALER_EXCHANGE_PrivateCoinDetails *pcd - = &wr->details.success.coins[i]; + = &wr->details.ok.coins[i]; TALER_denom_sig_deep_copy (&cs->sig, &pcd->sig); diff --git a/src/testing/testing_api_cmd_check_aml_decision.c b/src/testing/testing_api_cmd_check_aml_decision.c index d77e9b6b9..dd317142b 100644 --- a/src/testing/testing_api_cmd_check_aml_decision.c +++ b/src/testing/testing_api_cmd_check_aml_decision.c @@ -116,10 +116,10 @@ check_aml_decision_cb (void *cls, GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_amount (ref, &amount)); - for (unsigned int i = 0; idetails.success.aml_history_length; i++) + for (unsigned int i = 0; idetails.ok.aml_history_length; i++) { const struct TALER_EXCHANGE_AmlDecisionDetail *aml_history - = &adr->details.success.aml_history[i]; + = &adr->details.ok.aml_history[i]; if ( (NULL == oldest) || (0 != diff --git a/src/testing/testing_api_cmd_contract_get.c b/src/testing/testing_api_cmd_contract_get.c index 428bf7e8c..d599cb595 100644 --- a/src/testing/testing_api_cmd_contract_get.c +++ b/src/testing/testing_api_cmd_contract_get.c @@ -121,7 +121,7 @@ get_cb (void *cls, const struct TALER_PurseMergePrivateKeyP *mp; const json_t *ct; - ds->purse_pub = dr->details.success.purse_pub; + ds->purse_pub = dr->details.ok.purse_pub; if (ds->merge) { if (GNUNET_OK != @@ -136,8 +136,8 @@ get_cb (void *cls, TALER_CRYPTO_contract_decrypt_for_merge ( &ds->contract_priv, &ds->purse_pub, - dr->details.success.econtract, - dr->details.success.econtract_size, + dr->details.ok.econtract, + dr->details.ok.econtract_size, &ds->merge_priv); if (0 != GNUNET_memcmp (mp, @@ -153,8 +153,8 @@ get_cb (void *cls, ds->contract_terms = TALER_CRYPTO_contract_decrypt_for_deposit ( &ds->contract_priv, - dr->details.success.econtract, - dr->details.success.econtract_size); + dr->details.ok.econtract, + dr->details.ok.econtract_size); } if (NULL == ds->contract_terms) { diff --git a/src/testing/testing_api_cmd_deposit.c b/src/testing/testing_api_cmd_deposit.c index 16ac139f4..1b097a346 100644 --- a/src/testing/testing_api_cmd_deposit.c +++ b/src/testing/testing_api_cmd_deposit.c @@ -264,9 +264,9 @@ deposit_cb (void *cls, if (MHD_HTTP_OK == dr->hr.http_status) { ds->deposit_succeeded = GNUNET_YES; - ds->exchange_timestamp = dr->details.success.deposit_timestamp; - ds->exchange_pub = *dr->details.success.exchange_pub; - ds->exchange_sig = *dr->details.success.exchange_sig; + ds->exchange_timestamp = dr->details.ok.deposit_timestamp; + ds->exchange_pub = *dr->details.ok.exchange_pub; + ds->exchange_sig = *dr->details.ok.exchange_sig; } TALER_TESTING_interpreter_next (ds->is); } diff --git a/src/testing/testing_api_cmd_deposits_get.c b/src/testing/testing_api_cmd_deposits_get.c index 8fd4e813c..8f7970895 100644 --- a/src/testing/testing_api_cmd_deposits_get.c +++ b/src/testing/testing_api_cmd_deposits_get.c @@ -128,7 +128,7 @@ deposit_wtid_cb (void *cls, switch (dr->hr.http_status) { case MHD_HTTP_OK: - tts->wtid = dr->details.success.wtid; + tts->wtid = dr->details.ok.wtid; if (NULL != tts->bank_transfer_reference) { const struct TALER_TESTING_Command *bank_transfer_cmd; @@ -155,7 +155,7 @@ deposit_wtid_cb (void *cls, } /* Compare that expected and gotten subjects match. */ - if (0 != GNUNET_memcmp (&dr->details.success.wtid, + if (0 != GNUNET_memcmp (&dr->details.ok.wtid, wtid_want)) { GNUNET_break (0); diff --git a/src/testing/testing_api_cmd_nexus_fetch_transactions.c b/src/testing/testing_api_cmd_nexus_fetch_transactions.c index 152647963..ff1497f3f 100644 --- a/src/testing/testing_api_cmd_nexus_fetch_transactions.c +++ b/src/testing/testing_api_cmd_nexus_fetch_transactions.c @@ -80,6 +80,7 @@ nft_run (void *cls, "wget", "--header=Content-Type:application/json", "--auth-no-challenge", + "--output-file=/dev/null", "--post-data={\"level\":\"all\",\"rangeType\":\"latest\"}", user, pass, diff --git a/src/testing/testing_api_cmd_purse_create_deposit.c b/src/testing/testing_api_cmd_purse_create_deposit.c index 200127b77..6fa7d91f9 100644 --- a/src/testing/testing_api_cmd_purse_create_deposit.c +++ b/src/testing/testing_api_cmd_purse_create_deposit.c @@ -176,8 +176,8 @@ deposit_cb (void *cls, } if (MHD_HTTP_OK == dr->hr.http_status) { - ds->exchange_pub = dr->details.success.exchange_pub; - ds->exchange_sig = dr->details.success.exchange_sig; + ds->exchange_pub = dr->details.ok.exchange_pub; + ds->exchange_sig = dr->details.ok.exchange_sig; } TALER_TESTING_interpreter_next (ds->is); } diff --git a/src/testing/testing_api_cmd_purse_deposit.c b/src/testing/testing_api_cmd_purse_deposit.c index ff8e6d2e3..aaf6ff6ba 100644 --- a/src/testing/testing_api_cmd_purse_deposit.c +++ b/src/testing/testing_api_cmd_purse_deposit.c @@ -152,8 +152,8 @@ deposit_cb (void *cls, if (MHD_HTTP_OK == dr->hr.http_status) { if (-1 != - TALER_amount_cmp (&dr->details.success.total_deposited, - &dr->details.success.purse_value_after_fees)) + TALER_amount_cmp (&dr->details.ok.total_deposited, + &dr->details.ok.purse_value_after_fees)) { const struct TALER_TESTING_Command *purse_cmd; const struct TALER_ReserveSignatureP *reserve_sig; @@ -213,7 +213,7 @@ deposit_cb (void *cls, /* Note: change when flags below changes! */ ds->reserve_history.amount - = dr->details.success.purse_value_after_fees; + = dr->details.ok.purse_value_after_fees; if (true) { ds->reserve_history.details.merge_details.purse_fee = gf->fees.purse; @@ -226,7 +226,7 @@ deposit_cb (void *cls, } } ds->reserve_history.details.merge_details.h_contract_terms - = dr->details.success.h_contract_terms; + = dr->details.ok.h_contract_terms; ds->reserve_history.details.merge_details.merge_pub = *merge_pub; ds->reserve_history.details.merge_details.purse_pub @@ -236,7 +236,7 @@ deposit_cb (void *cls, ds->reserve_history.details.merge_details.merge_timestamp = *merge_timestamp; ds->reserve_history.details.merge_details.purse_expiration - = dr->details.success.purse_expiration; + = dr->details.ok.purse_expiration; ds->reserve_history.details.merge_details.min_age = ds->min_age; ds->reserve_history.details.merge_details.flags diff --git a/src/testing/testing_api_cmd_purse_get.c b/src/testing/testing_api_cmd_purse_get.c index 3e7da38f0..60638752f 100644 --- a/src/testing/testing_api_cmd_purse_get.c +++ b/src/testing/testing_api_cmd_purse_get.c @@ -147,11 +147,11 @@ purse_status_cb (void *cls, TALER_string_to_amount (ss->expected_balance, &eb)); if (0 != TALER_amount_cmp (&eb, - &rs->details.success.balance)) + &rs->details.ok.balance)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected amount in purse: %s\n", - TALER_amount_to_string (&rs->details.success.balance)); + TALER_amount_to_string (&rs->details.ok.balance)); TALER_TESTING_interpreter_fail (ss->is); return; } diff --git a/src/testing/testing_api_cmd_recoup.c b/src/testing/testing_api_cmd_recoup.c index ed8c7eed7..e11475f20 100644 --- a/src/testing/testing_api_cmd_recoup.c +++ b/src/testing/testing_api_cmd_recoup.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2018 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -73,15 +73,14 @@ struct RecoupState * was paid back belonged to the right reserve. * * @param cls closure - * @param hr HTTP response details - * @param reserve_pub public key of the reserve receiving the recoup + * @param rr response details */ static void recoup_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_ReservePublicKeyP *reserve_pub) + const struct TALER_EXCHANGE_RecoupResponse *rr) { struct RecoupState *ps = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr; struct TALER_TESTING_Interpreter *is = ps->is; struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; const struct TALER_TESTING_Command *reserve_cmd; @@ -135,12 +134,6 @@ recoup_cb (void *cls, { const struct TALER_ReservePrivateKeyP *reserve_priv; - if (NULL == reserve_pub) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } if (GNUNET_OK != TALER_TESTING_get_trait_reserve_priv (reserve_cmd, &reserve_priv)) @@ -151,7 +144,7 @@ recoup_cb (void *cls, } GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, &ps->reserve_pub.eddsa_pub); - if (0 != GNUNET_memcmp (reserve_pub, + if (0 != GNUNET_memcmp (&rr->details.ok.reserve_pub, &ps->reserve_pub)) { GNUNET_break (0); diff --git a/src/testing/testing_api_cmd_recoup_refresh.c b/src/testing/testing_api_cmd_recoup_refresh.c index 6081a4ba1..ff7dab004 100644 --- a/src/testing/testing_api_cmd_recoup_refresh.c +++ b/src/testing/testing_api_cmd_recoup_refresh.c @@ -73,15 +73,14 @@ struct RecoupRefreshState * was paid back belonged to the right old coin. * * @param cls closure - * @param hr HTTP response details - * @param old_coin_pub public key of the dirty coin + * @param rrr response details */ static void recoup_refresh_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_CoinSpendPublicKeyP *old_coin_pub) + const struct TALER_EXCHANGE_RecoupRefreshResponse *rrr) { struct RecoupRefreshState *rrs = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &rrr->hr; struct TALER_TESTING_Interpreter *is = rrs->is; struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; char *cref; @@ -150,7 +149,7 @@ recoup_refresh_cb (void *cls, GNUNET_CRYPTO_eddsa_key_get_public (&dirty_priv->eddsa_priv, &oc.eddsa_pub); if (0 != GNUNET_memcmp (&oc, - old_coin_pub)) + &rrr->details.ok.old_coin_pub)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c index 2aad77ce3..a8e2e8f39 100644 --- a/src/testing/testing_api_cmd_refresh.c +++ b/src/testing/testing_api_cmd_refresh.c @@ -411,7 +411,7 @@ reveal_cb (void *cls, switch (hr->http_status) { case MHD_HTTP_OK: - rrs->num_fresh_coins = rr->details.success.num_coins; + rrs->num_fresh_coins = rr->details.ok.num_coins; rrs->psa = GNUNET_new_array (rrs->num_fresh_coins, struct TALER_PlanchetMasterSecretP); rrs->fresh_coins = GNUNET_new_array (rrs->num_fresh_coins, @@ -419,7 +419,7 @@ reveal_cb (void *cls, for (unsigned int i = 0; inum_fresh_coins; i++) { const struct TALER_EXCHANGE_RevealedCoinInfo *coin - = &rr->details.success.coins[i]; + = &rr->details.ok.coins[i]; struct TALER_TESTING_FreshCoinData *fc = &rrs->fresh_coins[i]; rrs->psa[i] = coin->ps; @@ -675,11 +675,11 @@ link_cb (void *cls, TALER_TESTING_interpreter_fail (rls->is); return; } - if (lr->details.success.num_coins != *num_fresh_coins) + if (lr->details.ok.num_coins != *num_fresh_coins) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected number of fresh coins: %d vs %d in %s:%u\n", - lr->details.success.num_coins, + lr->details.ok.num_coins, *num_fresh_coins, __FILE__, __LINE__); @@ -687,11 +687,11 @@ link_cb (void *cls, return; } /* check that the coins match */ - for (unsigned int i = 0; idetails.success.num_coins; i++) - for (unsigned int j = i + 1; jdetails.success.num_coins; j++) + for (unsigned int i = 0; idetails.ok.num_coins; i++) + for (unsigned int j = i + 1; jdetails.ok.num_coins; j++) if (0 == - GNUNET_memcmp (&lr->details.success.coins[i].coin_priv, - &lr->details.success.coins[j].coin_priv)) + GNUNET_memcmp (&lr->details.ok.coins[i].coin_priv, + &lr->details.ok.coins[j].coin_priv)) GNUNET_break (0); /* Note: coins might be legitimately permutated in here... */ found = 0; @@ -709,12 +709,12 @@ link_cb (void *cls, return; } - for (unsigned int i = 0; idetails.success.num_coins; i++) + for (unsigned int i = 0; idetails.ok.num_coins; i++) { const struct TALER_EXCHANGE_LinkedCoinInfo *lci_i - = &lr->details.success.coins[i]; + = &lr->details.ok.coins[i]; - for (unsigned int j = 0; jdetails.success.num_coins; j++) + for (unsigned int j = 0; jdetails.ok.num_coins; j++) { const struct TALER_TESTING_FreshCoinData *fcj = &(*fc)[j]; @@ -735,12 +735,12 @@ link_cb (void *cls, } /* for j*/ } /* for i */ } - if (found != lr->details.success.num_coins) + if (found != lr->details.ok.num_coins) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Only %u/%u coins match expectations\n", found, - lr->details.success.num_coins); + lr->details.ok.num_coins); GNUNET_break (0); TALER_TESTING_interpreter_fail (rls->is); return; @@ -952,16 +952,16 @@ melt_cb (void *cls, } if (MHD_HTTP_OK == hr->http_status) { - rms->noreveal_index = mr->details.success.noreveal_index; - if (mr->details.success.num_mbds != rms->num_fresh_coins) + rms->noreveal_index = mr->details.ok.noreveal_index; + if (mr->details.ok.num_mbds != rms->num_fresh_coins) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rms->is); return; } GNUNET_free (rms->mbds); - rms->mbds = GNUNET_memdup (mr->details.success.mbds, - mr->details.success.num_mbds + rms->mbds = GNUNET_memdup (mr->details.ok.mbds, + mr->details.ok.num_mbds * sizeof (struct TALER_EXCHANGE_MeltBlindingDetail)); } diff --git a/src/testing/testing_api_cmd_refund.c b/src/testing/testing_api_cmd_refund.c index 4be3605a4..d41700d1a 100644 --- a/src/testing/testing_api_cmd_refund.c +++ b/src/testing/testing_api_cmd_refund.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -75,19 +75,14 @@ struct RefundState * response code is acceptable. * * @param cls closure - * @param hr HTTP response details - * @param exchange_pub public key the exchange - * used for signing @a obj. - * @param exchange_sig actual signature confirming the refund + * @param rr response details */ static void refund_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_ExchangePublicKeyP *exchange_pub, - const struct TALER_ExchangeSignatureP *exchange_sig) + const struct TALER_EXCHANGE_RefundResponse *rr) { - struct RefundState *rs = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr; struct TALER_TESTING_Command *refund_cmd; refund_cmd = &rs->is->commands[rs->is->ip]; diff --git a/src/testing/testing_api_cmd_revoke_denom_key.c b/src/testing/testing_api_cmd_revoke_denom_key.c index 7c77c3566..8afd4f203 100644 --- a/src/testing/testing_api_cmd_revoke_denom_key.c +++ b/src/testing/testing_api_cmd_revoke_denom_key.c @@ -65,14 +65,15 @@ struct RevokeState * Function called with information about the post revocation operation result. * * @param cls closure with a `struct RevokeState *` - * @param hr HTTP response data + * @param rdr response data */ static void success_cb ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *rdr) { struct RevokeState *rs = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &rdr->hr; rs->kh = NULL; if (rs->expected_response_code != hr->http_status) diff --git a/src/testing/testing_api_cmd_revoke_sign_key.c b/src/testing/testing_api_cmd_revoke_sign_key.c index 9745d728d..3b869312a 100644 --- a/src/testing/testing_api_cmd_revoke_sign_key.c +++ b/src/testing/testing_api_cmd_revoke_sign_key.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -65,14 +65,15 @@ struct RevokeState * Function called with information about the post revocation operation result. * * @param cls closure with a `struct RevokeState *` - * @param hr HTTP response data + * @param rsr response data */ static void success_cb ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *rsr) { struct RevokeState *rs = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &rsr->hr; rs->kh = NULL; if (rs->expected_response_code != hr->http_status) diff --git a/src/testing/testing_api_cmd_set_officer.c b/src/testing/testing_api_cmd_set_officer.c index 0e6de262f..1c0495f32 100644 --- a/src/testing/testing_api_cmd_set_officer.c +++ b/src/testing/testing_api_cmd_set_officer.c @@ -84,13 +84,15 @@ struct SetOfficerState * if the response code is acceptable. * * @param cls closure. - * @param hr HTTP response details + * @param ar response details */ static void set_officer_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct + TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse *ar) { struct SetOfficerState *ds = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &ar->hr; ds->dh = NULL; if (MHD_HTTP_NO_CONTENT != hr->http_status) diff --git a/src/testing/testing_api_cmd_transfer_get.c b/src/testing/testing_api_cmd_transfer_get.c index 3c467e6da..cb6bb7dfa 100644 --- a/src/testing/testing_api_cmd_transfer_get.c +++ b/src/testing/testing_api_cmd_transfer_get.c @@ -115,15 +115,14 @@ track_transfer_cleanup (void *cls, * wire fees and hashed wire details as well. * * @param cls closure. - * @param hr HTTP response details - * @param ta transfer data returned by the exchange + * @param tgr response details */ static void track_transfer_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_TransferData *ta) + const struct TALER_EXCHANGE_TransfersGetResponse *tgr) { struct TrackTransferState *tts = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &tgr->hr; struct TALER_TESTING_Interpreter *is = tts->is; struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; struct TALER_Amount expected_amount; @@ -148,138 +147,62 @@ track_transfer_cb (void *cls, switch (hr->http_status) { case MHD_HTTP_OK: - if (NULL == tts->expected_total_amount) { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } - if (NULL == tts->expected_wire_fee) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } + const struct TALER_EXCHANGE_TransferData *ta + = &tgr->details.ok.td; - if (GNUNET_OK != - TALER_string_to_amount (tts->expected_total_amount, - &expected_amount)) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } - if (0 != TALER_amount_cmp (&ta->total_amount, - &expected_amount)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Total amount mismatch to command %s - " - "%s vs %s\n", - cmd->label, - TALER_amount_to_string (&ta->total_amount), - TALER_amount_to_string (&expected_amount)); - json_dumpf (hr->reply, - stderr, - 0); - fprintf (stderr, "\n"); - TALER_TESTING_interpreter_fail (is); - return; - } - - if (GNUNET_OK != - TALER_string_to_amount (tts->expected_wire_fee, - &expected_amount)) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } - - if (0 != TALER_amount_cmp (&ta->wire_fee, - &expected_amount)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Wire fee mismatch to command %s\n", - cmd->label); - json_dumpf (hr->reply, - stderr, - 0); - TALER_TESTING_interpreter_fail (is); - return; - } - - /** - * Optionally checking: (1) wire-details for this transfer - * match the ones from a referenced "deposit" operation - - * or any operation that could provide wire-details. (2) - * Total amount for this transfer matches the one from any - * referenced command that could provide one. - */ - if (NULL != tts->wire_details_reference) - { - const struct TALER_TESTING_Command *wire_details_cmd; - const char **payto_uri; - struct TALER_PaytoHashP h_payto; - - wire_details_cmd - = TALER_TESTING_interpreter_lookup_command (is, - tts->wire_details_reference); - if (NULL == wire_details_cmd) + if (NULL == tts->expected_total_amount) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); return; } + if (NULL == tts->expected_wire_fee) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (GNUNET_OK != - TALER_TESTING_get_trait_payto_uri (wire_details_cmd, - &payto_uri)) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } - TALER_payto_hash (*payto_uri, - &h_payto); - if (0 != GNUNET_memcmp (&h_payto, - &ta->h_payto)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Wire hash missmath to command %s\n", - cmd->label); - json_dumpf (hr->reply, - stderr, - 0); - TALER_TESTING_interpreter_fail (is); - return; - } - } - if (NULL != tts->total_amount_reference) - { - const struct TALER_TESTING_Command *total_amount_cmd; - const struct TALER_Amount *total_amount_from_reference; - - total_amount_cmd - = TALER_TESTING_interpreter_lookup_command (is, - tts->total_amount_reference); - if (NULL == total_amount_cmd) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } - if (GNUNET_OK != - TALER_TESTING_get_trait_amount (total_amount_cmd, - &total_amount_from_reference)) + TALER_string_to_amount (tts->expected_total_amount, + &expected_amount)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); return; } if (0 != TALER_amount_cmp (&ta->total_amount, - total_amount_from_reference)) + &expected_amount)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Amount missmath to command %s\n", + "Total amount mismatch to command %s - " + "%s vs %s\n", + cmd->label, + TALER_amount_to_string (&ta->total_amount), + TALER_amount_to_string (&expected_amount)); + json_dumpf (hr->reply, + stderr, + 0); + fprintf (stderr, "\n"); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (GNUNET_OK != + TALER_string_to_amount (tts->expected_wire_fee, + &expected_amount)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + if (0 != TALER_amount_cmp (&ta->wire_fee, + &expected_amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire fee mismatch to command %s\n", cmd->label); json_dumpf (hr->reply, stderr, @@ -287,8 +210,92 @@ track_transfer_cb (void *cls, TALER_TESTING_interpreter_fail (is); return; } - } - } + + /** + * Optionally checking: (1) wire-details for this transfer + * match the ones from a referenced "deposit" operation - + * or any operation that could provide wire-details. (2) + * Total amount for this transfer matches the one from any + * referenced command that could provide one. + */ + if (NULL != tts->wire_details_reference) + { + const struct TALER_TESTING_Command *wire_details_cmd; + const char **payto_uri; + struct TALER_PaytoHashP h_payto; + + wire_details_cmd + = TALER_TESTING_interpreter_lookup_command (is, + tts-> + wire_details_reference); + if (NULL == wire_details_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (GNUNET_OK != + TALER_TESTING_get_trait_payto_uri (wire_details_cmd, + &payto_uri)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_payto_hash (*payto_uri, + &h_payto); + if (0 != GNUNET_memcmp (&h_payto, + &ta->h_payto)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire hash missmath to command %s\n", + cmd->label); + json_dumpf (hr->reply, + stderr, + 0); + TALER_TESTING_interpreter_fail (is); + return; + } + } + if (NULL != tts->total_amount_reference) + { + const struct TALER_TESTING_Command *total_amount_cmd; + const struct TALER_Amount *total_amount_from_reference; + + total_amount_cmd + = TALER_TESTING_interpreter_lookup_command (is, + tts-> + total_amount_reference); + if (NULL == total_amount_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (GNUNET_OK != + TALER_TESTING_get_trait_amount (total_amount_cmd, + &total_amount_from_reference)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (0 != TALER_amount_cmp (&ta->total_amount, + total_amount_from_reference)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Amount missmath to command %s\n", + cmd->label); + json_dumpf (hr->reply, + stderr, + 0); + TALER_TESTING_interpreter_fail (is); + return; + } + } + break; + } /* case OK */ + } /* switch on status */ TALER_TESTING_interpreter_next (is); } diff --git a/src/testing/testing_api_cmd_wire.c b/src/testing/testing_api_cmd_wire.c index 6e44403bc..5fbd41b1e 100644 --- a/src/testing/testing_api_cmd_wire.c +++ b/src/testing/testing_api_cmd_wire.c @@ -72,18 +72,14 @@ struct WireState * that the wire fee is acceptable too. * * @param cls closure. - * @param hr HTTP response details - * @param accounts_len length of the @a accounts array. - * @param accounts list of wire accounts of the exchange, - * NULL on error. + * @param wr response details */ static void wire_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - unsigned int accounts_len, - const struct TALER_EXCHANGE_WireAccount *accounts) + const struct TALER_EXCHANGE_WireResponse *wr) { struct WireState *ws = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &wr->hr; struct TALER_TESTING_Command *cmd = &ws->is->commands[ws->is->ip]; struct TALER_Amount expected_fee; @@ -100,6 +96,15 @@ wire_cb (void *cls, if (MHD_HTTP_OK == hr->http_status) { + unsigned int accounts_len + = wr->details.ok.accounts_len; + unsigned int fees_len + = wr->details.ok.fees_len; + const struct TALER_EXCHANGE_WireAccount *accounts + = wr->details.ok.accounts; + const struct TALER_EXCHANGE_WireFeesByMethod *fees + = wr->details.ok.fees; + for (unsigned int i = 0; imethod_found = GNUNET_OK; - if (NULL != ws->expected_fee) + } + GNUNET_free (method); + } + if (NULL != ws->expected_fee) + { + bool fee_found = false; + + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (ws->expected_fee, + &expected_fee)); + for (unsigned int i = 0; iexpected_method)) + continue; + for (const struct TALER_EXCHANGE_WireAggregateFees *waf + = fees[i].fees_head; + NULL != waf; + waf = waf->next) { - GNUNET_assert (GNUNET_OK == - TALER_string_to_amount (ws->expected_fee, - &expected_fee)); - for (const struct TALER_EXCHANGE_WireAggregateFees *waf - = accounts[i].fees; - NULL != waf; - waf = waf->next) + if (0 != TALER_amount_cmp (&waf->fees.wire, + &expected_fee)) { - if (0 != TALER_amount_cmp (&waf->fees.wire, - &expected_fee)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Wire fee mismatch to command %s\n", - cmd->label); - TALER_TESTING_interpreter_fail (ws->is); - GNUNET_free (method); - return; - } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire fee mismatch to command %s\n", + cmd->label); + TALER_TESTING_interpreter_fail (ws->is); + return; } + fee_found = true; } } - TALER_LOG_DEBUG ("Freeing method '%s'\n", - method); - GNUNET_free (method); + if (! fee_found) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "/wire does not contain expected fee '%s'\n", + ws->expected_fee); + TALER_TESTING_interpreter_fail (ws->is); + return; + } } if (GNUNET_OK != ws->method_found) { diff --git a/src/testing/testing_api_cmd_wire_add.c b/src/testing/testing_api_cmd_wire_add.c index c07e9bba6..c36f03b15 100644 --- a/src/testing/testing_api_cmd_wire_add.c +++ b/src/testing/testing_api_cmd_wire_add.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020-2023 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 @@ -67,13 +67,14 @@ struct WireAddState * if the response code is acceptable. * * @param cls closure. - * @param hr HTTP response details + * @param wer response details */ static void wire_add_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementWireEnableResponse *wer) { struct WireAddState *ds = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &wer->hr; ds->dh = NULL; if (ds->expected_response_code != hr->http_status) @@ -110,10 +111,14 @@ wire_add_run (void *cls, struct TALER_MasterSignatureP master_sig1; struct TALER_MasterSignatureP master_sig2; struct GNUNET_TIME_Timestamp now; + json_t *credit_rest; + json_t *debit_rest; (void) cmd; now = GNUNET_TIME_timestamp_get (); ds->is = is; + debit_rest = json_array (); + credit_rest = json_array (); if (ds->bad_sig) { memset (&master_sig1, @@ -126,10 +131,16 @@ wire_add_run (void *cls, else { TALER_exchange_offline_wire_add_sign (ds->payto_uri, + NULL, + debit_rest, + credit_rest, now, &is->master_priv, &master_sig1); TALER_exchange_wire_signature_make (ds->payto_uri, + NULL, + debit_rest, + credit_rest, &is->master_priv, &master_sig2); } @@ -137,11 +148,16 @@ wire_add_run (void *cls, is->ctx, is->exchange_url, ds->payto_uri, + NULL, + debit_rest, + credit_rest, now, &master_sig1, &master_sig2, &wire_add_cb, ds); + json_decref (debit_rest); + json_decref (credit_rest); if (NULL == ds->dh) { GNUNET_break (0); diff --git a/src/testing/testing_api_cmd_wire_del.c b/src/testing/testing_api_cmd_wire_del.c index 15d29d727..89fb83957 100644 --- a/src/testing/testing_api_cmd_wire_del.c +++ b/src/testing/testing_api_cmd_wire_del.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020, 2023 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 @@ -67,13 +67,14 @@ struct WireDelState * if the response code is acceptable. * * @param cls closure. - * @param hr HTTP response details + * @param wdr response details */ static void wire_del_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr) + const struct TALER_EXCHANGE_ManagementWireDisableResponse *wdr) { struct WireDelState *ds = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &wdr->hr; ds->dh = NULL; if (ds->expected_response_code != hr->http_status) diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index 1bd3c187e..8d53f4d07 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -297,10 +297,10 @@ reserve_withdraw_cb (void *cls, { case MHD_HTTP_OK: TALER_denom_sig_deep_copy (&ws->sig, - &wr->details.success.sig); - ws->coin_priv = wr->details.success.coin_priv; - ws->bks = wr->details.success.bks; - ws->exchange_vals = wr->details.success.exchange_vals; + &wr->details.ok.sig); + ws->coin_priv = wr->details.ok.coin_priv; + ws->bks = wr->details.ok.bks; + ws->exchange_vals = wr->details.ok.exchange_vals; if (0 != ws->total_backoff.rel_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c index 1865a1129..3ac9bea20 100644 --- a/src/testing/testing_api_loop.c +++ b/src/testing/testing_api_loop.c @@ -526,49 +526,46 @@ sighandler_child_death (void) void TALER_TESTING_cert_cb (void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_EXCHANGE_Keys *keys, - enum TALER_EXCHANGE_VersionCompatibility compat) + const struct TALER_EXCHANGE_KeysResponse *kr) { + const struct TALER_EXCHANGE_HttpResponse *hr = &kr->hr; struct MainContext *main_ctx = cls; struct TALER_TESTING_Interpreter *is = main_ctx->is; - (void) compat; - if (NULL == keys) + switch (hr->http_status) { - if (GNUNET_NO == is->working) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Got NULL response for /keys during startup (%u/%d), retrying!\n", - hr->http_status, - (int) hr->ec); - TALER_EXCHANGE_disconnect (is->exchange); - GNUNET_assert ( - NULL != (is->exchange - = TALER_EXCHANGE_connect (is->ctx, - main_ctx->exchange_url, - &TALER_TESTING_cert_cb, - main_ctx, - TALER_EXCHANGE_OPTION_END))); - return; - } - else + case MHD_HTTP_OK: + /* dealt with below */ + break; + default: + if (GNUNET_YES == is->working) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Got NULL response for /keys during execution (%u/%d)!\n", hr->http_status, (int) hr->ec); + return; } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Got failure response for /keys during startup (%u/%d), retrying!\n", + hr->http_status, + (int) hr->ec); + TALER_EXCHANGE_disconnect (is->exchange); + GNUNET_assert ( + NULL != (is->exchange + = TALER_EXCHANGE_connect (is->ctx, + main_ctx->exchange_url, + &TALER_TESTING_cert_cb, + main_ctx, + TALER_EXCHANGE_OPTION_END))); + return; } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Got %d DK from /keys in generation %u\n", - keys->num_denom_keys, - is->key_generation + 1); - } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got %d DK from /keys in generation %u\n", + kr->details.ok.keys->num_denom_keys, + is->key_generation + 1); is->key_generation++; - is->keys = keys; + is->keys = kr->details.ok.keys; /* /keys has been called for some reason and * the interpreter is already running. */ diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c index b1e3b93a3..15dda8b34 100644 --- a/src/util/offline_signatures.c +++ b/src/util/offline_signatures.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020-2022 Taler Systems SA + Copyright (C) 2020-2023 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 @@ -677,6 +677,22 @@ struct TALER_MasterAddWirePS * Hash over the exchange's payto URI. */ struct TALER_PaytoHashP h_payto GNUNET_PACKED; + + /** + * Hash over the conversion URL, all zeros if there + * is no conversion URL. + */ + struct GNUNET_HashCode h_conversion_url; + + /** + * Hash over the debit restrictions. + */ + struct GNUNET_HashCode h_debit_restrictions; + + /** + * Hash over the credit restrictions. + */ + struct GNUNET_HashCode h_credit_restrictions; }; GNUNET_NETWORK_STRUCT_END @@ -685,6 +701,9 @@ GNUNET_NETWORK_STRUCT_END void TALER_exchange_offline_wire_add_sign ( const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, struct GNUNET_TIME_Timestamp now, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) @@ -697,6 +716,14 @@ TALER_exchange_offline_wire_add_sign ( TALER_payto_hash (payto_uri, &kv.h_payto); + if (NULL != conversion_url) + GNUNET_CRYPTO_hash (conversion_url, + strlen (conversion_url), + &kv.h_conversion_url); + TALER_json_hash (debit_restrictions, + &kv.h_debit_restrictions); + TALER_json_hash (credit_restrictions, + &kv.h_credit_restrictions); GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, &kv, &master_sig->eddsa_signature); @@ -706,6 +733,9 @@ TALER_exchange_offline_wire_add_sign ( enum GNUNET_GenericReturnValue TALER_exchange_offline_wire_add_verify ( const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, struct GNUNET_TIME_Timestamp sign_time, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig) @@ -718,6 +748,14 @@ TALER_exchange_offline_wire_add_verify ( TALER_payto_hash (payto_uri, &aw.h_payto); + if (NULL != conversion_url) + GNUNET_CRYPTO_hash (conversion_url, + strlen (conversion_url), + &aw.h_conversion_url); + TALER_json_hash (debit_restrictions, + &aw.h_debit_restrictions); + TALER_json_hash (credit_restrictions, + &aw.h_credit_restrictions); return GNUNET_CRYPTO_eddsa_verify ( TALER_SIGNATURE_MASTER_ADD_WIRE, @@ -1095,6 +1133,22 @@ struct TALER_MasterWireDetailsPS */ struct TALER_PaytoHashP h_wire_details GNUNET_PACKED; + /** + * Hash over the conversion URL, all zeros if there + * is no conversion URL. + */ + struct GNUNET_HashCode h_conversion_url; + + /** + * Hash over the debit restrictions. + */ + struct GNUNET_HashCode h_debit_restrictions; + + /** + * Hash over the credit restrictions. + */ + struct GNUNET_HashCode h_credit_restrictions; + }; GNUNET_NETWORK_STRUCT_END @@ -1103,6 +1157,9 @@ GNUNET_NETWORK_STRUCT_END enum GNUNET_GenericReturnValue TALER_exchange_wire_signature_check ( const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig) { @@ -1113,6 +1170,14 @@ TALER_exchange_wire_signature_check ( TALER_payto_hash (payto_uri, &wd.h_wire_details); + if (NULL != conversion_url) + GNUNET_CRYPTO_hash (conversion_url, + strlen (conversion_url), + &wd.h_conversion_url); + TALER_json_hash (debit_restrictions, + &wd.h_debit_restrictions); + TALER_json_hash (credit_restrictions, + &wd.h_credit_restrictions); return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_DETAILS, &wd, &master_sig->eddsa_signature, @@ -1123,6 +1188,9 @@ TALER_exchange_wire_signature_check ( void TALER_exchange_wire_signature_make ( const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { @@ -1133,6 +1201,14 @@ TALER_exchange_wire_signature_make ( TALER_payto_hash (payto_uri, &wd.h_wire_details); + if (NULL != conversion_url) + GNUNET_CRYPTO_hash (conversion_url, + strlen (conversion_url), + &wd.h_conversion_url); + TALER_json_hash (debit_restrictions, + &wd.h_debit_restrictions); + TALER_json_hash (credit_restrictions, + &wd.h_credit_restrictions); GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, &wd, &master_sig->eddsa_signature); From faca037018820ba3c21724c7f6ab41a72206e4ff Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 30 Apr 2023 23:37:01 +0200 Subject: [PATCH 13/53] expose TALER_EXCHANGE_parse_accounts() in external API --- contrib/gana | 2 +- src/include/taler_exchange_service.h | 27 ++++ src/lib/exchange_api_common.c | 192 +++++++++++++++++++++++++ src/lib/exchange_api_wire.c | 208 ++------------------------- 4 files changed, 232 insertions(+), 197 deletions(-) diff --git a/contrib/gana b/contrib/gana index 5cfe18c5b..d831c7f72 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 5cfe18c5bbfd404a5f7cf27a78577c881ddb9ebd +Subproject commit d831c7f72a5030e20efb4ada7babc103ccd01fab diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index f9330ec5c..fc5fb284b 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -909,6 +909,33 @@ struct TALER_EXCHANGE_WireAccount }; +/** + * Parse array of @a accounts of the exchange into @a was. + * + * @param master_pub master public key of the exchange, NULL to not verify signatures + * @param accounts array of accounts to parse + * @param[out] was where to write the result (already allocated) + * @param was_length length of the @a was array, must match the length of @a accounts + * @return #GNUNET_OK if parsing @a accounts succeeded + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_parse_accounts (const struct TALER_MasterPublicKeyP *master_pub, + const json_t *accounts, + struct TALER_EXCHANGE_WireAccount was[], + unsigned int was_length); + + +/** + * Free data within @a was, but not @a was itself. + * + * @param was array of wire account data + * @param was_len length of the @a was array + */ +void +TALER_EXCHANGE_free_accounts (struct TALER_EXCHANGE_WireAccount *was, + unsigned int was_len); + + /** * Response to a /wire request. */ diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index b895bf9a8..285a5292a 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -2194,4 +2194,196 @@ TALER_EXCHANGE_verify_deposit_signature_ ( } +/** + * Parse account restriction in @a jrest into @a rest. + * + * @param jrest array of account restrictions in JSON + * @param[out] resta_len set to length of @a resta + * @param[out] resta account restriction array to set + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_restrictions (const json_t *jresta, + unsigned int *resta_len, + struct TALER_EXCHANGE_AccountRestriction **resta) +{ + if (! json_is_array (jresta)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *resta_len = json_array_size (jresta); + if (0 == *resta_len) + { + /* no restrictions, perfectly OK */ + *resta = NULL; + return GNUNET_OK; + } + *resta = GNUNET_new_array (*resta_len, + struct TALER_EXCHANGE_AccountRestriction); + for (unsigned int i = 0; i<*resta_len; i++) + { + const json_t *jr = json_array_get (jresta, + i); + struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i]; + const char *type = json_string_value (json_object_get (jr, + "type")); + + if (NULL == type) + { + GNUNET_break (0); + goto fail; + } + if (0 == strcmp (type, + "deny")) + { + ar->type = TALER_EXCHANGE_AR_DENY; + continue; + } + if (0 == strcmp (type, + "regex")) + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ( + "payto_regex", + &ar->details.regex.posix_egrep), + GNUNET_JSON_spec_string ( + "human_hint", + &ar->details.regex.human_hint), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ( + "human_hint_i18n", + &ar->details.regex.human_hint_i18n), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jr, + spec, + NULL, NULL)) + { + /* bogus reply */ + GNUNET_break_op (0); + goto fail; + } + ar->type = TALER_EXCHANGE_AR_REGEX; + continue; + } + /* unsupported type */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +fail: + GNUNET_free (*resta); + *resta_len = 0; + return GNUNET_SYSERR; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_parse_accounts (const struct TALER_MasterPublicKeyP *master_pub, + const json_t *accounts, + struct TALER_EXCHANGE_WireAccount was[], + unsigned int was_length) +{ + memset (was, + 0, + sizeof (struct TALER_EXCHANGE_WireAccount) * was_length); + GNUNET_assert (was_length == + json_array_size (accounts)); + for (unsigned int i = 0; + ipayto_uri), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("conversion_url", + &wa->conversion_url), + NULL), + GNUNET_JSON_spec_json ("credit_restrictions", + &credit_restrictions), + GNUNET_JSON_spec_json ("debit_restrictions", + &debit_restrictions), + GNUNET_JSON_spec_fixed_auto ("master_sig", + &wa->master_sig), + GNUNET_JSON_spec_end () + }; + json_t *account; + + account = json_array_get (accounts, + i); + if (GNUNET_OK != + GNUNET_JSON_parse (account, + spec_account, + NULL, NULL)) + { + /* bogus reply */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + { + char *err; + + err = TALER_payto_validate (wa->payto_uri); + if (NULL != err) + { + GNUNET_break_op (0); + GNUNET_free (err); + return GNUNET_SYSERR; + } + } + + if ( (NULL != master_pub) && + (GNUNET_OK != + TALER_exchange_wire_signature_check (wa->payto_uri, + wa->conversion_url, + debit_restrictions, + credit_restrictions, + master_pub, + &wa->master_sig)) ) + { + /* bogus reply */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (GNUNET_OK != + parse_restrictions (credit_restrictions, + &wa->credit_restrictions_length, + &wa->credit_restrictions)) || + (GNUNET_OK != + parse_restrictions (debit_restrictions, + &wa->debit_restrictions_length, + &wa->debit_restrictions)) ) + { + /* bogus reply */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_JSON_parse_free (spec_account); + } /* end 'for all accounts */ + return GNUNET_OK; +} + + +void +TALER_EXCHANGE_free_accounts (struct TALER_EXCHANGE_WireAccount *was, + unsigned int was_len) +{ + for (unsigned int i = 0; icredit_restrictions); + GNUNET_free (wa->debit_restrictions); + } +} + + /* end of exchange_api_common.c */ diff --git a/src/lib/exchange_api_wire.c b/src/lib/exchange_api_wire.c index 498265282..f38ca86b6 100644 --- a/src/lib/exchange_api_wire.c +++ b/src/lib/exchange_api_wire.c @@ -173,114 +173,6 @@ parse_fees (const struct TALER_MasterPublicKeyP *master_pub, } -/** - * Parse account restriction in @a jrest into @a rest. - * - * @param jrest array of account restrictions in JSON - * @param[out] resta_len set to length of @a resta - * @param[out] resta account restriction array to set - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_restrictions (const json_t *jresta, - unsigned int *resta_len, - struct TALER_EXCHANGE_AccountRestriction **resta) -{ - if (! json_is_array (jresta)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - *resta_len = json_array_size (jresta); - if (0 == *resta_len) - { - /* no restrictions, perfectly OK */ - *resta = NULL; - return GNUNET_OK; - } - *resta = GNUNET_new_array (*resta_len, - struct TALER_EXCHANGE_AccountRestriction); - for (unsigned int i = 0; i<*resta_len; i++) - { - const json_t *jr = json_array_get (jresta, - i); - struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i]; - const char *type = json_string_value (json_object_get (jr, - "type")); - - if (NULL == type) - { - GNUNET_break (0); - goto fail; - } - if (0 == strcmp (type, - "deny")) - { - ar->type = TALER_EXCHANGE_AR_DENY; - continue; - } - if (0 == strcmp (type, - "regex")) - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ( - "payto_regex", - &ar->details.regex.posix_egrep), - GNUNET_JSON_spec_string ( - "human_hint", - &ar->details.regex.human_hint), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ( - "human_hint_i18n", - &ar->details.regex.human_hint_i18n), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (jr, - spec, - NULL, NULL)) - { - /* bogus reply */ - GNUNET_break_op (0); - goto fail; - } - ar->type = TALER_EXCHANGE_AR_REGEX; - continue; - } - /* unsupported type */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -fail: - GNUNET_free (*resta); - *resta_len = 0; - return GNUNET_SYSERR; -} - - -/** - * Free data within @a was, but not @a was itself. - * - * @param was array of wire account data - * @param was_len length of the @a was array - */ -static void -free_accounts (struct TALER_EXCHANGE_WireAccount *was, - unsigned int was_len) -{ - for (unsigned int i = 0; icredit_restrictions); - GNUNET_free (wa->debit_restrictions); - } -} - - /** * Function called when we're done processing the * HTTP /wire request. @@ -383,101 +275,25 @@ handle_wire_finished (void *cls, { struct TALER_EXCHANGE_WireAccount was[wr.details.ok.accounts_len]; - memset (was, - 0, - sizeof (was)); wr.details.ok.accounts = was; - for (unsigned int i = 0; - ipayto_uri), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("conversion_url", - &wa->conversion_url), - NULL), - GNUNET_JSON_spec_json ("credit_restrictions", - &credit_restrictions), - GNUNET_JSON_spec_json ("debit_restrictions", - &debit_restrictions), - GNUNET_JSON_spec_fixed_auto ("master_sig", - &wa->master_sig), - GNUNET_JSON_spec_end () - }; - json_t *account; - - account = json_array_get (accounts, - i); - if (GNUNET_OK != - GNUNET_JSON_parse (account, - spec_account, - NULL, NULL)) - { - /* bogus reply */ - GNUNET_break_op (0); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - { - char *err; - - err = TALER_payto_validate (wa->payto_uri); - if (NULL != err) - { - GNUNET_break_op (0); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - GNUNET_free (err); - break; - } - } - - if (GNUNET_OK != - TALER_exchange_wire_signature_check (wa->payto_uri, - wa->conversion_url, - debit_restrictions, - credit_restrictions, - &master_pub, - &wa->master_sig)) - { - /* bogus reply */ - GNUNET_break_op (0); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_EXCHANGE_WIRE_SIGNATURE_INVALID; - break; - } - if ( (GNUNET_OK != - parse_restrictions (credit_restrictions, - &wa->credit_restrictions_length, - &wa->credit_restrictions)) || - (GNUNET_OK != - parse_restrictions (debit_restrictions, - &wa->debit_restrictions_length, - &wa->debit_restrictions)) ) - { - /* bogus reply */ - GNUNET_break_op (0); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_JSON_parse_free (spec_account); - } /* end 'for all accounts */ - if ( (0 != wr.hr.http_status) && - (NULL != wh->cb) ) + GNUNET_break_op (0); + wr.hr.http_status = 0; + wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + else if (NULL != wh->cb) { wh->cb (wh->cb_cls, &wr); wh->cb = NULL; } - free_accounts (was, - wr.details.ok.accounts_len); + TALER_EXCHANGE_free_accounts (was, + wr.details.ok.accounts_len); } /* end of 'parse accounts */ free_fees (fbm, wr.details.ok.fees_len); From 647ae694cc9f1aef44e792b96676b115231e8898 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 1 May 2023 19:37:29 +0200 Subject: [PATCH 14/53] -fix uninitialized variable and memory leak --- src/kyclogic/plugin_kyclogic_oauth2.c | 120 ++++++++++++++------------ src/lib/exchange_api_wire.c | 1 + src/testing/testing_api_cmd_oauth.c | 1 + 3 files changed, 67 insertions(+), 55 deletions(-) diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c index 228525e28..c72b04b7c 100644 --- a/src/kyclogic/plugin_kyclogic_oauth2.c +++ b/src/kyclogic/plugin_kyclogic_oauth2.c @@ -490,8 +490,6 @@ initiate_task (void *cls) struct PluginState *ps = pd->ps; char *hps; char *url; - char *redirect_uri; - char *redirect_uri_encoded; char legi_s[42]; ih->task = NULL; @@ -501,19 +499,27 @@ initiate_task (void *cls) (unsigned long long) ih->legitimization_uuid); hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto, sizeof (ih->h_payto)); - GNUNET_asprintf (&redirect_uri, - "%skyc-proof/%s?state=%s", - ps->exchange_base_url, - pd->section, - hps); - redirect_uri_encoded = TALER_urlencode (redirect_uri); - GNUNET_free (redirect_uri); - GNUNET_asprintf (&url, - "%s?response_type=code&client_id=%s&redirect_uri=%s", - pd->login_url, - pd->client_id, - redirect_uri_encoded); - GNUNET_free (redirect_uri_encoded); + { + char *redirect_uri_encoded; + + { + char *redirect_uri; + + GNUNET_asprintf (&redirect_uri, + "%skyc-proof/%s?state=%s", + ps->exchange_base_url, + pd->section, + hps); + redirect_uri_encoded = TALER_urlencode (redirect_uri); + GNUNET_free (redirect_uri); + } + GNUNET_asprintf (&url, + "%s?response_type=code&client_id=%s&redirect_uri=%s", + pd->login_url, + pd->client_id, + redirect_uri_encoded); + GNUNET_free (redirect_uri_encoded); + } /* FIXME-API: why do we *redirect* the client here, instead of making the HTTP request *ourselves* and forwarding the response? This prevents us @@ -582,6 +588,37 @@ oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih) } +/** + * Cancel KYC proof. + * + * @param[in] ph handle of operation to cancel + */ +static void +oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph) +{ + if (NULL != ph->task) + { + GNUNET_SCHEDULER_cancel (ph->task); + ph->task = NULL; + } + if (NULL != ph->job) + { + GNUNET_CURL_job_cancel (ph->job); + ph->job = NULL; + } + if (NULL != ph->response) + { + MHD_destroy_response (ph->response); + ph->response = NULL; + } + GNUNET_free (ph->provider_user_id); + if (NULL != ph->attributes) + json_decref (ph->attributes); + GNUNET_free (ph->post_body); + GNUNET_free (ph); +} + + /** * Function called to asynchronously return the final * result to the callback. @@ -602,10 +639,8 @@ return_proof_response (void *cls) ph->attributes, ph->http_status, ph->response); - GNUNET_free (ph->provider_user_id); - if (NULL != ph->attributes) - json_decref (ph->attributes); - GNUNET_free (ph); + ph->response = NULL; /*Ownership passed to 'ph->cb'!*/ + oauth2_proof_cancel (ph); } @@ -1101,7 +1136,6 @@ oauth2_proof (void *cls, 1)); { char *client_id; - char *redirect_uri; char *client_secret; char *authorization_code; char *redirect_uri_encoded; @@ -1109,13 +1143,17 @@ oauth2_proof (void *cls, hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto, sizeof (ph->h_payto)); - GNUNET_asprintf (&redirect_uri, - "%skyc-proof/%s?state=%s", - ps->exchange_base_url, - pd->section, - hps); - redirect_uri_encoded = TALER_urlencode (redirect_uri); - GNUNET_free (redirect_uri); + { + char *redirect_uri; + + GNUNET_asprintf (&redirect_uri, + "%skyc-proof/%s?state=%s", + ps->exchange_base_url, + pd->section, + hps); + redirect_uri_encoded = TALER_urlencode (redirect_uri); + GNUNET_free (redirect_uri); + } GNUNET_assert (NULL != redirect_uri_encoded); client_id = curl_easy_escape (ph->eh, pd->client_id, @@ -1164,34 +1202,6 @@ oauth2_proof (void *cls, } -/** - * Cancel KYC proof. - * - * @param[in] ph handle of operation to cancel - */ -static void -oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph) -{ - if (NULL != ph->task) - { - GNUNET_SCHEDULER_cancel (ph->task); - ph->task = NULL; - } - if (NULL != ph->job) - { - GNUNET_CURL_job_cancel (ph->job); - ph->job = NULL; - } - if (NULL != ph->response) - { - MHD_destroy_response (ph->response); - ph->response = NULL; - } - GNUNET_free (ph->post_body); - GNUNET_free (ph); -} - - /** * Function to asynchronously return the 404 not found * page for the webhook. diff --git a/src/lib/exchange_api_wire.c b/src/lib/exchange_api_wire.c index f38ca86b6..084988058 100644 --- a/src/lib/exchange_api_wire.c +++ b/src/lib/exchange_api_wire.c @@ -261,6 +261,7 @@ handle_wire_finished (void *cls, fbm = parse_fees (&master_pub, fees, &wr.details.ok.fees_len); + wr.details.ok.fees = fbm; if (NULL == fbm) { /* bogus reply */ diff --git a/src/testing/testing_api_cmd_oauth.c b/src/testing/testing_api_cmd_oauth.c index 514b4ac8c..0bcf2f680 100644 --- a/src/testing/testing_api_cmd_oauth.c +++ b/src/testing/testing_api_cmd_oauth.c @@ -310,6 +310,7 @@ cleanup (void *cls, (void) toe; if (NULL == rc) return; + MHD_destroy_post_processor (rc->pp); GNUNET_free (rc->code); GNUNET_free (rc->client_id); GNUNET_free (rc->redirect_uri); From 8ce9433736f799a89a85409b99e61649d4bdf74f Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 1 May 2023 23:10:44 +0200 Subject: [PATCH 15/53] fix uninitialized old_scope --- src/exchange/taler-exchange-httpd_reserves_get.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c b/src/exchange/taler-exchange-httpd_reserves_get.c index c22e62bf5..21c0b74aa 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get.c +++ b/src/exchange/taler-exchange-httpd_reserves_get.c @@ -51,6 +51,11 @@ struct ReservePoller */ struct MHD_Connection *connection; + /** + * Our request context. + */ + struct TEH_RequestContext *rc; + /** * Subscription for the database event we are * waiting for. @@ -154,6 +159,8 @@ db_event_cb (void *cls, (void) extra_size; if (! rp->suspended) return; /* might get multiple wake-up events */ + GNUNET_async_scope_enter (&rp->rc->async_scope_id, + &old_scope); TEH_check_invariants (); rp->suspended = false; MHD_resume_connection (rp->connection); @@ -176,6 +183,7 @@ TEH_handler_reserves_get (struct TEH_RequestContext *rc, rp = GNUNET_new (struct ReservePoller); rp->connection = rc->connection; + rp->rc = rc; rc->rh_ctx = rp; rc->rh_cleaner = &rp_cleanup; GNUNET_CONTAINER_DLL_insert (rp_head, From 00021d7e83dd1008b8abe7173b8bcb441e169ff5 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 2 May 2023 11:03:26 +0200 Subject: [PATCH 16/53] -doxygen --- src/lib/exchange_api_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 285a5292a..588b12834 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -2197,7 +2197,7 @@ TALER_EXCHANGE_verify_deposit_signature_ ( /** * Parse account restriction in @a jrest into @a rest. * - * @param jrest array of account restrictions in JSON + * @param jresta array of account restrictions in JSON * @param[out] resta_len set to length of @a resta * @param[out] resta account restriction array to set * @return #GNUNET_OK on success From 41cb79c685b25bcf2c3e566f25c386c89442cbeb Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 2 May 2023 12:07:27 +0200 Subject: [PATCH 17/53] implement taler-exchange-offline account restriction and conversion functionality --- src/exchange-tools/taler-exchange-offline.c | 137 +++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index ec48ec831..fbf8a6ac6 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -2975,6 +2975,81 @@ do_del_auditor (char *const *args) } +/** + * Parse account restriction. + * + * @param args the array of command-line arguments to process next + * @param[in,out] restrictions JSON array to update + * @return -1 on error, otherwise number of arguments from @a args that were used + */ +static int +parse_restriction (char *const *args, + json_t *restrictions) +{ + if (NULL == args[0]) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Restriction TYPE argument missing\n"); + return -1; + } + if (0 == strcmp (args[0], + "deny")) + { + GNUNET_assert (0 == + json_array_append_new ( + restrictions, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "deny")))); + return 1; + } + if (0 == strcmp (args[0], + "regex")) + { + json_t *i18n; + json_error_t err; + + if ( (NULL == args[1]) || + (NULL == args[2]) || + (NULL == args[3]) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Mandatory arguments for restriction of type `regex' missing (REGEX, HINT, HINT-I18 required)\n"); + return -1; + } + i18n = json_loads (args[3], + JSON_REJECT_DUPLICATES, + &err); + if (NULL == i18n) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid JSON for restriction of type `regex': `%s` at %d\n", + args[3], + err.position); + return -1; + } + GNUNET_assert (0 == + json_array_append_new ( + restrictions, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "regex"), + GNUNET_JSON_pack_string ("regex", + args[1]), + GNUNET_JSON_pack_string ("human_hint", + args[2]), + GNUNET_JSON_pack_object_steal ("human_hint_i18n", + i18n) + ))); + return 4; + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Restriction TYPE `%s' unsupported\n", + args[0]); + return -1; +} + + /** * Add wire account. * @@ -3041,12 +3116,70 @@ do_add_wire (char *const *args) } GNUNET_free (wire_method); } - // FIXME: init new args properly! debit_restrictions = json_array (); GNUNET_assert (NULL != debit_restrictions); credit_restrictions = json_array (); GNUNET_assert (NULL != credit_restrictions); + while (NULL != args[num_args]) + { + if (0 == strcmp (args[num_args], + "conversion-url")) + { + num_args++; + conversion_url = args[num_args]; + if (NULL == conversion_url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "'conversion-url' requires an argument\n"); + global_ret = EXIT_INVALIDARGUMENT; + test_shutdown (); + json_decref (debit_restrictions); + json_decref (credit_restrictions); + return; + } + num_args++; + continue; + } + if (0 == strcmp (args[num_args], + "credit-restriction")) + { + int iret; + num_args++; + iret = parse_restriction (&args[num_args], + credit_restrictions); + if (iret <= 0) + { + global_ret = EXIT_INVALIDARGUMENT; + test_shutdown (); + json_decref (debit_restrictions); + json_decref (credit_restrictions); + return; + } + num_args += iret; + continue; + } + if (0 == strcmp (args[num_args], + "debit-restriction")) + { + int iret; + + num_args++; + iret = parse_restriction (&args[num_args], + debit_restrictions); + if (iret <= 0) + { + global_ret = EXIT_INVALIDARGUMENT; + test_shutdown (); + json_decref (debit_restrictions); + json_decref (credit_restrictions); + return; + } + num_args += iret; + continue; + } + break; + } TALER_exchange_offline_wire_add_sign (args[0], conversion_url, debit_restrictions, @@ -5091,7 +5224,7 @@ work (void *cls) { .name = "enable-account", .help = - "enable wire account of the exchange (payto-URI must be given as argument)", + "enable wire account of the exchange (payto-URI must be given as argument; for optional argument see man page)", .cb = &do_add_wire }, { From ffd4057c61a1c507e348b76d1df10641c7ed64b1 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 2 May 2023 17:29:41 +0200 Subject: [PATCH 18/53] use same canonicalization of JSON as for contract hashes when doing normal JSON hashing --- src/json/json.c | 168 +---------------------------------------------- src/util/util.c | 169 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 169 insertions(+), 168 deletions(-) diff --git a/src/json/json.c b/src/json/json.c index 7d7e4ecba..832863f31 100644 --- a/src/json/json.c +++ b/src/json/json.c @@ -61,170 +61,6 @@ contains_real (const json_t *json) } -/** - * Dump character in the low range into @a buf - * following RFC 8785. - * - * @param[in,out] buf buffer to modify - * @param val value to dump - */ -static void -lowdump (struct GNUNET_Buffer *buf, - unsigned char val) -{ - char scratch[7]; - - switch (val) - { - case 0x8: - GNUNET_buffer_write (buf, - "\\b", - 2); - break; - case 0x9: - GNUNET_buffer_write (buf, - "\\t", - 2); - break; - case 0xA: - GNUNET_buffer_write (buf, - "\\n", - 2); - break; - case 0xC: - GNUNET_buffer_write (buf, - "\\f", - 2); - break; - case 0xD: - GNUNET_buffer_write (buf, - "\\r", - 2); - break; - default: - GNUNET_snprintf (scratch, - sizeof (scratch), - "\\u%04x", - (unsigned int) val); - GNUNET_buffer_write (buf, - scratch, - 6); - break; - } -} - - -/** - * Re-encode string at @a inp to match RFC 8785 (section 3.2.2.2). - * - * @param[in,out] inp pointer to string to re-encode - * @return number of bytes in resulting @a inp - */ -static size_t -rfc8785encode (char **inp) -{ - struct GNUNET_Buffer buf = { 0 }; - size_t left = strlen (*inp) + 1; - size_t olen; - char *in = *inp; - const char *pos = in; - - GNUNET_buffer_prealloc (&buf, - left + 40); - buf.warn_grow = 0; /* disable, + 40 is just a wild guess */ - while (1) - { - int mbl = u8_mblen ((unsigned char *) pos, - left); - unsigned char val; - - if (0 == mbl) - break; - val = (unsigned char) *pos; - if ( (1 == mbl) && - (val <= 0x1F) ) - { - /* Should not happen, as input is produced by - * JSON stringification */ - GNUNET_break (0); - lowdump (&buf, - val); - } - else if ( (1 == mbl) && ('\\' == *pos) ) - { - switch (*(pos + 1)) - { - case '\\': - mbl = 2; - GNUNET_buffer_write (&buf, - pos, - mbl); - break; - case 'u': - { - unsigned int num; - uint32_t n32; - unsigned char res[8]; - size_t rlen; - - GNUNET_assert ( (1 == - sscanf (pos + 2, - "%4x", - &num)) || - (1 == - sscanf (pos + 2, - "%4X", - &num)) ); - mbl = 6; - n32 = (uint32_t) num; - rlen = sizeof (res); - u32_to_u8 (&n32, - 1, - res, - &rlen); - if ( (1 == rlen) && - (res[0] <= 0x1F) ) - { - lowdump (&buf, - res[0]); - } - else - { - GNUNET_buffer_write (&buf, - (const char *) res, - rlen); - } - } - break; - default: - mbl = 2; - GNUNET_buffer_write (&buf, - pos, - mbl); - break; - } - } - else - { - GNUNET_buffer_write (&buf, - pos, - mbl); - } - left -= mbl; - pos += mbl; - } - - /* 0-terminate buffer */ - GNUNET_buffer_write (&buf, - "", - 1); - GNUNET_free (in); - *inp = GNUNET_buffer_reap (&buf, - &olen); - return olen; -} - - /** * Dump the @a json to a string and hash it. * @@ -262,7 +98,7 @@ dump_and_hash (const json_t *json, GNUNET_break (0); return GNUNET_SYSERR; } - len = rfc8785encode (&wire_enc); + len = TALER_rfc8785encode (&wire_enc); if (NULL == salt) { GNUNET_CRYPTO_hash (wire_enc, @@ -1031,7 +867,7 @@ TALER_JSON_canonicalize (const json_t *input) GNUNET_break (0); return NULL; } - rfc8785encode (&wire_enc); + TALER_rfc8785encode (&wire_enc); return wire_enc; } diff --git a/src/util/util.c b/src/util/util.c index 96d791912..7cd4b0c35 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -25,6 +25,7 @@ #include "taler_util.h" #include "taler_attributes.h" #include +#include const char * @@ -204,6 +205,170 @@ TALER_denom_fee_check_currency ( } +/** + * Dump character in the low range into @a buf + * following RFC 8785. + * + * @param[in,out] buf buffer to modify + * @param val value to dump + */ +static void +lowdump (struct GNUNET_Buffer *buf, + unsigned char val) +{ + char scratch[7]; + + switch (val) + { + case 0x8: + GNUNET_buffer_write (buf, + "\\b", + 2); + break; + case 0x9: + GNUNET_buffer_write (buf, + "\\t", + 2); + break; + case 0xA: + GNUNET_buffer_write (buf, + "\\n", + 2); + break; + case 0xC: + GNUNET_buffer_write (buf, + "\\f", + 2); + break; + case 0xD: + GNUNET_buffer_write (buf, + "\\r", + 2); + break; + default: + GNUNET_snprintf (scratch, + sizeof (scratch), + "\\u%04x", + (unsigned int) val); + GNUNET_buffer_write (buf, + scratch, + 6); + break; + } +} + + +/** + * Re-encode string at @a inp to match RFC 8785 (section 3.2.2.2). + * + * @param[in,out] inp pointer to string to re-encode + * @return number of bytes in resulting @a inp + */ +size_t +TALER_rfc8785encode (char **inp) +{ + struct GNUNET_Buffer buf = { 0 }; + size_t left = strlen (*inp) + 1; + size_t olen; + char *in = *inp; + const char *pos = in; + + GNUNET_buffer_prealloc (&buf, + left + 40); + buf.warn_grow = 0; /* disable, + 40 is just a wild guess */ + while (1) + { + int mbl = u8_mblen ((unsigned char *) pos, + left); + unsigned char val; + + if (0 == mbl) + break; + val = (unsigned char) *pos; + if ( (1 == mbl) && + (val <= 0x1F) ) + { + /* Should not happen, as input is produced by + * JSON stringification */ + GNUNET_break (0); + lowdump (&buf, + val); + } + else if ( (1 == mbl) && ('\\' == *pos) ) + { + switch (*(pos + 1)) + { + case '\\': + mbl = 2; + GNUNET_buffer_write (&buf, + pos, + mbl); + break; + case 'u': + { + unsigned int num; + uint32_t n32; + unsigned char res[8]; + size_t rlen; + + GNUNET_assert ( (1 == + sscanf (pos + 2, + "%4x", + &num)) || + (1 == + sscanf (pos + 2, + "%4X", + &num)) ); + mbl = 6; + n32 = (uint32_t) num; + rlen = sizeof (res); + u32_to_u8 (&n32, + 1, + res, + &rlen); + if ( (1 == rlen) && + (res[0] <= 0x1F) ) + { + lowdump (&buf, + res[0]); + } + else + { + GNUNET_buffer_write (&buf, + (const char *) res, + rlen); + } + } + break; + default: + mbl = 2; + GNUNET_buffer_write (&buf, + pos, + mbl); + break; + } + } + else + { + GNUNET_buffer_write (&buf, + pos, + mbl); + } + left -= mbl; + pos += mbl; + } + + /* 0-terminate buffer */ + GNUNET_buffer_write (&buf, + "", + 1); + GNUNET_free (in); + *inp = GNUNET_buffer_reap (&buf, + &olen); + return olen; +} + + /** * Hash normalized @a j JSON object or array and * store the result in @a hc. @@ -221,11 +386,11 @@ TALER_json_hash (const json_t *j, cstr = json_dumps (j, JSON_COMPACT | JSON_SORT_KEYS); GNUNET_assert (NULL != cstr); - clen = strlen (cstr); + clen = TALER_rfc8785encode (&cstr); GNUNET_CRYPTO_hash (cstr, clen, hc); - free (cstr); + GNUNET_free (cstr); } From 4267f1d76299252de273fa723c6037fef30fb7f1 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 2 May 2023 17:32:26 +0200 Subject: [PATCH 19/53] include 0-terminator when hashing --- src/util/offline_signatures.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c index 15dda8b34..40ccfbc21 100644 --- a/src/util/offline_signatures.c +++ b/src/util/offline_signatures.c @@ -718,7 +718,7 @@ TALER_exchange_offline_wire_add_sign ( &kv.h_payto); if (NULL != conversion_url) GNUNET_CRYPTO_hash (conversion_url, - strlen (conversion_url), + strlen (conversion_url) + 1, &kv.h_conversion_url); TALER_json_hash (debit_restrictions, &kv.h_debit_restrictions); @@ -750,7 +750,7 @@ TALER_exchange_offline_wire_add_verify ( &aw.h_payto); if (NULL != conversion_url) GNUNET_CRYPTO_hash (conversion_url, - strlen (conversion_url), + strlen (conversion_url) + 1, &aw.h_conversion_url); TALER_json_hash (debit_restrictions, &aw.h_debit_restrictions); @@ -1172,7 +1172,7 @@ TALER_exchange_wire_signature_check ( &wd.h_wire_details); if (NULL != conversion_url) GNUNET_CRYPTO_hash (conversion_url, - strlen (conversion_url), + strlen (conversion_url) + 1, &wd.h_conversion_url); TALER_json_hash (debit_restrictions, &wd.h_debit_restrictions); @@ -1203,7 +1203,7 @@ TALER_exchange_wire_signature_make ( &wd.h_wire_details); if (NULL != conversion_url) GNUNET_CRYPTO_hash (conversion_url, - strlen (conversion_url), + strlen (conversion_url) + 1, &wd.h_conversion_url); TALER_json_hash (debit_restrictions, &wd.h_debit_restrictions); From 8e0f9b40c0ffcf3367b9a0cf2e1f0bebda34e0cb Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 2 May 2023 21:48:23 +0200 Subject: [PATCH 20/53] check regex syntax at least --- src/exchange-tools/taler-exchange-offline.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index fbf8a6ac6..80765dd9b 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -24,6 +24,8 @@ #include "taler_json_lib.h" #include "taler_exchange_service.h" #include "taler_extensions.h" +#include + /** * Name of the input for the 'sign' and 'show' operation. @@ -3017,6 +3019,21 @@ parse_restriction (char *const *args, "Mandatory arguments for restriction of type `regex' missing (REGEX, HINT, HINT-I18 required)\n"); return -1; } + { + regex_t ex; + + if (0 != regcomp (&ex, + args[1], + REG_NOSUB | REG_EXTENDED)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid regular expression `%s'\n", + args[1]); + return -1; + } + regfree (&ex); + } + i18n = json_loads (args[3], JSON_REJECT_DUPLICATES, &err); From 82bb911720f80aa5932f83c68b5ac93b58ec19d3 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 2 May 2023 23:23:47 +0200 Subject: [PATCH 21/53] -update gana --- contrib/gana | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/gana b/contrib/gana index d831c7f72..fa2259bd6 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit d831c7f72a5030e20efb4ada7babc103ccd01fab +Subproject commit fa2259bd6824b2c739130218ca4316fb79addfbe From 0b8752bb1b0f61e9d140e4d89a612ddea01c56e2 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 3 May 2023 20:25:39 +0200 Subject: [PATCH 22/53] -more readable, no semantic change --- contrib/gana | 2 +- src/lib/exchange_api_transfers_get.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/gana b/contrib/gana index fa2259bd6..85736484c 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit fa2259bd6824b2c739130218ca4316fb79addfbe +Subproject commit 85736484cb0da26aded705ebb1e944e8bb1b8504 diff --git a/src/lib/exchange_api_transfers_get.c b/src/lib/exchange_api_transfers_get.c index c2a1c0b15..f00e36ce3 100644 --- a/src/lib/exchange_api_transfers_get.c +++ b/src/lib/exchange_api_transfers_get.c @@ -241,7 +241,6 @@ check_transfers_get_response_ok ( GNUNET_free (details); } GNUNET_JSON_parse_free (spec); - TALER_EXCHANGE_transfers_get_cancel (wdh); return GNUNET_OK; } @@ -276,7 +275,10 @@ handle_transfers_get_finished (void *cls, if (GNUNET_OK == check_transfers_get_response_ok (wdh, j)) + { + TALER_EXCHANGE_transfers_get_cancel (wdh); return; + } GNUNET_break_op (0); tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; tgr.hr.http_status = 0; From 4c1a2c030761fc64773d035df6cdd01db0204d13 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 4 May 2023 14:42:06 +0200 Subject: [PATCH 23/53] more shared logic for argument/header parsing --- .../taler-exchange-httpd_deposits_get.c | 158 ++++++++------- src/exchange/taler-exchange-httpd_kyc-check.c | 30 +-- src/exchange/taler-exchange-httpd_kyc-proof.c | 27 +-- .../taler-exchange-httpd_purses_delete.c | 26 +-- .../taler-exchange-httpd_purses_get.c | 32 +-- .../taler-exchange-httpd_reserves_get.c | 33 +--- src/include/taler_mhd_lib.h | 182 +++++++++++++++++- src/mhd/mhd_parsing.c | 112 +++++++++-- 8 files changed, 380 insertions(+), 220 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_deposits_get.c b/src/exchange/taler-exchange-httpd_deposits_get.c index ebbb13e08..26c9e2618 100644 --- a/src/exchange/taler-exchange-httpd_deposits_get.c +++ b/src/exchange/taler-exchange-httpd_deposits_get.c @@ -64,6 +64,12 @@ struct DepositWtidContext */ struct TALER_WireTransferIdentifierRawP wtid; + /** + * Signature by the merchant. + */ + struct TALER_MerchantSignatureP merchant_sig; + + /** * Set by #handle_wtid data to the coin's contribution to the wire transfer. */ @@ -275,89 +281,103 @@ handle_track_transaction_request ( } +/** + * Function called to clean up a context. + * + * @param rc request context with data to clean up + */ +static void +dwc_cleaner (struct TEH_RequestContext *rc) +{ + struct DepositWtidContext *ctx = rc->rh_ctx; + + GNUNET_free (ctx); +} + + MHD_RESULT TEH_handler_deposits_get (struct TEH_RequestContext *rc, const char *const args[4]) { - enum GNUNET_GenericReturnValue res; - struct TALER_MerchantSignatureP merchant_sig; - struct DepositWtidContext ctx; + struct DepositWtidContext *ctx = rc->rh_ctx; - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[0], - strlen (args[0]), - &ctx.h_wire, - sizeof (ctx.h_wire))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE, - args[0]); - } - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[1], - strlen (args[1]), - &ctx.merchant, - sizeof (ctx.merchant))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB, - args[1]); - } - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[2], - strlen (args[2]), - &ctx.h_contract_terms, - sizeof (ctx.h_contract_terms))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS, - args[2]); - } - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[3], - strlen (args[3]), - &ctx.coin_pub, - sizeof (ctx.coin_pub))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB, - args[3]); - } - res = TALER_MHD_parse_request_arg_data (rc->connection, - "merchant_sig", - &merchant_sig, - sizeof (merchant_sig)); - if (GNUNET_SYSERR == res) - return MHD_NO; /* internal error */ - if (GNUNET_NO == res) - return MHD_YES; /* parse error */ - TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + if (NULL == ctx) { + ctx = GNUNET_new (struct DepositWtidContext); + rc->rh_ctx = ctx; + rc->rh_cleaner = &dwc_cleaner; + if (GNUNET_OK != - TALER_merchant_deposit_verify (&ctx.merchant, - &ctx.coin_pub, - &ctx.h_contract_terms, - &ctx.h_wire, - &merchant_sig)) + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &ctx->h_wire, + sizeof (ctx->h_wire))) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID, - NULL); + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE, + args[0]); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[1], + strlen (args[1]), + &ctx->merchant, + sizeof (ctx->merchant))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB, + args[1]); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[2], + strlen (args[2]), + &ctx->h_contract_terms, + sizeof (ctx->h_contract_terms))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS, + args[2]); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[3], + strlen (args[3]), + &ctx->coin_pub, + sizeof (ctx->coin_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB, + args[3]); + } + TALER_MHD_parse_request_arg_auto_t (rc->connection, + "merchant_sig", + &ctx->merchant_sig); + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + { + if (GNUNET_OK != + TALER_merchant_deposit_verify (&ctx->merchant, + &ctx->coin_pub, + &ctx->h_contract_terms, + &ctx->h_wire, + &ctx->merchant_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID, + NULL); + } } } return handle_track_transaction_request (rc->connection, - &ctx); + ctx); } diff --git a/src/exchange/taler-exchange-httpd_kyc-check.c b/src/exchange/taler-exchange-httpd_kyc-check.c index bf4e4dea1..0372573bb 100644 --- a/src/exchange/taler-exchange-httpd_kyc-check.c +++ b/src/exchange/taler-exchange-httpd_kyc-check.c @@ -520,34 +520,8 @@ TEH_handler_kyc_check ( "usertype"); } - { - const char *ts; - - ts = MHD_lookup_connection_value (rc->connection, - MHD_GET_ARGUMENT_KIND, - "timeout_ms"); - if (NULL != ts) - { - char dummy; - unsigned long long tms; - - if (1 != - sscanf (ts, - "%llu%c", - &tms, - &dummy)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "timeout_ms"); - } - kyp->timeout = GNUNET_TIME_relative_to_absolute ( - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - tms)); - } - } + TALER_MHD_parse_request_timeout (rc->connection, + &kyp->timeout); } if ( (NULL == kyp->eh) && diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c index 6d06f0c82..9668ee54c 100644 --- a/src/exchange/taler-exchange-httpd_kyc-proof.c +++ b/src/exchange/taler-exchange-httpd_kyc-proof.c @@ -297,7 +297,6 @@ TEH_handler_kyc_proof ( { struct KycProofContext *kpc = rc->rh_ctx; const char *provider_section_or_logic = args[0]; - const char *h_payto; if (NULL == kpc) { @@ -310,33 +309,13 @@ TEH_handler_kyc_proof ( TALER_EC_GENERIC_ENDPOINT_UNKNOWN, "'/kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO' required"); } - h_payto = MHD_lookup_connection_value (rc->connection, - MHD_GET_ARGUMENT_KIND, - "state"); - if (NULL == h_payto) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MISSING, - "h_payto"); - } kpc = GNUNET_new (struct KycProofContext); kpc->rc = rc; rc->rh_ctx = kpc; rc->rh_cleaner = &clean_kpc; - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (h_payto, - strlen (h_payto), - &kpc->h_payto, - sizeof (kpc->h_payto))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "h_payto"); - } + TALER_MHD_parse_request_arg_auto_t (rc->connection, + "state", + &kpc->h_payto); if (GNUNET_OK != TALER_KYCLOGIC_lookup_logic (provider_section_or_logic, &kpc->logic, diff --git a/src/exchange/taler-exchange-httpd_purses_delete.c b/src/exchange/taler-exchange-httpd_purses_delete.c index 58cc78250..5bf7c24c9 100644 --- a/src/exchange/taler-exchange-httpd_purses_delete.c +++ b/src/exchange/taler-exchange-httpd_purses_delete.c @@ -57,29 +57,9 @@ TEH_handler_purses_delete ( TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED, args[0]); } - { - const char *sig; - - sig = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - "Taler-Purse-Signature"); - if ( (NULL == sig) || - (GNUNET_OK != - GNUNET_STRINGS_string_to_data (sig, - strlen (sig), - &purse_sig, - sizeof (purse_sig))) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - (NULL == sig) - ? TALER_EC_GENERIC_PARAMETER_MISSING - : TALER_EC_GENERIC_PARAMETER_MALFORMED, - "Taler-Purse-Signature"); - } - } - + TALER_MHD_parse_request_header_auto_t (connection, + "Taler-Purse-Signature", + &purse_sig); if (GNUNET_OK != TALER_wallet_purse_delete_verify (&purse_pub, &purse_sig)) diff --git a/src/exchange/taler-exchange-httpd_purses_get.c b/src/exchange/taler-exchange-httpd_purses_get.c index 434798a80..613372357 100644 --- a/src/exchange/taler-exchange-httpd_purses_get.c +++ b/src/exchange/taler-exchange-httpd_purses_get.c @@ -243,36 +243,8 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc, args[1]); } - { - const char *long_poll_timeout_ms; - - long_poll_timeout_ms - = MHD_lookup_connection_value (rc->connection, - MHD_GET_ARGUMENT_KIND, - "timeout_ms"); - if (NULL != long_poll_timeout_ms) - { - unsigned int timeout_ms; - char dummy; - struct GNUNET_TIME_Relative timeout; - - if (1 != sscanf (long_poll_timeout_ms, - "%u%c", - &timeout_ms, - &dummy)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "timeout_ms (must be non-negative number)"); - } - timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - timeout_ms); - gc->timeout = GNUNET_TIME_relative_to_absolute (timeout); - } - } - + TALER_MHD_parse_request_timeout (rc->connection, + &gc->timeout); if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) && (NULL == gc->eh) ) { diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c b/src/exchange/taler-exchange-httpd_reserves_get.c index 21c0b74aa..3ffbda293 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get.c +++ b/src/exchange/taler-exchange-httpd_reserves_get.c @@ -178,9 +178,6 @@ TEH_handler_reserves_get (struct TEH_RequestContext *rc, if (NULL == rp) { - struct GNUNET_TIME_Relative timeout - = GNUNET_TIME_UNIT_ZERO; - rp = GNUNET_new (struct ReservePoller); rp->connection = rc->connection; rp->rc = rc; @@ -201,34 +198,8 @@ TEH_handler_reserves_get (struct TEH_RequestContext *rc, TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, args[0]); } - { - const char *long_poll_timeout_ms; - - long_poll_timeout_ms - = MHD_lookup_connection_value (rc->connection, - MHD_GET_ARGUMENT_KIND, - "timeout_ms"); - if (NULL != long_poll_timeout_ms) - { - unsigned int timeout_ms; - char dummy; - - if (1 != sscanf (long_poll_timeout_ms, - "%u%c", - &timeout_ms, - &dummy)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "timeout_ms (must be non-negative number)"); - } - timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - timeout_ms); - } - } - rp->timeout = GNUNET_TIME_relative_to_absolute (timeout); + TALER_MHD_parse_request_timeout (rc->connection, + &rp->timeout); } if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) && diff --git a/src/include/taler_mhd_lib.h b/src/include/taler_mhd_lib.h index b64231352..e2e8ecb08 100644 --- a/src/include/taler_mhd_lib.h +++ b/src/include/taler_mhd_lib.h @@ -437,7 +437,47 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection, /** - * Extract fixed-size base32crockford encoded data from request. + * Extract optional "timeout_ms" argument from request. + * + * @param connection the MHD connection + * @param[out] expiration set to #GNUNET_TIME_UNIT_ZERO_ABS if there was no timeout, + * the current time plus the value given under "timeout_ms" otherwise + * @return #GNUNET_OK on success, #GNUNET_NO if an + * error was returned on @a connection (caller should return #MHD_YES) and + * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO) + */ +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_timeout (struct MHD_Connection *connection, + struct GNUNET_TIME_Absolute *expiration); + + +/** + * Extract optional "timeout_ms" argument from request. + * Macro that *returns* #MHD_YES/#MHD_NO if the "timeout_ms" + * argument existed but failed to parse. + * + * @param connection the MHD connection + * @param[out] expiration set to #GNUNET_TIME_UNIT_ZERO_ABS if there was no timeout, + * the current time plus the value given under "timeout_ms" otherwise + */ +#define TALER_MHD_parse_request_timeout(connection,expiration) \ + do { \ + switch (TALER_MHD_parse_request_arg_timeout (connection, \ + expiration)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) + + +/** + * Extract fixed-size base32crockford encoded data from request argument. * * Queues an error response to the connection if the parameter is missing or * invalid. @@ -446,16 +486,152 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection, * @param param_name the name of the parameter with the key * @param[out] out_data pointer to store the result * @param out_size expected size of @a out_data + * @param[out] present set to true if argument was found * @return * #GNUNET_YES if the the argument is present - * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_NO if the argument is malformed * #GNUNET_SYSERR on internal error (error response could not be sent) */ enum GNUNET_GenericReturnValue TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection, const char *param_name, void *out_data, - size_t out_size); + size_t out_size, + bool *present); + + +/** + * Extract fixed-size base32crockford encoded data from request header. + * + * Queues an error response to the connection if the parameter is missing or + * invalid. + * + * @param connection the MHD connection + * @param header_name the name of the HTTP header with the value + * @param[out] out_data pointer to store the result + * @param out_size expected size of @a out_data + * @param[out] present set to true if argument was found + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, + const char *header_name, + void *out_data, + size_t out_size, + bool *present); + +/** + * Extract fixed-size base32crockford encoded data from request. + * + * @param connection the MHD connection + * @param name the name of the parameter with the key + * @param[out] out_data pointer to store the result, type must determine size + * @param[in,out] required pass true to require presence of this argument; if 'false' + * set to true if the argument was found + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +#define TALER_MHD_parse_request_arg_auto(connection,name,val,required) \ + do { \ + bool p; \ + switch (TALER_MHD_parse_request_arg_data (connection, name, \ + val, sizeof (*val), &p)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + return MHD_YES; \ + case GNUNET_OK: \ + if (required & (! p)) \ + return TALER_MHD_reply_with_error ( \ + connection, \ + MHD_HTTP_BAD_REQUEST, \ + TALER_EC_GENERIC_PARAMETER_MISSING, \ + name); \ + required = p; \ + break; \ + } \ + } while (0) + + +/** + * Extract required fixed-size base32crockford encoded data from request. + * + * @param connection the MHD connection + * @param name the name of the parameter with the key + * @param[out] out_data pointer to store the result, type must determine size + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +#define TALER_MHD_parse_request_arg_auto_t(connection,name,val) \ + do { \ + bool b = true; \ + TALER_MHD_parse_request_arg_auto (connection,name,val,b); \ + } while (0) + +/** + * Extract fixed-size base32crockford encoded data from request. + * + * @param connection the MHD connection + * @param name the name of the header with the key + * @param[out] out_data pointer to store the result, type must determine size + * @param[in,out] required pass true to require presence of this argument; if 'false' + * set to true if the argument was found + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +#define TALER_MHD_parse_request_header_auto(connection,name,val,required) \ + do { \ + bool p; \ + switch (TALER_MHD_parse_request_header_data (connection, name, \ + val, sizeof (*val), &p)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + return MHD_YES; \ + case GNUNET_OK: \ + if (required & (! p)) \ + return TALER_MHD_reply_with_error ( \ + connection, \ + MHD_HTTP_BAD_REQUEST, \ + TALER_EC_GENERIC_PARAMETER_MISSING, \ + name); \ + required = p; \ + break; \ + } \ + } while (0) + + +/** + * Extract required fixed-size base32crockford encoded data from request. + * + * @param connection the MHD connection + * @param name the name of the header with the key + * @param[out] out_data pointer to store the result, type must determine size + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +#define TALER_MHD_parse_request_header_auto_t(connection,name,val) \ + do { \ + bool b = true; \ + TALER_MHD_parse_request_header_auto (connection,name,val,b); \ + } while (0) /** diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c index ee647f4b7..e76450831 100644 --- a/src/mhd/mhd_parsing.c +++ b/src/mhd/mhd_parsing.c @@ -86,25 +86,40 @@ TALER_MHD_parse_post_cleanup_callback (void *con_cls) } -enum GNUNET_GenericReturnValue -TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection, - const char *param_name, - void *out_data, - size_t out_size) +/** + * Extract fixed-size base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is missing or + * invalid. + * + * @param connection the MHD connection + * @param param_name the name of the HTTP key with the value + * @param kind whether to extract from header, argument or footer + * @param[out] out_data pointer to store the result + * @param out_size expected size of @a out_data + * @param[out] present set to true if argument was found + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +static enum GNUNET_GenericReturnValue +parse_request_data (struct MHD_Connection *connection, + const char *param_name, + enum MHD_ValueKind kind, + void *out_data, + size_t out_size, + bool *present) { const char *str; str = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, + kind, param_name); if (NULL == str) { - return (MHD_NO == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MISSING, - param_name)) - ? GNUNET_SYSERR : GNUNET_NO; + *present = false; + return GNUNET_OK; } if (GNUNET_OK != GNUNET_STRINGS_string_to_data (str, @@ -117,6 +132,79 @@ TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection, TALER_EC_GENERIC_PARAMETER_MALFORMED, param_name)) ? GNUNET_SYSERR : GNUNET_NO; + *present = true; + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection, + const char *param_name, + void *out_data, + size_t out_size, + bool *present) +{ + return parse_request_data (connection, + param_name, + MHD_GET_ARGUMENT_KIND, + out_data, + out_size, + present); +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, + const char *header_name, + void *out_data, + size_t out_size, + bool *present) +{ + return parse_request_data (connection, + header_name, + MHD_HEADER_KIND, + out_data, + out_size, + present); +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_timeout (struct MHD_Connection *connection, + struct GNUNET_TIME_Absolute *expiration) +{ + const char *ts; + char dummy; + unsigned long long tms; + + ts = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "timeout_ms"); + if (NULL == ts) + { + *expiration = GNUNET_TIME_UNIT_ZERO_ABS; + return GNUNET_OK; + } + if (1 != + sscanf (ts, + "%llu%c", + &tms, + &dummy)) + { + MHD_RESULT mret; + + GNUNET_break_op (0); + mret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "timeout_ms"); + return (MHD_YES == mret) + ? GNUNET_NO + : GNUNET_SYSERR; + } + *expiration = GNUNET_TIME_relative_to_absolute ( + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + tms)); return GNUNET_OK; } From 2de2b6e3cfbd6e766d02b3c44e2238aafaa91baa Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 4 May 2023 16:04:29 +0200 Subject: [PATCH 24/53] -fix crypto test --- src/util/test_crypto.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/util/test_crypto.c b/src/util/test_crypto.c index 80a247770..80ce9083d 100644 --- a/src/util/test_crypto.c +++ b/src/util/test_crypto.c @@ -355,15 +355,24 @@ test_exchange_sigs (void) struct TALER_MasterPrivateKeyP priv; struct TALER_MasterPublicKeyP pub; struct TALER_MasterSignatureP sig; + json_t *rest; GNUNET_CRYPTO_eddsa_key_create (&priv.eddsa_priv); + rest = json_array (); + GNUNET_assert (NULL != rest); TALER_exchange_wire_signature_make (pt, + NULL, + rest, + rest, &priv, &sig); GNUNET_CRYPTO_eddsa_key_get_public (&priv.eddsa_priv, &pub.eddsa_pub); if (GNUNET_OK != TALER_exchange_wire_signature_check (pt, + NULL, + rest, + rest, &pub, &sig)) { @@ -373,12 +382,28 @@ test_exchange_sigs (void) if (GNUNET_OK == TALER_exchange_wire_signature_check ( "payto://x-taler-bank/localhost/Other", + NULL, + rest, + rest, &pub, &sig)) { GNUNET_break (0); return 1; } + if (GNUNET_OK == + TALER_exchange_wire_signature_check ( + pt, + "http://example.com/", + rest, + rest, + &pub, + &sig)) + { + GNUNET_break (0); + return 1; + } + json_decref (rest); return 0; } From 7c0de44a2b62fe47168eaa857c038901533bd48b Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 4 May 2023 17:14:54 +0200 Subject: [PATCH 25/53] towards LP support for GET /deposits (#7808) --- src/exchange/taler-exchange-httpd.c | 1 + .../taler-exchange-httpd_deposits_get.c | 165 ++++++++++++++++-- .../taler-exchange-httpd_deposits_get.h | 7 + .../taler-exchange-httpd_reserves_get.c | 3 +- src/include/taler_exchangedb_plugin.h | 30 +++- 5 files changed, 189 insertions(+), 17 deletions(-) diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index d247d981b..ac3eae272 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -2215,6 +2215,7 @@ do_shutdown (void *cls) mhd = TALER_MHD_daemon_stop (); TEH_resume_keys_requests (true); + TEH_deposits_get_cleanup (); TEH_reserves_get_cleanup (); TEH_purses_get_cleanup (); TEH_kyc_check_cleanup (); diff --git a/src/exchange/taler-exchange-httpd_deposits_get.c b/src/exchange/taler-exchange-httpd_deposits_get.c index 26c9e2618..10b4af517 100644 --- a/src/exchange/taler-exchange-httpd_deposits_get.c +++ b/src/exchange/taler-exchange-httpd_deposits_get.c @@ -23,6 +23,7 @@ #include #include #include +#include "taler_dbevents.h" #include "taler_json_lib.h" #include "taler_mhd_lib.h" #include "taler_signatures.h" @@ -37,6 +38,26 @@ struct DepositWtidContext { + /** + * Kept in a DLL. + */ + struct DepositWtidContext *next; + + /** + * Kept in a DLL. + */ + struct DepositWtidContext *prev; + + /** + * Context for the request we are processing. + */ + struct TEH_RequestContext *rc; + + /** + * Subscription for the database event we are waiting for. + */ + struct GNUNET_DB_EventHandler *eh; + /** * Hash over the proposal data of the contract for which this deposit is made. */ @@ -85,6 +106,11 @@ struct DepositWtidContext */ struct GNUNET_TIME_Timestamp execution_time; + /** + * Timeout of the request, for long-polling. + */ + struct GNUNET_TIME_Absolute timeout; + /** * Set by #handle_wtid to the coin contribution to the transaction * (that is, @e coin_contribution minus @e coin_fee). @@ -107,9 +133,45 @@ struct DepositWtidContext * Set to #GNUNET_SYSERR if there was a serious error. */ enum GNUNET_GenericReturnValue pending; + + /** + * #GNUNET_YES if we were suspended, #GNUNET_SYSERR + * if we were woken up due to shutdown. + */ + enum GNUNET_GenericReturnValue suspended; }; +/** + * Head of DLL of suspended requests. + */ +static struct DepositWtidContext *dwc_head; + +/** + * Tail of DLL of suspended requests. + */ +static struct DepositWtidContext *dwc_tail; + + +void +TEH_deposits_get_cleanup () +{ + struct DepositWtidContext *n; + for (struct DepositWtidContext *ctx = dwc_head; + NULL != ctx; + ctx = n) + { + n = ctx->next; + GNUNET_assert (GNUNET_YES == ctx->suspended); + ctx->suspended = GNUNET_SYSERR; + MHD_resume_connection (ctx->rc->connection); + GNUNET_CONTAINER_DLL_remove (dwc_head, + dwc_tail, + ctx); + } +} + + /** * A merchant asked for details about a deposit. Provide * them. Generates the 200 reply. @@ -233,34 +295,99 @@ deposits_get_transaction (void *cls, } +/** + * Function called on events received from Postgres. + * Wakes up long pollers. + * + * @param cls the `struct DepositWtidContext *` + * @param extra additional event data provided + * @param extra_size number of bytes in @a extra + */ +static void +db_event_cb (void *cls, + const void *extra, + size_t extra_size) +{ + struct DepositWtidContext *ctx = cls; + struct GNUNET_AsyncScopeSave old_scope; + + (void) extra; + (void) extra_size; + if (GNUNET_NO != ctx->suspended) + return; /* might get multiple wake-up events */ + GNUNET_CONTAINER_DLL_remove (dwc_head, + dwc_tail, + ctx); + GNUNET_async_scope_enter (&ctx->rc->async_scope_id, + &old_scope); + TEH_check_invariants (); + ctx->suspended = GNUNET_NO; + MHD_resume_connection (ctx->rc->connection); + TALER_MHD_daemon_trigger (); + TEH_check_invariants (); + GNUNET_async_scope_restore (&old_scope); +} + + /** * Lookup and return the wire transfer identifier. * - * @param connection the MHD connection to handle * @param ctx context of the signed request to execute * @return MHD result code */ static MHD_RESULT handle_track_transaction_request ( - struct MHD_Connection *connection, struct DepositWtidContext *ctx) { - MHD_RESULT mhd_ret; + struct MHD_Connection *connection = ctx->rc->connection; - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "handle deposits GET", - TEH_MT_REQUEST_OTHER, - &mhd_ret, - &deposits_get_transaction, - ctx)) - return mhd_ret; + if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) && + (NULL == ctx->eh) ) + { + struct TALER_CoinDepositEventP rep = { + .header.size = htons (sizeof (rep)), + .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED), + .coin_pub = ctx->coin_pub, + .merchant_pub = ctx->merchant, + .h_wire = ctx->h_wire + }; + + ctx->eh = TEH_plugin->event_listen ( + TEH_plugin->cls, + GNUNET_TIME_absolute_get_remaining (ctx->timeout), + &rep.header, + &db_event_cb, + ctx); + } + { + MHD_RESULT mhd_ret; + + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + "handle deposits GET", + TEH_MT_REQUEST_OTHER, + &mhd_ret, + &deposits_get_transaction, + ctx)) + return mhd_ret; + } if (GNUNET_SYSERR == ctx->pending) return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_INVARIANT_FAILURE, "wire fees exceed aggregate in database"); - if (ctx->pending) + if (GNUNET_YES == ctx->pending) + { + if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) && + (GNUNET_NO == ctx->suspended) ) + { + GNUNET_CONTAINER_DLL_insert (dwc_head, + dwc_tail, + ctx); + ctx->suspended = GNUNET_YES; + MHD_suspend_connection (connection); + return MHD_YES; + } return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_ACCEPTED, @@ -276,6 +403,7 @@ handle_track_transaction_request ( ctx->kyc.ok), GNUNET_JSON_pack_timestamp ("execution_time", ctx->execution_time)); + } return reply_deposit_details (connection, ctx); } @@ -291,6 +419,13 @@ dwc_cleaner (struct TEH_RequestContext *rc) { struct DepositWtidContext *ctx = rc->rh_ctx; + GNUNET_assert (GNUNET_NO == ctx->suspended); + if (NULL != ctx->eh) + { + TEH_plugin->event_listen_cancel (TEH_plugin->cls, + ctx->eh); + ctx->eh = NULL; + } GNUNET_free (ctx); } @@ -304,6 +439,7 @@ TEH_handler_deposits_get (struct TEH_RequestContext *rc, if (NULL == ctx) { ctx = GNUNET_new (struct DepositWtidContext); + ctx->rc = rc; rc->rh_ctx = ctx; rc->rh_cleaner = &dwc_cleaner; @@ -358,6 +494,8 @@ TEH_handler_deposits_get (struct TEH_RequestContext *rc, TALER_MHD_parse_request_arg_auto_t (rc->connection, "merchant_sig", &ctx->merchant_sig); + TALER_MHD_parse_request_timeout (rc->connection, + &ctx->timeout); TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; { if (GNUNET_OK != @@ -376,8 +514,7 @@ TEH_handler_deposits_get (struct TEH_RequestContext *rc, } } - return handle_track_transaction_request (rc->connection, - ctx); + return handle_track_transaction_request (ctx); } diff --git a/src/exchange/taler-exchange-httpd_deposits_get.h b/src/exchange/taler-exchange-httpd_deposits_get.h index aee7521a5..c7b1698bb 100644 --- a/src/exchange/taler-exchange-httpd_deposits_get.h +++ b/src/exchange/taler-exchange-httpd_deposits_get.h @@ -26,6 +26,13 @@ #include "taler-exchange-httpd.h" +/** + * Resume long pollers on GET /deposits. + */ +void +TEH_deposits_get_cleanup (void); + + /** * Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB" * request. diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c b/src/exchange/taler-exchange-httpd_reserves_get.c index 3ffbda293..bbaac8535 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get.c +++ b/src/exchange/taler-exchange-httpd_reserves_get.c @@ -57,8 +57,7 @@ struct ReservePoller struct TEH_RequestContext *rc; /** - * Subscription for the database event we are - * waiting for. + * Subscription for the database event we are waiting for. */ struct GNUNET_DB_EventHandler *eh; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index d025b5327..d55f96421 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -149,7 +149,35 @@ struct TALER_EXCHANGEDB_DenominationKeyInformation GNUNET_NETWORK_STRUCT_BEGIN /** - * Signature of events signalling a reserve got funding. + * Events signalling that a coin deposit status + * changed. + */ +struct TALER_CoinDepositEventP +{ + /** + * Of type #TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED. + */ + struct GNUNET_DB_EventHeaderP header; + + /** + * The coin's public key. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * The Merchant's public key. + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + /** + * Hash over the wiring information of the merchant. + */ + struct TALER_MerchantWireHashP h_wire; + +}; + +/** + * Events signalling a reserve got funding. */ struct TALER_ReserveEventP { From 1e88796045ca0216b6c83234522423d1f9831fdd Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 4 May 2023 17:36:43 +0200 Subject: [PATCH 26/53] finish implementation for #7808 --- .../taler-exchange-httpd_deposits_get.c | 6 +- src/exchangedb/pg_aggregate.c | 69 +++++++++++-------- src/include/taler_exchange_service.h | 2 + src/include/taler_exchangedb_plugin.h | 12 +--- src/lib/exchange_api_deposits_get.c | 26 ++++++- src/testing/testing_api_cmd_deposits_get.c | 1 + 6 files changed, 70 insertions(+), 46 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_deposits_get.c b/src/exchange/taler-exchange-httpd_deposits_get.c index 10b4af517..818900c60 100644 --- a/src/exchange/taler-exchange-httpd_deposits_get.c +++ b/src/exchange/taler-exchange-httpd_deposits_get.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2017, 2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -347,9 +347,7 @@ handle_track_transaction_request ( struct TALER_CoinDepositEventP rep = { .header.size = htons (sizeof (rep)), .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED), - .coin_pub = ctx->coin_pub, - .merchant_pub = ctx->merchant, - .h_wire = ctx->h_wire + .merchant_pub = ctx->merchant }; ctx->eh = TEH_plugin->event_listen ( diff --git a/src/exchangedb/pg_aggregate.c b/src/exchangedb/pg_aggregate.c index 6e94cbebb..76d0adec3 100644 --- a/src/exchangedb/pg_aggregate.c +++ b/src/exchangedb/pg_aggregate.c @@ -22,6 +22,7 @@ #include "taler_error_codes.h" #include "taler_dbevents.h" #include "taler_pq_lib.h" +#include "pg_event_notify.h" #include "pg_aggregate.h" #include "pg_helper.h" @@ -35,34 +36,12 @@ TEH_PG_aggregate ( { struct PostgresClosure *pg = cls; struct GNUNET_TIME_Absolute now = {0}; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_absolute_time (&now), - GNUNET_PQ_query_param_auto_from_type (merchant_pub), - GNUNET_PQ_query_param_auto_from_type (h_payto), - GNUNET_PQ_query_param_auto_from_type (wtid), - GNUNET_PQ_query_param_end - }; uint64_t sum_deposit_value; uint64_t sum_deposit_frac; uint64_t sum_refund_value; uint64_t sum_refund_frac; uint64_t sum_fee_value; uint64_t sum_fee_frac; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("sum_deposit_value", - &sum_deposit_value), - GNUNET_PQ_result_spec_uint64 ("sum_deposit_fraction", - &sum_deposit_frac), - GNUNET_PQ_result_spec_uint64 ("sum_refund_value", - &sum_refund_value), - GNUNET_PQ_result_spec_uint64 ("sum_refund_fraction", - &sum_refund_frac), - GNUNET_PQ_result_spec_uint64 ("sum_fee_value", - &sum_fee_value), - GNUNET_PQ_result_spec_uint64 ("sum_fee_fraction", - &sum_fee_frac), - GNUNET_PQ_result_spec_end - }; enum GNUNET_DB_QueryStatus qs; struct TALER_Amount sum_deposit; struct TALER_Amount sum_refund; @@ -71,8 +50,6 @@ TEH_PG_aggregate ( now = GNUNET_TIME_absolute_round_down (GNUNET_TIME_absolute_get (), pg->aggregator_shift); - - /* Used in #postgres_aggregate() */ PREPARE (pg, "aggregate", "WITH dep AS (" /* restrict to our merchant and account and mark as done */ @@ -148,11 +125,35 @@ TEH_PG_aggregate ( " FULL OUTER JOIN ref ON (FALSE)" /* We just want all sums */ " FULL OUTER JOIN fees ON (FALSE);"); + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_auto_from_type (merchant_pub), + GNUNET_PQ_query_param_auto_from_type (h_payto), + GNUNET_PQ_query_param_auto_from_type (wtid), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("sum_deposit_value", + &sum_deposit_value), + GNUNET_PQ_result_spec_uint64 ("sum_deposit_fraction", + &sum_deposit_frac), + GNUNET_PQ_result_spec_uint64 ("sum_refund_value", + &sum_refund_value), + GNUNET_PQ_result_spec_uint64 ("sum_refund_fraction", + &sum_refund_frac), + GNUNET_PQ_result_spec_uint64 ("sum_fee_value", + &sum_fee_value), + GNUNET_PQ_result_spec_uint64 ("sum_fee_fraction", + &sum_fee_frac), + GNUNET_PQ_result_spec_end + }; - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "aggregate", - params, - rs); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "aggregate", + params, + rs); + } if (qs < 0) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); @@ -165,6 +166,18 @@ TEH_PG_aggregate ( total)); return qs; } + { + struct TALER_CoinDepositEventP rep = { + .header.size = htons (sizeof (rep)), + .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED), + .merchant_pub = *merchant_pub + }; + + TEH_PG_event_notify (pg, + &rep.header, + NULL, + 0); + } GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (pg->currency, &sum_deposit)); diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index fc5fb284b..3769315e8 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -3546,6 +3546,7 @@ typedef void * @param h_wire hash of merchant's wire transfer details * @param h_contract_terms hash of the proposal data * @param coin_pub public key of the coin + * @param timeout timeout to use for long-polling, 0 for no long polling * @param cb function to call with the result * @param cb_cls closure for @a cb * @return handle to abort request @@ -3557,6 +3558,7 @@ TALER_EXCHANGE_deposits_get ( const struct TALER_MerchantWireHashP *h_wire, const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct GNUNET_TIME_Relative timeout, TALER_EXCHANGE_DepositGetCallback cb, void *cb_cls); diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index d55f96421..3a6ba6514 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -160,20 +160,10 @@ struct TALER_CoinDepositEventP struct GNUNET_DB_EventHeaderP header; /** - * The coin's public key. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * The Merchant's public key. + * Public key of the merchant. */ struct TALER_MerchantPublicKeyP merchant_pub; - /** - * Hash over the wiring information of the merchant. - */ - struct TALER_MerchantWireHashP h_wire; - }; /** diff --git a/src/lib/exchange_api_deposits_get.c b/src/lib/exchange_api_deposits_get.c index bd5f2f653..9ec25e45a 100644 --- a/src/lib/exchange_api_deposits_get.c +++ b/src/lib/exchange_api_deposits_get.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -260,6 +260,7 @@ TALER_EXCHANGE_deposits_get ( const struct TALER_MerchantWireHashP *h_wire, const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct GNUNET_TIME_Relative timeout, TALER_EXCHANGE_DepositGetCallback cb, void *cb_cls) { @@ -293,6 +294,7 @@ TALER_EXCHANGE_deposits_get ( char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2]; char chash_str[sizeof (struct TALER_PrivateContractHashP) * 2]; char whash_str[sizeof (struct TALER_MerchantWireHashP) * 2]; + char timeout_str[24]; char *end; end = GNUNET_STRINGS_data_to_string (h_wire, @@ -320,15 +322,33 @@ TALER_EXCHANGE_deposits_get ( msig_str, sizeof (msig_str)); *end = '\0'; + if (GNUNET_TIME_relative_is_zero (timeout)) + { + timeout_str[0] = '\0'; + } + else + { + GNUNET_snprintf ( + timeout_str, + sizeof (timeout_str), + "%llu", + (unsigned long long) ( + timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us)); + } GNUNET_snprintf (arg_str, sizeof (arg_str), - "/deposits/%s/%s/%s/%s?merchant_sig=%s", + "/deposits/%s/%s/%s/%s?merchant_sig=%s%s%s", whash_str, mpub_str, chash_str, cpub_str, - msig_str); + msig_str, + GNUNET_TIME_relative_is_zero (timeout) + ? "" + : "&timeout_ms=", + timeout_str); } dwh = GNUNET_new (struct TALER_EXCHANGE_DepositGetHandle); diff --git a/src/testing/testing_api_cmd_deposits_get.c b/src/testing/testing_api_cmd_deposits_get.c index 8f7970895..c39d7f6c1 100644 --- a/src/testing/testing_api_cmd_deposits_get.c +++ b/src/testing/testing_api_cmd_deposits_get.c @@ -281,6 +281,7 @@ track_transaction_run (void *cls, &h_wire_details, &h_contract_terms, &coin_pub, + GNUNET_TIME_UNIT_ZERO, &deposit_wtid_cb, tts); GNUNET_assert (NULL != tts->tth); From 737b3338ed460b56096b9b016b727a0d34b30d23 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 4 May 2023 21:32:36 +0200 Subject: [PATCH 27/53] -doxygen --- contrib/gnunet.tag | 8 +++++++- src/include/taler_mhd_lib.h | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/contrib/gnunet.tag b/contrib/gnunet.tag index f270ba5ea..bdc112a17 100644 --- a/contrib/gnunet.tag +++ b/contrib/gnunet.tag @@ -31,7 +31,13 @@ #define GNUNET_TIME_UNIT_FOREVER_ABS - gnunet_util_lib.h + gnunet_time_lib.h + + + + #define + GNUNET_TIME_UNIT_ZERO_ABS + gnunet_time_lib.h diff --git a/src/include/taler_mhd_lib.h b/src/include/taler_mhd_lib.h index e2e8ecb08..9c7c06d9a 100644 --- a/src/include/taler_mhd_lib.h +++ b/src/include/taler_mhd_lib.h @@ -528,7 +528,7 @@ TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, * * @param connection the MHD connection * @param name the name of the parameter with the key - * @param[out] out_data pointer to store the result, type must determine size + * @param[out] val pointer to store the result, type must determine size * @param[in,out] required pass true to require presence of this argument; if 'false' * set to true if the argument was found * @return @@ -566,7 +566,7 @@ TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, * * @param connection the MHD connection * @param name the name of the parameter with the key - * @param[out] out_data pointer to store the result, type must determine size + * @param[out] val pointer to store the result, type must determine size * @return * #GNUNET_YES if the the argument is present * #GNUNET_NO if the argument is absent or malformed @@ -583,7 +583,7 @@ TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, * * @param connection the MHD connection * @param name the name of the header with the key - * @param[out] out_data pointer to store the result, type must determine size + * @param[out] val pointer to store the result, type must determine size * @param[in,out] required pass true to require presence of this argument; if 'false' * set to true if the argument was found * @return @@ -621,7 +621,7 @@ TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, * * @param connection the MHD connection * @param name the name of the header with the key - * @param[out] out_data pointer to store the result, type must determine size + * @param[out] val pointer to store the result, type must determine size * @return * #GNUNET_YES if the the argument is present * #GNUNET_NO if the argument is absent or malformed From 1f9427e1d9672b93577aea4c9d5a63575ee0b525 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 6 May 2023 19:43:17 +0200 Subject: [PATCH 28/53] add convenience function for content-length limiation --- contrib/gana | 2 +- src/exchange/taler-exchange-httpd.c | 29 +------------- src/include/taler_mhd_lib.h | 59 +++++++++++++++++++++++++---- src/mhd/mhd_parsing.c | 42 ++++++++++++++++++++ 4 files changed, 96 insertions(+), 36 deletions(-) diff --git a/contrib/gana b/contrib/gana index 85736484c..4654d82b1 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 85736484cb0da26aded705ebb1e944e8bb1b8504 +Subproject commit 4654d82b143cd69dfe7a7bf2f816f6f91f6052e2 diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index ac3eae272..97cf54c86 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -1617,33 +1617,8 @@ handle_mhd_request (void *cls, if (0 == strcasecmp (method, MHD_HTTP_METHOD_POST)) { - const char *cl; - - /* Maybe check for maximum upload size - and refuse requests if they are just too big. */ - cl = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_CONTENT_LENGTH); - if (NULL != cl) - { - unsigned long long cv; - char dummy; - - if (1 != sscanf (cl, - "%llu%c", - &cv, - &dummy)) - { - /* Not valid HTTP request, just close connection. */ - GNUNET_break_op (0); - return MHD_NO; - } - if (cv > TALER_MHD_REQUEST_BUFFER_MAX) - { - GNUNET_break_op (0); - return TALER_MHD_reply_request_too_large (connection); - } - } + TALER_MHD_check_content_length (connection, + TALER_MHD_REQUEST_BUFFER_MAX); } } diff --git a/src/include/taler_mhd_lib.h b/src/include/taler_mhd_lib.h index 9c7c06d9a..e4aa916e7 100644 --- a/src/include/taler_mhd_lib.h +++ b/src/include/taler_mhd_lib.h @@ -596,14 +596,14 @@ TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, bool p; \ switch (TALER_MHD_parse_request_header_data (connection, name, \ val, sizeof (*val), &p)) \ - { \ - case GNUNET_SYSERR: \ - GNUNET_break (0); \ - return MHD_NO; \ - case GNUNET_NO: \ - GNUNET_break_op (0); \ - return MHD_YES; \ - case GNUNET_OK: \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + return MHD_YES; \ + case GNUNET_OK: \ if (required & (! p)) \ return TALER_MHD_reply_with_error ( \ connection, \ @@ -634,6 +634,49 @@ TALER_MHD_parse_request_header_data (struct MHD_Connection *connection, } while (0) +/** + * Check that the 'Content-Length' header is giving + * a length below @a max_len. If not, return an + * appropriate error response and return the + * correct #MHD_YES/#MHD_NO value from this function. + * + * @param connection the MHD connection + * @param max_len maximum allowed content length + * @return + * #GNUNET_YES if the the argument is present + * #GNUNET_NO if the argument is absent or malformed + * #GNUNET_SYSERR on internal error (error response could not be sent) + */ +enum GNUNET_GenericReturnValue +TALER_MHD_check_content_length_ (struct MHD_Connection *connection, + unsigned long long max_len); + + +/** + * Check that the 'Content-Length' header is giving + * a length below @a max_len. If not, return an + * appropriate error response and return the + * correct #MHD_YES/#MHD_NO value from this function. + * + * @param connection the MHD connection + * @param max_len maximum allowed content length + */ +#define TALER_MHD_check_content_length(connection,max_len) \ + do { \ + switch (TALER_MHD_check_content_length_ (connection, max_len)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + return MHD_YES; \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) + + /** * Parse the configuration to determine on which port * or UNIX domain path we should run an HTTP service. diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c index e76450831..b047df7d3 100644 --- a/src/mhd/mhd_parsing.c +++ b/src/mhd/mhd_parsing.c @@ -350,4 +350,46 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection, } +enum GNUNET_GenericReturnValue +TALER_MHD_check_content_length_ (struct MHD_Connection *connection, + unsigned long long max_len) +{ + const char *cl; + unsigned long long cv; + char dummy; + + /* Maybe check for maximum upload size + and refuse requests if they are just too big. */ + cl = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONTENT_LENGTH); + if (NULL == cl) + return GNUNET_OK; + if (1 != sscanf (cl, + "%llu%c", + &cv, + &dummy)) + { + /* Not valid HTTP request, just close connection. */ + GNUNET_break_op (0); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + MHD_HTTP_HEADER_CONTENT_LENGTH)) + ? GNUNET_NO + : GNUNET_SYSERR; + } + if (cv > TALER_MHD_REQUEST_BUFFER_MAX) + { + GNUNET_break_op (0); + return (MHD_YES == + TALER_MHD_reply_request_too_large (connection)) + ? GNUNET_NO + : GNUNET_SYSERR; + } + return GNUNET_OK; +} + + /* end of mhd_parsing.c */ From 404b2b78f187e3da2fedee5748b9bfcdfa4a105c Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 6 May 2023 20:55:40 +0200 Subject: [PATCH 29/53] add convenience function TALER_TEMPLATING_reply_error --- src/include/taler_templating_lib.h | 20 +++++++++++++++ src/mhd/mhd_parsing.c | 11 ++++++++- src/templating/templating_api.c | 39 ++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/include/taler_templating_lib.h b/src/include/taler_templating_lib.h index 53946b9c0..ebda2ecf3 100644 --- a/src/include/taler_templating_lib.h +++ b/src/include/taler_templating_lib.h @@ -91,6 +91,26 @@ TALER_TEMPLATING_reply (struct MHD_Connection *connection, const char *taler_uri, const json_t *root); + +/** + * Load a @a template and substitute an error message based on @a ec and @a + * detail, returning the result to the @a connection with the given @a + * http_status code. + * + * @param connection the connection we act upon + * @param template basename of the template to load + * @param http_status code to use on success + * @param ec error code to return + * @param detail optional text to add to the template + * @return #MHD_YES on success, #MHD_NO to just close the connection + */ +MHD_RESULT +TALER_TEMPLATING_reply_error (struct MHD_Connection *connection, + const char *template_basename, + unsigned int http_status, + enum TALER_ErrorCode ec, + const char *detail); + /** * Preload templates. * diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c index b047df7d3..9e3cb5714 100644 --- a/src/mhd/mhd_parsing.c +++ b/src/mhd/mhd_parsing.c @@ -364,7 +364,16 @@ TALER_MHD_check_content_length_ (struct MHD_Connection *connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH); if (NULL == cl) - return GNUNET_OK; + { + GNUNET_break_op (0); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + MHD_HTTP_HEADER_CONTENT_LENGTH)) + ? GNUNET_NO + : GNUNET_SYSERR; + } if (1 != sscanf (cl, "%llu%c", &cv, diff --git a/src/templating/templating_api.c b/src/templating/templating_api.c index 324e199eb..dba042e5d 100644 --- a/src/templating/templating_api.c +++ b/src/templating/templating_api.c @@ -428,6 +428,45 @@ load_template (void *cls, } +MHD_RESULT +TALER_TEMPLATING_reply_error (struct MHD_Connection *connection, + const char *template_basename, + unsigned int http_status, + enum TALER_ErrorCode ec, + const char *detail) +{ + json_t *data; + enum GNUNET_GenericReturnValue ret; + + data = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("ec", + ec), + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint (ec)), + GNUNET_JSON_pack_string ("detail", + detail) + ); + ret = TALER_TEMPLATING_reply (connection, + http_status, + template_basename, + NULL, + NULL, + data); + json_decref (data); + switch (ret) + { + case GNUNET_OK: + return MHD_YES; + case GNUNET_NO: + return MHD_YES; + case GNUNET_SYSERR: + return MHD_NO; + } + GNUNET_assert (0); + return MHD_NO; +} + + enum GNUNET_GenericReturnValue TALER_TEMPLATING_init (const char *subsystem) { From 1639cefa617435ac4df5a8cd70c298aa1e1a820a Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 6 May 2023 21:29:43 +0200 Subject: [PATCH 30/53] -doxygen --- contrib/gana | 2 +- src/include/taler_templating_lib.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/gana b/contrib/gana index 4654d82b1..e50e37672 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 4654d82b143cd69dfe7a7bf2f816f6f91f6052e2 +Subproject commit e50e37672fae7983fb5e934cd1d381b92648f7b6 diff --git a/src/include/taler_templating_lib.h b/src/include/taler_templating_lib.h index ebda2ecf3..a4665a113 100644 --- a/src/include/taler_templating_lib.h +++ b/src/include/taler_templating_lib.h @@ -98,7 +98,7 @@ TALER_TEMPLATING_reply (struct MHD_Connection *connection, * http_status code. * * @param connection the connection we act upon - * @param template basename of the template to load + * @param template_basename basename of the template to load * @param http_status code to use on success * @param ec error code to return * @param detail optional text to add to the template From 6d363488a1cc874e9dbd3f3841439b4e4df2c826 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 7 May 2023 00:16:06 +0200 Subject: [PATCH 31/53] allow NULL --- doc/doxygen/taler.doxy | 56 +++------------------------------ src/templating/templating_api.c | 5 +-- 2 files changed, 8 insertions(+), 53 deletions(-) diff --git a/doc/doxygen/taler.doxy b/doc/doxygen/taler.doxy index 6eeb514a5..b1c8637a2 100644 --- a/doc/doxygen/taler.doxy +++ b/doc/doxygen/taler.doxy @@ -5,7 +5,7 @@ #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "GNU Taler: Exchange" -PROJECT_NUMBER = 0.8.3 +PROJECT_NUMBER = 0.9.3 PROJECT_LOGO = logo.svg OUTPUT_DIRECTORY = . CREATE_SUBDIRS = YES @@ -97,59 +97,11 @@ WARN_LOGFILE = INPUT = ../../src INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.d \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.idl \ - *.odl \ - *.cs \ - *.php \ - *.php3 \ - *.inc \ - *.m \ - *.mm \ - *.dox \ - *.py \ - *.f90 \ - *.f \ - *.vhd \ - *.vhdl \ - *.C \ - *.CC \ - *.C++ \ - *.II \ - *.I++ \ - *.H \ - *.HH \ - *.H++ \ - *.CS \ - *.PHP \ - *.PHP3 \ - *.M \ - *.MM \ - *.PY \ - *.F90 \ - *.F \ - *.VHD \ - *.VHDL + *.h RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = */test_* \ - */.svn/* \ */.git/* \ */perf_* .* \ .* \ @@ -191,7 +143,9 @@ HTML_STYLESHEET = GENERATE_HTMLHELP = NO GENERATE_DOCSET = NO DOCSET_FEEDNAME = "GNU Taler Source Documentation" -DOCSET_BUNDLE_ID = net.taler +DOCSET_BUNDLE_ID = net.taler.exchange +DOCSET_PUBLISHER_ID = net.taler +DOCSET_PUBLISHER_NAME = Taler Systems SA HTML_DYNAMIC_SECTIONS = NO CHM_FILE = HHC_LOCATION = diff --git a/src/templating/templating_api.c b/src/templating/templating_api.c index dba042e5d..4bd7c5fe7 100644 --- a/src/templating/templating_api.c +++ b/src/templating/templating_api.c @@ -443,8 +443,9 @@ TALER_TEMPLATING_reply_error (struct MHD_Connection *connection, ec), GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec)), - GNUNET_JSON_pack_string ("detail", - detail) + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("detail", + detail)) ); ret = TALER_TEMPLATING_reply (connection, http_status, From fddd06c15210557997e3ac468ca54677eacbf412 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 7 May 2023 17:52:46 +0200 Subject: [PATCH 32/53] proc doc --- doc/flows/Makefile | 3 + doc/flows/fees-coins.tex | 39 +++++++++ doc/flows/fees-wire.tex | 30 +++++++ doc/flows/int-deposit.tex | 52 ++++++++++++ doc/flows/int-pay.tex | 58 +++++++++++++ doc/flows/int-pull.tex | 55 +++++++++++++ doc/flows/int-push.tex | 47 +++++++++++ doc/flows/int-refund.tex | 39 +++++++++ doc/flows/int-shutdown.tex | 48 +++++++++++ doc/flows/int-withdraw.tex | 49 +++++++++++ doc/flows/kyc-balance.tex | 57 +++++++++++++ doc/flows/kyc-deposit.tex | 71 ++++++++++++++++ doc/flows/kyc-pull.tex | 78 ++++++++++++++++++ doc/flows/kyc-push.tex | 79 ++++++++++++++++++ doc/flows/kyc-withdraw.tex | 45 ++++++++++ doc/flows/main.tex | 159 ++++++++++++++++++++++++++++++++++++ doc/flows/proc-aml.tex | 47 +++++++++++ doc/flows/proc-domestic.tex | 66 +++++++++++++++ doc/flows/proc-kyc.tex | 43 ++++++++++ 19 files changed, 1065 insertions(+) create mode 100644 doc/flows/Makefile create mode 100644 doc/flows/fees-coins.tex create mode 100644 doc/flows/fees-wire.tex create mode 100644 doc/flows/int-deposit.tex create mode 100644 doc/flows/int-pay.tex create mode 100644 doc/flows/int-pull.tex create mode 100644 doc/flows/int-push.tex create mode 100644 doc/flows/int-refund.tex create mode 100644 doc/flows/int-shutdown.tex create mode 100644 doc/flows/int-withdraw.tex create mode 100644 doc/flows/kyc-balance.tex create mode 100644 doc/flows/kyc-deposit.tex create mode 100644 doc/flows/kyc-pull.tex create mode 100644 doc/flows/kyc-push.tex create mode 100644 doc/flows/kyc-withdraw.tex create mode 100644 doc/flows/main.tex create mode 100644 doc/flows/proc-aml.tex create mode 100644 doc/flows/proc-domestic.tex create mode 100644 doc/flows/proc-kyc.tex diff --git a/doc/flows/Makefile b/doc/flows/Makefile new file mode 100644 index 000000000..e6af0897c --- /dev/null +++ b/doc/flows/Makefile @@ -0,0 +1,3 @@ +all: + pdflatex main.tex + pdflatex main.tex diff --git a/doc/flows/fees-coins.tex b/doc/flows/fees-coins.tex new file mode 100644 index 000000000..c24f19a28 --- /dev/null +++ b/doc/flows/fees-coins.tex @@ -0,0 +1,39 @@ +\section{Fees per coin} \label{sec:fees:coin} + +Payments with Taler are always made using coins. Each coin has a specific +denomination, and an exchange will issue coins in different denominations (in +the same currency). The fees per coin depend on the operation and the +denomination. + +The primary fee to be paid is a {\bf deposit} fee that is +charged whenever a coin is fully or partially deposited +into a bank account or another wallet. + +A secondary fee to be paid is a {\bf change} fee that is +charged whenever a coin partially spent and change must +be rendered. + +Coins also have an {\bf expiration} date of approximately {\bf one year}. +After the expiration date, coins become worthless. Wallets that are online +{\bf three months} {\em before} a coin expires will automatically trade any +such coins for one or more fresh coins with a later expiration date. This +process is also subject to the {\bf change} fee. + + +\begin{table}[h!] + \caption{Fees per coin. Coin denomination values are given in units of CHF 0.01.} + \label{table:fees:coins} + \begin{center} + \begin{tabular}{l|c|r} + {\bf Denomination} & {\bf Fee type} & {\bf Amount} \\ \hline \hline + $2^{-4}-2^{ 0}$ & deposit & {\em CHF 0.00125} \\ + $2^{-4}-2^{ 0}$ & change & {\em CHF 0.00125} \\ + $2^{ 0}-2^{ 3}$ & deposit & {\em CHF 0.00250} \\ + $2^{ 0}-2^{ 3}$ & change & {\em CHF 0.00125} \\ + $2^{ 4}-2^{ 8}$ & deposit & {\em CHF 0.005} \\ + $2^{ 4}-2^{ 8}$ & change & {\em CHF 0.00125} \\ + $2^{ 8}-2^{12}$ & deposit & {\em CHF 0.01} \\ + $2^{ 8}-2^{12}$ & change & {\em CHF 0.00125} \\ + \end{tabular} + \end{center} +\end{table} diff --git a/doc/flows/fees-wire.tex b/doc/flows/fees-wire.tex new file mode 100644 index 000000000..214a69021 --- /dev/null +++ b/doc/flows/fees-wire.tex @@ -0,0 +1,30 @@ +\section{Fees per wire} \label{sec:fees:wire} + +Wire fees apply whenever an exchange needs to initiate a wire transfer to +another bank account. Wire fees do not apply to every individual payment to a +merchant, as merchants can choose to {\em aggregate} multiple micropayments +into one large payment on the wire. Wire fees also do not apply to +wallet-to-wallet payments within the Taler system. + +A {\bf wire} fee is applied when a merchant receives +an aggregated payment into their bank account. + +A {\bf closing} fee is applied when a wallet fails to +withdraw coins and money has to be sent back to the +originating bank account. + +\begin{table}[h!] + \caption{Table with wire fees. Wire fees are set annually.} + \label{table:fees:wire} + \begin{center} + \begin{tabular}{l|c|r} + {\bf Year} & {\bf Fee type} & {\bf Amount} \\ \hline \hline + 2023 & wire & {\em CHF 0.05} \\ + 2023 & closing & {\em CHF 0.10} \\ + 2024 & wire & {\em CHF 0.05} \\ + 2024 & closing & {\em CHF 0.10} \\ + 2025 & wire & {\em CHF 0.05} \\ + 2025 & closing & {\em CHF 0.10} \\ + \end{tabular} + \end{center} +\end{table} diff --git a/doc/flows/int-deposit.tex b/doc/flows/int-deposit.tex new file mode 100644 index 000000000..4b1f657ba --- /dev/null +++ b/doc/flows/int-deposit.tex @@ -0,0 +1,52 @@ +\section{Deposit} + + + +\begin{figure}[h!] + \begin{sequencediagram} + \newinst{wallet}{\shortstack{Customer wallet \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID}; + \end{tikzpicture} + }} + \newinst[2]{exchange}{\shortstack{Taler (exchange) \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[2]{bank}{\shortstack{Customer bank \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] {Checking \\ Accounts}; + \end{tikzpicture} + }} + \postlevel + \begin{callself}{wallet}{Review deposit fees}{} + \end{callself} + \mess[0]{wallet}{Deposit {(Coins)}}{exchange} + \begin{sdblock}{Acceptable account?}{} + \mess[0]{exchange}{{Refuse deposit}}{wallet} + \end{sdblock} + \begin{sdblock}{KYC/AML required?}{} + \begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{} + \end{callself} + \end{sdblock} +% \prelevel +% \prelevel +% \begin{sdblock}{User abort?}{} +% \mess[0]{wallet}{{Request abort}}{exchange} +% \mess[0]{exchange}{{Abort confirmation}}{wallet} +% \end{sdblock} + \mess[0]{exchange}{{Initiate transfer}}{bank} + +\end{sequencediagram} + \caption{Deposit interactions between customer, Taler exchange (payment + service provider) and customer's bank.} + \label{fig:int:deposit} +\end{figure} + +We do {\bf not} permit the customer to regain control over their funds {\em + unless} they pass the KYC/AML checks. The technical reason is simply that +the KYC/AML checks happen {\em after} the aggregation logic and at this point +refunds are no longer permitted. From a compliance perspective, this also +prevents malicious customers from risk-free probing of the system. diff --git a/doc/flows/int-pay.tex b/doc/flows/int-pay.tex new file mode 100644 index 000000000..d2f0fb585 --- /dev/null +++ b/doc/flows/int-pay.tex @@ -0,0 +1,58 @@ +\section{Pay} + +\begin{figure}[h!] + \begin{sequencediagram} + \newinst{wallet}{\shortstack{Customer wallet \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID}; + \end{tikzpicture} + }} + \newinst[1]{merchant}{\shortstack{Merchant \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[1]{exchange}{\shortstack{Taler (exchange) \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[1]{bank}{\shortstack{Merchant bank \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] {Commercial \\ Accounts}; + \end{tikzpicture} + }} + \postlevel + \mess[0]{wallet}{Browse catalog}{merchant} + \mess[0]{merchant}{Commercial offer}{wallet} + \begin{callself}{wallet}{Review offer}{} + \end{callself} + \mess[0]{wallet}{Send payment {(Coins)}}{merchant} + \mess[0]{merchant}{Deposit {(Coins)}}{exchange} + \begin{sdblock}{Acceptable account?}{} + \mess[0]{exchange}{{Refuse deposit}}{merchant} + \mess[0]{merchant}{{Refund purchase}}{wallet} + \end{sdblock} + \mess[0]{exchange}{{Confirm deposit}}{merchant} + \mess[0]{merchant}{Fulfill order}{wallet} + \begin{callself}{exchange}{Aggregate transactions}{} + \end{callself} + \begin{sdblock}{KYC/AML required?}{} + \begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{} + \end{callself} + \end{sdblock} + \mess[0]{exchange}{{Initiate transfer}}{bank} + \end{sequencediagram} + \caption{Deposit interactions between customer, merchant, + Taler exchange (payment service provider) and merchant bank.} + \label{fig:int:pay} +\end{figure} + +{\bf Internal note:} The exchange refusing a deposit immediately based on +unaccaptable merchant accounts may not be fully implemented (this is a very +recent feature, after all); especially the merchant then automatically +refunding the purchase to the customer is certainly missing. However, +the entire situation only arises when a merchant is incorrectly configured +and in violation of the terms of service. diff --git a/doc/flows/int-pull.tex b/doc/flows/int-pull.tex new file mode 100644 index 000000000..8c9b66b1b --- /dev/null +++ b/doc/flows/int-pull.tex @@ -0,0 +1,55 @@ +\section{Pull payment (aka invoicing)} + +\begin{figure}[h!] + \begin{sequencediagram} + \newinst{payer}{\shortstack{Payer \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] {Pre-funded \\ Wallet}; + \end{tikzpicture} + }} + \newinst[2]{exchange}{\shortstack{Taler (exchange) \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[2]{payee}{\shortstack{Payee \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID}; + \end{tikzpicture} + }} + \postlevel + \begin{callself}{payee}{Review pull payment fees}{} + \end{callself} + \mess[0]{payee}{{Create invoice (Wallet ID)}}{exchange} + + \mess[0]{exchange}{{Invoice ready}}{payee} + \mess[0]{payee}{{Send invoice (e.g. via QR code)}}{payer} + + \begin{callself}{payer}{Review invoice}{} + \end{callself} + \mess[0]{payer}{{Make payment (Coins)}}{exchange} + + \begin{sdblock}{Domestic wallet?}{} + \begin{callself}{exchange}{Figure~\ref{fig:proc:domestic}}{} + \end{callself} + \end{sdblock} + \begin{sdblock}{KYC/AML required?}{} + \begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{} + \end{callself} + \end{sdblock} + + \mess[0]{exchange}{{Distribute digital cash}}{payee} + +\end{sequencediagram} + \caption{Interactions between wallets and Taler exchange + in a pull payment.} + \label{fig:int:pull} +\end{figure} + +We do {\bf not} permit the payer to regain control over their funds, once the +payment was made they are locked {\em until} the payee passes the KYC/AML +checks. We only do the AML/KYC process once the funds are locked at the +exchange. This ensures we know the actual transacted amounts (which may be +lower than the total amounts requested) and prevents risk-free probing +attacks. diff --git a/doc/flows/int-push.tex b/doc/flows/int-push.tex new file mode 100644 index 000000000..fd49e8d40 --- /dev/null +++ b/doc/flows/int-push.tex @@ -0,0 +1,47 @@ +\section{Push payment} + +\begin{figure}[h!] + \begin{sequencediagram} + \newinst{payer}{\shortstack{Payer \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] {Pre-funded \\ Wallet}; + \end{tikzpicture} + }} + \newinst[2]{exchange}{\shortstack{Taler (exchange) \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[2]{payee}{\shortstack{Payee \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID}; + \end{tikzpicture} + }} + \postlevel + \begin{callself}{payer}{Review push payment fees}{} + \end{callself} + \mess[0]{payer}{{Push funds (Coins)}}{exchange} + \mess[0]{payer}{{Offer payment (e.g. via QR code)}}{payee} + \begin{callself}{payee}{Review payment offer}{} + \end{callself} + \mess[0]{payee}{{Request funds (Wallet ID)}}{exchange} + \begin{sdblock}{Domestic wallet?}{} + \begin{callself}{exchange}{Figure~\ref{fig:proc:domestic}}{} + \end{callself} + \end{sdblock} + \begin{sdblock}{KYC/AML required?}{} + \begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{} + \end{callself} + \end{sdblock} + \mess[0]{exchange}{{Distribute digital cash}}{payee} +% \postlevel + \begin{sdblock}{Payment offer expired?}{} + \mess[0]{exchange}{{Return funds}}{payer} + \end{sdblock} + +\end{sequencediagram} + \caption{Interactions between wallets and Taler exchange + in a push payment.} + \label{fig:int:push} +\end{figure} diff --git a/doc/flows/int-refund.tex b/doc/flows/int-refund.tex new file mode 100644 index 000000000..351ecda2b --- /dev/null +++ b/doc/flows/int-refund.tex @@ -0,0 +1,39 @@ +\section{Refund} + +\begin{figure}[h!] + \begin{sequencediagram} + \newinst{wallet}{\shortstack{Customer wallet \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID}; + \end{tikzpicture} + }} + \newinst[2]{merchant}{\shortstack{Merchant \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[2]{exchange}{\shortstack{Taler (exchange) \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \postlevel + \begin{callself}{merchant}{Initiate refund}{} + \end{callself} + \mess[0]{merchant}{{Refund offer (QR code)}}{wallet} + \mess[0]{wallet}{Request refund}{merchant} + \mess[0]{merchant}{Approve refund}{exchange} + \mess[0]{exchange}{Confirm refund}{merchant} + \mess[0]{merchant}{Return refund confirmation}{wallet} + \end{sequencediagram} + \caption{Refund processing when a merchant is unable to fulfill + a contract. Refunds must happen {\em before} the + exchange has aggregated the original transaction for + a bank transfer to the merchant. Furthermore, refunds + can only go to the customer who made the original payment + and the refund cannot exceed the amount of the original + payment.} + \label{fig:int:refund} +\end{figure} diff --git a/doc/flows/int-shutdown.tex b/doc/flows/int-shutdown.tex new file mode 100644 index 000000000..e8ee6feed --- /dev/null +++ b/doc/flows/int-shutdown.tex @@ -0,0 +1,48 @@ +\section{Shutdown} + +\begin{figure}[h!] + \begin{sequencediagram} + \newinst{wallet}{\shortstack{Customer wallet \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID}; + \end{tikzpicture} + }} + \newinst[2]{exchange}{\shortstack{Taler (exchange) \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[2]{bank}{\shortstack{Customer bank \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] {Checking \\ Accounts}; + \end{tikzpicture} + }} + \postlevel + + \begin{callself}{exchange}{Operator initiates shutdown}{} + \end{callself} + \mess[0]{exchange}{{Shutdown alert}}{wallet} + \begin{sdblock}{Bank account known?}{} + \begin{callself}{wallet}{Designate bank account}{} + \end{callself} + \end{sdblock} + \mess[0]{wallet}{{Deposit (Coins)}}{exchange} + \begin{sdblock}{Acceptable account?}{} + \mess[0]{exchange}{{Refuse deposit}}{wallet} + \end{sdblock} + \begin{sdblock}{KYC/AML required?}{} + \begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{} + \end{callself} + \end{sdblock} + \mess[0]{exchange}{{Initiate transfer}}{bank} +\end{sequencediagram} + \caption{Shutdown interactions between customer, Taler exchange (payment + service provider) and bank.} + \label{fig:int:shutdown} +\end{figure} + +KYC/AML requirements are relaxed in cases where the customer is able to +cryptographically demonstrate that they previously withdrew these coins from +the designated checking account. Thus, KYC/AML checks here primarily still +apply if the customer received the funds via P2P transfers from other wallets. diff --git a/doc/flows/int-withdraw.tex b/doc/flows/int-withdraw.tex new file mode 100644 index 000000000..9b044df67 --- /dev/null +++ b/doc/flows/int-withdraw.tex @@ -0,0 +1,49 @@ +\section{Withdraw} + +\begin{figure}[h!] + \begin{sequencediagram} + \newinst{wallet}{\shortstack{Customer wallet \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID}; + \end{tikzpicture} + }} + \newinst[2]{exchange}{\shortstack{Taler (exchange) \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[2]{bank}{\shortstack{Customer bank \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] {Checking \\ Accounts}; + \end{tikzpicture} + }} + \postlevel + \mess[0]{wallet}{Withdraw {(Amount)}}{exchange} + \mess[0]{exchange}{{Configuration (ToS, Fees)}}{wallet} + \begin{sdblock}{once}{} + \begin{callself}{wallet}{Accept ToS}{} + \end{callself} + \end{sdblock} + \begin{callself}{wallet}{Review withdraw fees}{} + \end{callself} + \mess[0]{wallet}{{Initiate transfer (Amount, Credit account, Wallet ID)}}{bank} + \mess[0]{bank}{{Credit (Wallet ID)}}{exchange} + + \begin{sdblock}{Acceptable transfer?}{} + \mess[0]{exchange}{{Bounce funds}}{bank} + \end{sdblock} + \postlevel + \mess[0]{exchange}{Confirm wire transfer}{wallet} + \mess[0]{wallet}{Request digital cash}{exchange} + \mess[0]{exchange}{Distribute digital cash}{wallet} + \postlevel + \begin{sdblock}{Withdraw period expired?}{} + \mess[0]{exchange}{{Return remaining funds}}{bank} + \end{sdblock} +\end{sequencediagram} + \caption{Withdraw interactions between customer, Taler exchange (payment + service provider) and bank. The amount of digital cash distributed is + subject to limits per origin account (see Figure~\ref{fig:kyc:withdraw}).} + \label{fig:int:withdraw} +\end{figure} diff --git a/doc/flows/kyc-balance.tex b/doc/flows/kyc-balance.tex new file mode 100644 index 000000000..1192021b3 --- /dev/null +++ b/doc/flows/kyc-balance.tex @@ -0,0 +1,57 @@ +\section{KYC: Balance} + +Note: this process is not implemented and would require non-trivial extra work +if required. + +\begin{figure}[h!] + \begin{center} +\begin{tikzpicture}[node distance=1cm,font=\sffamily, + start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30}, + end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30}, + process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30}, + failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30}, + io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30}, + decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30}, + arr/.style={very thick,-latex}, + every edge quotes/.style = {auto, font=\footnotesize, sloped} + ] + \node (start) [start] {Start}; + \node (balance) [decision,below=of start,text width=3cm] {Transaction leaves wallet balance below AML threshold?}; + \node (registered) [decision,below=of balance,text width=3cm] {Wallet has been subject to KYC?}; + \node (kyc) [process, below=of registered] {KYC process}; + \node (aml) [process, left=of kyc] {AML process}; + \node (allow) [end, right=of balance] {Allow}; + \node (deny) [failed, right=of registered] {Deny}; + \draw[arr] (start) -> (balance) {}; + \draw[arr] (balance) -> (registered); + \draw (balance) edge["No"] (registered); + \draw[arr] (balance) -> (allow); + \draw (balance) edge["Yes"] (allow); + + \draw[arr] (registered) -> (kyc); + \draw (registered) edge["No"] (kyc); + \draw[arr] (registered) -> (deny); + \draw (registered) edge["Yes"] (deny); + + \draw[arr] (kyc) -> (deny); + \draw (kyc) edge["Failed"] (deny); + \draw[arr] (kyc) -> (aml); + \draw (kyc) edge["Ok"] (aml); + + \draw[arr] (aml) -> (balance.west); + \draw (aml) edge["New threshold"] (balance.west); +\end{tikzpicture} + \end{center} + \caption{Regulatory process when a wallet exceeds its AML threshold. + When the transfer is denied the transaction (withdraw, P2P transfer) + is refused by the wallet.} +\end{figure} + + +\begin{table}[h!] + \caption{Settings for the balance trigger} + \begin{tabular}{l|l|r} + {\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline + Default AML threshold & Amount & {\em 1000 CHF} \\ + \end{tabular} +\end{table} diff --git a/doc/flows/kyc-deposit.tex b/doc/flows/kyc-deposit.tex new file mode 100644 index 000000000..2423235ab --- /dev/null +++ b/doc/flows/kyc-deposit.tex @@ -0,0 +1,71 @@ +\section{KYC: Deposit} + +\begin{figure}[h!] + \begin{center} +\begin{tikzpicture}[node distance=1cm,font=\sffamily, + start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30}, + end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30}, + process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30}, + failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30}, + io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30}, + decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30}, + arr/.style={very thick,-latex}, + every edge quotes/.style = {auto, font=\footnotesize, sloped} + ] + \node (start) [start] {Start}; + \node (country) [decision,below=of start,text width=2.5cm] {Target account in allowed country?}; + \node (amount) [decision, below=of country,text width=2.5cm] {Target account received less than KYC threshold?}; + \node (kyc) [process, right=of amount] {KYC process}; + \node (high) [decision, below=of amount,text width=2.5cm] {Target account received more than its AML threshold?}; + \node (aml) [process, right=of high] {AML process}; + \node (dummy) [below right=of aml] {}; + \node (allow) [end, below right=of dummy] {Allow}; + \node (deny) [failed, right=of kyc] {Deny}; + \draw[arr] (start) -> (country) {}; + + \draw[arr] (country) -> (amount); + \draw (country) edge["Yes"] (amount); + + \draw[arr] (country.east) -> (deny); + \draw (country.east) edge["No"] (deny); + + \draw[arr] (amount) -> (high); + \draw (amount) edge["Yes"] (high); + + \draw[arr] (amount.east) -> (kyc); + \draw (amount.east) edge["No"] (kyc); + + \draw[arr] (kyc) -> (deny); + \draw (kyc) edge["Failed"] (deny); + + \draw[arr] (kyc) -> (high); + \draw (kyc) edge["Succeeded"] (high); + + \draw[arr] (high.south) -> (allow); + \draw (high.south) edge["Yes"] (allow); + + \draw[arr] (high.east) -> (aml); + \draw (high.east) edge["No"] (aml); + + \draw[arr] (aml) -> (deny); + \draw (aml) edge["Violation"] (deny); + + \draw[arr] (aml) -> (allow); + \draw (aml) edge["Ok"] (allow); +\end{tikzpicture} + \end{center} + \caption{Regulatory process when depositing digital cash into a bank + account. When the transfer is denied, the money is returned to the + originating wallet.} +\end{figure} + + +\begin{table}[h!] + \caption{Settings for the deposit trigger} + \begin{tabular}{l|l|r} + {\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline + Allowed bank accounts & RFC 8905 RegEx & {\em CH*} \\ \hline + KYC deposit threshold & Amount & {\em 1000 CHF} \\ + Default AML deposit threshold & Amount & {\em 2500 CHF} \\ + \end{tabular} +\end{table} diff --git a/doc/flows/kyc-pull.tex b/doc/flows/kyc-pull.tex new file mode 100644 index 000000000..b7cd34477 --- /dev/null +++ b/doc/flows/kyc-pull.tex @@ -0,0 +1,78 @@ +\section{KYC/AML: Pull Payment} + +\begin{figure}[h!] + \begin{center} +\begin{tikzpicture}[node distance=0.9cm,font=\sffamily, + start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30}, + end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30}, + process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30}, + failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30}, + io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30}, + decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30}, + arr/.style={very thick,-latex}, + every edge quotes/.style = {auto, font=\footnotesize, sloped} + ] + \node (start) [start] {Start}; + \node (wallet) [decision,below=of start,text width=2.5cm] {Wallet linked to (domestic) phone number?}; + \node (domestic) [process, right=of wallet] {Validate phone number}; + \node (amount) [decision, below=of wallet,text width=2.5cm] {Wallet received less than KYC threshold from other wallets?}; + \node (kyc) [process, right=of amount] {KYC process}; + \node (high) [decision, below=of amount,text width=2.5cm] {Wallet received more than its AML threshold?}; + \node (aml) [process, right=of high] {AML process}; + \node (dummy) [below right=of aml] {}; + \node (allow) [end, below right=of dummy] {Allow invoicing}; + \node (deny) [failed, right=of kyc] {Deny}; + \draw[arr] (start) -> (wallet) {}; + + \draw[arr] (wallet) -> (amount); + \draw (wallet) edge["Yes"] (amount); + + \draw[arr] (wallet.east) -> (domestic); + \draw (wallet.east) edge["No"] (domestic); + + \draw[arr] (domestic) -> (amount); + \draw (domestic) edge["Confirmed"] (amount); + + \draw[arr] (domestic) -> (deny); + \draw (domestic) edge["Failed"] (deny); + + \draw[arr] (amount) -> (high); + \draw (amount) edge["Yes"] (high); + + \draw[arr] (amount.east) -> (kyc); + \draw (amount.east) edge["No"] (kyc); + + \draw[arr] (kyc) -> (deny); + \draw (kyc) edge["Failed"] (deny); + + \draw[arr] (kyc) -> (high); + \draw (kyc) edge["Succeeded"] (high); + + \draw[arr] (high.south) -> (allow); + \draw (high.south) edge["Yes"] (allow); + + \draw[arr] (high.east) -> (aml); + \draw (high.east) edge["No"] (aml); + + \draw[arr] (aml) -> (deny); + \draw (aml) edge["Violation"] (deny); + + \draw[arr] (aml) -> (allow); + \draw (aml) edge["Ok"] (allow); +\end{tikzpicture} + \end{center} + \caption{Regulatory process when receiving payments from another wallet. + The threshold depends on the risk profile from the KYC process. + When invoicing is denied the wallet cannot generate the invoice.} +\end{figure} + + +\begin{table}[h!] + \caption{Settings for the pull payment trigger} + \begin{tabular}{l|l|r} + {\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline + Permitted phone numbers & Dialing prefix & {\em +41} \\ + P2P KYC threshold & Amount & {\em 100 CHF} \\ + Default P2P AML threshold & Amount & {\em 1000 CHF} \\ + \end{tabular} +\end{table} diff --git a/doc/flows/kyc-push.tex b/doc/flows/kyc-push.tex new file mode 100644 index 000000000..6d25ac7ef --- /dev/null +++ b/doc/flows/kyc-push.tex @@ -0,0 +1,79 @@ +\section{KYC/AML: Push Payment} + +\begin{figure}[h!] + \begin{center} +\begin{tikzpicture}[node distance=0.9cm,font=\sffamily, + start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30}, + end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30}, + process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30}, + failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30}, + io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30}, + decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30}, + arr/.style={very thick,-latex}, + every edge quotes/.style = {auto, font=\footnotesize, sloped} + ] + \node (start) [start] {Start}; + \node (wallet) [decision,below=of start,text width=2.5cm] {Wallet linked to (domestic) phone number?}; + \node (domestic) [process, right=of wallet] {Validate phone number}; + \node (amount) [decision, below=of wallet,text width=2.5cm] {Wallet received less than KYC threshold from other wallets?}; + \node (kyc) [process, right=of amount] {KYC process}; + \node (high) [decision, below=of amount,text width=2.5cm] {Wallet received more than its AML threshold?}; + \node (aml) [process, right=of high] {AML process}; + \node (dummy) [below right=of aml] {}; + \node (allow) [end, below right=of dummy] {Allow}; + \node (deny) [failed, right=of kyc] {Deny}; + \draw[arr] (start) -> (wallet) {}; + + \draw[arr] (wallet) -> (amount); + \draw (wallet) edge["Yes"] (amount); + + \draw[arr] (wallet.east) -> (domestic); + \draw (wallet.east) edge["No"] (domestic); + + \draw[arr] (domestic) -> (amount); + \draw (domestic) edge["Confirmed"] (amount); + + \draw[arr] (domestic) -> (deny); + \draw (domestic) edge["Failed"] (deny); + + \draw[arr] (amount) -> (high); + \draw (amount) edge["Yes"] (high); + + \draw[arr] (amount.east) -> (kyc); + \draw (amount.east) edge["No"] (kyc); + + \draw[arr] (kyc) -> (deny); + \draw (kyc) edge["Failed"] (deny); + + \draw[arr] (kyc) -> (high); + \draw (kyc) edge["Succeeded"] (high); + + \draw[arr] (high.south) -> (allow); + \draw (high.south) edge["Yes"] (allow); + + \draw[arr] (high.east) -> (aml); + \draw (high.east) edge["No"] (aml); + + \draw[arr] (aml) -> (deny); + \draw (aml) edge["Violation"] (deny); + + \draw[arr] (aml) -> (allow); + \draw (aml) edge["Ok"] (allow); +\end{tikzpicture} + \end{center} + \caption{Regulatory process when receiving payments from another wallet. + The threshold depends on the risk profile from the KYC process. + When the transfer is denied the money is (eventually) returned to + the originating wallet.} +\end{figure} + + +\begin{table}[h!] + \caption{Settings for the push payment trigger} + \begin{tabular}{l|l|r} + {\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline + Permitted phone numbers & Dialing prefix & {\em +41} \\ + P2P KYC threshold & Amount & {\em 100 CHF} \\ + Default P2P AML threshold & Amount & {\em 1000 CHF} \\ + \end{tabular} +\end{table} diff --git a/doc/flows/kyc-withdraw.tex b/doc/flows/kyc-withdraw.tex new file mode 100644 index 000000000..ecdc9a399 --- /dev/null +++ b/doc/flows/kyc-withdraw.tex @@ -0,0 +1,45 @@ +\section{KYC: Withdraw} + +\begin{figure}[h!] + \begin{center} +\begin{tikzpicture}[node distance=1cm,font=\sffamily, + start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30}, + end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30}, + process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30}, + failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30}, + io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30}, + decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30}, + arr/.style={very thick,-latex}, + every edge quotes/.style = {auto, font=\footnotesize, sloped} + ] + \node (start) [start] {Start}; + \node (country) [decision,below=of start,text width=3cm] {Wire transfer originates from allowed country?}; + \node (amount) [decision, below=of country,text width=3cm] {Transferred less than maximum amount from origin account over last month?}; + \node (allow) [end, below=of amount] {Allow}; + \node (deny) [failed, right=of allow] {Deny}; + \draw[arr] (start) -> (country) {}; + \draw[arr] (country) -> (amount); + \draw (country) edge["Yes"] (amount); + \draw[arr] (country.east) -> (deny); + \draw (country.east) edge["No"] (deny); + \draw[arr] (amount) -> (allow); + \draw (amount) edge["Yes"] (allow); + \draw[arr] (amount.east) -> (deny); + \draw (amount.east) edge["No"] (deny); +\end{tikzpicture} + \end{center} + \caption{Regulatory process when withdrawing digital cash from a + bank account. + When the transfer is denied the money is (eventually) returned to + the originating bank account.} + \label{fig:kyc:withdraw} +\end{figure} + +\begin{table}[h!] + \caption{Settings for the withdraw trigger} + \begin{tabular}{l|l|r} + {\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline + Allowed bank accounts & RFC 8905 RegEx & {\em CH*} \\ \hline + Monthly withdraw maximum & Amount & {\em 1000 CHF} \\ + \end{tabular} +\end{table} diff --git a/doc/flows/main.tex b/doc/flows/main.tex new file mode 100644 index 000000000..c2aee65ac --- /dev/null +++ b/doc/flows/main.tex @@ -0,0 +1,159 @@ +\documentclass[10pt,a4paper,oneside]{book} +\usepackage[utf8]{inputenc} +\usepackage{url} +\usepackage{graphicx} +\usepackage{hyperref} +\usepackage{qrcode} +\usepackage{pgf-umlsd} +\usepackage{tikz} +\usetikzlibrary{shapes,arrows} +\usetikzlibrary{positioning} +\usetikzlibrary{calc} +\usetikzlibrary{quotes} +\author{Christian Grothoff} +\title{Flows in the GNU Taler System} + +\begin{document} + +\tableofcontents + +\chapter{Interactions} \label{chap:interactions} + +This chapter introduces the main payment interactions in the GNU Taler payment +system. For each interaction, we introduce the parties involved and in which +order they interact and how. In each interaction it is possible that the +Taler exchange needs to trigger a compliance process. These regulatory +riggers are described in more detail in Chapter~\ref{chap:triggers}. + +The main interactions of the system are: + +\begin{description} + \item[withdraw] a customer withdraws digital cash to their wallet + \item[deposit] a customer returns digital cash into their bank account + \item[pay] a customer pays into bank account of a merchant + \item[refund] a merchant decides to return funds to a customer + \item[push] a customer sends a payment to another wallet + \item[pull] a customer requests a payment from another wallet (effectively sending an invoice) + \item[shutdown] the Taler payment system operator informs the customers that the system is being shut down for good +\end{description} + +Taler has no accounts (this is digital cash) and thus there is no ``opening'' +or ``closing'' of accounts. The equivalent of ``opening'' an account is thus +to withdraw digital cash. The equivalent of ``closing'' an account is to +either (1) deposit the funds explicitly into a bank account, or (2) the coins +will expire if the wallet was lost (including long-term offline or +uninstalled). Finally, if a wallet remains (occasionally) online but a user +does simply not spend the coins will (3) diminish in value from the change +fees (see Section~\ref{sec:fees:coin}) that apply to prevent the coins from +expiring outright. + +The following sections describe the respective processes for each of these +interactions. + +\include{int-withdraw} +\include{int-deposit} +\include{int-pay} +\include{int-refund} +\include{int-push} +\include{int-pull} +\include{int-shutdown} + + +\chapter{Regulatory Triggers} \label{chap:triggers} + +In this chapter we show decision diagrams for regulatory processes of the +various core operations of the GNU Taler payment system. In each case, the +{\bf start} state refers to one of the interactions described in the previous +chapter. The payment system will then use the process to arrive at an {\bf + allow} decision which permits the transaction to go through, or at a {\bf + deny} decision which ensures that the funds are not moved. + +The specific {\em decisions} (in green) depend on the risk profile and the +regulatory environment. The tables in each section list the specific values +that are to be configured. + +There are five types if interactions that can trigger regulatory processes: + +\begin{description} + \item[withdraw] a customer withdraws digital cash from their {\bf bank account} + \item[deposit] a merchant's {\bf bank account} is designated to receive a payment in digital cash + \item[push] a {\bf wallet} accepts a payment from another wallet + \item[pull] a {\bf wallet} requests a payment from another wallet + \item[balance] a withdraw or P2P payment causes the balance of a {\bf wallet} to exceed a given threshold +\end{description} + +We note in bold the {\bf anchor} for the regulator process. The anchor is used +to link the interaction to an identity. Once an identity has been established +for a particular anchor, that link is considered established for all types of +activities involving that anchor. A wallet is uniquely identified in the +system by its unique cryptographic key. A bank account is uniquely identified +in the system by its (RFC 8905) bank routing data (usually including BIC, IBAN +and account owner name). + +The KYC and AML processes themselves are described in +Chapter~\ref{chap:regproc}. + +\include{kyc-withdraw} +\include{kyc-deposit} +\include{kyc-push} +\include{kyc-pull} +\include{kyc-balance} + +\chapter{Regulatory Processes} \label{chap:regproc} + +This chapter describes the interactions between the customer, exchange and +organizations or staff assisting with regulatory processes designed to ensure +that customers are residents in the area of operation of the payment service +provider, are properly identified, and do not engage in money laundering. + +The three main regulatory processes are: + +\begin{description} +\item[domestic check] This process establishes that a user is generally + eligible to use the payment system. The process checks that the user has an + eligible address, but stops short of establishing the user's identity. +\item[kyc] This process establishes a user's legal identity, possibly + using external providers to review documents and check against blacklists. +\item[aml] The AML process reviews suspicious payment activities for + money laundering. Here AML staff reviews all collected information. +\end{description} + +\include{proc-domestic} +\include{proc-kyc} +\include{proc-aml} + +\chapter{Fees} \label{chap:fees} + +The business model for operating a Taler exchange is to charge transaction +fees. Fees are charged on certain operations by the exchange. There are two +types of fees, {\bf wire fees} and {\bf coin fees}. This chapter describes +the fee structure. + +Fixed, amount-independent {\bf wire fees} are charged on wire transfers using +the core banking system. Details on wire fees are described in +Section~\ref{sec:fees:wire}. + +Coin fees are more complex, as they do not exactly follow neither the usual +percentage of volume model of other payment systems. Instead, coin fees are +applied per coin, resulting in a {\em logarithmic} fee structure. As a +result, the effective fee {\em percentage} for tiny transactions is high (for +example 50\% for transactions of 0.0025 CHF) while the effective fee +percentage for large transactions is nominal (for example $\approx$ 0.05\% for +transactions of $\approx$ 40 CHF). Details on coin fees are described in +Section~\ref{sec:fees:coin}. + +Fees are configurable (and that fee types beyond those described here are +supported by the software). Thus, the specific fees may be adjusted in the +future based on business decisions. However, changes to the fees are never +retroactively applied to coins already in circulation. Wire fees that have +been publicly announced for a particular time period also cannot be changed. +Finally, any change to the terms of service must also be explicitly accepted +by the users before they withdraw additional funds. + + +\include{fees-wire} +\include{fees-coins} +%\include{fees-other} + + +\end{document} diff --git a/doc/flows/proc-aml.tex b/doc/flows/proc-aml.tex new file mode 100644 index 000000000..04700faf8 --- /dev/null +++ b/doc/flows/proc-aml.tex @@ -0,0 +1,47 @@ +\section{AML process} + +\begin{figure}[h!] + \begin{sequencediagram} + \newinst{wallet}{\shortstack{Customer \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Action}; + \end{tikzpicture} + }} + \newinst[2]{exchange}{\shortstack{Taler (exchange) \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[2]{staff}{\shortstack{AML staff \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Access \\ Token}; + \end{tikzpicture} + }} + \postlevel + \mess[0]{wallet}{{Initial action}}{exchange} + \begin{callself}{exchange}{Establish AML requirement}{} + \end{callself} + \begin{callself}{exchange}{Queue AML task}{} + \end{callself} + \mess[0]{exchange}{Wait for AML}{wallet} + \mess[0]{staff}{Request AML work}{exchange} + \mess[0]{exchange}{{Open AML task(s)}}{staff} + \mess[0]{staff}{Request details}{exchange} + \mess[0]{exchange}{KYC/AML data}{staff} + \begin{callself}{staff}{Review and decide}{} + \end{callself} + \mess[0]{staff}{{Decision documentation}}{exchange} + \mess[0]{exchange}{AML decision}{wallet} + \mess[0]{wallet}{{Retry action}}{exchange} +\end{sequencediagram} + \caption{Deposit interactions between customer, Taler exchange (payment + service provider) and the AML staff. The process can be + triggered by various {\em actions} described in Chapter~\ref{chap:triggers}. + AML staff interactions are cryptographically secured and + decisions and the provided reasoning are archived by the exchange. + AML staff may interact with the customer (out-of-band) + in its decision process. + } + \label{fig:proc:aml} +\end{figure} diff --git a/doc/flows/proc-domestic.tex b/doc/flows/proc-domestic.tex new file mode 100644 index 000000000..6ea43b8eb --- /dev/null +++ b/doc/flows/proc-domestic.tex @@ -0,0 +1,66 @@ +\section{Domestic wallet check} + +\begin{figure}[h!] + \begin{sequencediagram} + \newinst{wallet}{\shortstack{Customer wallet \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID}; + \end{tikzpicture} + }} + \newinst[2]{exchange}{\shortstack{Taler (exchange) \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[2]{sms}{\shortstack{Address validator}} + + \postlevel + \mess[0]{wallet}{{P2P payment (Wallet ID)}}{exchange} + \begin{callself}{exchange}{New wallet?}{} + \end{callself} + \mess[0]{exchange}{Request address validation}{sms} + \mess[0]{sms}{Validation process ID}{exchange} + \mess[0]{exchange}{Request address validation}{wallet} + \mess[0]{wallet}{Send address}{sms} + \mess[0]{sms}{{Send PIN/TAN code (to address)}}{wallet} + \mess[0]{wallet}{Supply PIN/TAN code}{sms} + \mess[0]{sms}{{Confirmed customer address}}{exchange} + \mess[0]{exchange}{{Confirm completion}}{wallet} + \mess[0]{wallet}{{Retry action}}{exchange} +\end{sequencediagram} + \caption{Deposit interactions between customer, Taler exchange (payment + service provider) and external address validation service. The process can be + triggered by wallet-to-wallet (P2P) payments described in Chapter~\ref{chap:triggers}.} + \label{fig:proc:domestic} +\end{figure} + +Our users have to accept the terms of service which restrict the use of the +service to domestic customers. For interactions with the core banking system, +this simply means that we only accept payments from or to domestic bank +accounts. For P2P payments between wallets, we require that the wallets are +controlled by a domestic entity. We define domestic entities as those that +are able to receive messages at a domestic address. Two types of addresses are +supported: + +\begin{itemize} +\item Control over a domestic {\bf mobile phone number} is established + by sending an SMS message with a PIN/TAN to the MSIN. +\item Control over a domestic {\bf postal address} is established by + sending a letter with a PIN/TAN to the address. +\end{itemize} + +Depending on the type of address, a validation has a limited validity period, +as shown in Table~\ref{table:proc:domestic}. When the validity period is +over, a wallet has to re-do the address validation before they can receive any +further funds through the service. + +\begin{table}[h!] + \caption{Restrictions on address validations} + \label{table:proc:domestic} + \begin{tabular}{l|l|r} + {\bf Type} & {\bf Validity period} & {\bf Restricted to} \\ \hline \hline + Mobile phone number & 12 months & {\em +41} \\ + Postal address & 36 months & {\em Switzerland} \\ + \end{tabular} +\end{table} diff --git a/doc/flows/proc-kyc.tex b/doc/flows/proc-kyc.tex new file mode 100644 index 000000000..33883c40b --- /dev/null +++ b/doc/flows/proc-kyc.tex @@ -0,0 +1,43 @@ +\section{KYC process} + +\begin{figure}[h!] + \begin{sequencediagram} + \newinst{wallet}{\shortstack{Customer \\ + \\ \begin{tikzpicture} + \node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Action}; + \end{tikzpicture} + }} + \newinst[2]{exchange}{\shortstack{Taler (exchange) \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + \newinst[2]{kyc}{\shortstack{KYC provider \\ + \\ \begin{tikzpicture}[shape aspect=.5] + \tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}} + \node at (1.5,0) {\shortstack{{{\tiny Database}}}}; + \end{tikzpicture} + }} + + \postlevel + \mess[0]{wallet}{{Initial action}}{exchange} + \begin{callself}{exchange}{Establish KYC requirement}{} + \end{callself} + \mess[0]{exchange}{Request new KYC process}{kyc} + \mess[0]{kyc}{{Process identifier (PI)}}{exchange} + \mess[0]{exchange}{{KYC required (PI)}}{wallet} + \mess[0]{wallet}{{KYC start (PI)}}{kyc} + \mess[0]{kyc}{{Request identity documentation}}{wallet} + \mess[0]{wallet}{{Upload identity documentation}}{kyc} + \begin{callself}{kyc}{Validate documentation}{} + \end{callself} + \mess[0]{kyc}{{Share documentation (PI)}}{exchange} + \mess[0]{kyc}{{Confirm completion}}{wallet} + \mess[0]{wallet}{{Retry action}}{exchange} +\end{sequencediagram} + \caption{Deposit interactions between customer, Taler exchange (payment + service provider) and external KYC provider. The process can be + triggered by various {\em actions} described in Chapter~\ref{chap:triggers}.} + \label{fig:proc:kyc} +\end{figure} From f40932196e9cc710bf00d57bbca7669444165ea5 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Sun, 7 May 2023 18:37:24 +0200 Subject: [PATCH 33/53] flows: minor edits --- contrib/gana | 2 +- doc/flows/int-refund.tex | 2 +- doc/flows/proc-domestic.tex | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/gana b/contrib/gana index e50e37672..24d505904 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit e50e37672fae7983fb5e934cd1d381b92648f7b6 +Subproject commit 24d5059048972d4a3f9dc3db1181339d73364c08 diff --git a/doc/flows/int-refund.tex b/doc/flows/int-refund.tex index 351ecda2b..3f11e5600 100644 --- a/doc/flows/int-refund.tex +++ b/doc/flows/int-refund.tex @@ -23,7 +23,7 @@ \begin{callself}{merchant}{Initiate refund}{} \end{callself} \mess[0]{merchant}{{Refund offer (QR code)}}{wallet} - \mess[0]{wallet}{Request refund}{merchant} + \mess[0]{wallet}{Download refund}{merchant} \mess[0]{merchant}{Approve refund}{exchange} \mess[0]{exchange}{Confirm refund}{merchant} \mess[0]{merchant}{Return refund confirmation}{wallet} diff --git a/doc/flows/proc-domestic.tex b/doc/flows/proc-domestic.tex index 6ea43b8eb..387b964d5 100644 --- a/doc/flows/proc-domestic.tex +++ b/doc/flows/proc-domestic.tex @@ -23,8 +23,8 @@ \mess[0]{sms}{Validation process ID}{exchange} \mess[0]{exchange}{Request address validation}{wallet} \mess[0]{wallet}{Send address}{sms} - \mess[0]{sms}{{Send PIN/TAN code (to address)}}{wallet} - \mess[0]{wallet}{Supply PIN/TAN code}{sms} + \mess[0]{sms}{{Send confirmation code (to address)}}{wallet} + \mess[0]{wallet}{Supply confirmation code}{sms} \mess[0]{sms}{{Confirmed customer address}}{exchange} \mess[0]{exchange}{{Confirm completion}}{wallet} \mess[0]{wallet}{{Retry action}}{exchange} @@ -45,9 +45,9 @@ supported: \begin{itemize} \item Control over a domestic {\bf mobile phone number} is established - by sending an SMS message with a PIN/TAN to the MSIN. + by sending an SMS message with a confirmation code to the MSIN. \item Control over a domestic {\bf postal address} is established by - sending a letter with a PIN/TAN to the address. + sending a letter with a confirmation code to the address. \end{itemize} Depending on the type of address, a validation has a limited validity period, From 85f6c8cdcce509d504e7dd8606beab2f45e7dab8 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 7 May 2023 21:41:54 +0200 Subject: [PATCH 34/53] add kyc collection --- contrib/gana | 2 +- doc/flows/proc-kyc.tex | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/contrib/gana b/contrib/gana index 24d505904..e50e37672 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 24d5059048972d4a3f9dc3db1181339d73364c08 +Subproject commit e50e37672fae7983fb5e934cd1d381b92648f7b6 diff --git a/doc/flows/proc-kyc.tex b/doc/flows/proc-kyc.tex index 33883c40b..006a05561 100644 --- a/doc/flows/proc-kyc.tex +++ b/doc/flows/proc-kyc.tex @@ -41,3 +41,48 @@ triggered by various {\em actions} described in Chapter~\ref{chap:triggers}.} \label{fig:proc:kyc} \end{figure} + +At the beginning of the KYC process, the user needs to specify +whether they are an {\bf individual} or a {\bf business}. This +then determines which types of attributes are collected in the +KYC process (Table~\ref{table:proc:kyc:individual} vs. +Table~\ref{table:proc:kyc:business}). + +\begin{table} + \caption{Information collected for individuals} + \label{table:proc:kyc:individual} + \begin{center} + \begin{tabular}{l|c|r} + {\bf Type} & {\bf Required} & {\bf Example} \\ \hline \hline + Surname & yes & Mustermann \\ + First name(s) & yes & Max \\ + Date of birth & yes & 1.1.1980 \\ + Nationality & yes & Swiss \\ + Actual address of domicile & yes & Seestrasse 3, 8008 Zuerich \\ + Phone number & no & +41-123456789 \\ + E-mail & no & me@example.com \\ + Identification document & yes & JPG image \\ + \end{tabular} + \end{center} +\end{table} + + +\begin{table} + \caption{Information collected for businesses} + \label{table:proc:kyc:business} + \begin{center} + \begin{tabular}{l|c|r} + {\bf Type} & {\bf Required} & {\bf Example} \\ \hline \hline + Company name & yes & Mega AG \\ + Registered office & yes & Seestrasse 4, 8008 Zuerich \\ + Company identification document & yes & PDF file \\ \hline + Contact person name & yes & Max Mustermann \\ + Phone number & no & +41-123456789 \\ + E-mail & yes & me@example.com \\ + Identification document & yes & JPG image \\ + Date of birth & yes & 1.1.1980 \\ + Nationality & yes & Swiss \\ \hline + Power of attorney arrangement & yes & PDF file \\ + \end{tabular} + \end{center} +\end{table} From f009e0bd1261fe5b73ae7294faee3ac3e20ce802 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 9 May 2023 14:07:13 +0200 Subject: [PATCH 35/53] -make content-length optional again --- src/mhd/mhd_parsing.c | 4 ++++ src/testing/testing_api_helpers_bank.c | 23 +++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c index 9e3cb5714..b1f8417e4 100644 --- a/src/mhd/mhd_parsing.c +++ b/src/mhd/mhd_parsing.c @@ -365,6 +365,9 @@ TALER_MHD_check_content_length_ (struct MHD_Connection *connection, MHD_HTTP_HEADER_CONTENT_LENGTH); if (NULL == cl) { + return GNUNET_OK; +#if 0 + /* wallet currently doesn't always send content-length! */ GNUNET_break_op (0); return (MHD_YES == TALER_MHD_reply_with_error (connection, @@ -373,6 +376,7 @@ TALER_MHD_check_content_length_ (struct MHD_Connection *connection, MHD_HTTP_HEADER_CONTENT_LENGTH)) ? GNUNET_NO : GNUNET_SYSERR; +#endif } if (1 != sscanf (cl, "%llu%c", diff --git a/src/testing/testing_api_helpers_bank.c b/src/testing/testing_api_helpers_bank.c index 2507a87e9..f2f92956d 100644 --- a/src/testing/testing_api_helpers_bank.c +++ b/src/testing/testing_api_helpers_bank.c @@ -30,6 +30,10 @@ #define BANK_FAIL() \ do {GNUNET_break (0); return NULL; } while (0) +#define JDBC_TALERCHECK \ + "jdbc:postgresql://localhost/talercheck?socketFactory=org.newsclub.net.unix." \ + "AFUNIXSocketFactory$FactoryArg&socketFactoryArg" \ + "=/var/run/postgresql/.s.PGSQL.5432" struct TALER_FAKEBANK_Handle * TALER_TESTING_run_fakebank (const char *bank_url, @@ -95,7 +99,7 @@ TALER_TESTING_run_libeufin (const struct TALER_TESTING_BankConfiguration *bc) setenv ( "LIBEUFIN_NEXUS_DB_CONNECTION", - "jdbc:sqlite:/tmp/libeufin-exchange-test-nexusdb.sqlite3", + JDBC_TALERCHECK, 1); // not overwriting any potentially existing DB. nexus_proc = GNUNET_OS_start_process ( @@ -145,7 +149,7 @@ TALER_TESTING_run_libeufin (const struct TALER_TESTING_BankConfiguration *bc) fprintf (stderr, "\n"); setenv ( "LIBEUFIN_SANDBOX_DB_CONNECTION", - "jdbc:sqlite:/tmp/libeufin-exchange-test-sandboxdb.sqlite3", + JDBC_TALERCHECK, 1); // not overwriting any potentially existing DB. setenv ( "LIBEUFIN_SANDBOX_ADMIN_PASSWORD", @@ -366,7 +370,12 @@ TALER_TESTING_prepare_libeufin (const char *config_filename, /* DB preparation */ if (reset_db) { - if (0 != system ("rm -f /tmp/libeufin-exchange-test-nexusdb.sqlite3")) + setenv ( + "LIBEUFIN_NEXUS_DB_CONNECTION", + JDBC_TALERCHECK, + 1); // not overwriting any potentially existing DB. + + if (0 != system ("libeufin-nexus reset-tables")) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to invoke db-removal command on nexusdb.\n"); @@ -374,7 +383,13 @@ TALER_TESTING_prepare_libeufin (const char *config_filename, GNUNET_CONFIGURATION_destroy (cfg); return GNUNET_SYSERR; } - if (0 != system ("rm -f /tmp/libeufin-exchange-test-sandboxdb.sqlite3")) + + setenv ( + "LIBEUFIN_SANDBOX_DB_CONNECTION", + JDBC_TALERCHECK, + 1); // not overwriting any potentially existing DB. + + if (0 != system ("libeufin-sandbox reset-tables")) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to invoke db-removal command on sandboxdb.\n"); From d1379e492de19070a8f7c2c3dba70f39fe956888 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 9 May 2023 14:11:21 +0200 Subject: [PATCH 36/53] -fix include --- src/include/taler_templating_lib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/taler_templating_lib.h b/src/include/taler_templating_lib.h index a4665a113..6af6db715 100644 --- a/src/include/taler_templating_lib.h +++ b/src/include/taler_templating_lib.h @@ -22,7 +22,7 @@ #define TALER_TEMPLATING_LIB_H #include - +#include "taler_mhd_lib.h" /** * Fill in Mustach template @a tmpl using the data from @a root From dc5b0fb0d35b9758f6eac7b0bd144db00eef7216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Tue, 9 May 2023 20:40:43 +0200 Subject: [PATCH 37/53] Fix age mask parsing from config - initialize age mask to zero - drop default bitstring for age mask, use string instead -remove default age mask bits, use string instead -strdup --- src/extensions/age_restriction/age_restriction.c | 6 +++--- src/include/taler_extensions.h | 4 ---- src/util/age_restriction.c | 3 +++ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/extensions/age_restriction/age_restriction.c b/src/extensions/age_restriction/age_restriction.c index 2d6d0cbd7..481cb133d 100644 --- a/src/extensions/age_restriction/age_restriction.c +++ b/src/extensions/age_restriction/age_restriction.c @@ -207,10 +207,10 @@ libtaler_extension_age_restriction_init (void *arg) return NULL; } - mask.bits = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; + if (NULL == groups) + groups = GNUNET_strdup (TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS); - if ((groups != NULL) && - (GNUNET_OK != TALER_parse_age_group_string (groups, &mask))) + if (GNUNET_OK != TALER_parse_age_group_string (groups, &mask)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "[age restriction] couldn't parse age groups: '%s'\n", diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h index bd5b72480..75f225344 100644 --- a/src/include/taler_extensions.h +++ b/src/include/taler_extensions.h @@ -344,10 +344,6 @@ TALER_extensions_verify_manifests_signature ( * The default age mask represents the age groups * 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-... */ -#define TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK (1 | 1 << 8 | 1 << 10 \ - | 1 << 12 | 1 << 14 \ - | 1 << 16 | 1 << 18 \ - | 1 << 21) #define TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS "8:10:12:14:16:18:21" diff --git a/src/util/age_restriction.c b/src/util/age_restriction.c index beb68e5a6..cf81d915b 100644 --- a/src/util/age_restriction.c +++ b/src/util/age_restriction.c @@ -513,6 +513,9 @@ TALER_parse_age_group_string ( unsigned int val = 0; char c; + /* reset mask */ + mask->bits = 0; + while (*pos) { c = *pos++; From 4e79967f9ba980b64e89a453efbb9ed4bfa4cc37 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 9 May 2023 23:52:57 +0200 Subject: [PATCH 38/53] -consistency --- src/util/offline_signatures.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c index 40ccfbc21..fbff850df 100644 --- a/src/util/offline_signatures.c +++ b/src/util/offline_signatures.c @@ -1005,9 +1005,9 @@ TALER_exchange_offline_global_fee_sign ( const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { - struct TALER_MasterGlobalFeePS kv = { + struct TALER_MasterGlobalFeePS wf = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_GLOBAL_FEES), - .purpose.size = htonl (sizeof (kv)), + .purpose.size = htonl (sizeof (wf)), .start_date = GNUNET_TIME_timestamp_hton (start_time), .end_date = GNUNET_TIME_timestamp_hton (end_time), .purse_timeout = GNUNET_TIME_relative_hton (purse_timeout), @@ -1015,10 +1015,10 @@ TALER_exchange_offline_global_fee_sign ( .purse_account_limit = htonl (purse_account_limit) }; - TALER_global_fee_set_hton (&kv.fees, + TALER_global_fee_set_hton (&wf.fees, fees); GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, - &kv, + &wf, &master_sig->eddsa_signature); } From 1cf58e8ff8efc82f0e8bf1a058047d48b86e060e Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 00:02:52 +0200 Subject: [PATCH 39/53] -fix warning --- src/include/taler_util.h | 10 ++++++++++ src/util/util.c | 6 ------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/include/taler_util.h b/src/include/taler_util.h index 8192ed87c..6c2948221 100644 --- a/src/include/taler_util.h +++ b/src/include/taler_util.h @@ -229,6 +229,16 @@ void TALER_OS_init (void); +/** + * Re-encode string at @a inp to match RFC 8785 (section 3.2.2.2). + * + * @param[in,out] inp pointer to string to re-encode + * @return number of bytes in resulting @a inp + */ +size_t +TALER_rfc8785encode (char **inp); + + /** * URL-encode a string according to rfc3986. * diff --git a/src/util/util.c b/src/util/util.c index 7cd4b0c35..82c5f7f39 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -258,12 +258,6 @@ lowdump (struct GNUNET_Buffer *buf, } -/** - * Re-encode string at @a inp to match RFC 8785 (section 3.2.2.2). - * - * @param[in,out] inp pointer to string to re-encode - * @return number of bytes in resulting @a inp - */ size_t TALER_rfc8785encode (char **inp) { From aedd13a77856ebc9344643571d9e13cc93297b05 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 00:27:33 +0200 Subject: [PATCH 40/53] assertions to make compiler happy --- src/auditor/taler-auditor-sync.c | 3 +++ src/json/json.c | 1 + 2 files changed, 4 insertions(+) diff --git a/src/auditor/taler-auditor-sync.c b/src/auditor/taler-auditor-sync.c index a7ccf800a..2b4bf8558 100644 --- a/src/auditor/taler-auditor-sync.c +++ b/src/auditor/taler-auditor-sync.c @@ -605,6 +605,9 @@ main (int argc, level, NULL)); GNUNET_free (level); + /* suppress compiler warnings... */ + GNUNET_assert (NULL != src_cfgfile); + GNUNET_assert (NULL != dst_cfgfile); if (0 == strcmp (src_cfgfile, dst_cfgfile)) { diff --git a/src/json/json.c b/src/json/json.c index 832863f31..fb00fb535 100644 --- a/src/json/json.c +++ b/src/json/json.c @@ -655,6 +655,7 @@ parse_path (json_t *obj, json_t *next_obj = NULL; char *next_dot; + GNUNET_assert (NULL != id); /* make stupid compiler happy */ if (NULL == next_id) { cb (cb_cls, From 3ebd0a70b2bba2e64615c0973477a610e117c97a Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 00:57:04 +0200 Subject: [PATCH 41/53] fix 0-length VLAs --- src/kyclogic/kyclogic_api.c | 9 +++++---- src/lib/exchange_api_handle.c | 16 ++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c index 0ef1295ed..65f3f3ba3 100644 --- a/src/kyclogic/kyclogic_api.c +++ b/src/kyclogic/kyclogic_api.c @@ -780,10 +780,11 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg) TALER_KYCLOGIC_kyc_done (); return GNUNET_SYSERR; } - qsort (kyc_triggers, - num_kyc_triggers, - sizeof (struct TALER_KYCLOGIC_KycTrigger *), - &sort_by_timeframe); + if (0 != num_kyc_triggers) + qsort (kyc_triggers, + num_kyc_triggers, + sizeof (struct TALER_KYCLOGIC_KycTrigger *), + &sort_by_timeframe); return GNUNET_OK; } diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index f6a5e979d..0e8046305 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -1366,10 +1366,10 @@ keys_completed_cb (void *cls, kd.num_denom_keys); /* First make a shallow copy, we then need another pass for the RSA key... */ - memcpy (kd.denom_keys, - kd_old.denom_keys, - kd_old.num_denom_keys * sizeof (struct - TALER_EXCHANGE_DenomPublicKey)); + GNUNET_memcpy (kd.denom_keys, + kd_old.denom_keys, + kd_old.num_denom_keys * sizeof (struct + TALER_EXCHANGE_DenomPublicKey)); for (unsigned int i = 0; idenom_keys, anew->num_denom_keys, aold->num_denom_keys); - memcpy (anew->denom_keys, - aold->denom_keys, - aold->num_denom_keys - * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); + GNUNET_memcpy (anew->denom_keys, + aold->denom_keys, + aold->num_denom_keys + * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); } /* Old auditors got just copied into new ones. */ From c014acf3c4ccf03109b0141d6b68d4f464464e19 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 01:01:37 +0200 Subject: [PATCH 42/53] always use GNUNET_memcpy --- src/auditor/taler-helper-auditor-coins.c | 6 +- src/auditor/taler-helper-auditor-wire.c | 24 +++--- src/bank-lib/bank_api_transfer.c | 12 +-- src/bank-lib/fakebank.c | 6 +- src/exchange/taler-exchange-closer.c | 8 +- src/exchange/taler-exchange-httpd.c | 6 +- src/exchange/taler-exchange-transfer.c | 6 +- src/exchangedb/bench_db.c | 12 +-- src/exchangedb/pg_lookup_records_by_table.c | 6 +- src/kyclogic/taler-exchange-kyc-tester.c | 6 +- src/pq/pq_query_helper.c | 84 ++++++++++----------- src/pq/pq_result_helper.c | 66 ++++++++-------- src/templating/mustach.c | 6 +- src/testing/test_bank_api_twisted.c | 6 +- src/testing/testing_api_cmd_batch.c | 6 +- src/testing/testing_api_loop.c | 6 +- src/util/amount.c | 30 ++++---- src/util/crypto_confirmation.c | 6 +- src/util/crypto_contract.c | 42 +++++------ src/util/crypto_helper_esign.c | 6 +- src/util/crypto_helper_rsa.c | 12 +-- src/util/exchange_signatures.c | 16 ++-- src/util/iban.c | 6 +- src/util/payto.c | 6 +- src/util/taler-exchange-secmod-cs.c | 24 +++--- src/util/taler-exchange-secmod-eddsa.c | 6 +- src/util/taler-exchange-secmod-rsa.c | 36 ++++----- src/util/util.c | 6 +- 28 files changed, 231 insertions(+), 231 deletions(-) diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c index 7637e463d..8edbcf29b 100644 --- a/src/auditor/taler-helper-auditor-coins.c +++ b/src/auditor/taler-helper-auditor-coins.c @@ -172,9 +172,9 @@ coin_history_index (const struct TALER_CoinSpendPublicKeyP *coin_pub) { uint32_t i; - memcpy (&i, - coin_pub, - sizeof (i)); + GNUNET_memcpy (&i, + coin_pub, + sizeof (i)); return i % MAX_COIN_HISTORIES; } diff --git a/src/auditor/taler-helper-auditor-wire.c b/src/auditor/taler-helper-auditor-wire.c index 8615c439e..bfc465b05 100644 --- a/src/auditor/taler-helper-auditor-wire.c +++ b/src/auditor/taler-helper-auditor-wire.c @@ -674,12 +674,12 @@ hash_rc (const char *receiver_account, size_t slen = strlen (receiver_account); char buf[sizeof (struct TALER_WireTransferIdentifierRawP) + slen]; - memcpy (buf, - wtid, - sizeof (*wtid)); - memcpy (&buf[sizeof (*wtid)], - receiver_account, - slen); + GNUNET_memcpy (buf, + wtid, + sizeof (*wtid)); + GNUNET_memcpy (&buf[sizeof (*wtid)], + receiver_account, + slen); GNUNET_CRYPTO_hash (buf, sizeof (buf), key); @@ -1504,9 +1504,9 @@ history_debit_cb (void *cls, roi->details.execution_date = dd->execution_date; roi->details.wtid = dd->wtid; roi->details.credit_account_uri = (const char *) &roi[1]; - memcpy (&roi[1], - dd->credit_account_uri, - slen); + GNUNET_memcpy (&roi[1], + dd->credit_account_uri, + slen); if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put (out_map, &roi->subject_hash, @@ -1678,9 +1678,9 @@ reserve_in_cb (void *cls, rii->details.execution_date = execution_date; rii->details.reserve_pub = *reserve_pub; rii->details.debit_account_uri = (const char *) &rii[1]; - memcpy (&rii[1], - sender_account_details, - slen); + GNUNET_memcpy (&rii[1], + sender_account_details, + slen); GNUNET_CRYPTO_hash (&wire_reference, sizeof (uint64_t), &rii->row_off_hash); diff --git a/src/bank-lib/bank_api_transfer.c b/src/bank-lib/bank_api_transfer.c index 3b50018d3..94d8c6b61 100644 --- a/src/bank-lib/bank_api_transfer.c +++ b/src/bank-lib/bank_api_transfer.c @@ -99,12 +99,12 @@ TALER_BANK_prepare_transfer ( wp->account_len = htonl ((uint32_t) d_len); wp->exchange_url_len = htonl ((uint32_t) u_len); end = (char *) &wp[1]; - memcpy (end, - destination_account_payto_uri, - d_len); - memcpy (end + d_len, - exchange_base_url, - u_len); + GNUNET_memcpy (end, + destination_account_payto_uri, + d_len); + GNUNET_memcpy (end + d_len, + exchange_base_url, + u_len); *buf = (char *) wp; } diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index 60492e50f..c916ad70c 100644 --- a/src/bank-lib/fakebank.c +++ b/src/bank-lib/fakebank.c @@ -1389,9 +1389,9 @@ make_transfer ( if (NULL != timestamp) *timestamp = t->date; t->type = T_DEBIT; - memcpy (t->subject.debit.exchange_base_url, - exchange_base_url, - url_len); + GNUNET_memcpy (t->subject.debit.exchange_base_url, + exchange_base_url, + url_len); t->subject.debit.wtid = *subject; if (NULL == request_uid) GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE, diff --git a/src/exchange/taler-exchange-closer.c b/src/exchange/taler-exchange-closer.c index 41c6436a1..63a98bd0d 100644 --- a/src/exchange/taler-exchange-closer.c +++ b/src/exchange/taler-exchange-closer.c @@ -312,10 +312,10 @@ expired_reserve_cb (void *cls, memset (&wtid, 0, sizeof (wtid)); - memcpy (&wtid, - reserve_pub, - GNUNET_MIN (sizeof (wtid), - sizeof (*reserve_pub))); + GNUNET_memcpy (&wtid, + reserve_pub, + GNUNET_MIN (sizeof (wtid), + sizeof (*reserve_pub))); qs = db_plugin->insert_reserve_closed (db_plugin->cls, reserve_pub, now, diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 97cf54c86..6c6398bc6 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -932,9 +932,9 @@ proceed_with_handler (struct TEH_RequestContext *rc, /* Parse command-line arguments */ /* make a copy of 'url' because 'strtok_r()' will modify */ - memcpy (d, - url, - ulen); + GNUNET_memcpy (d, + url, + ulen); i = 0; args[i++] = strtok_r (d, "/", &sp); while ( (NULL != args[i - 1]) && diff --git a/src/exchange/taler-exchange-transfer.c b/src/exchange/taler-exchange-transfer.c index 5a4aace9c..255fe76a0 100644 --- a/src/exchange/taler-exchange-transfer.c +++ b/src/exchange/taler-exchange-transfer.c @@ -563,9 +563,9 @@ wire_prepare_cb (void *cls, } wpd = GNUNET_malloc (sizeof (struct WirePrepareData) + buf_size); - memcpy (&wpd[1], - buf, - buf_size); + GNUNET_memcpy (&wpd[1], + buf, + buf_size); wpd->buf_size = buf_size; wpd->row_id = rowid; GNUNET_CONTAINER_DLL_insert (wpd_head, diff --git a/src/exchangedb/bench_db.c b/src/exchangedb/bench_db.c index a85834d13..302d23062 100644 --- a/src/exchangedb/bench_db.c +++ b/src/exchangedb/bench_db.c @@ -169,9 +169,9 @@ bem_insert (struct GNUNET_PQ_Context *conn, GNUNET_CRYPTO_hash (&b, sizeof (b), &hc); - memcpy (&ihc, - &hc, - sizeof (ihc)); + GNUNET_memcpy (&ihc, + &hc, + sizeof (ihc)); { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (&hc), @@ -265,9 +265,9 @@ bem_select (struct GNUNET_PQ_Context *conn, GNUNET_CRYPTO_hash (&b, sizeof (b), &hc); - memcpy (&ihc, - &hc, - sizeof (ihc)); + GNUNET_memcpy (&ihc, + &hc, + sizeof (ihc)); { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint32 (&ihc), diff --git a/src/exchangedb/pg_lookup_records_by_table.c b/src/exchangedb/pg_lookup_records_by_table.c index efa0fec54..534e9a1d2 100644 --- a/src/exchangedb/pg_lookup_records_by_table.c +++ b/src/exchangedb/pg_lookup_records_by_table.c @@ -1118,9 +1118,9 @@ lrbt_cb_table_refresh_transfer_keys (void *cls, ctx->error = true; return; } - memcpy (&td.details.refresh_transfer_keys.tprivs[0], - tpriv, - tpriv_size); + GNUNET_memcpy (&td.details.refresh_transfer_keys.tprivs[0], + tpriv, + tpriv_size); ctx->cb (ctx->cb_cls, &td); GNUNET_PQ_cleanup_result (rs); diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c index 652d498c6..c2efafd72 100644 --- a/src/kyclogic/taler-exchange-kyc-tester.c +++ b/src/kyclogic/taler-exchange-kyc-tester.c @@ -990,9 +990,9 @@ proceed_with_handler (struct TEKT_RequestContext *rc, /* Parse command-line arguments */ /* make a copy of 'url' because 'strtok_r()' will modify */ - memcpy (d, - url, - ulen); + GNUNET_memcpy (d, + url, + ulen); i = 0; args[i++] = strtok_r (d, "/", &sp); while ( (NULL != args[i - 1]) && diff --git a/src/pq/pq_query_helper.c b/src/pq/pq_query_helper.c index 1db608edd..2904b63fb 100644 --- a/src/pq/pq_query_helper.c +++ b/src/pq/pq_query_helper.c @@ -202,21 +202,21 @@ qconv_denom_pub (void *cls, } len = tlen + sizeof (be); buf = GNUNET_malloc (len); - memcpy (buf, - be, - sizeof (be)); + GNUNET_memcpy (buf, + be, + sizeof (be)); switch (denom_pub->cipher) { case TALER_DENOMINATION_RSA: - memcpy (&buf[sizeof (be)], - tbuf, - tlen); + GNUNET_memcpy (&buf[sizeof (be)], + tbuf, + tlen); GNUNET_free (tbuf); break; case TALER_DENOMINATION_CS: - memcpy (&buf[sizeof (be)], - &denom_pub->details.cs_public_key, - tlen); + GNUNET_memcpy (&buf[sizeof (be)], + &denom_pub->details.cs_public_key, + tlen); break; default: GNUNET_assert (0); @@ -298,21 +298,21 @@ qconv_denom_sig (void *cls, } len = tlen + sizeof (be); buf = GNUNET_malloc (len); - memcpy (buf, - &be, - sizeof (be)); + GNUNET_memcpy (buf, + &be, + sizeof (be)); switch (denom_sig->cipher) { case TALER_DENOMINATION_RSA: - memcpy (&buf[sizeof (be)], - tbuf, - tlen); + GNUNET_memcpy (&buf[sizeof (be)], + tbuf, + tlen); GNUNET_free (tbuf); break; case TALER_DENOMINATION_CS: - memcpy (&buf[sizeof (be)], - &denom_sig->details.cs_signature, - tlen); + GNUNET_memcpy (&buf[sizeof (be)], + &denom_sig->details.cs_signature, + tlen); break; default: GNUNET_assert (0); @@ -394,21 +394,21 @@ qconv_blinded_denom_sig (void *cls, } len = tlen + sizeof (be); buf = GNUNET_malloc (len); - memcpy (buf, - &be, - sizeof (be)); + GNUNET_memcpy (buf, + &be, + sizeof (be)); switch (denom_sig->cipher) { case TALER_DENOMINATION_RSA: - memcpy (&buf[sizeof (be)], - tbuf, - tlen); + GNUNET_memcpy (&buf[sizeof (be)], + tbuf, + tlen); GNUNET_free (tbuf); break; case TALER_DENOMINATION_CS: - memcpy (&buf[sizeof (be)], - &denom_sig->details.blinded_cs_answer, - tlen); + GNUNET_memcpy (&buf[sizeof (be)], + &denom_sig->details.blinded_cs_answer, + tlen); break; default: GNUNET_assert (0); @@ -487,20 +487,20 @@ qconv_blinded_planchet (void *cls, } len = tlen + sizeof (be); buf = GNUNET_malloc (len); - memcpy (buf, - &be, - sizeof (be)); + GNUNET_memcpy (buf, + &be, + sizeof (be)); switch (bp->cipher) { case TALER_DENOMINATION_RSA: - memcpy (&buf[sizeof (be)], - bp->details.rsa_blinded_planchet.blinded_msg, - tlen); + GNUNET_memcpy (&buf[sizeof (be)], + bp->details.rsa_blinded_planchet.blinded_msg, + tlen); break; case TALER_DENOMINATION_CS: - memcpy (&buf[sizeof (be)], - &bp->details.cs_blinded_planchet, - tlen); + GNUNET_memcpy (&buf[sizeof (be)], + &bp->details.cs_blinded_planchet, + tlen); break; default: GNUNET_assert (0); @@ -578,17 +578,17 @@ qconv_exchange_withdraw_values (void *cls, } len = tlen + sizeof (be); buf = GNUNET_malloc (len); - memcpy (buf, - &be, - sizeof (be)); + GNUNET_memcpy (buf, + &be, + sizeof (be)); switch (alg_values->cipher) { case TALER_DENOMINATION_RSA: break; case TALER_DENOMINATION_CS: - memcpy (&buf[sizeof (be)], - &alg_values->details.cs_values, - tlen); + GNUNET_memcpy (&buf[sizeof (be)], + &alg_values->details.cs_values, + tlen); break; default: GNUNET_assert (0); diff --git a/src/pq/pq_result_helper.c b/src/pq/pq_result_helper.c index 139cf1cbf..9441412d4 100644 --- a/src/pq/pq_result_helper.c +++ b/src/pq/pq_result_helper.c @@ -113,9 +113,9 @@ extract_amount_nbo_helper (PGresult *result, } len = GNUNET_MIN (TALER_CURRENCY_LEN - 1, strlen (currency)); - memcpy (r_amount_nbo->currency, - currency, - len); + GNUNET_memcpy (r_amount_nbo->currency, + currency, + len); return GNUNET_OK; } @@ -420,9 +420,9 @@ extract_denom_pub (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } - memcpy (be, - res, - sizeof (be)); + GNUNET_memcpy (be, + res, + sizeof (be)); res += sizeof (be); len -= sizeof (be); pk->cipher = ntohl (be[0]); @@ -445,9 +445,9 @@ extract_denom_pub (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } - memcpy (&pk->details.cs_public_key, - res, - len); + GNUNET_memcpy (&pk->details.cs_public_key, + res, + len); return GNUNET_OK; default: GNUNET_break (0); @@ -543,9 +543,9 @@ extract_denom_sig (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } - memcpy (&be, - res, - sizeof (be)); + GNUNET_memcpy (&be, + res, + sizeof (be)); if (0x00 != ntohl (be[1])) { GNUNET_break (0); @@ -572,9 +572,9 @@ extract_denom_sig (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } - memcpy (&sig->details.cs_signature, - res, - len); + GNUNET_memcpy (&sig->details.cs_signature, + res, + len); return GNUNET_OK; default: GNUNET_break (0); @@ -670,9 +670,9 @@ extract_blinded_denom_sig (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } - memcpy (&be, - res, - sizeof (be)); + GNUNET_memcpy (&be, + res, + sizeof (be)); if (0x01 != ntohl (be[1])) /* magic marker: blinded */ { GNUNET_break (0); @@ -699,9 +699,9 @@ extract_blinded_denom_sig (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } - memcpy (&sig->details.blinded_cs_answer, - res, - len); + GNUNET_memcpy (&sig->details.blinded_cs_answer, + res, + len); return GNUNET_OK; default: GNUNET_break (0); @@ -798,9 +798,9 @@ extract_blinded_planchet (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } - memcpy (&be, - res, - sizeof (be)); + GNUNET_memcpy (&be, + res, + sizeof (be)); if (0x0100 != ntohl (be[1])) /* magic marker: blinded */ { GNUNET_break (0); @@ -824,9 +824,9 @@ extract_blinded_planchet (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } - memcpy (&bp->details.cs_blinded_planchet, - res, - len); + GNUNET_memcpy (&bp->details.cs_blinded_planchet, + res, + len); return GNUNET_OK; default: GNUNET_break (0); @@ -923,9 +923,9 @@ extract_exchange_withdraw_values (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } - memcpy (&be, - res, - sizeof (be)); + GNUNET_memcpy (&be, + res, + sizeof (be)); if (0x010000 != ntohl (be[1])) /* magic marker: EWV */ { GNUNET_break (0); @@ -949,9 +949,9 @@ extract_exchange_withdraw_values (void *cls, GNUNET_break (0); return GNUNET_SYSERR; } - memcpy (&alg_values->details.cs_values, - res, - len); + GNUNET_memcpy (&alg_values->details.cs_values, + res, + len); return GNUNET_OK; default: GNUNET_break (0); diff --git a/src/templating/mustach.c b/src/templating/mustach.c index caa80dcc9..9797c533d 100644 --- a/src/templating/mustach.c +++ b/src/templating/mustach.c @@ -297,7 +297,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const #endif if (len > MUSTACH_MAX_LENGTH) return MUSTACH_ERROR_TAG_TOO_LONG; - memcpy(name, beg, len); + GNUNET_memcpy(name, beg, len); name[len] = 0; break; } @@ -317,7 +317,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const return MUSTACH_ERROR_BAD_SEPARATORS; oplen = l; tmp = alloca(oplen + 1); - memcpy(tmp, beg, oplen); + GNUNET_memcpy(tmp, beg, oplen); tmp[oplen] = 0; opstr = tmp; while (l < len && isspace(beg[l])) l++; @@ -325,7 +325,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const return MUSTACH_ERROR_BAD_SEPARATORS; cllen = len - l; tmp = alloca(cllen + 1); - memcpy(tmp, beg + l, cllen); + GNUNET_memcpy(tmp, beg + l, cllen); tmp[cllen] = 0; clstr = tmp; break; diff --git a/src/testing/test_bank_api_twisted.c b/src/testing/test_bank_api_twisted.c index 80629b6f9..84379ad1f 100644 --- a/src/testing/test_bank_api_twisted.c +++ b/src/testing/test_bank_api_twisted.c @@ -85,9 +85,9 @@ run (void *cls, memset (&wtid, 0x5a, sizeof (wtid)); - memcpy (&exchange_auth_twisted, - &bc.exchange_auth, - sizeof (struct TALER_BANK_AuthenticationData)); + GNUNET_memcpy (&exchange_auth_twisted, + &bc.exchange_auth, + sizeof (struct TALER_BANK_AuthenticationData)); if (with_fakebank) exchange_auth_twisted.wire_gateway_url = "http://localhost:8888/2/"; diff --git a/src/testing/testing_api_cmd_batch.c b/src/testing/testing_api_cmd_batch.c index a5263b03e..d81a77678 100644 --- a/src/testing/testing_api_cmd_batch.c +++ b/src/testing/testing_api_cmd_batch.c @@ -150,9 +150,9 @@ TALER_TESTING_cmd_batch (const char *label, bs->batch = GNUNET_new_array (i + 1, struct TALER_TESTING_Command); - memcpy (bs->batch, - batch, - sizeof (struct TALER_TESTING_Command) * i); + GNUNET_memcpy (bs->batch, + batch, + sizeof (struct TALER_TESTING_Command) * i); { struct TALER_TESTING_Command cmd = { .cls = bs, diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c index 3ac9bea20..271b6e768 100644 --- a/src/testing/testing_api_loop.c +++ b/src/testing/testing_api_loop.c @@ -452,9 +452,9 @@ TALER_TESTING_run2 (struct TALER_TESTING_Interpreter *is, is->commands = GNUNET_malloc_large ( (i + 1) * sizeof (struct TALER_TESTING_Command)); GNUNET_assert (NULL != is->commands); - memcpy (is->commands, - commands, - sizeof (struct TALER_TESTING_Command) * i); + GNUNET_memcpy (is->commands, + commands, + sizeof (struct TALER_TESTING_Command) * i); is->timeout_task = GNUNET_SCHEDULER_add_delayed ( timeout, &do_timeout, diff --git a/src/util/amount.c b/src/util/amount.c index d5698e8b6..dfe10b07e 100644 --- a/src/util/amount.c +++ b/src/util/amount.c @@ -72,9 +72,9 @@ TALER_string_to_amount (const char *str, } GNUNET_assert (TALER_CURRENCY_LEN > (colon - str)); - memcpy (amount->currency, - str, - colon - str); + GNUNET_memcpy (amount->currency, + str, + colon - str); /* 0-terminate *and* normalize buffer by setting everything to '\0' */ memset (&amount->currency [colon - str], 0, @@ -193,9 +193,9 @@ TALER_amount_hton (struct TALER_AmountNBO *res, TALER_amount_is_valid (d)); res->value = GNUNET_htonll (d->value); res->fraction = htonl (d->fraction); - memcpy (res->currency, - d->currency, - TALER_CURRENCY_LEN); + GNUNET_memcpy (res->currency, + d->currency, + TALER_CURRENCY_LEN); } @@ -205,9 +205,9 @@ TALER_amount_ntoh (struct TALER_Amount *res, { res->value = GNUNET_ntohll (dn->value); res->fraction = ntohl (dn->fraction); - memcpy (res->currency, - dn->currency, - TALER_CURRENCY_LEN); + GNUNET_memcpy (res->currency, + dn->currency, + TALER_CURRENCY_LEN); GNUNET_assert (GNUNET_YES == TALER_amount_is_valid (res)); } @@ -225,9 +225,9 @@ TALER_amount_set_zero (const char *cur, memset (amount, 0, sizeof (struct TALER_Amount)); - memcpy (amount->currency, - cur, - slen); + GNUNET_memcpy (amount->currency, + cur, + slen); return GNUNET_OK; } @@ -680,9 +680,9 @@ TALER_amount_multiply (struct TALER_Amount *result, if (GNUNET_SYSERR == TALER_amount_normalize (&in)) return TALER_AAR_INVALID_NORMALIZATION_FAILED; - memcpy (result->currency, - amount->currency, - TALER_CURRENCY_LEN); + GNUNET_memcpy (result->currency, + amount->currency, + TALER_CURRENCY_LEN); if ( (0 == factor) || ( (0 == in.value) && (0 == in.fraction) ) ) diff --git a/src/util/crypto_confirmation.c b/src/util/crypto_confirmation.c index e52562e3e..bbdf701ec 100644 --- a/src/util/crypto_confirmation.c +++ b/src/util/crypto_confirmation.c @@ -90,9 +90,9 @@ compute_totp (struct GNUNET_TIME_Timestamp ts, mc = gcry_md_read (md, GCRY_MD_SHA1); GNUNET_assert (NULL != mc); - memcpy (hmac, - mc, - sizeof (hmac)); + GNUNET_memcpy (hmac, + mc, + sizeof (hmac)); gcry_md_close (md); } diff --git a/src/util/crypto_contract.c b/src/util/crypto_contract.c index 3bfe9eb89..bec34c983 100644 --- a/src/util/crypto_contract.c +++ b/src/util/crypto_contract.c @@ -131,9 +131,9 @@ blob_encrypt (const struct NonceP *nonce, + data_size; *res_size = ciphertext_size; *res = GNUNET_malloc (ciphertext_size); - memcpy (*res, - nonce, - crypto_secretbox_NONCEBYTES); + GNUNET_memcpy (*res, + nonce, + crypto_secretbox_NONCEBYTES); GNUNET_assert (0 == crypto_secretbox_easy (*res + crypto_secretbox_NONCEBYTES, data, @@ -274,9 +274,9 @@ TALER_CRYPTO_contract_encrypt_for_merge ( hdr->header.ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER); hdr->header.clen = htonl ((uint32_t) clen); hdr->merge_priv = *merge_priv; - memcpy (&hdr[1], - xbuf, - cbuf_size); + GNUNET_memcpy (&hdr[1], + xbuf, + cbuf_size); GNUNET_free (xbuf); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, &nonce, @@ -423,9 +423,9 @@ TALER_CRYPTO_contract_encrypt_for_deposit ( hdr = GNUNET_malloc (sizeof (*hdr) + cbuf_size); hdr->ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST); hdr->clen = htonl ((uint32_t) clen); - memcpy (&hdr[1], - xbuf, - cbuf_size); + GNUNET_memcpy (&hdr[1], + xbuf, + cbuf_size); GNUNET_free (xbuf); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, &nonce, @@ -441,12 +441,12 @@ TALER_CRYPTO_contract_encrypt_for_deposit ( GNUNET_free (hdr); /* prepend purse_pub */ *econtract = GNUNET_malloc (xecontract_size + sizeof (*purse_pub)); - memcpy (*econtract, - purse_pub, - sizeof (*purse_pub)); - memcpy (sizeof (*purse_pub) + *econtract, - xecontract, - xecontract_size); + GNUNET_memcpy (*econtract, + purse_pub, + sizeof (*purse_pub)); + GNUNET_memcpy (sizeof (*purse_pub) + *econtract, + xecontract, + xecontract_size); *econtract_size = xecontract_size + sizeof (*purse_pub); GNUNET_free (xecontract); } @@ -573,9 +573,9 @@ TALER_CRYPTO_kyc_attributes_encrypt ( cbuf_size = compressBound (clen); xbuf = GNUNET_malloc (cbuf_size + sizeof (uint32_t)); belen = htonl ((uint32_t) clen); - memcpy (xbuf, - &belen, - sizeof (belen)); + GNUNET_memcpy (xbuf, + &belen, + sizeof (belen)); ret = compress (xbuf + 4, &cbuf_size, (const Bytef *) cstr, @@ -623,9 +623,9 @@ TALER_CRYPTO_kyc_attributes_decrypt ( GNUNET_break_op (0); return NULL; } - memcpy (&belen, - xhdr, - sizeof (belen)); + GNUNET_memcpy (&belen, + xhdr, + sizeof (belen)); clen = ntohl (belen); if (clen >= GNUNET_MAX_MALLOC_CHECKED) { diff --git a/src/util/crypto_helper_esign.c b/src/util/crypto_helper_esign.c index 5a9ad74e2..5b04d0ead 100644 --- a/src/util/crypto_helper_esign.c +++ b/src/util/crypto_helper_esign.c @@ -357,9 +357,9 @@ TALER_CRYPTO_helper_esign_sign_ ( sr->header.size = htons (sizeof (buf)); sr->header.type = htons (TALER_HELPER_EDDSA_MT_REQ_SIGN); sr->reserved = htonl (0); - memcpy (&sr->purpose, - purpose, - purpose_size); + GNUNET_memcpy (&sr->purpose, + purpose, + purpose_size); if (GNUNET_OK != TALER_crypto_helper_send_all (esh->sock, buf, diff --git a/src/util/crypto_helper_rsa.c b/src/util/crypto_helper_rsa.c index efded50a1..4098a846b 100644 --- a/src/util/crypto_helper_rsa.c +++ b/src/util/crypto_helper_rsa.c @@ -417,9 +417,9 @@ TALER_CRYPTO_helper_rsa_sign ( sr->header.type = htons (TALER_HELPER_RSA_MT_REQ_SIGN); sr->reserved = htonl (0); sr->h_rsa = *rsr->h_rsa; - memcpy (&sr[1], - rsr->msg, - rsr->msg_size); + GNUNET_memcpy (&sr[1], + rsr->msg, + rsr->msg_size); if (GNUNET_OK != TALER_crypto_helper_send_all (dh->sock, buf, @@ -655,9 +655,9 @@ TALER_CRYPTO_helper_rsa_batch_sign ( sr->header.size = htons (sizeof (*sr) + rsr->msg_size); sr->reserved = htonl (0); sr->h_rsa = *rsr->h_rsa; - memcpy (&sr[1], - rsr->msg, - rsr->msg_size); + GNUNET_memcpy (&sr[1], + rsr->msg, + rsr->msg_size); wbuf += sizeof (*sr) + rsr->msg_size; } GNUNET_assert (wbuf == &obuf[mlen]); diff --git a/src/util/exchange_signatures.c b/src/util/exchange_signatures.c index d8bf716c7..6f8ebdaff 100644 --- a/src/util/exchange_signatures.c +++ b/src/util/exchange_signatures.c @@ -1177,10 +1177,10 @@ TALER_exchange_online_denomination_expired_sign ( }; /* strncpy would create a compiler warning */ - memcpy (dua.operation, - op, - GNUNET_MIN (sizeof (dua.operation), - strlen (op))); + GNUNET_memcpy (dua.operation, + op, + GNUNET_MIN (sizeof (dua.operation), + strlen (op))); return scb (&dua.purpose, pub, sig); @@ -1204,10 +1204,10 @@ TALER_exchange_online_denomination_expired_verify ( }; /* strncpy would create a compiler warning */ - memcpy (dua.operation, - op, - GNUNET_MIN (sizeof (dua.operation), - strlen (op))); + GNUNET_memcpy (dua.operation, + op, + GNUNET_MIN (sizeof (dua.operation), + strlen (op))); return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED, &dua, diff --git a/src/util/iban.c b/src/util/iban.c index efd8c4282..c2274d3cb 100644 --- a/src/util/iban.c +++ b/src/util/iban.c @@ -233,9 +233,9 @@ TALER_iban_validate (const char *iban) return GNUNET_strdup ("IBAN number too short to be valid"); if (len > 34) return GNUNET_strdup ("IBAN number too long to be valid"); - memcpy (cc, iban, 2); - memcpy (ibancpy, iban + 4, len - 4); - memcpy (ibancpy + len - 4, iban, 4); + GNUNET_memcpy (cc, iban, 2); + GNUNET_memcpy (ibancpy, iban + 4, len - 4); + GNUNET_memcpy (ibancpy + len - 4, iban, 4); ibancpy[len] = '\0'; cc_entry.code = cc; cc_entry.english = NULL; diff --git a/src/util/payto.c b/src/util/payto.c index 81664b1df..9b0e83e85 100644 --- a/src/util/payto.c +++ b/src/util/payto.c @@ -267,9 +267,9 @@ TALER_payto_hash (const char *payto, &sha512); GNUNET_static_assert (sizeof (sha512) > sizeof (*h_payto)); /* truncate */ - memcpy (h_payto, - &sha512, - sizeof (*h_payto)); + GNUNET_memcpy (h_payto, + &sha512, + sizeof (*h_payto)); } diff --git a/src/util/taler-exchange-secmod-cs.c b/src/util/taler-exchange-secmod-cs.c index 2cdf09adf..ed0eba15d 100644 --- a/src/util/taler-exchange-secmod-cs.c +++ b/src/util/taler-exchange-secmod-cs.c @@ -423,9 +423,9 @@ generate_response (struct DenominationKey *dk) &an->secm_sig); an->secm_pub = TES_smpub; p = (void *) &an[1]; - memcpy (p, - denom->section, - nlen); + GNUNET_memcpy (p, + denom->section, + nlen); dk->an = an; } @@ -1373,9 +1373,9 @@ cs_client_init (struct TES_Client *client) NULL != dk; dk = dk->next) { - memcpy (&buf[obs], - dk->an, - ntohs (dk->an->header.size)); + GNUNET_memcpy (&buf[obs], + dk->an, + ntohs (dk->an->header.size)); obs += ntohs (dk->an->header.size); } } @@ -1472,18 +1472,18 @@ cs_update_client_keys (struct TES_Client *client) .h_cs = key->h_cs }; - memcpy (&buf[obs], - &pn, - sizeof (pn)); + GNUNET_memcpy (&buf[obs], + &pn, + sizeof (pn)); GNUNET_assert (obs + sizeof (pn) > obs); obs += sizeof (pn); } else { - memcpy (&buf[obs], - key->an, - ntohs (key->an->header.size)); + GNUNET_memcpy (&buf[obs], + key->an, + ntohs (key->an->header.size)); GNUNET_assert (obs + ntohs (key->an->header.size) > obs); obs += ntohs (key->an->header.size); diff --git a/src/util/taler-exchange-secmod-eddsa.c b/src/util/taler-exchange-secmod-eddsa.c index e07e9a71d..3b78e71df 100644 --- a/src/util/taler-exchange-secmod-eddsa.c +++ b/src/util/taler-exchange-secmod-eddsa.c @@ -826,9 +826,9 @@ parse_key (const char *filename, filename); return GNUNET_SYSERR; } - memcpy (&priv, - buf, - buf_size); + GNUNET_memcpy (&priv, + buf, + buf_size); { struct GNUNET_CRYPTO_EddsaPublicKey pub; diff --git a/src/util/taler-exchange-secmod-rsa.c b/src/util/taler-exchange-secmod-rsa.c index c651d78a8..414cb4acc 100644 --- a/src/util/taler-exchange-secmod-rsa.c +++ b/src/util/taler-exchange-secmod-rsa.c @@ -394,13 +394,13 @@ generate_response (struct DenominationKey *dk) &an->secm_sig); an->secm_pub = TES_smpub; p = (void *) &an[1]; - memcpy (p, - buf, - buf_len); + GNUNET_memcpy (p, + buf, + buf_len); GNUNET_free (buf); - memcpy (p + buf_len, - denom->section, - nlen); + GNUNET_memcpy (p + buf_len, + denom->section, + nlen); dk->an = an; } @@ -524,9 +524,9 @@ send_signature (struct TES_Client *client, sr = GNUNET_malloc (tsize); sr->header.size = htons (tsize); sr->header.type = htons (TALER_HELPER_RSA_MT_RES_SIGNATURE); - memcpy (&sr[1], - buf, - buf_size); + GNUNET_memcpy (&sr[1], + buf, + buf_size); GNUNET_free (buf); ret = TES_transmit (client->csock, &sr->header); @@ -1111,9 +1111,9 @@ rsa_client_init (struct TES_Client *client) NULL != dk; dk = dk->next) { - memcpy (&buf[obs], - dk->an, - ntohs (dk->an->header.size)); + GNUNET_memcpy (&buf[obs], + dk->an, + ntohs (dk->an->header.size)); GNUNET_assert (obs + ntohs (dk->an->header.size) > obs); obs += ntohs (dk->an->header.size); @@ -1212,18 +1212,18 @@ rsa_update_client_keys (struct TES_Client *client) .h_rsa = key->h_rsa }; - memcpy (&buf[obs], - &pn, - sizeof (pn)); + GNUNET_memcpy (&buf[obs], + &pn, + sizeof (pn)); GNUNET_assert (obs + sizeof (pn) > obs); obs += sizeof (pn); } else { - memcpy (&buf[obs], - key->an, - ntohs (key->an->header.size)); + GNUNET_memcpy (&buf[obs], + key->an, + ntohs (key->an->header.size)); GNUNET_assert (obs + ntohs (key->an->header.size) > obs); obs += ntohs (key->an->header.size); diff --git a/src/util/util.c b/src/util/util.c index 82c5f7f39..da5727487 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -41,9 +41,9 @@ TALER_b2s (const void *buf, &hc); tmp = GNUNET_STRINGS_data_to_string_alloc (&hc, sizeof (hc)); - memcpy (ret, - tmp, - 8); + GNUNET_memcpy (ret, + tmp, + 8); GNUNET_free (tmp); ret[8] = '\0'; return ret; From 09f09a21045ed6407fd23a9edb23c6da9299604c Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 01:07:20 +0200 Subject: [PATCH 43/53] fix more VLAs --- src/lib/exchange_api_lookup_aml_decision.c | 2 +- src/testing/testing_api_cmd_refresh.c | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/exchange_api_lookup_aml_decision.c b/src/lib/exchange_api_lookup_aml_decision.c index fc1a8a8e4..39d53c51f 100644 --- a/src/lib/exchange_api_lookup_aml_decision.c +++ b/src/lib/exchange_api_lookup_aml_decision.c @@ -196,7 +196,7 @@ parse_decision_ok (struct TALER_EXCHANGE_LookupAmlDecision *lh, struct TALER_EXCHANGE_AmlDecisionDetail aml_history_ar[ GNUNET_NZL (lr.details.ok.aml_history_length)]; struct TALER_EXCHANGE_KycHistoryDetail kyc_attributes_ar[ - lr.details.ok.kyc_attributes_length]; + GNUNET_NZL (lr.details.ok.kyc_attributes_length)]; enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; lr.details.ok.aml_history = aml_history_ar; diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c index a8e2e8f39..9c2bd8d5e 100644 --- a/src/testing/testing_api_cmd_refresh.c +++ b/src/testing/testing_api_cmd_refresh.c @@ -1239,9 +1239,11 @@ melt_traits (void *cls, TALER_TESTING_make_trait_h_age_commitment ( index, rms->refresh_data.melt_h_age_commitment), - TALER_TESTING_make_trait_exchange_wd_value (index, - &rms->mbds[index].alg_value), TALER_TESTING_make_trait_refresh_secret (&rms->rms), + (NULL != rms->mbds) + ? TALER_TESTING_make_trait_exchange_wd_value (index, + &rms->mbds[index].alg_value) + : TALER_TESTING_trait_end (), TALER_TESTING_trait_end () }; From 5f9c3021db128e53013f8ad22781254f57c3c355 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 01:08:05 +0200 Subject: [PATCH 44/53] exceptions-apply --- src/templating/mustach.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/templating/mustach.c b/src/templating/mustach.c index 9797c533d..caa80dcc9 100644 --- a/src/templating/mustach.c +++ b/src/templating/mustach.c @@ -297,7 +297,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const #endif if (len > MUSTACH_MAX_LENGTH) return MUSTACH_ERROR_TAG_TOO_LONG; - GNUNET_memcpy(name, beg, len); + memcpy(name, beg, len); name[len] = 0; break; } @@ -317,7 +317,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const return MUSTACH_ERROR_BAD_SEPARATORS; oplen = l; tmp = alloca(oplen + 1); - GNUNET_memcpy(tmp, beg, oplen); + memcpy(tmp, beg, oplen); tmp[oplen] = 0; opstr = tmp; while (l < len && isspace(beg[l])) l++; @@ -325,7 +325,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const return MUSTACH_ERROR_BAD_SEPARATORS; cllen = len - l; tmp = alloca(cllen + 1); - GNUNET_memcpy(tmp, beg + l, cllen); + memcpy(tmp, beg + l, cllen); tmp[cllen] = 0; clstr = tmp; break; From cc34502ac11a0fc77eb68a469b037fe2e2ee6c20 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 01:13:45 +0200 Subject: [PATCH 45/53] -fix TOTP calculation --- src/util/crypto_confirmation.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/crypto_confirmation.c b/src/util/crypto_confirmation.c index bbdf701ec..a238d5376 100644 --- a/src/util/crypto_confirmation.c +++ b/src/util/crypto_confirmation.c @@ -102,7 +102,7 @@ compute_totp (struct GNUNET_TIME_Timestamp ts, offset = hmac[sizeof (hmac) - 1] & 0x0f; for (int count = 0; count < 4; count++) - code |= hmac[offset + 3 - count] << (8 * count); + code |= ((uint32_t) hmac[offset + 3 - count]) << (8 * count); code &= 0x7fffffff; /* always use 8 digits (maximum) */ code = code % 100000000; From 5259ea05321783a177cab9e30f77cb3dc6f54049 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 01:21:52 +0200 Subject: [PATCH 46/53] -fix logging --- src/exchange/taler-exchange-httpd.c | 1 - src/lib/exchange_api_lookup_aml_decision.c | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 6c6398bc6..7e11655c1 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -542,7 +542,6 @@ handle_get_aml (struct TEH_RequestContext *rc, TALER_EC_GENERIC_DB_FETCH_FAILED, NULL); case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_FORBIDDEN, TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_ACCESS_DENIED, diff --git a/src/lib/exchange_api_lookup_aml_decision.c b/src/lib/exchange_api_lookup_aml_decision.c index 39d53c51f..9e20c83b8 100644 --- a/src/lib/exchange_api_lookup_aml_decision.c +++ b/src/lib/exchange_api_lookup_aml_decision.c @@ -256,6 +256,8 @@ handle_lookup_finished (void *cls, GNUNET_assert (NULL == lh->decision_cb); TALER_EXCHANGE_lookup_aml_decision_cancel (lh); return; + case MHD_HTTP_NO_CONTENT: + break; case MHD_HTTP_BAD_REQUEST: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); From 495496340554ec4699e9dbc561d921b242c92e6d Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 09:13:09 +0200 Subject: [PATCH 47/53] ensure amounts are in upper-case --- src/util/amount.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/util/amount.c b/src/util/amount.c index dfe10b07e..9cd0739c9 100644 --- a/src/util/amount.c +++ b/src/util/amount.c @@ -72,9 +72,8 @@ TALER_string_to_amount (const char *str, } GNUNET_assert (TALER_CURRENCY_LEN > (colon - str)); - GNUNET_memcpy (amount->currency, - str, - colon - str); + for (unsigned int i = 0; icurrency[i] = toupper (str[i]); /* 0-terminate *and* normalize buffer by setting everything to '\0' */ memset (&amount->currency [colon - str], 0, @@ -193,9 +192,8 @@ TALER_amount_hton (struct TALER_AmountNBO *res, TALER_amount_is_valid (d)); res->value = GNUNET_htonll (d->value); res->fraction = htonl (d->fraction); - GNUNET_memcpy (res->currency, - d->currency, - TALER_CURRENCY_LEN); + for (unsigned int i = 0; icurrency[i] = toupper (d->currency[i]); } @@ -225,9 +223,8 @@ TALER_amount_set_zero (const char *cur, memset (amount, 0, sizeof (struct TALER_Amount)); - GNUNET_memcpy (amount->currency, - cur, - slen); + for (unsigned int i = 0; icurrency[i] = toupper (cur[i]); return GNUNET_OK; } From b15713f42e9ecbc67e9b8ffd178d97d08b7377bb Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 22:09:47 +0200 Subject: [PATCH 48/53] add helper logic for JSON2JSON conversion --- src/include/taler_util.h | 54 ++++- src/util/Makefile.am | 13 +- src/util/conversion.c | 402 ++++++++++++++++++++++++++++++++++++ src/util/test_conversion.c | 149 +++++++++++++ src/util/test_conversion.sh | 5 + 5 files changed, 621 insertions(+), 2 deletions(-) create mode 100644 src/util/conversion.c create mode 100644 src/util/test_conversion.c create mode 100755 src/util/test_conversion.sh diff --git a/src/include/taler_util.h b/src/include/taler_util.h index 6c2948221..0b15eb8a8 100644 --- a/src/include/taler_util.h +++ b/src/include/taler_util.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -569,6 +569,58 @@ enum GNUNET_GenericReturnValue TALER_JSON_parse_age_groups (const json_t *root, struct TALER_AgeMask *mask); + +/** + * Handle to an external process that will assist + * with some JSON-to-JSON conversion. + */ +struct TALER_JSON_ExternalConversion; + +/** + * Type of a callback that receives a JSON @a result. + * + * @param cls closure + * @param status_type how did the process die + * @apram code termination status code from the process + * @param result some JSON result, NULL if we failed to get an JSON output + */ +typedef void +(*TALER_JSON_JsonCallback) (void *cls, + enum GNUNET_OS_ProcessStatusType status_type, + unsigned long code, + const json_t *result); + + +/** + * Launch some external helper @a binary to convert some @a input + * and eventually call @a cb with the result. + * + * @param input JSON to serialize and pass to the helper process + * @param cb function to call on the result + * @param cb_cls closure for @a cb + * @param binary name of the binary to execute + * @param ... NULL-terminated list of arguments for the @a binary, + * usually starting with again the name of the binary + * @return handle to cancel the operation (and kill the helper) + */ +struct TALER_JSON_ExternalConversion * +TALER_JSON_external_conversion_start (const json_t *input, + TALER_JSON_JsonCallback cb, + void *cb_cls, + const char *binary, + ...); + +/** + * Abort external conversion, killing the process and preventing + * the callback from being called. Must not be called after the + * callback was invoked. + * + * @param[in] ec external conversion handle to cancel + */ +void +TALER_JSON_external_conversion_stop ( + struct TALER_JSON_ExternalConversion *ec); + #undef __TALER_UTIL_LIB_H_INSIDE__ #endif diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 115ea5055..9d1ec9d42 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -20,7 +20,8 @@ EXTRA_DIST = \ taler-config.in \ test_helper_eddsa.conf \ test_helper_rsa.conf \ - test_helper_cs.conf + test_helper_cs.conf \ + test_conversion.sh bin_PROGRAMS = \ taler-exchange-secmod-eddsa \ @@ -80,6 +81,7 @@ libtalerutil_la_SOURCES = \ aml_signatures.c \ auditor_signatures.c \ config.c \ + conversion.c \ crypto.c \ crypto_confirmation.c \ crypto_contract.c \ @@ -125,6 +127,7 @@ AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH= check_PROGRAMS = \ test_age_restriction \ test_amount \ + test_conversion \ test_crypto \ test_helper_eddsa \ test_helper_rsa \ @@ -141,6 +144,14 @@ test_age_restriction_LDADD = \ -lgnunetutil \ libtalerutil.la +test_conversion_SOURCES = \ + test_conversion.c +test_conversion_LDADD = \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + libtalerutil.la + test_amount_SOURCES = \ test_amount.c test_amount_LDADD = \ diff --git a/src/util/conversion.c b/src/util/conversion.c new file mode 100644 index 000000000..501ca0145 --- /dev/null +++ b/src/util/conversion.c @@ -0,0 +1,402 @@ +/* + This file is part of TALER + Copyright (C) 2023 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 conversion.c + * @brief helper routines to run some external JSON-to-JSON converter + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include + + +struct TALER_JSON_ExternalConversion +{ + /** + * Callback to call with the result. + */ + TALER_JSON_JsonCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Handle to the helper process. + */ + struct GNUNET_OS_Process *helper; + + /** + * Pipe for the stdin of the @e helper. + */ + struct GNUNET_DISK_FileHandle *chld_stdin; + + /** + * Pipe for the stdout of the @e helper. + */ + struct GNUNET_DISK_FileHandle *chld_stdout; + + /** + * Handle to wait on the child to terminate. + */ + struct GNUNET_ChildWaitHandle *cwh; + + /** + * Task to read JSON output from the child. + */ + struct GNUNET_SCHEDULER_Task *read_task; + + /** + * Task to send JSON input to the child. + */ + struct GNUNET_SCHEDULER_Task *write_task; + + /** + * Buffer with data we need to send to the helper. + */ + void *write_buf; + + /** + * Buffer for reading data from the helper. + */ + void *read_buf; + + /** + * Total length of @e write_buf. + */ + size_t write_size; + + /** + * Current write position in @e write_buf. + */ + size_t write_pos; + + /** + * Current size of @a read_buf. + */ + size_t read_size; + + /** + * Current offset in @a read_buf. + */ + size_t read_pos; + +}; + + +/** + * Function called when we can read more data from + * the child process. + * + * @param cls our `struct TALER_JSON_ExternalConversion *` + */ +static void +read_cb (void *cls) +{ + struct TALER_JSON_ExternalConversion *ec = cls; + + ec->read_task = NULL; + while (1) + { + ssize_t ret; + + if (ec->read_size == ec->read_pos) + { + /* Grow input buffer */ + size_t ns; + void *tmp; + + ns = GNUNET_MAX (2 * ec->read_size, + 1024); + if (ns > GNUNET_MAX_MALLOC_CHECKED) + ns = GNUNET_MAX_MALLOC_CHECKED; + if (ec->read_size == ns) + { + /* Helper returned more than 40 MB of data! Stop reading! */ + GNUNET_break (0); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_file_close (ec->chld_stdin)); + return; + } + tmp = GNUNET_malloc_large (ns); + if (NULL == tmp) + { + /* out of memory, also stop reading */ + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "malloc"); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_file_close (ec->chld_stdin)); + return; + } + GNUNET_memcpy (tmp, + ec->read_buf, + ec->read_pos); + GNUNET_free (ec->read_buf); + ec->read_buf = tmp; + ec->read_size = ns; + } + ret = GNUNET_DISK_file_read (ec->chld_stdout, + ec->read_buf, + ec->read_size - ec->read_pos); + if (ret < 0) + { + if ( (EAGAIN != errno) && + (EWOULDBLOCK != errno) && + (EINTR != errno) ) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "read"); + return; + } + break; + } + if (0 == ret) + { + /* regular end of stream, good! */ + return; + } + GNUNET_assert (ec->read_size >= ec->read_pos + ret); + ec->read_pos += ret; + } + ec->read_task + = GNUNET_SCHEDULER_add_read_file ( + GNUNET_TIME_UNIT_FOREVER_REL, + ec->chld_stdout, + &read_cb, + ec); +} + + +/** + * Function called when we can write more data to + * the child process. + * + * @param cls our `struct TALER_JSON_ExternalConversion *` + */ +static void +write_cb (void *cls) +{ + struct TALER_JSON_ExternalConversion *ec = cls; + ssize_t ret; + + ec->write_task = NULL; + while (ec->write_size > ec->write_pos) + { + ret = GNUNET_DISK_file_write (ec->chld_stdin, + ec->write_buf + ec->write_pos, + ec->write_size - ec->write_pos); + if (ret < 0) + { + if ( (EAGAIN != errno) && + (EINTR != errno) ) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "write"); + break; + } + if (0 == ret) + { + GNUNET_break (0); + break; + } + GNUNET_assert (ec->write_size >= ec->write_pos + ret); + ec->write_pos += ret; + } + if ( (ec->write_size > ec->write_pos) && + ( (EAGAIN == errno) || + (EWOULDBLOCK == errno) || + (EINTR == errno) ) ) + { + ec->write_task + = GNUNET_SCHEDULER_add_write_file ( + GNUNET_TIME_UNIT_FOREVER_REL, + ec->chld_stdin, + &write_cb, + ec); + } + else + { + GNUNET_break (GNUNET_OK == + GNUNET_DISK_file_close (ec->chld_stdin)); + ec->chld_stdin = NULL; + } +} + + +/** + * Defines a GNUNET_ChildCompletedCallback which is sent back + * upon death or completion of a child process. + * + * @param cls handle for the callback + * @param type type of the process + * @param exit_code status code of the process + * + */ +static void +child_done_cb (void *cls, + enum GNUNET_OS_ProcessStatusType type, + long unsigned int exit_code) +{ + struct TALER_JSON_ExternalConversion *ec = cls; + json_t *j; + json_error_t err; + + ec->cwh = NULL; + if (NULL != ec->read_task) + { + GNUNET_SCHEDULER_cancel (ec->read_task); + /* We could get the process termination notification before having drained + the read buffer. So drain it now, just in case. */ + read_cb (ec); + } + if (NULL != ec->read_task) + { + GNUNET_SCHEDULER_cancel (ec->read_task); + ec->read_task = NULL; + } + GNUNET_OS_process_destroy (ec->helper); + ec->helper = NULL; + j = json_loadb (ec->read_buf, + ec->read_pos, + JSON_REJECT_DUPLICATES, + &err); + if (NULL == j) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse JSON from helper at %d: %s\n", + err.position, + err.text); + } + ec->cb (ec->cb_cls, + type, + exit_code, + j); + json_decref (j); + TALER_JSON_external_conversion_stop (ec); +} + + +struct TALER_JSON_ExternalConversion * +TALER_JSON_external_conversion_start (const json_t *input, + TALER_JSON_JsonCallback cb, + void *cb_cls, + const char *binary, + ...) +{ + struct TALER_JSON_ExternalConversion *ec; + struct GNUNET_DISK_PipeHandle *pipe_stdin; + struct GNUNET_DISK_PipeHandle *pipe_stdout; + va_list ap; + + ec = GNUNET_new (struct TALER_JSON_ExternalConversion); + ec->cb = cb; + ec->cb_cls = cb_cls; + pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ); + GNUNET_assert (NULL != pipe_stdin); + pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE); + GNUNET_assert (NULL != pipe_stdout); + va_start (ap, + binary); + ec->helper = GNUNET_OS_start_process_va (GNUNET_OS_INHERIT_STD_ERR, + pipe_stdin, + pipe_stdout, + NULL, + binary, + ap); + va_end (ap); + if (NULL == ec->helper) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to run conversion helper `%s'\n", + binary); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_pipe_close (pipe_stdin)); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_pipe_close (pipe_stdout)); + GNUNET_free (ec); + return NULL; + } + ec->chld_stdin = + GNUNET_DISK_pipe_detach_end (pipe_stdin, + GNUNET_DISK_PIPE_END_WRITE); + ec->chld_stdout = + GNUNET_DISK_pipe_detach_end (pipe_stdout, + GNUNET_DISK_PIPE_END_READ); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_pipe_close (pipe_stdin)); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_pipe_close (pipe_stdout)); + ec->write_buf = json_dumps (input, JSON_COMPACT); + ec->write_size = strlen (ec->write_buf); + ec->read_task + = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + ec->chld_stdout, + &read_cb, + ec); + ec->write_task + = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL, + ec->chld_stdin, + &write_cb, + ec); + ec->cwh = GNUNET_wait_child (ec->helper, + &child_done_cb, + ec); + return ec; +} + + +void +TALER_JSON_external_conversion_stop ( + struct TALER_JSON_ExternalConversion *ec) +{ + if (NULL != ec->cwh) + { + GNUNET_wait_child_cancel (ec->cwh); + ec->cwh = NULL; + } + if (NULL != ec->helper) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (ec->helper, + SIGKILL)); + GNUNET_OS_process_destroy (ec->helper); + } + if (NULL != ec->read_task) + { + GNUNET_SCHEDULER_cancel (ec->read_task); + ec->read_task = NULL; + } + if (NULL != ec->write_task) + { + GNUNET_SCHEDULER_cancel (ec->write_task); + ec->write_task = NULL; + } + if (NULL != ec->chld_stdin) + { + GNUNET_break (GNUNET_OK == + GNUNET_DISK_file_close (ec->chld_stdin)); + ec->chld_stdin = NULL; + } + if (NULL != ec->chld_stdout) + { + GNUNET_break (GNUNET_OK == + GNUNET_DISK_file_close (ec->chld_stdout)); + ec->chld_stdout = NULL; + } + GNUNET_free (ec->read_buf); + free (ec->write_buf); + GNUNET_free (ec); +} diff --git a/src/util/test_conversion.c b/src/util/test_conversion.c new file mode 100644 index 000000000..00cb35e72 --- /dev/null +++ b/src/util/test_conversion.c @@ -0,0 +1,149 @@ +/* + This file is part of TALER + (C) 2023 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 util/test_conversion.c + * @brief Tests for conversion logic + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include + +/** + * Return value from main(). + */ +static int global_ret; + +/** + * Handle to our helper. + */ +static struct TALER_JSON_ExternalConversion *ec; + + +/** + * Type of a callback that receives a JSON @a result. + * + * @param cls closure + * @param status_type how did the process die + * @apram code termination status code from the process + * @param result some JSON result, NULL if we failed to get an JSON output + */ +static void +conv_cb (void *cls, + enum GNUNET_OS_ProcessStatusType status_type, + unsigned long code, + const json_t *result) +{ + json_t *expect; + + (void) cls; + (void) status_type; + ec = NULL; + global_ret = 3; + if (42 != code) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected return value from helper: %u\n", + (unsigned int) code); + return; + } + expect = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("foo", + "arg") + ); + if (1 == json_equal (expect, + result)) + { + global_ret = 0; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected JSON result\n"); + json_dumpf (result, + stderr, + JSON_INDENT (2)); + global_ret = 4; + } + json_decref (expect); +} + + +/** + * Function called on shutdown/CTRL-C. + * + * @param cls NULL + */ +static void +do_shutdown (void *cls) +{ + (void) cls; + if (NULL != ec) + { + GNUNET_break (0); + global_ret = 2; + TALER_JSON_external_conversion_stop (ec); + ec = NULL; + } +} + + +/** + * Main test function. + * + * @param cls NULL + */ +static void +run (void *cls) +{ + json_t *input; + + (void) cls; + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + input = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("key", + "foo") + ); + ec = TALER_JSON_external_conversion_start (input, + &conv_cb, + NULL, + "./test_conversion.sh", + "test_conversion.sh", + "arg", + NULL); + json_decref (input); + GNUNET_assert (NULL != ec); +} + + +int +main (int argc, + const char *const argv[]) +{ + (void) argc; + (void) argv; + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test-conversion", + "WARNING", + NULL); + GNUNET_OS_init (TALER_project_data_default ()); + global_ret = 1; + GNUNET_SCHEDULER_run (&run, + NULL); + return global_ret; +} diff --git a/src/util/test_conversion.sh b/src/util/test_conversion.sh new file mode 100755 index 000000000..26e1a36d8 --- /dev/null +++ b/src/util/test_conversion.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +KEY=$(jq -r .key) +echo -n "{\"$KEY\":\"$1\"}" +exit 42 From 0dd0fff17d0802f48bfab0bac498968abc8a59cf Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 10 May 2023 22:13:18 +0200 Subject: [PATCH 49/53] -typo --- src/include/taler_util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/taler_util.h b/src/include/taler_util.h index 0b15eb8a8..1de264c12 100644 --- a/src/include/taler_util.h +++ b/src/include/taler_util.h @@ -581,7 +581,7 @@ struct TALER_JSON_ExternalConversion; * * @param cls closure * @param status_type how did the process die - * @apram code termination status code from the process + * @param code termination status code from the process * @param result some JSON result, NULL if we failed to get an JSON output */ typedef void From ec8ad2e3b3f1298a49ab9592babc0a15c2053e54 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 11 May 2023 01:16:53 +0200 Subject: [PATCH 50/53] update mustach library --- src/templating/.gitignore | 1 + src/templating/AUTHORS | 9 + src/templating/LICENSE-2.0.txt | 202 ---------- src/templating/LICENSE.txt | 14 + src/templating/Makefile.am | 10 + src/templating/README.md | 304 ++++++++++----- src/templating/mustach-cjson.c | 239 ++++++++++++ src/templating/mustach-cjson.h | 96 +++++ src/templating/mustach-jansson.c | 622 +++++++++++-------------------- src/templating/mustach-jansson.h | 72 ++-- src/templating/mustach-json-c.c | 267 +++++++++++++ src/templating/mustach-json-c.h | 160 ++++++++ src/templating/mustach-tool.c | 176 +++++++-- src/templating/mustach-wrap.c | 456 ++++++++++++++++++++++ src/templating/mustach-wrap.h | 234 ++++++++++++ src/templating/mustach.c | 254 ++++++++----- src/templating/mustach.h | 174 ++++++--- src/templating/templating_api.c | 20 +- 18 files changed, 2390 insertions(+), 920 deletions(-) delete mode 100644 src/templating/LICENSE-2.0.txt create mode 100644 src/templating/LICENSE.txt create mode 100644 src/templating/mustach-cjson.c create mode 100644 src/templating/mustach-cjson.h create mode 100644 src/templating/mustach-json-c.c create mode 100644 src/templating/mustach-json-c.h create mode 100644 src/templating/mustach-wrap.c create mode 100644 src/templating/mustach-wrap.h diff --git a/src/templating/.gitignore b/src/templating/.gitignore index b2bf6ef99..ac9c37f45 100644 --- a/src/templating/.gitignore +++ b/src/templating/.gitignore @@ -1 +1,2 @@ test_mustach_jansson +taler-mustach-tool diff --git a/src/templating/AUTHORS b/src/templating/AUTHORS index 2fcc60437..b440c24ed 100644 --- a/src/templating/AUTHORS +++ b/src/templating/AUTHORS @@ -4,8 +4,15 @@ Main author: Contributors: Abhishek Mishra Atlas + Ben Beasley + Gabriel Zachmann Harold L Marzan + Kurt Jung Lailton Fernando Mariano + Lucas Ramage + Paramtamtam + RekGRpth + Ryan Fox Sami Kerola Sijmen J. Mulder Tomasz Sieprawski @@ -13,6 +20,7 @@ Contributors: Packagers: pkgsrc: Sijmen J. Mulder alpine linux: Lucas Ramage + RPM & DEB: Marcus Hardt Thanks to issue submitters: Dante Torres @@ -21,3 +29,4 @@ Thanks to issue submitters: Mark Bucciarelli Paul Wisehart Thierry Fournier + SASU OKFT diff --git a/src/templating/LICENSE-2.0.txt b/src/templating/LICENSE-2.0.txt deleted file mode 100644 index d64569567..000000000 --- a/src/templating/LICENSE-2.0.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/templating/LICENSE.txt b/src/templating/LICENSE.txt new file mode 100644 index 000000000..495aeefd5 --- /dev/null +++ b/src/templating/LICENSE.txt @@ -0,0 +1,14 @@ + +Copyright (c) 2017-2020 by José Bollo + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, +OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. diff --git a/src/templating/Makefile.am b/src/templating/Makefile.am index 2501151a8..fcdc22cd4 100644 --- a/src/templating/Makefile.am +++ b/src/templating/Makefile.am @@ -6,6 +6,15 @@ if USE_COVERAGE XLIB = -lgcov endif +noinst_PROGRAMS = \ + taler-mustach-tool + +taler_mustach_tool_SOURCES = \ + mustach-tool.c \ + mustach-jansson.h +taler_mustach_tool_LDADD = \ + libmustach.la \ + -ljansson lib_LTLIBRARIES = \ libtalertemplating.la @@ -15,6 +24,7 @@ noinst_LTLIBRARIES = \ libtalertemplating_la_SOURCES = \ mustach.c mustach.h \ + mustach-wrap.c mustach-wrap.h \ mustach-jansson.c mustach-jansson.h \ templating_api.c libtalertemplating_la_LIBADD = \ diff --git a/src/templating/README.md b/src/templating/README.md index a6df19f64..6699b0e89 100644 --- a/src/templating/README.md +++ b/src/templating/README.md @@ -1,16 +1,27 @@ -# Introduction to Mustach 0.99 +# Introduction to Mustach 1.2 `mustach` is a C implementation of the [mustache](http://mustache.github.io "main site for mustache") template specification. The main site for `mustach` is on [gitlab](https://gitlab.com/jobol/mustach). -The best way to use mustach is to copy the files **mustach.h** and **mustach.c** +The simpliest way to use mustach is to copy the files **mustach.h** and **mustach.c** directly into your project and use it. -Alternatively, make and meson files are provided for building `mustach` and +If you are using one of the JSON libraries listed below, you can get extended feature +by also including **mustach-wrap.h**, **mustach-wrap.c**, **mustach-XXX.h** and +**mustach-XXX.c** in your project (see below for **XXX**) + +- [json-c](https://github.com/json-c/json-c): use **XXX** = **json-c** +- [jansson](http://www.digip.org/jansson/): use **XXX** = **jansson** +- [cJSON](https://github.com/DaveGamble/cJSON): use **XXX** = **cjson** + +Alternatively, make and meson files are provided for building `mustach` and `libmustach.so` shared library. +Since version 1.0, the makefile allows to compile and install different +flavours. See below for details. + ## Distributions offering mustach package ### Alpine Linux @@ -30,6 +41,13 @@ make See http://pkgsrc.se/devel/mustach +## Known projects using Mustach + +This [wiki page](https://gitlab.com/jobol/mustach/-/wikis/projects-using-mustach) +lists the known project that are using mustach and that kindly told it. + +Don't hesitate to tell us if you are interested to be listed there. + ## Using Mustach from sources The file **mustach.h** is the main documentation. Look at it. @@ -38,15 +56,25 @@ The current source files are: - **mustach.c** core implementation of mustache in C - **mustach.h** header file for core definitions +- **mustach-wrap.c** generic wrapper of mustach for easier integration +- **mustach-wrap.h** header file for using mustach-wrap - **mustach-json-c.c** tiny json wrapper of mustach using [json-c](https://github.com/json-c/json-c) -- **mustach-json-c.h** header file for using the tiny JSON wrapper -- **mustach-tool.c** simple tool for applying template files to a JSON file +- **mustach-json-c.h** header file for using the tiny json-c wrapper +- **mustach-cjson.c** tiny json wrapper of mustach using [cJSON](https://github.com/DaveGamble/cJSON) +- **mustach-cjson.h** header file for using the tiny cJSON wrapper +- **mustach-jansson.c** tiny json wrapper of mustach using [jansson](https://www.digip.org/jansson/) +- **mustach-jansson.h** header file for using the tiny jansson wrapper +- **mustach-tool.c** simple tool for applying template files to one JSON file -The file **mustach-json-c.c** is the main example of use of **mustach** core -and it is also a practical implementation that can be used. It uses the library -json-c. (NOTE for Mac OS: available through homebrew). +The file **mustach-json-c.c** is the historical example of use of **mustach** and +**mustach-wrap** core and it is also a practical implementation that can be used. +It uses the library json-c. (NOTE for Mac OS: available through homebrew). -HELP REQUESTED TO GIVE EXAMPLE BASED ON OTHER LIBRARIES (ex: janson, ...). +Since version 1.0, the project also provide integration of other JSON libraries: +**cJSON** and **jansson**. + +*If you integrate a new library with* **mustach**, *your contribution will be +welcome here*. The tool **mustach** is build using `make`, its usage is: @@ -57,14 +85,14 @@ It then outputs the result of applying the templates files to the JSON file. ### Portability Some system does not provide *open_memstream*. In that case, tell your -preferred compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**. +prefered compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**. Example: - gcc -DNO_OPEN_MEMSTREAM + CFLAGS=-DNO_OPEN_MEMSTREAM make ### Integration -The file **mustach.h** is the main documentation. Look at it. +The files **mustach.h** and **mustach-wrap.h** are the main documentation. Look at it. The file **mustach-json-c.c** provides a good example of integration. @@ -76,139 +104,211 @@ If you intend to use specific escaping and/or specific output, the callbacks of the interface **mustach_itf** that you have to implement are: `enter`, `next`, `leave`, `get` and `emit`. -### Extensions +### Compilation Using Make -By default, the current implementation provides the following extensions: +Building and installing can be done using make. -#### Explicit Substitution +Example: -This is a core extension implemented in file **mustach.c**. + $ make tool=cjson libs=none PREFIX=/usr/local DESTDIR=/ install + $ make tool=jsonc libs=single PREFIX=/ DESTDIR=$HOME/.local install -In somecases the name of the key used for substitution begins with a +The makefile knows following switches (\*: default): + + Switch name | Values | Description + --------------+---------+----------------------------------------------- + jsonc | (unset) | Auto detection of json-c + | no | Don't compile for json-c + | yes | Compile for json-c that must exist + --------------+---------+----------------------------------------------- + cjson | (unset) | Auto detection of cJSON + | no | Don't compile for cJSON + | yes | Compile for cJSON that must exist + --------------+---------+----------------------------------------------- + jansson | (unset) | Auto detection of jansson + | no | Don't compile for jansson + | yes | Compile for jansson that must exist + --------------+---------+----------------------------------------------- + tool | (unset) | Auto detection + | cjson | Use cjson library + | jsonc | Use jsonc library + | jansson | Use jansson library + | none | Don't compile the tool + --------------+---------+---------------------------------------------- + libs | (unset) | Like 'all' + | all | Like 'single' AND 'split' + | single | Only libmustach.so + | split | All the possible libmustach-XXX.so ... + | none | No library is produced + +The libraries that can be produced are: + + Library name | Content + --------------------+-------------------------------------------------------- + libmustach-core | mustach.c mustach-wrap.c + libmustach-cjson | mustach.c mustach-wrap.c mustach-cjson.c + libmustach-jsonc | mustach.c mustach-wrap.c mustach-json-c.c + libmustach-jansson | mustach.c mustach-wrap.c mustach-jansson.c + libmustach | mustach.c mustach-wrap.c mustach-{cjson,json-c,jansson}.c + +There is no dependencies of a library to an other. This is intended and doesn't +hurt today because the code is small. + +## Extensions + +The current implementation provides extensions to specifications of **mustache**. +This extensions can be activated or deactivated using flags. + +Here is the summary. + + Flag name | Description + -------------------------------+------------------------------------------------ + Mustach_With_Colon | Explicit tag substition with colon + Mustach_With_EmptyTag | Empty Tag Allowed + -------------------------------+------------------------------------------------ + Mustach_With_Equal | Value Testing Equality + Mustach_With_Compare | Value Comparing + Mustach_With_JsonPointer | Interpret JSON Pointers + Mustach_With_ObjectIter | Iteration On Objects + Mustach_With_EscFirstCmp | Escape First Compare + Mustach_With_ErrorUndefined | Error when a requested tag is undefined + -------------------------------+------------------------------------------------ + Mustach_With_AllExtensions | Activate all known extensions + Mustach_With_NoExtensions | Disable any extension + +For the details, see below. + +### Explicit Tag Substitution With Colon (Mustach_With_Colon) + +In somecases the name of the key used for substition begins with a character reserved for mustach: one of `#`, `^`, `/`, `&`, `{`, `>` and `=`. + This extension introduces the special character `:` to explicitly tell mustach to just substitute the value. So `:` becomes a new special character. -#### Value Testing and Comparing +This is a core extension implemented in file **mustach.c**. -This are a tool extension implemented in file **mustach-json-c.c**. +### Empty Tag Allowed (Mustach_With_EmptyTag) -These extensions allows you to test the value of the selected key. -They allow to write `key=value` (matching test) or `key=!value` +When an empty tag is found, instead of automatically raising the error +MUSTACH\_ERROR\_EMPTY\_TAG pass it. + +This is a core extension implemented in file **mustach.c**. + +### Value Testing Equality (Mustach_With_Equal) + +This extension allows you to test the value of the selected key. +It allows to write `key=value` (matching test) or `key=!value` (not matching test) in any query. -The specific comparison extension also allows to compare if greater, -lesser, etc.. than a value. It allows to write `key>value`. +This is a wrap extension implemented in file **mustach-wrap.c**. + +### Value Comparing (Mustach_With_Compare) + +These extension extends the extension for testing equality to also +compare values if greater or lesser. +Its allows to write `key>value` (greater), `key>=value` (greater or equal), +`key` signs when +values comparisons are enabled: `~=` gives `=` in the key. -### Removing Extensions +This is a wrap extension implemented in file **mustach-wrap.c**. -When compiling mustach.c or mustach-json-c.c, -extensions can be removed by defining macros -using option -D. +### Iteration On Objects (Mustach_With_ObjectIter) -The possible macros are of 3 categories, the global, -the mustach core specific and the mustach-json-c example -of implementation specific. +With this extension, using the pattern `{{#X.*}}...{{/X.*}}` +allows to iterate on fields of `X`. -#### Global macros +Example: -- `NO_EXTENSION_FOR_MUSTACH` +- `{{s.*}} {{*}}:{{.}}{{/s.*}}` applied on `{"s":{"a":1,"b":true}}` produces ` a:1 b:true` - This macro disables any current or future - extensions for the core or the example. +Here the single star `{{*}}` is replaced by the iterated key +and the single dot `{{.}}` is replaced by its value. -#### Macros for the core mustach engine (mustach.c) +This is a wrap extension implemented in file **mustach-wrap.c**. -- `NO_COLON_EXTENSION_FOR_MUSTACH` +### Error when a requested tag is undefined (Mustach_With_ErrorUndefined) - This macro remove the ability to use colon (:) - as explicit command for variable substitution. - This extension allows to have name starting - with one of the mustach character `:#^/&{=>` +Report the error MUSTACH_ERROR_UNDEFINED_TAG when a requested tag +is not defined. -- `NO_ALLOW_EMPTY_TAG` +This is a wrap extension implemented in file **mustach-wrap.c**. - Generate the error MUSTACH_ERROR_EMPTY_TAG automatically - when an empty tag is encountered. +### Access To Current Value -#### Macros for the implementation example (mustach-json-c.c) +*this was an extension but is now always enforced* -- `NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH` +The value of the current field can be accessed using single dot. - This macro allows the program to check whether - the actual value is equal to an expected value. - This is useful in `{{#key=val}}` or `{{^key=val}}` - with the corresponding `{{/key=val}}`. - It can also be used in `{{key=val}}` but this - doesn't seem to be useful. +Examples: -- `NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH` +- `{{#key}}{{.}}{{/key}}` applied to `{"key":3.14}` produces `3.14` +- `{{#array}} {{.}}{{/array}}` applied to `{"array":[1,2]}` produces ` 1 2`. - This macro allows the program to compare the actual - value with an expected value. The comparison operators - are `=`, `>`, `<`, `>=`, `<=`. The meaning of the - comparison numeric or alphabetic depends on the type - of the inspected value. Also the result of the comparison - can be inverted if the value starts with `!`. - Example of use: `{{key>=val}}`, or `{{#key>=val}}` and - `{{^key>=val}}` with their matching `{{/key>=val}}`. +This is a wrap extension implemented in file **mustach-wrap.c**. -- `NO_USE_VALUE_ESCAPE_FIRST_EXTENSION_FOR_MUSTACH` +### Partial Data First - This macro fordids automatic escaping of coparison - sign appearing at first column. +*this was an extension but is now always enforced* -- `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH` +The default resolution for partial pattern like `{{> name}}` +is to search for `name` in the current json context and +as a file named `name` or if not found `name.mustache`. - This macro removes the possible use of JSON pointers. - JSON pointers are defined in IETF RFC 6901. - If not set, any key starting with "/" is a JSON pointer. - This implies to use the colon to introduce keys. - So `NO_COLON_EXTENSION_FOR_MUSTACH` implies - `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH`. - A special escaping is used for `=`, `<`, `>` signs when - values comparisons are enabled: `~=` gives `=` in the key. +By default, the order of the search is (1) as a file, +and if not found, (2) in the current json context. -- `NO_OBJECT_ITERATION_FOR_MUSTACH` +When this option is set, the order is reverted and content +of partial is search (1) in the current json context, +and if not found, (2) as a file. - Disable the object iteration extension. That extension allows - to iterate over the keys of an object. The iteration on object - is selected by using the selector `{{#key.*}}`. In the context - of iterating over object keys, the single key `{{*}}` returns the - key and `{{.}}` returns the value. +That option is useful to keep the compatibility with +versions of *mustach* anteriors to 1.2.0. -- `NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH` +This is a wrap extension implemented in file **mustach-wrap.c**. - Disable access to current object value using single dot - like in `{{.}}`. +### Escape First Compare -- `NO_INCLUDE_PARTIAL_FALLBACK` +This extension automatically escapes comparisons appears as +first characters. + +This is a wrap extension implemented in file **mustach-wrap.c**. + +## Difference with version 0.99 and previous + +### Extensions + +The extensions can no more be removed at compile time, use +flags to select your required extension on need. + +### Name of functions + +Names of functions were improved. Old names remain but are obsolete +and legacy. Their removal in far future versions is possible. + +The table below summarize the changes. + + legacy name | name since version 1.0.0 + ------------------+----------------------- + fmustach | mustach_file + fdmustach | mustach_fd + mustach | mustach_mem + fmustach_json_c | mustach_json_c_file + fdmustach_json_c | mustach_json_c_fd + mustach_json_c | mustach_json_c_mem + mustach_json_c | mustach_json_c_write - Disable include of file by partial pattern like `{{> name}}`. - By default if a such pattern is found, **mustach** search - for `name` in the current json context. This what is done - historically and when `NO_INCLUDE_PARTIAL_FALLBACK` is defined. - When `NO_INCLUDE_PARTIAL_FALLBACK` is defined, if the value is - found in the json context, the files `name` and `name.mustache` - are searched in that order and the first file found is used - as partial content. The macro `INCLUDE_PARTIAL_EXTENSION` can - be use for changing the extension added. diff --git a/src/templating/mustach-cjson.c b/src/templating/mustach-cjson.c new file mode 100644 index 000000000..48b97e7df --- /dev/null +++ b/src/templating/mustach-cjson.c @@ -0,0 +1,239 @@ +/* + Author: José Bollo + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#define _GNU_SOURCE + +#include +#include +#include + +#include "mustach.h" +#include "mustach-wrap.h" +#include "mustach-cjson.h" + +struct expl { + cJSON null; + cJSON *root; + cJSON *selection; + int depth; + struct { + cJSON *cont; + cJSON *obj; + cJSON *next; + int is_objiter; + } stack[MUSTACH_MAX_DEPTH]; +}; + +static int start(void *closure) +{ + struct expl *e = closure; + e->depth = 0; + memset(&e->null, 0, sizeof e->null); + e->null.type = cJSON_NULL; + e->selection = &e->null; + e->stack[0].cont = NULL; + e->stack[0].obj = e->root; + return MUSTACH_OK; +} + +static int compare(void *closure, const char *value) +{ + struct expl *e = closure; + cJSON *o = e->selection; + double d; + + if (cJSON_IsNumber(o)) { + d = o->valuedouble - atof(value); + return d < 0 ? -1 : d > 0 ? 1 : 0; + } else if (cJSON_IsString(o)) { + return strcmp(o->valuestring, value); + } else if (cJSON_IsTrue(o)) { + return strcmp("true", value); + } else if (cJSON_IsFalse(o)) { + return strcmp("false", value); + } else if (cJSON_IsNull(o)) { + return strcmp("null", value); + } else { + return 1; + } +} + +static int sel(void *closure, const char *name) +{ + struct expl *e = closure; + cJSON *o; + int i, r; + + if (name == NULL) { + o = e->stack[e->depth].obj; + r = 1; + } else { + i = e->depth; + while (i >= 0 && !(o = cJSON_GetObjectItemCaseSensitive(e->stack[i].obj, name))) + i--; + if (i >= 0) + r = 1; + else { + o = &e->null; + r = 0; + } + } + e->selection = o; + return r; +} + +static int subsel(void *closure, const char *name) +{ + struct expl *e = closure; + cJSON *o; + int r; + + o = cJSON_GetObjectItemCaseSensitive(e->selection, name); + r = o != NULL; + if (r) + e->selection = o; + return r; +} + +static int enter(void *closure, int objiter) +{ + struct expl *e = closure; + cJSON *o; + + if (++e->depth >= MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + + o = e->selection; + e->stack[e->depth].is_objiter = 0; + if (objiter) { + if (! cJSON_IsObject(o)) + goto not_entering; + if (o->child == NULL) + goto not_entering; + e->stack[e->depth].obj = o->child; + e->stack[e->depth].next = o->child->next; + e->stack[e->depth].cont = o; + e->stack[e->depth].is_objiter = 1; + } else if (cJSON_IsArray(o)) { + if (o->child == NULL) + goto not_entering; + e->stack[e->depth].obj = o->child; + e->stack[e->depth].next = o->child->next; + e->stack[e->depth].cont = o; + } else if ((cJSON_IsObject(o) && o->child == NULL) || (! cJSON_IsFalse(o) && ! cJSON_IsNull(o))) { + e->stack[e->depth].obj = o; + e->stack[e->depth].cont = NULL; + e->stack[e->depth].next = NULL; + } else + goto not_entering; + return 1; + +not_entering: + e->depth--; + return 0; +} + +static int next(void *closure) +{ + struct expl *e = closure; + cJSON *o; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + o = e->stack[e->depth].next; + if (o == NULL) + return 0; + + e->stack[e->depth].obj = o; + e->stack[e->depth].next = o->next; + return 1; +} + +static int leave(void *closure) +{ + struct expl *e = closure; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + e->depth--; + return 0; +} + +static int get(void *closure, struct mustach_sbuf *sbuf, int key) +{ + struct expl *e = closure; + const char *s; + + if (key) { + s = e->stack[e->depth].is_objiter + ? e->stack[e->depth].obj->string + : ""; + } + else if (cJSON_IsString(e->selection)) + s = e->selection->valuestring; + else if (cJSON_IsNull(e->selection)) + s = ""; + else { + s = cJSON_PrintUnformatted(e->selection); + if (s == NULL) + return MUSTACH_ERROR_SYSTEM; + sbuf->freecb = cJSON_free; + } + sbuf->value = s; + return 1; +} + +const struct mustach_wrap_itf mustach_cJSON_wrap_itf = { + .start = start, + .stop = NULL, + .compare = compare, + .sel = sel, + .subsel = subsel, + .enter = enter, + .next = next, + .leave = leave, + .get = get +}; + +int mustach_cJSON_file(const char *template, size_t length, cJSON *root, int flags, FILE *file) +{ + struct expl e; + e.root = root; + return mustach_wrap_file(template, length, &mustach_cJSON_wrap_itf, &e, flags, file); +} + +int mustach_cJSON_fd(const char *template, size_t length, cJSON *root, int flags, int fd) +{ + struct expl e; + e.root = root; + return mustach_wrap_fd(template, length, &mustach_cJSON_wrap_itf, &e, flags, fd); +} + +int mustach_cJSON_mem(const char *template, size_t length, cJSON *root, int flags, char **result, size_t *size) +{ + struct expl e; + e.root = root; + return mustach_wrap_mem(template, length, &mustach_cJSON_wrap_itf, &e, flags, result, size); +} + +int mustach_cJSON_write(const char *template, size_t length, cJSON *root, int flags, mustach_write_cb_t *writecb, void *closure) +{ + struct expl e; + e.root = root; + return mustach_wrap_write(template, length, &mustach_cJSON_wrap_itf, &e, flags, writecb, closure); +} + +int mustach_cJSON_emit(const char *template, size_t length, cJSON *root, int flags, mustach_emit_cb_t *emitcb, void *closure) +{ + struct expl e; + e.root = root; + return mustach_wrap_emit(template, length, &mustach_cJSON_wrap_itf, &e, flags, emitcb, closure); +} + diff --git a/src/templating/mustach-cjson.h b/src/templating/mustach-cjson.h new file mode 100644 index 000000000..ae0d818cb --- /dev/null +++ b/src/templating/mustach-cjson.h @@ -0,0 +1,96 @@ +/* + Author: José Bollo + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _mustach_cJSON_h_included_ +#define _mustach_cJSON_h_included_ + +/* + * mustach-json-c is intended to make integration of cJSON + * library by providing integrated functions. + */ + +#include +#include "mustach-wrap.h" + +/** + * Wrap interface used internally by mustach cJSON functions. + * Can be used for overriding behaviour. + */ +extern const struct mustach_wrap_itf mustach_cJSON_wrap_itf; + +/** + * mustach_cJSON_file - Renders the mustache 'template' in 'file' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @file: the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_cJSON_file(const char *template, size_t length, cJSON *root, int flags, FILE *file); + +/** + * mustach_cJSON_fd - Renders the mustache 'template' in 'fd' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @fd: the file descriptor number where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_cJSON_fd(const char *template, size_t length, cJSON *root, int flags, int fd); + + +/** + * mustach_cJSON_mem - Renders the mustache 'template' in 'result' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @result: the pointer receiving the result when 0 is returned + * @size: the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_cJSON_mem(const char *template, size_t length, cJSON *root, int flags, char **result, size_t *size); + +/** + * mustach_cJSON_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @writecb: the function that write values + * @closure: the closure for the write function + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_cJSON_write(const char *template, size_t length, cJSON *root, int flags, mustach_write_cb_t *writecb, void *closure); + +/** + * mustach_cJSON_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @emitcb: the function that emit values + * @closure: the closure for the write function + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_cJSON_emit(const char *template, size_t length, cJSON *root, int flags, mustach_emit_cb_t *emitcb, void *closure); + +#endif + diff --git a/src/templating/mustach-jansson.c b/src/templating/mustach-jansson.c index c65fe2b01..3ce0e0a84 100644 --- a/src/templating/mustach-jansson.c +++ b/src/templating/mustach-jansson.c @@ -1,429 +1,251 @@ /* - Copyright (C) 2020 Taler Systems SA - - Original license: Author: José Bollo - Author: José Bollo https://gitlab.com/jobol/mustach - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + SPDX-License-Identifier: ISC */ -#include "platform.h" +#define _GNU_SOURCE + +#include +#include + +#include "mustach.h" +#include "mustach-wrap.h" #include "mustach-jansson.h" -struct Context -{ - /** - * Context object. - */ - json_t *cont; - - /** - * Current object. - */ - json_t *obj; - - /** - * Opaque object iterator. - */ - void *iter; - - /** - * Current index when iterating over an array. - */ - unsigned int index; - - /** - * Count when iterating over an array. - */ - unsigned int count; - - bool is_objiter; +struct expl { + json_t *root; + json_t *selection; + int depth; + struct { + json_t *cont; + json_t *obj; + void *iter; + int is_objiter; + size_t index, count; + } stack[MUSTACH_MAX_DEPTH]; }; -enum Bang +static int start(void *closure) { - BANG_NONE, - BANG_I18N, - BANG_STRINGIFY, - BANG_AMOUNT_CURRENCY, - BANG_AMOUNT_DECIMAL, + struct expl *e = closure; + e->depth = 0; + e->selection = json_null(); + e->stack[0].cont = NULL; + e->stack[0].obj = e->root; + e->stack[0].index = 0; + e->stack[0].count = 1; + return MUSTACH_OK; +} + +static int compare(void *closure, const char *value) +{ + struct expl *e = closure; + json_t *o = e->selection; + double d; + json_int_t i; + + switch (json_typeof(o)) { + case JSON_REAL: + d = json_number_value(o) - atof(value); + return d < 0 ? -1 : d > 0 ? 1 : 0; + case JSON_INTEGER: + i = (json_int_t)json_integer_value(o) - (json_int_t)atoll(value); + return i < 0 ? -1 : i > 0 ? 1 : 0; + case JSON_STRING: + return strcmp(json_string_value(o), value); + case JSON_TRUE: + return strcmp("true", value); + case JSON_FALSE: + return strcmp("false", value); + case JSON_NULL: + return strcmp("null", value); + default: + return 1; + } +} + +static int sel(void *closure, const char *name) +{ + struct expl *e = closure; + json_t *o; + int i, r; + + if (name == NULL) { + o = e->stack[e->depth].obj; + r = 1; + } else { + i = e->depth; + while (i >= 0 && !(o = json_object_get(e->stack[i].obj, name))) + i--; + if (i >= 0) + r = 1; + else { + o = json_null(); + r = 0; + } + } + e->selection = o; + return r; +} + +static int subsel(void *closure, const char *name) +{ + struct expl *e = closure; + json_t *o; + int r; + + o = json_object_get(e->selection, name); + r = o != NULL; + if (r) + e->selection = o; + return r; +} + +static int enter(void *closure, int objiter) +{ + struct expl *e = closure; + json_t *o; + + if (++e->depth >= MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + + o = e->selection; + e->stack[e->depth].is_objiter = 0; + if (objiter) { + if (!json_is_object(o)) + goto not_entering; + e->stack[e->depth].iter = json_object_iter(o); + if (e->stack[e->depth].iter == NULL) + goto not_entering; + e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter); + e->stack[e->depth].cont = o; + e->stack[e->depth].is_objiter = 1; + } else if (json_is_array(o)) { + e->stack[e->depth].count = json_array_size(o); + if (e->stack[e->depth].count == 0) + goto not_entering; + e->stack[e->depth].cont = o; + e->stack[e->depth].obj = json_array_get(o, 0); + e->stack[e->depth].index = 0; + } else if ((json_is_object(o) && json_object_size(0)) || (!json_is_false(o) && !json_is_null(o))) { + e->stack[e->depth].count = 1; + e->stack[e->depth].cont = NULL; + e->stack[e->depth].obj = o; + e->stack[e->depth].index = 0; + } else + goto not_entering; + return 1; + +not_entering: + e->depth--; + return 0; +} + +static int next(void *closure) +{ + struct expl *e = closure; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + if (e->stack[e->depth].is_objiter) { + e->stack[e->depth].iter = json_object_iter_next(e->stack[e->depth].cont, e->stack[e->depth].iter); + if (e->stack[e->depth].iter == NULL) + return 0; + e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter); + return 1; + } + + e->stack[e->depth].index++; + if (e->stack[e->depth].index >= e->stack[e->depth].count) + return 0; + + e->stack[e->depth].obj = json_array_get(e->stack[e->depth].cont, e->stack[e->depth].index); + return 1; +} + +static int leave(void *closure) +{ + struct expl *e = closure; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + e->depth--; + return 0; +} + +static int get(void *closure, struct mustach_sbuf *sbuf, int key) +{ + struct expl *e = closure; + const char *s; + + if (key) { + s = e->stack[e->depth].is_objiter + ? json_object_iter_key(e->stack[e->depth].iter) + : ""; + } + else if (json_is_string(e->selection)) + s = json_string_value(e->selection); + else if (json_is_null(e->selection)) + s = ""; + else { + s = json_dumps(e->selection, JSON_ENCODE_ANY | JSON_COMPACT); + if (s == NULL) + return MUSTACH_ERROR_SYSTEM; + sbuf->freecb = free; + } + sbuf->value = s; + return 1; +} + +const struct mustach_wrap_itf mustach_jansson_wrap_itf = { + .start = start, + .stop = NULL, + .compare = compare, + .sel = sel, + .subsel = subsel, + .enter = enter, + .next = next, + .leave = leave, + .get = get }; -struct JanssonClosure +int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file) { - json_t *root; - mustach_jansson_write_cb writecb; - int depth; - - /** - * Did the last find(..) call result in an iterable? - */ - struct Context stack[MUSTACH_MAX_DEPTH]; - - /** - * The last object we found should be iterated over. - */ - bool found_iter; - - /** - * Last bang we found. - */ - enum Bang found_bang; - - /** - * Language for i18n lookups. - */ - const char *lang; -}; - - -static json_t * -walk (json_t *obj, const char *path) -{ - char *saveptr = NULL; - char *sp = GNUNET_strdup (path); - char *p = sp; - while (true) - { - char *tok = strtok_r (p, ".", &saveptr); - if (tok == NULL) - break; - obj = json_object_get (obj, tok); - if (obj == NULL) - break; - p = NULL; - } - GNUNET_free (sp); - return obj; + struct expl e; + e.root = root; + return mustach_wrap_file(template, length, &mustach_jansson_wrap_itf, &e, flags, file); } - -static json_t * -find (struct JanssonClosure *e, const char *name) +int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd) { - json_t *obj = NULL; - char *path = GNUNET_strdup (name); - char *bang; - - bang = strchr (path, '!'); - - e->found_bang = BANG_NONE; - - if (NULL != bang) - { - *bang = 0; - bang++; - - if (0 == strcmp (bang, "i18n")) - e->found_bang = BANG_I18N; - else if (0 == strcmp (bang, "stringify")) - e->found_bang = BANG_STRINGIFY; - else if (0 == strcmp (bang, "amount_decimal")) - e->found_bang = BANG_AMOUNT_CURRENCY; - else if (0 == strcmp (bang, "amount_currency")) - e->found_bang = BANG_AMOUNT_DECIMAL; - } - - if (BANG_I18N == e->found_bang && NULL != e->lang) - { - char *aug_path; - GNUNET_asprintf (&aug_path, "%s_i18n.%s", path, e->lang); - obj = walk (e->stack[e->depth].obj, aug_path); - GNUNET_free (aug_path); - } - - if (NULL == obj) - { - obj = walk (e->stack[e->depth].obj, path); - } - - GNUNET_free (path); - - return obj; + struct expl e; + e.root = root; + return mustach_wrap_fd(template, length, &mustach_jansson_wrap_itf, &e, flags, fd); } - -static int -start (void *closure) +int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size) { - struct JanssonClosure *e = closure; - e->depth = 0; - e->stack[0].cont = NULL; - e->stack[0].obj = e->root; - e->stack[0].index = 0; - e->stack[0].count = 1; - e->lang = json_string_value (json_object_get (e->root, "$language")); - return MUSTACH_OK; + struct expl e; + e.root = root; + return mustach_wrap_mem(template, length, &mustach_jansson_wrap_itf, &e, flags, result, size); } - -static int -emituw (void *closure, const char *buffer, size_t size, int escape, FILE *file) +int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure) { - struct JanssonClosure *e = closure; - if (! escape) - e->writecb (file, buffer, size); - else - do - { - switch (*buffer) - { - case '<': - e->writecb (file, "<", 4); - break; - case '>': - e->writecb (file, ">", 4); - break; - case '&': - e->writecb (file, "&", 5); - break; - default: - e->writecb (file, buffer, 1); - break; - } - buffer++; - } - while(--size); - return MUSTACH_OK; + struct expl e; + e.root = root; + return mustach_wrap_write(template, length, &mustach_jansson_wrap_itf, &e, flags, writecb, closure); } - -static int -enter (void *closure, const char *name) +int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure) { - struct JanssonClosure *e = closure; - json_t *o = find (e, name); - if (++e->depth >= MUSTACH_MAX_DEPTH) - return MUSTACH_ERROR_TOO_DEEP; - - if (json_is_object (o)) - { - if (e->found_iter) - { - void *iter = json_object_iter (o); - if (NULL == iter) - { - e->depth--; - return 0; - } - e->stack[e->depth].is_objiter = 1; - e->stack[e->depth].iter = iter; - e->stack[e->depth].obj = json_object_iter_value (iter); - e->stack[e->depth].cont = o; - } - else - { - e->stack[e->depth].is_objiter = 0; - e->stack[e->depth].obj = o; - e->stack[e->depth].cont = o; - } - return 1; - } - - if (json_is_array (o)) - { - unsigned int size = json_array_size (o); - if (size == 0) - { - e->depth--; - return 0; - } - e->stack[e->depth].count = size; - e->stack[e->depth].cont = o; - e->stack[e->depth].obj = json_array_get (o, 0); - e->stack[e->depth].index = 0; - e->stack[e->depth].is_objiter = 0; - return 1; - } - - e->depth--; - return 0; + struct expl e; + e.root = root; + return mustach_wrap_emit(template, length, &mustach_jansson_wrap_itf, &e, flags, emitcb, closure); } - -static int -next (void *closure) -{ - struct JanssonClosure *e = closure; - struct Context *ctx; - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; - ctx = &e->stack[e->depth]; - if (ctx->is_objiter) - { - ctx->iter = json_object_iter_next (ctx->obj, ctx->iter); - if (NULL == ctx->iter) - return 0; - ctx->obj = json_object_iter_value (ctx->iter); - return 1; - } - ctx->index++; - if (ctx->index >= ctx->count) - return 0; - ctx->obj = json_array_get (ctx->cont, ctx->index); - return 1; -} - - -static int -leave (void *closure) -{ - struct JanssonClosure *e = closure; - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; - e->depth--; - return 0; -} - - -static void -freecb (void *v) -{ - free (v); -} - - -static int -get (void *closure, const char *name, struct mustach_sbuf *sbuf) -{ - struct JanssonClosure *e = closure; - json_t *obj; - - if ( (0 == strcmp (name, "*") ) && - (e->stack[e->depth].is_objiter) ) - { - sbuf->value = json_object_iter_key (e->stack[e->depth].iter); - return MUSTACH_OK; - } - obj = find (e, name); - if (NULL != obj) - { - switch (e->found_bang) - { - case BANG_I18N: - case BANG_NONE: - { - const char *s = json_string_value (obj); - if (NULL != s) - { - sbuf->value = s; - return MUSTACH_OK; - } - } - break; - case BANG_STRINGIFY: - sbuf->value = json_dumps (obj, JSON_INDENT (2)); - sbuf->freecb = freecb; - return MUSTACH_OK; - case BANG_AMOUNT_DECIMAL: - { - char *s; - char *c; - if (! json_is_string (obj)) - break; - s = GNUNET_strdup (json_string_value (obj)); - c = strchr (s, ':'); - if (NULL != c) - *c = 0; - sbuf->value = s; - sbuf->freecb = freecb; - return MUSTACH_OK; - } - break; - case BANG_AMOUNT_CURRENCY: - { - const char *s; - if (! json_is_string (obj)) - break; - s = json_string_value (obj); - s = strchr (s, ':'); - if (NULL == s) - break; - sbuf->value = s + 1; - return MUSTACH_OK; - } - break; - default: - break; - } - } - sbuf->value = ""; - return MUSTACH_OK; -} - - -static struct mustach_itf itf = { - .start = start, - .put = NULL, - .enter = enter, - .next = next, - .leave = leave, - .partial = NULL, - .get = get, - .emit = NULL, - .stop = NULL -}; - -static struct mustach_itf itfuw = { - .start = start, - .put = NULL, - .enter = enter, - .next = next, - .leave = leave, - .partial = NULL, - .get = get, - .emit = emituw, - .stop = NULL -}; - -int -fmustach_jansson (const char *template, json_t *root, FILE *file) -{ - struct JanssonClosure e = { 0 }; - e.root = root; - return fmustach (template, &itf, &e, file); -} - - -int -fdmustach_jansson (const char *template, json_t *root, int fd) -{ - struct JanssonClosure e = { 0 }; - e.root = root; - return fdmustach (template, &itf, &e, fd); -} - - -int -mustach_jansson (const char *template, json_t *root, char **result, - size_t *size) -{ - struct JanssonClosure e = { 0 }; - e.root = root; - e.writecb = NULL; - return mustach (template, &itf, &e, result, size); -} - - -int -umustach_jansson (const char *template, json_t *root, mustach_jansson_write_cb - writecb, void *closure) -{ - struct JanssonClosure e = { 0 }; - e.root = root; - e.writecb = writecb; - return fmustach (template, &itfuw, &e, closure); -} diff --git a/src/templating/mustach-jansson.h b/src/templating/mustach-jansson.h index 8fe989fa5..8def948e0 100644 --- a/src/templating/mustach-jansson.h +++ b/src/templating/mustach-jansson.h @@ -1,60 +1,60 @@ /* - Copyright (C) 2020 Taler Systems SA - - Original license: - Author: José Bollo Author: José Bollo https://gitlab.com/jobol/mustach - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + SPDX-License-Identifier: ISC */ #ifndef _mustach_jansson_h_included_ #define _mustach_jansson_h_included_ -#include "taler_json_lib.h" -#include "mustach.h" +/* + * mustach-jansson is intended to make integration of jansson + * library by providing integrated functions. + */ + +#include +#include "mustach-wrap.h" /** - * fmustach_jansson - Renders the mustache 'template' in 'file' for 'root'. + * Wrap interface used internally by mustach jansson functions. + * Can be used for overriding behaviour. + */ +extern const struct mustach_wrap_itf mustach_jansson_wrap_itf; + +/** + * mustach_jansson_file - Renders the mustache 'template' in 'file' for 'root'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @root: the root json object to render - * \@file: the file where to write the result + * @file: the file where to write the result * * Returns 0 in case of success, -1 with errno set in case of system error * a other negative value in case of error. */ -extern int fmustach_jansson (const char *template, json_t *root, FILE *file); +extern int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file); /** - * fmustach_jansson - Renders the mustache 'template' in 'fd' for 'root'. + * mustach_jansson_fd - Renders the mustache 'template' in 'fd' for 'root'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @root: the root json object to render * @fd: the file descriptor number where to write the result * * Returns 0 in case of success, -1 with errno set in case of system error * a other negative value in case of error. */ -extern int fdmustach_jansson (const char *template, json_t *root, int fd); +extern int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd); /** - * fmustach_jansson - Renders the mustache 'template' in 'result' for 'root'. + * mustach_jansson_mem - Renders the mustache 'template' in 'result' for 'root'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @root: the root json object to render * @result: the pointer receiving the result when 0 is returned * @size: the size of the returned result @@ -62,13 +62,13 @@ extern int fdmustach_jansson (const char *template, json_t *root, int fd); * Returns 0 in case of success, -1 with errno set in case of system error * a other negative value in case of error. */ -extern int mustach_jansson (const char *template, json_t *root, char **result, - size_t *size); +extern int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size); /** - * umustach_jansson - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. + * mustach_jansson_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @root: the root json object to render * @writecb: the function that write values * @closure: the closure for the write function @@ -76,9 +76,21 @@ extern int mustach_jansson (const char *template, json_t *root, char **result, * Returns 0 in case of success, -1 with errno set in case of system error * a other negative value in case of error. */ -typedef int (*mustach_jansson_write_cb)(void *closure, const char *buffer, - size_t size); -extern int umustach_jansson (const char *template, json_t *root, - mustach_jansson_write_cb writecb, void *closure); +extern int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure); + +/** + * mustach_jansson_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @emitcb: the function that emit values + * @closure: the closure for the write function + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure); #endif + diff --git a/src/templating/mustach-json-c.c b/src/templating/mustach-json-c.c new file mode 100644 index 000000000..a21a113fb --- /dev/null +++ b/src/templating/mustach-json-c.c @@ -0,0 +1,267 @@ +/* + Author: José Bollo + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#define _GNU_SOURCE + +#include +#include + +#include "mustach.h" +#include "mustach-wrap.h" +#include "mustach-json-c.h" + +struct expl { + struct json_object *root; + struct json_object *selection; + int depth; + struct { + struct json_object *cont; + struct json_object *obj; + struct json_object_iterator iter; + struct json_object_iterator enditer; + int is_objiter; + int index, count; + } stack[MUSTACH_MAX_DEPTH]; +}; + +static int start(void *closure) +{ + struct expl *e = closure; + e->depth = 0; + e->selection = NULL; + e->stack[0].cont = NULL; + e->stack[0].obj = e->root; + e->stack[0].index = 0; + e->stack[0].count = 1; + return MUSTACH_OK; +} + +static int compare(void *closure, const char *value) +{ + struct expl *e = closure; + struct json_object *o = e->selection; + double d; + int64_t i; + + switch (json_object_get_type(o)) { + case json_type_double: + d = json_object_get_double(o) - atof(value); + return d < 0 ? -1 : d > 0 ? 1 : 0; + case json_type_int: + i = json_object_get_int64(o) - (int64_t)atoll(value); + return i < 0 ? -1 : i > 0 ? 1 : 0; + default: + return strcmp(json_object_get_string(o), value); + } +} + +static int sel(void *closure, const char *name) +{ + struct expl *e = closure; + struct json_object *o; + int i, r; + + if (name == NULL) { + o = e->stack[e->depth].obj; + r = 1; + } else { + i = e->depth; + while (i >= 0 && !json_object_object_get_ex(e->stack[i].obj, name, &o)) + i--; + if (i >= 0) + r = 1; + else { + o = NULL; + r = 0; + } + } + e->selection = o; + return r; +} + +static int subsel(void *closure, const char *name) +{ + struct expl *e = closure; + struct json_object *o; + int r; + + r = json_object_object_get_ex(e->selection, name, &o); + if (r) + e->selection = o; + return r; +} + +static int enter(void *closure, int objiter) +{ + struct expl *e = closure; + struct json_object *o; + + if (++e->depth >= MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + + o = e->selection; + e->stack[e->depth].is_objiter = 0; + if (objiter) { + if (!json_object_is_type(o, json_type_object)) + goto not_entering; + + e->stack[e->depth].iter = json_object_iter_begin(o); + e->stack[e->depth].enditer = json_object_iter_end(o); + if (json_object_iter_equal(&e->stack[e->depth].iter, &e->stack[e->depth].enditer)) + goto not_entering; + e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].iter); + e->stack[e->depth].cont = o; + e->stack[e->depth].is_objiter = 1; + } else if (json_object_is_type(o, json_type_array)) { + e->stack[e->depth].count = json_object_array_length(o); + if (e->stack[e->depth].count == 0) + goto not_entering; + e->stack[e->depth].cont = o; + e->stack[e->depth].obj = json_object_array_get_idx(o, 0); + e->stack[e->depth].index = 0; + } else if (json_object_is_type(o, json_type_object) || json_object_get_boolean(o)) { + e->stack[e->depth].count = 1; + e->stack[e->depth].cont = NULL; + e->stack[e->depth].obj = o; + e->stack[e->depth].index = 0; + } else + goto not_entering; + return 1; + +not_entering: + e->depth--; + return 0; +} + +static int next(void *closure) +{ + struct expl *e = closure; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + if (e->stack[e->depth].is_objiter) { + json_object_iter_next(&e->stack[e->depth].iter); + if (json_object_iter_equal(&e->stack[e->depth].iter, &e->stack[e->depth].enditer)) + return 0; + e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].iter); + return 1; + } + + e->stack[e->depth].index++; + if (e->stack[e->depth].index >= e->stack[e->depth].count) + return 0; + + e->stack[e->depth].obj = json_object_array_get_idx(e->stack[e->depth].cont, e->stack[e->depth].index); + return 1; +} + +static int leave(void *closure) +{ + struct expl *e = closure; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + e->depth--; + return 0; +} + +static int get(void *closure, struct mustach_sbuf *sbuf, int key) +{ + struct expl *e = closure; + const char *s; + + if (key) + s = e->stack[e->depth].is_objiter + ? json_object_iter_peek_name(&e->stack[e->depth].iter) + : ""; + else + switch (json_object_get_type(e->selection)) { + case json_type_string: + s = json_object_get_string(e->selection); + break; + case json_type_null: + s = ""; + break; + default: + s = json_object_to_json_string_ext(e->selection, 0); + break; + } + sbuf->value = s; + return 1; +} + +const struct mustach_wrap_itf mustach_json_c_wrap_itf = { + .start = start, + .stop = NULL, + .compare = compare, + .sel = sel, + .subsel = subsel, + .enter = enter, + .next = next, + .leave = leave, + .get = get +}; + +int mustach_json_c_file(const char *template, size_t length, struct json_object *root, int flags, FILE *file) +{ + struct expl e; + e.root = root; + return mustach_wrap_file(template, length, &mustach_json_c_wrap_itf, &e, flags, file); +} + +int mustach_json_c_fd(const char *template, size_t length, struct json_object *root, int flags, int fd) +{ + struct expl e; + e.root = root; + return mustach_wrap_fd(template, length, &mustach_json_c_wrap_itf, &e, flags, fd); +} + +int mustach_json_c_mem(const char *template, size_t length, struct json_object *root, int flags, char **result, size_t *size) +{ + struct expl e; + e.root = root; + return mustach_wrap_mem(template, length, &mustach_json_c_wrap_itf, &e, flags, result, size); +} + +int mustach_json_c_write(const char *template, size_t length, struct json_object *root, int flags, mustach_write_cb_t *writecb, void *closure) +{ + struct expl e; + e.root = root; + return mustach_wrap_write(template, length, &mustach_json_c_wrap_itf, &e, flags, writecb, closure); +} + +int mustach_json_c_emit(const char *template, size_t length, struct json_object *root, int flags, mustach_emit_cb_t *emitcb, void *closure) +{ + struct expl e; + e.root = root; + return mustach_wrap_emit(template, length, &mustach_json_c_wrap_itf, &e, flags, emitcb, closure); +} + +int fmustach_json_c(const char *template, struct json_object *root, FILE *file) +{ + return mustach_json_c_file(template, 0, root, -1, file); +} + +int fdmustach_json_c(const char *template, struct json_object *root, int fd) +{ + return mustach_json_c_fd(template, 0, root, -1, fd); +} + +int mustach_json_c(const char *template, struct json_object *root, char **result, size_t *size) +{ + return mustach_json_c_mem(template, 0, root, -1, result, size); +} + +int umustach_json_c(const char *template, struct json_object *root, mustach_write_cb_t *writecb, void *closure) +{ + return mustach_json_c_write(template, 0, root, -1, writecb, closure); +} + + diff --git a/src/templating/mustach-json-c.h b/src/templating/mustach-json-c.h new file mode 100644 index 000000000..50846c6cb --- /dev/null +++ b/src/templating/mustach-json-c.h @@ -0,0 +1,160 @@ +/* + Author: José Bollo + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _mustach_json_c_h_included_ +#define _mustach_json_c_h_included_ + +/* + * mustach-json-c is intended to make integration of json-c + * library by providing integrated functions. + */ + +#include +#include "mustach-wrap.h" + +/** + * Wrap interface used internally by mustach json-c functions. + * Can be used for overriding behaviour. + */ +extern const struct mustach_wrap_itf mustach_json_c_wrap_itf; + +/** + * mustach_json_c_file - Renders the mustache 'template' in 'file' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @file: the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_json_c_file(const char *template, size_t length, struct json_object *root, int flags, FILE *file); + +/** + * mustach_json_c_fd - Renders the mustache 'template' in 'fd' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @fd: the file descriptor number where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_json_c_fd(const char *template, size_t length, struct json_object *root, int flags, int fd); + +/** + * mustach_json_c_mem - Renders the mustache 'template' in 'result' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @result: the pointer receiving the result when 0 is returned + * @size: the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_json_c_mem(const char *template, size_t length, struct json_object *root, int flags, char **result, size_t *size); + +/** + * mustach_json_c_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @writecb: the function that write values + * @closure: the closure for the write function + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_json_c_write(const char *template, size_t length, struct json_object *root, int flags, mustach_write_cb_t *writecb, void *closure); + +/** + * mustach_json_c_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @emitcb: the function that emit values + * @closure: the closure for the write function + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_json_c_emit(const char *template, size_t length, struct json_object *root, int flags, mustach_emit_cb_t *emitcb, void *closure); + +/*************************************************************************** +* compatibility with version before 1.0 +*/ + +/** + * OBSOLETE use mustach_json_c_file + * + * fmustach_json_c - Renders the mustache 'template' in 'file' for 'root'. + * + * @template: the template string to instantiate + * @root: the root json object to render + * @file: the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ + +DEPRECATED_MUSTACH(extern int fmustach_json_c(const char *template, struct json_object *root, FILE *file)); + +/** + * OBSOLETE use mustach_json_c_fd + * + * fdmustach_json_c - Renders the mustache 'template' in 'fd' for 'root'. + * + * @template: the template string to instantiate + * @root: the root json object to render + * @fd: the file descriptor number where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ + +DEPRECATED_MUSTACH(extern int fdmustach_json_c(const char *template, struct json_object *root, int fd)); + +/** + * OBSOLETE use mustach_json_c_mem + * + * mustach_json_c - Renders the mustache 'template' in 'result' for 'root'. + * + * @template: the template string to instantiate + * @root: the root json object to render + * @result: the pointer receiving the result when 0 is returned + * @size: the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ + +DEPRECATED_MUSTACH(extern int mustach_json_c(const char *template, struct json_object *root, char **result, size_t *size)); + +/** + * OBSOLETE use mustach_json_c_write + * + * umustach_json_c - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. + * + * @template: the template string to instantiate + * @root: the root json object to render + * @writecb: the function that write values + * @closure: the closure for the write function + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +typedef mustach_write_cb_t *mustach_json_write_cb; +DEPRECATED_MUSTACH(extern int umustach_json_c(const char *template, struct json_object *root, mustach_write_cb_t *writecb, void *closure)); + +#endif diff --git a/src/templating/mustach-tool.c b/src/templating/mustach-tool.c index 364e34a84..0c8f44070 100644 --- a/src/templating/mustach-tool.c +++ b/src/templating/mustach-tool.c @@ -1,20 +1,9 @@ /* Author: José Bollo - Author: José Bollo https://gitlab.com/jobol/mustach - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + SPDX-License-Identifier: ISC */ #define _GNU_SOURCE @@ -27,7 +16,7 @@ #include #include -#include "mustach-json-c.h" +#include "mustach-wrap.h" static const size_t BLOCKSIZE = 8192; @@ -43,16 +32,39 @@ static const char *errors[] = { "bad unescape tag", "invalid interface", "item not found", - "partial not found" + "partial not found", + "undefined tag" }; +static const char *errmsg = 0; +static int flags = 0; +static FILE *output = 0; + static void help(char *prog) { - printf("usage: %s json-file mustach-templates...\n", basename(prog)); + char *name = basename(prog); +#define STR_INDIR(x) #x +#define STR(x) STR_INDIR(x) + printf("%s version %s\n", name, STR(VERSION)); +#undef STR +#undef STR_INDIR + printf( + "\n" + "USAGE:\n" + " %s [FLAGS] \n" + "\n" + "FLAGS:\n" + " -h, --help Prints help information\n" + " -s, --strict Error when a tag is undefined\n" + "\n" + "ARGS: (if a file is -, read standard input)\n" + " JSON file with input data\n" + " Template files to instantiate\n", + name); exit(0); } -static char *readfile(const char *filename) +static char *readfile(const char *filename, size_t *length) { int f; struct stat s; @@ -106,50 +118,140 @@ static char *readfile(const char *filename) } while(rc > 0); close(f); + if (length != NULL) + *length = pos; result[pos] = 0; return result; } +static int load_json(const char *filename); +static int process(const char *content, size_t length); +static void close_json(); + int main(int ac, char **av) { - struct json_object *o; - char *t; + char *t, *f; char *prog = *av; int s; + size_t length; (void)ac; /* unused */ + flags = Mustach_With_AllExtensions; + output = stdout; - if (*++av) { + for( ++av ; av[0] && av[0][0] == '-' && av[0][1] != 0 ; av++) { if (!strcmp(*av, "-h") || !strcmp(*av, "--help")) help(prog); - if (av[0][0] == '-' && !av[0][1]) - o = json_object_from_fd(0); - else - o = json_object_from_file(av[0]); -#if JSON_C_VERSION_NUM >= 0x000D00 - if (json_util_get_last_err() != NULL) { - fprintf(stderr, "Bad json: %s (file %s)\n", json_util_get_last_err(), av[0]); - exit(1); - } - else -#endif - if (o == NULL) { - fprintf(stderr, "Aborted: null json (file %s)\n", av[0]); + if (!strcmp(*av, "-s") || !strcmp(*av, "--strict")) + flags |= Mustach_With_ErrorUndefined; + } + if (*av) { + f = (av[0][0] == '-' && !av[0][1]) ? "/dev/stdin" : av[0]; + s = load_json(f); + if (s < 0) { + fprintf(stderr, "Can't load json file %s\n", av[0]); + if(errmsg) + fprintf(stderr, " reason: %s\n", errmsg); exit(1); } while(*++av) { - t = readfile(*av); - s = fmustach_json_c(t, o, stdout); - if (s != 0) { + t = readfile(*av, &length); + s = process(t, length); + free(t); + if (s != MUSTACH_OK) { s = -s; if (s < 1 || s >= (int)(sizeof errors / sizeof * errors)) s = 0; fprintf(stderr, "Template error %s (file %s)\n", errors[s], *av); } - free(t); } - json_object_put(o); + close_json(); } return 0; } +#define MUSTACH_TOOL_JSON_C 1 +#define MUSTACH_TOOL_JANSSON 2 +#define MUSTACH_TOOL_CJSON 3 + +#define TOOL MUSTACH_TOOL_JANSSON + +#if TOOL == MUSTACH_TOOL_JSON_C + +#include "mustach-json-c.h" + +static struct json_object *o; +static int load_json(const char *filename) +{ + o = json_object_from_file(filename); +#if JSON_C_VERSION_NUM >= 0x000D00 + errmsg = json_util_get_last_err(); + if (errmsg != NULL) + return -1; +#endif + if (o == NULL) { + errmsg = "null json"; + return -1; + } + return 0; +} +static int process(const char *content, size_t length) +{ + return mustach_json_c_file(content, length, o, flags, output); +} +static void close_json() +{ + json_object_put(o); +} + +#elif TOOL == MUSTACH_TOOL_JANSSON + +#include "mustach-jansson.h" + +static json_t *o; +static json_error_t e; +static int load_json(const char *filename) +{ + o = json_load_file(filename, JSON_DECODE_ANY, &e); + if (o == NULL) { + errmsg = e.text; + return -1; + } + return 0; +} +static int process(const char *content, size_t length) +{ + return mustach_jansson_file(content, length, o, flags, output); +} +static void close_json() +{ + json_decref(o); +} + +#elif TOOL == MUSTACH_TOOL_CJSON + +#include "mustach-cjson.h" + +static cJSON *o; +static int load_json(const char *filename) +{ + char *t; + size_t length; + + t = readfile(filename, &length); + o = t ? cJSON_ParseWithLength(t, length) : NULL; + free(t); + return -!o; +} +static int process(const char *content, size_t length) +{ + return mustach_cJSON_file(content, length, o, flags, output); +} +static void close_json() +{ + cJSON_Delete(o); +} + +#else +#error "no defined json library" +#endif diff --git a/src/templating/mustach-wrap.c b/src/templating/mustach-wrap.c new file mode 100644 index 000000000..75cc9d1f6 --- /dev/null +++ b/src/templating/mustach-wrap.c @@ -0,0 +1,456 @@ +/* + Author: José Bollo + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +#include "mustach.h" +#include "mustach-wrap.h" + +#if !defined(INCLUDE_PARTIAL_EXTENSION) +# define INCLUDE_PARTIAL_EXTENSION ".mustache" +#endif + +/* global hook for partials */ +int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf) = NULL; + +/* internal structure for wrapping */ +struct wrap { + /* original interface */ + const struct mustach_wrap_itf *itf; + + /* original closure */ + void *closure; + + /* flags */ + int flags; + + /* emiter callback */ + mustach_emit_cb_t *emitcb; + + /* write callback */ + mustach_write_cb_t *writecb; +}; + +/* length given by masking with 3 */ +enum comp { + C_no = 0, + C_eq = 1, + C_lt = 5, + C_le = 6, + C_gt = 9, + C_ge = 10 +}; + +enum sel { + S_none = 0, + S_ok = 1, + S_objiter = 2, + S_ok_or_objiter = S_ok | S_objiter +}; + +static enum comp getcomp(char *head, int sflags) +{ + return (head[0] == '=' && (sflags & Mustach_With_Equal)) ? C_eq + : (head[0] == '<' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_le : C_lt) + : (head[0] == '>' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_ge : C_gt) + : C_no; +} + +static char *keyval(char *head, int sflags, enum comp *comp) +{ + char *w, car, escaped; + enum comp k; + + k = C_no; + w = head; + car = *head; + escaped = (sflags & Mustach_With_EscFirstCmp) && (getcomp(head, sflags) != C_no); + while (car && (escaped || (k = getcomp(head, sflags)) == C_no)) { + if (escaped) + escaped = 0; + else + escaped = ((sflags & Mustach_With_JsonPointer) ? car == '~' : car == '\\') + && (getcomp(head + 1, sflags) != C_no); + if (!escaped) + *w++ = car; + head++; + car = *head; + } + *w = 0; + *comp = k; + return k == C_no ? NULL : &head[k & 3]; +} + +static char *getkey(char **head, int sflags) +{ + char *result, *iter, *write, car; + + car = *(iter = *head); + if (!car) + result = NULL; + else { + result = write = iter; + if (sflags & Mustach_With_JsonPointer) + { + while (car && car != '/') { + if (car == '~') + switch (iter[1]) { + case '1': car = '/'; /*@fallthrough@*/ + case '0': iter++; + } + *write++ = car; + car = *++iter; + } + *write = 0; + while (car == '/') + car = *++iter; + } + else + { + while (car && car != '.') { + if (car == '\\' && (iter[1] == '.' || iter[1] == '\\')) + car = *++iter; + *write++ = car; + car = *++iter; + } + *write = 0; + while (car == '.') + car = *++iter; + } + *head = iter; + } + return result; +} + +static enum sel sel(struct wrap *w, const char *name) +{ + enum sel result; + int i, j, sflags, scmp; + char *key, *value; + enum comp k; + + /* make a local writeable copy */ + size_t lenname = 1 + strlen(name); + char buffer[lenname]; + char *copy = buffer; + memcpy(copy, name, lenname); + + /* check if matches json pointer selection */ + sflags = w->flags; + if (sflags & Mustach_With_JsonPointer) { + if (copy[0] == '/') + copy++; + else + sflags ^= Mustach_With_JsonPointer; + } + + /* extract the value, translate the key and get the comparator */ + if (sflags & (Mustach_With_Equal | Mustach_With_Compare)) + value = keyval(copy, sflags, &k); + else { + k = C_no; + value = NULL; + } + + /* case of . alone if Mustach_With_SingleDot? */ + if (copy[0] == '.' && copy[1] == 0 /*&& (sflags & Mustach_With_SingleDot)*/) + /* yes, select current */ + result = w->itf->sel(w->closure, NULL) ? S_ok : S_none; + else + { + /* not the single dot, extract the first key */ + key = getkey(©, sflags); + if (key == NULL) + return 0; + + /* select the root item */ + if (w->itf->sel(w->closure, key)) + result = S_ok; + else if (key[0] == '*' + && !key[1] + && !value + && !*copy + && (w->flags & Mustach_With_ObjectIter) + && w->itf->sel(w->closure, NULL)) + result = S_ok_or_objiter; + else + result = S_none; + if (result == S_ok) { + /* iterate the selection of sub items */ + key = getkey(©, sflags); + while(result == S_ok && key) { + if (w->itf->subsel(w->closure, key)) + /* nothing */; + else if (key[0] == '*' + && !key[1] + && !value + && !*copy + && (w->flags & Mustach_With_ObjectIter)) + result = S_objiter; + else + result = S_none; + key = getkey(©, sflags); + } + } + } + /* should it be compared? */ + if (result == S_ok && value) { + if (!w->itf->compare) + result = S_none; + else { + i = value[0] == '!'; + scmp = w->itf->compare(w->closure, &value[i]); + switch (k) { + case C_eq: j = scmp == 0; break; + case C_lt: j = scmp < 0; break; + case C_le: j = scmp <= 0; break; + case C_gt: j = scmp > 0; break; + case C_ge: j = scmp >= 0; break; + default: j = i; break; + } + if (i == j) + result = S_none; + } + } + return result; +} + +static int start(void *closure) +{ + struct wrap *w = closure; + return w->itf->start ? w->itf->start(w->closure) : MUSTACH_OK; +} + +static void stop(void *closure, int status) +{ + struct wrap *w = closure; + if (w->itf->stop) + w->itf->stop(w->closure, status); +} + +static int write(struct wrap *w, const char *buffer, size_t size, FILE *file) +{ + int r; + + if (w->writecb) + r = w->writecb(file, buffer, size); + else + r = fwrite(buffer, 1, size, file) == size ? MUSTACH_OK : MUSTACH_ERROR_SYSTEM; + return r; +} + +static int emit(void *closure, const char *buffer, size_t size, int escape, FILE *file) +{ + struct wrap *w = closure; + int r; + size_t s, i; + char car; + + if (w->emitcb) + r = w->emitcb(file, buffer, size, escape); + else if (!escape) + r = write(w, buffer, size, file); + else { + i = 0; + r = MUSTACH_OK; + while(i < size && r == MUSTACH_OK) { + s = i; + while (i < size && (car = buffer[i]) != '<' && car != '>' && car != '&' && car != '"') + i++; + if (i != s) + r = write(w, &buffer[s], i - s, file); + if (i < size && r == MUSTACH_OK) { + switch(car) { + case '<': r = write(w, "<", 4, file); break; + case '>': r = write(w, ">", 4, file); break; + case '&': r = write(w, "&", 5, file); break; + case '"': r = write(w, """, 6, file); break; + } + i++; + } + } + } + return r; +} + +static int enter(void *closure, const char *name) +{ + struct wrap *w = closure; + enum sel s = sel(w, name); + return s == S_none ? 0 : w->itf->enter(w->closure, s & S_objiter); +} + +static int next(void *closure) +{ + struct wrap *w = closure; + return w->itf->next(w->closure); +} + +static int leave(void *closure) +{ + struct wrap *w = closure; + return w->itf->leave(w->closure); +} + +static int getoptional(struct wrap *w, const char *name, struct mustach_sbuf *sbuf) +{ + enum sel s = sel(w, name); + if (!(s & S_ok)) + return 0; + return w->itf->get(w->closure, sbuf, s & S_objiter); +} + +static int get(void *closure, const char *name, struct mustach_sbuf *sbuf) +{ + struct wrap *w = closure; + if (getoptional(w, name, sbuf) <= 0) { + if (w->flags & Mustach_With_ErrorUndefined) + return MUSTACH_ERROR_UNDEFINED_TAG; + sbuf->value = ""; + } + return MUSTACH_OK; +} + +static int get_partial_from_file(const char *name, struct mustach_sbuf *sbuf) +{ + static char extension[] = INCLUDE_PARTIAL_EXTENSION; + size_t s; + long pos; + FILE *file; + char *path, *buffer; + + /* allocate path */ + s = strlen(name); + path = malloc(s + sizeof extension); + if (path == NULL) + return MUSTACH_ERROR_SYSTEM; + + /* try without extension first */ + memcpy(path, name, s + 1); + file = fopen(path, "r"); + if (file == NULL) { + memcpy(&path[s], extension, sizeof extension); + file = fopen(path, "r"); + } + free(path); + + /* if file opened */ + if (file == NULL) + return MUSTACH_ERROR_PARTIAL_NOT_FOUND; + + /* compute file size */ + if (fseek(file, 0, SEEK_END) >= 0 + && (pos = ftell(file)) >= 0 + && fseek(file, 0, SEEK_SET) >= 0) { + /* allocate value */ + s = (size_t)pos; + buffer = malloc(s + 1); + if (buffer != NULL) { + /* read value */ + if (1 == fread(buffer, s, 1, file)) { + /* force zero at end */ + sbuf->value = buffer; + buffer[s] = 0; + sbuf->freecb = free; + fclose(file); + return MUSTACH_OK; + } + free(buffer); + } + } + fclose(file); + return MUSTACH_ERROR_SYSTEM; +} + +static int partial(void *closure, const char *name, struct mustach_sbuf *sbuf) +{ + struct wrap *w = closure; + int rc; + if (mustach_wrap_get_partial != NULL) + rc = mustach_wrap_get_partial(name, sbuf); + else if (w->flags & Mustach_With_PartialDataFirst) { + if (getoptional(w, name, sbuf) > 0) + rc = MUSTACH_OK; + else + rc = get_partial_from_file(name, sbuf); + } + else { + rc = get_partial_from_file(name, sbuf); + if (rc != MUSTACH_OK && getoptional(w, name, sbuf) > 0) + rc = MUSTACH_OK; + } + if (rc != MUSTACH_OK) + sbuf->value = ""; + return MUSTACH_OK; +} + +const struct mustach_itf mustach_wrap_itf = { + .start = start, + .put = NULL, + .enter = enter, + .next = next, + .leave = leave, + .partial = partial, + .get = get, + .emit = emit, + .stop = stop +}; + +static void wrap_init(struct wrap *wrap, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, mustach_write_cb_t *writecb) +{ + if (flags & Mustach_With_Compare) + flags |= Mustach_With_Equal; + wrap->closure = closure; + wrap->itf = itf; + wrap->flags = flags; + wrap->emitcb = emitcb; + wrap->writecb = writecb; +} + +int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file) +{ + struct wrap w; + wrap_init(&w, itf, closure, flags, NULL, NULL); + return mustach_file(template, length, &mustach_wrap_itf, &w, flags, file); +} + +int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd) +{ + struct wrap w; + wrap_init(&w, itf, closure, flags, NULL, NULL); + return mustach_fd(template, length, &mustach_wrap_itf, &w, flags, fd); +} + +int mustach_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size) +{ + struct wrap w; + wrap_init(&w, itf, closure, flags, NULL, NULL); + return mustach_mem(template, length, &mustach_wrap_itf, &w, flags, result, size); +} + +int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure) +{ + struct wrap w; + wrap_init(&w, itf, closure, flags, NULL, writecb); + return mustach_file(template, length, &mustach_wrap_itf, &w, flags, writeclosure); +} + +int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure) +{ + struct wrap w; + wrap_init(&w, itf, closure, flags, emitcb, NULL); + return mustach_file(template, length, &mustach_wrap_itf, &w, flags, emitclosure); +} + diff --git a/src/templating/mustach-wrap.h b/src/templating/mustach-wrap.h new file mode 100644 index 000000000..37e6ff6cf --- /dev/null +++ b/src/templating/mustach-wrap.h @@ -0,0 +1,234 @@ +/* + Author: José Bollo + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _mustach_wrap_h_included_ +#define _mustach_wrap_h_included_ + +/* + * mustach-wrap is intended to make integration of JSON + * libraries easier by wrapping mustach extensions in a + * single place. + * + * As before, using mustach and only mustach is possible + * (by using only mustach.h) but does not implement high + * level features coming with extensions implemented by + * this high level wrapper. + */ +#include "mustach.h" +/* + * Definition of the writing callbacks for mustach functions + * producing output to callbacks. + * + * Two callback types are defined: + * + * @mustach_write_cb_t: + * + * callback receiving the escaped data to be written as 3 parameters: + * + * 1. the 'closure', the same given to the wmustach_... function + * 2. a pointer to a 'buffer' containing the characters to be written + * 3. the size in bytes of the data pointed by 'buffer' + * + * @mustach_emit_cb_t: + * + * callback receiving the data to be written and a flag indicating + * if escaping should be done or not as 4 parameters: + * + * 1. the 'closure', the same given to the emustach_... function + * 2. a pointer to a 'buffer' containing the characters to be written + * 3. the size in bytes of the data pointed by 'buffer' + * 4. a boolean indicating if 'escape' should be done + */ +#ifndef _mustach_output_callbacks_defined_ +#define _mustach_output_callbacks_defined_ +typedef int mustach_write_cb_t(void *closure, const char *buffer, size_t size); +typedef int mustach_emit_cb_t(void *closure, const char *buffer, size_t size, int escape); +#endif + +/** + * Flags specific to mustach wrap + */ +#define Mustach_With_SingleDot 4 /* obsolete, always set */ +#define Mustach_With_Equal 8 +#define Mustach_With_Compare 16 +#define Mustach_With_JsonPointer 32 +#define Mustach_With_ObjectIter 64 +#define Mustach_With_IncPartial 128 /* obsolete, always set */ +#define Mustach_With_EscFirstCmp 256 +#define Mustach_With_PartialDataFirst 512 +#define Mustach_With_ErrorUndefined 1024 + +#undef Mustach_With_AllExtensions +#define Mustach_With_AllExtensions 1023 /* don't include ErrorUndefined */ + +/** + * mustach_wrap_itf - high level wrap of mustach - interface for callbacks + * + * The functions sel, subsel, enter and next should return 0 or 1. + * + * All other functions should normally return MUSTACH_OK (zero). + * + * If any function returns a negative value, it means an error that + * stop the processing and that is reported to the caller. Mustach + * also has its own error codes. Using the macros MUSTACH_ERROR_USER + * and MUSTACH_IS_ERROR_USER could help to avoid clashes. + * + * @start: If defined (can be NULL), starts the mustach processing + * of the closure, called at the very beginning before any + * mustach processing occurs. + * + * @stop: If defined (can be NULL), stops the mustach processing + * of the closure, called at the very end after all mustach + * processing occurered. The status returned by the processing + * is passed to the stop. + * + * @compare: If defined (can be NULL), compares the value of the + * currently selected item with the given value and returns + * a negative value if current value is lesser, a positive + * value if the current value is greater or zero when + * values are equals. + * If 'compare' is NULL, any comparison in mustach + * is going to fails. + * + * @sel: Selects the item of the given 'name'. If 'name' is NULL + * Selects the current item. Returns 1 if the selection is + * effective or else 0 if the selection failed. + * + * @subsel: Selects from the currently selected object the value of + * the field of given name. Returns 1 if the selection is + * effective or else 0 if the selection failed. + * + * @enter: Enters the section of 'name' if possible. + * Musts return 1 if entered or 0 if not entered. + * When 1 is returned, the function 'leave' will always be called. + * Conversely 'leave' is never called when enter returns 0 or + * a negative value. + * When 1 is returned, the function must activate the first + * item of the section. + * + * @next: Activates the next item of the section if it exists. + * Musts return 1 when the next item is activated. + * Musts return 0 when there is no item to activate. + * + * @leave: Leaves the last entered section + * + * @get: Returns in 'sbuf' the value of the current selection if 'key' + * is zero. Otherwise, when 'key' is not zero, return in 'sbuf' + * the name of key of the current selection, or if no such key + * exists, the empty string. Must return 1 if possible or + * 0 when not possible or an error code. + */ +struct mustach_wrap_itf { + int (*start)(void *closure); + void (*stop)(void *closure, int status); + int (*compare)(void *closure, const char *value); + int (*sel)(void *closure, const char *name); + int (*subsel)(void *closure, const char *name); + int (*enter)(void *closure, int objiter); + int (*next)(void *closure); + int (*leave)(void *closure); + int (*get)(void *closure, struct mustach_sbuf *sbuf, int key); +}; + +/** + * Mustach interface used internally by mustach wrapper functions. + * Can be used for overriding behaviour. + */ +extern const struct mustach_itf mustach_wrap_itf; + +/** + * Global hook for providing partials. When set to a not NULL value, the pointed + * function replaces the default behaviour and is called to provide the partial + * of the given 'name' in 'sbuf'. + * The function must return MUSTACH_OK when it filled 'sbuf' with value of partial + * or must return an error code if it failed. + */ +extern int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf); + +/** + * mustach_wrap_file - Renders the mustache 'template' in 'file' for an abstract + * wrapper of interface 'itf' and 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @itf: the interface of the abstract wrapper + * @closure: the closure of the abstract wrapper + * @file: the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file); + +/** + * mustach_wrap_fd - Renders the mustache 'template' in 'fd' for an abstract + * wrapper of interface 'itf' and 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @itf: the interface of the abstract wrapper + * @closure: the closure of the abstract wrapper + * @fd: the file descriptor number where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd); + +/** + * mustach_wrap_mem - Renders the mustache 'template' in 'result' for an abstract + * wrapper of interface 'itf' and 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @itf: the interface of the abstract wrapper + * @closure: the closure of the abstract wrapper + * @result: the pointer receiving the result when 0 is returned + * @size: the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size); + +/** + * mustach_wrap_write - Renders the mustache 'template' for an abstract + * wrapper of interface 'itf' and 'closure' to custom writer + * 'writecb' with 'writeclosure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @itf: the interface of the abstract wrapper + * @closure: the closure of the abstract wrapper + * @writecb: the function that write values + * @closure: the closure for the write function + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure); + +/** + * mustach_wrap_emit - Renders the mustache 'template' for an abstract + * wrapper of interface 'itf' and 'closure' to custom emiter 'emitcb' + * with 'emitclosure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @itf: the interface of the abstract wrapper + * @closure: the closure of the abstract wrapper + * @emitcb: the function that emit values + * @closure: the closure for the write function + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure); + +#endif + diff --git a/src/templating/mustach.c b/src/templating/mustach.c index caa80dcc9..548c38224 100644 --- a/src/templating/mustach.c +++ b/src/templating/mustach.c @@ -1,20 +1,9 @@ /* Author: José Bollo - Author: José Bollo https://gitlab.com/jobol/mustach - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + SPDX-License-Identifier: ISC */ #define _GNU_SOURCE @@ -27,19 +16,9 @@ #ifdef _WIN32 #include #endif -#ifdef __sun -# include -#endif #include "mustach.h" -#if defined(NO_EXTENSION_FOR_MUSTACH) -# undef NO_COLON_EXTENSION_FOR_MUSTACH -# define NO_COLON_EXTENSION_FOR_MUSTACH -# undef NO_ALLOW_EMPTY_TAG -# define NO_ALLOW_EMPTY_TAG -#endif - struct iwrap { int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file); void *closure; /* closure for: enter, next, leave, emit, get */ @@ -51,6 +30,13 @@ struct iwrap { int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf); int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf); void *closure_partial; /* closure for partial */ + int flags; +}; + +struct prefix { + size_t len; + const char *start; + struct prefix *prefix; }; #if !defined(NO_OPEN_MEMSTREAM) @@ -135,6 +121,7 @@ static inline void sbuf_reset(struct mustach_sbuf *sbuf) sbuf->value = NULL; sbuf->freecb = NULL; sbuf->closure = NULL; + sbuf->length = 0; } static inline void sbuf_release(struct mustach_sbuf *sbuf) @@ -143,38 +130,47 @@ static inline void sbuf_release(struct mustach_sbuf *sbuf) sbuf->releasecb(sbuf->value, sbuf->closure); } +static inline size_t sbuf_length(struct mustach_sbuf *sbuf) +{ + size_t length = sbuf->length; + if (length == 0 && sbuf->value != NULL) + length = strlen(sbuf->value); + return length; +} + static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file) { - size_t i, j; + size_t i, j, r; (void)closure; /* unused */ if (!escape) - return fwrite(buffer, size, 1, file) != 1 ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK; + return fwrite(buffer, 1, size, file) != size ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK; - i = 0; + r = i = 0; while (i < size) { j = i; - while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&') + while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&' && buffer[j] != '"') j++; if (j != i && fwrite(&buffer[i], j - i, 1, file) != 1) return MUSTACH_ERROR_SYSTEM; if (j < size) { switch(buffer[j++]) { case '<': - if (fwrite("<", 4, 1, file) != 1) - return MUSTACH_ERROR_SYSTEM; + r = fwrite("<", 4, 1, file); break; case '>': - if (fwrite(">", 4, 1, file) != 1) - return MUSTACH_ERROR_SYSTEM; + r = fwrite(">", 4, 1, file); break; case '&': - if (fwrite("&", 5, 1, file) != 1) - return MUSTACH_ERROR_SYSTEM; + r = fwrite("&", 5, 1, file); + break; + case '"': + r = fwrite(""", 6, 1, file); break; - default: break; } + if (r != 1) + return MUSTACH_ERROR_SYSTEM; } i = j; } @@ -191,7 +187,7 @@ static int iwrap_put(void *closure, const char *name, int escape, FILE *file) sbuf_reset(&sbuf); rc = iwrap->get(iwrap->closure, name, &sbuf); if (rc >= 0) { - length = strlen(sbuf.value); + length = sbuf_length(&sbuf); if (length) rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file); sbuf_release(&sbuf); @@ -220,55 +216,109 @@ static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *s if (rc == 0) { sbuf->value = result; sbuf->freecb = free; + sbuf->length = size; } } } return rc; } -static int process(const char *template, struct iwrap *iwrap, FILE *file, const char *opstr, const char *clstr) +static int emitprefix(struct iwrap *iwrap, FILE *file, struct prefix *prefix) +{ + if (prefix->prefix) { + int rc = emitprefix(iwrap, file, prefix->prefix); + if (rc < 0) + return rc; + } + return prefix->len ? iwrap->emit(iwrap->closure, prefix->start, prefix->len, 0, file) : 0; +} + +static int process(const char *template, size_t length, struct iwrap *iwrap, FILE *file, struct prefix *prefix) { struct mustach_sbuf sbuf; - char name[MUSTACH_MAX_LENGTH + 1], c, *tmp; - const char *beg, *term; - struct { const char *name, *again; size_t length; int enabled, entered; } stack[MUSTACH_MAX_DEPTH]; + char opstr[MUSTACH_MAX_DELIM_LENGTH], clstr[MUSTACH_MAX_DELIM_LENGTH]; + char name[MUSTACH_MAX_LENGTH + 1], c; + const char *beg, *term, *end; + struct { const char *name, *again; size_t length; unsigned enabled: 1, entered: 1; } stack[MUSTACH_MAX_DEPTH]; size_t oplen, cllen, len, l; - int depth, rc, enabled; + int depth, rc, enabled, stdalone; + struct prefix pref; - enabled = 1; - oplen = strlen(opstr); - cllen = strlen(clstr); - depth = 0; - for(;;) { - beg = strstr(template, opstr); - if (beg == NULL) { - /* no more mustach */ - if (enabled && template[0]) { - rc = iwrap->emit(iwrap->closure, template, strlen(template), 0, file); - if (rc < 0) - return rc; + pref.prefix = prefix; + end = template + (length ? length : strlen(template)); + opstr[0] = opstr[1] = '{'; + clstr[0] = clstr[1] = '}'; + oplen = cllen = 2; + stdalone = enabled = 1; + depth = pref.len = 0; + for (;;) { + /* search next openning delimiter */ + for (beg = template ; ; beg++) { + c = beg == end ? '\n' : *beg; + if (c == '\n') { + l = (beg != end) + (size_t)(beg - template); + if (stdalone != 2 && enabled) { + if (beg != template /* don't prefix empty lines */) { + rc = emitprefix(iwrap, file, &pref); + if (rc < 0) + return rc; + } + rc = iwrap->emit(iwrap->closure, template, l, 0, file); + if (rc < 0) + return rc; + } + if (beg == end) /* no more mustach */ + return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK; + template += l; + stdalone = 1; + pref.len = 0; + } + else if (!isspace(c)) { + if (stdalone == 2 && enabled) { + rc = emitprefix(iwrap, file, &pref); + if (rc < 0) + return rc; + pref.len = 0; + stdalone = 0; + } + if (c == *opstr && end - beg >= (ssize_t)oplen) { + for (l = 1 ; l < oplen && beg[l] == opstr[l] ; l++); + if (l == oplen) + break; + } + stdalone = 0; } - return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK; - } - if (enabled && beg != template) { - rc = iwrap->emit(iwrap->closure, template, (size_t)(beg - template), 0, file); - if (rc < 0) - return rc; } + + pref.start = template; + pref.len = enabled ? (size_t)(beg - template) : 0; beg += oplen; - term = strstr(beg, clstr); - if (term == NULL) - return MUSTACH_ERROR_UNEXPECTED_END; + + /* search next closing delimiter */ + for (term = beg ; ; term++) { + if (term == end) + return MUSTACH_ERROR_UNEXPECTED_END; + if (*term == *clstr && end - term >= (ssize_t)cllen) { + for (l = 1 ; l < cllen && term[l] == clstr[l] ; l++); + if (l == cllen) + break; + } + } template = term + cllen; len = (size_t)(term - beg); c = *beg; switch(c) { + case ':': + stdalone = 0; + if (iwrap->flags & Mustach_With_Colon) + goto exclude_first; + goto get_name; case '!': case '=': break; case '{': - for (l = 0 ; clstr[l] == '}' ; l++); - if (clstr[l]) { + for (l = 0 ; l < cllen && clstr[l] == '}' ; l++); + if (l < cllen) { if (!len || beg[len-1] != '}') return MUSTACH_ERROR_BAD_UNESCAPE_TAG; len--; @@ -279,55 +329,63 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const } c = '&'; /*@fallthrough@*/ + case '&': + stdalone = 0; + /*@fallthrough@*/ case '^': case '#': case '/': - case '&': case '>': -#if !defined(NO_COLON_EXTENSION_FOR_MUSTACH) - case ':': -#endif - beg++; len--; +exclude_first: + beg++; + len--; + goto get_name; default: + stdalone = 0; +get_name: while (len && isspace(beg[0])) { beg++; len--; } while (len && isspace(beg[len-1])) len--; -#if !defined(NO_ALLOW_EMPTY_TAG) - if (len == 0) + if (len == 0 && !(iwrap->flags & Mustach_With_EmptyTag)) return MUSTACH_ERROR_EMPTY_TAG; -#endif if (len > MUSTACH_MAX_LENGTH) return MUSTACH_ERROR_TAG_TOO_LONG; memcpy(name, beg, len); name[len] = 0; break; } + if (stdalone) + stdalone = 2; + else if (enabled) { + rc = emitprefix(iwrap, file, &pref); + if (rc < 0) + return rc; + pref.len = 0; + } switch(c) { case '!': /* comment */ /* nothing to do */ break; case '=': - /* defines separators */ + /* defines delimiters */ if (len < 5 || beg[len - 1] != '=') return MUSTACH_ERROR_BAD_SEPARATORS; beg++; len -= 2; + while (len && isspace(*beg)) + beg++, len--; + while (len && isspace(beg[len - 1])) + len--; for (l = 0; l < len && !isspace(beg[l]) ; l++); - if (l == len) + if (l == len || l > MUSTACH_MAX_DELIM_LENGTH) return MUSTACH_ERROR_BAD_SEPARATORS; oplen = l; - tmp = alloca(oplen + 1); - memcpy(tmp, beg, oplen); - tmp[oplen] = 0; - opstr = tmp; + memcpy(opstr, beg, l); while (l < len && isspace(beg[l])) l++; - if (l == len) + if (l == len || len - l > MUSTACH_MAX_DELIM_LENGTH) return MUSTACH_ERROR_BAD_SEPARATORS; cllen = len - l; - tmp = alloca(cllen + 1); - memcpy(tmp, beg + l, cllen); - tmp[cllen] = 0; - clstr = tmp; + memcpy(clstr, beg + l, cllen); break; case '^': case '#': @@ -343,8 +401,8 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const stack[depth].name = beg; stack[depth].again = template; stack[depth].length = len; - stack[depth].enabled = enabled; - stack[depth].entered = rc; + stack[depth].enabled = enabled != 0; + stack[depth].entered = rc != 0; if ((c == '#') == (rc == 0)) enabled = 0; depth++; @@ -370,7 +428,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const sbuf_reset(&sbuf); rc = iwrap->partial(iwrap->closure_partial, name, &sbuf); if (rc >= 0) { - rc = process(sbuf.value, iwrap, file, opstr, clstr); + rc = process(sbuf.value, sbuf_length(&sbuf), iwrap, file, &pref); sbuf_release(&sbuf); } if (rc < 0) @@ -389,7 +447,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const } } -int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file) +int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file) { int rc; struct iwrap iwrap; @@ -422,17 +480,18 @@ int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE iwrap.next = itf->next; iwrap.leave = itf->leave; iwrap.get = itf->get; + iwrap.flags = flags; /* process */ rc = itf->start ? itf->start(closure) : 0; if (rc == 0) - rc = process(template, &iwrap, file, "{{", "}}"); + rc = process(template, length, &iwrap, file, 0); if (itf->stop) itf->stop(closure, rc); return rc; } -int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd) +int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd) { int rc; FILE *file; @@ -442,13 +501,13 @@ int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int rc = MUSTACH_ERROR_SYSTEM; errno = ENOMEM; } else { - rc = fmustach(template, itf, closure, file); + rc = mustach_file(template, length, itf, closure, flags, file); fclose(file); } return rc; } -int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size) +int mustach_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, char **result, size_t *size) { int rc; FILE *file; @@ -461,7 +520,7 @@ int mustach(const char *template, struct mustach_itf *itf, void *closure, char * if (file == NULL) rc = MUSTACH_ERROR_SYSTEM; else { - rc = fmustach(template, itf, closure, file); + rc = mustach_file(template, length, itf, closure, flags, file); if (rc < 0) memfile_abort(file, result, size); else @@ -470,3 +529,18 @@ int mustach(const char *template, struct mustach_itf *itf, void *closure, char * return rc; } +int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file) +{ + return mustach_file(template, 0, itf, closure, Mustach_With_AllExtensions, file); +} + +int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd) +{ + return mustach_fd(template, 0, itf, closure, Mustach_With_AllExtensions, fd); +} + +int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size) +{ + return mustach_mem(template, 0, itf, closure, Mustach_With_AllExtensions, result, size); +} + diff --git a/src/templating/mustach.h b/src/templating/mustach.h index ad952275c..8c4a43f10 100644 --- a/src/templating/mustach.h +++ b/src/templating/mustach.h @@ -1,20 +1,9 @@ /* Author: José Bollo - Author: José Bollo https://gitlab.com/jobol/mustach - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + SPDX-License-Identifier: ISC */ #ifndef _mustach_h_included_ @@ -25,7 +14,7 @@ struct mustach_sbuf; /* see below */ /** * Current version of mustach and its derivates */ -#define MUSTACH_VERSION 99 +#define MUSTACH_VERSION 102 #define MUSTACH_VERSION_MAJOR (MUSTACH_VERSION / 100) #define MUSTACH_VERSION_MINOR (MUSTACH_VERSION % 100) @@ -37,20 +26,59 @@ struct mustach_sbuf; /* see below */ /** * Maximum length of tags in mustaches {{...}} */ -#define MUSTACH_MAX_LENGTH 1024 +#define MUSTACH_MAX_LENGTH 4096 /** - * mustach_itf - interface for callbacks + * Maximum length of delimitors (2 normally but extended here) + */ +#define MUSTACH_MAX_DELIM_LENGTH 8 + +/** + * Flags specific to mustach core + */ +#define Mustach_With_NoExtensions 0 +#define Mustach_With_Colon 1 +#define Mustach_With_EmptyTag 2 +#define Mustach_With_AllExtensions 3 + +/* + * Definition of error codes returned by mustach + */ +#define MUSTACH_OK 0 +#define MUSTACH_ERROR_SYSTEM -1 +#define MUSTACH_ERROR_UNEXPECTED_END -2 +#define MUSTACH_ERROR_EMPTY_TAG -3 +#define MUSTACH_ERROR_TAG_TOO_LONG -4 +#define MUSTACH_ERROR_BAD_SEPARATORS -5 +#define MUSTACH_ERROR_TOO_DEEP -6 +#define MUSTACH_ERROR_CLOSING -7 +#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8 +#define MUSTACH_ERROR_INVALID_ITF -9 +#define MUSTACH_ERROR_ITEM_NOT_FOUND -10 +#define MUSTACH_ERROR_PARTIAL_NOT_FOUND -11 +#define MUSTACH_ERROR_UNDEFINED_TAG -12 + +/* + * You can use definition below for user specific error * - * All of this function should return a negative value to stop - * the mustache processing. The returned negative value will be - * then returned to the caller of mustach as is. + * The macro MUSTACH_ERROR_USER is involutive so for any value + * value = MUSTACH_ERROR_USER(MUSTACH_ERROR_USER(value)) + */ +#define MUSTACH_ERROR_USER_BASE -100 +#define MUSTACH_ERROR_USER(x) (MUSTACH_ERROR_USER_BASE-(x)) +#define MUSTACH_IS_ERROR_USER(x) (MUSTACH_ERROR_USER(x) >= 0) + +/** + * mustach_itf - pure abstract mustach - interface for callbacks * * The functions enter and next should return 0 or 1. * * All other functions should normally return MUSTACH_OK (zero). - * If it returns a negative value, it means an error that stop - * the process and that is reported to the caller. + * + * If any function returns a negative value, it means an error that + * stop the processing and that is reported to the caller. Mustach + * also has its own error codes. Using the macros MUSTACH_ERROR_USER + * and MUSTACH_IS_ERROR_USER could help to avoid clashes. * * @start: If defined (can be NULL), starts the mustach processing * of the closure, called at the very beginning before any @@ -92,18 +120,18 @@ struct mustach_sbuf; /* see below */ * the meaning of 'FILE *file' is abstract for mustach's process and * then you can use 'FILE*file' pass any kind of pointer (including NULL) * to the function 'fmustach'. An example of a such behaviour is given by - * the implementation of 'umustach_json_c'. + * the implementation of 'mustach_json_c_write'. * * @get: If defined (can be NULL), returns in 'sbuf' the value of 'name'. * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be * the empty string. In that later case an implementation can * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names. - * If NULL and 'put' NULL the error MUSTACH_ERROR_INVALID_ITF + * If 'get' is NULL and 'put' NULL the error MUSTACH_ERROR_INVALID_ITF * is returned. * * @stop: If defined (can be NULL), stops the mustach processing * of the closure, called at the very end after all mustach - * processing occurerd. The status returned by the processing + * processing occurered. The status returned by the processing * is passed to the stop. * * The array below summarize status of callbacks: @@ -127,7 +155,7 @@ struct mustach_sbuf; /* see below */ * * The DUCK case runs on one leg. 'get' is not used if 'partial' is defined * but is used for 'partial' if 'partial' is NULL. Thus for clarity, do not use - * it that way but define 'partial' and let 'get' NULL. + * it that way but define 'partial' and let 'get' be NULL. * * The DANGEROUS case is special: it allows abstract FILE if 'partial' is defined * but forbids abstract FILE when 'partial' is NULL. @@ -167,6 +195,9 @@ struct mustach_itf { * Can be NULL. * * @closure: The closure to use for 'releasecb'. + * + * @length: Length of the value or zero if unknown and value null terminated. + * To return the empty string, let it to zero and let value to NULL. */ struct mustach_sbuf { const char *value; @@ -175,45 +206,28 @@ struct mustach_sbuf { void (*releasecb)(const char *value, void *closure); }; void *closure; + size_t length; }; -/* - * Definition of error codes returned by mustach - */ -#define MUSTACH_OK 0 -#define MUSTACH_ERROR_SYSTEM -1 -#define MUSTACH_ERROR_UNEXPECTED_END -2 -#define MUSTACH_ERROR_EMPTY_TAG -3 -#define MUSTACH_ERROR_TAG_TOO_LONG -4 -#define MUSTACH_ERROR_BAD_SEPARATORS -5 -#define MUSTACH_ERROR_TOO_DEEP -6 -#define MUSTACH_ERROR_CLOSING -7 -#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8 -#define MUSTACH_ERROR_INVALID_ITF -9 -#define MUSTACH_ERROR_ITEM_NOT_FOUND -10 -#define MUSTACH_ERROR_PARTIAL_NOT_FOUND -11 - -/* You can use definition below for user specific error */ -#define MUSTACH_ERROR_USER_BASE -100 -#define MUSTACH_ERROR_USER(x) (MUSTACH_ERROR_USER_BASE-(x)) - /** - * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. + * mustach_file - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @itf: the interface to the functions that mustach calls * @closure: the closure to pass to functions called - * \@file: the file where to write the result + * @file: the file where to write the result * * Returns 0 in case of success, -1 with errno set in case of system error * a other negative value in case of error. */ -extern int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file); +extern int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file); /** - * fmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. + * mustach_fd - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @itf: the interface to the functions that mustach calls * @closure: the closure to pass to functions called * @fd: the file descriptor number where to write the result @@ -221,12 +235,13 @@ extern int fmustach(const char *template, struct mustach_itf *itf, void *closure * Returns 0 in case of success, -1 with errno set in case of system error * a other negative value in case of error. */ -extern int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd); +extern int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd); /** - * fmustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. + * mustach_mem - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @itf: the interface to the functions that mustach calls * @closure: the closure to pass to functions called * @result: the pointer receiving the result when 0 is returned @@ -235,7 +250,64 @@ extern int fdmustach(const char *template, struct mustach_itf *itf, void *closur * Returns 0 in case of success, -1 with errno set in case of system error * a other negative value in case of error. */ -extern int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size); +extern int mustach_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, char **result, size_t *size); + +/*************************************************************************** +* compatibility with version before 1.0 +*/ +#ifdef __GNUC__ +#define DEPRECATED_MUSTACH(func) func __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define DEPRECATED_MUSTACH(func) __declspec(deprecated) func +#elif !defined(DEPRECATED_MUSTACH) +#pragma message("WARNING: You need to implement DEPRECATED_MUSTACH for this compiler") +#define DEPRECATED_MUSTACH(func) func +#endif +/** + * OBSOLETE use mustach_file + * + * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. + * + * @template: the template string to instantiate, null terminated + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @file: the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +DEPRECATED_MUSTACH(extern int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file)); + +/** + * OBSOLETE use mustach_fd + * + * fdmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. + * + * @template: the template string to instantiate, null terminated + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @fd: the file descriptor number where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +DEPRECATED_MUSTACH(extern int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd)); + +/** + * OBSOLETE use mustach_mem + * + * mustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. + * + * @template: the template string to instantiate, null terminated + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @result: the pointer receiving the result when 0 is returned + * @size: the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +DEPRECATED_MUSTACH(extern int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size)); #endif diff --git a/src/templating/templating_api.c b/src/templating/templating_api.c index 4bd7c5fe7..9261bde79 100644 --- a/src/templating/templating_api.c +++ b/src/templating/templating_api.c @@ -180,10 +180,12 @@ TALER_TEMPLATING_fill (const char *tmpl, int eno; if (0 != - (eno = mustach_jansson (tmpl, - (json_t *) root, - (char **) result, - result_size))) + (eno = mustach_jansson_mem (tmpl, + 0, /* length of tmpl */ + (json_t *) root, + Mustach_With_NoExtensions, + (char **) result, + result_size))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "mustach failed on template with error %d\n", @@ -237,10 +239,12 @@ TALER_TEMPLATING_build (struct MHD_Connection *connection, GNUNET_free (static_url); } if (0 != - (eno = mustach_jansson (tmpl, - (json_t *) root, - &body, - &body_size))) + (eno = mustach_jansson_mem (tmpl, + 0, + (json_t *) root, + Mustach_With_NoExtensions, + &body, + &body_size))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "mustach failed on template `%s' with error %d\n", From 7899bc5621d7f5040e2938b034705a7e3569f31d Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 11 May 2023 01:18:24 +0200 Subject: [PATCH 51/53] externalize persona JSON conversion logic, expand with file download --- src/kyclogic/Makefile.am | 6 +- src/kyclogic/kyclogic-persona.conf | 4 + src/kyclogic/plugin_kyclogic_persona.c | 374 ++++++++---------- .../taler-exchange-kyc-persona-converter.sh | 54 +++ src/util/.gitignore | 1 + 5 files changed, 235 insertions(+), 204 deletions(-) create mode 100755 src/kyclogic/taler-exchange-kyc-persona-converter.sh diff --git a/src/kyclogic/Makefile.am b/src/kyclogic/Makefile.am index 858331f39..20430a4ea 100644 --- a/src/kyclogic/Makefile.am +++ b/src/kyclogic/Makefile.am @@ -16,7 +16,11 @@ pkgcfg_DATA = \ EXTRA_DIST = \ $(pkgcfg_DATA) \ - sample.conf + sample.conf \ + persona-sample-reply.json + +bin_SCRIPTS = \ + taler-exchange-kyc-persona-converter.sh lib_LTLIBRARIES = \ libtalerkyclogic.la diff --git a/src/kyclogic/kyclogic-persona.conf b/src/kyclogic/kyclogic-persona.conf index 7f02bf498..2d52a9ee0 100644 --- a/src/kyclogic/kyclogic-persona.conf +++ b/src/kyclogic/kyclogic-persona.conf @@ -29,6 +29,10 @@ KYC_PERSONA_SUBDOMAIN = taler # Authentication token to use. KYC_PERSONA_AUTH_TOKEN = persona_sandbox_42 +# Program that converts Persona KYC data into the +# GNU Taler format. +KYC_PERSONA_CONVERTER_HELPER = taler-exchange-kyc-persona-converter.sh + # Form to use. KYC_PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx diff --git a/src/kyclogic/plugin_kyclogic_persona.c b/src/kyclogic/plugin_kyclogic_persona.c index 4f01ae40e..35ab9ded7 100644 --- a/src/kyclogic/plugin_kyclogic_persona.c +++ b/src/kyclogic/plugin_kyclogic_persona.c @@ -111,6 +111,12 @@ struct TALER_KYCLOGIC_ProviderDetails */ char *subdomain; + /** + * Name of the program we use to convert outputs + * from Persona into our JSON inputs. + */ + char *conversion_binary; + /** * Where to redirect the client upon completion. */ @@ -230,6 +236,12 @@ struct TALER_KYCLOGIC_ProofHandle */ char *url; + /** + * Handle to an external process that converts the + * Persona response to our internal format. + */ + struct TALER_JSON_ExternalConversion *ec; + /** * Hash of the payto:// URI we are checking the KYC for. */ @@ -246,6 +258,11 @@ struct TALER_KYCLOGIC_ProofHandle */ char *provider_user_id; + /** + * Account ID from the service. + */ + char *account_id; + /** * Inquiry ID at the provider. */ @@ -294,6 +311,11 @@ struct TALER_KYCLOGIC_WebhookHandle */ char *inquiry_id; + /** + * Account ID from the service. + */ + char *account_id; + /** * URL of the cURL request. */ @@ -315,6 +337,12 @@ struct TALER_KYCLOGIC_WebhookHandle */ const char *template_id; + /** + * Handle to an external process that converts the + * Persona response to our internal format. + */ + struct TALER_JSON_ExternalConversion *ec; + /** * Our account ID. */ @@ -344,6 +372,7 @@ persona_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd) GNUNET_free (pd->auth_token); GNUNET_free (pd->template_id); GNUNET_free (pd->subdomain); + GNUNET_free (pd->conversion_binary); GNUNET_free (pd->salt); GNUNET_free (pd->section); GNUNET_free (pd->post_kyc_redirect_url); @@ -418,6 +447,18 @@ persona_load_configuration (void *cls, persona_unload_configuration (pd); return NULL; } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (ps->cfg, + provider_section_name, + "KYC_PERSONA_CONVERTER_HELPER", + &pd->conversion_binary)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + provider_section_name, + "KYC_PERSONA_CONVERTER_HELPER"); + persona_unload_configuration (pd); + return NULL; + } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (ps->cfg, provider_section_name, @@ -838,8 +879,14 @@ persona_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph) GNUNET_CURL_job_cancel (ph->job); ph->job = NULL; } + if (NULL != ph->ec) + { + TALER_JSON_external_conversion_stop (ph->ec); + ph->ec = NULL; + } GNUNET_free (ph->url); GNUNET_free (ph->provider_user_id); + GNUNET_free (ph->account_id); GNUNET_free (ph->inquiry_id); GNUNET_free (ph); } @@ -922,161 +969,6 @@ proof_reply_error (struct TALER_KYCLOGIC_ProofHandle *ph, } -/** - * Convert KYC attribute data from Persona response. - * - * @param attr json array with Persona attribute data - * @return KYC attribute data - */ -static json_t * -convert_attributes (const json_t *attr) -{ - const char *country_code = NULL; - const char *name_first = NULL; - const char *name_middle = NULL; - const char *name_last = NULL; - const char *address_street_1 = NULL; - const char *address_street_2 = NULL; - const char *address_city = NULL; - const char *address_postal_code = NULL; - const char *birthdate = NULL; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("country-code", - &country_code), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("name-first", - &name_first), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("name-middle", - &name_middle), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("name-last", - &name_last), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("address-street-1", - &address_street_1), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("address-street-2", - &address_street_2), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("address-city", - &address_city), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("address-postal-code", - &address_postal_code), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("birthdate", - &birthdate), - NULL), - GNUNET_JSON_spec_end () - }; - json_t *ret; - - if (GNUNET_OK != - GNUNET_JSON_parse (attr, - spec, - NULL, NULL)) - { - GNUNET_break (0); - json_dumpf (attr, - stderr, - JSON_INDENT (2)); - return NULL; - } - { - char *name = NULL; - char *street = NULL; - char *city = NULL; - - if ( (NULL != name_last) || - (NULL != name_first) || - (NULL != name_middle) ) - { - GNUNET_asprintf (&name, - "%s, %s %s", - (NULL != name_last) - ? name_last - : "", - (NULL != name_first) - ? name_first - : "", - (NULL != name_middle) - ? name_middle - : ""); - } - if ( (NULL != address_city) || - (NULL != address_postal_code) ) - { - GNUNET_asprintf (&city, - "%s%s%s %s", - (NULL != country_code) - ? country_code - : "", - (NULL != country_code) - ? "-" - : "", - (NULL != address_postal_code) - ? address_postal_code - : "", - (NULL != address_city) - ? address_city - : ""); - } - if ( (NULL != address_street_1) || - (NULL != address_street_2) ) - { - GNUNET_asprintf (&street, - "%s%s%s", - (NULL != address_street_1) - ? address_street_1 - : "", - ( (NULL != address_street_1) && - (NULL != address_street_2) ) - ? "\n" - : "", - (NULL != address_street_2) - ? address_street_2 - : ""); - } - ret = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ( - TALER_ATTRIBUTE_BIRTHDATE, - birthdate)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ( - TALER_ATTRIBUTE_FULL_NAME, - name)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ( - TALER_ATTRIBUTE_ADDRESS_STREET, - street)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ( - TALER_ATTRIBUTE_ADDRESS_CITY, - city)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ( - TALER_ATTRIBUTE_RESIDENCES, - country_code)) - ); - GNUNET_free (street); - GNUNET_free (city); - GNUNET_free (name); - } - return ret; -} - - /** * Return a response for the @a ph request indicating a * protocol violation by the Persona server. @@ -1115,6 +1007,86 @@ return_invalid_response (struct TALER_KYCLOGIC_ProofHandle *ph, } +/** + * Start the external conversion helper. + * + * @param pd configuration details + * @param attr attributes to give to the helper + * @param cb function to call with the result + * @param cb_cls closure for @a cb + * @return handle for the helper + */ +static struct TALER_JSON_ExternalConversion * +start_conversion (const struct TALER_KYCLOGIC_ProviderDetails *pd, + const json_t *attr, + TALER_JSON_JsonCallback cb, + void *cb_cls) +{ + return TALER_JSON_external_conversion_start ( + attr, + cb, + cb_cls, + pd->conversion_binary, + pd->conversion_binary, + "-a", + pd->auth_token, + NULL + ); +} + + +/** + * Type of a callback that receives a JSON @a result. + * + * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *` + * @param status_type how did the process die + * @param code termination status code from the process + * @param result some JSON result, NULL if we failed to get an JSON output + */ +static void +proof_post_conversion_cb (void *cls, + enum GNUNET_OS_ProcessStatusType status_type, + unsigned long code, + const json_t *attr) +{ + struct TALER_KYCLOGIC_ProofHandle *ph = cls; + struct MHD_Response *resp; + struct GNUNET_TIME_Absolute expiration; + + ph->ec = NULL; + if ( (NULL == attr) || + (0 != code) ) + { + GNUNET_break_op (0); + return_invalid_response (ph, + MHD_HTTP_OK, + ph->inquiry_id, + "converter", + NULL); + persona_proof_cancel (ph); + return; + } + expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity); + resp = MHD_create_response_from_buffer (0, + "", + MHD_RESPMEM_PERSISTENT); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_LOCATION, + ph->pd->post_kyc_redirect_url)); + TALER_MHD_add_global_headers (resp); + ph->cb (ph->cb_cls, + TALER_KYCLOGIC_STATUS_SUCCESS, + ph->account_id, + ph->inquiry_id, + expiration, + attr, + MHD_HTTP_SEE_OTHER, + resp); + persona_proof_cancel (ph); +} + + /** * Function called when we're done processing the * HTTP "/api/v1/inquiries/{inquiry-id}" request. @@ -1283,46 +1255,15 @@ handle_proof_finished (void *cls, data); break; } - - { - struct MHD_Response *resp; - struct GNUNET_TIME_Absolute expiration; - json_t *attr; - - attr = convert_attributes (attributes); - if (NULL == attr) - { - GNUNET_break_op (0); - return_invalid_response (ph, - response_code, - inquiry_id, - "data-relationships-account-data-id", - data); - break; - } - expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity); - resp = MHD_create_response_from_buffer (0, - "", - MHD_RESPMEM_PERSISTENT); - GNUNET_break (MHD_YES == - MHD_add_response_header (resp, - MHD_HTTP_HEADER_LOCATION, - ph->pd->post_kyc_redirect_url)); - TALER_MHD_add_global_headers (resp); - ph->cb (ph->cb_cls, - TALER_KYCLOGIC_STATUS_SUCCESS, - account_id, - inquiry_id, - expiration, - attr, - MHD_HTTP_SEE_OTHER, - resp); - json_decref (attr); - } + ph->account_id = GNUNET_strdup (account_id); + ph->ec = start_conversion (ph->pd, + j, + &proof_post_conversion_cb, + ph); GNUNET_JSON_parse_free (ispec); } GNUNET_JSON_parse_free (spec); - break; + return; /* continued in proof_post_conversion_cb */ } case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_NOT_FOUND: @@ -1580,6 +1521,12 @@ persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh) GNUNET_CURL_job_cancel (wh->job); wh->job = NULL; } + if (NULL != wh->ec) + { + TALER_JSON_external_conversion_stop (wh->ec); + wh->ec = NULL; + } + GNUNET_free (wh->account_id); GNUNET_free (wh->inquiry_id); GNUNET_free (wh->url); GNUNET_free (wh); @@ -1650,6 +1597,32 @@ webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *wh, } +/** + * Type of a callback that receives a JSON @a result. + * + * @param cls closure with a `struct TALER_KYCLOGIC_WebhookHandle *` + * @param status_type how did the process die + * @param code termination status code from the process + * @param result some JSON result, NULL if we failed to get an JSON output + */ +static void +webhook_post_conversion_cb (void *cls, + enum GNUNET_OS_ProcessStatusType status_type, + unsigned long code, + const json_t *attr) +{ + struct TALER_KYCLOGIC_WebhookHandle *wh = cls; + + wh->ec = NULL; + webhook_generic_reply (wh, + TALER_KYCLOGIC_STATUS_SUCCESS, + wh->account_id, + wh->inquiry_id, + attr, + MHD_HTTP_OK); +} + + /** * Function called when we're done processing the * HTTP "/api/v1/inquiries/{inquiry_id}" request. @@ -1723,7 +1696,6 @@ handle_webhook_finished (void *cls, NULL), GNUNET_JSON_spec_end () }; - json_t *attr; if (GNUNET_OK != GNUNET_JSON_parse (attributes, @@ -1807,19 +1779,15 @@ handle_webhook_finished (void *cls, MHD_HTTP_BAD_GATEWAY); break; } - - attr = convert_attributes (attributes); - webhook_generic_reply (wh, - TALER_KYCLOGIC_STATUS_SUCCESS, - account_id, - inquiry_id, - attr, - MHD_HTTP_OK); - json_decref (attr); + wh->account_id = GNUNET_strdup (account_id); + wh->ec = start_conversion (wh->pd, + j, + &webhook_post_conversion_cb, + wh); GNUNET_JSON_parse_free (ispec); } GNUNET_JSON_parse_free (spec); - break; + return; /* continued in webhook_post_conversion_cb */ } case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_NOT_FOUND: diff --git a/src/kyclogic/taler-exchange-kyc-persona-converter.sh b/src/kyclogic/taler-exchange-kyc-persona-converter.sh new file mode 100755 index 000000000..a5d4d03ac --- /dev/null +++ b/src/kyclogic/taler-exchange-kyc-persona-converter.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# This file is in the public domain. +# +# This code converts (some of) the JSON output from Persona into the GNU Taler +# specific KYC attribute data (again in JSON format). We may need to download +# and inline file data in the process, for authorization pass "-a" with the +# respective bearer token. +# + +# Die if anything goes wrong. +set -eu + +# Parse command-line options +while getopts ':a:' OPTION; do + case "$OPTION" in + a) + TOKEN="$OPTARG" + ;; + ?) + echo "Unrecognized command line option" + exit 1 + ;; + esac +done + + +# First, extract everything from stdin. +J=$(jq '{"first":.data.attributes."name-first","middle":.data.attributes."name-middle","last":.data.attributes."name-last","cc":.data.attributes.fields."address-country-code".value,"birthdate":.data.attributes.birthdate,"city":.data.attributes."address-city","postcode":.data.attributes."address-postal-code","street-1":.data.attributes."address-street-1","street-2":.data.attributes."address-street-2","address-subdivision":.data.attributes."address-subdivision","identification-number":.data.attributes."identification-number","photo":.included[]|select(.type=="verification/government-id")|.attributes|select(.status=="passed")|."front-photo-url"}') + + +# Next, combine some fields into larger values. +FULLNAME=$(echo "$J" | jq -r '[.first,.middle,.last]|join(" ")') +STREET=$(echo $J | jq -r '[."street-1",."street-2"]|join(" ")') +CITY=$(echo $J | jq -r '[.postcode,.city,."address-subdivision,.cc"]|join(" ")') + +# Download and base32-encode the photo +PHOTO_URL=$(echo "$J" | jq -r '.photo') +PHOTO_FILE=$(mktemp -t tmp.XXXXXXXXXX) +if [ -z "${TOKEN:-}" ] +then + wget -q --output-document=- "$PHOTO_URL" | gnunet-base32 > ${PHOTO_FILE} +else + wget -q --output-document=- --header "Authorization: Bearer $TOKEN" "$PHOTO_URL" | gnunet-base32 > ${PHOTO_FILE} +fi + +# Combine into final result. +echo "$J" | jq \ + --arg full_name "${FULLNAME}" \ + --arg street "${STREET}" \ + --arg city "${CITY}" \ + --rawfile photo "${PHOTO_FILE}" \ + '{$full_name,$street,$city,"birthdate":.birthdate,"residences":.cc,"identification_number":."identification-number",$photo}' + +exit 0 diff --git a/src/util/.gitignore b/src/util/.gitignore index c5f8c76dd..d79786ec7 100644 --- a/src/util/.gitignore +++ b/src/util/.gitignore @@ -9,3 +9,4 @@ test_helper_cs test_helper_cs_home/ test_helper_eddsa test_helper_eddsa_home/ +test_conversion From bc03a27cba050e06ee449fc29d6024be39bd7a84 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 11 May 2023 11:47:35 +0200 Subject: [PATCH 52/53] -fix doxygen --- contrib/gana | 2 +- src/kyclogic/plugin_kyclogic_persona.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/gana b/contrib/gana index e50e37672..85736484c 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit e50e37672fae7983fb5e934cd1d381b92648f7b6 +Subproject commit 85736484cb0da26aded705ebb1e944e8bb1b8504 diff --git a/src/kyclogic/plugin_kyclogic_persona.c b/src/kyclogic/plugin_kyclogic_persona.c index 35ab9ded7..e14a50390 100644 --- a/src/kyclogic/plugin_kyclogic_persona.c +++ b/src/kyclogic/plugin_kyclogic_persona.c @@ -1041,7 +1041,7 @@ start_conversion (const struct TALER_KYCLOGIC_ProviderDetails *pd, * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *` * @param status_type how did the process die * @param code termination status code from the process - * @param result some JSON result, NULL if we failed to get an JSON output + * @param attr result some JSON result, NULL if we failed to get an JSON output */ static void proof_post_conversion_cb (void *cls, @@ -1603,7 +1603,7 @@ webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *wh, * @param cls closure with a `struct TALER_KYCLOGIC_WebhookHandle *` * @param status_type how did the process die * @param code termination status code from the process - * @param result some JSON result, NULL if we failed to get an JSON output + * @param attr some JSON result, NULL if we failed to get an JSON output */ static void webhook_post_conversion_cb (void *cls, From ff1a28319fe31741958a0b1cfa761fd44878db45 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 11 May 2023 15:24:16 +0200 Subject: [PATCH 53/53] -fix FTBFS --- src/templating/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/src/templating/Makefile.am b/src/templating/Makefile.am index fcdc22cd4..6b5234dba 100644 --- a/src/templating/Makefile.am +++ b/src/templating/Makefile.am @@ -41,6 +41,7 @@ libtalertemplating_la_LDFLAGS = \ libmustach_la_SOURCES = \ mustach.c mustach.h \ + mustach-wrap.c mustach-wrap.h \ mustach-jansson.c mustach-jansson.h test_mustach_jansson_SOURCES = \