[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-donau] 03/03: [build] copy src/{curl,json,mhd,pq,sq} from exchang
From: |
gnunet |
Subject: |
[taler-donau] 03/03: [build] copy src/{curl,json,mhd,pq,sq} from exchange to build succesfully |
Date: |
Mon, 23 Oct 2023 14:18:24 +0200 |
This is an automated email from the git hooks/post-receive script.
pius-loosli pushed a commit to branch master
in repository donau.
commit a9e76052a241f6e666ec02c64ab2d2456453ac55
Author: Pius Loosli <loosp2@bfh.ch>
AuthorDate: Mon Oct 23 14:16:26 2023 +0200
[build] copy src/{curl,json,mhd,pq,sq} from exchange to build succesfully
---
src/curl/Makefile.am | 24 +
src/curl/curl.c | 107 ++++
src/json/.gitignore | 1 +
src/json/Makefile.am | 43 ++
src/json/i18n.c | 134 +++++
src/json/json.c | 886 ++++++++++++++++++++++++++++
src/json/json_helper.c | 1366 +++++++++++++++++++++++++++++++++++++++++++
src/json/json_pack.c | 308 ++++++++++
src/json/json_wire.c | 122 ++++
src/json/test_json.c | 439 ++++++++++++++
src/mhd/Makefile.am | 29 +
src/mhd/mhd_config.c | 493 ++++++++++++++++
src/mhd/mhd_legal.c | 694 ++++++++++++++++++++++
src/mhd/mhd_parsing.c | 444 ++++++++++++++
src/mhd/mhd_responses.c | 550 +++++++++++++++++
src/mhd/mhd_run.c | 175 ++++++
src/pq/Makefile.am | 42 ++
src/pq/pq_common.c | 68 +++
src/pq/pq_common.h | 125 ++++
src/pq/pq_query_helper.c | 1183 +++++++++++++++++++++++++++++++++++++
src/pq/pq_result_helper.c | 1424 +++++++++++++++++++++++++++++++++++++++++++++
src/pq/test_pq.c | 250 ++++++++
src/sq/Makefile.am | 40 ++
src/sq/sq_query_helper.c | 175 ++++++
src/sq/sq_result_helper.c | 237 ++++++++
src/sq/test_sq.c | 215 +++++++
26 files changed, 9574 insertions(+)
diff --git a/src/curl/Makefile.am b/src/curl/Makefile.am
new file mode 100644
index 0000000..f60a380
--- /dev/null
+++ 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
new file mode 100644
index 0000000..caa0052
--- /dev/null
+++ 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
new file mode 100644
index 0000000..d288434
--- /dev/null
+++ b/src/json/.gitignore
@@ -0,0 +1 @@
+test_json_wire
diff --git a/src/json/Makefile.am b/src/json/Makefile.am
new file mode 100644
index 0000000..6bd5b46
--- /dev/null
+++ 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
new file mode 100644
index 0000000..f927a71
--- /dev/null
+++ 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
new file mode 100644
index 0000000..fb00fb5
--- /dev/null
+++ 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
new file mode 100644
index 0000000..6c96035
--- /dev/null
+++ 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",
+ ¤cy),
+ 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
new file mode 100644
index 0000000..c6844c1
--- /dev/null
+++ 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
new file mode 100644
index 0000000..9d22d28
--- /dev/null
+++ 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
new file mode 100644
index 0000000..d37f66e
--- /dev/null
+++ 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
new file mode 100644
index 0000000..f7f052d
--- /dev/null
+++ 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
new file mode 100644
index 0000000..31ec3e4
--- /dev/null
+++ 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
new file mode 100644
index 0000000..137cc43
--- /dev/null
+++ 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
new file mode 100644
index 0000000..381b064
--- /dev/null
+++ 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
new file mode 100644
index 0000000..7dd6824
--- /dev/null
+++ 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
new file mode 100644
index 0000000..8388fbf
--- /dev/null
+++ 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
new file mode 100644
index 0000000..4b192d7
--- /dev/null
+++ 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
new file mode 100644
index 0000000..8b6f8f2
--- /dev/null
+++ 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
new file mode 100644
index 0000000..6172c0b
--- /dev/null
+++ 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
new file mode 100644
index 0000000..dd6cf67
--- /dev/null
+++ 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
new file mode 100644
index 0000000..95850bc
--- /dev/null
+++ 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
new file mode 100644
index 0000000..237c8a9
--- /dev/null
+++ 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
new file mode 100644
index 0000000..f27dec3
--- /dev/null
+++ 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
new file mode 100644
index 0000000..711e638
--- /dev/null
+++ 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
new file mode 100644
index 0000000..9d80837
--- /dev/null
+++ 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
new file mode 100644
index 0000000..8f464fa
--- /dev/null
+++ 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 */
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.