donau

Donation authority for GNU Taler (experimental)
Log | Files | Refs | Submodules | README | LICENSE

commit a9e76052a241f6e666ec02c64ab2d2456453ac55
parent bc1f2c38e6e072fe0c2fb599c8eabb4d7667f7a3
Author: Pius Loosli <loosp2@bfh.ch>
Date:   Mon, 23 Oct 2023 14:16:26 +0200

[build] copy src/{curl,json,mhd,pq,sq} from exchange to build succesfully

Diffstat:
Asrc/curl/Makefile.am | 24++++++++++++++++++++++++
Asrc/curl/curl.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/json/.gitignore | 1+
Asrc/json/Makefile.am | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/json/i18n.c | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/json/json.c | 886+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/json/json_helper.c | 1366+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/json/json_pack.c | 308+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/json/json_wire.c | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/json/test_json.c | 439+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd/Makefile.am | 29+++++++++++++++++++++++++++++
Asrc/mhd/mhd_config.c | 493+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd/mhd_legal.c | 694+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd/mhd_parsing.c | 444+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd/mhd_responses.c | 550+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd/mhd_run.c | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/pq/Makefile.am | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/pq/pq_common.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/pq/pq_common.h | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/pq/pq_query_helper.c | 1183+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/pq/pq_result_helper.c | 1424+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/pq/test_pq.c | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sq/Makefile.am | 40++++++++++++++++++++++++++++++++++++++++
Asrc/sq/sq_query_helper.c | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sq/sq_result_helper.c | 237+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sq/test_sq.c | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
26 files changed, 9574 insertions(+), 0 deletions(-)

diff --git a/src/curl/Makefile.am b/src/curl/Makefile.am @@ -0,0 +1,24 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libtalercurl.la + +libtalercurl_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined +libtalercurl_la_SOURCES = \ + curl.c +libtalercurl_la_LIBADD = \ + -lgnunetcurl \ + -lgnunetutil \ + $(LIBGNURLCURL_LIBS) \ + -ljansson \ + -lz \ + -lm \ + $(XLIB) diff --git a/src/curl/curl.c b/src/curl/curl.c @@ -0,0 +1,107 @@ +/* + This file is part of TALER + Copyright (C) 2019-2021 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 + <http://www.gnu.org/licenses/> +*/ +/** + * @file curl/curl.c + * @brief Helper routines for interactions with libcurl + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_curl_lib.h" + + +#if TALER_CURL_COMPRESS_BODIES +#include <zlib.h> +#endif + + +enum GNUNET_GenericReturnValue +TALER_curl_easy_post (struct TALER_CURL_PostContext *ctx, + CURL *eh, + const json_t *body) +{ + char *str; + size_t slen; + + str = json_dumps (body, + JSON_COMPACT); + if (NULL == str) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + slen = strlen (str); + if (TALER_CURL_COMPRESS_BODIES && + (! ctx->disable_compression) ) + { + Bytef *cbuf; + uLongf cbuf_size; + int ret; + + cbuf_size = compressBound (slen); + cbuf = GNUNET_malloc (cbuf_size); + ret = compress (cbuf, + &cbuf_size, + (const Bytef *) str, + slen); + if (Z_OK != ret) + { + /* compression failed!? */ + GNUNET_break (0); + GNUNET_free (cbuf); + return GNUNET_SYSERR; + } + free (str); + slen = (size_t) cbuf_size; + ctx->json_enc = (char *) cbuf; + GNUNET_assert ( + NULL != + (ctx->headers = curl_slist_append ( + ctx->headers, + "Content-Encoding: deflate"))); + } + else + { + ctx->json_enc = str; + } + GNUNET_assert ( + NULL != + (ctx->headers = curl_slist_append ( + ctx->headers, + "Content-Type: application/json"))); + + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + ctx->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + slen)); + return GNUNET_OK; +} + + +void +TALER_curl_easy_post_finished (struct TALER_CURL_PostContext *ctx) +{ + curl_slist_free_all (ctx->headers); + ctx->headers = NULL; + GNUNET_free (ctx->json_enc); + ctx->json_enc = NULL; +} diff --git a/src/json/.gitignore b/src/json/.gitignore @@ -0,0 +1 @@ +test_json_wire diff --git a/src/json/Makefile.am b/src/json/Makefile.am @@ -0,0 +1,43 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libtalerjson.la + +libtalerjson_la_SOURCES = \ + i18n.c \ + json.c \ + json_helper.c \ + json_pack.c \ + json_wire.c +libtalerjson_la_LDFLAGS = \ + -version-info 1:0:1 \ + -no-undefined +libtalerjson_la_LIBADD = \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetjson \ + -lgnunetutil \ + -lunistring \ + -ljansson \ + -lm \ + $(XLIB) + +TESTS = \ + test_json + +check_PROGRAMS= \ + test_json + +test_json_SOURCES = \ + test_json.c +test_json_LDADD = \ + $(top_builddir)/src/json/libtalerjson.la \ + -lgnunetjson \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil \ + -ljansson diff --git a/src/json/i18n.c b/src/json/i18n.c @@ -0,0 +1,134 @@ +/* + This file is part of TALER + Copyright (C) 2020, 2021 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file json/i18n.c + * @brief helper functions for i18n in JSON processing + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_json_lib.h" + + +const json_t * +TALER_JSON_extract_i18n (const json_t *object, + const char *language_pattern, + const char *field) +{ + const json_t *ret; + json_t *i18n; + double quality = -1; + + ret = json_object_get (object, + field); + if (NULL == ret) + return NULL; /* field MUST exist in object */ + { + char *name; + + GNUNET_asprintf (&name, + "%s_i18n", + field); + i18n = json_object_get (object, + name); + GNUNET_free (name); + } + if (NULL == i18n) + return ret; + { + const char *key; + json_t *value; + + json_object_foreach (i18n, key, value) { + double q = TALER_language_matches (language_pattern, + key); + if (q > quality) + { + quality = q; + ret = value; + } + } + } + return ret; +} + + +bool +TALER_JSON_check_i18n (const json_t *i18n) +{ + const char *field; + json_t *member; + + if (! json_is_object (i18n)) + return false; + json_object_foreach ((json_t *) i18n, field, member) + { + if (! json_is_string (member)) + return false; + /* Field name must be either of format "en_UK" + or just "en"; we do not care about capitalization; + for syntax, see GNU Gettext manual, including + appendix A for rare language codes. */ + switch (strlen (field)) + { + case 0: + case 1: + return false; + case 2: + if (! isalpha (field[0])) + return false; + if (! isalpha (field[1])) + return false; + break; + case 3: + case 4: + return false; + case 5: + if (! isalpha (field[0])) + return false; + if (! isalpha (field[1])) + return false; + if ('_' != field[2]) + return false; + if (! isalpha (field[3])) + return false; + if (! isalpha (field[4])) + return false; + break; + case 6: + if (! isalpha (field[0])) + return false; + if (! isalpha (field[1])) + return false; + if ('_' != field[2]) + return false; + if (! isalpha (field[3])) + return false; + if (! isalpha (field[4])) + return false; + if (! isalpha (field[5])) + return false; + break; + default: + return false; + } + } + return true; +} + + +/* end of i18n.c */ diff --git a/src/json/json.c b/src/json/json.c @@ -0,0 +1,886 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016, 2020, 2021 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file json/json.c + * @brief helper functions for JSON processing using libjansson + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_json_lib.h" +#include <unistr.h> + + +/** + * Check if @a json contains a 'real' value anywhere. + * + * @param json json to check + * @return true if a real is in it somewhere + */ +static bool +contains_real (const json_t *json) +{ + if (json_is_real (json)) + return true; + if (json_is_object (json)) + { + json_t *member; + const char *name; + + json_object_foreach ((json_t *) json, name, member) + if (contains_real (member)) + return true; + return false; + } + if (json_is_array (json)) + { + json_t *member; + size_t index; + + json_array_foreach ((json_t *) json, index, member) + if (contains_real (member)) + return true; + return false; + } + return false; +} + + +/** + * Dump the @a json to a string and hash it. + * + * @param json value to hash + * @param salt salt value to include when using HKDF, + * NULL to not use any salt and to use SHA512 + * @param[out] hc where to store the hash + * @return #GNUNET_OK on success, + * #GNUNET_NO if @a json was not hash-able + * #GNUNET_SYSERR on failure + */ +static enum GNUNET_GenericReturnValue +dump_and_hash (const json_t *json, + const char *salt, + struct GNUNET_HashCode *hc) +{ + char *wire_enc; + size_t len; + + if (NULL == json) + { + GNUNET_break_op (0); + return GNUNET_NO; + } + if (contains_real (json)) + { + GNUNET_break_op (0); + return GNUNET_NO; + } + if (NULL == (wire_enc = json_dumps (json, + JSON_ENCODE_ANY + | JSON_COMPACT + | JSON_SORT_KEYS))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + len = TALER_rfc8785encode (&wire_enc); + if (NULL == salt) + { + GNUNET_CRYPTO_hash (wire_enc, + len, + hc); + } + else + { + if (GNUNET_YES != + GNUNET_CRYPTO_kdf (hc, + sizeof (*hc), + salt, + strlen (salt) + 1, + wire_enc, + len, + NULL, + 0)) + { + GNUNET_break (0); + free (wire_enc); + return GNUNET_SYSERR; + } + } + free (wire_enc); + return GNUNET_OK; +} + + +/** + * Replace "forgettable" parts of a JSON object with their salted hash. + * + * @param[in] in some JSON value + * @param[out] out resulting JSON value + * @return #GNUNET_OK on success, + * #GNUNET_NO if @a json was not hash-able + * #GNUNET_SYSERR on failure + */ +static enum GNUNET_GenericReturnValue +forget (const json_t *in, + json_t **out) +{ + if (json_is_real (in)) + { + /* floating point is not allowed! */ + GNUNET_break_op (0); + return GNUNET_NO; + } + if (json_is_array (in)) + { + /* array is a JSON array */ + size_t index; + json_t *value; + json_t *ret; + + ret = json_array (); + if (NULL == ret) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + json_array_foreach (in, index, value) { + enum GNUNET_GenericReturnValue iret; + json_t *t; + + iret = forget (value, + &t); + if (GNUNET_OK != iret) + { + json_decref (ret); + return iret; + } + if (0 != json_array_append_new (ret, + t)) + { + GNUNET_break (0); + json_decref (ret); + return GNUNET_SYSERR; + } + } + *out = ret; + return GNUNET_OK; + } + if (json_is_object (in)) + { + json_t *ret; + const char *key; + json_t *value; + json_t *fg; + json_t *rx; + + fg = json_object_get (in, + "$forgettable"); + rx = json_object_get (in, + "$forgotten"); + if (NULL != rx) + { + rx = json_deep_copy (rx); /* should be shallow + by structure, but + deep copy is safer */ + if (NULL == rx) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + ret = json_object (); + if (NULL == ret) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + json_object_foreach ((json_t*) in, key, value) { + json_t *t; + json_t *salt; + enum GNUNET_GenericReturnValue iret; + + if (fg == value) + continue; /* skip! */ + if (rx == value) + continue; /* skip! */ + if ( (NULL != rx) && + (NULL != + json_object_get (rx, + key)) ) + { + (void) json_object_del (ret, + key); + continue; /* already forgotten earlier */ + } + iret = forget (value, + &t); + if (GNUNET_OK != iret) + { + json_decref (ret); + json_decref (rx); + return iret; + } + if ( (NULL != fg) && + (NULL != (salt = json_object_get (fg, + key))) ) + { + /* 't' is to be forgotten! */ + struct GNUNET_HashCode hc; + + if (! json_is_string (salt)) + { + GNUNET_break_op (0); + json_decref (ret); + json_decref (rx); + json_decref (t); + return GNUNET_NO; + } + iret = dump_and_hash (t, + json_string_value (salt), + &hc); + if (GNUNET_OK != iret) + { + json_decref (ret); + json_decref (rx); + json_decref (t); + return iret; + } + json_decref (t); + /* scrub salt */ + if (0 != + json_object_del (fg, + key)) + { + GNUNET_break_op (0); + json_decref (ret); + json_decref (rx); + return GNUNET_NO; + } + if (NULL == rx) + rx = json_object (); + if (NULL == rx) + { + GNUNET_break (0); + json_decref (ret); + return GNUNET_SYSERR; + } + if (0 != + json_object_set_new (rx, + key, + GNUNET_JSON_from_data_auto (&hc))) + { + GNUNET_break (0); + json_decref (ret); + json_decref (rx); + return GNUNET_SYSERR; + } + } + else + { + /* 't' to be used without 'forgetting' */ + if (0 != + json_object_set_new (ret, + key, + t)) + { + GNUNET_break (0); + json_decref (ret); + json_decref (rx); + return GNUNET_SYSERR; + } + } + } /* json_object_foreach */ + if ( (NULL != rx) && + (0 != + json_object_set_new (ret, + "$forgotten", + rx)) ) + { + GNUNET_break (0); + json_decref (ret); + return GNUNET_SYSERR; + } + *out = ret; + return GNUNET_OK; + } + *out = json_incref ((json_t *) in); + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_contract_hash (const json_t *json, + struct TALER_PrivateContractHashP *hc) +{ + enum GNUNET_GenericReturnValue ret; + json_t *cjson; + json_t *dc; + + dc = json_deep_copy (json); + ret = forget (dc, + &cjson); + json_decref (dc); + if (GNUNET_OK != ret) + return ret; + ret = dump_and_hash (cjson, + NULL, + &hc->hash); + json_decref (cjson); + return ret; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_contract_mark_forgettable (json_t *json, + const char *field) +{ + json_t *fg; + struct GNUNET_ShortHashCode salt; + + if (! json_is_object (json)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* check field name is legal for forgettable field */ + for (const char *f = field; '\0' != *f; f++) + { + char c = *f; + + if ( (c >= 'a') && (c <= 'z') ) + continue; + if ( (c >= 'A') && (c <= 'Z') ) + continue; + if ( (c >= '0') && (c <= '9') ) + continue; + if ('_' == c) + continue; + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (NULL == json_object_get (json, + field)) + { + /* field must exist */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + fg = json_object_get (json, + "$forgettable"); + if (NULL == fg) + { + fg = json_object (); + if (0 != + json_object_set_new (json, + "$forgettable", + fg)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &salt, + sizeof (salt)); + if (0 != + json_object_set_new (fg, + field, + GNUNET_JSON_from_data_auto (&salt))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_contract_part_forget (json_t *json, + const char *field) +{ + json_t *fg; + const json_t *part; + json_t *fp; + json_t *rx; + struct GNUNET_HashCode hc; + const char *salt; + enum GNUNET_GenericReturnValue ret; + + if (! json_is_object (json)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (NULL == (part = json_object_get (json, + field))) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Did not find field `%s' we were asked to forget\n", + field); + return GNUNET_SYSERR; + } + fg = json_object_get (json, + "$forgettable"); + if (NULL == fg) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Did not find '$forgettable' attribute trying to forget field `%s'\n", + field); + return GNUNET_SYSERR; + } + rx = json_object_get (json, + "$forgotten"); + if (NULL == rx) + { + rx = json_object (); + if (0 != + json_object_set_new (json, + "$forgotten", + rx)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + if (NULL != + json_object_get (rx, + field)) + { + if (! json_is_null (json_object_get (json, + field))) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Field `%s' market as forgotten, but still exists!\n", + field); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Already forgot field `%s'\n", + field); + return GNUNET_NO; + } + salt = json_string_value (json_object_get (fg, + field)); + if (NULL == salt) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Did not find required salt to forget field `%s'\n", + field); + return GNUNET_SYSERR; + } + + /* need to recursively forget to compute 'hc' */ + ret = forget (part, + &fp); + if (GNUNET_OK != ret) + return ret; + if (GNUNET_OK != + dump_and_hash (fp, + salt, + &hc)) + { + json_decref (fp); + GNUNET_break (0); + return GNUNET_SYSERR; + } + json_decref (fp); + /* drop salt */ + if (0 != + json_object_del (fg, + field)) + { + json_decref (fp); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* remember field as 'forgotten' */ + if (0 != + json_object_set_new (rx, + field, + GNUNET_JSON_from_data_auto (&hc))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* finally, set 'forgotten' field to null */ + if (0 != + json_object_del (json, + field)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Look over all of the values of a '$forgettable' object. Replace 'True' + * values with proper random salts. Fails if any forgettable values are + * neither 'True' nor valid salts (strings). + * + * @param[in,out] f JSON to transform + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +seed_forgettable (json_t *f) +{ + const char *key; + json_t *val; + + json_object_foreach (f, + key, + val) + { + if (json_is_string (val)) + continue; + if (json_is_true (val)) + { + struct GNUNET_ShortHashCode sh; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &sh, + sizeof (sh)); + if (0 != + json_object_set_new (f, + key, + GNUNET_JSON_from_data_auto (&sh))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + continue; + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Forgettable field `%s' has invalid value\n", + key); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Take a given contract with "forgettable" fields marked + * but with 'True' instead of a real salt. Replaces all + * 'True' values with proper random salts. Fails if any + * forgettable markers are neither 'True' nor valid salts. + * + * @param[in,out] json JSON to transform + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +TALER_JSON_contract_seed_forgettable (json_t *json) +{ + if (json_is_object (json)) + { + const char *key; + json_t *val; + + json_object_foreach (json, + key, + val) + { + if (0 == strcmp ("$forgettable", + key)) + { + if (GNUNET_OK != + seed_forgettable (val)) + return GNUNET_SYSERR; + continue; + } + if (GNUNET_OK != + TALER_JSON_contract_seed_forgettable (val)) + return GNUNET_SYSERR; + } + } + if (json_is_array (json)) + { + size_t index; + json_t *val; + + json_array_foreach (json, + index, + val) + { + if (GNUNET_OK != + TALER_JSON_contract_seed_forgettable (val)) + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Parse a json path. + * + * @param obj the object that the path is relative to. + * @param prev the parent of @e obj. + * @param path the path to parse. + * @param cb the callback to call, if we get to the end of @e path. + * @param cb_cls the closure for the callback. + * @return #GNUNET_OK on success, #GNUNET_SYSERR if @e path is malformed. + */ +static enum GNUNET_GenericReturnValue +parse_path (json_t *obj, + json_t *prev, + const char *path, + TALER_JSON_ExpandPathCallback cb, + void *cb_cls) +{ + char *id = GNUNET_strdup (path); + char *next_id = strchr (id, + '.'); + char *next_path; + char *bracket; + json_t *next_obj = NULL; + char *next_dot; + + GNUNET_assert (NULL != id); /* make stupid compiler happy */ + if (NULL == next_id) + { + cb (cb_cls, + id, + prev); + GNUNET_free (id); + return GNUNET_OK; + } + bracket = strchr (next_id, + '['); + *next_id = '\0'; + next_id++; + next_path = GNUNET_strdup (next_id); + next_dot = strchr (next_id, + '.'); + if (NULL != next_dot) + *next_dot = '\0'; + /* If this is the first time this is called, make sure id is "$" */ + if ( (NULL == prev) && + (0 != strcmp (id, + "$"))) + { + GNUNET_free (id); + GNUNET_free (next_path); + return GNUNET_SYSERR; + } + + /* Check for bracketed indices */ + if (NULL != bracket) + { + char *end_bracket = strchr (bracket, + ']'); + if (NULL == end_bracket) + { + GNUNET_free (id); + GNUNET_free (next_path); + return GNUNET_SYSERR; + } + *end_bracket = '\0'; + + *bracket = '\0'; + bracket++; + + json_t *array = json_object_get (obj, + next_id); + if (0 == strcmp (bracket, + "*")) + { + size_t index; + json_t *value; + int ret = GNUNET_OK; + + json_array_foreach (array, index, value) { + ret = parse_path (value, + obj, + next_path, + cb, + cb_cls); + if (GNUNET_OK != ret) + { + GNUNET_free (id); + GNUNET_free (next_path); + return ret; + } + } + } + else + { + unsigned int index; + char dummy; + + if (1 != sscanf (bracket, + "%u%c", + &index, + &dummy)) + { + GNUNET_free (id); + GNUNET_free (next_path); + return GNUNET_SYSERR; + } + next_obj = json_array_get (array, + index); + } + } + else + { + /* No brackets, so just fetch the object by name */ + next_obj = json_object_get (obj, + next_id); + } + + if (NULL != next_obj) + { + int ret = parse_path (next_obj, + obj, + next_path, + cb, + cb_cls); + GNUNET_free (id); + GNUNET_free (next_path); + return ret; + } + GNUNET_free (id); + GNUNET_free (next_path); + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_expand_path (json_t *json, + const char *path, + TALER_JSON_ExpandPathCallback cb, + void *cb_cls) +{ + return parse_path (json, + NULL, + path, + cb, + cb_cls); +} + + +enum TALER_ErrorCode +TALER_JSON_get_error_code (const json_t *json) +{ + const json_t *jc; + + if (NULL == json) + return TALER_EC_GENERIC_INVALID_RESPONSE; + jc = json_object_get (json, "code"); + /* The caller already knows that the JSON represents an error, + so we are dealing with a missing error code here. */ + if (NULL == jc) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Expected Taler error code `code' in JSON, but field does not exist!\n"); + return TALER_EC_INVALID; + } + if (json_is_integer (jc)) + return (enum TALER_ErrorCode) json_integer_value (jc); + GNUNET_break_op (0); + return TALER_EC_INVALID; +} + + +const char * +TALER_JSON_get_error_hint (const json_t *json) +{ + const json_t *jc; + + if (NULL == json) + return NULL; + jc = json_object_get (json, + "hint"); + if (NULL == jc) + return NULL; /* no hint, is allowed */ + if (! json_is_string (jc)) + { + /* Hints must be strings */ + GNUNET_break_op (0); + return NULL; + } + return json_string_value (jc); +} + + +enum TALER_ErrorCode +TALER_JSON_get_error_code2 (const void *data, + size_t data_size) +{ + json_t *json; + enum TALER_ErrorCode ec; + json_error_t err; + + json = json_loadb (data, + data_size, + JSON_REJECT_DUPLICATES, + &err); + if (NULL == json) + return TALER_EC_INVALID; + ec = TALER_JSON_get_error_code (json); + json_decref (json); + if (ec == TALER_EC_NONE) + return TALER_EC_INVALID; + return ec; +} + + +void +TALER_deposit_policy_hash (const json_t *policy, + struct TALER_ExtensionPolicyHashP *ech) +{ + GNUNET_assert (GNUNET_OK == + dump_and_hash (policy, + "taler-extensions-policy", + &ech->hash)); +} + + +char * +TALER_JSON_canonicalize (const json_t *input) +{ + char *wire_enc; + + if (NULL == (wire_enc = json_dumps (input, + JSON_ENCODE_ANY + | JSON_COMPACT + | JSON_SORT_KEYS))) + { + GNUNET_break (0); + return NULL; + } + TALER_rfc8785encode (&wire_enc); + return wire_enc; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_extensions_manifests_hash (const json_t *manifests, + struct TALER_ExtensionManifestsHashP *ech) +{ + return dump_and_hash (manifests, + "taler-extensions-manifests", + &ech->hash); +} + + +/* End of json/json.c */ diff --git a/src/json/json_helper.c b/src/json/json_helper.c @@ -0,0 +1,1366 @@ +/* + This file is part of TALER + Copyright (C) 2014-2021 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file json/json_helper.c + * @brief helper functions to generate specifications to parse + * Taler-specific JSON objects with libgnunetjson + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_json_lib.h" + + +/** + * Convert string value to numeric cipher value. + * + * @param cipher_s input string + * @return numeric cipher value + */ +static enum TALER_DenominationCipher +string_to_cipher (const char *cipher_s) +{ + if ((0 == strcasecmp (cipher_s, + "RSA")) || + (0 == strcasecmp (cipher_s, + "RSA+age_restricted"))) + return TALER_DENOMINATION_RSA; + if ((0 == strcasecmp (cipher_s, + "CS")) || + (0 == strcasecmp (cipher_s, + "CS+age_restricted"))) + return TALER_DENOMINATION_CS; + return TALER_DENOMINATION_INVALID; +} + + +json_t * +TALER_JSON_from_amount (const struct TALER_Amount *amount) +{ + char *amount_str = TALER_amount_to_string (amount); + + GNUNET_assert (NULL != amount_str); + { + json_t *j = json_string (amount_str); + + GNUNET_free (amount_str); + return j; + } +} + + +/** + * Parse given JSON object to Amount + * + * @param cls closure, expected currency, or NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_amount (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + const char *currency = cls; + struct TALER_Amount *r_amount = spec->ptr; + + (void) cls; + if (! json_is_string (root)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_string_to_amount (json_string_value (root), + r_amount)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (NULL != currency) && + (0 != + strcasecmp (currency, + r_amount->currency)) ) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Expected currency `%s', but amount used currency `%s' in field `%s'\n", + currency, + r_amount->currency, + spec->field); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_amount (const char *name, + const char *currency, + struct TALER_Amount *r_amount) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_amount, + .cleaner = NULL, + .cls = (void *) currency, + .field = name, + .ptr = r_amount, + .ptr_size = 0, + .size_ptr = NULL + }; + + GNUNET_assert (NULL != currency); + return ret; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_amount_any (const char *name, + struct TALER_Amount *r_amount) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_amount, + .cleaner = NULL, + .cls = NULL, + .field = name, + .ptr = r_amount, + .ptr_size = 0, + .size_ptr = NULL + }; + + return ret; +} + + +/** + * Parse given JSON object to currency spec. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_cspec (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_CurrencySpecification *r_cspec = spec->ptr; + const char *name; + const char *currency; + const char *decimal_separator; + uint32_t fid; + uint32_t fnd; + uint32_t ftzd; + const json_t *map; + struct GNUNET_JSON_Specification gspec[] = { + GNUNET_JSON_spec_string ("currency", + &currency), + GNUNET_JSON_spec_string ("name", + &name), + GNUNET_JSON_spec_string ("decimal_separator", + &decimal_separator), + GNUNET_JSON_spec_uint32 ("num_fractional_input_digits", + &fid), + GNUNET_JSON_spec_uint32 ("num_fractional_normal_digits", + &fnd), + GNUNET_JSON_spec_uint32 ("num_fractional_trailing_zero_digits", + &ftzd), + GNUNET_JSON_spec_bool ("is_currency_name_leading", + &r_cspec->is_currency_name_leading), + GNUNET_JSON_spec_object_const ("alt_unit_names", + &map), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + memset (r_cspec->currency, + 0, + sizeof (r_cspec->currency)); + if (GNUNET_OK != + GNUNET_JSON_parse (root, + gspec, + &emsg, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + emsg); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (strlen (currency) >= TALER_CURRENCY_LEN) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (fid > TALER_AMOUNT_FRAC_LEN) || + (fnd > TALER_AMOUNT_FRAC_LEN) || + (ftzd > TALER_AMOUNT_FRAC_LEN) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_check_currency (currency)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + strcpy (r_cspec->currency, + currency); + if (GNUNET_OK != + TALER_check_currency_scale_map (map)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + r_cspec->name = GNUNET_strdup (name); + r_cspec->decimal_separator = GNUNET_strdup (decimal_separator); + r_cspec->map_alt_unit_names = json_incref ((json_t *) map); + return GNUNET_OK; +} + + +/** + * Cleanup data left from parsing encrypted contract. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_cspec (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_CurrencySpecification *cspec = spec->ptr; + + (void) cls; + GNUNET_free (cspec->name); + GNUNET_free (cspec->decimal_separator); + json_decref (cspec->map_alt_unit_names); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_currency_specification ( + const char *name, + struct TALER_CurrencySpecification *r_cspec) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_cspec, + .cleaner = &clean_cspec, + .cls = NULL, + .field = name, + .ptr = r_cspec, + .ptr_size = 0, + .size_ptr = NULL + }; + + return ret; +} + + +static enum GNUNET_GenericReturnValue +parse_denomination_group (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationGroup *group = spec->ptr; + const char *cipher; + const char *currency = cls; + bool age_mask_missing = false; + bool has_age_restricted_suffix = false; + struct GNUNET_JSON_Specification gspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + TALER_JSON_spec_amount ("value", + currency, + &group->value), + TALER_JSON_SPEC_DENOM_FEES ("fee", + currency, + &group->fees), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("age_mask", + &group->age_mask.bits), + &age_mask_missing), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + gspec, + &emsg, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + emsg); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + group->cipher = string_to_cipher (cipher); + if (TALER_DENOMINATION_INVALID == group->cipher) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* age_mask and suffix must be consistent */ + has_age_restricted_suffix = + (NULL != strstr (cipher, "+age_restricted")); + if (has_age_restricted_suffix && age_mask_missing) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (age_mask_missing) + group->age_mask.bits = 0; + + return GNUNET_OK; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_denomination_group (const char *name, + const char *currency, + struct TALER_DenominationGroup *group) +{ + struct GNUNET_JSON_Specification ret = { + .cls = (void *) currency, + .parser = &parse_denomination_group, + .cleaner = NULL, + .field = name, + .ptr = group, + .ptr_size = sizeof(*group), + .size_ptr = NULL, + }; + + return ret; +} + + +/** + * Parse given JSON object to an encrypted contract. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_econtract (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_EncryptedContract *econtract = spec->ptr; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_varsize ("econtract", + &econtract->econtract, + &econtract->econtract_size), + GNUNET_JSON_spec_fixed_auto ("econtract_sig", + &econtract->econtract_sig), + GNUNET_JSON_spec_fixed_auto ("contract_pub", + &econtract->contract_pub), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Cleanup data left from parsing encrypted contract. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_econtract (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_EncryptedContract *econtract = spec->ptr; + + (void) cls; + GNUNET_free (econtract->econtract); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_econtract (const char *name, + struct TALER_EncryptedContract *econtract) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_econtract, + .cleaner = &clean_econtract, + .cls = NULL, + .field = name, + .ptr = econtract, + .ptr_size = 0, + .size_ptr = NULL + }; + + return ret; +} + + +/** + * Parse given JSON object to an age commitmnet + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_age_commitment (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_AgeCommitment *age_commitment = spec->ptr; + json_t *pk; + unsigned int idx; + size_t num; + + (void) cls; + if ( (NULL == root) || + (! json_is_array (root))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + num = json_array_size (root); + if (32 <= num || 0 == num) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + age_commitment->num = num; + age_commitment->keys = + GNUNET_new_array (num, + struct TALER_AgeCommitmentPublicKeyP); + + json_array_foreach (root, idx, pk) { + const char *emsg; + unsigned int eline; + struct GNUNET_JSON_Specification pkspec[] = { + GNUNET_JSON_spec_fixed_auto ( + NULL, + &age_commitment->keys[idx].pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (pk, + pkspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + }; + + return GNUNET_OK; +} + + +/** + * Cleanup data left fom parsing age commitment + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_age_commitment (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_AgeCommitment *age_commitment = spec->ptr; + + (void) cls; + + if (NULL == age_commitment || + NULL == age_commitment->keys) + return; + + age_commitment->num = 0; + GNUNET_free (age_commitment->keys); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_age_commitment (const char *name, + struct TALER_AgeCommitment *age_commitment) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_age_commitment, + .cleaner = &clean_age_commitment, + .cls = NULL, + .field = name, + .ptr = age_commitment, + .ptr_size = 0, + .size_ptr = NULL + }; + + return ret; +} + + +/** + * Parse given JSON object to denomination public key. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_denom_pub (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationPublicKey *denom_pub = spec->ptr; + const char *cipher; + bool age_mask_missing = false; + struct GNUNET_JSON_Specification dspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("age_mask", + &denom_pub->age_mask.bits), + &age_mask_missing), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + dspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (age_mask_missing) + denom_pub->age_mask.bits = 0; + + denom_pub->cipher = string_to_cipher (cipher); + switch (denom_pub->cipher) + { + case TALER_DENOMINATION_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_rsa_public_key ( + "rsa_public_key", + &denom_pub->details.rsa_public_key), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + } + case TALER_DENOMINATION_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed ("cs_public_key", + &denom_pub->details.cs_public_key, + sizeof (denom_pub->details.cs_public_key)), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + } + default: + GNUNET_break_op (0); + return GNUNET_SYSERR; + } +} + + +/** + * Cleanup data left from parsing denomination public key. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_denom_pub (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationPublicKey *denom_pub = spec->ptr; + + (void) cls; + TALER_denom_pub_free (denom_pub); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_denom_pub (const char *field, + struct TALER_DenominationPublicKey *pk) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_denom_pub, + .cleaner = &clean_denom_pub, + .field = field, + .ptr = pk + }; + + pk->cipher = TALER_DENOMINATION_INVALID; + return ret; +} + + +/** + * Parse given JSON object partially into a denomination public key. + * + * Depending on the cipher in cls, it parses the corresponding public key type. + * + * @param cls closure, enum TALER_DenominationCipher + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_denom_pub_cipher (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationPublicKey *denom_pub = spec->ptr; + enum TALER_DenominationCipher cipher = + (enum TALER_DenominationCipher) (long) cls; + const char *emsg; + unsigned int eline; + + switch (cipher) + { + case TALER_DENOMINATION_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_rsa_public_key ( + "rsa_pub", + &denom_pub->details.rsa_public_key), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + denom_pub->cipher = cipher; + return GNUNET_OK; + } + case TALER_DENOMINATION_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed ("cs_pub", + &denom_pub->details.cs_public_key, + sizeof (denom_pub->details.cs_public_key)), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + denom_pub->cipher = cipher; + return GNUNET_OK; + } + default: + GNUNET_break_op (0); + denom_pub->cipher = 0; + return GNUNET_SYSERR; + } +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_denom_pub_cipher (const char *field, + enum TALER_DenominationCipher cipher, + struct TALER_DenominationPublicKey *pk) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_denom_pub_cipher, + .cleaner = &clean_denom_pub, + .field = field, + .cls = (void *) cipher, + .ptr = pk + }; + + return ret; +} + + +/** + * Parse given JSON object to denomination signature. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_denom_sig (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationSignature *denom_sig = spec->ptr; + const char *cipher; + struct GNUNET_JSON_Specification dspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + dspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + denom_sig->cipher = string_to_cipher (cipher); + switch (denom_sig->cipher) + { + case TALER_DENOMINATION_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_rsa_signature ( + "rsa_signature", + &denom_sig->details.rsa_signature), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + } + case TALER_DENOMINATION_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed_auto ("cs_signature_r", + &denom_sig->details.cs_signature.r_point), + GNUNET_JSON_spec_fixed_auto ("cs_signature_s", + &denom_sig->details.cs_signature.s_scalar), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + } + default: + GNUNET_break_op (0); + return GNUNET_SYSERR; + } +} + + +/** + * Cleanup data left from parsing denomination public key. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_denom_sig (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationSignature *denom_sig = spec->ptr; + + (void) cls; + TALER_denom_sig_free (denom_sig); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_denom_sig (const char *field, + struct TALER_DenominationSignature *sig) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_denom_sig, + .cleaner = &clean_denom_sig, + .field = field, + .ptr = sig + }; + + sig->cipher = TALER_DENOMINATION_INVALID; + return ret; +} + + +/** + * Parse given JSON object to blinded denomination signature. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_blinded_denom_sig (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr; + const char *cipher; + struct GNUNET_JSON_Specification dspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + dspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + denom_sig->cipher = string_to_cipher (cipher); + switch (denom_sig->cipher) + { + case TALER_DENOMINATION_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_rsa_signature ( + "blinded_rsa_signature", + &denom_sig->details.blinded_rsa_signature), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + } + case TALER_DENOMINATION_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_uint32 ("b", + &denom_sig->details.blinded_cs_answer.b), + GNUNET_JSON_spec_fixed_auto ("s", + &denom_sig->details.blinded_cs_answer. + s_scalar), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + } + break; + default: + GNUNET_break_op (0); + return GNUNET_SYSERR; + } +} + + +/** + * Cleanup data left from parsing denomination public key. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_blinded_denom_sig (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr; + + (void) cls; + TALER_blinded_denom_sig_free (denom_sig); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_blinded_denom_sig ( + const char *field, + struct TALER_BlindedDenominationSignature *sig) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_blinded_denom_sig, + .cleaner = &clean_blinded_denom_sig, + .field = field, + .ptr = sig + }; + + sig->cipher = TALER_DENOMINATION_INVALID; + return ret; +} + + +/** + * Parse given JSON object to blinded planchet. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_blinded_planchet (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_BlindedPlanchet *blinded_planchet = spec->ptr; + const char *cipher; + struct GNUNET_JSON_Specification dspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + dspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + blinded_planchet->cipher = string_to_cipher (cipher); + switch (blinded_planchet->cipher) + { + case TALER_DENOMINATION_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_varsize ( + "rsa_blinded_planchet", + &blinded_planchet->details.rsa_blinded_planchet.blinded_msg, + &blinded_planchet->details.rsa_blinded_planchet.blinded_msg_size), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + } + case TALER_DENOMINATION_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed_auto ( + "cs_nonce", + &blinded_planchet->details.cs_blinded_planchet.nonce), + GNUNET_JSON_spec_fixed_auto ( + "cs_blinded_c0", + &blinded_planchet->details.cs_blinded_planchet.c[0]), + GNUNET_JSON_spec_fixed_auto ( + "cs_blinded_c1", + &blinded_planchet->details.cs_blinded_planchet.c[1]), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + } + break; + default: + GNUNET_break_op (0); + return GNUNET_SYSERR; + } +} + + +/** + * Cleanup data left from parsing blinded planchet. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_blinded_planchet (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_BlindedPlanchet *blinded_planchet = spec->ptr; + + (void) cls; + TALER_blinded_planchet_free (blinded_planchet); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_blinded_planchet (const char *field, + struct TALER_BlindedPlanchet *blinded_planchet) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_blinded_planchet, + .cleaner = &clean_blinded_planchet, + .field = field, + .ptr = blinded_planchet + }; + + blinded_planchet->cipher = TALER_DENOMINATION_INVALID; + return ret; +} + + +/** + * Parse given JSON object to exchange withdraw values (/csr). + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_exchange_withdraw_values (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_ExchangeWithdrawValues *ewv = spec->ptr; + const char *cipher; + struct GNUNET_JSON_Specification dspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + dspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + ewv->cipher = string_to_cipher (cipher); + switch (ewv->cipher) + { + case TALER_DENOMINATION_RSA: + return GNUNET_OK; + case TALER_DENOMINATION_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed ( + "r_pub_0", + &ewv->details.cs_values.r_pub[0], + sizeof (struct GNUNET_CRYPTO_CsRPublic)), + GNUNET_JSON_spec_fixed ( + "r_pub_1", + &ewv->details.cs_values.r_pub[1], + sizeof (struct GNUNET_CRYPTO_CsRPublic)), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + } + default: + GNUNET_break_op (0); + return GNUNET_SYSERR; + } +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_exchange_withdraw_values ( + const char *field, + struct TALER_ExchangeWithdrawValues *ewv) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_exchange_withdraw_values, + .field = field, + .ptr = ewv + }; + + ewv->cipher = TALER_DENOMINATION_INVALID; + return ret; +} + + +/** + * Closure for #parse_i18n_string. + */ +struct I18nContext +{ + /** + * Language pattern to match. + */ + char *lp; + + /** + * Name of the field to match. + */ + const char *field; +}; + + +/** + * Parse given JSON object to internationalized string. + * + * @param cls closure, our `struct I18nContext *` + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_i18n_string (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct I18nContext *ctx = cls; + json_t *i18n; + json_t *val; + + { + char *i18nf; + + GNUNET_asprintf (&i18nf, + "%s_i18n", + ctx->field); + i18n = json_object_get (root, + i18nf); + GNUNET_free (i18nf); + } + + val = json_object_get (root, + ctx->field); + if ( (NULL != i18n) && + (NULL != ctx->lp) ) + { + double best = 0.0; + json_t *pos; + const char *lang; + + json_object_foreach (i18n, lang, pos) + { + double score; + + score = TALER_language_matches (ctx->lp, + lang); + if (score > best) + { + best = score; + val = pos; + } + } + } + + { + const char *str; + + str = json_string_value (val); + *(const char **) spec->ptr = str; + } + return GNUNET_OK; +} + + +/** + * Function called to clean up data from earlier parsing. + * + * @param cls closure + * @param spec our specification entry with data to clean. + */ +static void +i18n_cleaner (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct I18nContext *ctx = cls; + + (void) spec; + if (NULL != ctx) + { + GNUNET_free (ctx->lp); + GNUNET_free (ctx); + } +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_i18n_string (const char *name, + const char *language_pattern, + const char **strptr) +{ + struct I18nContext *ctx = GNUNET_new (struct I18nContext); + struct GNUNET_JSON_Specification ret = { + .parser = &parse_i18n_string, + .cleaner = &i18n_cleaner, + .cls = ctx, + .field = NULL, /* we want the main object */ + .ptr = strptr, + .ptr_size = 0, + .size_ptr = NULL + }; + + ctx->lp = (NULL != language_pattern) + ? GNUNET_strdup (language_pattern) + : NULL; + ctx->field = name; + *strptr = NULL; + return ret; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_i18n_str (const char *name, + const char **strptr) +{ + const char *lang = getenv ("LANG"); + char *dot; + char *l; + struct GNUNET_JSON_Specification ret; + + if (NULL != lang) + { + dot = strchr (lang, + '.'); + if (NULL == dot) + l = GNUNET_strdup (lang); + else + l = GNUNET_strndup (lang, + dot - lang); + } + else + { + l = NULL; + } + ret = TALER_JSON_spec_i18n_string (name, + l, + strptr); + GNUNET_free (l); + return ret; +} + + +/* end of json/json_helper.c */ diff --git a/src/json/json_pack.c b/src/json/json_pack.c @@ -0,0 +1,308 @@ +/* + This file is part of TALER + Copyright (C) 2021, 2022 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file json/json_pack.c + * @brief helper functions for JSON object packing + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_json_lib.h" + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_time_abs_human (const char *name, + struct GNUNET_TIME_Absolute at) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + .object = json_string ( + GNUNET_STRINGS_absolute_time_to_string (at)) + }; + + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_econtract ( + const char *name, + const struct TALER_EncryptedContract *econtract) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == econtract) + return ps; + ps.object + = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_varsize ("econtract", + econtract->econtract, + econtract->econtract_size), + GNUNET_JSON_pack_data_auto ("econtract_sig", + &econtract->econtract_sig), + GNUNET_JSON_pack_data_auto ("contract_pub", + &econtract->contract_pub)); + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_age_commitment ( + const char *name, + const struct TALER_AgeCommitment *age_commitment) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + json_t *keys; + + if (NULL == age_commitment || + 0 == age_commitment->num) + return ps; + + GNUNET_assert (NULL != + (keys = json_array ())); + + for (size_t i = 0; + i < age_commitment->num; + i++) + { + json_t *val; + val = GNUNET_JSON_from_data (&age_commitment->keys[i], + sizeof(age_commitment->keys[i])); + GNUNET_assert (NULL != val); + GNUNET_assert (0 == + json_array_append_new (keys, val)); + } + + ps.object = keys; + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_denom_pub ( + const char *name, + const struct TALER_DenominationPublicKey *pk) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == pk) + return ps; + switch (pk->cipher) + { + case TALER_DENOMINATION_RSA: + ps.object + = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", "RSA"), + GNUNET_JSON_pack_uint64 ("age_mask", + pk->age_mask.bits), + GNUNET_JSON_pack_rsa_public_key ("rsa_public_key", + pk->details.rsa_public_key)); + break; + case TALER_DENOMINATION_CS: + ps.object + = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", "CS"), + GNUNET_JSON_pack_uint64 ("age_mask", + pk->age_mask.bits), + GNUNET_JSON_pack_data_varsize ("cs_public_key", + &pk->details.cs_public_key, + sizeof (pk->details.cs_public_key))); + break; + default: + GNUNET_assert (0); + } + + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_denom_sig ( + const char *name, + const struct TALER_DenominationSignature *sig) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == sig) + return ps; + switch (sig->cipher) + { + case TALER_DENOMINATION_RSA: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "RSA"), + GNUNET_JSON_pack_rsa_signature ("rsa_signature", + sig->details.rsa_signature)); + break; + case TALER_DENOMINATION_CS: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "CS"), + GNUNET_JSON_pack_data_auto ("cs_signature_r", + &sig->details.cs_signature.r_point), + GNUNET_JSON_pack_data_auto ("cs_signature_s", + &sig->details.cs_signature.s_scalar)); + break; + default: + GNUNET_assert (0); + } + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_exchange_withdraw_values ( + const char *name, + const struct TALER_ExchangeWithdrawValues *ewv) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == ewv) + return ps; + switch (ewv->cipher) + { + case TALER_DENOMINATION_RSA: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "RSA")); + break; + case TALER_DENOMINATION_CS: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "CS"), + GNUNET_JSON_pack_data_varsize ( + "r_pub_0", + &ewv->details.cs_values.r_pub[0], + sizeof(struct GNUNET_CRYPTO_CsRPublic)), + GNUNET_JSON_pack_data_varsize ( + "r_pub_1", + &ewv->details.cs_values.r_pub[1], + sizeof(struct GNUNET_CRYPTO_CsRPublic)) + ); + break; + default: + GNUNET_assert (0); + } + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_blinded_denom_sig ( + const char *name, + const struct TALER_BlindedDenominationSignature *sig) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == sig) + return ps; + switch (sig->cipher) + { + case TALER_DENOMINATION_RSA: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "RSA"), + GNUNET_JSON_pack_rsa_signature ("blinded_rsa_signature", + sig->details.blinded_rsa_signature)); + break; + case TALER_DENOMINATION_CS: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "CS"), + GNUNET_JSON_pack_uint64 ("b", + sig->details.blinded_cs_answer.b), + GNUNET_JSON_pack_data_auto ("s", + &sig->details.blinded_cs_answer.s_scalar)); + break; + default: + GNUNET_assert (0); + } + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_blinded_planchet ( + const char *name, + const struct TALER_BlindedPlanchet *blinded_planchet) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == blinded_planchet) + return ps; + switch (blinded_planchet->cipher) + { + case TALER_DENOMINATION_RSA: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "RSA"), + GNUNET_JSON_pack_data_varsize ( + "rsa_blinded_planchet", + blinded_planchet->details.rsa_blinded_planchet.blinded_msg, + blinded_planchet->details.rsa_blinded_planchet.blinded_msg_size)); + break; + case TALER_DENOMINATION_CS: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "CS"), + GNUNET_JSON_pack_data_auto ( + "cs_nonce", + &blinded_planchet->details.cs_blinded_planchet.nonce), + GNUNET_JSON_pack_data_auto ( + "cs_blinded_c0", + &blinded_planchet->details.cs_blinded_planchet.c[0]), + GNUNET_JSON_pack_data_auto ( + "cs_blinded_c1", + &blinded_planchet->details.cs_blinded_planchet.c[1])); + break; + default: + GNUNET_assert (0); + } + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_amount (const char *name, + const struct TALER_Amount *amount) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + .object = (NULL != amount) + ? TALER_JSON_from_amount (amount) + : NULL + }; + + return ps; +} + + +/* End of json/json_pack.c */ diff --git a/src/json/json_wire.c b/src/json/json_wire.c @@ -0,0 +1,122 @@ +/* + This file is part of TALER + Copyright (C) 2018, 2021 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file json/json_wire.c + * @brief helper functions to generate or check /wire replies + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_json_lib.h" + + +enum GNUNET_GenericReturnValue +TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s, + struct TALER_MerchantWireHashP *hc) +{ + const char *payto_uri; + struct TALER_WireSaltP salt; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("payto_uri", + &payto_uri), + GNUNET_JSON_spec_fixed_auto ("salt", + &salt), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (wire_s, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Validating `%s'\n", + payto_uri); + { + 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; + } + } + TALER_merchant_wire_signature_hash (payto_uri, + &salt, + hc); + return GNUNET_OK; +} + + +char * +TALER_JSON_wire_to_payto (const json_t *wire_s) +{ + json_t *payto_o; + const char *payto_str; + char *err; + + payto_o = json_object_get (wire_s, + "payto_uri"); + if ( (NULL == payto_o) || + (NULL == (payto_str = json_string_value (payto_o))) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Malformed wire record encountered: lacks payto://-url\n"); + return NULL; + } + if (NULL != + (err = TALER_payto_validate (payto_str))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Malformed wire record encountered: payto URI `%s' invalid: %s\n", + payto_str, + err); + GNUNET_free (err); + return NULL; + } + return GNUNET_strdup (payto_str); +} + + +char * +TALER_JSON_wire_to_method (const json_t *wire_s) +{ + json_t *payto_o; + const char *payto_str; + + payto_o = json_object_get (wire_s, + "payto_uri"); + if ( (NULL == payto_o) || + (NULL == (payto_str = json_string_value (payto_o))) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Fatally malformed wire record encountered: lacks payto://-url\n"); + return NULL; + } + return TALER_payto_get_method (payto_str); +} + + +/* end of json_wire.c */ diff --git a/src/json/test_json.c b/src/json/test_json.c @@ -0,0 +1,439 @@ +/* + This file is part of TALER + (C) 2015, 2016, 2020 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 <http://www.gnu.org/licenses/> +*/ + +/** + * @file json/test_json.c + * @brief Tests for Taler-specific crypto logic + * @author Christian Grothoff <christian@grothoff.org> + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_json_lib.h" + + +/** + * Test amount conversion from/to JSON. + * + * @return 0 on success + */ +static int +test_amount (void) +{ + json_t *j; + struct TALER_Amount a1; + struct TALER_Amount a2; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("amount", + "EUR", + &a2), + GNUNET_JSON_spec_end () + }; + + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("EUR:4.3", + &a1)); + j = json_pack ("{s:o}", "amount", TALER_JSON_from_amount (&a1)); + GNUNET_assert (NULL != j); + GNUNET_assert (GNUNET_OK == + GNUNET_JSON_parse (j, spec, + NULL, NULL)); + GNUNET_assert (0 == + TALER_amount_cmp (&a1, + &a2)); + json_decref (j); + return 0; +} + + +struct TestPath_Closure +{ + const char **object_ids; + + const json_t **parents; + + unsigned int results_length; + + int cmp_result; +}; + + +static void +path_cb (void *cls, + const char *object_id, + json_t *parent) +{ + struct TestPath_Closure *cmp = cls; + if (NULL == cmp) + return; + unsigned int i = cmp->results_length; + if ((0 != strcmp (cmp->object_ids[i], + object_id)) || + (1 != json_equal (cmp->parents[i], + parent))) + cmp->cmp_result = 1; + cmp->results_length += 1; +} + + +static int +test_contract (void) +{ + struct TALER_PrivateContractHashP h1; + struct TALER_PrivateContractHashP h2; + json_t *c1; + json_t *c2; + json_t *c3; + json_t *c4; + + c1 = json_pack ("{s:s, s:{s:s, s:{s:b}}}", + "k1", "v1", + "k2", "n1", "n2", + /***/ "$forgettable", "n1", true); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_seed_forgettable (c1)); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_hash (c1, + &h1)); + json_decref (c1); + + c1 = json_pack ("{s:s, s:{s:s, s:{s:s}}}", + "k1", "v1", + "k2", "n1", "n2", + /***/ "$forgettable", "n1", "salt"); + GNUNET_assert (NULL != c1); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_mark_forgettable (c1, + "k1")); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_mark_forgettable (c1, + "k2")); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_hash (c1, + &h1)); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_part_forget (c1, + "k1")); + /* check salt was forgotten */ + GNUNET_assert (NULL == + json_object_get (json_object_get (c1, + "$forgettable"), + "k1")); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_hash (c1, + &h2)); + if (0 != + GNUNET_memcmp (&h1, + &h2)) + { + GNUNET_break (0); + json_decref (c1); + return 1; + } + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_part_forget (json_object_get (c1, + "k2"), + "n1")); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_hash (c1, + &h2)); + if (0 != + GNUNET_memcmp (&h1, + &h2)) + { + GNUNET_break (0); + json_decref (c1); + return 1; + } + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_part_forget (c1, + "k2")); + // json_dumpf (c1, stderr, JSON_INDENT (2)); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_hash (c1, + &h2)); + json_decref (c1); + if (0 != + GNUNET_memcmp (&h1, + &h2)) + { + GNUNET_break (0); + return 1; + } + + c1 = json_pack ("{s:I, s:{s:s}, s:{s:b, s:{s:s}}, s:{s:s}}", + "k1", 1, + "$forgettable", "k1", "SALT", + "k2", "n1", true, + /***/ "$forgettable", "n1", "salt", + "k3", "n1", "string"); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_hash (c1, + &h1)); + // json_dumpf (c1, stderr, JSON_INDENT (2)); + json_decref (c1); + { + char *s; + + s = GNUNET_STRINGS_data_to_string_alloc (&h1, + sizeof (h1)); + if (0 != + strcmp (s, + "VDE8JPX0AEEE3EX1K8E11RYEWSZQKGGZCV6BWTE4ST1C8711P7H850Z7F2Q2HSSYETX87ERC2JNHWB7GTDWTDWMM716VKPSRBXD7SRR")) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid reference hash: %s\n", + s); + GNUNET_free (s); + return 1; + } + GNUNET_free (s); + } + + + c2 = json_pack ("{s:s}", + "n1", "n2"); + GNUNET_assert (NULL != c2); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_mark_forgettable (c2, + "n1")); + c3 = json_pack ("{s:s, s:o}", + "k1", "v1", + "k2", c2); + GNUNET_assert (NULL != c3); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_mark_forgettable (c3, + "k1")); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_hash (c3, + &h1)); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_part_forget (c2, + "n1")); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_hash (c3, + &h2)); + json_decref (c3); + c4 = json_pack ("{s:{s:s}, s:[{s:s}, {s:s}, {s:s}]}", + "abc1", + "xyz", "value", + "fruit", + "name", "banana", + "name", "apple", + "name", "orange"); + GNUNET_assert (NULL != c4); + GNUNET_assert (GNUNET_SYSERR == + TALER_JSON_expand_path (c4, + "%.xyz", + &path_cb, + NULL)); + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (c4, + "$.nonexistent_id", + &path_cb, + NULL)); + GNUNET_assert (GNUNET_SYSERR == + TALER_JSON_expand_path (c4, + "$.fruit[n]", + &path_cb, + NULL)); + + { + const char *object_ids[] = { "xyz" }; + const json_t *parents[] = { + json_object_get (c4, + "abc1") + }; + struct TestPath_Closure tp = { + .object_ids = object_ids, + .parents = parents, + .results_length = 0, + .cmp_result = 0 + }; + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (c4, + "$.abc1.xyz", + &path_cb, + &tp)); + GNUNET_assert (1 == tp.results_length); + GNUNET_assert (0 == tp.cmp_result); + } + { + const char *object_ids[] = { "name" }; + const json_t *parents[] = { + json_array_get (json_object_get (c4, + "fruit"), + 0) + }; + struct TestPath_Closure tp = { + .object_ids = object_ids, + .parents = parents, + .results_length = 0, + .cmp_result = 0 + }; + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (c4, + "$.fruit[0].name", + &path_cb, + &tp)); + GNUNET_assert (1 == tp.results_length); + GNUNET_assert (0 == tp.cmp_result); + } + { + const char *object_ids[] = { "name", "name", "name" }; + const json_t *parents[] = { + json_array_get (json_object_get (c4, + "fruit"), + 0), + json_array_get (json_object_get (c4, + "fruit"), + 1), + json_array_get (json_object_get (c4, + "fruit"), + 2) + }; + struct TestPath_Closure tp = { + .object_ids = object_ids, + .parents = parents, + .results_length = 0, + .cmp_result = 0 + }; + GNUNET_assert (GNUNET_OK == + TALER_JSON_expand_path (c4, + "$.fruit[*].name", + &path_cb, + &tp)); + GNUNET_assert (3 == tp.results_length); + GNUNET_assert (0 == tp.cmp_result); + } + json_decref (c4); + if (0 != + GNUNET_memcmp (&h1, + &h2)) + { + GNUNET_break (0); + return 1; + } + return 0; +} + + +static int +test_json_canon (void) +{ + { + json_t *c1; + char *canon; + c1 = json_pack ("{s:s}", + "k1", "Hello\nWorld"); + + canon = TALER_JSON_canonicalize (c1); + GNUNET_assert (NULL != canon); + + printf ("canon: '%s'\n", canon); + + GNUNET_assert (0 == strcmp (canon, + "{\"k1\":\"Hello\\nWorld\"}")); + } + { + json_t *c1; + char *canon; + c1 = json_pack ("{s:s}", + "k1", "Testing “unicode” characters"); + + canon = TALER_JSON_canonicalize (c1); + GNUNET_assert (NULL != canon); + + printf ("canon: '%s'\n", canon); + + GNUNET_assert (0 == strcmp (canon, + "{\"k1\":\"Testing “unicode” characters\"}")); + } + { + json_t *c1; + char *canon; + c1 = json_pack ("{s:s}", + "k1", "low range \x05 chars"); + + canon = TALER_JSON_canonicalize (c1); + GNUNET_assert (NULL != canon); + + printf ("canon: '%s'\n", canon); + + GNUNET_assert (0 == strcmp (canon, + "{\"k1\":\"low range \\u0005 chars\"}")); + } + + + return 0; +} + + +static int +test_rfc8785 (void) +{ + struct TALER_PrivateContractHashP h1; + json_t *c1; + + c1 = json_pack ("{s:s}", + "k1", "\x08\x0B\t\1\\\x0d"); + GNUNET_assert (GNUNET_OK == + TALER_JSON_contract_hash (c1, + &h1)); + { + char *s; + + s = GNUNET_STRINGS_data_to_string_alloc (&h1, + sizeof (h1)); + if (0 != + strcmp (s, + "531S33T8ZRGW6548G7T67PMDNGS4Z1D8A2GMB87G3PNKYTW6KGF7Q99XVCGXBKVA2HX6PR5ENJ1PQ5ZTYMMXQB6RM7S82VP7ZG2X5G8")) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid reference hash: %s\n", + s); + GNUNET_free (s); + json_decref (c1); + return 1; + } + GNUNET_free (s); + } + json_decref (c1); + return 0; +} + + +int +main (int argc, + const char *const argv[]) +{ + (void) argc; + (void) argv; + GNUNET_log_setup ("test-json", + "WARNING", + NULL); + if (0 != test_amount ()) + return 1; + if (0 != test_contract ()) + return 2; + if (0 != test_json_canon ()) + return 2; + if (0 != test_rfc8785 ()) + return 2; + return 0; +} + + +/* end of test_json.c */ diff --git a/src/mhd/Makefile.am b/src/mhd/Makefile.am @@ -0,0 +1,29 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libtalermhd.la + +libtalermhd_la_SOURCES = \ + mhd_config.c \ + mhd_legal.c \ + mhd_parsing.c \ + mhd_responses.c \ + mhd_run.c +libtalermhd_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined +libtalermhd_la_LIBADD = \ + -lgnunetjson \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil \ + -lmicrohttpd \ + -ljansson \ + -lz \ + $(XLIB) diff --git a/src/mhd/mhd_config.c b/src/mhd/mhd_config.c @@ -0,0 +1,493 @@ +/* + This file is part of TALER + Copyright (C) 2014--2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd_config.c + * @brief functions to configure and setup MHD + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_mhd_lib.h" + + +/** + * Backlog for listen operation on UNIX domain sockets. + */ +#define UNIX_BACKLOG 500 + + +/** + * Parse the configuration to determine on which port + * or UNIX domain path we should run an HTTP service. + * + * @param cfg configuration to parse + * @param section section of the configuration to parse (usually "exchange") + * @param[out] rport set to the port number, or 0 for none + * @param[out] unix_path set to the UNIX path, or NULL for none + * @param[out] unix_mode set to the mode to be used for @a unix_path + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + uint16_t *rport, + char **unix_path, + mode_t *unix_mode) +{ + const char *choices[] = { + "tcp", + "unix", + NULL + }; + const char *serve_type; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_choice (cfg, + section, + "SERVE", + choices, + &serve_type)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "SERVE", + "serve type (tcp or unix) required"); + return GNUNET_SYSERR; + } + + if (0 == strcasecmp (serve_type, + "tcp")) + { + unsigned long long port; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + section, + "PORT", + &port)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "PORT", + "port number required"); + return GNUNET_SYSERR; + } + + if ( (0 == port) || + (port > UINT16_MAX) ) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "PORT", + "port number not in [1,65535]"); + return GNUNET_SYSERR; + } + *rport = (uint16_t) port; + *unix_path = NULL; + return GNUNET_OK; + } + if (0 == strcmp (serve_type, + "unix")) + { + struct sockaddr_un s_un; + char *modestring; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + section, + "UNIXPATH", + unix_path)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "UNIXPATH", + "UNIXPATH value required"); + return GNUNET_SYSERR; + } + if (strlen (*unix_path) >= sizeof (s_un.sun_path)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "unixpath `%s' is too long\n", + *unix_path); + GNUNET_free (*unix_path); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "UNIXPATH_MODE", + &modestring)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "UNIXPATH_MODE"); + GNUNET_free (*unix_path); + return GNUNET_SYSERR; + } + errno = 0; + *unix_mode = (mode_t) strtoul (modestring, NULL, 8); + if (0 != errno) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "UNIXPATH_MODE", + "must be octal number"); + GNUNET_free (modestring); + GNUNET_free (*unix_path); + return GNUNET_SYSERR; + } + GNUNET_free (modestring); + return GNUNET_OK; + } + /* not reached */ + GNUNET_assert (0); + return GNUNET_SYSERR; +} + + +/** + * Function called for logging by MHD. + * + * @param cls closure, NULL + * @param fm format string (`printf()`-style) + * @param ap arguments to @a fm + */ +void +TALER_MHD_handle_logs (void *cls, + const char *fm, + va_list ap) +{ + static int cache; + char buf[2048]; + + (void) cls; + if (-1 == cache) + return; + if (0 == cache) + { + if (0 == + GNUNET_get_log_call_status (GNUNET_ERROR_TYPE_INFO, + "libmicrohttpd", + __FILE__, + __FUNCTION__, + __LINE__)) + { + cache = -1; + return; + } + } + cache = 1; + vsnprintf (buf, + sizeof (buf), + fm, + ap); + GNUNET_log_from_nocheck (GNUNET_ERROR_TYPE_INFO, + "libmicrohttpd", + "%s", + buf); +} + + +/** + * Open UNIX domain socket for listining at @a unix_path with + * permissions @a unix_mode. + * + * @param unix_path where to listen + * @param unix_mode access permissions to set + * @return -1 on error, otherwise the listen socket + */ +int +TALER_MHD_open_unix_path (const char *unix_path, + mode_t unix_mode) +{ + struct GNUNET_NETWORK_Handle *nh; + struct sockaddr_un *un; + + if (sizeof (un->sun_path) <= strlen (unix_path)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "unixpath `%s' is too long\n", + unix_path); + return -1; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating listen socket '%s' with mode %o\n", + unix_path, + unix_mode); + + if (GNUNET_OK != + GNUNET_DISK_directory_create_for_file (unix_path)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "mkdir", + unix_path); + } + + un = GNUNET_new (struct sockaddr_un); + un->sun_family = AF_UNIX; + strncpy (un->sun_path, + unix_path, + sizeof (un->sun_path) - 1); + GNUNET_NETWORK_unix_precheck (un); + + if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX, + SOCK_STREAM, + 0))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "socket"); + GNUNET_free (un); + return -1; + } + + if (GNUNET_OK != + GNUNET_NETWORK_socket_bind (nh, + (void *) un, + sizeof (struct sockaddr_un))) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "bind", + unix_path); + GNUNET_free (un); + GNUNET_NETWORK_socket_close (nh); + return -1; + } + GNUNET_free (un); + if (GNUNET_OK != + GNUNET_NETWORK_socket_listen (nh, + UNIX_BACKLOG)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "listen"); + GNUNET_NETWORK_socket_close (nh); + return -1; + } + + if (0 != chmod (unix_path, + unix_mode)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "chmod"); + GNUNET_NETWORK_socket_close (nh); + return -1; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "set socket '%s' to mode %o\n", + unix_path, + unix_mode); + + /* extract and return actual socket handle from 'nh' */ + { + int fd; + + fd = GNUNET_NETWORK_get_fd (nh); + GNUNET_NETWORK_socket_free_memory_only_ (nh); + return fd; + } +} + + +/** + * Bind a listen socket to the UNIX domain path or the TCP port and IP address + * as specified in @a cfg in section @a section. IF only a port was + * specified, set @a port and return -1. Otherwise, return the bound file + * descriptor. + * + * @param cfg configuration to parse + * @param section configuration section to use + * @param[out] port port to set, if TCP without BINDTO + * @return -1 and a port of zero on error, otherwise + * either -1 and a port, or a bound stream socket + */ +int +TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + uint16_t *port) +{ + char *bind_to; + struct GNUNET_NETWORK_Handle *nh; + + /* try systemd passing first */ + { + const char *listen_pid; + const char *listen_fds; + + /* check for systemd-style FD passing */ + listen_pid = getenv ("LISTEN_PID"); + listen_fds = getenv ("LISTEN_FDS"); + if ( (NULL != listen_pid) && + (NULL != listen_fds) && + (getpid () == strtol (listen_pid, + NULL, + 10)) && + (1 == strtoul (listen_fds, + NULL, + 10)) ) + { + int fh; + int flags; + + fh = 3; + flags = fcntl (fh, + F_GETFD); + if ( (-1 == flags) && + (EBADF == errno) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Bad listen socket passed, ignored\n"); + fh = -1; + } + flags |= FD_CLOEXEC; + if ( (-1 != fh) && + (0 != fcntl (fh, + F_SETFD, + flags)) ) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "fcntl"); + if (-1 != fh) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Successfully obtained listen socket from hypervisor\n"); + return fh; + } + } + } + + /* now try configuration file */ + *port = 0; + { + char *serve_unixpath; + mode_t unixpath_mode; + + if (GNUNET_OK != + TALER_MHD_parse_config (cfg, + section, + port, + &serve_unixpath, + &unixpath_mode)) + return -1; + if (NULL != serve_unixpath) + { + int ret; + + ret = TALER_MHD_open_unix_path (serve_unixpath, + unixpath_mode); + GNUNET_free (serve_unixpath); + return ret; + } + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "BIND_TO", + &bind_to)) + return -1; /* only set port */ + + /* let's have fun binding... */ + { + char port_str[6]; + struct addrinfo hints; + struct addrinfo *res; + int ec; + + GNUNET_snprintf (port_str, + sizeof (port_str), + "%u", + (unsigned int) *port); + *port = 0; /* do NOT return port in case of errors */ + memset (&hints, + 0, + sizeof (hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE +#ifdef AI_IDN + | AI_IDN +#endif + ; + + if (0 != + (ec = getaddrinfo (bind_to, + port_str, + &hints, + &res))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to resolve BIND_TO address `%s': %s\n", + bind_to, + gai_strerror (ec)); + GNUNET_free (bind_to); + return -1; + } + GNUNET_free (bind_to); + + if (NULL == (nh = GNUNET_NETWORK_socket_create (res->ai_family, + res->ai_socktype, + res->ai_protocol))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "socket"); + freeaddrinfo (res); + return -1; + } + { + const int on = 1; + + if (GNUNET_OK != + GNUNET_NETWORK_socket_setsockopt (nh, + SOL_SOCKET, + SO_REUSEPORT, + &on, + sizeof(on))) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "setsockopt"); + } + if (GNUNET_OK != + GNUNET_NETWORK_socket_bind (nh, + res->ai_addr, + res->ai_addrlen)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "bind"); + freeaddrinfo (res); + return -1; + } + freeaddrinfo (res); + } + + if (GNUNET_OK != + GNUNET_NETWORK_socket_listen (nh, + UNIX_BACKLOG)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "listen"); + GNUNET_SCHEDULER_shutdown (); + return -1; + } + + /* extract and return actual socket handle from 'nh' */ + { + int fh; + + fh = GNUNET_NETWORK_get_fd (nh); + GNUNET_NETWORK_socket_free_memory_only_ (nh); + return fh; + } +} diff --git a/src/mhd/mhd_legal.c b/src/mhd/mhd_legal.c @@ -0,0 +1,694 @@ +/* + This file is part of TALER + Copyright (C) 2019, 2020, 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd_legal.c + * @brief API for returning legal documents based on client language + * and content type preferences + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include "taler_util.h" +#include "taler_mhd_lib.h" + +/** + * How long should browsers/proxies cache the "legal" replies? + */ +#define MAX_TERMS_CACHING GNUNET_TIME_UNIT_DAYS + + +/** + * Entry in the terms-of-service array. + */ +struct Terms +{ + /** + * Kept in a DLL. + */ + struct Terms *prev; + + /** + * Kept in a DLL. + */ + struct Terms *next; + + /** + * Mime type of the terms. + */ + const char *mime_type; + + /** + * The terms (NOT 0-terminated!), mmap()'ed. Do not free, + * use munmap() instead. + */ + void *terms; + + /** + * The desired language. + */ + char *language; + + /** + * deflated @e terms, to return if client supports deflate compression. + * malloc()'ed. NULL if @e terms does not compress. + */ + void *compressed_terms; + + /** + * Number of bytes in @e terms. + */ + size_t terms_size; + + /** + * Number of bytes in @e compressed_terms. + */ + size_t compressed_terms_size; + + /** + * Sorting key by format preference in case + * everything else is equal. Higher is preferred. + */ + unsigned int priority; + +}; + + +/** + * Prepared responses for legal documents + * (terms of service, privacy policy). + */ +struct TALER_MHD_Legal +{ + /** + * DLL of terms of service. + */ + struct Terms *terms_head; + + /** + * DLL of terms of service. + */ + struct Terms *terms_tail; + + /** + * Etag to use for the terms of service (= version). + */ + char *terms_etag; +}; + + +/** + * Check if @a mime matches the @a accept_pattern. + * + * @param accept_pattern a mime pattern like "text/plain" + * or "image/STAR" + * @param mime the mime type to match + * @return true if @a mime matches the @a accept_pattern + */ +static bool +mime_matches (const char *accept_pattern, + const char *mime) +{ + const char *da = strchr (accept_pattern, '/'); + const char *dm = strchr (mime, '/'); + const char *end; + + if ( (NULL == da) || + (NULL == dm) ) + return (0 == strcmp ("*", accept_pattern)); + /* FIXME: eventually, we might want to parse the "q=$FLOAT" + part after the ';' and figure out which one is the + best/preferred match instead of returning a boolean... */ + end = strchr (da, ';'); + if (NULL == end) + end = &da[strlen (da)]; + return + ( ( (1 == da - accept_pattern) && + ('*' == *accept_pattern) ) || + ( (da - accept_pattern == dm - mime) && + (0 == strncasecmp (accept_pattern, + mime, + da - accept_pattern)) ) ) && + ( (0 == strcmp (da, "/*")) || + (0 == strncasecmp (da, + dm, + end - da)) ); +} + + +bool +TALER_MHD_xmime_matches (const char *accept_pattern, + const char *mime) +{ + char *ap = GNUNET_strdup (accept_pattern); + char *sptr; + + for (const char *tok = strtok_r (ap, ",", &sptr); + NULL != tok; + tok = strtok_r (NULL, ",", &sptr)) + { + if (mime_matches (tok, + mime)) + { + GNUNET_free (ap); + return true; + } + } + GNUNET_free (ap); + return false; +} + + +MHD_RESULT +TALER_MHD_reply_legal (struct MHD_Connection *conn, + struct TALER_MHD_Legal *legal) +{ + struct MHD_Response *resp; + struct Terms *t; + struct GNUNET_TIME_Absolute a; + struct GNUNET_TIME_Timestamp m; + char dat[128]; + char *langs; + + a = GNUNET_TIME_relative_to_absolute (MAX_TERMS_CACHING); + m = GNUNET_TIME_absolute_to_timestamp (a); + TALER_MHD_get_date_string (m.abs_time, + dat); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Setting '%s' header to '%s'\n", + MHD_HTTP_HEADER_EXPIRES, + dat); + if (NULL != legal) + { + const char *etag; + + etag = MHD_lookup_connection_value (conn, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_IF_NONE_MATCH); + if ( (NULL != etag) && + (NULL != legal->terms_etag) && + (0 == strcasecmp (etag, + legal->terms_etag)) ) + { + MHD_RESULT ret; + + resp = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + TALER_MHD_add_global_headers (resp); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_EXPIRES, + dat)); + + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_ETAG, + legal->terms_etag)); + ret = MHD_queue_response (conn, + MHD_HTTP_NOT_MODIFIED, + resp); + GNUNET_break (MHD_YES == ret); + MHD_destroy_response (resp); + return ret; + } + } + + t = NULL; + langs = NULL; + if (NULL != legal) + { + const char *mime; + const char *lang; + + mime = MHD_lookup_connection_value (conn, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT); + if (NULL == mime) + mime = "text/plain"; + lang = MHD_lookup_connection_value (conn, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT_LANGUAGE); + if (NULL == lang) + lang = "en"; + /* Find best match: must match mime type (if possible), and if + mime type matches, ideally also language */ + for (struct Terms *p = legal->terms_head; + NULL != p; + p = p->next) + { + if ( (NULL == t) || + (TALER_MHD_xmime_matches (mime, + p->mime_type)) ) + { + if (NULL == langs) + { + langs = GNUNET_strdup (p->language); + } + else + { + char *tmp = langs; + + GNUNET_asprintf (&langs, + "%s,%s", + tmp, + p->language); + GNUNET_free (tmp); + } + if ( (NULL == t) || + (! TALER_MHD_xmime_matches (mime, + t->mime_type)) || + (TALER_language_matches (lang, + p->language) > + TALER_language_matches (lang, + t->language) ) ) + t = p; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Best match for %s/%s: %s / %s\n", + lang, + mime, + (NULL != t) ? t->mime_type : "<none>", + (NULL != t) ? t->language : "<none>"); + } + + if (NULL == t) + { + /* Default terms of service if none are configured */ + static struct Terms none = { + .mime_type = "text/plain", + .terms = "not configured", + .language = "en", + .terms_size = strlen ("not configured") + }; + + t = &none; + } + + /* try to compress the response */ + resp = NULL; + if (MHD_YES == + TALER_MHD_can_compress (conn)) + { + resp = MHD_create_response_from_buffer (t->compressed_terms_size, + t->compressed_terms, + MHD_RESPMEM_PERSISTENT); + if (MHD_NO == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_ENCODING, + "deflate")) + { + GNUNET_break (0); + MHD_destroy_response (resp); + resp = NULL; + } + } + if (NULL == resp) + { + /* could not generate compressed response, return uncompressed */ + resp = MHD_create_response_from_buffer (t->terms_size, + (void *) t->terms, + MHD_RESPMEM_PERSISTENT); + } + TALER_MHD_add_global_headers (resp); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_EXPIRES, + dat)); + if (NULL != langs) + { + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + "Avail-Languages", + langs)); + GNUNET_free (langs); + } + /* Set cache control headers: our response varies depending on these headers */ + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_VARY, + MHD_HTTP_HEADER_ACCEPT_LANGUAGE "," + MHD_HTTP_HEADER_ACCEPT "," + MHD_HTTP_HEADER_ACCEPT_ENCODING)); + /* Information is always public, revalidate after 10 days */ + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CACHE_CONTROL, + "public max-age=864000")); + if (NULL != legal) + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_ETAG, + legal->terms_etag)); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + t->mime_type)); + { + MHD_RESULT ret; + + ret = MHD_queue_response (conn, + MHD_HTTP_OK, + resp); + MHD_destroy_response (resp); + return ret; + } +} + + +/** + * Load all the terms of service from @a path under language @a lang + * from file @a name + * + * @param[in,out] legal where to write the result + * @param path where the terms are found + * @param lang which language directory to crawl + * @param name specific file to access + */ +static void +load_terms (struct TALER_MHD_Legal *legal, + const char *path, + const char *lang, + const char *name) +{ + static struct MimeMap + { + const char *ext; + const char *mime; + unsigned int priority; + } mm[] = { + { .ext = ".txt", .mime = "text/plain", .priority = 150 }, + { .ext = ".html", .mime = "text/html", .priority = 100 }, + { .ext = ".htm", .mime = "text/html", .priority = 99 }, + { .ext = ".md", .mime = "text/markdown", .priority = 50 }, + { .ext = ".pdf", .mime = "application/pdf", .priority = 25 }, + { .ext = ".jpg", .mime = "image/jpeg" }, + { .ext = ".jpeg", .mime = "image/jpeg" }, + { .ext = ".png", .mime = "image/png" }, + { .ext = ".gif", .mime = "image/gif" }, + { .ext = ".epub", .mime = "application/epub+zip", .priority = 10 }, + { .ext = ".xml", .mime = "text/xml", .priority = 10 }, + { .ext = NULL, .mime = NULL } + }; + const char *ext = strrchr (name, '.'); + const char *mime; + unsigned int priority; + + if (NULL == ext) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unsupported file `%s' in directory `%s/%s': lacks extension\n", + name, + path, + lang); + return; + } + if ( (NULL == legal->terms_etag) || + (0 != strncmp (legal->terms_etag, + name, + ext - name - 1)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Filename `%s' does not match Etag `%s' in directory `%s/%s'. Ignoring it.\n", + name, + legal->terms_etag, + path, + lang); + return; + } + mime = NULL; + for (unsigned int i = 0; NULL != mm[i].ext; i++) + if (0 == strcasecmp (mm[i].ext, + ext)) + { + mime = mm[i].mime; + priority = mm[i].priority; + break; + } + if (NULL == mime) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unsupported file extension `%s' of file `%s' in directory `%s/%s'\n", + ext, + name, + path, + lang); + return; + } + /* try to read the file with the terms of service */ + { + struct stat st; + char *fn; + int fd; + + GNUNET_asprintf (&fn, + "%s/%s/%s", + path, + lang, + name); + fd = open (fn, O_RDONLY); + if (-1 == fd) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "open", + fn); + GNUNET_free (fn); + return; + } + if (0 != fstat (fd, &st)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "fstat", + fn); + GNUNET_break (0 == close (fd)); + GNUNET_free (fn); + return; + } + if (SIZE_MAX < ((unsigned long long) st.st_size)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "fstat-size", + fn); + GNUNET_break (0 == close (fd)); + GNUNET_free (fn); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Loading legal information from file `%s'\n", + fn); + { + void *buf; + size_t bsize; + + bsize = (size_t) st.st_size; + buf = mmap (NULL, + bsize, + PROT_READ, + MAP_SHARED, + fd, + 0); + if (MAP_FAILED == buf) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "mmap", + fn); + GNUNET_break (0 == close (fd)); + GNUNET_free (fn); + return; + } + GNUNET_break (0 == close (fd)); + GNUNET_free (fn); + + /* insert into global list of terms of service */ + { + struct Terms *t; + + t = GNUNET_new (struct Terms); + t->mime_type = mime; + t->terms = buf; + t->language = GNUNET_strdup (lang); + t->terms_size = bsize; + t->priority = priority; + buf = GNUNET_memdup (t->terms, + t->terms_size); + if (TALER_MHD_body_compress (&buf, + &bsize)) + { + t->compressed_terms = buf; + t->compressed_terms_size = bsize; + } + else + { + GNUNET_free (buf); + } + { + struct Terms *prev = NULL; + + for (struct Terms *pos = legal->terms_head; + NULL != pos; + pos = pos->next) + { + if (pos->priority < priority) + break; + prev = pos; + } + GNUNET_CONTAINER_DLL_insert_after (legal->terms_head, + legal->terms_tail, + prev, + t); + } + } + } + } +} + + +/** + * Load all the terms of service from @a path under language @a lang. + * + * @param[in,out] legal where to write the result + * @param path where the terms are found + * @param lang which language directory to crawl + */ +static void +load_language (struct TALER_MHD_Legal *legal, + const char *path, + const char *lang) +{ + char *dname; + DIR *d; + + GNUNET_asprintf (&dname, + "%s/%s", + path, + lang); + d = opendir (dname); + if (NULL == d) + { + GNUNET_free (dname); + return; + } + for (struct dirent *de = readdir (d); + NULL != de; + de = readdir (d)) + { + const char *fn = de->d_name; + + if (fn[0] == '.') + continue; + load_terms (legal, + path, + lang, + fn); + } + GNUNET_break (0 == closedir (d)); + GNUNET_free (dname); +} + + +struct TALER_MHD_Legal * +TALER_MHD_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + const char *diroption, + const char *tagoption) +{ + struct TALER_MHD_Legal *legal; + char *path; + DIR *d; + + legal = GNUNET_new (struct TALER_MHD_Legal); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + tagoption, + &legal->terms_etag)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + section, + tagoption); + GNUNET_free (legal); + return NULL; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + section, + diroption, + &path)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + section, + diroption); + GNUNET_free (legal->terms_etag); + GNUNET_free (legal); + return NULL; + } + d = opendir (path); + if (NULL == d) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING, + section, + diroption, + "Could not open directory"); + GNUNET_free (legal->terms_etag); + GNUNET_free (legal); + GNUNET_free (path); + return NULL; + } + for (struct dirent *de = readdir (d); + NULL != de; + de = readdir (d)) + { + const char *lang = de->d_name; + + if (lang[0] == '.') + continue; + if (0 == strcmp (lang, + "locale")) + continue; + load_language (legal, + path, + lang); + } + GNUNET_break (0 == closedir (d)); + GNUNET_free (path); + return legal; +} + + +void +TALER_MHD_legal_free (struct TALER_MHD_Legal *legal) +{ + struct Terms *t; + if (NULL == legal) + return; + while (NULL != (t = legal->terms_head)) + { + GNUNET_free (t->language); + GNUNET_free (t->compressed_terms); + if (0 != munmap (t->terms, t->terms_size)) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "munmap"); + GNUNET_CONTAINER_DLL_remove (legal->terms_head, + legal->terms_tail, + t); + GNUNET_free (t); + } + GNUNET_free (legal->terms_etag); + GNUNET_free (legal); +} diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c @@ -0,0 +1,444 @@ +/* + This file is part of TALER + Copyright (C) 2014--2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd_parsing.c + * @brief functions to parse incoming requests (MHD arguments and JSON snippets) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_post_json (struct MHD_Connection *connection, + void **con_cls, + const char *upload_data, + size_t *upload_data_size, + json_t **json) +{ + enum GNUNET_JSON_PostResult pr; + + pr = GNUNET_JSON_post_parser (TALER_MHD_REQUEST_BUFFER_MAX, + connection, + con_cls, + upload_data, + upload_data_size, + json); + switch (pr) + { + case GNUNET_JSON_PR_OUT_OF_MEMORY: + GNUNET_break (NULL == *json); + return (MHD_NO == + TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_PARSER_OUT_OF_MEMORY, + NULL)) ? GNUNET_SYSERR : GNUNET_NO; + + case GNUNET_JSON_PR_CONTINUE: + GNUNET_break (NULL == *json); + return GNUNET_YES; + case GNUNET_JSON_PR_REQUEST_TOO_LARGE: + GNUNET_break (NULL == *json); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Closing connection, upload too large\n"); + return GNUNET_SYSERR; + case GNUNET_JSON_PR_JSON_INVALID: + GNUNET_break (NULL == *json); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_JSON_INVALID, + NULL)) + ? GNUNET_NO : GNUNET_SYSERR; + case GNUNET_JSON_PR_SUCCESS: + GNUNET_break (NULL != *json); + return GNUNET_YES; + } + /* this should never happen */ + GNUNET_break (0); + return GNUNET_SYSERR; +} + + +void +TALER_MHD_parse_post_cleanup_callback (void *con_cls) +{ + GNUNET_JSON_post_parser_cleanup (con_cls); +} + + +/** + * 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, + kind, + param_name); + if (NULL == str) + { + *present = false; + return GNUNET_OK; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (str, + strlen (str), + out_data, + out_size)) + return (MHD_NO == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + 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; +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_number (struct MHD_Connection *connection, + const char *name, + uint64_t *off) +{ + const char *ts; + char dummy; + unsigned long long num; + + ts = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + name); + if (NULL == ts) + return GNUNET_OK; + if (1 != + sscanf (ts, + "%llu%c", + &num, + &dummy)) + { + MHD_RESULT mret; + + GNUNET_break_op (0); + mret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + name); + return (MHD_YES == mret) + ? GNUNET_NO + : GNUNET_SYSERR; + } + *off = (uint64_t) num; + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_json_data (struct MHD_Connection *connection, + const json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + enum GNUNET_GenericReturnValue ret; + const char *error_json_name; + unsigned int error_line; + + ret = GNUNET_JSON_parse (root, + spec, + &error_json_name, + &error_line); + if (GNUNET_SYSERR == ret) + { + if (NULL == error_json_name) + error_json_name = "<no field>"; + ret = (MHD_YES == + TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_BAD_REQUEST, + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + TALER_EC_GENERIC_JSON_INVALID)), + GNUNET_JSON_pack_uint64 ("code", + TALER_EC_GENERIC_JSON_INVALID), + GNUNET_JSON_pack_string ("field", + error_json_name), + GNUNET_JSON_pack_uint64 ("line", + error_line))) + ? GNUNET_NO : GNUNET_SYSERR; + return ret; + } + return GNUNET_YES; +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_internal_json_data (struct MHD_Connection *connection, + const json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + enum GNUNET_GenericReturnValue ret; + const char *error_json_name; + unsigned int error_line; + + ret = GNUNET_JSON_parse (root, + spec, + &error_json_name, + &error_line); + if (GNUNET_SYSERR == ret) + { + if (NULL == error_json_name) + error_json_name = "<no field>"; + ret = (MHD_YES == + TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)), + GNUNET_JSON_pack_uint64 ("code", + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE), + GNUNET_JSON_pack_string ("field", + error_json_name), + GNUNET_JSON_pack_uint64 ("line", + error_line))) + ? GNUNET_NO : GNUNET_SYSERR; + return ret; + } + return GNUNET_YES; +} + + +enum GNUNET_GenericReturnValue +TALER_MHD_parse_json_array (struct MHD_Connection *connection, + const json_t *root, + struct GNUNET_JSON_Specification *spec, + ...) +{ + enum GNUNET_GenericReturnValue ret; + const char *error_json_name; + unsigned int error_line; + va_list ap; + json_int_t dim; + + va_start (ap, spec); + dim = 0; + while ( (-1 != (ret = va_arg (ap, int))) && + (NULL != root) ) + { + dim++; + root = json_array_get (root, ret); + } + va_end (ap); + if (NULL == root) + { + ret = (MHD_YES == + TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_BAD_REQUEST, + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + TALER_EC_GENERIC_JSON_INVALID)), + GNUNET_JSON_pack_uint64 ("code", + TALER_EC_GENERIC_JSON_INVALID), + GNUNET_JSON_pack_string ("detail", + "expected array"), + GNUNET_JSON_pack_uint64 ("dimension", + dim))) + ? GNUNET_NO : GNUNET_SYSERR; + return ret; + } + ret = GNUNET_JSON_parse (root, + spec, + &error_json_name, + &error_line); + if (GNUNET_SYSERR == ret) + { + if (NULL == error_json_name) + error_json_name = "<no field>"; + ret = (MHD_YES == + TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_BAD_REQUEST, + GNUNET_JSON_pack_string ("detail", + error_json_name), + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + TALER_EC_GENERIC_JSON_INVALID)), + GNUNET_JSON_pack_uint64 ("code", + TALER_EC_GENERIC_JSON_INVALID), + GNUNET_JSON_pack_uint64 ("line", + error_line))) + ? GNUNET_NO : GNUNET_SYSERR; + return ret; + } + return GNUNET_YES; +} + + +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 0 + /* wallet currently doesn't always send content-length! */ + 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; +#endif + } + 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 */ diff --git a/src/mhd/mhd_responses.c b/src/mhd/mhd_responses.c @@ -0,0 +1,550 @@ +/* + This file is part of TALER + Copyright (C) 2014-2021 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd_responses.c + * @brief API for generating HTTP replies + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <zlib.h> +#include "taler_util.h" +#include "taler_mhd_lib.h" + + +/** + * Global options for response generation. + */ +static enum TALER_MHD_GlobalOptions TM_go; + + +void +TALER_MHD_setup (enum TALER_MHD_GlobalOptions go) +{ + TM_go = go; +} + + +void +TALER_MHD_add_global_headers (struct MHD_Response *response) +{ + if (0 != (TM_go & TALER_MHD_GO_FORCE_CONNECTION_CLOSE)) + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONNECTION, + "close")); + /* The wallet, operating from a background page, needs CORS to + be disabled otherwise browsers block access. */ + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, + "*")); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + /* Not available as MHD constant yet */ + "Access-Control-Expose-Headers", + "*")); +} + + +MHD_RESULT +TALER_MHD_can_compress (struct MHD_Connection *connection) +{ + const char *ae; + const char *de; + + if (0 != (TM_go & TALER_MHD_GO_DISABLE_COMPRESSION)) + return MHD_NO; + ae = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT_ENCODING); + if (NULL == ae) + return MHD_NO; + if (0 == strcmp (ae, + "*")) + return MHD_YES; + de = strstr (ae, + "deflate"); + if (NULL == de) + return MHD_NO; + if ( ( (de == ae) || + (de[-1] == ',') || + (de[-1] == ' ') ) && + ( (de[strlen ("deflate")] == '\0') || + (de[strlen ("deflate")] == ',') || + (de[strlen ("deflate")] == ';') ) ) + return MHD_YES; + return MHD_NO; +} + + +MHD_RESULT +TALER_MHD_body_compress (void **buf, + size_t *buf_size) +{ + Bytef *cbuf; + uLongf cbuf_size; + MHD_RESULT ret; + + cbuf_size = compressBound (*buf_size); + cbuf = malloc (cbuf_size); + if (NULL == cbuf) + return MHD_NO; + ret = compress (cbuf, + &cbuf_size, + (const Bytef *) *buf, + *buf_size); + if ( (Z_OK != ret) || + (cbuf_size >= *buf_size) ) + { + /* compression failed */ + free (cbuf); + return MHD_NO; + } + free (*buf); + *buf = (void *) cbuf; + *buf_size = (size_t) cbuf_size; + return MHD_YES; +} + + +struct MHD_Response * +TALER_MHD_make_json (const json_t *json) +{ + struct MHD_Response *response; + char *json_str; + + json_str = json_dumps (json, + JSON_INDENT (2)); + if (NULL == json_str) + { + GNUNET_break (0); + return NULL; + } + response = MHD_create_response_from_buffer (strlen (json_str), + json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == response) + { + free (json_str); + GNUNET_break (0); + return NULL; + } + TALER_MHD_add_global_headers (response); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json")); + return response; +} + + +struct MHD_Response * +TALER_MHD_make_json_steal (json_t *json) +{ + struct MHD_Response *res; + + res = TALER_MHD_make_json (json); + json_decref (json); + return res; +} + + +MHD_RESULT +TALER_MHD_reply_json (struct MHD_Connection *connection, + const json_t *json, + unsigned int response_code) +{ + struct MHD_Response *response; + void *json_str; + size_t json_len; + MHD_RESULT is_compressed; + + json_str = json_dumps (json, + JSON_INDENT (2)); + if (NULL == json_str) + { + /** + * This log helps to figure out which + * function called this one and assert-failed. + */ + TALER_LOG_ERROR ("Aborting json-packing for HTTP code: %u\n", + response_code); + + GNUNET_assert (0); + return MHD_NO; + } + json_len = strlen (json_str); + /* try to compress the body */ + is_compressed = MHD_NO; + if (MHD_YES == + TALER_MHD_can_compress (connection)) + is_compressed = TALER_MHD_body_compress (&json_str, + &json_len); + response = MHD_create_response_from_buffer (json_len, + json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == response) + { + free (json_str); + GNUNET_break (0); + return MHD_NO; + } + TALER_MHD_add_global_headers (response); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json")); + if (MHD_YES == is_compressed) + { + /* Need to indicate to client that body is compressed */ + if (MHD_NO == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_ENCODING, + "deflate")) + { + GNUNET_break (0); + MHD_destroy_response (response); + return MHD_NO; + } + } + + { + MHD_RESULT ret; + + ret = MHD_queue_response (connection, + response_code, + response); + MHD_destroy_response (response); + return ret; + } +} + + +MHD_RESULT +TALER_MHD_reply_json_steal (struct MHD_Connection *connection, + json_t *json, + unsigned int response_code) +{ + MHD_RESULT ret; + + ret = TALER_MHD_reply_json (connection, + json, + response_code); + json_decref (json); + return ret; +} + + +MHD_RESULT +TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection) +{ + struct MHD_Response *response; + + response = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + return MHD_NO; + /* This adds the Access-Control-Allow-Origin header. + * All endpoints of the exchange allow CORS. */ + TALER_MHD_add_global_headers (response); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + /* Not available as MHD constant yet */ + "Access-Control-Allow-Headers", + "*")); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + /* Not available as MHD constant yet */ + "Access-Control-Allow-Methods", + "*")); + { + MHD_RESULT ret; + + ret = MHD_queue_response (connection, + MHD_HTTP_NO_CONTENT, + response); + MHD_destroy_response (response); + return ret; + } +} + + +MHD_RESULT +TALER_MHD_reply_json_pack (struct MHD_Connection *connection, + unsigned int response_code, + const char *fmt, + ...) +{ + json_t *json; + json_error_t jerror; + + { + va_list argp; + + va_start (argp, + fmt); + json = json_vpack_ex (&jerror, + 0, + fmt, + argp); + va_end (argp); + } + + if (NULL == json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to pack JSON with format `%s': %s\n", + fmt, + jerror.text); + GNUNET_break (0); + return MHD_NO; + } + + { + MHD_RESULT ret; + + ret = TALER_MHD_reply_json (connection, + json, + response_code); + json_decref (json); + return ret; + } +} + + +struct MHD_Response * +TALER_MHD_make_json_pack (const char *fmt, + ...) +{ + json_t *json; + json_error_t jerror; + + { + va_list argp; + + va_start (argp, fmt); + json = json_vpack_ex (&jerror, + 0, + fmt, + argp); + va_end (argp); + } + + if (NULL == json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to pack JSON with format `%s': %s\n", + fmt, + jerror.text); + GNUNET_break (0); + return NULL; + } + + { + struct MHD_Response *response; + + response = TALER_MHD_make_json (json); + json_decref (json); + return response; + } +} + + +struct MHD_Response * +TALER_MHD_make_error (enum TALER_ErrorCode ec, + const char *detail) +{ + return TALER_MHD_MAKE_JSON_PACK ( + TALER_MHD_PACK_EC (ec), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("detail", detail))); +} + + +MHD_RESULT +TALER_MHD_reply_with_error (struct MHD_Connection *connection, + unsigned int http_status, + enum TALER_ErrorCode ec, + const char *detail) +{ + return TALER_MHD_REPLY_JSON_PACK ( + connection, + http_status, + TALER_MHD_PACK_EC (ec), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("detail", detail))); +} + + +MHD_RESULT +TALER_MHD_reply_with_ec (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *detail) +{ + unsigned int hc = TALER_ErrorCode_get_http_status (ec); + + if ( (0 == hc) || + (UINT_MAX == hc) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid Taler error code %d provided for response!\n", + (int) ec); + hc = MHD_HTTP_INTERNAL_SERVER_ERROR; + } + return TALER_MHD_reply_with_error (connection, + hc, + ec, + detail); +} + + +MHD_RESULT +TALER_MHD_reply_request_too_large (struct MHD_Connection *connection) +{ + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT, + NULL); +} + + +MHD_RESULT +TALER_MHD_reply_agpl (struct MHD_Connection *connection, + const char *url) +{ + const char *agpl = + "This server is licensed under the Affero GPL. You will now be redirected to the source code."; + struct MHD_Response *response; + + response = MHD_create_response_from_buffer (strlen (agpl), + (void *) agpl, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TALER_MHD_add_global_headers (response); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/plain")); + if (MHD_NO == + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + url)) + { + GNUNET_break (0); + MHD_destroy_response (response); + return MHD_NO; + } + + { + MHD_RESULT ret; + + ret = MHD_queue_response (connection, + MHD_HTTP_FOUND, + response); + MHD_destroy_response (response); + return ret; + } +} + + +MHD_RESULT +TALER_MHD_reply_static (struct MHD_Connection *connection, + unsigned int http_status, + const char *mime_type, + const char *body, + size_t body_size) +{ + struct MHD_Response *response; + + response = MHD_create_response_from_buffer (body_size, + (void *) body, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TALER_MHD_add_global_headers (response); + if (NULL != mime_type) + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + mime_type)); + { + MHD_RESULT ret; + + ret = MHD_queue_response (connection, + http_status, + response); + MHD_destroy_response (response); + return ret; + } +} + + +void +TALER_MHD_get_date_string (struct GNUNET_TIME_Absolute at, + char date[128]) +{ + static const char *const days[] = + { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + static const char *const mons[] = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", + "Nov", "Dec"}; + struct tm now; + time_t t; +#if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ + ! defined(HAVE_GMTIME_R) + struct tm*pNow; +#endif + + date[0] = 0; + t = (time_t) (at.abs_value_us / 1000LL / 1000LL); +#if defined(HAVE_C11_GMTIME_S) + if (NULL == gmtime_s (&t, &now)) + return; +#elif defined(HAVE_W32_GMTIME_S) + if (0 != gmtime_s (&now, &t)) + return; +#elif defined(HAVE_GMTIME_R) + if (NULL == gmtime_r (&t, &now)) + return; +#else + pNow = gmtime (&t); + if (NULL == pNow) + return; + now = *pNow; +#endif + sprintf (date, + "%3s, %02u %3s %04u %02u:%02u:%02u GMT", + days[now.tm_wday % 7], + (unsigned int) now.tm_mday, + mons[now.tm_mon % 12], + (unsigned int) (1900 + now.tm_year), + (unsigned int) now.tm_hour, + (unsigned int) now.tm_min, + (unsigned int) now.tm_sec); +} + + +/* end of mhd_responses.c */ diff --git a/src/mhd/mhd_run.c b/src/mhd/mhd_run.c @@ -0,0 +1,175 @@ +/* + This file is part of TALER + Copyright (C) 2019-2021 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd_run.c + * @brief API for running an MHD daemon with the + * GNUnet scheduler + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include "taler_util.h" +#include "taler_mhd_lib.h" + + +/** + * Set to true if we should immediately MHD_run() again. + */ +static bool triggered; + +/** + * Task running the HTTP server. + */ +static struct GNUNET_SCHEDULER_Task *mhd_task; + +/** + * The MHD daemon we are running. + */ +static struct MHD_Daemon *mhd; + + +/** + * Function that queries MHD's select sets and + * starts the task waiting for them. + */ +static struct GNUNET_SCHEDULER_Task * +prepare_daemon (void); + + +/** + * Call MHD to process pending requests and then go back + * and schedule the next run. + * + * @param cls NULL + */ +static void +run_daemon (void *cls) +{ + (void) cls; + mhd_task = NULL; + do { + triggered = false; + GNUNET_assert (MHD_YES == + MHD_run (mhd)); + } while (triggered); + mhd_task = prepare_daemon (); +} + + +/** + * Function that queries MHD's select sets and starts the task waiting for + * them. + * + * @return task handle for the MHD task. + */ +static struct GNUNET_SCHEDULER_Task * +prepare_daemon (void) +{ + struct GNUNET_SCHEDULER_Task *ret; + fd_set rs; + fd_set ws; + fd_set es; + struct GNUNET_NETWORK_FDSet *wrs; + struct GNUNET_NETWORK_FDSet *wws; + int max; + MHD_UNSIGNED_LONG_LONG timeout; + int haveto; + struct GNUNET_TIME_Relative tv; + + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + wrs = GNUNET_NETWORK_fdset_create (); + wws = GNUNET_NETWORK_fdset_create (); + max = -1; + GNUNET_assert (MHD_YES == + MHD_get_fdset (mhd, + &rs, + &ws, + &es, + &max)); + haveto = MHD_get_timeout (mhd, + &timeout); + if (haveto == MHD_YES) + tv = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + timeout); + else + tv = GNUNET_TIME_UNIT_FOREVER_REL; + GNUNET_NETWORK_fdset_copy_native (wrs, + &rs, + max + 1); + GNUNET_NETWORK_fdset_copy_native (wws, + &ws, + max + 1); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding run_daemon select task\n"); + ret = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH, + tv, + wrs, + wws, + &run_daemon, + NULL); + GNUNET_NETWORK_fdset_destroy (wrs); + GNUNET_NETWORK_fdset_destroy (wws); + return ret; +} + + +void +TALER_MHD_daemon_start (struct MHD_Daemon *daemon) +{ + GNUNET_assert (NULL == mhd); + mhd = daemon; + mhd_task = prepare_daemon (); +} + + +struct MHD_Daemon * +TALER_MHD_daemon_stop (void) +{ + struct MHD_Daemon *ret; + + if (NULL != mhd_task) + { + GNUNET_SCHEDULER_cancel (mhd_task); + mhd_task = NULL; + } + ret = mhd; + mhd = NULL; + return ret; +} + + +void +TALER_MHD_daemon_trigger (void) +{ + if (NULL != mhd_task) + { + GNUNET_SCHEDULER_cancel (mhd_task); + mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon, + NULL); + } + else + { + triggered = true; + } +} + + +/* end of mhd_run.c */ diff --git a/src/pq/Makefile.am b/src/pq/Makefile.am @@ -0,0 +1,42 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS) $(POSTGRESQL_CPPFLAGS) + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libtalerpq.la + +libtalerpq_la_SOURCES = \ + pq_common.h pq_common.c \ + pq_query_helper.c \ + pq_result_helper.c +libtalerpq_la_LIBADD = \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil -ljansson \ + -lgnunetpq \ + -lpq \ + $(XLIB) +libtalerpq_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + -version-info 0:0:0 \ + -no-undefined + +check_PROGRAMS= \ + test_pq + +TESTS = \ + $(check_PROGRAMS) + +test_pq_SOURCES = \ + test_pq.c +test_pq_LDADD = \ + libtalerpq.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetpq \ + -lgnunetutil \ + -ljansson \ + -lpq \ + $(XLIB) diff --git a/src/pq/pq_common.c b/src/pq/pq_common.c @@ -0,0 +1,68 @@ +/* + 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file pq/pq_common.c + * @brief common defines for the pq functions + * @author Özgür Kesim + */ +#include "platform.h" +#include "pq_common.h" + +struct TALER_PQ_AmountP +TALER_PQ_make_taler_pq_amount_ ( + const struct TALER_Amount *amount, + uint32_t oid_v, + uint32_t oid_f) +{ + struct TALER_PQ_AmountP rval = { + .cnt = htonl (2), + .oid_v = htonl (oid_v), + .oid_f = htonl (oid_f), + .sz_v = htonl (sizeof((amount)->value)), + .sz_f = htonl (sizeof((amount)->fraction)), + .v = GNUNET_htonll ((amount)->value), + .f = htonl ((amount)->fraction) + }; + + return rval; +} + + +size_t +TALER_PQ_make_taler_pq_amount_currency_ ( + const struct TALER_Amount *amount, + uint32_t oid_v, + uint32_t oid_f, + uint32_t oid_c, + struct TALER_PQ_AmountCurrencyP *rval) +{ + size_t clen = strlen (amount->currency); + + GNUNET_assert (clen < TALER_CURRENCY_LEN); + rval->cnt = htonl (3); + rval->oid_v = htonl (oid_v); + rval->oid_f = htonl (oid_f); + rval->oid_c = htonl (oid_c); + rval->sz_v = htonl (sizeof(amount->value)); + rval->sz_f = htonl (sizeof(amount->fraction)); + rval->sz_c = htonl (clen); + rval->v = GNUNET_htonll (amount->value); + rval->f = htonl (amount->fraction); + memcpy (rval->c, + amount->currency, + clen); + return sizeof (*rval) - TALER_CURRENCY_LEN + clen; +} diff --git a/src/pq/pq_common.h b/src/pq/pq_common.h @@ -0,0 +1,125 @@ +/* + 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file pq/pq_common.h + * @brief common defines for the pq functions + * @author Özgür Kesim + */ +#ifndef TALER_PQ_COMMON_H_ +#define TALER_PQ_COMMON_H_ + +#include "taler_util.h" + +/** + * Internal types that are supported as TALER-exchange-specific array types. + * + * To support a new type, + * 1. add a new entry into this list, + * 2. for query-support, implement the size calculation and memory copying in + * qconv_array() accordingly, in pq_query_helper.c + * 3. provide a query-API for arrays of the type, by calling + * query_param_array_generic with the appropriate parameters, + * in pq_query_helper.c + * 4. for result-support, implement memory copying by adding another case + * to extract_array_generic, in pq_result_helper.c + * 5. provide a result-spec-API for arrays of the type, + * in pq_result_helper.c + * 6. expose the API's in taler_pq_lib.h + */ +enum TALER_PQ_ArrayType +{ + TALER_PQ_array_of_blinded_denom_sig, + TALER_PQ_array_of_blinded_coin_hash, + TALER_PQ_array_of_denom_hash, + /** + * Amounts *without* currency. + */ + TALER_PQ_array_of_amount, + TALER_PQ_array_of_MAX, /* must be last */ +}; + +/** + * Memory representation of an taler amount record for Postgres. + * + * All values need to be in network-byte-order. + */ +struct TALER_PQ_AmountP +{ + uint32_t cnt; /* # elements in the tuple (== 2) */ + uint32_t oid_v; /* oid of .v */ + uint32_t sz_v; /* size of .v */ + uint64_t v; /* value */ + uint32_t oid_f; /* oid of .f */ + uint32_t sz_f; /* size of .f */ + uint32_t f; /* fraction */ +} __attribute__((packed)); + + +/** + * Memory representation of an taler amount record with currency for Postgres. + * + * All values need to be in network-byte-order. + */ +struct TALER_PQ_AmountCurrencyP +{ + uint32_t cnt; /* # elements in the tuple (== 3) */ + uint32_t oid_v; /* oid of .v */ + uint32_t sz_v; /* size of .v */ + uint64_t v; /* value */ + uint32_t oid_f; /* oid of .f */ + uint32_t sz_f; /* size of .f */ + uint32_t f; /* fraction */ + uint32_t oid_c; /* oid of .c */ + uint32_t sz_c; /* size of .c */ + uint8_t c[TALER_CURRENCY_LEN]; /* currency */ +} __attribute__((packed)); + + +/** + * Create a `struct TALER_PQ_AmountP` for initialization + * + * @param amount amount of type `struct TALER_Amount *` + * @param oid_v OID of the INT8 type in postgres + * @param oid_f OID of the INT4 type in postgres + */ +struct TALER_PQ_AmountP +TALER_PQ_make_taler_pq_amount_ ( + const struct TALER_Amount *amount, + uint32_t oid_v, + uint32_t oid_f); + + +/** + * Create a `struct TALER_PQ_AmountCurrencyP` for initialization + * + * @param amount amount of type `struct TALER_Amount *` + * @param oid_v OID of the INT8 type in postgres + * @param oid_f OID of the INT4 type in postgres + * @param oid_c OID of the TEXT type in postgres + * @param[out] rval set to encoded @a amount + * @return actual (useful) size of @a rval for Postgres + */ +size_t +TALER_PQ_make_taler_pq_amount_currency_ ( + const struct TALER_Amount *amount, + uint32_t oid_v, + uint32_t oid_f, + uint32_t oid_c, + struct TALER_PQ_AmountCurrencyP *rval); + + +#endif /* TALER_PQ_COMMON_H_ */ +/* end of pg/pq_common.h */ diff --git a/src/pq/pq_query_helper.c b/src/pq/pq_query_helper.c @@ -0,0 +1,1183 @@ +/* + 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file pq/pq_query_helper.c + * @brief helper functions for Taler-specific libpq (PostGres) interactions + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Florian Dold + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_pq_lib.h> +#include "taler_pq_lib.h" +#include "pq_common.h" + + +/** + * Function called to convert input amount into SQL parameter as tuple. + * + * @param cls closure + * @param data pointer to input argument, here a `struct TALER_Amount` + * @param data_len number of bytes in @a data (if applicable) + * @param[out] param_values SQL data to set + * @param[out] param_lengths SQL length data to set + * @param[out] param_formats SQL format data to set + * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays + * @param[out] scratch buffer for dynamic allocations (to be done via GNUNET_malloc() + * @param scratch_length number of entries left in @a scratch + * @return -1 on error, number of offsets used in @a scratch otherwise + */ +static int +qconv_amount_currency_tuple (void *cls, + const void *data, + size_t data_len, + void *param_values[], + int param_lengths[], + int param_formats[], + unsigned int param_length, + void *scratch[], + unsigned int scratch_length) +{ + struct GNUNET_PQ_Context *db = cls; + const struct TALER_Amount *amount = data; + size_t sz; + + GNUNET_assert (NULL != db); + GNUNET_assert (NULL != amount); + GNUNET_assert (1 == param_length); + GNUNET_assert (1 <= scratch_length); + GNUNET_assert (sizeof (struct TALER_Amount) == data_len); + GNUNET_static_assert (sizeof(uint32_t) == sizeof(Oid)); + { + char *out; + Oid oid_v; + Oid oid_f; + Oid oid_c; + struct TALER_PQ_AmountCurrencyP d; + + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, + "int8", + &oid_v)); + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, + "int4", + &oid_f)); + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, + "varchar", + &oid_c)); + sz = TALER_PQ_make_taler_pq_amount_currency_ (amount, + oid_v, + oid_f, + oid_c, + &d); + out = GNUNET_malloc (sz); + memcpy (out, + &d, + sz); + scratch[0] = out; + } + + param_values[0] = scratch[0]; + param_lengths[0] = sz; + param_formats[0] = 1; + + return 1; +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_amount_with_currency ( + const struct GNUNET_PQ_Context *db, + const struct TALER_Amount *amount) +{ + struct GNUNET_PQ_QueryParam res = { + .conv_cls = (void *) db, + .conv = &qconv_amount_currency_tuple, + .data = amount, + .size = sizeof (*amount), + .num_params = 1, + }; + + return res; +} + + +/** + * Function called to convert input amount into SQL parameter as tuple. + * + * @param cls closure + * @param data pointer to input argument, here a `struct TALER_Amount` + * @param data_len number of bytes in @a data (if applicable) + * @param[out] param_values SQL data to set + * @param[out] param_lengths SQL length data to set + * @param[out] param_formats SQL format data to set + * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays + * @param[out] scratch buffer for dynamic allocations (to be done via GNUNET_malloc() + * @param scratch_length number of entries left in @a scratch + * @return -1 on error, number of offsets used in @a scratch otherwise + */ +static int +qconv_amount_tuple (void *cls, + const void *data, + size_t data_len, + void *param_values[], + int param_lengths[], + int param_formats[], + unsigned int param_length, + void *scratch[], + unsigned int scratch_length) +{ + struct GNUNET_PQ_Context *db = cls; + const struct TALER_Amount *amount = data; + size_t sz; + + GNUNET_assert (NULL != db); + GNUNET_assert (NULL != amount); + GNUNET_assert (1 == param_length); + GNUNET_assert (1 <= scratch_length); + GNUNET_assert (sizeof (struct TALER_Amount) == data_len); + GNUNET_static_assert (sizeof(uint32_t) == sizeof(Oid)); + { + char *out; + Oid oid_v; + Oid oid_f; + + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, + "int8", + &oid_v)); + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, + "int4", + &oid_f)); + + { + struct TALER_PQ_AmountP d + = TALER_PQ_make_taler_pq_amount_ (amount, + oid_v, + oid_f); + + sz = sizeof(d); + out = GNUNET_malloc (sz); + scratch[0] = out; + GNUNET_memcpy (out, + &d, + sizeof(d)); + } + } + + param_values[0] = scratch[0]; + param_lengths[0] = sz; + param_formats[0] = 1; + + return 1; +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_amount ( + const struct GNUNET_PQ_Context *db, + const struct TALER_Amount *amount) +{ + struct GNUNET_PQ_QueryParam res = { + .conv_cls = (void *) db, + .conv = &qconv_amount_tuple, + .data = amount, + .size = sizeof (*amount), + .num_params = 1, + }; + + return res; +} + + +/** + * Function called to convert input argument into SQL parameters. + * + * @param cls closure + * @param data pointer to input argument + * @param data_len number of bytes in @a data (if applicable) + * @param[out] param_values SQL data to set + * @param[out] param_lengths SQL length data to set + * @param[out] param_formats SQL format data to set + * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays + * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc() + * @param scratch_length number of entries left in @a scratch + * @return -1 on error, number of offsets used in @a scratch otherwise + */ +static int +qconv_denom_pub (void *cls, + const void *data, + size_t data_len, + void *param_values[], + int param_lengths[], + int param_formats[], + unsigned int param_length, + void *scratch[], + unsigned int scratch_length) +{ + const struct TALER_DenominationPublicKey *denom_pub = data; + size_t tlen; + size_t len; + uint32_t be[2]; + char *buf; + void *tbuf; + + (void) cls; + (void) data_len; + GNUNET_assert (1 == param_length); + GNUNET_assert (scratch_length > 0); + GNUNET_break (NULL == cls); + be[0] = htonl ((uint32_t) denom_pub->cipher); + be[1] = htonl (denom_pub->age_mask.bits); + switch (denom_pub->cipher) + { + case TALER_DENOMINATION_RSA: + tlen = GNUNET_CRYPTO_rsa_public_key_encode ( + denom_pub->details.rsa_public_key, + &tbuf); + break; + case TALER_DENOMINATION_CS: + tlen = sizeof (denom_pub->details.cs_public_key); + break; + default: + GNUNET_assert (0); + } + len = tlen + sizeof (be); + buf = GNUNET_malloc (len); + GNUNET_memcpy (buf, + be, + sizeof (be)); + switch (denom_pub->cipher) + { + case TALER_DENOMINATION_RSA: + GNUNET_memcpy (&buf[sizeof (be)], + tbuf, + tlen); + GNUNET_free (tbuf); + break; + case TALER_DENOMINATION_CS: + GNUNET_memcpy (&buf[sizeof (be)], + &denom_pub->details.cs_public_key, + tlen); + break; + default: + GNUNET_assert (0); + } + + scratch[0] = buf; + param_values[0] = (void *) buf; + param_lengths[0] = len; + param_formats[0] = 1; + return 1; +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_denom_pub ( + const struct TALER_DenominationPublicKey *denom_pub) +{ + struct GNUNET_PQ_QueryParam res = { + .conv = &qconv_denom_pub, + .data = denom_pub, + .num_params = 1 + }; + + return res; +} + + +/** + * Function called to convert input argument into SQL parameters. + * + * @param cls closure + * @param data pointer to input argument + * @param data_len number of bytes in @a data (if applicable) + * @param[out] param_values SQL data to set + * @param[out] param_lengths SQL length data to set + * @param[out] param_formats SQL format data to set + * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays + * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc() + * @param scratch_length number of entries left in @a scratch + * @return -1 on error, number of offsets used in @a scratch otherwise + */ +static int +qconv_denom_sig (void *cls, + const void *data, + size_t data_len, + void *param_values[], + int param_lengths[], + int param_formats[], + unsigned int param_length, + void *scratch[], + unsigned int scratch_length) +{ + const struct TALER_DenominationSignature *denom_sig = data; + size_t tlen; + size_t len; + uint32_t be[2]; + char *buf; + void *tbuf; + + (void) cls; + (void) data_len; + GNUNET_assert (1 == param_length); + GNUNET_assert (scratch_length > 0); + GNUNET_break (NULL == cls); + be[0] = htonl ((uint32_t) denom_sig->cipher); + be[1] = htonl (0x00); /* magic marker: unblinded */ + switch (denom_sig->cipher) + { + case TALER_DENOMINATION_RSA: + tlen = GNUNET_CRYPTO_rsa_signature_encode ( + denom_sig->details.rsa_signature, + &tbuf); + break; + case TALER_DENOMINATION_CS: + tlen = sizeof (denom_sig->details.cs_signature); + break; + default: + GNUNET_assert (0); + } + len = tlen + sizeof (be); + buf = GNUNET_malloc (len); + GNUNET_memcpy (buf, + &be, + sizeof (be)); + switch (denom_sig->cipher) + { + case TALER_DENOMINATION_RSA: + GNUNET_memcpy (&buf[sizeof (be)], + tbuf, + tlen); + GNUNET_free (tbuf); + break; + case TALER_DENOMINATION_CS: + GNUNET_memcpy (&buf[sizeof (be)], + &denom_sig->details.cs_signature, + tlen); + break; + default: + GNUNET_assert (0); + } + + scratch[0] = buf; + param_values[0] = (void *) buf; + param_lengths[0] = len; + param_formats[0] = 1; + return 1; +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_denom_sig ( + const struct TALER_DenominationSignature *denom_sig) +{ + struct GNUNET_PQ_QueryParam res = { + .conv = &qconv_denom_sig, + .data = denom_sig, + .num_params = 1 + }; + + return res; +} + + +/** + * Function called to convert input argument into SQL parameters. + * + * @param cls closure + * @param data pointer to input argument + * @param data_len number of bytes in @a data (if applicable) + * @param[out] param_values SQL data to set + * @param[out] param_lengths SQL length data to set + * @param[out] param_formats SQL format data to set + * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays + * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc() + * @param scratch_length number of entries left in @a scratch + * @return -1 on error, number of offsets used in @a scratch otherwise + */ +static int +qconv_blinded_denom_sig (void *cls, + const void *data, + size_t data_len, + void *param_values[], + int param_lengths[], + int param_formats[], + unsigned int param_length, + void *scratch[], + unsigned int scratch_length) +{ + const struct TALER_BlindedDenominationSignature *denom_sig = data; + size_t tlen; + size_t len; + uint32_t be[2]; + char *buf; + void *tbuf; + + (void) cls; + (void) data_len; + GNUNET_assert (1 == param_length); + GNUNET_assert (scratch_length > 0); + GNUNET_break (NULL == cls); + be[0] = htonl ((uint32_t) denom_sig->cipher); + be[1] = htonl (0x01); /* magic marker: blinded */ + switch (denom_sig->cipher) + { + case TALER_DENOMINATION_RSA: + tlen = GNUNET_CRYPTO_rsa_signature_encode ( + denom_sig->details.blinded_rsa_signature, + &tbuf); + break; + case TALER_DENOMINATION_CS: + tlen = sizeof (denom_sig->details.blinded_cs_answer); + break; + default: + GNUNET_assert (0); + } + len = tlen + sizeof (be); + buf = GNUNET_malloc (len); + GNUNET_memcpy (buf, + &be, + sizeof (be)); + switch (denom_sig->cipher) + { + case TALER_DENOMINATION_RSA: + GNUNET_memcpy (&buf[sizeof (be)], + tbuf, + tlen); + GNUNET_free (tbuf); + break; + case TALER_DENOMINATION_CS: + GNUNET_memcpy (&buf[sizeof (be)], + &denom_sig->details.blinded_cs_answer, + tlen); + break; + default: + GNUNET_assert (0); + } + + scratch[0] = buf; + param_values[0] = (void *) buf; + param_lengths[0] = len; + param_formats[0] = 1; + return 1; +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_blinded_denom_sig ( + const struct TALER_BlindedDenominationSignature *denom_sig) +{ + struct GNUNET_PQ_QueryParam res = { + .conv = &qconv_blinded_denom_sig, + .data = denom_sig, + .num_params = 1 + }; + + return res; +} + + +/** + * Function called to convert input argument into SQL parameters. + * + * @param cls closure + * @param data pointer to input argument + * @param data_len number of bytes in @a data (if applicable) + * @param[out] param_values SQL data to set + * @param[out] param_lengths SQL length data to set + * @param[out] param_formats SQL format data to set + * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays + * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc() + * @param scratch_length number of entries left in @a scratch + * @return -1 on error, number of offsets used in @a scratch otherwise + */ +static int +qconv_blinded_planchet (void *cls, + const void *data, + size_t data_len, + void *param_values[], + int param_lengths[], + int param_formats[], + unsigned int param_length, + void *scratch[], + unsigned int scratch_length) +{ + const struct TALER_BlindedPlanchet *bp = data; + size_t tlen; + size_t len; + uint32_t be[2]; + char *buf; + + (void) cls; + (void) data_len; + GNUNET_assert (1 == param_length); + GNUNET_assert (scratch_length > 0); + GNUNET_break (NULL == cls); + be[0] = htonl ((uint32_t) bp->cipher); + be[1] = htonl (0x0100); /* magic marker: blinded */ + switch (bp->cipher) + { + case TALER_DENOMINATION_RSA: + tlen = bp->details.rsa_blinded_planchet.blinded_msg_size; + break; + case TALER_DENOMINATION_CS: + tlen = sizeof (bp->details.cs_blinded_planchet); + break; + default: + GNUNET_assert (0); + } + len = tlen + sizeof (be); + buf = GNUNET_malloc (len); + GNUNET_memcpy (buf, + &be, + sizeof (be)); + switch (bp->cipher) + { + case TALER_DENOMINATION_RSA: + GNUNET_memcpy (&buf[sizeof (be)], + bp->details.rsa_blinded_planchet.blinded_msg, + tlen); + break; + case TALER_DENOMINATION_CS: + GNUNET_memcpy (&buf[sizeof (be)], + &bp->details.cs_blinded_planchet, + tlen); + break; + default: + GNUNET_assert (0); + } + scratch[0] = buf; + param_values[0] = (void *) buf; + param_lengths[0] = len; + param_formats[0] = 1; + return 1; +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_blinded_planchet ( + const struct TALER_BlindedPlanchet *bp) +{ + struct GNUNET_PQ_QueryParam res = { + .conv = &qconv_blinded_planchet, + .data = bp, + .num_params = 1 + }; + + return res; +} + + +/** + * Function called to convert input argument into SQL parameters. + * + * @param cls closure + * @param data pointer to input argument + * @param data_len number of bytes in @a data (if applicable) + * @param[out] param_values SQL data to set + * @param[out] param_lengths SQL length data to set + * @param[out] param_formats SQL format data to set + * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays + * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc() + * @param scratch_length number of entries left in @a scratch + * @return -1 on error, number of offsets used in @a scratch otherwise + */ +static int +qconv_exchange_withdraw_values (void *cls, + const void *data, + size_t data_len, + void *param_values[], + int param_lengths[], + int param_formats[], + unsigned int param_length, + void *scratch[], + unsigned int scratch_length) +{ + const struct TALER_ExchangeWithdrawValues *alg_values = data; + size_t tlen; + size_t len; + uint32_t be[2]; + char *buf; + + (void) cls; + (void) data_len; + GNUNET_assert (1 == param_length); + GNUNET_assert (scratch_length > 0); + GNUNET_break (NULL == cls); + be[0] = htonl ((uint32_t) alg_values->cipher); + be[1] = htonl (0x010000); /* magic marker: EWV */ + switch (alg_values->cipher) + { + case TALER_DENOMINATION_RSA: + tlen = 0; + break; + case TALER_DENOMINATION_CS: + tlen = sizeof (struct TALER_DenominationCSPublicRPairP); + break; + default: + GNUNET_assert (0); + } + len = tlen + sizeof (be); + buf = GNUNET_malloc (len); + GNUNET_memcpy (buf, + &be, + sizeof (be)); + switch (alg_values->cipher) + { + case TALER_DENOMINATION_RSA: + break; + case TALER_DENOMINATION_CS: + GNUNET_memcpy (&buf[sizeof (be)], + &alg_values->details.cs_values, + tlen); + break; + default: + GNUNET_assert (0); + } + scratch[0] = buf; + param_values[0] = (void *) buf; + param_lengths[0] = len; + param_formats[0] = 1; + return 1; +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_exchange_withdraw_values ( + const struct TALER_ExchangeWithdrawValues *alg_values) +{ + struct GNUNET_PQ_QueryParam res = { + .conv = &qconv_exchange_withdraw_values, + .data = alg_values, + .num_params = 1 + }; + + return res; +} + + +/** + * Function called to convert input argument into SQL parameters. + * + * @param cls closure + * @param data pointer to input argument, here a `json_t *` + * @param data_len number of bytes in @a data (if applicable) + * @param[out] param_values SQL data to set + * @param[out] param_lengths SQL length data to set + * @param[out] param_formats SQL format data to set + * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays + * @param[out] scratch buffer for dynamic allocations (to be done via GNUNET_malloc() + * @param scratch_length number of entries left in @a scratch + * @return -1 on error, number of offsets used in @a scratch otherwise + */ +static int +qconv_json (void *cls, + const void *data, + size_t data_len, + void *param_values[], + int param_lengths[], + int param_formats[], + unsigned int param_length, + void *scratch[], + unsigned int scratch_length) +{ + const json_t *json = data; + char *str; + + (void) cls; + (void) data_len; + GNUNET_assert (1 == param_length); + GNUNET_assert (scratch_length > 0); + str = json_dumps (json, JSON_COMPACT); + if (NULL == str) + return -1; + scratch[0] = str; + param_values[0] = (void *) str; + param_lengths[0] = strlen (str); + param_formats[0] = 1; + return 1; +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_json (const json_t *x) +{ + struct GNUNET_PQ_QueryParam res = { + .conv = &qconv_json, + .data = x, + .num_params = 1 + }; + + return res; +} + + +/** ------------------- Array support -----------------------------------**/ + +/** + * Closure for the array type handlers. + * + * May contain sizes information for the data, given (and handled) by the + * caller. + */ +struct qconv_array_cls +{ + /** + * If not null, contains the array of sizes (the size of the array is the + * .size field in the ambient GNUNET_PQ_QueryParam struct). We do not free + * this memory. + * + * If not null, this value has precedence over @a sizes, which MUST be NULL */ + const size_t *sizes; + + /** + * If @a size and @a c_sizes are NULL, this field defines the same size + * for each element in the array. + */ + size_t same_size; + + /** + * If true, the array parameter to the data pointer to the qconv_array is a + * continuous byte array of data, either with @a same_size each or sizes + * provided bytes by @a sizes; + */ + bool continuous; + + /** + * Type of the array elements + */ + enum TALER_PQ_ArrayType typ; + + /** + * Oid of the array elements + */ + Oid oid; + + /** + * db context, needed for OID-lookup of basis-types + */ + struct GNUNET_PQ_Context *db; +}; + +/** + * Callback to cleanup a qconv_array_cls to be used during + * GNUNET_PQ_cleanup_query_params_closures + */ +static void +qconv_array_cls_cleanup (void *cls) +{ + GNUNET_free (cls); +} + + +/** + * Function called to convert input argument into SQL parameters for arrays + * + * Note: the format for the encoding of arrays for libpq is not very well + * documented. We peeked into various sources (postgresql and libpqtypes) for + * guidance. + * + * @param cls Closure of type struct qconv_array_cls* + * @param data Pointer to first element in the array + * @param data_len Number of _elements_ in array @a data (if applicable) + * @param[out] param_values SQL data to set + * @param[out] param_lengths SQL length data to set + * @param[out] param_formats SQL format data to set + * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays + * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc() + * @param scratch_length number of entries left in @a scratch + * @return -1 on error, number of offsets used in @a scratch otherwise + */ +static int +qconv_array ( + void *cls, + const void *data, + size_t data_len, + void *param_values[], + int param_lengths[], + int param_formats[], + unsigned int param_length, + void *scratch[], + unsigned int scratch_length) +{ + struct qconv_array_cls *meta = cls; + size_t num = data_len; + size_t total_size; + const size_t *sizes; + bool same_sized; + void *elements = NULL; + bool noerror = true; + /* needed to capture the encoded rsa signatures */ + void **buffers = NULL; + size_t *buffer_lengths = NULL; + + (void) (param_length); + (void) (scratch_length); + + GNUNET_assert (NULL != meta); + GNUNET_assert (num < INT_MAX); + + sizes = meta->sizes; + same_sized = (0 != meta->same_size); + +#define RETURN_UNLESS(cond) \ + do { \ + if (! (cond)) \ + { \ + GNUNET_break ((cond)); \ + noerror = false; \ + goto DONE; \ + } \ + } while (0) + + /* Calculate sizes and check bounds */ + { + /* num * length-field */ + size_t x = sizeof(uint32_t); + size_t y = x * num; + RETURN_UNLESS ((0 == num) || (y / num == x)); + + /* size of header */ + total_size = x = sizeof(struct GNUNET_PQ_ArrayHeader_P); + total_size += y; + RETURN_UNLESS (total_size >= x); + + /* sizes of elements */ + if (same_sized) + { + x = num * meta->same_size; + RETURN_UNLESS ((0 == num) || (x / num == meta->same_size)); + + y = total_size; + total_size += x; + RETURN_UNLESS (total_size >= y); + } + else /* sizes are different per element */ + { + switch (meta->typ) + { + case TALER_PQ_array_of_blinded_denom_sig: + { + const struct TALER_BlindedDenominationSignature *denom_sigs = data; + size_t len; + + buffers = GNUNET_new_array (num, void *); + buffer_lengths = GNUNET_new_array (num, size_t); + + for (size_t i = 0; i<num; i++) + { + switch (denom_sigs[i].cipher) + { + case TALER_DENOMINATION_RSA: + len = GNUNET_CRYPTO_rsa_signature_encode ( + denom_sigs[i].details.blinded_rsa_signature, + &buffers[i]); + RETURN_UNLESS (len != 0); + break; + case TALER_DENOMINATION_CS: + len = sizeof (denom_sigs[i].details.blinded_cs_answer); + break; + default: + GNUNET_assert (0); + } + + /* for the cipher and marker */ + len += 2 * sizeof(uint32_t); + buffer_lengths[i] = len; + + y = total_size; + total_size += len; + RETURN_UNLESS (total_size >= y); + } + sizes = buffer_lengths; + break; + } + default: + GNUNET_assert (0); + } + } + + RETURN_UNLESS (INT_MAX > total_size); + RETURN_UNLESS (0 != total_size); + + elements = GNUNET_malloc (total_size); + } + + /* Write data */ + { + char *out = elements; + struct GNUNET_PQ_ArrayHeader_P h = { + .ndim = htonl (1), /* We only support one-dimensional arrays */ + .has_null = htonl (0), /* We do not support NULL entries in arrays */ + .lbound = htonl (1), /* Default start index value */ + .dim = htonl (num), + .oid = htonl (meta->oid), + }; + + /* Write header */ + GNUNET_memcpy (out, + &h, + sizeof(h)); + out += sizeof(h); + + /* Write elements */ + for (size_t i = 0; i < num; i++) + { + size_t sz = same_sized ? meta->same_size : sizes[i]; + + *(uint32_t *) out = htonl (sz); + out += sizeof(uint32_t); + switch (meta->typ) + { + case TALER_PQ_array_of_amount: + { + const struct TALER_Amount *amounts = data; + Oid oid_v; + Oid oid_f; + + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (meta->db, + "int8", + &oid_v)); + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (meta->db, + "int4", + &oid_f)); + { + struct TALER_PQ_AmountP am + = TALER_PQ_make_taler_pq_amount_ ( + &amounts[i], + oid_v, + oid_f); + + GNUNET_memcpy (out, + &am, + sizeof(am)); + } + break; + } + case TALER_PQ_array_of_blinded_denom_sig: + { + const struct TALER_BlindedDenominationSignature *denom_sigs = data; + uint32_t be[2]; + + be[0] = htonl ((uint32_t) denom_sigs[i].cipher); + be[1] = htonl (0x01); /* magic margker: blinded */ + GNUNET_memcpy (out, + &be, + sizeof(be)); + out += sizeof(be); + sz -= sizeof(be); + + switch (denom_sigs[i].cipher) + { + case TALER_DENOMINATION_RSA: + /* For RSA, 'same_sized' must have been false */ + GNUNET_assert (NULL != buffers); + GNUNET_memcpy (out, + buffers[i], + sz); + break; + case TALER_DENOMINATION_CS: + GNUNET_memcpy (out, + &denom_sigs[i].details.blinded_cs_answer, + sz); + break; + default: + GNUNET_assert (0); + } + break; + } + case TALER_PQ_array_of_blinded_coin_hash: + { + const struct TALER_BlindedCoinHashP *coin_hs = data; + + GNUNET_memcpy (out, + &coin_hs[i], + sizeof(struct TALER_BlindedCoinHashP)); + + break; + } + case TALER_PQ_array_of_denom_hash: + { + const struct TALER_DenominationHashP *denom_hs = data; + + GNUNET_memcpy (out, + &denom_hs[i], + sizeof(struct TALER_DenominationHashP)); + break; + } + default: + { + GNUNET_assert (0); + break; + } + } + out += sz; + } + } + param_values[0] = elements; + param_lengths[0] = total_size; + param_formats[0] = 1; + scratch[0] = elements; + +DONE: + if (NULL != buffers) + { + for (size_t i = 0; i<num; i++) + GNUNET_free (buffers[i]); + GNUNET_free (buffers); + } + GNUNET_free (buffer_lengths); + if (noerror) + return 1; + return -1; +} + + +/** + * Function to generate a typ specific query parameter and corresponding closure + * + * @param num Number of elements in @a elements + * @param continuous If true, @a elements is an continuous array of data + * @param elements Array of @a num elements, either continuous or pointers + * @param sizes Array of @a num sizes, one per element, may be NULL + * @param same_size If not 0, all elements in @a elements have this size + * @param typ Supported internal type of each element in @a elements + * @param oid Oid of the type to be used in Postgres + * @param[in,out] db our database handle for looking up OIDs + * @return Query parameter + */ +static struct GNUNET_PQ_QueryParam +query_param_array_generic ( + unsigned int num, + bool continuous, + const void *elements, + const size_t *sizes, + size_t same_size, + enum TALER_PQ_ArrayType typ, + Oid oid, + struct GNUNET_PQ_Context *db) +{ + struct qconv_array_cls *meta = GNUNET_new (struct qconv_array_cls); + meta->typ = typ; + meta->oid = oid; + meta->sizes = sizes; + meta->same_size = same_size; + meta->continuous = continuous; + meta->db = db; + + struct GNUNET_PQ_QueryParam res = { + .conv = qconv_array, + .conv_cls = meta, + .conv_cls_cleanup = qconv_array_cls_cleanup, + .data = elements, + .size = num, + .num_params = 1, + }; + + return res; +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_array_blinded_denom_sig ( + size_t num, + const struct TALER_BlindedDenominationSignature *denom_sigs, + struct GNUNET_PQ_Context *db) +{ + Oid oid; + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, "bytea", &oid)); + return query_param_array_generic (num, + true, + denom_sigs, + NULL, + 0, + TALER_PQ_array_of_blinded_denom_sig, + oid, + NULL); +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_array_blinded_coin_hash ( + size_t num, + const struct TALER_BlindedCoinHashP *coin_hs, + struct GNUNET_PQ_Context *db) +{ + Oid oid; + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, "bytea", &oid)); + return query_param_array_generic (num, + true, + coin_hs, + NULL, + sizeof(struct TALER_BlindedCoinHashP), + TALER_PQ_array_of_blinded_coin_hash, + oid, + NULL); +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_array_denom_hash ( + size_t num, + const struct TALER_DenominationHashP *denom_hs, + struct GNUNET_PQ_Context *db) +{ + Oid oid; + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, "bytea", &oid)); + return query_param_array_generic (num, + true, + denom_hs, + NULL, + sizeof(struct TALER_DenominationHashP), + TALER_PQ_array_of_denom_hash, + oid, + NULL); +} + + +struct GNUNET_PQ_QueryParam +TALER_PQ_query_param_array_amount ( + size_t num, + const struct TALER_Amount *amounts, + struct GNUNET_PQ_Context *db) +{ + Oid oid; + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, "taler_amount", &oid)); + return query_param_array_generic ( + num, + true, + amounts, + NULL, + sizeof(struct TALER_PQ_AmountP), + TALER_PQ_array_of_amount, + oid, + db); +} + + +/* end of pq/pq_query_helper.c */ diff --git a/src/pq/pq_result_helper.c b/src/pq/pq_result_helper.c @@ -0,0 +1,1424 @@ +/* + This file is part of TALER + Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file pq/pq_result_helper.c + * @brief functions to initialize parameter arrays + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "pq_common.h" +#include "taler_pq_lib.h" + + +/** + * Extract an amount from a tuple including the currency from a Postgres + * database @a result at row @a row. + * + * @param cls closure; not used + * @param result where to extract data from + * @param row row to extract data from + * @param fname name (or prefix) of the fields to extract from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_NO if at least one result was NULL + * #GNUNET_SYSERR if a result was invalid (non-existing field) + */ +static enum GNUNET_GenericReturnValue +extract_amount_currency_tuple (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + struct TALER_Amount *r_amount = dst; + int col; + + (void) cls; + if (sizeof (struct TALER_Amount) != *dst_size) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* Set return value to invalid in case we don't finish */ + memset (r_amount, + 0, + sizeof (struct TALER_Amount)); + col = PQfnumber (result, + fname); + if (col < 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Field `%s' does not exist in result\n", + fname); + return GNUNET_SYSERR; + } + if (PQgetisnull (result, + row, + col)) + { + return GNUNET_NO; + } + + /* Parse the tuple */ + { + struct TALER_PQ_AmountCurrencyP ap; + const char *in; + size_t size; + + size = PQgetlength (result, + row, + col); + if ( (size >= sizeof (ap)) || + (size <= sizeof (ap) - TALER_CURRENCY_LEN) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Incorrect size of binary field `%s' (got %zu, expected (%zu-%zu))\n", + fname, + size, + sizeof (ap) - TALER_CURRENCY_LEN, + sizeof (ap)); + return GNUNET_SYSERR; + } + + in = PQgetvalue (result, + row, + col); + memset (&ap.c, + 0, + TALER_CURRENCY_LEN); + memcpy (&ap, + in, + size); + if (3 != ntohl (ap.cnt)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Incorrect number of elements in tuple-field `%s'\n", + fname); + return GNUNET_SYSERR; + } + /* TODO[oec]: OID-checks? */ + + r_amount->value = GNUNET_ntohll (ap.v); + r_amount->fraction = ntohl (ap.f); + memcpy (r_amount->currency, + ap.c, + TALER_CURRENCY_LEN); + if ('\0' != r_amount->currency[TALER_CURRENCY_LEN - 1]) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid currency (not 0-terminated) in tuple field `%s'\n", + fname); + /* be sure nobody uses this by accident */ + memset (r_amount, + 0, + sizeof (struct TALER_Amount)); + return GNUNET_SYSERR; + } + } + + if (r_amount->value >= TALER_AMOUNT_MAX_VALUE) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Value in field `%s' exceeds legal range\n", + fname); + return GNUNET_SYSERR; + } + if (r_amount->fraction >= TALER_AMOUNT_FRAC_BASE) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Fraction in field `%s' exceeds legal range\n", + fname); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_amount_with_currency (const char *name, + struct TALER_Amount *amount) +{ + struct GNUNET_PQ_ResultSpec res = { + .conv = &extract_amount_currency_tuple, + .dst = (void *) amount, + .dst_size = sizeof (*amount), + .fname = name + }; + + return res; +} + + +/** + * Extract an amount from a tuple from a Postgres database @a result at row @a row. + * + * @param cls closure, a `const char *` giving the currency + * @param result where to extract data from + * @param row row to extract data from + * @param fname name (or prefix) of the fields to extract from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_NO if at least one result was NULL + * #GNUNET_SYSERR if a result was invalid (non-existing field) + */ +static enum GNUNET_GenericReturnValue +extract_amount_tuple (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + struct TALER_Amount *r_amount = dst; + const char *currency = cls; + int col; + size_t len; + + if (sizeof (struct TALER_Amount) != *dst_size) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* Set return value to invalid in case we don't finish */ + memset (r_amount, + 0, + sizeof (struct TALER_Amount)); + col = PQfnumber (result, + fname); + if (col < 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Field `%s' does not exist in result\n", + fname); + return GNUNET_SYSERR; + } + if (PQgetisnull (result, + row, + col)) + { + return GNUNET_NO; + } + + /* Parse the tuple */ + { + struct TALER_PQ_AmountP ap; + const char *in; + size_t size; + + size = PQgetlength (result, + row, + col); + if (sizeof(ap) != size) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Incorrect size of binary field `%s' (got %zu, expected %zu)\n", + fname, + size, + sizeof(ap)); + return GNUNET_SYSERR; + } + + in = PQgetvalue (result, + row, + col); + memcpy (&ap, + in, + size); + if (2 != ntohl (ap.cnt)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Incorrect number of elements in tuple-field `%s'\n", + fname); + return GNUNET_SYSERR; + } + /* TODO[oec]: OID-checks? */ + + r_amount->value = GNUNET_ntohll (ap.v); + r_amount->fraction = ntohl (ap.f); + } + + if (r_amount->value >= TALER_AMOUNT_MAX_VALUE) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Value in field `%s' exceeds legal range\n", + fname); + return GNUNET_SYSERR; + } + if (r_amount->fraction >= TALER_AMOUNT_FRAC_BASE) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Fraction in field `%s' exceeds legal range\n", + fname); + return GNUNET_SYSERR; + } + + len = GNUNET_MIN (TALER_CURRENCY_LEN - 1, + strlen (currency)); + + GNUNET_memcpy (r_amount->currency, + currency, + len); + return GNUNET_OK; +} + + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_amount (const char *name, + const char *currency, + struct TALER_Amount *amount) +{ + struct GNUNET_PQ_ResultSpec res = { + .conv = &extract_amount_tuple, + .cls = (void *) currency, + .dst = (void *) amount, + .dst_size = sizeof (*amount), + .fname = name + }; + + return res; +} + + +/** + * Extract data from a Postgres database @a result at row @a row. + * + * @param cls closure + * @param result where to extract data from + * @param row row to extract data from + * @param fname name (or prefix) of the fields to extract from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_NO if at least one result was NULL + * #GNUNET_SYSERR if a result was invalid (non-existing field) + */ +static enum GNUNET_GenericReturnValue +extract_json (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + json_t **j_dst = dst; + const char *res; + int fnum; + json_error_t json_error; + size_t slen; + + (void) cls; + (void) dst_size; + fnum = PQfnumber (result, + fname); + if (fnum < 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Field `%s' does not exist in result\n", + fname); + return GNUNET_SYSERR; + } + if (PQgetisnull (result, + row, + fnum)) + return GNUNET_NO; + slen = PQgetlength (result, + row, + fnum); + res = (const char *) PQgetvalue (result, + row, + fnum); + *j_dst = json_loadb (res, + slen, + JSON_REJECT_DUPLICATES, + &json_error); + if (NULL == *j_dst) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse JSON result for field `%s': %s (%s)\n", + fname, + json_error.text, + json_error.source); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called to clean up memory allocated + * by a #GNUNET_PQ_ResultConverter. + * + * @param cls closure + * @param rd result data to clean up + */ +static void +clean_json (void *cls, + void *rd) +{ + json_t **dst = rd; + + (void) cls; + if (NULL != *dst) + { + json_decref (*dst); + *dst = NULL; + } +} + + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_json (const char *name, + json_t **jp) +{ + struct GNUNET_PQ_ResultSpec res = { + .conv = &extract_json, + .cleaner = &clean_json, + .dst = (void *) jp, + .fname = name + }; + + return res; +} + + +/** + * Extract data from a Postgres database @a result at row @a row. + * + * @param cls closure + * @param result where to extract data from + * @param row the row to extract data from + * @param fname name (or prefix) of the fields to extract from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL) + */ +static enum GNUNET_GenericReturnValue +extract_denom_pub (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + struct TALER_DenominationPublicKey *pk = dst; + size_t len; + const char *res; + int fnum; + uint32_t be[2]; + + (void) cls; + (void) dst_size; + fnum = PQfnumber (result, + fname); + if (fnum < 0) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (PQgetisnull (result, + row, + fnum)) + return GNUNET_NO; + + /* if a field is null, continue but + * remember that we now return a different result */ + len = PQgetlength (result, + row, + fnum); + res = PQgetvalue (result, + row, + fnum); + if (len < sizeof (be)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (be, + res, + sizeof (be)); + res += sizeof (be); + len -= sizeof (be); + pk->cipher = ntohl (be[0]); + pk->age_mask.bits = ntohl (be[1]); + switch (pk->cipher) + { + case TALER_DENOMINATION_RSA: + pk->details.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (res, + len); + if (NULL == pk->details.rsa_public_key) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + case TALER_DENOMINATION_CS: + if (sizeof (pk->details.cs_public_key) != len) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&pk->details.cs_public_key, + res, + len); + return GNUNET_OK; + default: + GNUNET_break (0); + } + return GNUNET_SYSERR; +} + + +/** + * Function called to clean up memory allocated + * by a #GNUNET_PQ_ResultConverter. + * + * @param cls closure + * @param rd result data to clean up + */ +static void +clean_denom_pub (void *cls, + void *rd) +{ + struct TALER_DenominationPublicKey *denom_pub = rd; + + (void) cls; + TALER_denom_pub_free (denom_pub); +} + + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_denom_pub (const char *name, + struct TALER_DenominationPublicKey *denom_pub) +{ + struct GNUNET_PQ_ResultSpec res = { + .conv = &extract_denom_pub, + .cleaner = &clean_denom_pub, + .dst = (void *) denom_pub, + .fname = name + }; + + return res; +} + + +/** + * Extract data from a Postgres database @a result at row @a row. + * + * @param cls closure + * @param result where to extract data from + * @param row the row to extract data from + * @param fname name (or prefix) of the fields to extract from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL) + */ +static enum GNUNET_GenericReturnValue +extract_denom_sig (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + struct TALER_DenominationSignature *sig = dst; + size_t len; + const char *res; + int fnum; + uint32_t be[2]; + + (void) cls; + (void) dst_size; + fnum = PQfnumber (result, + fname); + if (fnum < 0) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (PQgetisnull (result, + row, + fnum)) + return GNUNET_NO; + + /* if a field is null, continue but + * remember that we now return a different result */ + len = PQgetlength (result, + row, + fnum); + res = PQgetvalue (result, + row, + fnum); + if (len < sizeof (be)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&be, + res, + sizeof (be)); + if (0x00 != ntohl (be[1])) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + res += sizeof (be); + len -= sizeof (be); + sig->cipher = ntohl (be[0]); + switch (sig->cipher) + { + case TALER_DENOMINATION_RSA: + sig->details.rsa_signature + = GNUNET_CRYPTO_rsa_signature_decode (res, + len); + if (NULL == sig->details.rsa_signature) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + case TALER_DENOMINATION_CS: + if (sizeof (sig->details.cs_signature) != len) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&sig->details.cs_signature, + res, + len); + return GNUNET_OK; + default: + GNUNET_break (0); + } + return GNUNET_SYSERR; +} + + +/** + * Function called to clean up memory allocated + * by a #GNUNET_PQ_ResultConverter. + * + * @param cls closure + * @param rd result data to clean up + */ +static void +clean_denom_sig (void *cls, + void *rd) +{ + struct TALER_DenominationSignature *denom_sig = rd; + + (void) cls; + TALER_denom_sig_free (denom_sig); +} + + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_denom_sig (const char *name, + struct TALER_DenominationSignature *denom_sig) +{ + struct GNUNET_PQ_ResultSpec res = { + .conv = &extract_denom_sig, + .cleaner = &clean_denom_sig, + .dst = (void *) denom_sig, + .fname = name + }; + + return res; +} + + +/** + * Extract data from a Postgres database @a result at row @a row. + * + * @param cls closure + * @param result where to extract data from + * @param row the row to extract data from + * @param fname name (or prefix) of the fields to extract from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL) + */ +static enum GNUNET_GenericReturnValue +extract_blinded_denom_sig (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + struct TALER_BlindedDenominationSignature *sig = dst; + size_t len; + const char *res; + int fnum; + uint32_t be[2]; + + (void) cls; + (void) dst_size; + fnum = PQfnumber (result, + fname); + if (fnum < 0) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (PQgetisnull (result, + row, + fnum)) + return GNUNET_NO; + + /* if a field is null, continue but + * remember that we now return a different result */ + len = PQgetlength (result, + row, + fnum); + res = PQgetvalue (result, + row, + fnum); + if (len < sizeof (be)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&be, + res, + sizeof (be)); + if (0x01 != ntohl (be[1])) /* magic marker: blinded */ + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + res += sizeof (be); + len -= sizeof (be); + sig->cipher = ntohl (be[0]); + switch (sig->cipher) + { + case TALER_DENOMINATION_RSA: + sig->details.blinded_rsa_signature + = GNUNET_CRYPTO_rsa_signature_decode (res, + len); + if (NULL == sig->details.blinded_rsa_signature) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + case TALER_DENOMINATION_CS: + if (sizeof (sig->details.blinded_cs_answer) != len) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&sig->details.blinded_cs_answer, + res, + len); + return GNUNET_OK; + default: + GNUNET_break (0); + } + return GNUNET_SYSERR; +} + + +/** + * Function called to clean up memory allocated + * by a #GNUNET_PQ_ResultConverter. + * + * @param cls closure + * @param rd result data to clean up + */ +static void +clean_blinded_denom_sig (void *cls, + void *rd) +{ + struct TALER_BlindedDenominationSignature *denom_sig = rd; + + (void) cls; + TALER_blinded_denom_sig_free (denom_sig); +} + + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_blinded_denom_sig ( + const char *name, + struct TALER_BlindedDenominationSignature *denom_sig) +{ + struct GNUNET_PQ_ResultSpec res = { + .conv = &extract_blinded_denom_sig, + .cleaner = &clean_blinded_denom_sig, + .dst = (void *) denom_sig, + .fname = name + }; + + return res; +} + + +/** + * Extract data from a Postgres database @a result at row @a row. + * + * @param cls closure + * @param result where to extract data from + * @param row the row to extract data from + * @param fname name (or prefix) of the fields to extract from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL) + */ +static enum GNUNET_GenericReturnValue +extract_blinded_planchet (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + struct TALER_BlindedPlanchet *bp = dst; + size_t len; + const char *res; + int fnum; + uint32_t be[2]; + + (void) cls; + (void) dst_size; + fnum = PQfnumber (result, + fname); + if (fnum < 0) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (PQgetisnull (result, + row, + fnum)) + return GNUNET_NO; + + /* if a field is null, continue but + * remember that we now return a different result */ + len = PQgetlength (result, + row, + fnum); + res = PQgetvalue (result, + row, + fnum); + if (len < sizeof (be)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&be, + res, + sizeof (be)); + if (0x0100 != ntohl (be[1])) /* magic marker: blinded */ + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + res += sizeof (be); + len -= sizeof (be); + bp->cipher = ntohl (be[0]); + switch (bp->cipher) + { + case TALER_DENOMINATION_RSA: + bp->details.rsa_blinded_planchet.blinded_msg_size + = len; + bp->details.rsa_blinded_planchet.blinded_msg + = GNUNET_memdup (res, + len); + return GNUNET_OK; + case TALER_DENOMINATION_CS: + if (sizeof (bp->details.cs_blinded_planchet) != len) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&bp->details.cs_blinded_planchet, + res, + len); + return GNUNET_OK; + default: + GNUNET_break (0); + } + return GNUNET_SYSERR; +} + + +/** + * Function called to clean up memory allocated + * by a #GNUNET_PQ_ResultConverter. + * + * @param cls closure + * @param rd result data to clean up + */ +static void +clean_blinded_planchet (void *cls, + void *rd) +{ + struct TALER_BlindedPlanchet *bp = rd; + + (void) cls; + TALER_blinded_planchet_free (bp); +} + + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_blinded_planchet ( + const char *name, + struct TALER_BlindedPlanchet *bp) +{ + struct GNUNET_PQ_ResultSpec res = { + .conv = &extract_blinded_planchet, + .cleaner = &clean_blinded_planchet, + .dst = (void *) bp, + .fname = name + }; + + return res; +} + + +/** + * Extract data from a Postgres database @a result at row @a row. + * + * @param cls closure + * @param result where to extract data from + * @param row row to extract data from + * @param fname name (or prefix) of the fields to extract from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL) + */ +static enum GNUNET_GenericReturnValue +extract_exchange_withdraw_values (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + struct TALER_ExchangeWithdrawValues *alg_values = dst; + size_t len; + const char *res; + int fnum; + uint32_t be[2]; + + (void) cls; + (void) dst_size; + fnum = PQfnumber (result, + fname); + if (fnum < 0) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (PQgetisnull (result, + row, + fnum)) + return GNUNET_NO; + + /* if a field is null, continue but + * remember that we now return a different result */ + len = PQgetlength (result, + row, + fnum); + res = PQgetvalue (result, + row, + fnum); + if (len < sizeof (be)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&be, + res, + sizeof (be)); + if (0x010000 != ntohl (be[1])) /* magic marker: EWV */ + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + res += sizeof (be); + len -= sizeof (be); + alg_values->cipher = ntohl (be[0]); + switch (alg_values->cipher) + { + case TALER_DENOMINATION_RSA: + if (0 != len) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + case TALER_DENOMINATION_CS: + if (sizeof (struct TALER_DenominationCSPublicRPairP) != len) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&alg_values->details.cs_values, + res, + len); + return GNUNET_OK; + default: + GNUNET_break (0); + } + return GNUNET_SYSERR; +} + + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_exchange_withdraw_values ( + const char *name, + struct TALER_ExchangeWithdrawValues *ewv) +{ + struct GNUNET_PQ_ResultSpec res = { + .conv = &extract_exchange_withdraw_values, + .dst = (void *) ewv, + .fname = name + }; + + return res; +} + + +/** + * Closure for the array result specifications. Contains type information + * for the generic parser extract_array_generic and out-pointers for the results. + */ +struct ArrayResultCls +{ + /* Oid of the expected type, must match the oid in the header of the PQResult struct */ + Oid oid; + + /* Target type */ + enum TALER_PQ_ArrayType typ; + + /* If not 0, defines the expected size of each entry */ + size_t same_size; + + /* Out-pointer to write the number of elements in the array */ + size_t *num; + + /* Out-pointer. If @a typ is TALER_PQ_array_of_byte and @a same_size is 0, + * allocate and put the array of @a num sizes here. NULL otherwise */ + size_t **sizes; + + /* DB_connection, needed for OID-lookup for composite types */ + const struct GNUNET_PQ_Context *db; + + /* Currency information for amount composites */ + char currency[TALER_CURRENCY_LEN]; +}; + +/** + * Extract data from a Postgres database @a result as array of a specific type + * from row @a row. The type information and optionally additional + * out-parameters are given in @a cls which is of type array_result_cls. + * + * @param cls closure of type array_result_cls + * @param result where to extract data from + * @param row row to extract data from + * @param fname name (or prefix) of the fields to extract from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL) + */ +static enum GNUNET_GenericReturnValue +extract_array_generic ( + void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + const struct ArrayResultCls *info = cls; + int data_sz; + char *data; + void *out = NULL; + struct GNUNET_PQ_ArrayHeader_P header; + int col_num; + + GNUNET_assert (NULL != dst); + *((void **) dst) = NULL; + + #define FAIL_IF(cond) \ + do { \ + if ((cond)) \ + { \ + GNUNET_break (! (cond)); \ + goto FAIL; \ + } \ + } while (0) + + col_num = PQfnumber (result, fname); + FAIL_IF (0 > col_num); + + data_sz = PQgetlength (result, row, col_num); + FAIL_IF (0 > data_sz); + FAIL_IF (sizeof(header) > (size_t) data_sz); + + data = PQgetvalue (result, row, col_num); + FAIL_IF (NULL == data); + + { + struct GNUNET_PQ_ArrayHeader_P *h = + (struct GNUNET_PQ_ArrayHeader_P *) data; + + header.ndim = ntohl (h->ndim); + header.has_null = ntohl (h->has_null); + header.oid = ntohl (h->oid); + header.dim = ntohl (h->dim); + header.lbound = ntohl (h->lbound); + + FAIL_IF (1 != header.ndim); + FAIL_IF (INT_MAX <= header.dim); + FAIL_IF (0 != header.has_null); + FAIL_IF (1 != header.lbound); + FAIL_IF (info->oid != header.oid); + } + + if (NULL != info->num) + *info->num = header.dim; + + { + char *in = data + sizeof(header); + + switch (info->typ) + { + case TALER_PQ_array_of_amount: + { + struct TALER_Amount *amounts; + if (NULL != dst_size) + *dst_size = sizeof(struct TALER_Amount) * (header.dim); + + amounts = GNUNET_new_array (header.dim, struct TALER_Amount); + *((void **) dst) = amounts; + + for (uint32_t i = 0; i < header.dim; i++) + { + struct TALER_PQ_AmountP ap; + struct TALER_Amount *amount = &amounts[i]; + uint32_t val; + size_t sz; + + GNUNET_memcpy (&val, + in, + sizeof(val)); + sz = ntohl (val); + in += sizeof(val); + + /* total size for this array-entry */ + FAIL_IF (sizeof(ap) > sz); + + GNUNET_memcpy (&ap, + in, + sz); + FAIL_IF (2 != ntohl (ap.cnt)); + + amount->value = GNUNET_ntohll (ap.v); + amount->fraction = ntohl (ap.f); + GNUNET_memcpy (amount->currency, + info->currency, + TALER_CURRENCY_LEN); + + in += sizeof(struct TALER_PQ_AmountP); + } + return GNUNET_OK; + } + case TALER_PQ_array_of_denom_hash: + if (NULL != dst_size) + *dst_size = sizeof(struct TALER_DenominationHashP) * (header.dim); + out = GNUNET_new_array (header.dim, struct TALER_DenominationHashP); + *((void **) dst) = out; + for (uint32_t i = 0; i < header.dim; i++) + { + uint32_t val; + size_t sz; + + GNUNET_memcpy (&val, + in, + sizeof(val)); + sz = ntohl (val); + FAIL_IF (sz != sizeof(struct TALER_DenominationHashP)); + in += sizeof(uint32_t); + *(struct TALER_DenominationHashP *) out = + *(struct TALER_DenominationHashP *) in; + in += sz; + out += sz; + } + return GNUNET_OK; + + case TALER_PQ_array_of_blinded_coin_hash: + if (NULL != dst_size) + *dst_size = sizeof(struct TALER_BlindedCoinHashP) * (header.dim); + out = GNUNET_new_array (header.dim, struct TALER_BlindedCoinHashP); + *((void **) dst) = out; + for (uint32_t i = 0; i < header.dim; i++) + { + uint32_t val; + size_t sz; + + GNUNET_memcpy (&val, + in, + sizeof(val)); + sz = ntohl (val); + FAIL_IF (sz != sizeof(struct TALER_BlindedCoinHashP)); + in += sizeof(uint32_t); + *(struct TALER_BlindedCoinHashP *) out = + *(struct TALER_BlindedCoinHashP *) in; + in += sz; + out += sz; + } + return GNUNET_OK; + + case TALER_PQ_array_of_blinded_denom_sig: + { + struct TALER_BlindedDenominationSignature *denom_sigs; + if (0 == header.dim) + { + if (NULL != dst_size) + *dst_size = 0; + break; + } + + denom_sigs = GNUNET_new_array (header.dim, + struct TALER_BlindedDenominationSignature); + *((void **) dst) = denom_sigs; + + /* copy data */ + for (uint32_t i = 0; i < header.dim; i++) + { + struct TALER_BlindedDenominationSignature *denom_sig = &denom_sigs[i]; + uint32_t be[2]; + uint32_t val; + size_t sz; + + GNUNET_memcpy (&val, + in, + sizeof(val)); + sz = ntohl (val); + FAIL_IF (sizeof(be) > sz); + + in += sizeof(val); + GNUNET_memcpy (&be, + in, + sizeof(be)); + FAIL_IF (0x01 != ntohl (be[1])); /* magic marker: blinded */ + + in += sizeof(be); + sz -= sizeof(be); + + denom_sig->cipher = ntohl (be[0]); + switch (denom_sig->cipher) + { + case TALER_DENOMINATION_RSA: + denom_sig->details.blinded_rsa_signature = + GNUNET_CRYPTO_rsa_signature_decode (in, + sz); + FAIL_IF (NULL == denom_sig->details.blinded_rsa_signature); + break; + + case TALER_DENOMINATION_CS: + FAIL_IF (sizeof(denom_sig->details.blinded_cs_answer) != sz); + GNUNET_memcpy (&denom_sig->details.blinded_cs_answer, + in, + sz); + break; + + default: + FAIL_IF (true); + } + + in += sz; + } + return GNUNET_OK; + } + default: + FAIL_IF (true); + } + } + +FAIL: + GNUNET_free (*(void **) dst); + return GNUNET_SYSERR; + #undef FAIL_IF + +} + + +/** + * Cleanup of the data and closure of an array spec. + */ +static void +array_cleanup (void *cls, + void *rd) +{ + + struct ArrayResultCls *info = cls; + void **dst = rd; + + if ((0 == info->same_size) && + (NULL != info->sizes)) + GNUNET_free (*(info->sizes)); + + GNUNET_free (cls); + GNUNET_free (*dst); + *dst = NULL; +} + + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_array_blinded_denom_sig ( + struct GNUNET_PQ_Context *db, + const char *name, + size_t *num, + struct TALER_BlindedDenominationSignature **denom_sigs) +{ + struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls); + + info->num = num; + info->typ = TALER_PQ_array_of_blinded_denom_sig; + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, + "bytea", + &info->oid)); + + struct GNUNET_PQ_ResultSpec res = { + .conv = extract_array_generic, + .cleaner = array_cleanup, + .dst = (void *) denom_sigs, + .fname = name, + .cls = info + }; + return res; + +}; + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_array_blinded_coin_hash ( + struct GNUNET_PQ_Context *db, + const char *name, + size_t *num, + struct TALER_BlindedCoinHashP **h_coin_evs) +{ + struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls); + + info->num = num; + info->typ = TALER_PQ_array_of_blinded_coin_hash; + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, + "bytea", + &info->oid)); + + struct GNUNET_PQ_ResultSpec res = { + .conv = extract_array_generic, + .cleaner = array_cleanup, + .dst = (void *) h_coin_evs, + .fname = name, + .cls = info + }; + return res; + +}; + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_array_denom_hash ( + struct GNUNET_PQ_Context *db, + const char *name, + size_t *num, + struct TALER_DenominationHashP **denom_hs) +{ + struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls); + + info->num = num; + info->typ = TALER_PQ_array_of_denom_hash; + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, + "bytea", + &info->oid)); + + struct GNUNET_PQ_ResultSpec res = { + .conv = extract_array_generic, + .cleaner = array_cleanup, + .dst = (void *) denom_hs, + .fname = name, + .cls = info + }; + return res; + +}; + +struct GNUNET_PQ_ResultSpec +TALER_PQ_result_spec_array_amount ( + struct GNUNET_PQ_Context *db, + const char *name, + const char *currency, + size_t *num, + struct TALER_Amount **amounts) +{ + struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls); + + info->num = num; + info->typ = TALER_PQ_array_of_amount; + info->db = db; + GNUNET_assert (GNUNET_OK == + GNUNET_PQ_get_oid_by_name (db, + "taler_amount", + &info->oid)); + + { + size_t clen = GNUNET_MIN (TALER_CURRENCY_LEN - 1, + strlen (currency)); + GNUNET_memcpy (&info->currency, + currency, + clen); + } + + struct GNUNET_PQ_ResultSpec res = { + .conv = extract_array_generic, + .cleaner = array_cleanup, + .dst = (void *) amounts, + .fname = name, + .cls = info, + }; + return res; + + +} + + +/* end of pq_result_helper.c */ diff --git a/src/pq/test_pq.c b/src/pq/test_pq.c @@ -0,0 +1,250 @@ +/* + This file is part of TALER + (C) 2015, 2016, 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file pq/test_pq.c + * @brief Tests for Postgres convenience API + * @author Christian Grothoff <christian@grothoff.org> + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_pq_lib.h" + + +/** + * Setup prepared statements. + * + * @param db database handle to initialize + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure + */ +static enum GNUNET_GenericReturnValue +postgres_prepare (struct GNUNET_PQ_Context *db) +{ + struct GNUNET_PQ_PreparedStatement ps[] = { + GNUNET_PQ_make_prepare ("test_insert", + "INSERT INTO test_pq (" + " tamount" + ",json" + ",aamount" + ",tamountc" + ") VALUES " + "($1, $2, $3, $4);"), + GNUNET_PQ_make_prepare ("test_select", + "SELECT" + " tamount" + ",json" + ",aamount" + ",tamountc" + " FROM test_pq;"), + GNUNET_PQ_PREPARED_STATEMENT_END + }; + + return GNUNET_PQ_prepare_statements (db, + ps); +} + + +/** + * Run actual test queries. + * + * @return 0 on success + */ +static int +run_queries (struct GNUNET_PQ_Context *conn) +{ + struct TALER_Amount tamount; + struct TALER_Amount aamount[3]; + struct TALER_Amount tamountc; + json_t *json; + + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("EUR:5.3", + &aamount[0])); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("EUR:6.4", + &aamount[1])); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("EUR:7.5", + &aamount[2])); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("EUR:7.7", + &tamount)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("FOO:8.7", + &tamountc)); + json = json_object (); + GNUNET_assert (NULL != json); + GNUNET_assert (0 == + json_object_set_new (json, + "foo", + json_integer (42))); + { + struct GNUNET_PQ_QueryParam params_insert[] = { + TALER_PQ_query_param_amount (conn, + &tamount), + TALER_PQ_query_param_json (json), + TALER_PQ_query_param_array_amount (3, + aamount, + conn), + TALER_PQ_query_param_amount_with_currency (conn, + &tamountc), + GNUNET_PQ_query_param_end + }; + PGresult *result; + + result = GNUNET_PQ_exec_prepared (conn, + "test_insert", + params_insert); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Database failure: %s\n", + PQresultErrorMessage (result)); + PQclear (result); + return 1; + } + PQclear (result); + json_decref (json); + } + { + struct TALER_Amount tamount2; + struct TALER_Amount tamountc2; + struct TALER_Amount *pamount; + size_t npamount; + json_t *json2; + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec results_select[] = { + TALER_PQ_result_spec_amount ("tamount", + "EUR", + &tamount2), + TALER_PQ_result_spec_json ("json", + &json2), + TALER_PQ_result_spec_array_amount (conn, + "aamount", + "EUR", + &npamount, + &pamount), + TALER_PQ_result_spec_amount_with_currency ("tamountc", + &tamountc2), + GNUNET_PQ_result_spec_end + }; + + if (1 != + GNUNET_PQ_eval_prepared_singleton_select (conn, + "test_select", + params_select, + results_select)) + { + GNUNET_break (0); + return 1; + } + GNUNET_break (0 == + TALER_amount_cmp (&tamount, + &tamount2)); + GNUNET_break (42 == + json_integer_value (json_object_get (json2, + "foo"))); + GNUNET_break (3 == npamount); + for (size_t i = 0; i < 3; i++) + { + GNUNET_break (0 == + TALER_amount_cmp (&aamount[i], + &pamount[i])); + } + GNUNET_break (0 == + TALER_amount_cmp (&tamountc, + &tamountc2)); + GNUNET_PQ_cleanup_result (results_select); + } + return 0; +} + + +int +main (int argc, + const char *const argv[]) +{ + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("DO $$ " + " BEGIN" + " CREATE TYPE taler_amount AS" + " (val INT8, frac INT4);" + " EXCEPTION" + " WHEN duplicate_object THEN null;" + " END " + "$$;"), + GNUNET_PQ_make_execute ("DO $$ " + " BEGIN" + " CREATE TYPE taler_amount_currency AS" + " (val INT8, frac INT4, curr VARCHAR(12));" + " EXCEPTION" + " WHEN duplicate_object THEN null;" + " END " + "$$;"), + GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_pq (" + " tamount taler_amount NOT NULL" + ",json VARCHAR NOT NULL" + ",aamount taler_amount[]" + ",tamountc taler_amount_currency" + ")"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + struct GNUNET_PQ_Context *conn; + int ret; + + (void) argc; + (void) argv; + GNUNET_log_setup ("test-pq", + "WARNING", + NULL); + conn = GNUNET_PQ_connect ("postgres:///talercheck", + NULL, + es, + NULL); + if (NULL == conn) + return 77; + if (GNUNET_OK != + postgres_prepare (conn)) + { + GNUNET_break (0); + GNUNET_PQ_disconnect (conn); + return 1; + } + + ret = run_queries (conn); + { + struct GNUNET_PQ_ExecuteStatement ds[] = { + GNUNET_PQ_make_execute ("DROP TABLE test_pq"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + if (GNUNET_OK != + GNUNET_PQ_exec_statements (conn, + ds)) + { + fprintf (stderr, + "Failed to drop table\n"); + GNUNET_PQ_disconnect (conn); + return 1; + } + } + GNUNET_PQ_disconnect (conn); + return ret; +} + + +/* end of test_pq.c */ diff --git a/src/sq/Makefile.am b/src/sq/Makefile.am @@ -0,0 +1,40 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS) $(SQLITE_CPPFLAGS) + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libtalersq.la + +libtalersq_la_SOURCES = \ + sq_query_helper.c \ + sq_result_helper.c +libtalersq_la_LIBADD = \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil -ljansson \ + -lsqlite3 \ + $(XLIB) +libtalersq_la_LDFLAGS = \ + $(SQLITE_LDFLAGS) \ + -version-info 0:0:0 \ + -no-undefined + +check_PROGRAMS= \ + test_sq + +TESTS = \ + $(check_PROGRAMS) + +test_sq_SOURCES = \ + test_sq.c +test_sq_LDADD = \ + libtalersq.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetsq \ + -lgnunetutil \ + -ljansson \ + -lsqlite3 \ + $(XLIB) diff --git a/src/sq/sq_query_helper.c b/src/sq/sq_query_helper.c @@ -0,0 +1,175 @@ +/* + This file is part of TALER + Copyright (C) 2020 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file sq/sq_query_helper.c + * @brief helper functions for Taler-specific SQLite3 interactions + * @author Jonathan Buchanan + */ +#include "platform.h" +#include <sqlite3.h> +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_sq_lib.h> +#include "taler_sq_lib.h" + + +/** + * Function called to convert input argument into SQL parameters. + * + * @param cls closure + * @param data pointer to input argument, here a `struct TALER_Amount` + * @param data_len number of bytes in @a data (if applicable) + * @param stmt sqlite statement to parameters for + * @param off offset of the argument to bind in @a stmt, numbered from 1, + * so immediately suitable for passing to `sqlite3_bind`-functions. + * @return #GNUNET_SYSERR on error, #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +qconv_amount (void *cls, + const void *data, + size_t data_len, + sqlite3_stmt *stmt, + unsigned int off) +{ + const struct TALER_Amount *amount = data; + + (void) cls; + GNUNET_assert (sizeof (struct TALER_Amount) == data_len); + if (SQLITE_OK != sqlite3_bind_int64 (stmt, + (int) off, + (sqlite3_int64) amount->value)) + return GNUNET_SYSERR; + if (SQLITE_OK != sqlite3_bind_int64 (stmt, + (int) off + 1, + (sqlite3_int64) amount->fraction)) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +struct GNUNET_SQ_QueryParam +TALER_SQ_query_param_amount (const struct TALER_Amount *x) +{ + struct GNUNET_SQ_QueryParam res = { + .conv = &qconv_amount, + .data = x, + .size = sizeof (*x), + .num_params = 2 + }; + + return res; +} + + +/** + * Function called to convert input argument into SQL parameters. + * + * @param cls closure + * @param data pointer to input argument, here a `struct TALER_AmountNBO` + * @param data_len number of bytes in @a data (if applicable) + * @param stmt sqlite statement to parameters for + * @param off offset of the argument to bind in @a stmt, numbered from 1, + * so immediately suitable for passing to `sqlite3_bind`-functions. + * @return #GNUNET_SYSERR on error, #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +qconv_amount_nbo (void *cls, + const void *data, + size_t data_len, + sqlite3_stmt *stmt, + unsigned int off) +{ + const struct TALER_AmountNBO *amount = data; + struct TALER_Amount amount_hbo; + + (void) cls; + (void) data_len; + TALER_amount_ntoh (&amount_hbo, + amount); + return qconv_amount (cls, + &amount_hbo, + sizeof (struct TALER_Amount), + stmt, + off); +} + + +struct GNUNET_SQ_QueryParam +TALER_SQ_query_param_amount_nbo (const struct TALER_AmountNBO *x) +{ + struct GNUNET_SQ_QueryParam res = { + .conv = &qconv_amount_nbo, + .data = x, + .size = sizeof (*x), + .num_params = 2 + }; + + return res; +} + + +/** + * Function called to convert input argument into SQL parameters. + * + * @param cls closure + * @param data pointer to input argument, here a `struct TALER_Amount` + * @param data_len number of bytes in @a data (if applicable) + * @param stmt sqlite statement to parameters for + * @param off offset of the argument to bind in @a stmt, numbered from 1, + * so immediately suitable for passing to `sqlite3_bind`-functions. + * @return #GNUNET_SYSERR on error, #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +qconv_json (void *cls, + const void *data, + size_t data_len, + sqlite3_stmt *stmt, + unsigned int off) +{ + const json_t *json = data; + char *str; + + (void) cls; + (void) data_len; + str = json_dumps (json, JSON_COMPACT); + if (NULL == str) + return GNUNET_SYSERR; + + if (SQLITE_OK != sqlite3_bind_text (stmt, + (int) off, + str, + strlen (str), + SQLITE_TRANSIENT)) + return GNUNET_SYSERR; + GNUNET_free (str); + return GNUNET_OK; +} + + +struct GNUNET_SQ_QueryParam +TALER_SQ_query_param_json (const json_t *x) +{ + struct GNUNET_SQ_QueryParam res = { + .conv = &qconv_json, + .data = x, + .size = sizeof (*x), + .num_params = 1 + }; + + return res; +} + + +/* end of sq/sq_query_helper.c */ diff --git a/src/sq/sq_result_helper.c b/src/sq/sq_result_helper.c @@ -0,0 +1,237 @@ +/* + This file is part of TALER + Copyright (C) 2020 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file sq/sq_result_helper.c + * @brief functions to initialize parameter arrays + * @author Jonathan Buchanan + */ +#include "platform.h" +#include <sqlite3.h> +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_sq_lib.h> +#include "taler_sq_lib.h" +#include "taler_util.h" + + +/** + * Extract amount data from a SQLite database + * + * @param cls closure, a `const char *` giving the currency + * @param result where to extract data from + * @param column column to extract data from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL) + */ +static enum GNUNET_GenericReturnValue +extract_amount (void *cls, + sqlite3_stmt *result, + unsigned int column, + size_t *dst_size, + void *dst) +{ + struct TALER_Amount *amount = dst; + const char *currency = cls; + if ((sizeof (struct TALER_Amount) != *dst_size) || + (SQLITE_INTEGER != sqlite3_column_type (result, + (int) column)) || + (SQLITE_INTEGER != sqlite3_column_type (result, + (int) column + 1))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_strlcpy (amount->currency, + currency, + TALER_CURRENCY_LEN); + amount->value = (uint64_t) sqlite3_column_int64 (result, + (int) column); + uint64_t frac = (uint64_t) sqlite3_column_int64 (result, + (int) column + 1); + amount->fraction = (uint32_t) frac; + return GNUNET_YES; +} + + +struct GNUNET_SQ_ResultSpec +TALER_SQ_result_spec_amount (const char *currency, + struct TALER_Amount *amount) +{ + struct GNUNET_SQ_ResultSpec res = { + .conv = &extract_amount, + .cls = (void *) currency, + .dst = (void *) amount, + .dst_size = sizeof (struct TALER_Amount), + .num_params = 2 + }; + + return res; +} + + +/** + * Extract amount data from a SQLite database + * + * @param cls closure, a `const char *` giving the currency + * @param result where to extract data from + * @param column column to extract data from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL) + */ +static enum GNUNET_GenericReturnValue +extract_amount_nbo (void *cls, + sqlite3_stmt *result, + unsigned int column, + size_t *dst_size, + void *dst) +{ + struct TALER_AmountNBO *amount = dst; + struct TALER_Amount amount_hbo; + size_t amount_hbo_size = sizeof (struct TALER_Amount); + + (void) cls; + (void) dst_size; + if (GNUNET_YES != + extract_amount (cls, + result, + column, + &amount_hbo_size, + &amount_hbo)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + TALER_amount_hton (amount, + &amount_hbo); + return GNUNET_YES; +} + + +struct GNUNET_SQ_ResultSpec +TALER_SQ_result_spec_amount_nbo (const char *currency, + struct TALER_AmountNBO *amount) +{ + struct GNUNET_SQ_ResultSpec res = { + .conv = &extract_amount_nbo, + .cls = (void *) currency, + .dst = (void *) amount, + .dst_size = sizeof (struct TALER_AmountNBO), + .num_params = 2 + }; + + return res; +} + + +/** + * Extract amount data from a SQLite database + * + * @param cls closure + * @param result where to extract data from + * @param column column to extract data from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL) + */ +static enum GNUNET_GenericReturnValue +extract_json (void *cls, + sqlite3_stmt *result, + unsigned int column, + size_t *dst_size, + void *dst) +{ + json_t **j_dst = dst; + const char *res; + json_error_t json_error; + size_t slen; + + (void) cls; + (void) dst_size; + if (SQLITE_TEXT != sqlite3_column_type (result, + column)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + res = (const char *) sqlite3_column_text (result, + column); + slen = strlen (res); + *j_dst = json_loadb (res, + slen, + JSON_REJECT_DUPLICATES, + &json_error); + if (NULL == *j_dst) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse JSON result for column %d: %s (%s)\n", + column, + json_error.text, + json_error.source); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called to clean up memory allocated + * by a #GNUNET_SQ_ResultConverter. + * + * @param cls closure + */ +static void +clean_json (void *cls) +{ + json_t **dst = cls; + + (void) cls; + if (NULL != *dst) + { + json_decref (*dst); + *dst = NULL; + } +} + + +/** + * json_t expected. + * + * @param[out] jp where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_SQ_ResultSpec +TALER_SQ_result_spec_json (json_t **jp) +{ + struct GNUNET_SQ_ResultSpec res = { + .conv = &extract_json, + .cleaner = &clean_json, + .dst = (void *) jp, + .cls = (void *) jp, + .num_params = 1 + }; + + return res; +} + + +/* end of sq/sq_result_helper.c */ diff --git a/src/sq/test_sq.c b/src/sq/test_sq.c @@ -0,0 +1,215 @@ +/* + This file is part of TALER + Copyright (C) 2020 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file sq/test_sq.c + * @brief Tests for SQLite3 convenience API + * @author Jonathan Buchanan + */ +#include "platform.h" +#include "taler_sq_lib.h" + + +/** + * Run actual test queries. + * + * @return 0 on success + */ +static int +run_queries (sqlite3 *db) +{ + struct TALER_Amount hamount; + struct TALER_AmountNBO namount; + json_t *json; + sqlite3_stmt *test_insert; + sqlite3_stmt *test_select; + struct GNUNET_SQ_PrepareStatement ps[] = { + GNUNET_SQ_make_prepare ("INSERT INTO test_sq (" + " hamount_val" + ",hamount_frac" + ",namount_val" + ",namount_frac" + ",json" + ") VALUES " + "($1, $2, $3, $4, $5)", + &test_insert), + GNUNET_SQ_make_prepare ("SELECT" + " hamount_val" + ",hamount_frac" + ",namount_val" + ",namount_frac" + ",json" + " FROM test_sq", + &test_select), + GNUNET_SQ_PREPARE_END + }; + int ret = 0; + + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("EUR:1.23", + &hamount)); + TALER_amount_hton (&namount, + &hamount); + json = json_object (); + GNUNET_assert (NULL != json); + GNUNET_assert (0 == + json_object_set_new (json, + "foo", + json_integer (42))); + GNUNET_assert (NULL != json); + GNUNET_assert (GNUNET_OK == + GNUNET_SQ_prepare (db, + ps)); + + { + struct GNUNET_SQ_QueryParam params_insert[] = { + TALER_SQ_query_param_amount (&hamount), + TALER_SQ_query_param_amount_nbo (&namount), + TALER_SQ_query_param_json (json), + GNUNET_SQ_query_param_end + }; + GNUNET_SQ_reset (db, + test_insert); + GNUNET_assert (GNUNET_OK == GNUNET_SQ_bind (test_insert, + params_insert)); + GNUNET_assert (SQLITE_DONE == sqlite3_step (test_insert)); + sqlite3_finalize (test_insert); + } + + { + struct TALER_Amount result_amount; + struct TALER_AmountNBO nresult_amount; + struct TALER_Amount nresult_amount_converted; + json_t *result_json; + struct GNUNET_SQ_QueryParam params_select[] = { + GNUNET_SQ_query_param_end + }; + struct GNUNET_SQ_ResultSpec results_select[] = { + TALER_SQ_result_spec_amount ("EUR", + &result_amount), + TALER_SQ_result_spec_amount_nbo ("EUR", + &nresult_amount), + TALER_SQ_result_spec_json (&result_json), + GNUNET_SQ_result_spec_end + }; + + GNUNET_SQ_reset (db, + test_select); + GNUNET_assert (GNUNET_OK == GNUNET_SQ_bind (test_select, + params_select)); + GNUNET_assert (SQLITE_ROW == sqlite3_step (test_select)); + + GNUNET_assert (GNUNET_OK == GNUNET_SQ_extract_result (test_select, + results_select)); + TALER_amount_ntoh (&nresult_amount_converted, + &nresult_amount); + if ((GNUNET_OK != TALER_amount_cmp_currency (&hamount, + &result_amount)) || + (0 != TALER_amount_cmp (&hamount, + &result_amount)) || + (GNUNET_OK != TALER_amount_cmp_currency (&hamount, + &nresult_amount_converted)) || + (0 != TALER_amount_cmp (&hamount, + &nresult_amount_converted)) || + (1 != json_equal (json, + result_json)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Result from database doesn't match input\n"); + ret = 1; + } + GNUNET_SQ_cleanup_result (results_select); + sqlite3_finalize (test_select); + } + json_decref (json); + + return ret; +} + + +int +main (int argc, + const char *const argv[]) +{ + struct GNUNET_SQ_ExecuteStatement es[] = { + GNUNET_SQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_sq (" + " hamount_val INT8 NOT NULL" + ",hamount_frac INT8 NOT NULL" + ",namount_val INT8 NOT NULL" + ",namount_frac INT8 NOT NULL" + ",json VARCHAR NOT NULL" + ")"), + GNUNET_SQ_EXECUTE_STATEMENT_END + }; + sqlite3 *db; + int ret; + + (void) argc; + (void) argv; + GNUNET_log_setup ("test-pq", + "WARNING", + NULL); + + if (SQLITE_OK != sqlite3_open ("talercheck.db", + &db)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to open SQLite3 database\n"); + return 77; + } + + if (GNUNET_OK != GNUNET_SQ_exec_statements (db, + es)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to create new table\n"); + if ((SQLITE_OK != sqlite3_close (db)) || + (0 != unlink ("talercheck.db"))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to close db or unlink\n"); + } + return 1; + } + + ret = run_queries (db); + + if (SQLITE_OK != + sqlite3_exec (db, + "DROP TABLE test_sq", + NULL, NULL, NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to drop table\n"); + ret = 1; + } + + if (SQLITE_OK != sqlite3_close (db)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to close database\n"); + ret = 1; + } + if (0 != unlink ("talercheck.db")) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to unlink test database file\n"); + ret = 1; + } + return ret; +} + + +/* end of sq/test_sq.c */