From d75a2de4e9a450269291a47593f2e50c37eca733 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 5 Feb 2016 22:24:10 +0000 Subject: creating libgnunetpq library --- configure.ac | 4 +- pkgconfig/gnunetpq.pc.in | 12 + po/POTFILES.in | 19 +- src/Makefile.am | 2 +- src/include/Makefile.am | 1 + src/include/gnunet_postgres_lib.h | 40 ++- src/include/gnunet_pq_lib.h | 454 +++++++++++++++++++++++++ src/postgres/postgres.c | 61 +++- src/pq/Makefile.am | 39 +++ src/pq/pq.c | 169 ++++++++++ src/pq/pq_query_helper.c | 386 ++++++++++++++++++++++ src/pq/pq_result_helper.c | 674 ++++++++++++++++++++++++++++++++++++++ src/pq/test_pq.c | 288 ++++++++++++++++ 13 files changed, 2111 insertions(+), 38 deletions(-) create mode 100644 pkgconfig/gnunetpq.pc.in create mode 100644 src/include/gnunet_pq_lib.h create mode 100644 src/pq/Makefile.am create mode 100644 src/pq/pq.c create mode 100644 src/pq/pq_query_helper.c create mode 100644 src/pq/pq_result_helper.c create mode 100644 src/pq/test_pq.c diff --git a/configure.ac b/configure.ac index fcf0e7f4d..42f7f5feb 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ # This file is part of GNUnet. -# (C) 2001--2015 Christian Grothoff (and other contributing authors) +# (C) 2001--2016 Christian Grothoff (and other contributing authors) # # GNUnet is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published @@ -1566,6 +1566,7 @@ src/peerinfo/peerinfo.conf src/peerinfo-tool/Makefile src/peerstore/Makefile src/peerstore/peerstore.conf +src/pq/Makefile src/postgres/Makefile src/psycutil/Makefile src/psyc/Makefile @@ -1635,6 +1636,7 @@ pkgconfig/gnunetnamestore.pc pkgconfig/gnunetnat.pc pkgconfig/gnunetnse.pc pkgconfig/gnunetpeerinfo.pc +pkgconfig/gnunetpq.pc pkgconfig/gnunetpostgres.pc pkgconfig/gnunetpsyc.pc pkgconfig/gnunetpsycstore.pc diff --git a/pkgconfig/gnunetpq.pc.in b/pkgconfig/gnunetpq.pc.in new file mode 100644 index 000000000..b24ea220b --- /dev/null +++ b/pkgconfig/gnunetpq.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: GNUnet PQ +Description: API with common functions for interacting with libpq +URL: http://gnunet.org +Version: @VERSION@ +Requires: +Libs: -L${libdir} -lgnunetpq +Cflags: -I${includedir} diff --git a/po/POTFILES.in b/po/POTFILES.in index afe2b47b3..c4665a775 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -123,7 +123,6 @@ src/dv/dv_api.c src/dv/gnunet-dv.c src/dv/gnunet-service-dv.c src/dv/plugin_transport_dv.c -src/env/env.c src/exit/gnunet-daemon-exit.c src/exit/gnunet-helper-exit.c src/exit/gnunet-helper-exit-windows.c @@ -203,9 +202,13 @@ src/identity/gnunet-identity.c src/identity/gnunet-service-identity.c src/identity/identity_api.c src/identity/identity_api_lookup.c +src/identity/plugin_gnsrecord_identity.c src/identity/plugin_rest_identity.c -src/identity-token/gnunet-identity-token.c -src/identity-token/plugin_rest_identity_token.c +src/identity-provider/gnunet-identity-token.c +src/identity-provider/gnunet-service-identity-provider.c +src/identity-provider/identity_provider_api.c +src/identity-provider/identity_token.c +src/identity-provider/plugin_rest_identity_provider.c src/multicast/gnunet-multicast.c src/multicast/gnunet-service-multicast.c src/multicast/multicast_api.c @@ -251,12 +254,17 @@ src/peerstore/peerstore_api.c src/peerstore/peerstore_common.c src/peerstore/plugin_peerstore_sqlite.c src/postgres/postgres.c +src/pq/pq.c +src/pq/pq_query_helper.c +src/pq/pq_result_helper.c src/psyc/gnunet-service-psyc.c src/psyc/psyc_api.c src/psycstore/gnunet-service-psycstore.c src/psycstore/plugin_psycstore_sqlite.c src/psycstore/psycstore_api.c -src/psycstore/psyc_util_lib.c +src/psycutil/psyc_env.c +src/psycutil/psyc_message.c +src/psycutil/psyc_slicer.c src/pt/gnunet-daemon-pt.c src/regex/gnunet-daemon-regexprofiler.c src/regex/gnunet-regex-profiler.c @@ -278,8 +286,11 @@ src/revocation/gnunet-service-revocation.c src/revocation/revocation_api.c src/rps/gnunet-rps.c src/rps/gnunet-service-rps.c +src/rps/gnunet-service-rps_custommap.c +src/rps/gnunet-service-rps_peers.c src/rps/gnunet-service-rps_sampler.c src/rps/gnunet-service-rps_sampler_elem.c +src/rps/gnunet-service-rps_view.c src/rps/rps_api.c src/rps/rps-test_util.c src/scalarproduct/gnunet-scalarproduct.c diff --git a/src/Makefile.am b/src/Makefile.am index 925d395aa..edfbb6922 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -44,7 +44,7 @@ if HAVE_MYSQL endif if HAVE_POSTGRESQL - POSTGRES_DIR = postgres + POSTGRES_DIR = pq postgres endif if HAVE_REST diff --git a/src/include/Makefile.am b/src/include/Makefile.am index dd32da171..626089dbc 100644 --- a/src/include/Makefile.am +++ b/src/include/Makefile.am @@ -84,6 +84,7 @@ gnunetinclude_HEADERS = \ gnunet_peerstore_plugin.h \ gnunet_peerstore_service.h \ gnunet_plugin_lib.h \ + gnunet_pq_lib.h \ gnunet_postgres_lib.h \ gnunet_psycstore_plugin.h \ gnunet_psycstore_service.h \ diff --git a/src/include/gnunet_postgres_lib.h b/src/include/gnunet_postgres_lib.h index 79fb66f67..0307df50d 100644 --- a/src/include/gnunet_postgres_lib.h +++ b/src/include/gnunet_postgres_lib.h @@ -45,7 +45,7 @@ extern "C" /** * Check if the result obtained from Postgres has * the desired status code. If not, log an error, clear the - * result and return GNUNET_SYSERR. + * result and return #GNUNET_SYSERR. * * @param dbh database handle * @param ret return value from database operation to check @@ -54,25 +54,29 @@ extern "C" * @param args arguments given to the command * @param filename name of the source file where the command was run * @param line line number in the source file - * @return GNUNET_OK if the result is acceptable + * @return #GNUNET_OK if the result is acceptable */ int -GNUNET_POSTGRES_check_result_ (PGconn *dbh, PGresult * ret, int expected_status, - const char *command, const char *args, - const char *filename, int line); +GNUNET_POSTGRES_check_result_ (PGconn *dbh, + PGresult *ret, + int expected_status, + const char *command, + const char *args, + const char *filename, + int line); /** * Check if the result obtained from Postgres has * the desired status code. If not, log an error, clear the - * result and return GNUNET_SYSERR. + * result and return #GNUNET_SYSERR. * * @param dbh database handle * @param ret return value from database operation to check * @param expected_status desired status * @param command description of the command that was run * @param args arguments given to the command - * @return GNUNET_OK if the result is acceptable + * @return #GNUNET_OK if the result is acceptable */ #define GNUNET_POSTGRES_check_result(dbh,ret,expected_status,command,args) GNUNET_POSTGRES_check_result_(dbh,ret,expected_status,command,args,__FILE__,__LINE__) @@ -84,10 +88,13 @@ GNUNET_POSTGRES_check_result_ (PGconn *dbh, PGresult * ret, int expected_status, * @param sql statement to run * @param filename filename for error reporting * @param line code line for error reporting - * @return GNUNET_OK on success + * @return #GNUNET_OK on success */ int -GNUNET_POSTGRES_exec_ (PGconn *dbh, const char *sql, const char *filename, int line); +GNUNET_POSTGRES_exec_ (PGconn *dbh, + const char *sql, + const char *filename, + int line); /** @@ -95,7 +102,7 @@ GNUNET_POSTGRES_exec_ (PGconn *dbh, const char *sql, const char *filename, int l * * @param dbh database handle * @param sql statement to run - * @return GNUNET_OK on success + * @return #GNUNET_OK on success */ #define GNUNET_POSTGRES_exec(dbh,sql) GNUNET_POSTGRES_exec_(dbh,sql,__FILE__,__LINE__) @@ -109,12 +116,15 @@ GNUNET_POSTGRES_exec_ (PGconn *dbh, const char *sql, const char *filename, int l * @param nparams number of parameters in sql * @param filename filename for error reporting * @param line code line for error reporting - * @return GNUNET_OK on success + * @return #GNUNET_OK on success */ int -GNUNET_POSTGRES_prepare_ (PGconn *dbh, const char *name, const char *sql, +GNUNET_POSTGRES_prepare_ (PGconn *dbh, + const char *name, + const char *sql, int nparams, - const char *filename, int line); + const char *filename, + int line); /** @@ -124,7 +134,7 @@ GNUNET_POSTGRES_prepare_ (PGconn *dbh, const char *name, const char *sql, * @param name name for the prepared SQL statement * @param sql SQL code to prepare * @param nparams number of parameters in sql - * @return GNUNET_OK on success + * @return #GNUNET_OK on success */ #define GNUNET_POSTGRES_prepare(dbh,name,sql,nparams) GNUNET_POSTGRES_prepare_(dbh,name,sql,nparams,__FILE__,__LINE__) @@ -148,7 +158,7 @@ GNUNET_POSTGRES_connect (const struct GNUNET_CONFIGURATION_Handle *cfg, * @param dbh database handle * @param stmt name of the prepared statement * @param rowid which row to delete - * @return GNUNET_OK on success + * @return #GNUNET_OK on success */ int GNUNET_POSTGRES_delete_by_rowid (PGconn *dbh, diff --git a/src/include/gnunet_pq_lib.h b/src/include/gnunet_pq_lib.h new file mode 100644 index 000000000..dd41406d3 --- /dev/null +++ b/src/include/gnunet_pq_lib.h @@ -0,0 +1,454 @@ +/* + This file is part of GNUnet + Copyright (C) 2016 GNUnet e.V. + + GNUnet 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. + + GNUnet 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 + GNUnet; see the file COPYING. If not, If not, see +*/ +/** + * @file include/gnunet_pq_lib.h + * @brief helper functions for DB interactions + * @author Christian Grothoff + */ +#ifndef GNUNET_PQ_LIB_H_ +#define GNUNET_PQ_LIB_H_ + +#include +#include "gnunet_util_lib.h" + + +/** + * 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 + */ +typedef int +(*GNUNET_PQ_QueryConverter)(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); + + +/** + * @brief Description of a DB query parameter. + */ +struct GNUNET_PQ_QueryParam +{ + + /** + * Format of the rest of the entry, determines the data + * type that is being added to the query. + */ + GNUNET_PQ_QueryConverter conv; + + /** + * Closure for @e conv. + */ + void *conv_cls; + + /** + * Data or NULL. + */ + const void *data; + + /** + * Size of @e data + */ + size_t size; + + /** + * Number of parameters eaten by this operation. + */ + unsigned int num_params; +}; + + +/** + * End of query parameter specification. + */ +#define GNUNET_PQ_query_param_end { NULL, NULL, NULL, 0, 0 } + + +/** + * Generate query parameter for a buffer @a ptr of + * @a ptr_size bytes. + * + * @param ptr pointer to the query parameter to pass + * @oaran ptr_size number of bytes in @a ptr + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_fixed_size (const void *ptr, + size_t ptr_size); + + +/** + * Generate fixed-size query parameter with size determined + * by variable type. + * + * @param x pointer to the query parameter to pass. + */ +#define GNUNET_PQ_query_param_auto_from_type(x) GNUNET_PQ_query_param_fixed_size ((x), sizeof (*(x))) + + +/** + * Generate query parameter for an RSA public key. The + * database must contain a BLOB type in the respective position. + * + * @param x the query parameter to pass. + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_rsa_public_key (const struct GNUNET_CRYPTO_rsa_PublicKey *x); + + +/** + * Generate query parameter for an RSA signature. The + * database must contain a BLOB type in the respective position. + * + * @param x the query parameter to pass + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_rsa_signature (const struct GNUNET_CRYPTO_rsa_Signature *x); + + +/** + * Generate query parameter for an absolute time value. + * The database must store a 64-bit integer. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_absolute_time (const struct GNUNET_TIME_Absolute *x); + + +/** + * Generate query parameter for an absolute time value. + * The database must store a 64-bit integer. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_absolute_time_nbo (const struct GNUNET_TIME_AbsoluteNBO *x); + + +/** + * Generate query parameter for an uint16_t in host byte order. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_uint16 (const uint16_t *x); + + +/** + * Generate query parameter for an uint32_t in host byte order. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_uint32 (const uint32_t *x); + + +/** + * Generate query parameter for an uint16_t in host byte order. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_uint64 (const uint64_t *x); + + +/** + * Extract data from a Postgres database @a result at row @a row. + * + * @param cls closure + * @param result where to extract data from + * @param int 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) + */ +typedef int +(*GNUNET_PQ_ResultConverter)(void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst); + + +/** + * Function called to clean up memory allocated + * by a #GNUNET_PQ_ResultConverter. + * + * @param cls closure + * @param rd result data to clean up + */ +typedef void +(*GNUNET_PQ_ResultCleanup)(void *cls, + void *rd); + + +/** + * @brief Description of a DB result cell. + */ +struct GNUNET_PQ_ResultSpec +{ + + /** + * What is the format of the result? + */ + GNUNET_PQ_ResultConverter conv; + + /** + * Function to clean up result data, NULL if cleanup is + * not necessary. + */ + GNUNET_PQ_ResultCleanup cleaner; + + /** + * Closure for @e conv and @e cleaner. + */ + void *cls; + + /** + * Destination for the data. + */ + void *dst; + + /** + * Allowed size for the data, 0 for variable-size + * (in this case, the type of @e dst is a `void **` + * and we need to allocate a buffer of the right size). + */ + size_t dst_size; + + /** + * Field name of the desired result. + */ + const char *fname; + + /** + * Where to store actual size of the result. + */ + size_t *result_size; + +}; + + +/** + * End of result parameter specification. + * + * @return array last entry for the result specification to use + */ +#define GNUNET_PQ_result_spec_end { NULL, NULL, NULL, NULL, 0, NULL, NULL } + + +/** + * Variable-size result expected. + * + * @param name name of the field in the table + * @param[out] dst where to store the result, allocated + * @param[out] sptr where to store the size of @a dst + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_variable_size (const char *name, + void **dst, + size_t *sptr); + + +/** + * Fixed-size result expected. + * + * @param name name of the field in the table + * @param[out] dst where to store the result + * @param dst_size number of bytes in @a dst + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_fixed_size (const char *name, + void *dst, + size_t dst_size); + + + +/** + * We expect a fixed-size result, with size determined by the type of `* dst` + * + * @param name name of the field in the table + * @param dst point to where to store the result, type fits expected result size + * @return array entry for the result specification to use + */ +#define GNUNET_PQ_result_spec_auto_from_type(name, dst) GNUNET_PQ_result_spec_fixed_size (name, (dst), sizeof (*(dst))) + + +/** + * Variable-size result expected. + * + * @param name name of the field in the table + * @param[out] dst where to store the result, allocated + * @param[out] sptr where to store the size of @a dst + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_variable_size (const char *name, + void **dst, + size_t *sptr); + + +/** + * RSA public key expected. + * + * @param name name of the field in the table + * @param[out] rsa where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_rsa_public_key (const char *name, + struct GNUNET_CRYPTO_rsa_PublicKey **rsa); + + +/** + * RSA signature expected. + * + * @param name name of the field in the table + * @param[out] sig where to store the result; + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_rsa_signature (const char *name, + struct GNUNET_CRYPTO_rsa_Signature **sig); + + +/** + * Absolute time expected. + * + * @param name name of the field in the table + * @param[out] at where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_absolute_time (const char *name, + struct GNUNET_TIME_Absolute *at); + + +/** + * Absolute time expected. + * + * @param name name of the field in the table + * @param[out] at where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_absolute_time_nbo (const char *name, + struct GNUNET_TIME_AbsoluteNBO *at); + + +/** + * uint16_t expected. + * + * @param name name of the field in the table + * @param[out] u16 where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_uint16 (const char *name, + uint16_t *u16); + + +/** + * uint32_t expected. + * + * @param name name of the field in the table + * @param[out] u32 where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_uint32 (const char *name, + uint32_t *u32); + + +/** + * uint64_t expected. + * + * @param name name of the field in the table + * @param[out] u64 where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_uint64 (const char *name, + uint64_t *u64); + + +/** + * Execute a prepared statement. + * + * @param db_conn database connection + * @param name name of the prepared statement + * @param params parameters to the statement + * @return postgres result + */ +PGresult * +GNUNET_PQ_exec_prepared (PGconn *db_conn, + const char *name, + const struct GNUNET_PQ_QueryParam *params); + + +/** + * Extract results from a query result according to the given specification. + * If colums are NULL, the destination is not modified, and #GNUNET_NO + * is returned. + * + * @param result result to process + * @param[in,out] rs result specification to extract for + * @param row row from the result to extract + * @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) + */ +int +GNUNET_PQ_extract_result (PGresult *result, + struct GNUNET_PQ_ResultSpec *rs, + int row); + + +/** + * Free all memory that was allocated in @a rs during + * #GNUNET_PQ_extract_result(). + * + * @param rs reult specification to clean up + */ +void +GNUNET_PQ_cleanup_result (struct GNUNET_PQ_ResultSpec *rs); + + +#endif /* GNUNET_PQ_LIB_H_ */ + +/* end of include/gnunet_pq_lib.h */ diff --git a/src/postgres/postgres.c b/src/postgres/postgres.c index c03e312a5..856031d75 100644 --- a/src/postgres/postgres.c +++ b/src/postgres/postgres.c @@ -38,12 +38,16 @@ * @param args arguments given to the command * @param filename name of the source file where the command was run * @param line line number in the source file - * @return GNUNET_OK if the result is acceptable + * @return #GNUNET_OK if the result is acceptable */ int -GNUNET_POSTGRES_check_result_ (PGconn * dbh, PGresult * ret, - int expected_status, const char *command, - const char *args, const char *filename, int line) +GNUNET_POSTGRES_check_result_ (PGconn *dbh, + PGresult *ret, + int expected_status, + const char *command, + const char *args, + const char *filename, + int line) { if (ret == NULL) { @@ -72,10 +76,12 @@ GNUNET_POSTGRES_check_result_ (PGconn * dbh, PGresult * ret, * @param sql statement to run * @param filename filename for error reporting * @param line code line for error reporting - * @return GNUNET_OK on success + * @return #GNUNET_OK on success */ int -GNUNET_POSTGRES_exec_ (PGconn * dbh, const char *sql, const char *filename, +GNUNET_POSTGRES_exec_ (PGconn * dbh, + const char *sql, + const char *filename, int line) { PGresult *ret; @@ -99,18 +105,30 @@ GNUNET_POSTGRES_exec_ (PGconn * dbh, const char *sql, const char *filename, * @param nparams number of parameters in sql * @param filename filename for error reporting * @param line code line for error reporting - * @return GNUNET_OK on success + * @return #GNUNET_OK on success */ int -GNUNET_POSTGRES_prepare_ (PGconn * dbh, const char *name, const char *sql, - int nparams, const char *filename, int line) +GNUNET_POSTGRES_prepare_ (PGconn *dbh, + const char *name, + const char *sql, + int nparams, + const char *filename, + int line) { PGresult *ret; - ret = PQprepare (dbh, name, sql, nparams, NULL); + ret = PQprepare (dbh, + name, + sql, + nparams, NULL); if (GNUNET_OK != - GNUNET_POSTGRES_check_result_ (dbh, ret, PGRES_COMMAND_OK, "PQprepare", - sql, filename, line)) + GNUNET_POSTGRES_check_result_ (dbh, + ret, + PGRES_COMMAND_OK, + "PQprepare", + sql, + filename, + line)) return GNUNET_SYSERR; PQclear (ret); return GNUNET_OK; @@ -161,10 +179,12 @@ GNUNET_POSTGRES_connect (const struct GNUNET_CONFIGURATION_Handle * cfg, * @param dbh database handle * @param stmt name of the prepared statement * @param rowid which row to delete - * @return GNUNET_OK on success + * @return #GNUNET_OK on success */ int -GNUNET_POSTGRES_delete_by_rowid (PGconn * dbh, const char *stmt, uint32_t rowid) +GNUNET_POSTGRES_delete_by_rowid (PGconn * dbh, + const char *stmt, + uint32_t rowid) { uint32_t brow = htonl (rowid); const char *paramValues[] = { (const char *) &brow }; @@ -173,10 +193,17 @@ GNUNET_POSTGRES_delete_by_rowid (PGconn * dbh, const char *stmt, uint32_t rowid) PGresult *ret; ret = - PQexecPrepared (dbh, stmt, 1, paramValues, paramLengths, paramFormats, 1); + PQexecPrepared (dbh, stmt, 1, + paramValues, + paramLengths, + paramFormats, + 1); if (GNUNET_OK != - GNUNET_POSTGRES_check_result_ (dbh, ret, PGRES_COMMAND_OK, - "PQexecPrepared", "delrow", __FILE__, + GNUNET_POSTGRES_check_result_ (dbh, ret, + PGRES_COMMAND_OK, + "PQexecPrepared", + "delrow", + __FILE__, __LINE__)) { return GNUNET_SYSERR; diff --git a/src/pq/Makefile.am b/src/pq/Makefile.am new file mode 100644 index 000000000..66f57838e --- /dev/null +++ b/src/pq/Makefile.am @@ -0,0 +1,39 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include $(POSTGRESQL_CPPFLAGS) + +if MINGW + WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols +endif + +if USE_COVERAGE + AM_CFLAGS = --coverage +endif + +if HAVE_POSTGRESQL +lib_LTLIBRARIES = libgnunetpq.la +endif + +libgnunetpq_la_SOURCES = \ + pq.c \ + pq_query_helper.c \ + pq_result_helper.c +libgnunetpq_la_LIBADD = -lpq \ + $(top_builddir)/src/util/libgnunetutil.la +libgnunetpq_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + $(GN_LIB_LDFLAGS) \ + -version-info 0:0:0 + + +TESTS = \ + test_pq + +check_PROGRAMS= \ + test_pq + +test_pq_SOURCES = \ + test_pq.c +test_pq_LDADD = \ + libgnunetpq.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + -lpq $(XLIB) diff --git a/src/pq/pq.c b/src/pq/pq.c new file mode 100644 index 000000000..fe796044e --- /dev/null +++ b/src/pq/pq.c @@ -0,0 +1,169 @@ +/* + This file is part of GNUnet + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + GNUnet 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. + + GNUnet 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 + GNUnet; see the file COPYING. If not, If not, see +*/ +/** + * @file pq/pq.c + * @brief helper functions for libpq (PostGres) interactions + * @author Sree Harsha Totakura + * @author Florian Dold + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "gnunet_pq_lib.h" + + +/** + * Execute a prepared statement. + * + * @param db_conn database connection + * @param name name of the prepared statement + * @param params parameters to the statement + * @return postgres result + */ +PGresult * +GNUNET_PQ_exec_prepared (PGconn *db_conn, + const char *name, + const struct GNUNET_PQ_QueryParam *params) +{ + unsigned int len; + unsigned int i; + + /* count the number of parameters */ + len = 0; + for (i=0;0 != params[i].num_params;i++) + len += params[i].num_params; + + /* new scope to allow stack allocation without alloca */ + { + /* Scratch buffer for temporary storage */ + void *scratch[len]; + /* Parameter array we are building for the query */ + void *param_values[len]; + int param_lengths[len]; + int param_formats[len]; + unsigned int off; + /* How many entries in the scratch buffer are in use? */ + unsigned int soff; + PGresult *res; + int ret; + + off = 0; + soff = 0; + for (i=0;0 != params[i].num_params;i++) + { + const struct GNUNET_PQ_QueryParam *x = ¶ms[i]; + + ret = x->conv (x->conv_cls, + x->data, + x->size, + ¶m_values[off], + ¶m_lengths[off], + ¶m_formats[off], + x->num_params, + &scratch[soff], + len - soff); + if (ret < 0) + { + for (off = 0; off < soff; off++) + GNUNET_free (scratch[off]); + return NULL; + } + soff += ret; + off += x->num_params; + } + GNUNET_assert (off == len); + res = PQexecPrepared (db_conn, + name, + len, + (const char **) param_values, + param_lengths, + param_formats, + 1); + for (off = 0; off < soff; off++) + GNUNET_free (scratch[off]); + return res; + } +} + + +/** + * Free all memory that was allocated in @a rs during + * #GNUNET_PQ_extract_result(). + * + * @param rs reult specification to clean up + */ +void +GNUNET_PQ_cleanup_result (struct GNUNET_PQ_ResultSpec *rs) +{ + unsigned int i; + + for (i=0; NULL != rs[i].conv; i++) + if (NULL != rs[i].cleaner) + rs[i].cleaner (rs[i].cls, + rs[i].dst); +} + + +/** + * Extract results from a query result according to the given + * specification. If colums are NULL, the destination is not + * modified, and #GNUNET_NO is returned. + * + * @param result result to process + * @param[in,out] rs result specification to extract for + * @param row row from the result to extract + * @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) + */ +int +GNUNET_PQ_extract_result (PGresult *result, + struct GNUNET_PQ_ResultSpec *rs, + int row) +{ + unsigned int i; + int had_null = GNUNET_NO; + int ret; + + for (i=0; NULL != rs[i].conv; i++) + { + struct GNUNET_PQ_ResultSpec *spec; + + spec = &rs[i]; + ret = spec->conv (spec->cls, + result, + row, + spec->fname, + &spec->dst_size, + spec->dst); + if (GNUNET_SYSERR == ret) + return GNUNET_SYSERR; + if (GNUNET_NO == ret) + { + had_null = GNUNET_YES; + continue; + } + if (NULL != spec->result_size) + *spec->result_size = spec->dst_size; + } + if (GNUNET_YES == had_null) + return GNUNET_NO; + return GNUNET_OK; +} + + +/* end of pq/pq.c */ diff --git a/src/pq/pq_query_helper.c b/src/pq/pq_query_helper.c new file mode 100644 index 000000000..5f2764b62 --- /dev/null +++ b/src/pq/pq_query_helper.c @@ -0,0 +1,386 @@ + /* + This file is part of GNUnet + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + GNUnet 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. + + GNUnet 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 + GNUnet; see the file COPYING. If not, If not, see +*/ +/** + * @file pq/pq_query_helper.c + * @brief functions to initialize parameter arrays + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "gnunet_pq_lib.h" + + +/** + * 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_fixed (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) +{ + GNUNET_break (NULL == cls); + if (1 != param_length) + return -1; + param_values[0] = (void *) data; + param_lengths[0] = data_len; + param_formats[0] = 1; + return 0; +} + + +/** + * Generate query parameter for a buffer @a ptr of + * @a ptr_size bytes. + * + * @param ptr pointer to the query parameter to pass + * @oaran ptr_size number of bytes in @a ptr + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_fixed_size (const void *ptr, + size_t ptr_size) +{ + struct GNUNET_PQ_QueryParam res = + { &qconv_fixed, NULL, ptr, ptr_size, 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_uint16 (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 uint16_t *u_hbo = data; + uint16_t *u_nbo; + + GNUNET_break (NULL == cls); + if (1 != param_length) + return -1; + u_nbo = GNUNET_new (uint16_t); + scratch[0] = u_nbo; + *u_nbo = htons (*u_hbo); + param_values[0] = (void *) u_nbo; + param_lengths[0] = sizeof (uint16_t); + param_formats[0] = 1; + return 1; +} + + +/** + * Generate query parameter for an uint16_t in host byte order. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_uint16 (const uint16_t *x) +{ + struct GNUNET_PQ_QueryParam res = + { &qconv_uint16, NULL, x, sizeof (*x), 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_uint32 (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 uint32_t *u_hbo = data; + uint32_t *u_nbo; + + GNUNET_break (NULL == cls); + if (1 != param_length) + return -1; + u_nbo = GNUNET_new (uint32_t); + scratch[0] = u_nbo; + *u_nbo = htonl (*u_hbo); + param_values[0] = (void *) u_nbo; + param_lengths[0] = sizeof (uint32_t); + param_formats[0] = 1; + return 1; +} + + +/** + * Generate query parameter for an uint32_t in host byte order. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_uint32 (const uint32_t *x) +{ + struct GNUNET_PQ_QueryParam res = + { &qconv_uint32, NULL, x, sizeof (*x), 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_uint64 (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 uint64_t *u_hbo = data; + uint64_t *u_nbo; + + GNUNET_break (NULL == cls); + if (1 != param_length) + return -1; + u_nbo = GNUNET_new (uint64_t); + scratch[0] = u_nbo; + *u_nbo = GNUNET_htonll (*u_hbo); + param_values[0] = (void *) u_nbo; + param_lengths[0] = sizeof (uint64_t); + param_formats[0] = 1; + return 1; +} + + +/** + * Generate query parameter for an uint64_t in host byte order. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_uint64 (const uint64_t *x) +{ + struct GNUNET_PQ_QueryParam res = + { &qconv_uint64, NULL, x, sizeof (*x), 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_rsa_public_key (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 GNUNET_CRYPTO_rsa_PublicKey *rsa = data; + char *buf; + size_t buf_size; + + GNUNET_break (NULL == cls); + if (1 != param_length) + return -1; + buf_size = GNUNET_CRYPTO_rsa_public_key_encode (rsa, + &buf); + scratch[0] = buf; + param_values[0] = (void *) buf; + param_lengths[0] = buf_size - 1; /* DB doesn't like the trailing \0 */ + param_formats[0] = 1; + return 1; +} + + +/** + * Generate query parameter for an RSA public key. The + * database must contain a BLOB type in the respective position. + * + * @param x the query parameter to pass + * @return array entry for the query parameters to use + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_rsa_public_key (const struct GNUNET_CRYPTO_rsa_PublicKey *x) +{ + struct GNUNET_PQ_QueryParam res = + { &qconv_rsa_public_key, NULL, (x), 0, 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_rsa_signature (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 GNUNET_CRYPTO_rsa_Signature *sig = data; + char *buf; + size_t buf_size; + + GNUNET_break (NULL == cls); + if (1 != param_length) + return -1; + buf_size = GNUNET_CRYPTO_rsa_signature_encode (sig, + &buf); + scratch[0] = buf; + param_values[0] = (void *) buf; + param_lengths[0] = buf_size - 1; /* DB doesn't like the trailing \0 */ + param_formats[0] = 1; + return 1; +} + + +/** + * Generate query parameter for an RSA signature. The + * database must contain a BLOB type in the respective position. + * + * @param x the query parameter to pass + * @return array entry for the query parameters to use + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_rsa_signature (const struct GNUNET_CRYPTO_rsa_Signature *x) +{ + struct GNUNET_PQ_QueryParam res = + { &qconv_rsa_signature, NULL, (x), 0, 1 }; + return res; +} + + +/** + * Generate query parameter for an absolute time value. + * The database must store a 64-bit integer. + * + * @param x pointer to the query parameter to pass + * @return array entry for the query parameters to use + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_absolute_time (const struct GNUNET_TIME_Absolute *x) +{ + return GNUNET_PQ_query_param_uint64 (&x->abs_value_us); +} + + +/** + * Generate query parameter for an absolute time value. + * The database must store a 64-bit integer. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_PQ_QueryParam +GNUNET_PQ_query_param_absolute_time_nbo(const struct GNUNET_TIME_AbsoluteNBO *x) +{ + return GNUNET_PQ_query_param_auto_from_type (&x->abs_value_us__); +} + + +/* end of 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 000000000..dc5700730 --- /dev/null +++ b/src/pq/pq_result_helper.c @@ -0,0 +1,674 @@ + /* + This file is part of GNUnet + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + GNUnet 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. + + GNUnet 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 + GNUnet; see the file COPYING. If not, If not, see +*/ +/** + * @file pq/pq_result_helper.c + * @brief functions to extract result values + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include "gnunet_pq_lib.h" + + +/** + * 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_varsize_blob (void *cls, + void *rd) +{ + void **dst = rd; + + if (NULL != *dst) + { + GNUNET_free (*dst); + *dst = NULL; + } +} + + +/** + * Extract data from a Postgres database @a result at row @a row. + * + * @param cls closure + * @param result where to extract data from + * @param int 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 int +extract_varsize_blob (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + size_t len; + const char *res; + void *idst; + int fnum; + + 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; + + /* 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); + GNUNET_assert (NULL != res); + *dst_size = len; + idst = GNUNET_malloc (len); + *((void **) dst) = idst; + memcpy (idst, + res, + len); + return GNUNET_OK; +} + + +/** + * Variable-size result expected. + * + * @param name name of the field in the table + * @param[out] dst where to store the result, allocated + * @param[out] sptr where to store the size of @a dst + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_variable_size (const char *name, + void **dst, + size_t *sptr) +{ + struct GNUNET_PQ_ResultSpec res = + { &extract_varsize_blob, + &clean_varsize_blob, NULL, + (void *) (dst), 0, name, sptr }; + 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 int row to extract data from + * @param fname name (or prefix) of the fields to extract from + * @param[in] dst_size desired size, never 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 int +extract_fixed_blob (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + size_t len; + const char *res; + int fnum; + + 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; + + /* if a field is null, continue but + * remember that we now return a different result */ + len = PQgetlength (result, + row, + fnum); + if (*dst_size != len) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Field `%s' has wrong size (got %u, expected %u)\n", + fname, + (unsigned int) len, + (unsigned int) *dst_size); + return GNUNET_SYSERR; + } + res = PQgetvalue (result, + row, + fnum); + GNUNET_assert (NULL != res); + memcpy (dst, + res, + len); + return GNUNET_OK; +} + + +/** + * Fixed-size result expected. + * + * @param name name of the field in the table + * @param[out] dst where to store the result + * @param dst_size number of bytes in @a dst + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_fixed_size (const char *name, + void *dst, + size_t dst_size) +{ + struct GNUNET_PQ_ResultSpec res = + { &extract_fixed_blob, + NULL, NULL, + (dst), dst_size, name, NULL }; + 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 int 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 int +extract_rsa_public_key (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + struct GNUNET_CRYPTO_rsa_PublicKey **pk = dst; + size_t len; + const char *res; + int fnum; + + *pk = NULL; + 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; + + /* 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); + *pk = GNUNET_CRYPTO_rsa_public_key_decode (res, + len); + if (NULL == *pk) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Field `%s' contains bogus value (fails to decode)\n", + fname); + 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_rsa_public_key (void *cls, + void *rd) +{ + struct GNUNET_CRYPTO_rsa_PublicKey **pk = rd; + + if (NULL != *pk) + { + GNUNET_CRYPTO_rsa_public_key_free (*pk); + *pk = NULL; + } +} + + +/** + * RSA public key expected. + * + * @param name name of the field in the table + * @param[out] rsa where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_rsa_public_key (const char *name, + struct GNUNET_CRYPTO_rsa_PublicKey **rsa) +{ + struct GNUNET_PQ_ResultSpec res = + { &extract_rsa_public_key, + &clean_rsa_public_key, + NULL, + (void *) rsa, 0, name, NULL }; + 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 int 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 int +extract_rsa_signature (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + struct GNUNET_CRYPTO_rsa_Signature **sig = dst; + size_t len; + const char *res; + int fnum; + + *sig = NULL; + 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; + + /* 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); + *sig = GNUNET_CRYPTO_rsa_signature_decode (res, + len); + if (NULL == *sig) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Field `%s' contains bogus value (fails to decode)\n", + fname); + 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_rsa_signature (void *cls, + void *rd) +{ + struct GNUNET_CRYPTO_rsa_Signature **sig = rd; + + if (NULL != *sig) + { + GNUNET_CRYPTO_rsa_signature_free (*sig); + *sig = NULL; + } +} + + +/** + * RSA signature expected. + * + * @param name name of the field in the table + * @param[out] sig where to store the result; + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_rsa_signature (const char *name, + struct GNUNET_CRYPTO_rsa_Signature **sig) +{ + struct GNUNET_PQ_ResultSpec res = + { &extract_rsa_signature, + &clean_rsa_signature, + NULL, + (void *) sig, 0, (name), NULL }; + return res; +} + + +/** + * Absolute time expected. + * + * @param name name of the field in the table + * @param[out] at where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_absolute_time (const char *name, + struct GNUNET_TIME_Absolute *at) +{ + return GNUNET_PQ_result_spec_uint64 (name, + &at->abs_value_us); +} + + +/** + * Absolute time in network byte order expected. + * + * @param name name of the field in the table + * @param[out] at where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_absolute_time_nbo (const char *name, + struct GNUNET_TIME_AbsoluteNBO *at) +{ + struct GNUNET_PQ_ResultSpec res = + GNUNET_PQ_result_spec_auto_from_type(name, &at->abs_value_us__); + 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 int 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 int +extract_uint16 (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + uint16_t *udst = dst; + const uint16_t *res; + int fnum; + + 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; + GNUNET_assert (NULL != dst); + if (sizeof (uint16_t) != *dst_size) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + res = (uint16_t *) PQgetvalue (result, + row, + fnum); + *udst = ntohs (*res); + return GNUNET_OK; +} + + +/** + * uint16_t expected. + * + * @param name name of the field in the table + * @param[out] u16 where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_uint16 (const char *name, + uint16_t *u16) +{ + struct GNUNET_PQ_ResultSpec res = + { &extract_uint16, + NULL, + NULL, + (void *) u16, sizeof (*u16), (name), NULL }; + 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 int 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 int +extract_uint32 (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + uint32_t *udst = dst; + const uint32_t *res; + int fnum; + + 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; + GNUNET_assert (NULL != dst); + if (sizeof (uint32_t) != *dst_size) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + res = (uint32_t *) PQgetvalue (result, + row, + fnum); + *udst = ntohl (*res); + return GNUNET_OK; +} + + +/** + * uint32_t expected. + * + * @param name name of the field in the table + * @param[out] u32 where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_uint32 (const char *name, + uint32_t *u32) +{ + struct GNUNET_PQ_ResultSpec res = + { &extract_uint32, + NULL, + NULL, + (void *) u32, sizeof (*u32), (name), NULL }; + 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 int 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 int +extract_uint64 (void *cls, + PGresult *result, + int row, + const char *fname, + size_t *dst_size, + void *dst) +{ + uint64_t *udst = dst; + const uint64_t *res; + int fnum; + + 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; + GNUNET_assert (NULL != dst); + if (sizeof (uint64_t) != *dst_size) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + res = (uint64_t *) PQgetvalue (result, + row, + fnum); + *udst = GNUNET_ntohll (*res); + return GNUNET_OK; +} + + +/** + * uint64_t expected. + * + * @param name name of the field in the table + * @param[out] u64 where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_PQ_ResultSpec +GNUNET_PQ_result_spec_uint64 (const char *name, + uint64_t *u64) +{ + struct GNUNET_PQ_ResultSpec res = + { &extract_uint64, + NULL, + NULL, + (void *) u64, sizeof (*u64), (name), NULL }; + 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 000000000..b9bf1be76 --- /dev/null +++ b/src/pq/test_pq.c @@ -0,0 +1,288 @@ +/* + This file is part of GNUnet + (C) 2015, 2016 GNUnet e.V. + + GNUnet 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. + + GNUnet 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 + GNUnet; see the file COPYING. If not, If not, see +*/ +/** + * @file pq/test_pq.c + * @brief Tests for Postgres convenience API + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_pq_lib.h" + + +/** + * Setup prepared statements. + * + * @param db_conn connection handle to initialize + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure + */ +static int +postgres_prepare (PGconn *db_conn) +{ + PGresult *result; + +#define PREPARE(name, sql, ...) \ + do { \ + result = PQprepare (db_conn, name, sql, __VA_ARGS__); \ + if (PGRES_COMMAND_OK != PQresultStatus (result)) \ + { \ + GNUNET_break (0); \ + PQclear (result); result = NULL; \ + return GNUNET_SYSERR; \ + } \ + PQclear (result); result = NULL; \ + } while (0); + + PREPARE ("test_insert", + "INSERT INTO test_pq (" + " pub" + ",sig" + ",abs_time" + ",forever" + ",hash" + ",vsize" + ",u16" + ",u32" + ",u64" + ") VALUES " + "($1, $2, $3, $4, $5, $6," + "$7, $8, $9);", + 9, NULL); + PREPARE ("test_select", + "SELECT" + " pub" + ",sig" + ",abs_time" + ",forever" + ",hash" + ",vsize" + ",u16" + ",u32" + ",u64" + " FROM test_pq" + " ORDER BY abs_time DESC " + " LIMIT 1;", + 0, NULL); + return GNUNET_OK; +#undef PREPARE +} + + +/** + * Run actual test queries. + * + * @return 0 on success + */ +static int +run_queries (PGconn *conn) +{ + struct GNUNET_CRYPTO_rsa_PublicKey *pub; + struct GNUNET_CRYPTO_rsa_PublicKey *pub2 = NULL; + struct GNUNET_CRYPTO_rsa_Signature *sig; + struct GNUNET_CRYPTO_rsa_Signature *sig2 = NULL; + struct GNUNET_TIME_Absolute abs_time = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_Absolute abs_time2; + struct GNUNET_TIME_Absolute forever = GNUNET_TIME_UNIT_FOREVER_ABS; + struct GNUNET_TIME_Absolute forever2; + struct GNUNET_HashCode hc; + struct GNUNET_HashCode hc2; + PGresult *result; + int ret; + struct GNUNET_CRYPTO_rsa_PrivateKey *priv; + char msg[] = "Hello"; + void *msg2; + size_t msg2_len; + uint16_t u16; + uint16_t u162; + uint32_t u32; + uint32_t u322; + uint64_t u64; + uint64_t u642; + + priv = GNUNET_CRYPTO_rsa_private_key_create (1024); + pub = GNUNET_CRYPTO_rsa_private_key_get_public (priv); + sig = GNUNET_CRYPTO_rsa_sign (priv, + msg, + sizeof (msg)); + u16 = 16; + u32 = 32; + u64 = 64; + /* FIXME: test GNUNET_PQ_result_spec_variable_size */ + { + struct GNUNET_PQ_QueryParam params_insert[] = { + GNUNET_PQ_query_param_rsa_public_key (pub), + GNUNET_PQ_query_param_rsa_signature (sig), + GNUNET_PQ_query_param_absolute_time (&abs_time), + GNUNET_PQ_query_param_absolute_time (&forever), + GNUNET_PQ_query_param_auto_from_type (&hc), + GNUNET_PQ_query_param_fixed_size (msg, strlen (msg)), + GNUNET_PQ_query_param_uint16 (&u16), + GNUNET_PQ_query_param_uint32 (&u32), + GNUNET_PQ_query_param_uint64 (&u64), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec results_select[] = { + GNUNET_PQ_result_spec_rsa_public_key ("pub", &pub2), + GNUNET_PQ_result_spec_rsa_signature ("sig", &sig2), + GNUNET_PQ_result_spec_absolute_time ("abs_time", &abs_time2), + GNUNET_PQ_result_spec_absolute_time ("forever", &forever2), + GNUNET_PQ_result_spec_auto_from_type ("hash", &hc2), + GNUNET_PQ_result_spec_variable_size ("vsize", &msg2, &msg2_len), + GNUNET_PQ_result_spec_uint16 ("u16", &u162), + GNUNET_PQ_result_spec_uint32 ("u32", &u322), + GNUNET_PQ_result_spec_uint64 ("u64", &u642), + GNUNET_PQ_result_spec_end + }; + + 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); + GNUNET_CRYPTO_rsa_signature_free (sig); + GNUNET_CRYPTO_rsa_private_key_free (priv); + GNUNET_CRYPTO_rsa_public_key_free (pub); + return 1; + } + + PQclear (result); + result = GNUNET_PQ_exec_prepared (conn, + "test_select", + params_select); + if (1 != + PQntuples (result)) + { + GNUNET_break (0); + PQclear (result); + GNUNET_CRYPTO_rsa_signature_free (sig); + GNUNET_CRYPTO_rsa_private_key_free (priv); + GNUNET_CRYPTO_rsa_public_key_free (pub); + return 1; + } + ret = GNUNET_PQ_extract_result (result, + results_select, + 0); + GNUNET_break (GNUNET_YES == ret); + GNUNET_break (abs_time.abs_value_us == abs_time2.abs_value_us); + GNUNET_break (forever.abs_value_us == forever2.abs_value_us); + GNUNET_break (0 == + memcmp (&hc, + &hc2, + sizeof (struct GNUNET_HashCode))); + GNUNET_break (0 == + GNUNET_CRYPTO_rsa_signature_cmp (sig, + sig2)); + GNUNET_break (0 == + GNUNET_CRYPTO_rsa_public_key_cmp (pub, + pub2)); + GNUNET_break (strlen (msg) == msg2_len); + GNUNET_break (0 == + strncmp (msg, + msg2, + msg2_len)); + GNUNET_break (16 == u162); + GNUNET_break (32 == u322); + GNUNET_break (64 == u642); + GNUNET_PQ_cleanup_result (results_select); + PQclear (result); + } + GNUNET_CRYPTO_rsa_signature_free (sig); + GNUNET_CRYPTO_rsa_private_key_free (priv); + GNUNET_CRYPTO_rsa_public_key_free (pub); + if (GNUNET_OK != ret) + return 1; + + return 0; +} + + +int +main(int argc, + const char *const argv[]) +{ + PGconn *conn; + PGresult *result; + int ret; + + GNUNET_log_setup ("test-pq", + "WARNING", + NULL); + conn = PQconnectdb ("postgres:///gnunetcheck"); + if (CONNECTION_OK != PQstatus (conn)) + { + fprintf (stderr, + "Cannot run test, database connection failed: %s\n", + PQerrorMessage (conn)); + GNUNET_break (0); + PQfinish (conn); + return 0; /* We ignore this type of error... */ + } + + result = PQexec (conn, + "CREATE TEMPORARY TABLE IF NOT EXISTS test_pq (" + " pub BYTEA NOT NULL" + ",sig BYTEA NOT NULL" + ",abs_time INT8 NOT NULL" + ",forever INT8 NOT NULL" + ",hash BYTEA NOT NULL CHECK(LENGTH(hash)=64)" + ",vsize VARCHAR NOT NULL" + ",u16 INT2 NOT NULL" + ",u32 INT4 NOT NULL" + ",u64 INT8 NOT NULL" + ")"); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + fprintf (stderr, + "Failed to create table: %s\n", + PQerrorMessage (conn)); + PQclear (result); + PQfinish (conn); + return 1; + } + PQclear (result); + if (GNUNET_OK != + postgres_prepare (conn)) + { + GNUNET_break (0); + PQfinish (conn); + return 1; + } + ret = run_queries (conn); + result = PQexec (conn, + "DROP TABLE test_pq"); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + fprintf (stderr, + "Failed to create table: %s\n", + PQerrorMessage (conn)); + PQclear (result); + PQfinish (conn); + return 1; + } + PQclear (result); + PQfinish (conn); + return ret; +} + + +/* end of test_pq.c */ -- cgit v1.2.3