aboutsummaryrefslogtreecommitdiff
path: root/src/plugin
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugin')
-rw-r--r--src/plugin/Makefile.am18
-rw-r--r--src/plugin/block/Makefile.am37
-rw-r--r--src/plugin/block/meson.build15
-rw-r--r--src/plugin/block/plugin_block_template.c222
-rw-r--r--src/plugin/block/plugin_block_test.c270
-rw-r--r--src/plugin/datacache/Makefile.am76
-rw-r--r--src/plugin/datacache/datacache-0001.sql48
-rw-r--r--src/plugin/datacache/datacache-drop.sql25
-rw-r--r--src/plugin/datacache/meson.build30
-rw-r--r--src/plugin/datacache/plugin_datacache_heap.c530
-rw-r--r--src/plugin/datacache/plugin_datacache_postgres.c616
-rw-r--r--src/plugin/datacache/plugin_datacache_sqlite.c1063
-rw-r--r--src/plugin/datacache/plugin_datacache_template.c172
-rw-r--r--src/plugin/datastore/Makefile.am150
-rw-r--r--src/plugin/datastore/datastore-0001.sql49
-rw-r--r--src/plugin/datastore/datastore-drop.sql25
-rw-r--r--src/plugin/datastore/meson.build75
-rw-r--r--src/plugin/datastore/perf_plugin_datastore.c573
-rw-r--r--src/plugin/datastore/perf_plugin_datastore_data_heap.conf7
-rw-r--r--src/plugin/datastore/perf_plugin_datastore_data_postgres.conf10
-rw-r--r--src/plugin/datastore/perf_plugin_datastore_data_sqlite.conf4
-rw-r--r--src/plugin/datastore/plugin_datastore_heap.c944
-rw-r--r--src/plugin/datastore/plugin_datastore_postgres.c930
-rw-r--r--src/plugin/datastore/plugin_datastore_sqlite.c1375
-rw-r--r--src/plugin/datastore/plugin_datastore_template.c274
-rw-r--r--src/plugin/datastore/test_defaults.conf10
-rw-r--r--src/plugin/datastore/test_plugin_datastore.c478
-rw-r--r--src/plugin/datastore/test_plugin_datastore_data_heap.conf6
-rw-r--r--src/plugin/datastore/test_plugin_datastore_data_postgres.conf10
-rw-r--r--src/plugin/datastore/test_plugin_datastore_data_sqlite.conf4
-rw-r--r--src/plugin/dht/Makefile.am27
-rw-r--r--src/plugin/dht/meson.build9
-rw-r--r--src/plugin/dht/plugin_block_dht.c308
-rw-r--r--src/plugin/dns/Makefile.am25
-rw-r--r--src/plugin/dns/meson.build6
-rw-r--r--src/plugin/dns/plugin_block_dns.c290
-rw-r--r--src/plugin/fs/Makefile.am35
-rw-r--r--src/plugin/fs/meson.build8
-rw-r--r--src/plugin/fs/plugin_block_fs.c337
-rw-r--r--src/plugin/fs/test_plugin_block_fs.c86
-rw-r--r--src/plugin/gns/Makefile.am59
-rw-r--r--src/plugin/gns/meson.build17
-rw-r--r--src/plugin/gns/plugin_block_gns.c289
-rw-r--r--src/plugin/gns/plugin_gnsrecord_gns.c515
-rw-r--r--src/plugin/gnsrecord/Makefile.am25
-rw-r--r--src/plugin/gnsrecord/meson.build6
-rw-r--r--src/plugin/gnsrecord/plugin_gnsrecord_dns.c893
-rw-r--r--src/plugin/meson.build17
-rw-r--r--src/plugin/messenger/Makefile.am26
-rw-r--r--src/plugin/messenger/meson.build7
-rw-r--r--src/plugin/messenger/plugin_gnsrecord_messenger.c305
-rw-r--r--src/plugin/namecache/Makefile.am108
-rw-r--r--src/plugin/namecache/meson.build22
-rw-r--r--src/plugin/namecache/namecache-0001.sql42
-rw-r--r--src/plugin/namecache/namecache-drop.sql25
-rw-r--r--src/plugin/namecache/plugin_namecache_flat.c429
-rw-r--r--src/plugin/namecache/plugin_namecache_postgres.c315
-rw-r--r--src/plugin/namecache/plugin_namecache_sqlite.c589
-rw-r--r--src/plugin/namecache/test_plugin_namecache.c135
-rw-r--r--src/plugin/namecache/test_plugin_namecache_flat.conf2
-rw-r--r--src/plugin/namecache/test_plugin_namecache_postgres.conf3
-rw-r--r--src/plugin/namecache/test_plugin_namecache_sqlite.conf2
-rw-r--r--src/plugin/namestore/Makefile.am79
-rw-r--r--src/plugin/namestore/meson.build63
-rw-r--r--src/plugin/namestore/namestore-0001.sql49
-rw-r--r--src/plugin/namestore/namestore-drop.sql25
-rw-r--r--src/plugin/namestore/plugin_namestore_flat.c816
-rw-r--r--src/plugin/namestore/plugin_namestore_postgres.c786
-rw-r--r--src/plugin/namestore/plugin_namestore_sqlite.c1030
-rw-r--r--src/plugin/namestore/test_plugin_namestore.c217
-rw-r--r--src/plugin/namestore/test_plugin_namestore_postgres.conf4
-rw-r--r--src/plugin/namestore/test_plugin_namestore_sqlite.conf3
-rwxr-xr-xsrc/plugin/namestore/test_plugin_rest_namestore.sh131
-rw-r--r--src/plugin/peerstore/Makefile.am46
-rw-r--r--src/plugin/peerstore/meson.build9
-rw-r--r--src/plugin/peerstore/plugin_peerstore_sqlite.c743
-rw-r--r--src/plugin/peerstore/test_plugin_peerstore.c226
-rw-r--r--src/plugin/peerstore/test_plugin_peerstore_sqlite.conf2
-rw-r--r--src/plugin/reclaim/Makefile.am70
-rw-r--r--src/plugin/reclaim/meson.build28
-rw-r--r--src/plugin/reclaim/pabc_helper.c374
-rw-r--r--src/plugin/reclaim/pabc_helper.h41
-rw-r--r--src/plugin/reclaim/plugin_gnsrecord_reclaim.c195
-rw-r--r--src/plugin/reclaim/plugin_reclaim_attribute_basic.c181
-rw-r--r--src/plugin/reclaim/plugin_reclaim_credential_jwt.c512
-rw-r--r--src/plugin/reclaim/plugin_reclaim_credential_pabc.c572
-rw-r--r--src/plugin/regex/Makefile.am40
-rw-r--r--src/plugin/regex/meson.build23
-rw-r--r--src/plugin/regex/plugin_block_regex.c380
-rw-r--r--src/plugin/regex/regex_block_lib.c431
-rw-r--r--src/plugin/regex/regex_block_lib.h193
-rw-r--r--src/plugin/revocation/Makefile.am27
-rw-r--r--src/plugin/revocation/meson.build9
-rw-r--r--src/plugin/revocation/plugin_block_revocation.c309
-rw-r--r--src/plugin/seti/Makefile.am25
-rw-r--r--src/plugin/seti/meson.build7
-rw-r--r--src/plugin/seti/plugin_block_seti_test.c197
-rw-r--r--src/plugin/setu/Makefile.am21
-rw-r--r--src/plugin/setu/meson.build7
-rw-r--r--src/plugin/setu/plugin_block_setu_test.c195
100 files changed, 21074 insertions, 0 deletions
diff --git a/src/plugin/Makefile.am b/src/plugin/Makefile.am
new file mode 100644
index 000000000..f74726ea2
--- /dev/null
+++ b/src/plugin/Makefile.am
@@ -0,0 +1,18 @@
1SUBDIRS = \
2 block \
3 dns \
4 gnsrecord \
5 peerstore \
6 datacache \
7 datastore \
8 namecache \
9 namestore \
10 dht \
11 setu \
12 seti \
13 revocation \
14 regex \
15 gns \
16 fs \
17 reclaim \
18 messenger
diff --git a/src/plugin/block/Makefile.am b/src/plugin/block/Makefile.am
new file mode 100644
index 000000000..9b72e4e3c
--- /dev/null
+++ b/src/plugin/block/Makefile.am
@@ -0,0 +1,37 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4plugindir = $(libdir)/gnunet
5
6if USE_COVERAGE
7 AM_CFLAGS = --coverage
8endif
9
10plugin_LTLIBRARIES = \
11 libgnunet_plugin_block_test.la
12
13# Real plugins should of course go into
14# plugin_LTLIBRARIES
15noinst_LTLIBRARIES = \
16 libgnunet_plugin_block_template.la
17
18libgnunet_plugin_block_template_la_SOURCES = \
19 plugin_block_template.c
20libgnunet_plugin_block_template_la_LIBADD = \
21 $(top_builddir)/src/lib/block/libgnunetblockgroup.la \
22 $(top_builddir)/src/lib/block/libgnunetblock.la \
23 $(top_builddir)/src/lib/util/libgnunetutil.la \
24 $(LTLIBINTL)
25libgnunet_plugin_block_template_la_LDFLAGS = \
26 $(GN_PLUGIN_LDFLAGS)
27
28libgnunet_plugin_block_test_la_SOURCES = \
29 plugin_block_test.c
30libgnunet_plugin_block_test_la_LIBADD = \
31 $(top_builddir)/src/lib/block/libgnunetblockgroup.la \
32 $(top_builddir)/src/lib/block/libgnunetblock.la \
33 $(top_builddir)/src/lib/util/libgnunetutil.la \
34 $(LTLIBINTL)
35libgnunet_plugin_block_test_la_LDFLAGS = \
36 $(GN_PLUGIN_LDFLAGS)
37
diff --git a/src/plugin/block/meson.build b/src/plugin/block/meson.build
new file mode 100644
index 000000000..480a9f4ca
--- /dev/null
+++ b/src/plugin/block/meson.build
@@ -0,0 +1,15 @@
1plugin_dep = [libgnunetutil_dep, libgnunetblock_dep, libgnunetblockgroup_dep]
2shared_module('gnunet_plugin_block_test',
3 ['plugin_block_test.c'],
4 dependencies: plugin_dep,
5 include_directories: [incdir, configuration_inc],
6 install: true,
7 install_dir: get_option('libdir')/'gnunet')
8
9shared_module('gnunet_plugin_block_template',
10 ['plugin_block_template.c'],
11 dependencies: plugin_dep,
12 include_directories: [incdir, configuration_inc],
13 install: false,
14 install_dir: get_option('libdir')/'gnunet')
15
diff --git a/src/plugin/block/plugin_block_template.c b/src/plugin/block/plugin_block_template.c
new file mode 100644
index 000000000..4a271fa42
--- /dev/null
+++ b/src/plugin/block/plugin_block_template.c
@@ -0,0 +1,222 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2010, 2021, 2022 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file block/plugin_block_template.c
23 * @brief template for a block plugin
24 * @author Christian Grothoff
25 */
26
27#include "platform.h"
28#include "gnunet_block_plugin.h"
29#include "gnunet_block_group_lib.h"
30
31#define DEBUG_TEMPLATE GNUNET_EXTRA_LOGGING
32
33/**
34 * Number of bits we set per entry in the bloomfilter.
35 * Do not change!
36 */
37#define BLOOMFILTER_K 16
38
39
40/**
41 * How big is the BF we use for DHT blocks?
42 */
43#define TEMPLATE_BF_SIZE 8
44
45
46/**
47 * Create a new block group.
48 *
49 * @param ctx block context in which the block group is created
50 * @param type type of the block for which we are creating the group
51 * @param raw_data optional serialized prior state of the group, NULL if unavailable/fresh
52 * @param raw_data_size number of bytes in @a raw_data, 0 if unavailable/fresh
53 * @param va variable arguments specific to @a type
54 * @return block group handle, NULL if block groups are not supported
55 * by this @a type of block (this is not an error)
56 */
57static struct GNUNET_BLOCK_Group *
58block_plugin_template_create_group (void *ctx,
59 enum GNUNET_BLOCK_Type type,
60 const void *raw_data,
61 size_t raw_data_size,
62 va_list va)
63{
64 unsigned int bf_size;
65 const char *guard;
66
67 guard = va_arg (va, const char *);
68 if (0 == strcmp (guard,
69 "seen-set-size"))
70 bf_size = GNUNET_BLOCK_GROUP_compute_bloomfilter_size (va_arg (va, unsigned
71 int),
72 BLOOMFILTER_K);
73 else if (0 == strcmp (guard,
74 "filter-size"))
75 bf_size = va_arg (va, unsigned int);
76 else
77 {
78 GNUNET_break (0);
79 bf_size = TEMPLATE_BF_SIZE;
80 }
81 GNUNET_break (NULL == va_arg (va, const char *));
82 return GNUNET_BLOCK_GROUP_bf_create (ctx,
83 bf_size,
84 BLOOMFILTER_K,
85 type,
86 raw_data,
87 raw_data_size);
88}
89
90
91/**
92 * Function called to validate a query.
93 *
94 * @param cls closure
95 * @param type block type
96 * @param query original query (hash)
97 * @param xquery extended query data (can be NULL, depending on type)
98 * @param xquery_size number of bytes in @a xquery
99 * @return #GNUNET_OK if the query is fine, #GNUNET_NO if not, #GNUNET_SYSERR if not supported
100 */
101static enum GNUNET_GenericReturnValue
102block_plugin_template_check_query (void *cls,
103 enum GNUNET_BLOCK_Type type,
104 const struct GNUNET_HashCode *query,
105 const void *xquery,
106 size_t xquery_size)
107{
108 return GNUNET_SYSERR;
109}
110
111
112/**
113 * Function called to validate a block for storage.
114 *
115 * @param cls closure
116 * @param type block type
117 * @param block block data to validate
118 * @param block_size number of bytes in @a block
119 * @return #GNUNET_OK if the block is fine, #GNUNET_NO if not, #GNUNET_SYSERR if not supported
120 */
121static enum GNUNET_GenericReturnValue
122block_plugin_template_check_block (void *cls,
123 enum GNUNET_BLOCK_Type type,
124 const void *block,
125 size_t block_size)
126{
127 return GNUNET_SYSERR;
128}
129
130
131/**
132 * Function called to validate a reply to a request. Note that it is assumed
133 * that the reply has already been matched to the key (and signatures checked)
134 * as it would be done with the GetKeyFunction and the
135 * BlockEvaluationFunction.
136 *
137 * @param cls closure
138 * @param type block type
139 * @param group which block group to use for evaluation
140 * @param query original query (hash)
141 * @param xquery extrended query data (can be NULL, depending on type)
142 * @param xquery_size number of bytes in @a xquery
143 * @param reply_block response to validate
144 * @param reply_block_size number of bytes in @a reply_block
145 * @return characterization of result
146 */
147static enum GNUNET_BLOCK_ReplyEvaluationResult
148block_plugin_template_check_reply (
149 void *cls,
150 enum GNUNET_BLOCK_Type type,
151 struct GNUNET_BLOCK_Group *group,
152 const struct GNUNET_HashCode *query,
153 const void *xquery,
154 size_t xquery_size,
155 const void *reply_block,
156 size_t reply_block_size)
157{
158 return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
159}
160
161
162/**
163 * Function called to obtain the key for a block.
164 *
165 * @param cls closure
166 * @param type block type
167 * @param block block to get the key for
168 * @param block_size number of bytes in block
169 * @param key set to the key (query) for the given block
170 * @return #GNUNET_OK on success, #GNUNET_SYSERR if type not supported
171 * (or if extracting a key from a block of this type does not work)
172 */
173static enum GNUNET_GenericReturnValue
174block_plugin_template_get_key (void *cls,
175 enum GNUNET_BLOCK_Type type,
176 const void *block,
177 size_t block_size,
178 struct GNUNET_HashCode *key)
179{
180 return GNUNET_SYSERR;
181}
182
183
184/**
185 * Entry point for the plugin.
186 *
187 * @param cls a `const struct GNUNET_CONFIGURATION_Handle`
188 */
189void *
190libgnunet_plugin_block_template_init (void *cls)
191{
192 static const enum GNUNET_BLOCK_Type types[] = {
193 /* NOTE: insert supported block types here */
194 GNUNET_BLOCK_TYPE_ANY /* end of list */
195 };
196 struct GNUNET_BLOCK_PluginFunctions *api;
197
198 api = GNUNET_new (struct GNUNET_BLOCK_PluginFunctions);
199 api->get_key = &block_plugin_template_get_key;
200 api->check_query = &block_plugin_template_check_query;
201 api->check_block = &block_plugin_template_check_block;
202 api->check_reply = &block_plugin_template_check_reply;
203 api->create_group = &block_plugin_template_create_group;
204 api->types = types;
205 return api;
206}
207
208
209/**
210 * Exit point from the plugin.
211 */
212void *
213libgnunet_plugin_block_template_done (void *cls)
214{
215 struct GNUNET_BLOCK_PluginFunctions *api = cls;
216
217 GNUNET_free (api);
218 return NULL;
219}
220
221
222/* end of plugin_block_template.c */
diff --git a/src/plugin/block/plugin_block_test.c b/src/plugin/block/plugin_block_test.c
new file mode 100644
index 000000000..5afac9205
--- /dev/null
+++ b/src/plugin/block/plugin_block_test.c
@@ -0,0 +1,270 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2010, 2021, 2022 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file block/plugin_block_test.c
23 * @brief block plugin to test the DHT as a simple key-value store;
24 * this plugin simply accepts any (new) response for any key
25 * @author Christian Grothoff
26 */
27
28#include "platform.h"
29#include "gnunet_block_plugin.h"
30#include "gnunet_block_group_lib.h"
31
32/**
33 * Number of bits we set per entry in the bloomfilter.
34 * Do not change!
35 */
36#define BLOOMFILTER_K 16
37
38/**
39 * How big is the BF we use for DHT blocks?
40 */
41#define TEST_BF_SIZE 8
42
43
44/**
45 * Create a new block group.
46 *
47 * @param ctx block context in which the block group is created
48 * @param type type of the block for which we are creating the group
49 * @param raw_data optional serialized prior state of the group, NULL if unavailable/fresh
50 * @param raw_data_size number of bytes in @a raw_data, 0 if unavailable/fresh
51 * @param va variable arguments specific to @a type
52 * @return block group handle, NULL if block groups are not supported
53 * by this @a type of block (this is not an error)
54 */
55static struct GNUNET_BLOCK_Group *
56block_plugin_test_create_group (void *ctx,
57 enum GNUNET_BLOCK_Type type,
58 const void *raw_data,
59 size_t raw_data_size,
60 va_list va)
61{
62 unsigned int bf_size;
63 const char *guard;
64
65 guard = va_arg (va, const char *);
66 if (0 == strcmp (guard,
67 "seen-set-size"))
68 bf_size = GNUNET_BLOCK_GROUP_compute_bloomfilter_size (va_arg (va, unsigned
69 int),
70 BLOOMFILTER_K);
71 else if (0 == strcmp (guard,
72 "filter-size"))
73 bf_size = va_arg (va, unsigned int);
74 else
75 {
76 GNUNET_break (0);
77 bf_size = TEST_BF_SIZE;
78 }
79 GNUNET_break (NULL == va_arg (va, const char *));
80 return GNUNET_BLOCK_GROUP_bf_create (ctx,
81 bf_size,
82 BLOOMFILTER_K,
83 type,
84 raw_data,
85 raw_data_size);
86}
87
88
89/**
90 * Function called to validate a query.
91 *
92 * @param cls closure
93 * @param type block type
94 * @param query original query (hash)
95 * @param xquery extended query data (can be NULL, depending on type)
96 * @param xquery_size number of bytes in @a xquery
97 * @return #GNUNET_OK if the query is fine, #GNUNET_NO if not, #GNUNET_SYSERR if @a type is not supported
98 */
99static enum GNUNET_GenericReturnValue
100block_plugin_test_check_query (void *cls,
101 enum GNUNET_BLOCK_Type type,
102 const struct GNUNET_HashCode *query,
103 const void *xquery,
104 size_t xquery_size)
105{
106 (void) cls;
107 (void) query;
108 (void) xquery;
109 if (GNUNET_BLOCK_TYPE_TEST != type)
110 {
111 GNUNET_break (0);
112 return GNUNET_SYSERR;
113 }
114 if (0 != xquery_size)
115 {
116 GNUNET_break_op (0);
117 return GNUNET_NO;
118 }
119 return GNUNET_OK;
120}
121
122
123/**
124 * Function called to validate a block for storage.
125 *
126 * @param cls closure
127 * @param type block type
128 * @param block block data to validate
129 * @param block_size number of bytes in @a block
130 * @return #GNUNET_OK if the block is fine, #GNUNET_NO if not, #GNUNET_SYSERR if @a type is not supported
131 */
132static enum GNUNET_GenericReturnValue
133block_plugin_test_check_block (void *cls,
134 enum GNUNET_BLOCK_Type type,
135 const void *block,
136 size_t block_size)
137{
138 (void) cls;
139 (void) block;
140 (void) block_size;
141 if (GNUNET_BLOCK_TYPE_TEST != type)
142 {
143 GNUNET_break (0);
144 return GNUNET_SYSERR;
145 }
146 return GNUNET_OK;
147}
148
149
150/**
151 * Function called to validate a reply to a request. Note that it is assumed
152 * that the reply has already been matched to the key (and signatures checked)
153 * as it would be done with the GetKeyFunction and the
154 * BlockEvaluationFunction.
155 *
156 * @param cls closure
157 * @param type block type
158 * @param group which block group to use for evaluation
159 * @param query original query (hash)
160 * @param xquery extrended query data (can be NULL, depending on type)
161 * @param xquery_size number of bytes in @a xquery
162 * @param reply_block response to validate
163 * @param reply_block_size number of bytes in @a reply_block
164 * @return characterization of result
165 */
166static enum GNUNET_BLOCK_ReplyEvaluationResult
167block_plugin_test_check_reply (void *cls,
168 enum GNUNET_BLOCK_Type type,
169 struct GNUNET_BLOCK_Group *group,
170 const struct GNUNET_HashCode *query,
171 const void *xquery,
172 size_t xquery_size,
173 const void *reply_block,
174 size_t reply_block_size)
175{
176 struct GNUNET_HashCode chash;
177
178 (void) cls;
179 (void) query;
180 (void) xquery;
181 (void) xquery_size;
182 if (GNUNET_BLOCK_TYPE_TEST != type)
183 {
184 GNUNET_break (0);
185 return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
186 }
187 GNUNET_CRYPTO_hash (reply_block,
188 reply_block_size,
189 &chash);
190 if (GNUNET_YES ==
191 GNUNET_BLOCK_GROUP_bf_test_and_set (group,
192 &chash))
193 return GNUNET_BLOCK_REPLY_OK_DUPLICATE;
194 return GNUNET_BLOCK_REPLY_OK_MORE;
195}
196
197
198/**
199 * Function called to obtain the key for a block.
200 *
201 * @param cls closure
202 * @param type block type
203 * @param block block to get the key for
204 * @param block_size number of bytes in @a block
205 * @param key set to the key (query) for the given block
206 * @return #GNUNET_OK on success, #GNUNET_SYSERR if type not supported, #GNUNET_NO if extracting a key from a block of this type does not work
207 */
208static enum GNUNET_GenericReturnValue
209block_plugin_test_get_key (void *cls,
210 enum GNUNET_BLOCK_Type type,
211 const void *block,
212 size_t block_size,
213 struct GNUNET_HashCode *key)
214{
215 (void) cls;
216 (void) block;
217 (void) block_size;
218 (void) key;
219 if (GNUNET_BLOCK_TYPE_TEST != type)
220 {
221 GNUNET_break (0);
222 return GNUNET_SYSERR;
223 }
224 return GNUNET_NO;
225}
226
227
228/**
229 * Entry point for the plugin.
230 *
231 * @param cls NULL
232 * @return the exported block API
233 */
234void *
235libgnunet_plugin_block_test_init (void *cls)
236{
237 static const enum GNUNET_BLOCK_Type types[] = {
238 GNUNET_BLOCK_TYPE_TEST,
239 GNUNET_BLOCK_TYPE_ANY /* end of list */
240 };
241 struct GNUNET_BLOCK_PluginFunctions *api;
242
243 api = GNUNET_new (struct GNUNET_BLOCK_PluginFunctions);
244 api->get_key = &block_plugin_test_get_key;
245 api->check_query = &block_plugin_test_check_query;
246 api->check_block = &block_plugin_test_check_block;
247 api->check_reply = &block_plugin_test_check_reply;
248 api->create_group = &block_plugin_test_create_group;
249 api->types = types;
250 return api;
251}
252
253
254/**
255 * Exit point from the plugin.
256 *
257 * @param cls the return value from #libgnunet_plugin_block_test_init
258 * @return NULL
259 */
260void *
261libgnunet_plugin_block_test_done (void *cls)
262{
263 struct GNUNET_BLOCK_PluginFunctions *api = cls;
264
265 GNUNET_free (api);
266 return NULL;
267}
268
269
270/* end of plugin_block_test.c */
diff --git a/src/plugin/datacache/Makefile.am b/src/plugin/datacache/Makefile.am
new file mode 100644
index 000000000..b9ae06975
--- /dev/null
+++ b/src/plugin/datacache/Makefile.am
@@ -0,0 +1,76 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4plugindir = $(libdir)/gnunet
5
6pkgcfgdir= $(pkgdatadir)/config.d/
7
8sqldir = $(prefix)/share/gnunet/sql/
9
10sql_DATA = \
11 datacache-0001.sql \
12 datacache-drop.sql
13
14
15if USE_COVERAGE
16 AM_CFLAGS = --coverage -O0
17 XLIBS = -lgcov
18endif
19
20if HAVE_SQLITE
21 SQLITE_PLUGIN = libgnunet_plugin_datacache_sqlite.la
22endif
23if HAVE_POSTGRESQL
24 POSTGRES_PLUGIN = libgnunet_plugin_datacache_postgres.la
25endif
26
27plugin_LTLIBRARIES = \
28 $(SQLITE_PLUGIN) \
29 $(POSTGRES_PLUGIN) \
30 libgnunet_plugin_datacache_heap.la
31
32# Real plugins should of course go into
33# plugin_LTLIBRARIES
34noinst_LTLIBRARIES = \
35 libgnunet_plugin_datacache_template.la
36
37
38libgnunet_plugin_datacache_sqlite_la_SOURCES = \
39 plugin_datacache_sqlite.c
40libgnunet_plugin_datacache_sqlite_la_LIBADD = \
41 $(top_builddir)/src/lib/sq/libgnunetsq.la \
42 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) -lsqlite3 \
43 $(LTLIBINTL)
44libgnunet_plugin_datacache_sqlite_la_LDFLAGS = \
45 $(GN_PLUGIN_LDFLAGS)
46
47libgnunet_plugin_datacache_heap_la_SOURCES = \
48 plugin_datacache_heap.c
49libgnunet_plugin_datacache_heap_la_LIBADD = \
50 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) \
51 $(LTLIBINTL)
52libgnunet_plugin_datacache_heap_la_LDFLAGS = \
53 $(GN_PLUGIN_LDFLAGS)
54
55libgnunet_plugin_datacache_postgres_la_SOURCES = \
56 plugin_datacache_postgres.c
57libgnunet_plugin_datacache_postgres_la_LIBADD = \
58 $(top_builddir)/src/lib/pq/libgnunetpq.la \
59 $(top_builddir)/src/lib/util/libgnunetutil.la \
60 $(GN_PLUGIN_LDFLAGS) -lpq
61libgnunet_plugin_datacache_postgres_la_CPPFLAGS = \
62 $(POSTGRESQL_CPPFLAGS) $(AM_CPPFLAGS)
63libgnunet_plugin_datacache_postgres_la_LDFLAGS = \
64 $(GN_PLUGIN_LDFLAGS) $(POSTGRESQL_LDFLAGS)
65
66libgnunet_plugin_datacache_template_la_SOURCES = \
67 plugin_datacache_template.c
68libgnunet_plugin_datacache_template_la_LIBADD = \
69 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) \
70 $(LTLIBINTL)
71libgnunet_plugin_datacache_template_la_LDFLAGS = \
72 $(GN_PLUGIN_LDFLAGS)
73
74
75EXTRA_DIST = \
76 $(sql_DATA)
diff --git a/src/plugin/datacache/datacache-0001.sql b/src/plugin/datacache/datacache-0001.sql
new file mode 100644
index 000000000..6567de3c2
--- /dev/null
+++ b/src/plugin/datacache/datacache-0001.sql
@@ -0,0 +1,48 @@
1--
2-- This file is part of GNUnet
3-- Copyright (C) 2014--2022 GNUnet e.V.
4--
5-- GNUnet is free software; you can redistribute it and/or modify it under the
6-- terms of the GNU General Public License as published by the Free Software
7-- Foundation; either version 3, or (at your option) any later version.
8--
9-- GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY
10-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12--
13-- You should have received a copy of the GNU General Public License along with
14-- GNUnet; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15--
16
17-- Everything in one big transaction
18BEGIN;
19
20-- Check patch versioning is in place.
21SELECT _v.register_patch('datacache-0001', NULL, NULL);
22
23-------------------- Schema ----------------------------
24
25CREATE SCHEMA datacache;
26COMMENT ON SCHEMA datacache IS 'gnunet-datacache data';
27
28SET search_path TO datacache;
29
30CREATE TABLE IF NOT EXISTS gn180dc (
31 oid BIGINT GENERATED BY DEFAULT AS IDENTITY,
32 type INT4 NOT NULL,
33 ro INT4 NOT NULL,
34 prox INT4 NOT NULL,
35 expiration_time INT8 NOT NULL,
36 key BYTEA NOT NULL CHECK(LENGTH(key)=64),
37 trunc BYTEA NOT NULL CHECK(LENGTH(trunc)=32),
38 value BYTEA NOT NULL,
39 path BYTEA DEFAULT NULL);
40
41CREATE INDEX IF NOT EXISTS idx_oid
42 ON gn180dc (oid);
43CREATE INDEX IF NOT EXISTS idx_key
44 ON gn180dc (key);
45CREATE INDEX IF NOT EXISTS idx_dt
46 ON gn180dc (expiration_time);
47
48COMMIT;
diff --git a/src/plugin/datacache/datacache-drop.sql b/src/plugin/datacache/datacache-drop.sql
new file mode 100644
index 000000000..2dd84bca8
--- /dev/null
+++ b/src/plugin/datacache/datacache-drop.sql
@@ -0,0 +1,25 @@
1--
2-- This file is part of GNUnet
3-- Copyright (C) 2014--2022 GNUnet e.V.
4--
5-- GNUnet is free software; you can redistribute it and/or modify it under the
6-- terms of the GNU General Public License as published by the Free Software
7-- Foundation; either version 3, or (at your option) any later version.
8--
9-- GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY
10-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12--
13-- You should have received a copy of the GNU General Public License along with
14-- GNUnet; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15--
16
17-- Everything in one big transaction
18BEGIN;
19
20
21SELECT _v.unregister_patch('datacache-0001');
22
23DROP SCHEMA datacache CASCADE;
24
25COMMIT;
diff --git a/src/plugin/datacache/meson.build b/src/plugin/datacache/meson.build
new file mode 100644
index 000000000..3c9e7d330
--- /dev/null
+++ b/src/plugin/datacache/meson.build
@@ -0,0 +1,30 @@
1install_data ('datacache-0001.sql',
2 'datacache-drop.sql',
3 install_dir: get_option('datadir')/'gnunet'/'sql')
4
5shared_module('gnunet_plugin_datacache_sqlite',
6 ['plugin_datacache_sqlite.c'],
7 dependencies: [libgnunetutil_dep,
8 sqlite_dep,
9 libgnunetsq_dep],
10 include_directories: [incdir, configuration_inc],
11 install: true,
12 install_dir: get_option('libdir')/'gnunet')
13
14shared_module('gnunet_plugin_datacache_heap',
15 ['plugin_datacache_heap.c'],
16 dependencies: [libgnunetutil_dep],
17 include_directories: [incdir, configuration_inc],
18 install: true,
19 install_dir: get_option('libdir')/'gnunet')
20
21if pq_dep.found()
22 shared_module('gnunet_plugin_datacache_postgres',
23 ['plugin_datacache_postgres.c'],
24 dependencies: [libgnunetutil_dep,
25 pq_dep,
26 libgnunetpq_dep],
27 include_directories: [incdir, configuration_inc],
28 install: true,
29 install_dir: get_option('libdir')/'gnunet')
30endif
diff --git a/src/plugin/datacache/plugin_datacache_heap.c b/src/plugin/datacache/plugin_datacache_heap.c
new file mode 100644
index 000000000..0dd8e47f8
--- /dev/null
+++ b/src/plugin/datacache/plugin_datacache_heap.c
@@ -0,0 +1,530 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2012, 2015, 2022 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file datacache/plugin_datacache_heap.c
23 * @brief heap-only implementation of a database backend for the datacache
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_datacache_plugin.h"
29
30#define LOG(kind, ...) GNUNET_log_from (kind, "datacache-heap", __VA_ARGS__)
31
32#define LOG_STRERROR_FILE(kind, op, fn) GNUNET_log_from_strerror_file (kind, \
33 "datacache-heap", \
34 op, fn)
35
36#define NUM_HEAPS 24
37
38/**
39 * Context for all functions in this plugin.
40 */
41struct Plugin
42{
43 /**
44 * Our execution environment.
45 */
46 struct GNUNET_DATACACHE_PluginEnvironment *env;
47
48 /**
49 * Our hash map.
50 */
51 struct GNUNET_CONTAINER_MultiHashMap *map;
52
53 /**
54 * Heaps sorted by distance.
55 */
56 struct GNUNET_CONTAINER_Heap *heaps[NUM_HEAPS];
57};
58
59
60/**
61 * Entry in the hash map.
62 */
63struct Value
64{
65 /**
66 * Block data.
67 */
68 struct GNUNET_DATACACHE_Block block;
69
70 /**
71 * Corresponding node in the heap.
72 */
73 struct GNUNET_CONTAINER_HeapNode *hn;
74
75 /**
76 * Put path as a non-const pointer.
77 */
78 struct GNUNET_DHT_PathElement *put_path;
79
80 /**
81 * How close is the hash to us? Determines which heap we are in!
82 */
83 uint32_t distance;
84
85};
86
87
88#define OVERHEAD (sizeof(struct Value) + 64)
89
90
91/**
92 * Closure for #put_cb().
93 */
94struct PutContext
95{
96 /**
97 * Block data.
98 */
99 const struct GNUNET_DATACACHE_Block *block;
100
101 /**
102 * Value to set to true if an equivalent block was found.
103 */
104 bool found;
105};
106
107
108/**
109 * Function called during PUT to detect if an equivalent block
110 * already exists.
111 *
112 * @param cls the `struct PutContext`
113 * @param key the key for the value(s)
114 * @param value an existing value
115 * @return #GNUNET_YES if not found (to continue to iterate)
116 */
117static enum GNUNET_GenericReturnValue
118put_cb (void *cls,
119 const struct GNUNET_HashCode *key,
120 void *value)
121{
122 struct PutContext *put_ctx = cls;
123 struct Value *val = value;
124
125 if ((val->block.data_size == put_ctx->block->data_size) &&
126 (val->block.type == put_ctx->block->type) &&
127 (0 == memcmp (val->block.data,
128 put_ctx->block->data,
129 put_ctx->block->data_size)))
130 {
131 put_ctx->found = true;
132 val->block.expiration_time
133 = GNUNET_TIME_absolute_max (val->block.expiration_time,
134 put_ctx->block->expiration_time);
135 /* replace old path with new path */
136 GNUNET_free (val->put_path);
137 val->put_path = GNUNET_memdup (put_ctx->block->put_path,
138 put_ctx->block->put_path_length
139 * sizeof (struct GNUNET_DHT_PathElement));
140 val->block.put_path = val->put_path;
141 val->block.put_path_length = put_ctx->block->put_path_length;
142 GNUNET_CONTAINER_heap_update_cost (val->hn,
143 val->block.expiration_time.abs_value_us);
144 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
145 "Got same value for key %s and type %u (size %u vs %u)\n",
146 GNUNET_h2s (key),
147 (unsigned int) val->block.type,
148 (unsigned int) val->block.data_size,
149 (unsigned int) put_ctx->block->data_size);
150 return GNUNET_NO;
151 }
152 return GNUNET_YES;
153}
154
155
156/**
157 * Store an item in the datastore.
158 *
159 * @param cls closure (our `struct Plugin`)
160 * @param xor_distance how close is @a key to our PID?
161 * @param block data to store
162 * @return 0 if duplicate, -1 on error, number of bytes used otherwise
163 */
164static ssize_t
165heap_plugin_put (void *cls,
166 uint32_t xor_distance,
167 const struct GNUNET_DATACACHE_Block *block)
168{
169 struct Plugin *plugin = cls;
170 struct Value *val;
171 struct PutContext put_ctx = {
172 .block = block,
173 .found = false
174 };
175
176 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
177 "Storing %u bytes under key %s with path length %u\n",
178 (unsigned int) block->data_size,
179 GNUNET_h2s (&block->key),
180 block->put_path_length);
181 GNUNET_CONTAINER_multihashmap_get_multiple (plugin->map,
182 &block->key,
183 &put_cb,
184 &put_ctx);
185 if (GNUNET_YES == put_ctx.found)
186 return 0;
187 val = GNUNET_malloc (sizeof(struct Value)
188 + block->data_size);
189 GNUNET_memcpy (&val[1],
190 block->data,
191 block->data_size);
192 val->block = *block;
193 val->block.data = &val[1];
194 if (xor_distance >= NUM_HEAPS)
195 val->distance = NUM_HEAPS - 1;
196 else
197 val->distance = xor_distance;
198 if (0 != block->put_path_length)
199 {
200 val->put_path
201 = GNUNET_memdup (block->put_path,
202 block->put_path_length
203 * sizeof (struct GNUNET_DHT_PathElement));
204 val->block.put_path = val->put_path;
205 }
206 (void) GNUNET_CONTAINER_multihashmap_put (plugin->map,
207 &val->block.key,
208 val,
209 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
210 val->hn = GNUNET_CONTAINER_heap_insert (
211 plugin->heaps[val->distance],
212 val,
213 val->block.expiration_time.abs_value_us);
214 return val->block.data_size + OVERHEAD;
215}
216
217
218/**
219 * Closure for #get_cb().
220 */
221struct GetContext
222{
223 /**
224 * Function to call for each result.
225 */
226 GNUNET_DATACACHE_Iterator iter;
227
228 /**
229 * Closure for @e iter.
230 */
231 void *iter_cls;
232
233 /**
234 * Number of results found.
235 */
236 unsigned int cnt;
237
238 /**
239 * Block type requested.
240 */
241 enum GNUNET_BLOCK_Type type;
242};
243
244
245/**
246 * Function called during GET to find matching blocks.
247 * Only matches by type.
248 *
249 * @param cls the `struct GetContext`
250 * @param key the key for the value(s)
251 * @param value an existing value
252 * @return #GNUNET_YES to continue to iterate
253 */
254static enum GNUNET_GenericReturnValue
255get_cb (void *cls,
256 const struct GNUNET_HashCode *key,
257 void *value)
258{
259 struct GetContext *get_ctx = cls;
260 struct Value *val = value;
261 enum GNUNET_GenericReturnValue ret;
262
263 if ( (get_ctx->type != val->block.type) &&
264 (GNUNET_BLOCK_TYPE_ANY != get_ctx->type) )
265 {
266 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
267 "Result for key %s does not match block type %d\n",
268 GNUNET_h2s (key),
269 get_ctx->type);
270 return GNUNET_OK;
271 }
272 if (GNUNET_TIME_absolute_is_past (val->block.expiration_time))
273 {
274 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
275 "Result for key %s is expired\n",
276 GNUNET_h2s (key));
277 return GNUNET_OK;
278 }
279 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
280 "Found result for key %s\n",
281 GNUNET_h2s (key));
282 if (NULL != get_ctx->iter)
283 ret = get_ctx->iter (get_ctx->iter_cls,
284 &val->block);
285 else
286 ret = GNUNET_YES;
287 get_ctx->cnt++;
288 return ret;
289}
290
291
292/**
293 * Iterate over the results for a particular key
294 * in the datastore.
295 *
296 * @param cls closure (our `struct Plugin`)
297 * @param key
298 * @param type entries of which type are relevant?
299 * @param iter maybe NULL (to just count)
300 * @param iter_cls closure for @a iter
301 * @return the number of results found
302 */
303static unsigned int
304heap_plugin_get (void *cls,
305 const struct GNUNET_HashCode *key,
306 enum GNUNET_BLOCK_Type type,
307 GNUNET_DATACACHE_Iterator iter,
308 void *iter_cls)
309{
310 struct Plugin *plugin = cls;
311 struct GetContext get_ctx;
312
313 get_ctx.type = type;
314 get_ctx.iter = iter;
315 get_ctx.iter_cls = iter_cls;
316 get_ctx.cnt = 0;
317 GNUNET_CONTAINER_multihashmap_get_multiple (plugin->map,
318 key,
319 &get_cb,
320 &get_ctx);
321 return get_ctx.cnt;
322}
323
324
325/**
326 * Delete the entry with the lowest expiration value
327 * from the datacache right now.
328 *
329 * @param cls closure (our `struct Plugin`)
330 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
331 */
332static enum GNUNET_GenericReturnValue
333heap_plugin_del (void *cls)
334{
335 struct Plugin *plugin = cls;
336 struct Value *val;
337
338 for (unsigned int i = 0; i < NUM_HEAPS; i++)
339 {
340 val = GNUNET_CONTAINER_heap_remove_root (plugin->heaps[i]);
341 if (NULL != val)
342 break;
343 }
344 if (NULL == val)
345 return GNUNET_SYSERR;
346 GNUNET_assert (GNUNET_YES ==
347 GNUNET_CONTAINER_multihashmap_remove (plugin->map,
348 &val->block.key,
349 val));
350 plugin->env->delete_notify (plugin->env->cls,
351 &val->block.key,
352 val->block.data_size + OVERHEAD);
353 GNUNET_free (val->put_path);
354 GNUNET_free (val);
355 return GNUNET_OK;
356}
357
358
359/**
360 * Closure for #find_closest().
361 */
362struct GetClosestContext
363{
364 struct Value **values;
365
366 const struct GNUNET_HashCode *key;
367
368 enum GNUNET_BLOCK_Type type;
369
370 unsigned int num_results;
371
372};
373
374
375static enum GNUNET_GenericReturnValue
376find_closest (void *cls,
377 const struct GNUNET_HashCode *key,
378 void *value)
379{
380 struct GetClosestContext *gcc = cls;
381 struct Value *val = value;
382 unsigned int j;
383
384 if (1 != GNUNET_CRYPTO_hash_cmp (key,
385 gcc->key))
386 return GNUNET_OK; /* useless */
387 if ( (val->block.type != gcc->type) &&
388 (GNUNET_BLOCK_TYPE_ANY != gcc->type) )
389 return GNUNET_OK; /* useless */
390 j = gcc->num_results;
391 for (unsigned int i = 0; i < gcc->num_results; i++)
392 {
393 if (NULL == gcc->values[i])
394 {
395 j = i;
396 break;
397 }
398 if (1 ==
399 GNUNET_CRYPTO_hash_cmp (&gcc->values[i]->block.key,
400 key))
401 {
402 j = i;
403 break;
404 }
405 }
406 if (j == gcc->num_results)
407 return GNUNET_OK;
408 gcc->values[j] = val;
409 return GNUNET_OK;
410}
411
412
413/**
414 * Iterate over the results that are "close" to a particular key in
415 * the datacache. "close" is defined as numerically larger than @a
416 * key (when interpreted as a circular address space), with small
417 * distance.
418 *
419 * @param cls closure (internal context for the plugin)
420 * @param key area of the keyspace to look into
421 * @param type desired block type for the replies
422 * @param num_results number of results that should be returned to @a iter
423 * @param iter maybe NULL (to just count)
424 * @param iter_cls closure for @a iter
425 * @return the number of results found
426 */
427static unsigned int
428heap_plugin_get_closest (void *cls,
429 const struct GNUNET_HashCode *key,
430 enum GNUNET_BLOCK_Type type,
431 unsigned int num_results,
432 GNUNET_DATACACHE_Iterator iter,
433 void *iter_cls)
434{
435 struct Plugin *plugin = cls;
436 struct Value *values[num_results];
437 struct GetClosestContext gcc = {
438 .values = values,
439 .type = type,
440 .num_results = num_results * 2,
441 .key = key
442 };
443
444 GNUNET_CONTAINER_multihashmap_iterate (plugin->map,
445 &find_closest,
446 &gcc);
447 for (unsigned int i = 0; i < num_results * 2; i++)
448 {
449 if (NULL == values[i])
450 return i;
451 if ( (NULL != iter) &&
452 (GNUNET_SYSERR ==
453 iter (iter_cls,
454 &values[i]->block)) )
455 {
456 LOG (GNUNET_ERROR_TYPE_DEBUG,
457 "Ending iteration (client error)\n");
458 return i;
459 }
460 }
461 return num_results * 2;
462}
463
464
465/**
466 * Entry point for the plugin.
467 *
468 * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`)
469 * @return the plugin's closure (our `struct Plugin`)
470 */
471void *
472libgnunet_plugin_datacache_heap_init (void *cls)
473{
474 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
475 struct GNUNET_DATACACHE_PluginFunctions *api;
476 struct Plugin *plugin;
477
478 plugin = GNUNET_new (struct Plugin);
479 plugin->map = GNUNET_CONTAINER_multihashmap_create (1024, /* FIXME: base on quota! */
480 GNUNET_YES);
481 for (unsigned int i = 0; i < NUM_HEAPS; i++)
482 plugin->heaps[i] = GNUNET_CONTAINER_heap_create (
483 GNUNET_CONTAINER_HEAP_ORDER_MIN);
484 plugin->env = env;
485 api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
486 api->cls = plugin;
487 api->get = &heap_plugin_get;
488 api->put = &heap_plugin_put;
489 api->del = &heap_plugin_del;
490 api->get_closest = &heap_plugin_get_closest;
491 LOG (GNUNET_ERROR_TYPE_INFO,
492 _ ("Heap datacache running\n"));
493 return api;
494}
495
496
497/**
498 * Exit point from the plugin.
499 *
500 * @param cls closure (our "struct Plugin")
501 * @return NULL
502 */
503void *
504libgnunet_plugin_datacache_heap_done (void *cls)
505{
506 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
507 struct Plugin *plugin = api->cls;
508 struct Value *val;
509
510 for (unsigned int i = 0; i < NUM_HEAPS; i++)
511 {
512 while (NULL != (val = GNUNET_CONTAINER_heap_remove_root (plugin->heaps[i])))
513 {
514 GNUNET_assert (GNUNET_YES ==
515 GNUNET_CONTAINER_multihashmap_remove (plugin->map,
516 &val->block.key,
517 val));
518 GNUNET_free (val->put_path);
519 GNUNET_free (val);
520 }
521 GNUNET_CONTAINER_heap_destroy (plugin->heaps[i]);
522 }
523 GNUNET_CONTAINER_multihashmap_destroy (plugin->map);
524 GNUNET_free (plugin);
525 GNUNET_free (api);
526 return NULL;
527}
528
529
530/* end of plugin_datacache_heap.c */
diff --git a/src/plugin/datacache/plugin_datacache_postgres.c b/src/plugin/datacache/plugin_datacache_postgres.c
new file mode 100644
index 000000000..8bfd04aea
--- /dev/null
+++ b/src/plugin/datacache/plugin_datacache_postgres.c
@@ -0,0 +1,616 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2006, 2009, 2010, 2012, 2015, 2017, 2018, 2022 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file datacache/plugin_datacache_postgres.c
23 * @brief postgres for an implementation of a database backend for the datacache
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_pq_lib.h"
29#include "gnunet_datacache_plugin.h"
30
31#define LOG(kind, ...) GNUNET_log_from (kind, "datacache-postgres", __VA_ARGS__)
32
33/**
34 * Per-entry overhead estimate
35 */
36#define OVERHEAD (sizeof(struct GNUNET_HashCode) + 24)
37
38/**
39 * Context for all functions in this plugin.
40 */
41struct Plugin
42{
43 /**
44 * Our execution environment.
45 */
46 struct GNUNET_DATACACHE_PluginEnvironment *env;
47
48 /**
49 * Native Postgres database handle.
50 */
51 struct GNUNET_PQ_Context *dbh;
52
53 /**
54 * Number of key-value pairs in the database.
55 */
56 unsigned int num_items;
57};
58
59
60/**
61 * @brief Get a database handle
62 *
63 * @param plugin global context
64 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
65 */
66static enum GNUNET_GenericReturnValue
67init_connection (struct Plugin *plugin)
68{
69 struct GNUNET_PQ_PreparedStatement ps[] = {
70 GNUNET_PQ_make_prepare ("getkt",
71 "SELECT expiration_time,type,ro,value,trunc,path"
72 " FROM datacache.gn180dc"
73 " WHERE key=$1 AND type=$2 AND expiration_time >= $3"),
74 GNUNET_PQ_make_prepare ("getk",
75 "SELECT expiration_time,type,ro,value,trunc,path"
76 " FROM datacache.gn180dc"
77 " WHERE key=$1 AND expiration_time >= $2"),
78 GNUNET_PQ_make_prepare ("getex",
79 "SELECT LENGTH(value) AS len,oid,key"
80 " FROM datacache.gn180dc"
81 " WHERE expiration_time < $1"
82 " ORDER BY expiration_time ASC LIMIT 1"),
83 GNUNET_PQ_make_prepare ("getm",
84 "SELECT LENGTH(value) AS len,oid,key"
85 " FROM datacache.gn180dc"
86 " ORDER BY prox ASC, expiration_time ASC LIMIT 1"),
87 GNUNET_PQ_make_prepare ("get_closest",
88 "(SELECT expiration_time,type,ro,value,trunc,path,key"
89 " FROM datacache.gn180dc"
90 " WHERE key >= $1"
91 " AND expiration_time >= $2"
92 " AND ( (type = $3) OR ( 0 = $3) )"
93 " ORDER BY key ASC"
94 " LIMIT $4)"
95 " UNION "
96 "(SELECT expiration_time,type,ro,value,trunc,path,key"
97 " FROM datacache.gn180dc"
98 " WHERE key <= $1"
99 " AND expiration_time >= $2"
100 " AND ( (type = $3) OR ( 0 = $3) )"
101 " ORDER BY key DESC"
102 " LIMIT $4)"),
103 GNUNET_PQ_make_prepare ("delrow",
104 "DELETE FROM datacache.gn180dc"
105 " WHERE oid=$1"),
106 GNUNET_PQ_make_prepare ("put",
107 "INSERT INTO datacache.gn180dc"
108 " (type, ro, prox, expiration_time, key, value, trunc, path) "
109 "VALUES ($1, $2, $3, $4, $5, $6, $7, $8)"),
110 GNUNET_PQ_PREPARED_STATEMENT_END
111 };
112
113 plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->env->cfg,
114 "datacache-postgres",
115 "datacache-",
116 NULL,
117 ps);
118 if (NULL == plugin->dbh)
119 return GNUNET_SYSERR;
120 return GNUNET_OK;
121}
122
123
124/**
125 * Store an item in the datastore.
126 *
127 * @param cls closure (our `struct Plugin`)
128 * @param prox proximity of @a key to my PID
129 * @param block data to store
130 * @return 0 if duplicate, -1 on error, number of bytes used otherwise
131 */
132static ssize_t
133postgres_plugin_put (void *cls,
134 uint32_t prox,
135 const struct GNUNET_DATACACHE_Block *block)
136{
137 struct Plugin *plugin = cls;
138 uint32_t type32 = (uint32_t) block->type;
139 uint32_t ro32 = (uint32_t) block->type;
140 struct GNUNET_PQ_QueryParam params[] = {
141 GNUNET_PQ_query_param_uint32 (&type32),
142 GNUNET_PQ_query_param_uint32 (&ro32),
143 GNUNET_PQ_query_param_uint32 (&prox),
144 GNUNET_PQ_query_param_absolute_time (&block->expiration_time),
145 GNUNET_PQ_query_param_auto_from_type (&block->key),
146 GNUNET_PQ_query_param_fixed_size (block->data,
147 block->data_size),
148 GNUNET_PQ_query_param_auto_from_type (&block->trunc_peer),
149 (0 == block->put_path_length)
150 ? GNUNET_PQ_query_param_null ()
151 : GNUNET_PQ_query_param_fixed_size (
152 block->put_path,
153 block->put_path_length
154 * sizeof(struct GNUNET_DHT_PathElement)),
155 GNUNET_PQ_query_param_end
156 };
157 enum GNUNET_DB_QueryStatus ret;
158
159 ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
160 "put",
161 params);
162 if (0 > ret)
163 return -1;
164 plugin->num_items++;
165 return block->data_size + OVERHEAD;
166}
167
168
169/**
170 * Closure for #handle_results.
171 */
172struct HandleResultContext
173{
174 /**
175 * Function to call on each result, may be NULL.
176 */
177 GNUNET_DATACACHE_Iterator iter;
178
179 /**
180 * Closure for @e iter.
181 */
182 void *iter_cls;
183
184 /**
185 * Key used.
186 */
187 const struct GNUNET_HashCode *key;
188};
189
190
191/**
192 * Function to be called with the results of a SELECT statement
193 * that has returned @a num_results results. Parse the result
194 * and call the callback given in @a cls
195 *
196 * @param cls closure of type `struct HandleResultContext`
197 * @param result the postgres result
198 * @param num_results the number of results in @a result
199 */
200static void
201handle_results (void *cls,
202 PGresult *result,
203 unsigned int num_results)
204{
205 struct HandleResultContext *hrc = cls;
206
207 for (unsigned int i = 0; i < num_results; i++)
208 {
209 uint32_t type32;
210 uint32_t bro32;
211 void *data;
212 struct GNUNET_DATACACHE_Block block;
213 void *path = NULL;
214 size_t path_size = 0;
215 struct GNUNET_PQ_ResultSpec rs[] = {
216 GNUNET_PQ_result_spec_absolute_time ("expiration_time",
217 &block.expiration_time),
218 GNUNET_PQ_result_spec_uint32 ("type",
219 &type32),
220 GNUNET_PQ_result_spec_uint32 ("ro",
221 &bro32),
222 GNUNET_PQ_result_spec_variable_size ("value",
223 &data,
224 &block.data_size),
225 GNUNET_PQ_result_spec_auto_from_type ("trunc",
226 &block.trunc_peer),
227 GNUNET_PQ_result_spec_allow_null (
228 GNUNET_PQ_result_spec_variable_size ("path",
229 &path,
230 &path_size),
231 NULL),
232 GNUNET_PQ_result_spec_end
233 };
234
235 if (GNUNET_YES !=
236 GNUNET_PQ_extract_result (result,
237 rs,
238 i))
239 {
240 GNUNET_break (0);
241 return;
242 }
243 if (0 != (path_size % sizeof(struct GNUNET_DHT_PathElement)))
244 {
245 GNUNET_break (0);
246 path_size = 0;
247 path = NULL;
248 }
249 block.data = data;
250 block.put_path = path;
251 block.put_path_length
252 = path_size / sizeof (struct GNUNET_DHT_PathElement);
253 block.type = (enum GNUNET_BLOCK_Type) type32;
254 block.ro = (enum GNUNET_DHT_RouteOption) bro32;
255 block.key = *hrc->key;
256 LOG (GNUNET_ERROR_TYPE_DEBUG,
257 "Found result of size %u bytes and type %u in database\n",
258 (unsigned int) block.data_size,
259 (unsigned int) block.type);
260 if ( (NULL != hrc->iter) &&
261 (GNUNET_SYSERR ==
262 hrc->iter (hrc->iter_cls,
263 &block)) )
264 {
265 LOG (GNUNET_ERROR_TYPE_DEBUG,
266 "Ending iteration (client error)\n");
267 GNUNET_PQ_cleanup_result (rs);
268 return;
269 }
270 GNUNET_PQ_cleanup_result (rs);
271 }
272}
273
274
275/**
276 * Iterate over the results for a particular key
277 * in the datastore.
278 *
279 * @param cls closure (our `struct Plugin`)
280 * @param key key to look for
281 * @param type entries of which type are relevant?
282 * @param iter maybe NULL (to just count)
283 * @param iter_cls closure for @a iter
284 * @return the number of results found
285 */
286static unsigned int
287postgres_plugin_get (void *cls,
288 const struct GNUNET_HashCode *key,
289 enum GNUNET_BLOCK_Type type,
290 GNUNET_DATACACHE_Iterator iter,
291 void *iter_cls)
292{
293 struct Plugin *plugin = cls;
294 uint32_t type32 = (uint32_t) type;
295 struct GNUNET_TIME_Absolute now = { 0 };
296 struct GNUNET_PQ_QueryParam paramk[] = {
297 GNUNET_PQ_query_param_auto_from_type (key),
298 GNUNET_PQ_query_param_absolute_time (&now),
299 GNUNET_PQ_query_param_end
300 };
301 struct GNUNET_PQ_QueryParam paramkt[] = {
302 GNUNET_PQ_query_param_auto_from_type (key),
303 GNUNET_PQ_query_param_uint32 (&type32),
304 GNUNET_PQ_query_param_absolute_time (&now),
305 GNUNET_PQ_query_param_end
306 };
307 enum GNUNET_DB_QueryStatus res;
308 struct HandleResultContext hr_ctx;
309
310 now = GNUNET_TIME_absolute_get ();
311 hr_ctx.iter = iter;
312 hr_ctx.iter_cls = iter_cls;
313 hr_ctx.key = key;
314 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
315 (0 == type) ? "getk" : "getkt",
316 (0 == type) ? paramk : paramkt,
317 &handle_results,
318 &hr_ctx);
319 if (res < 0)
320 return 0;
321 return res;
322}
323
324
325/**
326 * Delete the entry with the lowest expiration value
327 * from the datacache right now.
328 *
329 * @param cls closure (our `struct Plugin`)
330 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
331 */
332static enum GNUNET_GenericReturnValue
333postgres_plugin_del (void *cls)
334{
335 struct Plugin *plugin = cls;
336 struct GNUNET_PQ_QueryParam pempty[] = {
337 GNUNET_PQ_query_param_end
338 };
339 uint32_t size;
340 uint64_t oid;
341 struct GNUNET_HashCode key;
342 struct GNUNET_PQ_ResultSpec rs[] = {
343 GNUNET_PQ_result_spec_uint32 ("len",
344 &size),
345 GNUNET_PQ_result_spec_uint64 ("oid",
346 &oid),
347 GNUNET_PQ_result_spec_auto_from_type ("key",
348 &key),
349 GNUNET_PQ_result_spec_end
350 };
351 enum GNUNET_DB_QueryStatus res;
352 struct GNUNET_PQ_QueryParam dparam[] = {
353 GNUNET_PQ_query_param_uint64 (&oid),
354 GNUNET_PQ_query_param_end
355 };
356 struct GNUNET_TIME_Absolute now;
357 struct GNUNET_PQ_QueryParam xparam[] = {
358 GNUNET_PQ_query_param_absolute_time (&now),
359 GNUNET_PQ_query_param_end
360 };
361
362 now = GNUNET_TIME_absolute_get ();
363 res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
364 "getex",
365 xparam,
366 rs);
367 if (0 >= res)
368 res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
369 "getm",
370 pempty,
371 rs);
372 if (0 > res)
373 return GNUNET_SYSERR;
374 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
375 {
376 /* no result */
377 LOG (GNUNET_ERROR_TYPE_DEBUG,
378 "Ending iteration (no more results)\n");
379 return 0;
380 }
381 res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
382 "delrow",
383 dparam);
384 if (0 > res)
385 {
386 GNUNET_PQ_cleanup_result (rs);
387 return GNUNET_SYSERR;
388 }
389 plugin->num_items--;
390 plugin->env->delete_notify (plugin->env->cls,
391 &key,
392 size + OVERHEAD);
393 GNUNET_PQ_cleanup_result (rs);
394 return GNUNET_OK;
395}
396
397
398/**
399 * Closure for #extract_result_cb.
400 */
401struct ExtractResultContext
402{
403 /**
404 * Function to call for each result found.
405 */
406 GNUNET_DATACACHE_Iterator iter;
407
408 /**
409 * Closure for @e iter.
410 */
411 void *iter_cls;
412};
413
414
415/**
416 * Function to be called with the results of a SELECT statement
417 * that has returned @a num_results results. Calls the `iter`
418 * from @a cls for each result.
419 *
420 * @param cls closure with the `struct ExtractResultContext`
421 * @param result the postgres result
422 * @param num_results the number of results in @a result
423 */
424static void
425extract_result_cb (void *cls,
426 PGresult *result,
427 unsigned int num_results)
428{
429 struct ExtractResultContext *erc = cls;
430
431 if (NULL == erc->iter)
432 return;
433 for (unsigned int i = 0; i < num_results; i++)
434 {
435 uint32_t type32;
436 uint32_t bro32;
437 struct GNUNET_DATACACHE_Block block;
438 void *data;
439 void *path;
440 size_t path_size;
441 struct GNUNET_PQ_ResultSpec rs[] = {
442 GNUNET_PQ_result_spec_absolute_time ("expiration_time",
443 &block.expiration_time),
444 GNUNET_PQ_result_spec_uint32 ("type",
445 &type32),
446 GNUNET_PQ_result_spec_uint32 ("ro",
447 &bro32),
448 GNUNET_PQ_result_spec_variable_size ("value",
449 &data,
450 &block.data_size),
451 GNUNET_PQ_result_spec_auto_from_type ("trunc",
452 &block.trunc_peer),
453 GNUNET_PQ_result_spec_variable_size ("path",
454 &path,
455 &path_size),
456 GNUNET_PQ_result_spec_auto_from_type ("key",
457 &block.key),
458 GNUNET_PQ_result_spec_end
459 };
460
461 if (GNUNET_YES !=
462 GNUNET_PQ_extract_result (result,
463 rs,
464 i))
465 {
466 GNUNET_break (0);
467 return;
468 }
469 if (0 != (path_size % sizeof(struct GNUNET_DHT_PathElement)))
470 {
471 GNUNET_break (0);
472 path_size = 0;
473 path = NULL;
474 }
475 block.type = (enum GNUNET_BLOCK_Type) type32;
476 block.ro = (enum GNUNET_DHT_RouteOption) bro32;
477 block.data = data;
478 block.put_path = path;
479 block.put_path_length = path_size / sizeof (struct GNUNET_DHT_PathElement);
480 LOG (GNUNET_ERROR_TYPE_DEBUG,
481 "Found result of size %u bytes and type %u in database\n",
482 (unsigned int) block.data_size,
483 (unsigned int) block.type);
484 if ( (NULL != erc->iter) &&
485 (GNUNET_SYSERR ==
486 erc->iter (erc->iter_cls,
487 &block)) )
488 {
489 LOG (GNUNET_ERROR_TYPE_DEBUG,
490 "Ending iteration (client error)\n");
491 GNUNET_PQ_cleanup_result (rs);
492 break;
493 }
494 GNUNET_PQ_cleanup_result (rs);
495 }
496}
497
498
499/**
500 * Iterate over the results that are "close" to a particular key in
501 * the datacache. "close" is defined as numerically larger than @a
502 * key (when interpreted as a circular address space), with small
503 * distance.
504 *
505 * @param cls closure (internal context for the plugin)
506 * @param key area of the keyspace to look into
507 * @param type desired block type for the replies
508 * @param num_results number of results that should be returned to @a iter
509 * @param iter maybe NULL (to just count)
510 * @param iter_cls closure for @a iter
511 * @return the number of results found
512 */
513static unsigned int
514postgres_plugin_get_closest (void *cls,
515 const struct GNUNET_HashCode *key,
516 enum GNUNET_BLOCK_Type type,
517 unsigned int num_results,
518 GNUNET_DATACACHE_Iterator iter,
519 void *iter_cls)
520{
521 struct Plugin *plugin = cls;
522 uint32_t num_results32 = (uint32_t) num_results;
523 uint32_t type32 = (uint32_t) type;
524 struct GNUNET_TIME_Absolute now;
525 struct GNUNET_PQ_QueryParam params[] = {
526 GNUNET_PQ_query_param_auto_from_type (key),
527 GNUNET_PQ_query_param_absolute_time (&now),
528 GNUNET_PQ_query_param_uint32 (&type32),
529 GNUNET_PQ_query_param_uint32 (&num_results32),
530 GNUNET_PQ_query_param_end
531 };
532 enum GNUNET_DB_QueryStatus res;
533 struct ExtractResultContext erc;
534
535 erc.iter = iter;
536 erc.iter_cls = iter_cls;
537 now = GNUNET_TIME_absolute_get ();
538 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
539 "get_closest",
540 params,
541 &extract_result_cb,
542 &erc);
543 if (0 > res)
544 {
545 LOG (GNUNET_ERROR_TYPE_DEBUG,
546 "Ending iteration (postgres error)\n");
547 return 0;
548 }
549 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
550 {
551 /* no result */
552 LOG (GNUNET_ERROR_TYPE_DEBUG,
553 "Ending iteration (no more results)\n");
554 return 0;
555 }
556 return res;
557}
558
559
560/**
561 * Entry point for the plugin.
562 *
563 * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`)
564 * @return the plugin's closure (our `struct Plugin`)
565 */
566void *
567libgnunet_plugin_datacache_postgres_init (void *cls)
568{
569 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
570 struct GNUNET_DATACACHE_PluginFunctions *api;
571 struct Plugin *plugin;
572
573 plugin = GNUNET_new (struct Plugin);
574 plugin->env = env;
575
576 if (GNUNET_OK != init_connection (plugin))
577 {
578 GNUNET_free (plugin);
579 return NULL;
580 }
581
582 api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
583 api->cls = plugin;
584 api->get = &postgres_plugin_get;
585 api->put = &postgres_plugin_put;
586 api->del = &postgres_plugin_del;
587 api->get_closest = &postgres_plugin_get_closest;
588 LOG (GNUNET_ERROR_TYPE_INFO,
589 "Postgres datacache running\n");
590 return api;
591}
592
593
594/**
595 * Exit point from the plugin.
596 *
597 * @param cls closure (our `struct Plugin`)
598 * @return NULL
599 */
600void *
601libgnunet_plugin_datacache_postgres_done (void *cls)
602{
603 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
604 struct Plugin *plugin = api->cls;
605
606 GNUNET_break (GNUNET_OK ==
607 GNUNET_PQ_exec_sql (plugin->dbh,
608 "datacache-drop"));
609 GNUNET_PQ_disconnect (plugin->dbh);
610 GNUNET_free (plugin);
611 GNUNET_free (api);
612 return NULL;
613}
614
615
616/* end of plugin_datacache_postgres.c */
diff --git a/src/plugin/datacache/plugin_datacache_sqlite.c b/src/plugin/datacache/plugin_datacache_sqlite.c
new file mode 100644
index 000000000..1c6f24a82
--- /dev/null
+++ b/src/plugin/datacache/plugin_datacache_sqlite.c
@@ -0,0 +1,1063 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2006, 2009, 2015, 2022 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file datacache/plugin_datacache_sqlite.c
23 * @brief sqlite for an implementation of a database backend for the datacache
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_datacache_plugin.h"
29#include "gnunet_sq_lib.h"
30#include <sqlite3.h>
31
32#define LOG(kind, ...) GNUNET_log_from (kind, "datacache-sqlite", __VA_ARGS__)
33
34#define LOG_STRERROR_FILE(kind, op, fn) \
35 GNUNET_log_from_strerror_file (kind, "datacache-sqlite", op, fn)
36
37
38/**
39 * How much overhead do we assume per entry in the
40 * datacache?
41 */
42#define OVERHEAD (sizeof(struct GNUNET_HashCode) + 36)
43
44/**
45 * Context for all functions in this plugin.
46 */
47struct Plugin
48{
49 /**
50 * Our execution environment.
51 */
52 struct GNUNET_DATACACHE_PluginEnvironment *env;
53
54 /**
55 * Handle to the sqlite database.
56 */
57 sqlite3 *dbh;
58
59 /**
60 * Filename used for the DB.
61 */
62 char *fn;
63
64 /**
65 * Prepared statement for #sqlite_plugin_put.
66 */
67 sqlite3_stmt *insert_stmt;
68
69 /**
70 * Prepared statement for #sqlite_plugin_get.
71 */
72 sqlite3_stmt *get_count_stmt;
73
74 /**
75 * Prepared statement for #sqlite_plugin_get.
76 */
77 sqlite3_stmt *get_count_any_stmt;
78
79 /**
80 * Prepared statement for #sqlite_plugin_get.
81 */
82 sqlite3_stmt *get_stmt;
83
84 /**
85 * Prepared statement for #sqlite_plugin_get.
86 */
87 sqlite3_stmt *get_any_stmt;
88
89 /**
90 * Prepared statement for #sqlite_plugin_del.
91 */
92 sqlite3_stmt *del_select_stmt;
93
94 /**
95 * Prepared statement for #sqlite_plugin_del.
96 */
97 sqlite3_stmt *del_expired_stmt;
98
99 /**
100 * Prepared statement for #sqlite_plugin_del.
101 */
102 sqlite3_stmt *del_stmt;
103
104 /**
105 * Prepared statement for #sqlite_plugin_get_closest.
106 */
107 sqlite3_stmt *get_closest_stmt;
108
109 /**
110 * Number of key-value pairs in the database.
111 */
112 unsigned int num_items;
113};
114
115
116/**
117 * Log an error message at log-level @a level that indicates
118 * a failure of the command @a cmd with the error from the database @a db
119 *
120 * @param db database handle
121 * @param level log level
122 * @param cmd failed command
123 */
124#define LOG_SQLITE(db, level, cmd) \
125 do \
126 { \
127 LOG (level, \
128 _ ("`%s' failed at %s:%d with error: %s\n"), \
129 cmd, \
130 __FILE__, \
131 __LINE__, \
132 sqlite3_errmsg (db)); \
133 } while (0)
134
135
136/**
137 * Execute SQL statement.
138 *
139 * @param db database handle
140 * @param cmd SQL command to execute
141 */
142#define SQLITE3_EXEC(db, cmd) \
143 do \
144 { \
145 emsg = NULL; \
146 if (SQLITE_OK != \
147 sqlite3_exec (db, cmd, NULL, NULL, &emsg)) \
148 { \
149 LOG (GNUNET_ERROR_TYPE_ERROR, \
150 _ ("`%s' failed at %s:%d with error: %s\n"), \
151 "sqlite3_exec", \
152 __FILE__, \
153 __LINE__, \
154 emsg); \
155 sqlite3_free (emsg); \
156 } \
157 } while (0)
158
159
160/**
161 * @brief Prepare a SQL statement
162 *
163 * @param dbh database handle
164 * @param zSql SQL statement text
165 * @param[out] ppStmt set to the prepared statement
166 * @return 0 on success
167 */
168static int
169sq_prepare (sqlite3 *dbh,
170 const char *zSql, /* SQL statement, UTF-8 encoded */
171 sqlite3_stmt **ppStmt)
172{ /* OUT: Statement handle */
173 char *dummy;
174
175 return sqlite3_prepare (dbh,
176 zSql,
177 strlen (zSql),
178 ppStmt,
179 (const char **) &dummy);
180}
181
182
183/**
184 * Store an item in the datastore.
185 *
186 * @param cls closure (our `struct Plugin`)
187 * @param xor_distance how close is @a key to our PID?
188 * @param block data to store
189 * @return 0 if duplicate, -1 on error, number of bytes used otherwise
190 */
191static ssize_t
192sqlite_plugin_put (void *cls,
193 uint32_t xor_distance,
194 const struct GNUNET_DATACACHE_Block *block)
195{
196 struct Plugin *plugin = cls;
197 uint32_t type32 = (uint32_t) block->type;
198 uint32_t ro32 = (uint32_t) block->ro;
199 struct GNUNET_SQ_QueryParam params[] = {
200 GNUNET_SQ_query_param_uint32 (&type32),
201 GNUNET_SQ_query_param_uint32 (&ro32),
202 GNUNET_SQ_query_param_absolute_time (&block->expiration_time),
203 GNUNET_SQ_query_param_auto_from_type (&block->key),
204 GNUNET_SQ_query_param_uint32 (&xor_distance),
205 GNUNET_SQ_query_param_fixed_size (block->data,
206 block->data_size),
207 GNUNET_SQ_query_param_fixed_size (block->put_path,
208 block->put_path_length
209 * sizeof(struct GNUNET_DHT_PathElement)),
210 GNUNET_SQ_query_param_auto_from_type (&block->trunc_peer),
211 GNUNET_SQ_query_param_end
212 };
213
214 LOG (GNUNET_ERROR_TYPE_DEBUG,
215 "Processing PUT of %u bytes with key `%s' and expiration %s\n",
216 (unsigned int) block->data_size,
217 GNUNET_h2s (&block->key),
218 GNUNET_STRINGS_relative_time_to_string (
219 GNUNET_TIME_absolute_get_remaining (
220 block->expiration_time),
221 GNUNET_YES));
222 if (GNUNET_OK !=
223 GNUNET_SQ_bind (plugin->insert_stmt,
224 params))
225 {
226 LOG_SQLITE (plugin->dbh,
227 GNUNET_ERROR_TYPE_ERROR,
228 "sqlite3_bind_xxx");
229 GNUNET_SQ_reset (plugin->dbh,
230 plugin->insert_stmt);
231 return -1;
232 }
233 if (SQLITE_DONE !=
234 sqlite3_step (plugin->insert_stmt))
235 {
236 LOG_SQLITE (plugin->dbh,
237 GNUNET_ERROR_TYPE_ERROR,
238 "sqlite3_step");
239 GNUNET_SQ_reset (plugin->dbh,
240 plugin->insert_stmt);
241 return -1;
242 }
243 plugin->num_items++;
244 GNUNET_SQ_reset (plugin->dbh,
245 plugin->insert_stmt);
246 return block->data_size + OVERHEAD;
247}
248
249
250/**
251 * Iterate over the results for a particular key
252 * in the datastore.
253 *
254 * @param cls closure (our `struct Plugin`)
255 * @param key
256 * @param iter maybe NULL (to just count)
257 * @param iter_cls closure for @a iter
258 * @return the number of results found
259 */
260static unsigned int
261get_any (void *cls,
262 const struct GNUNET_HashCode *key,
263 GNUNET_DATACACHE_Iterator iter,
264 void *iter_cls)
265{
266 struct Plugin *plugin = cls;
267 struct GNUNET_TIME_Absolute now;
268 unsigned int cnt;
269 uint32_t off;
270 uint32_t btype32;
271 uint32_t bro32;
272 unsigned int total;
273 struct GNUNET_DATACACHE_Block block;
274 void *path;
275 void *data;
276 size_t path_size;
277 struct GNUNET_SQ_QueryParam params_count[] = {
278 GNUNET_SQ_query_param_auto_from_type (key),
279 GNUNET_SQ_query_param_absolute_time (&now),
280 GNUNET_SQ_query_param_end
281 };
282 struct GNUNET_SQ_QueryParam params_select[] = {
283 GNUNET_SQ_query_param_auto_from_type (key),
284 GNUNET_SQ_query_param_absolute_time (&now),
285 GNUNET_SQ_query_param_uint32 (&off),
286 GNUNET_SQ_query_param_end
287 };
288 struct GNUNET_SQ_ResultSpec rs[] = {
289 GNUNET_SQ_result_spec_variable_size (&data,
290 &block.data_size),
291 GNUNET_SQ_result_spec_absolute_time (&block.expiration_time),
292 GNUNET_SQ_result_spec_variable_size (&path,
293 &path_size),
294 GNUNET_SQ_result_spec_auto_from_type (&block.trunc_peer),
295 GNUNET_SQ_result_spec_uint32 (&btype32),
296 GNUNET_SQ_result_spec_uint32 (&bro32),
297 GNUNET_SQ_result_spec_end
298 };
299
300 now = GNUNET_TIME_absolute_get ();
301 LOG (GNUNET_ERROR_TYPE_DEBUG,
302 "Processing GET for key `%s'\n",
303 GNUNET_h2s (key));
304
305 if (GNUNET_OK !=
306 GNUNET_SQ_bind (plugin->get_count_any_stmt,
307 params_count))
308 {
309 LOG_SQLITE (plugin->dbh,
310 GNUNET_ERROR_TYPE_ERROR,
311 "sqlite3_bind_xxx");
312 GNUNET_SQ_reset (plugin->dbh,
313 plugin->get_count_any_stmt);
314 return 0;
315 }
316 if (SQLITE_ROW !=
317 sqlite3_step (plugin->get_count_any_stmt))
318 {
319 LOG_SQLITE (plugin->dbh,
320 GNUNET_ERROR_TYPE_ERROR,
321 "sqlite_step");
322 GNUNET_SQ_reset (plugin->dbh,
323 plugin->get_count_any_stmt);
324 LOG (GNUNET_ERROR_TYPE_DEBUG,
325 "No content found when processing GET for key `%s'\n",
326 GNUNET_h2s (key));
327 return 0;
328 }
329 total = sqlite3_column_int (plugin->get_count_any_stmt,
330 0);
331 GNUNET_SQ_reset (plugin->dbh,
332 plugin->get_count_any_stmt);
333 if ( (0 == total) ||
334 (NULL == iter) )
335 {
336 if (0 == total)
337 LOG (GNUNET_ERROR_TYPE_DEBUG,
338 "No content found when processing GET for key `%s'\n",
339 GNUNET_h2s (key));
340 return total;
341 }
342
343 cnt = 0;
344 block.key = *key;
345 off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
346 total);
347 while (cnt < total)
348 {
349 off = (off + 1) % total;
350 if (GNUNET_OK !=
351 GNUNET_SQ_bind (plugin->get_any_stmt,
352 params_select))
353 {
354 LOG_SQLITE (plugin->dbh,
355 GNUNET_ERROR_TYPE_ERROR,
356 "sqlite3_bind_xxx");
357 GNUNET_SQ_reset (plugin->dbh,
358 plugin->get_any_stmt);
359 return cnt;
360 }
361 if (SQLITE_ROW !=
362 sqlite3_step (plugin->get_any_stmt))
363 break;
364 if (GNUNET_OK !=
365 GNUNET_SQ_extract_result (plugin->get_any_stmt,
366 rs))
367 {
368 GNUNET_break (0);
369 GNUNET_SQ_reset (plugin->dbh,
370 plugin->get_any_stmt);
371 break;
372 }
373 if (0 != path_size % sizeof(struct GNUNET_DHT_PathElement))
374 {
375 GNUNET_break (0);
376 path_size = 0;
377 path = NULL;
378 }
379 block.data = data;
380 block.put_path = path;
381 block.put_path_length = path_size / sizeof(struct GNUNET_DHT_PathElement);
382 block.type = (enum GNUNET_BLOCK_Type) btype32;
383 block.ro = (enum GNUNET_DHT_RouteOption) bro32;
384 cnt++;
385 LOG (GNUNET_ERROR_TYPE_DEBUG,
386 "Found %u-byte result when processing GET for key `%s'\n",
387 (unsigned int) block.data_size,
388 GNUNET_h2s (&block.key));
389 if (GNUNET_OK !=
390 iter (iter_cls,
391 &block))
392 {
393 GNUNET_SQ_cleanup_result (rs);
394 GNUNET_SQ_reset (plugin->dbh,
395 plugin->get_any_stmt);
396 break;
397 }
398 GNUNET_SQ_cleanup_result (rs);
399 GNUNET_SQ_reset (plugin->dbh,
400 plugin->get_any_stmt);
401 }
402 GNUNET_SQ_reset (plugin->dbh,
403 plugin->get_any_stmt);
404 return cnt;
405}
406
407
408/**
409 * Iterate over the results for a particular key
410 * in the datastore.
411 *
412 * @param cls closure (our `struct Plugin`)
413 * @param key
414 * @param type entries of which type are relevant?
415 * @param iter maybe NULL (to just count)
416 * @param iter_cls closure for @a iter
417 * @return the number of results found
418 */
419static unsigned int
420get_typed (void *cls,
421 const struct GNUNET_HashCode *key,
422 enum GNUNET_BLOCK_Type type,
423 GNUNET_DATACACHE_Iterator iter,
424 void *iter_cls)
425{
426 struct Plugin *plugin = cls;
427 uint32_t type32 = type;
428 struct GNUNET_TIME_Absolute now;
429 unsigned int cnt;
430 uint32_t off;
431 uint32_t bro32;
432 unsigned int total;
433 struct GNUNET_DATACACHE_Block block;
434 void *path;
435 void *data;
436 size_t path_size;
437 struct GNUNET_SQ_QueryParam params_count[] = {
438 GNUNET_SQ_query_param_auto_from_type (key),
439 GNUNET_SQ_query_param_uint32 (&type32),
440 GNUNET_SQ_query_param_absolute_time (&now),
441 GNUNET_SQ_query_param_end
442 };
443 struct GNUNET_SQ_QueryParam params_select[] = {
444 GNUNET_SQ_query_param_auto_from_type (key),
445 GNUNET_SQ_query_param_uint32 (&type32),
446 GNUNET_SQ_query_param_absolute_time (&now),
447 GNUNET_SQ_query_param_uint32 (&off),
448 GNUNET_SQ_query_param_end
449 };
450 struct GNUNET_SQ_ResultSpec rs[] = {
451 GNUNET_SQ_result_spec_variable_size (&data,
452 &block.data_size),
453 GNUNET_SQ_result_spec_absolute_time (&block.expiration_time),
454 GNUNET_SQ_result_spec_variable_size (&path,
455 &path_size),
456 GNUNET_SQ_result_spec_auto_from_type (&block.trunc_peer),
457 GNUNET_SQ_result_spec_uint32 (&bro32),
458 GNUNET_SQ_result_spec_end
459 };
460
461 now = GNUNET_TIME_absolute_get ();
462 LOG (GNUNET_ERROR_TYPE_DEBUG,
463 "Processing GET for key `%s'\n",
464 GNUNET_h2s (key));
465
466 if (GNUNET_OK !=
467 GNUNET_SQ_bind (plugin->get_count_stmt,
468 params_count))
469 {
470 LOG_SQLITE (plugin->dbh,
471 GNUNET_ERROR_TYPE_ERROR,
472 "sqlite3_bind_xxx");
473 GNUNET_SQ_reset (plugin->dbh,
474 plugin->get_count_stmt);
475 return 0;
476 }
477 if (SQLITE_ROW !=
478 sqlite3_step (plugin->get_count_stmt))
479 {
480 LOG_SQLITE (plugin->dbh,
481 GNUNET_ERROR_TYPE_ERROR,
482 "sqlite_step");
483 GNUNET_SQ_reset (plugin->dbh,
484 plugin->get_count_stmt);
485 LOG (GNUNET_ERROR_TYPE_DEBUG,
486 "No content found when processing GET for key `%s'\n",
487 GNUNET_h2s (key));
488 return 0;
489 }
490 total = sqlite3_column_int (plugin->get_count_stmt,
491 0);
492 GNUNET_SQ_reset (plugin->dbh,
493 plugin->get_count_stmt);
494 if ( (0 == total) ||
495 (NULL == iter) )
496 {
497 if (0 == total)
498 LOG (GNUNET_ERROR_TYPE_DEBUG,
499 "No content found when processing GET for key `%s'\n",
500 GNUNET_h2s (key));
501 return total;
502 }
503
504 cnt = 0;
505 block.key = *key;
506 off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
507 total);
508 while (cnt < total)
509 {
510 off = (off + 1) % total;
511 if (GNUNET_OK !=
512 GNUNET_SQ_bind (plugin->get_stmt,
513 params_select))
514 {
515 LOG_SQLITE (plugin->dbh,
516 GNUNET_ERROR_TYPE_ERROR,
517 "sqlite3_bind_xxx");
518 GNUNET_SQ_reset (plugin->dbh,
519 plugin->get_stmt);
520 return cnt;
521 }
522 if (SQLITE_ROW !=
523 sqlite3_step (plugin->get_stmt))
524 break;
525 if (GNUNET_OK !=
526 GNUNET_SQ_extract_result (plugin->get_stmt,
527 rs))
528 {
529 GNUNET_break (0);
530 GNUNET_SQ_reset (plugin->dbh,
531 plugin->get_stmt);
532 break;
533 }
534 if (0 != path_size % sizeof(struct GNUNET_DHT_PathElement))
535 {
536 GNUNET_break (0);
537 path_size = 0;
538 path = NULL;
539 }
540 block.data = data;
541 block.put_path = path;
542 block.put_path_length = path_size / sizeof(struct GNUNET_DHT_PathElement);
543 block.type = type;
544 block.ro = (enum GNUNET_DHT_RouteOption) bro32;
545 cnt++;
546 LOG (GNUNET_ERROR_TYPE_DEBUG,
547 "Found %u-byte result when processing GET for key `%s'\n",
548 (unsigned int) block.data_size,
549 GNUNET_h2s (&block.key));
550 if ( (NULL != iter) &&
551 (GNUNET_OK !=
552 iter (iter_cls,
553 &block)) )
554 {
555 GNUNET_SQ_cleanup_result (rs);
556 GNUNET_SQ_reset (plugin->dbh,
557 plugin->get_stmt);
558 break;
559 }
560 GNUNET_SQ_cleanup_result (rs);
561 GNUNET_SQ_reset (plugin->dbh,
562 plugin->get_stmt);
563 }
564 GNUNET_SQ_reset (plugin->dbh,
565 plugin->get_stmt);
566 return cnt;
567}
568
569
570/**
571 * Iterate over the results for a particular key
572 * in the datastore.
573 *
574 * @param cls closure (our `struct Plugin`)
575 * @param key
576 * @param type entries of which type are relevant?
577 * @param iter maybe NULL (to just count)
578 * @param iter_cls closure for @a iter
579 * @return the number of results found
580 */
581static unsigned int
582sqlite_plugin_get (void *cls,
583 const struct GNUNET_HashCode *key,
584 enum GNUNET_BLOCK_Type type,
585 GNUNET_DATACACHE_Iterator iter,
586 void *iter_cls)
587{
588 if (GNUNET_BLOCK_TYPE_ANY == type)
589 return get_any (cls,
590 key,
591 iter,
592 iter_cls);
593 return get_typed (cls,
594 key,
595 type,
596 iter,
597 iter_cls);
598}
599
600
601/**
602 * Delete the entry with the lowest expiration value
603 * from the datacache right now.
604 *
605 * @param cls closure (our `struct Plugin`)
606 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
607 */
608static enum GNUNET_GenericReturnValue
609sqlite_plugin_del (void *cls)
610{
611 struct Plugin *plugin = cls;
612 uint64_t rowid;
613 void *data;
614 size_t data_size;
615 struct GNUNET_HashCode hc;
616 struct GNUNET_TIME_Absolute now;
617 struct GNUNET_SQ_ResultSpec rs[] = {
618 GNUNET_SQ_result_spec_uint64 (&rowid),
619 GNUNET_SQ_result_spec_auto_from_type (&hc),
620 GNUNET_SQ_result_spec_variable_size (&data,
621 &data_size),
622 GNUNET_SQ_result_spec_end
623 };
624 struct GNUNET_SQ_QueryParam params[] = {
625 GNUNET_SQ_query_param_uint64 (&rowid),
626 GNUNET_SQ_query_param_end
627 };
628 struct GNUNET_SQ_QueryParam time_params[] = {
629 GNUNET_SQ_query_param_absolute_time (&now),
630 GNUNET_SQ_query_param_end
631 };
632
633 LOG (GNUNET_ERROR_TYPE_DEBUG,
634 "Processing DEL\n");
635 now = GNUNET_TIME_absolute_get ();
636 if (GNUNET_OK !=
637 GNUNET_SQ_bind (plugin->del_expired_stmt,
638 time_params))
639 {
640 LOG_SQLITE (plugin->dbh,
641 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
642 "sqlite3_bind");
643 GNUNET_SQ_reset (plugin->dbh,
644 plugin->del_expired_stmt);
645 return GNUNET_SYSERR;
646 }
647 if ( (SQLITE_ROW !=
648 sqlite3_step (plugin->del_expired_stmt)) ||
649 (GNUNET_OK !=
650 GNUNET_SQ_extract_result (plugin->del_expired_stmt,
651 rs)))
652 {
653 GNUNET_SQ_reset (plugin->dbh,
654 plugin->del_expired_stmt);
655 if (SQLITE_ROW !=
656 sqlite3_step (plugin->del_select_stmt))
657 {
658 LOG_SQLITE (plugin->dbh,
659 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
660 "sqlite3_step");
661 GNUNET_SQ_reset (plugin->dbh,
662 plugin->del_select_stmt);
663 return GNUNET_SYSERR;
664 }
665 if (GNUNET_OK !=
666 GNUNET_SQ_extract_result (plugin->del_select_stmt,
667 rs))
668 {
669 GNUNET_SQ_reset (plugin->dbh,
670 plugin->del_select_stmt);
671 GNUNET_break (0);
672 return GNUNET_SYSERR;
673 }
674 }
675 GNUNET_SQ_cleanup_result (rs);
676 GNUNET_SQ_reset (plugin->dbh,
677 plugin->del_select_stmt);
678 if (GNUNET_OK !=
679 GNUNET_SQ_bind (plugin->del_stmt,
680 params))
681 {
682 LOG_SQLITE (plugin->dbh,
683 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
684 "sqlite3_bind");
685 GNUNET_SQ_reset (plugin->dbh,
686 plugin->del_stmt);
687 return GNUNET_SYSERR;
688 }
689 if (SQLITE_DONE !=
690 sqlite3_step (plugin->del_stmt))
691 {
692 LOG_SQLITE (plugin->dbh,
693 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
694 "sqlite3_step");
695 GNUNET_SQ_reset (plugin->dbh,
696 plugin->del_stmt);
697 return GNUNET_SYSERR;
698 }
699 plugin->num_items--;
700 plugin->env->delete_notify (plugin->env->cls,
701 &hc,
702 data_size + OVERHEAD);
703 GNUNET_SQ_reset (plugin->dbh,
704 plugin->del_stmt);
705 return GNUNET_OK;
706}
707
708
709/**
710 * Iterate over the results that are "close" to a particular key in
711 * the datacache. "close" is defined as numerically larger than @a
712 * key (when interpreted as a circular address space), with small
713 * distance.
714 *
715 * @param cls closure (internal context for the plugin)
716 * @param key area of the keyspace to look into
717 * @param type desired block type for the replies
718 * @param num_results number of results that should be returned to @a iter
719 * @param iter maybe NULL (to just count)
720 * @param iter_cls closure for @a iter
721 * @return the number of results found
722 */
723static unsigned int
724sqlite_plugin_get_closest (void *cls,
725 const struct GNUNET_HashCode *key,
726 enum GNUNET_BLOCK_Type type,
727 unsigned int num_results,
728 GNUNET_DATACACHE_Iterator iter,
729 void *iter_cls)
730{
731 struct Plugin *plugin = cls;
732 uint32_t type32 = type;
733 uint32_t num_results32 = num_results;
734 struct GNUNET_TIME_Absolute now;
735 void *data;
736 void *path;
737 size_t path_size;
738 unsigned int cnt;
739 uint32_t bro32;
740 struct GNUNET_DATACACHE_Block block;
741 uint32_t rtype32;
742 struct GNUNET_SQ_QueryParam params[] = {
743 GNUNET_SQ_query_param_auto_from_type (key),
744 GNUNET_SQ_query_param_absolute_time (&now),
745 GNUNET_SQ_query_param_uint32 (&type32),
746 GNUNET_SQ_query_param_uint32 (&num_results32),
747 GNUNET_SQ_query_param_end
748 };
749 struct GNUNET_SQ_ResultSpec rs[] = {
750 GNUNET_SQ_result_spec_variable_size (&data,
751 &block.data_size),
752 GNUNET_SQ_result_spec_absolute_time (&block.expiration_time),
753 GNUNET_SQ_result_spec_variable_size (&path,
754 &path_size),
755 GNUNET_SQ_result_spec_auto_from_type (&block.trunc_peer),
756 GNUNET_SQ_result_spec_uint32 (&rtype32),
757 GNUNET_SQ_result_spec_uint32 (&bro32),
758 GNUNET_SQ_result_spec_auto_from_type (&block.key),
759 GNUNET_SQ_result_spec_end
760 };
761
762 now = GNUNET_TIME_absolute_get ();
763 LOG (GNUNET_ERROR_TYPE_DEBUG,
764 "Processing GET_CLOSEST for key `%s'\n",
765 GNUNET_h2s (key));
766 if (GNUNET_OK !=
767 GNUNET_SQ_bind (plugin->get_closest_stmt,
768 params))
769 {
770 LOG_SQLITE (plugin->dbh,
771 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
772 "sqlite3_bind_xxx");
773 GNUNET_SQ_reset (plugin->dbh,
774 plugin->get_closest_stmt);
775 return 0;
776 }
777 cnt = 0;
778 while (SQLITE_ROW ==
779 sqlite3_step (plugin->get_closest_stmt))
780 {
781 if (GNUNET_OK !=
782 GNUNET_SQ_extract_result (plugin->get_closest_stmt,
783 rs))
784 {
785 GNUNET_break (0);
786 break;
787 }
788 if (0 != path_size % sizeof(struct GNUNET_DHT_PathElement))
789 {
790 GNUNET_break (0);
791 path_size = 0;
792 path = NULL;
793 }
794 block.put_path_length
795 = path_size / sizeof(struct GNUNET_DHT_PathElement);
796 block.put_path = path;
797 block.data = data;
798 block.type = (enum GNUNET_BLOCK_Type) rtype32;
799 block.ro = (enum GNUNET_DHT_RouteOption) bro32;
800 cnt++;
801 LOG (GNUNET_ERROR_TYPE_DEBUG,
802 "Found %u-byte result at %s when processing GET_CLOSE\n",
803 (unsigned int) block.data_size,
804 GNUNET_h2s (&block.key));
805
806 if (GNUNET_OK !=
807 iter (iter_cls,
808 &block))
809 {
810 GNUNET_SQ_cleanup_result (rs);
811 break;
812 }
813 GNUNET_SQ_cleanup_result (rs);
814 }
815 GNUNET_SQ_reset (plugin->dbh,
816 plugin->get_closest_stmt);
817 return cnt;
818}
819
820
821/**
822 * Entry point for the plugin.
823 *
824 * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironment`)
825 * @return the plugin's closure (our `struct Plugin`)
826 */
827void *
828libgnunet_plugin_datacache_sqlite_init (void *cls)
829{
830 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
831 struct GNUNET_DATACACHE_PluginFunctions *api;
832 struct Plugin *plugin;
833 char *fn;
834 char *fn_utf8;
835 sqlite3 *dbh;
836 char *emsg;
837
838 if (GNUNET_YES ==
839 GNUNET_CONFIGURATION_get_value_yesno (env->cfg,
840 "datacache-sqlite",
841 "IN_MEMORY"))
842 {
843 if (SQLITE_OK !=
844 sqlite3_open (":memory:",
845 &dbh))
846 return NULL;
847 fn_utf8 = NULL;
848 }
849 else
850 {
851 fn = GNUNET_DISK_mktemp ("gnunet-datacache");
852 if (NULL == fn)
853 {
854 GNUNET_break (0);
855 return NULL;
856 }
857 /* fn should be UTF-8-encoded. If it isn't, it's a bug. */
858 fn_utf8 = GNUNET_strdup (fn);
859 if (SQLITE_OK !=
860 sqlite3_open (fn_utf8,
861 &dbh))
862 {
863 GNUNET_free (fn);
864 GNUNET_free (fn_utf8);
865 return NULL;
866 }
867 GNUNET_free (fn);
868 }
869
870 SQLITE3_EXEC (dbh, "PRAGMA temp_store=MEMORY");
871 SQLITE3_EXEC (dbh, "PRAGMA locking_mode=EXCLUSIVE");
872 SQLITE3_EXEC (dbh, "PRAGMA journal_mode=OFF");
873 SQLITE3_EXEC (dbh, "PRAGMA synchronous=OFF");
874 SQLITE3_EXEC (dbh, "PRAGMA page_size=4092");
875 if (GNUNET_YES ==
876 GNUNET_CONFIGURATION_get_value_yesno (env->cfg,
877 "datacache-sqlite",
878 "IN_MEMORY"))
879 SQLITE3_EXEC (dbh, "PRAGMA sqlite_temp_store=3");
880
881 SQLITE3_EXEC (dbh,
882 "CREATE TABLE ds180 ("
883 " type INTEGER NOT NULL DEFAULT 0,"
884 " ro INTEGER NOT NULL DEFAULT 0,"
885 " expire INTEGER NOT NULL,"
886 " key BLOB NOT NULL DEFAULT '',"
887 " prox INTEGER NOT NULL,"
888 " value BLOB NOT NULL,"
889 " trunc BLOB NOT NULL,"
890 " path BLOB DEFAULT '')");
891 SQLITE3_EXEC (dbh,
892 "CREATE INDEX idx_hashidx"
893 " ON ds180 (key,type,expire)");
894 SQLITE3_EXEC (dbh,
895 "CREATE INDEX idx_prox_expire"
896 " ON ds180 (prox,expire)");
897 SQLITE3_EXEC (dbh,
898 "CREATE INDEX idx_expire_only"
899 " ON ds180 (expire)");
900 plugin = GNUNET_new (struct Plugin);
901 plugin->env = env;
902 plugin->dbh = dbh;
903 plugin->fn = fn_utf8;
904
905 if ((SQLITE_OK !=
906 sq_prepare (plugin->dbh,
907 "INSERT INTO ds180"
908 " (type, ro, expire, key, prox, value, path, trunc)"
909 " VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
910 &plugin->insert_stmt)) ||
911 (SQLITE_OK !=
912 sq_prepare (plugin->dbh,
913 "SELECT COUNT(*) FROM ds180 "
914 "WHERE key=?"
915 " AND type=?"
916 " AND expire >= ?",
917 &plugin->get_count_stmt)) ||
918 (SQLITE_OK !=
919 sq_prepare (plugin->dbh,
920 "SELECT COUNT(*) FROM ds180 "
921 "WHERE key=? AND expire >= ?",
922 &plugin->get_count_any_stmt)) ||
923 (SQLITE_OK !=
924 sq_prepare (plugin->dbh,
925 "SELECT value,expire,path,trunc,ro"
926 " FROM ds180"
927 " WHERE key=?"
928 " AND type=?"
929 " AND expire >= ?"
930 " LIMIT 1 OFFSET ?",
931 &plugin->get_stmt)) ||
932 (SQLITE_OK !=
933 sq_prepare (plugin->dbh,
934 "SELECT value,expire,path,trunc,type,ro"
935 " FROM ds180"
936 " WHERE key=?"
937 " AND expire >= ?"
938 " LIMIT 1 OFFSET ?",
939 &plugin->get_any_stmt)) ||
940 (SQLITE_OK !=
941 sq_prepare (plugin->dbh,
942 "SELECT _ROWID_,key,value FROM ds180"
943 " WHERE expire < ?1"
944 " ORDER BY expire ASC LIMIT 1",
945 &plugin->del_expired_stmt)) ||
946 (SQLITE_OK !=
947 sq_prepare (plugin->dbh,
948 "SELECT _ROWID_,key,value FROM ds180"
949 " ORDER BY prox ASC, expire ASC LIMIT 1",
950 &plugin->del_select_stmt)) ||
951 (SQLITE_OK !=
952 sq_prepare (plugin->dbh,
953 "DELETE FROM ds180 WHERE _ROWID_=?",
954 &plugin->del_stmt)) ||
955 (SQLITE_OK !=
956 sq_prepare (plugin->dbh,
957 "SELECT * FROM ("
958 " SELECT value,expire,path,trunc,type,ro,key"
959 " FROM ds180 "
960 " WHERE key>=?1 "
961 " AND expire >= ?2"
962 " AND ( (type=?3) or (0 == ?3) )"
963 " ORDER BY KEY ASC LIMIT ?4)"
964 "UNION "
965 "SELECT * FROM ("
966 " SELECT value,expire,path,trunc,type,ro,key"
967 " FROM ds180 "
968 " WHERE key<=?1 "
969 " AND expire >= ?2"
970 " AND ( (type=?3) or (0 == ?3) )"
971 " ORDER BY KEY DESC LIMIT ?4)",
972 &plugin->get_closest_stmt)))
973 {
974 LOG_SQLITE (plugin->dbh,
975 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
976 "sq_prepare");
977 GNUNET_break (SQLITE_OK ==
978 sqlite3_close (plugin->dbh));
979 GNUNET_free (plugin);
980 return NULL;
981 }
982
983 api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
984 api->cls = plugin;
985 api->get = &sqlite_plugin_get;
986 api->put = &sqlite_plugin_put;
987 api->del = &sqlite_plugin_del;
988 api->get_closest = &sqlite_plugin_get_closest;
989 LOG (GNUNET_ERROR_TYPE_INFO,
990 "Sqlite datacache running\n");
991 return api;
992}
993
994
995/**
996 * Exit point from the plugin.
997 *
998 * @param cls closure (our `struct Plugin`)
999 * @return NULL
1000 */
1001void *
1002libgnunet_plugin_datacache_sqlite_done (void *cls)
1003{
1004 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
1005 struct Plugin *plugin = api->cls;
1006 int result;
1007
1008#if SQLITE_VERSION_NUMBER >= 3007000
1009 sqlite3_stmt *stmt;
1010#endif
1011
1012#if ! WINDOWS || defined(__CYGWIN__)
1013 if ( (NULL != plugin->fn) &&
1014 (0 != unlink (plugin->fn)) )
1015 LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING,
1016 "unlink",
1017 plugin->fn);
1018 GNUNET_free (plugin->fn);
1019#endif
1020 sqlite3_finalize (plugin->insert_stmt);
1021 sqlite3_finalize (plugin->get_count_stmt);
1022 sqlite3_finalize (plugin->get_count_any_stmt);
1023 sqlite3_finalize (plugin->get_stmt);
1024 sqlite3_finalize (plugin->get_any_stmt);
1025 sqlite3_finalize (plugin->del_select_stmt);
1026 sqlite3_finalize (plugin->del_expired_stmt);
1027 sqlite3_finalize (plugin->del_stmt);
1028 sqlite3_finalize (plugin->get_closest_stmt);
1029 result = sqlite3_close (plugin->dbh);
1030#if SQLITE_VERSION_NUMBER >= 3007000
1031 if (SQLITE_BUSY == result)
1032 {
1033 LOG (GNUNET_ERROR_TYPE_WARNING,
1034 _ (
1035 "Tried to close sqlite without finalizing all prepared statements.\n"));
1036 stmt = sqlite3_next_stmt (plugin->dbh,
1037 NULL);
1038 while (NULL != stmt)
1039 {
1040 result = sqlite3_finalize (stmt);
1041 if (result != SQLITE_OK)
1042 LOG (GNUNET_ERROR_TYPE_WARNING,
1043 "Failed to close statement %p: %d\n",
1044 stmt,
1045 result);
1046 stmt = sqlite3_next_stmt (plugin->dbh,
1047 NULL);
1048 }
1049 result = sqlite3_close (plugin->dbh);
1050 }
1051#endif
1052 if (SQLITE_OK != result)
1053 LOG_SQLITE (plugin->dbh,
1054 GNUNET_ERROR_TYPE_ERROR,
1055 "sqlite3_close");
1056
1057 GNUNET_free (plugin);
1058 GNUNET_free (api);
1059 return NULL;
1060}
1061
1062
1063/* end of plugin_datacache_sqlite.c */
diff --git a/src/plugin/datacache/plugin_datacache_template.c b/src/plugin/datacache/plugin_datacache_template.c
new file mode 100644
index 000000000..1bd712d39
--- /dev/null
+++ b/src/plugin/datacache/plugin_datacache_template.c
@@ -0,0 +1,172 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2006, 2009, 2015, 2022 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file datacache/plugin_datacache_template.c
23 * @brief template for an implementation of a database backend for the datacache
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_datacache_plugin.h"
29
30
31/**
32 * Context for all functions in this plugin.
33 */
34struct Plugin
35{
36 /**
37 * Our execution environment.
38 */
39 struct GNUNET_DATACACHE_PluginEnvironment *env;
40};
41
42
43/**
44 * Store an item in the datastore.
45 *
46 * @param cls closure (our `struct Plugin`)
47 * @param xor_distance distance of @a key to our PID
48 * @param block data to store
49 * @return 0 if duplicate, -1 on error, number of bytes used otherwise
50 */
51static ssize_t
52template_plugin_put (void *cls,
53 uint32_t xor_distance,
54 const struct GNUNET_DATACACHE_Block *block)
55{
56 GNUNET_break (0);
57 return -1;
58}
59
60
61/**
62 * Iterate over the results for a particular key
63 * in the datastore.
64 *
65 * @param cls closure (our `struct Plugin`)
66 * @param key
67 * @param type entries of which type are relevant?
68 * @param iter maybe NULL (to just count)
69 * @param iter_cls closure for @a iter
70 * @return the number of results found
71 */
72static unsigned int
73template_plugin_get (void *cls,
74 const struct GNUNET_HashCode *key,
75 enum GNUNET_BLOCK_Type type,
76 GNUNET_DATACACHE_Iterator iter,
77 void *iter_cls)
78{
79 GNUNET_break (0);
80 return 0;
81}
82
83
84/**
85 * Delete the entry with the lowest expiration value
86 * from the datacache right now.
87 *
88 * @param cls closure (our `struct Plugin`)
89 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
90 */
91static enum GNUNET_GenericReturnValue
92template_plugin_del (void *cls)
93{
94 GNUNET_break (0);
95 return GNUNET_SYSERR;
96}
97
98
99/**
100 * Iterate over the results that are "close" to a particular key in
101 * the datacache. "close" is defined as numerically larger than @a
102 * key (when interpreted as a circular address space), with small
103 * distance.
104 *
105 * @param cls closure (internal context for the plugin)
106 * @param key area of the keyspace to look into
107 * @param type desired block type for the replies
108 * @param num_results number of results that should be returned to @a iter
109 * @param iter maybe NULL (to just count)
110 * @param iter_cls closure for @a iter
111 * @return the number of results found
112 */
113static unsigned int
114template_plugin_get_closest (void *cls,
115 const struct GNUNET_HashCode *key,
116 enum GNUNET_BLOCK_Type type,
117 unsigned int num_results,
118 GNUNET_DATACACHE_Iterator iter,
119 void *iter_cls)
120{
121 GNUNET_break (0);
122 return 0;
123}
124
125
126/**
127 * Entry point for the plugin.
128 *
129 * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`)
130 * @return the plugin's closure (our `struct Plugin`)
131 */
132void *
133libgnunet_plugin_datacache_template_init (void *cls)
134{
135 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
136 struct GNUNET_DATACACHE_PluginFunctions *api;
137 struct Plugin *plugin;
138
139 plugin = GNUNET_new (struct Plugin);
140 plugin->env = env;
141 api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
142 api->cls = plugin;
143 api->get = &template_plugin_get;
144 api->put = &template_plugin_put;
145 api->del = &template_plugin_del;
146 api->get_closest = &template_plugin_get_closest;
147 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
148 "template",
149 "Template datacache running\n");
150 return api;
151}
152
153
154/**
155 * Exit point from the plugin.
156 *
157 * @param cls closure (our `struct Plugin`)
158 * @return NULL
159 */
160void *
161libgnunet_plugin_datacache_template_done (void *cls)
162{
163 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
164 struct Plugin *plugin = api->cls;
165
166 GNUNET_free (plugin);
167 GNUNET_free (api);
168 return NULL;
169}
170
171
172/* end of plugin_datacache_template.c */
diff --git a/src/plugin/datastore/Makefile.am b/src/plugin/datastore/Makefile.am
new file mode 100644
index 000000000..1f4ab59c8
--- /dev/null
+++ b/src/plugin/datastore/Makefile.am
@@ -0,0 +1,150 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4plugindir = $(libdir)/gnunet
5
6pkgcfgdir= $(pkgdatadir)/config.d/
7
8libexecdir= $(pkglibdir)/libexec/
9
10sqldir = $(prefix)/share/gnunet/sql/
11
12sql_DATA = \
13 datastore-0001.sql \
14 datastore-drop.sql
15
16if USE_COVERAGE
17 AM_CFLAGS = --coverage -O0
18 XLIBS = -lgcov
19endif
20
21
22if HAVE_SQLITE
23 SQLITE_PLUGIN = libgnunet_plugin_datastore_sqlite.la
24if HAVE_BENCHMARKS
25 SQLITE_BENCHMARKS = \
26 perf_plugin_datastore_sqlite
27endif
28 SQLITE_TESTS = \
29 test_plugin_datastore_sqlite \
30 $(SQLITE_BENCHMARKS)
31endif
32if HAVE_POSTGRESQL
33 POSTGRES_PLUGIN = libgnunet_plugin_datastore_postgres.la
34if HAVE_BENCHMARKS
35 POSTGRES_BENCHMARKS = \
36 perf_plugin_datastore_postgres
37endif
38 POSTGRES_TESTS = \
39 test_plugin_datastore_postgres \
40 $(POSTGRES_BENCHMARKS)
41endif
42
43plugin_LTLIBRARIES = \
44 $(SQLITE_PLUGIN) \
45 $(POSTGRES_PLUGIN) \
46 libgnunet_plugin_datastore_heap.la
47
48# Real plugins should of course go into
49# plugin_LTLIBRARIES
50noinst_LTLIBRARIES = \
51 libgnunet_plugin_datastore_template.la
52
53
54libgnunet_plugin_datastore_sqlite_la_SOURCES = \
55 plugin_datastore_sqlite.c
56libgnunet_plugin_datastore_sqlite_la_LIBADD = \
57 $(top_builddir)/src/lib/sq/libgnunetsq.la \
58 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) -lsqlite3 \
59 $(LTLIBINTL)
60libgnunet_plugin_datastore_sqlite_la_LDFLAGS = \
61 $(GN_PLUGIN_LDFLAGS)
62
63
64libgnunet_plugin_datastore_heap_la_SOURCES = \
65 plugin_datastore_heap.c
66libgnunet_plugin_datastore_heap_la_LIBADD = \
67 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) \
68 $(LTLIBINTL)
69libgnunet_plugin_datastore_heap_la_LDFLAGS = \
70 $(GN_PLUGIN_LDFLAGS)
71
72
73libgnunet_plugin_datastore_postgres_la_SOURCES = \
74 plugin_datastore_postgres.c
75libgnunet_plugin_datastore_postgres_la_LIBADD = \
76 $(top_builddir)/src/lib/pq/libgnunetpq.la \
77 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) -lpq
78libgnunet_plugin_datastore_postgres_la_LDFLAGS = \
79 $(GN_PLUGIN_LDFLAGS) $(POSTGRESQL_LDFLAGS)
80libgnunet_plugin_datastore_postgres_la_CPPFLAGS = \
81 $(POSTGRESQL_CPPFLAGS) $(AM_CPPFLAGS)
82
83
84libgnunet_plugin_datastore_template_la_SOURCES = \
85 plugin_datastore_template.c
86libgnunet_plugin_datastore_template_la_LIBADD = \
87 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) \
88 $(LTLIBINTL)
89libgnunet_plugin_datastore_template_la_LDFLAGS = \
90 $(GN_PLUGIN_LDFLAGS)
91
92check_PROGRAMS = \
93 perf_plugin_datastore_heap \
94 test_plugin_datastore_heap \
95 $(SQLITE_TESTS) \
96 $(POSTGRES_TESTS)
97
98if ENABLE_TEST_RUN
99AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
100TESTS = $(check_PROGRAMS)
101endif
102
103perf_plugin_datastore_heap_SOURCES = \
104 perf_plugin_datastore.c
105perf_plugin_datastore_heap_LDADD = \
106 $(top_builddir)/src/service/testing/libgnunettesting.la \
107 $(top_builddir)/src/lib/util/libgnunetutil.la
108
109test_plugin_datastore_heap_SOURCES = \
110 test_plugin_datastore.c
111test_plugin_datastore_heap_LDADD = \
112 $(top_builddir)/src/service/testing/libgnunettesting.la \
113 $(top_builddir)/src/lib/util/libgnunetutil.la
114
115
116perf_plugin_datastore_sqlite_SOURCES = \
117 perf_plugin_datastore.c
118perf_plugin_datastore_sqlite_LDADD = \
119 $(top_builddir)/src/service/testing/libgnunettesting.la \
120 $(top_builddir)/src/lib/util/libgnunetutil.la
121
122test_plugin_datastore_sqlite_SOURCES = \
123 test_plugin_datastore.c
124test_plugin_datastore_sqlite_LDADD = \
125 $(top_builddir)/src/service/testing/libgnunettesting.la \
126 $(top_builddir)/src/lib/util/libgnunetutil.la
127
128
129test_plugin_datastore_postgres_SOURCES = \
130 test_plugin_datastore.c
131test_plugin_datastore_postgres_LDADD = \
132 $(top_builddir)/src/service/testing/libgnunettesting.la \
133 $(top_builddir)/src/lib/util/libgnunetutil.la
134
135perf_plugin_datastore_postgres_SOURCES = \
136 perf_plugin_datastore.c
137perf_plugin_datastore_postgres_LDADD = \
138 $(top_builddir)/src/service/testing/libgnunettesting.la \
139 $(top_builddir)/src/lib/util/libgnunetutil.la
140
141
142EXTRA_DIST = \
143 test_defaults.conf \
144 perf_plugin_datastore_data_sqlite.conf \
145 test_plugin_datastore_data_sqlite.conf \
146 perf_plugin_datastore_data_heap.conf \
147 test_plugin_datastore_data_heap.conf \
148 perf_plugin_datastore_data_postgres.conf \
149 test_plugin_datastore_data_postgres.conf \
150 $(sql_DATA)
diff --git a/src/plugin/datastore/datastore-0001.sql b/src/plugin/datastore/datastore-0001.sql
new file mode 100644
index 000000000..0d4758be2
--- /dev/null
+++ b/src/plugin/datastore/datastore-0001.sql
@@ -0,0 +1,49 @@
1--
2-- This file is part of GNUnet
3-- Copyright (C) 2014--2022 GNUnet e.V.
4--
5-- GNUnet is free software; you can redistribute it and/or modify it under the
6-- terms of the GNU General Public License as published by the Free Software
7-- Foundation; either version 3, or (at your option) any later version.
8--
9-- GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY
10-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12--
13-- You should have received a copy of the GNU General Public License along with
14-- GNUnet; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15--
16
17-- Everything in one big transaction
18BEGIN;
19
20-- Check patch versioning is in place.
21SELECT _v.register_patch('datastore-0001', NULL, NULL);
22
23-------------------- Schema ----------------------------
24
25CREATE SCHEMA datastore;
26COMMENT ON SCHEMA datastore IS 'gnunet-datastore data';
27
28SET search_path TO datastore;
29
30CREATE TABLE IF NOT EXISTS gn090 (
31 repl INTEGER NOT NULL DEFAULT 0,
32 type INTEGER NOT NULL DEFAULT 0,
33 prio INTEGER NOT NULL DEFAULT 0,
34 anonLevel INTEGER NOT NULL DEFAULT 0,
35 expire BIGINT NOT NULL DEFAULT 0,
36 rvalue BIGINT NOT NULL DEFAULT 0,
37 hash BYTEA NOT NULL DEFAULT '',
38 vhash BYTEA NOT NULL DEFAULT '',
39 value BYTEA NOT NULL DEFAULT '',
40 oid BIGINT GENERATED BY DEFAULT AS IDENTITY);
41
42CREATE INDEX IF NOT EXISTS oid_hash ON gn090 (oid);
43CREATE INDEX IF NOT EXISTS idx_hash ON gn090 (hash);
44CREATE INDEX IF NOT EXISTS idx_prio_anon ON gn090 (prio,anonLevel);
45CREATE INDEX IF NOT EXISTS idx_prio_hash_anon ON gn090 (prio,hash,anonLevel);
46CREATE INDEX IF NOT EXISTS idx_repl_rvalue ON gn090 (repl,rvalue);
47CREATE INDEX IF NOT EXISTS idx_expire_hash ON gn090 (expire,hash);
48
49COMMIT;
diff --git a/src/plugin/datastore/datastore-drop.sql b/src/plugin/datastore/datastore-drop.sql
new file mode 100644
index 000000000..67fee303d
--- /dev/null
+++ b/src/plugin/datastore/datastore-drop.sql
@@ -0,0 +1,25 @@
1--
2-- This file is part of GNUnet
3-- Copyright (C) 2014--2022 GNUnet e.V.
4--
5-- GNUnet is free software; you can redistribute it and/or modify it under the
6-- terms of the GNU General Public License as published by the Free Software
7-- Foundation; either version 3, or (at your option) any later version.
8--
9-- GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY
10-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12--
13-- You should have received a copy of the GNU General Public License along with
14-- GNUnet; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15--
16
17-- Everything in one big transaction
18BEGIN;
19
20
21SELECT _v.unregister_patch('datastore-0001');
22
23DROP SCHEMA datastore CASCADE;
24
25COMMIT;
diff --git a/src/plugin/datastore/meson.build b/src/plugin/datastore/meson.build
new file mode 100644
index 000000000..6769cb78b
--- /dev/null
+++ b/src/plugin/datastore/meson.build
@@ -0,0 +1,75 @@
1install_data ('datastore-0001.sql',
2 'datastore-drop.sql',
3 install_dir: get_option('datadir')/'gnunet'/'sql')
4
5shared_module('gnunet_plugin_datastore_sqlite',
6 ['plugin_datastore_sqlite.c'],
7 dependencies: [libgnunetutil_dep,
8 sqlite_dep,
9 libgnunetsq_dep],
10 include_directories: [incdir, configuration_inc],
11 install: true,
12 install_dir: get_option('libdir')/'gnunet')
13
14shared_module('gnunet_plugin_datastore_heap',
15 ['plugin_datastore_heap.c'],
16 dependencies: [libgnunetutil_dep],
17 include_directories: [incdir, configuration_inc],
18 install: true,
19 install_dir: get_option('libdir')/'gnunet')
20
21if pq_dep.found()
22 shared_module('gnunet_plugin_datastore_postgres',
23 ['plugin_datastore_postgres.c'],
24 dependencies: [libgnunetutil_dep,
25 pq_dep,
26 libgnunetpq_dep],
27 include_directories: [incdir, configuration_inc],
28 install: true,
29 install_dir: get_option('libdir')/'gnunet')
30endif
31
32testds_plugin_sqlite = executable ('test_plugin_datastore_sqlite',
33 ['test_plugin_datastore.c'],
34 dependencies: [
35 libgnunetutil_dep,
36 ],
37 include_directories: [incdir, configuration_inc],
38 install: false)
39
40testds_plugin_heap = executable ('test_plugin_datastore_heap',
41 ['test_plugin_datastore.c'],
42 dependencies: [
43 libgnunetutil_dep,
44 ],
45 include_directories: [incdir, configuration_inc],
46 install: false)
47
48testds_plugin_pq = executable ('test_plugin_datastore_postgres',
49 ['test_plugin_datastore.c'],
50 dependencies: [
51 libgnunetutil_dep,
52 ],
53 include_directories: [incdir, configuration_inc],
54 install: false)
55
56configure_file(input : 'test_defaults.conf',
57 output : 'test_defaults.conf',
58 copy: true)
59configure_file(input : 'test_plugin_datastore_data_sqlite.conf',
60 output : 'test_plugin_datastore_data_sqlite.conf',
61 copy: true)
62configure_file(input : 'test_plugin_datastore_data_heap.conf',
63 output : 'test_plugin_datastore_data_heap.conf',
64 copy: true)
65configure_file(input : 'test_plugin_datastore_data_postgres.conf',
66 output : 'test_plugin_datastore_data_postgres.conf',
67 copy: true)
68
69test('test_plugin_datastore_sqlite', testds_plugin_sqlite,
70 suite: 'datastore', workdir: meson.current_build_dir())
71test('test_plugin_datastore_heap', testds_plugin_heap,
72 suite: 'datastore', workdir: meson.current_build_dir())
73test('test_plugin_datastore_postgres', testds_plugin_pq,
74 suite: 'datastore', workdir: meson.current_build_dir())
75
diff --git a/src/plugin/datastore/perf_plugin_datastore.c b/src/plugin/datastore/perf_plugin_datastore.c
new file mode 100644
index 000000000..8e63b08e6
--- /dev/null
+++ b/src/plugin/datastore/perf_plugin_datastore.c
@@ -0,0 +1,573 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2004, 2005, 2006, 2007, 2009, 2011 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/*
21 * @file perf_plugin_datastore.c
22 * @brief Profile database plugin directly, focusing on iterators.
23 * @author Christian Grothoff
24 */
25
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_protocols.h"
29#include "gnunet_datastore_plugin.h"
30#include "gnunet_testing_lib.h"
31#include <gauger.h>
32
33/**
34 * Target datastore size (in bytes). Realistic sizes are
35 * more like 16 GB (not the default of 16 MB); however,
36 * those take too long to run them in the usual "make check"
37 * sequence. Hence the value used for shipping is tiny.
38 */
39#define MAX_SIZE 1024LL * 1024 * 16 * 1
40
41#define ITERATIONS 2
42
43/**
44 * Number of put operations equivalent to 1/10th of MAX_SIZE
45 */
46#define PUT_10 (MAX_SIZE / 32 / 1024 / ITERATIONS)
47
48static char category[256];
49
50static unsigned int hits[PUT_10 / 8 + 1];
51
52static unsigned long long stored_bytes;
53
54static unsigned long long stored_entries;
55
56static unsigned long long stored_ops;
57
58static const char *plugin_name;
59
60static int ok;
61
62enum RunPhase
63{
64 RP_ERROR = 0,
65 RP_PUT,
66 RP_REP_GET,
67 RP_ZA_GET,
68 RP_EXP_GET,
69 RP_DONE
70};
71
72
73struct CpsRunContext
74{
75 unsigned int i;
76 struct GNUNET_TIME_Absolute start;
77 struct GNUNET_TIME_Absolute end;
78 const struct GNUNET_CONFIGURATION_Handle *cfg;
79 struct GNUNET_DATASTORE_PluginFunctions *api;
80 enum RunPhase phase;
81 unsigned int cnt;
82 unsigned int iter;
83 uint64_t offset;
84};
85
86
87/**
88 * Function called by plugins to notify us about a
89 * change in their disk utilization.
90 *
91 * @param cls closure (NULL)
92 * @param delta change in disk utilization,
93 * 0 for "reset to empty"
94 */
95static void
96disk_utilization_change_cb (void *cls, int delta)
97{
98}
99
100
101static void
102test (void *cls);
103
104
105/**
106 * Put continuation.
107 *
108 * @param cls closure
109 * @param key key for the item stored
110 * @param size size of the item stored
111 * @param status #GNUNET_OK or #GNUNET_SYSERROR
112 * @param msg error message on error
113 */
114static void
115put_continuation (void *cls,
116 const struct GNUNET_HashCode *key,
117 uint32_t size,
118 int status,
119 const char *msg)
120{
121 struct CpsRunContext *crc = cls;
122
123 if (GNUNET_OK != status)
124 {
125 fprintf (stderr, "ERROR: `%s'\n", msg);
126 }
127 else
128 {
129 stored_bytes += size;
130 stored_ops++;
131 stored_entries++;
132 }
133 GNUNET_SCHEDULER_add_now (&test, crc);
134}
135
136
137static void
138do_put (struct CpsRunContext *crc)
139{
140 char value[65536];
141 size_t size;
142 static struct GNUNET_HashCode key;
143 static int i;
144 unsigned int prio;
145
146 if (0 == i)
147 crc->start = GNUNET_TIME_absolute_get ();
148 if (PUT_10 == i)
149 {
150 i = 0;
151 crc->end = GNUNET_TIME_absolute_get ();
152 {
153 printf ("%s took %s for %llu items\n", "Storing an item",
154 GNUNET_STRINGS_relative_time_to_string (
155 GNUNET_TIME_absolute_get_difference (crc->start,
156 crc
157 ->end),
158 GNUNET_YES),
159 PUT_10);
160 if (PUT_10 > 0)
161 GAUGER (category, "Storing an item",
162 (crc->end.abs_value_us - crc->start.abs_value_us) / 1000LL
163 / PUT_10,
164 "ms/item");
165 }
166 crc->i++;
167 crc->start = GNUNET_TIME_absolute_get ();
168 crc->phase++;
169 GNUNET_SCHEDULER_add_now (&test, crc);
170 return;
171 }
172 /* most content is 32k */
173 size = 32 * 1024;
174 if (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 16) == 0) /* but some of it is less! */
175 size = 8 + GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 32 * 1024);
176 size = size - (size & 7); /* always multiple of 8 */
177
178 /* generate random key */
179 key.bits[0] = (unsigned int) GNUNET_TIME_absolute_get ().abs_value_us;
180 GNUNET_CRYPTO_hash (&key, sizeof(struct GNUNET_HashCode), &key);
181 memset (value, i, size);
182 if (i > 255)
183 memset (value, i - 255, size / 2);
184 value[0] = crc->i;
185 GNUNET_memcpy (&value[4], &i, sizeof(i));
186 prio = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 100);
187 crc->api->put (crc->api->cls,
188 &key,
189 false /* absent */,
190 size,
191 value,
192 1 + i % 4 /* type */,
193 prio,
194 i % 4 /* anonymity */,
195 0 /* replication */,
196 GNUNET_TIME_relative_to_absolute
197 (GNUNET_TIME_relative_multiply
198 (GNUNET_TIME_UNIT_MILLISECONDS,
199 60 * 60 * 60 * 1000
200 + GNUNET_CRYPTO_random_u32
201 (GNUNET_CRYPTO_QUALITY_WEAK, 1000))),
202 put_continuation,
203 crc);
204 i++;
205}
206
207
208static int
209iterate_zeros (void *cls,
210 const struct GNUNET_HashCode *key,
211 uint32_t size,
212 const void *data,
213 enum GNUNET_BLOCK_Type type,
214 uint32_t priority,
215 uint32_t anonymity,
216 uint32_t replication,
217 struct GNUNET_TIME_Absolute expiration,
218 uint64_t uid)
219{
220 struct CpsRunContext *crc = cls;
221 int i;
222 const char *cdata = data;
223
224 GNUNET_assert (key != NULL);
225 GNUNET_assert (size >= 8);
226 GNUNET_memcpy (&i, &cdata[4], sizeof(i));
227 hits[i / 8] |= (1 << (i % 8));
228
229 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
230 "Found result %d type=%u, priority=%u, size=%u, expire=%s\n",
231 i,
232 type, priority, size,
233 GNUNET_STRINGS_absolute_time_to_string (expiration));
234 crc->cnt++;
235 if (crc->cnt == PUT_10 / 4 - 1)
236 {
237 unsigned int bc;
238
239 bc = 0;
240 for (i = 0; i < PUT_10; i++)
241 if (0 != (hits[i / 8] & (1 << (i % 8))))
242 bc++;
243
244 crc->end = GNUNET_TIME_absolute_get ();
245 printf ("%s took %s yielding %u/%u items\n",
246 "Select random zero-anonymity item",
247 GNUNET_STRINGS_relative_time_to_string (
248 GNUNET_TIME_absolute_get_difference (crc->start,
249 crc
250 ->end),
251 GNUNET_YES),
252 bc, crc->cnt);
253 if (crc->cnt > 0)
254 GAUGER (category, "Select random zero-anonymity item",
255 (crc->end.abs_value_us - crc->start.abs_value_us) / 1000LL
256 / crc->cnt,
257 "ms/item");
258 memset (hits, 0, sizeof(hits));
259 crc->phase++;
260 crc->cnt = 0;
261 crc->start = GNUNET_TIME_absolute_get ();
262 }
263 GNUNET_SCHEDULER_add_now (&test, crc);
264 return GNUNET_OK;
265}
266
267
268static int
269expiration_get (void *cls,
270 const struct GNUNET_HashCode *key,
271 uint32_t size,
272 const void *data,
273 enum GNUNET_BLOCK_Type type,
274 uint32_t priority,
275 uint32_t anonymity,
276 uint32_t replication,
277 struct GNUNET_TIME_Absolute expiration,
278 uint64_t uid)
279{
280 struct CpsRunContext *crc = cls;
281 int i;
282 const char *cdata = data;
283
284 GNUNET_assert (size >= 8);
285 GNUNET_memcpy (&i, &cdata[4], sizeof(i));
286 hits[i / 8] |= (1 << (i % 8));
287 crc->cnt++;
288 if (PUT_10 <= crc->cnt)
289 {
290 unsigned int bc;
291
292 bc = 0;
293 for (i = 0; i < PUT_10; i++)
294 if (0 != (hits[i / 8] & (1 << (i % 8))))
295 bc++;
296
297 crc->end = GNUNET_TIME_absolute_get ();
298 printf ("%s took %s yielding %u/%u items\n",
299 "Selecting and deleting by expiration",
300 GNUNET_STRINGS_relative_time_to_string (
301 GNUNET_TIME_absolute_get_difference (crc->start,
302 crc
303 ->end),
304 GNUNET_YES),
305 bc, (unsigned int) PUT_10);
306 if (crc->cnt > 0)
307 GAUGER (category, "Selecting and deleting by expiration",
308 (crc->end.abs_value_us - crc->start.abs_value_us) / 1000LL
309 / crc->cnt,
310 "ms/item");
311 memset (hits, 0, sizeof(hits));
312 if (++crc->iter == ITERATIONS)
313 crc->phase++;
314 else
315 crc->phase = RP_PUT;
316 crc->cnt = 0;
317 crc->start = GNUNET_TIME_absolute_get ();
318 }
319 GNUNET_SCHEDULER_add_now (&test, crc);
320 return GNUNET_NO;
321}
322
323
324static int
325replication_get (void *cls,
326 const struct GNUNET_HashCode *key,
327 uint32_t size,
328 const void *data,
329 enum GNUNET_BLOCK_Type type,
330 uint32_t priority,
331 uint32_t anonymity,
332 uint32_t replication,
333 struct GNUNET_TIME_Absolute expiration,
334 uint64_t uid)
335{
336 struct CpsRunContext *crc = cls;
337 int i;
338 const char *cdata = data;
339
340 GNUNET_assert (NULL != key);
341 GNUNET_assert (size >= 8);
342 GNUNET_memcpy (&i, &cdata[4], sizeof(i));
343 hits[i / 8] |= (1 << (i % 8));
344 crc->cnt++;
345 if (PUT_10 <= crc->cnt)
346 {
347 unsigned int bc;
348
349 bc = 0;
350 for (i = 0; i < PUT_10; i++)
351 if (0 != (hits[i / 8] & (1 << (i % 8))))
352 bc++;
353
354 crc->end = GNUNET_TIME_absolute_get ();
355 printf ("%s took %s yielding %u/%u items\n",
356 "Selecting random item for replication",
357 GNUNET_STRINGS_relative_time_to_string (
358 GNUNET_TIME_absolute_get_difference (crc->start,
359 crc
360 ->end),
361 GNUNET_YES),
362 bc, (unsigned int) PUT_10);
363 if (crc->cnt > 0)
364 GAUGER (category, "Selecting random item for replication",
365 (crc->end.abs_value_us - crc->start.abs_value_us) / 1000LL
366 / crc->cnt,
367 "ms/item");
368 memset (hits, 0, sizeof(hits));
369 crc->phase++;
370 crc->offset = 0;
371 crc->cnt = 0;
372 crc->start = GNUNET_TIME_absolute_get ();
373 }
374
375 GNUNET_SCHEDULER_add_now (&test, crc);
376 return GNUNET_OK;
377}
378
379
380/**
381 * Function called when the service shuts
382 * down. Unloads our datastore plugin.
383 *
384 * @param api api to unload
385 * @param cfg configuration to use
386 */
387static void
388unload_plugin (struct GNUNET_DATASTORE_PluginFunctions *api,
389 const struct GNUNET_CONFIGURATION_Handle *cfg)
390{
391 char *name;
392 char *libname;
393
394 if (GNUNET_OK !=
395 GNUNET_CONFIGURATION_get_value_string (cfg, "DATASTORE", "DATABASE",
396 &name))
397 {
398 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
399 _ ("No `%s' specified for `%s' in configuration!\n"),
400 "DATABASE",
401 "DATASTORE");
402 return;
403 }
404 GNUNET_asprintf (&libname, "libgnunet_plugin_datastore_%s", name);
405 GNUNET_break (NULL == GNUNET_PLUGIN_unload (libname, api));
406 GNUNET_free (libname);
407 GNUNET_free (name);
408}
409
410
411/**
412 * Last task run during shutdown. Disconnects us from
413 * the transport and core.
414 */
415static void
416cleaning_task (void *cls)
417{
418 struct CpsRunContext *crc = cls;
419
420 unload_plugin (crc->api, crc->cfg);
421 GNUNET_free (crc);
422}
423
424
425static void
426test (void *cls)
427{
428 struct CpsRunContext *crc = cls;
429
430 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
431 "In phase %d, iteration %u\n", crc->phase, crc->cnt);
432 switch (crc->phase)
433 {
434 case RP_ERROR:
435 GNUNET_break (0);
436 crc->api->drop (crc->api->cls);
437 ok = 1;
438 GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
439 &cleaning_task, crc);
440 break;
441
442 case RP_PUT:
443 do_put (crc);
444 break;
445
446 case RP_REP_GET:
447 crc->api->get_replication (crc->api->cls, &replication_get, crc);
448 break;
449
450 case RP_ZA_GET:
451 crc->api->get_zero_anonymity (crc->api->cls, crc->offset++, 1,
452 &iterate_zeros, crc);
453 break;
454
455 case RP_EXP_GET:
456 crc->api->get_expiration (crc->api->cls, &expiration_get, crc);
457 break;
458
459 case RP_DONE:
460 crc->api->drop (crc->api->cls);
461 ok = 0;
462 GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
463 &cleaning_task, crc);
464 break;
465 }
466}
467
468
469/**
470 * Load the datastore plugin.
471 */
472static struct GNUNET_DATASTORE_PluginFunctions *
473load_plugin (const struct GNUNET_CONFIGURATION_Handle *cfg)
474{
475 static struct GNUNET_DATASTORE_PluginEnvironment env;
476 struct GNUNET_DATASTORE_PluginFunctions *ret;
477 char *name;
478 char *libname;
479
480 if (GNUNET_OK !=
481 GNUNET_CONFIGURATION_get_value_string (cfg, "DATASTORE", "DATABASE",
482 &name))
483 {
484 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
485 _ ("No `%s' specified for `%s' in configuration!\n"),
486 "DATABASE",
487 "DATASTORE");
488 return NULL;
489 }
490 env.cfg = cfg;
491 env.duc = &disk_utilization_change_cb;
492 env.cls = NULL;
493 GNUNET_log (GNUNET_ERROR_TYPE_INFO, _ ("Loading `%s' datastore plugin\n"),
494 name);
495 GNUNET_asprintf (&libname, "libgnunet_plugin_datastore_%s", name);
496 if (NULL == (ret = GNUNET_PLUGIN_load (libname, &env)))
497 {
498 fprintf (stderr, "Failed to load plugin `%s'!\n", name);
499 GNUNET_free (name);
500 GNUNET_free (libname);
501 return NULL;
502 }
503 GNUNET_free (libname);
504 GNUNET_free (name);
505 return ret;
506}
507
508
509static void
510run (void *cls, char *const *args, const char *cfgfile,
511 const struct GNUNET_CONFIGURATION_Handle *c)
512{
513 struct GNUNET_DATASTORE_PluginFunctions *api;
514 struct CpsRunContext *crc;
515
516 if (NULL == c)
517 {
518 GNUNET_break (0);
519 return;
520 }
521 api = load_plugin (c);
522 if (api == NULL)
523 {
524 fprintf (stderr,
525 "%s",
526 "Could not initialize plugin, assuming database not configured. Test not run!\n");
527 return;
528 }
529 crc = GNUNET_new (struct CpsRunContext);
530 crc->api = api;
531 crc->cfg = c;
532 crc->phase = RP_PUT;
533 ok = 2;
534 GNUNET_SCHEDULER_add_now (&test, crc);
535}
536
537
538int
539main (int argc, char *argv[])
540{
541 char dir_name[PATH_MAX];
542 char cfg_name[PATH_MAX];
543 char *const xargv[] = {
544 "perf-plugin-datastore",
545 "-c",
546 cfg_name,
547 NULL
548 };
549 struct GNUNET_GETOPT_CommandLineOption options[] = {
550 GNUNET_GETOPT_OPTION_END
551 };
552
553 plugin_name = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
554 GNUNET_snprintf (dir_name, sizeof(dir_name), "/tmp/perf-gnunet-datastore-%s",
555 plugin_name);
556 GNUNET_DISK_directory_remove (dir_name);
557 GNUNET_log_setup ("perf-plugin-datastore",
558 "WARNING",
559 NULL);
560 GNUNET_snprintf (category, sizeof(category), "DATASTORE-%s", plugin_name);
561 GNUNET_snprintf (cfg_name, sizeof(cfg_name),
562 "perf_plugin_datastore_data_%s.conf", plugin_name);
563 GNUNET_PROGRAM_run ((sizeof(xargv) / sizeof(char *)) - 1, xargv,
564 "perf-plugin-datastore", "nohelp", options, &run, NULL);
565 if (ok != 0)
566 fprintf (stderr, "Missed some testcases: %u\n", ok);
567 GNUNET_DISK_directory_remove (dir_name);
568
569 return ok;
570}
571
572
573/* end of perf_plugin_datastore.c */
diff --git a/src/plugin/datastore/perf_plugin_datastore_data_heap.conf b/src/plugin/datastore/perf_plugin_datastore_data_heap.conf
new file mode 100644
index 000000000..873cf9606
--- /dev/null
+++ b/src/plugin/datastore/perf_plugin_datastore_data_heap.conf
@@ -0,0 +1,7 @@
1@INLINE@ test_defaults.conf
2[PATHS]
3GNUNET_TEST_HOME = $GNUNET_TMP/perf-gnunet-datastore-heap/
4
5
6[datastore]
7DATABASE = heap
diff --git a/src/plugin/datastore/perf_plugin_datastore_data_postgres.conf b/src/plugin/datastore/perf_plugin_datastore_data_postgres.conf
new file mode 100644
index 000000000..7683887a8
--- /dev/null
+++ b/src/plugin/datastore/perf_plugin_datastore_data_postgres.conf
@@ -0,0 +1,10 @@
1@INLINE@ test_defaults.conf
2[PATHS]
3GNUNET_TEST_HOME = $GNUNET_TMP/perf-gnunet-datastore-postgres/
4
5[datastore]
6DATABASE = postgres
7
8[datastore-postgres]
9CONFIG = dbname=gnunetcheck
10
diff --git a/src/plugin/datastore/perf_plugin_datastore_data_sqlite.conf b/src/plugin/datastore/perf_plugin_datastore_data_sqlite.conf
new file mode 100644
index 000000000..888e020a6
--- /dev/null
+++ b/src/plugin/datastore/perf_plugin_datastore_data_sqlite.conf
@@ -0,0 +1,4 @@
1@INLINE@ test_defaults.conf
2[PATHS]
3GNUNET_TEST_HOME = $GNUNET_TMP/perf-gnunet-datastore-sqlite/
4
diff --git a/src/plugin/datastore/plugin_datastore_heap.c b/src/plugin/datastore/plugin_datastore_heap.c
new file mode 100644
index 000000000..a827a2763
--- /dev/null
+++ b/src/plugin/datastore/plugin_datastore_heap.c
@@ -0,0 +1,944 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2012 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file datastore/plugin_datastore_heap.c
23 * @brief heap-based datastore backend; usually we want the datastore
24 * to be persistent, and storing data in the heap is obviously
25 * NOT going to be persistent; still, this plugin is useful for
26 * testing/benchmarking --- but never for production!
27 * @author Christian Grothoff
28 */
29
30#include "platform.h"
31#include "gnunet_datastore_plugin.h"
32
33
34/**
35 * A value that we are storing.
36 */
37struct Value
38{
39 /**
40 * Key for the value.
41 */
42 struct GNUNET_HashCode key;
43
44 /**
45 * Pointer to the value's data (allocated at the end of this struct).
46 */
47 const void *data;
48
49 /**
50 * Entry for this value in the 'expire' heap.
51 */
52 struct GNUNET_CONTAINER_HeapNode *expire_heap;
53
54 /**
55 * Entry for this value in the 'replication' heap.
56 */
57 struct GNUNET_CONTAINER_HeapNode *replication_heap;
58
59 /**
60 * Expiration time for this value.
61 */
62 struct GNUNET_TIME_Absolute expiration;
63
64 /**
65 * Offset of this value in the array of the 'struct ZeroAnonByType';
66 * only used if anonymity is zero.
67 */
68 unsigned int zero_anon_offset;
69
70 /**
71 * Number of bytes in 'data'.
72 */
73 uint32_t size;
74
75 /**
76 * Priority of the value.
77 */
78 uint32_t priority;
79
80 /**
81 * Anonymity level for the value.
82 */
83 uint32_t anonymity;
84
85 /**
86 * Replication level for the value.
87 */
88 uint32_t replication;
89
90 /**
91 * Type of 'data'.
92 */
93 enum GNUNET_BLOCK_Type type;
94};
95
96
97/**
98 * We organize 0-anonymity values in arrays "by type".
99 */
100struct ZeroAnonByType
101{
102 /**
103 * We keep these in a DLL.
104 */
105 struct ZeroAnonByType *next;
106
107 /**
108 * We keep these in a DLL.
109 */
110 struct ZeroAnonByType *prev;
111
112 /**
113 * Array of 0-anonymity items of the given type.
114 */
115 struct Value **array;
116
117 /**
118 * Allocated size of the array.
119 */
120 unsigned int array_size;
121
122 /**
123 * First unused offset in 'array'.
124 */
125 unsigned int array_pos;
126
127 /**
128 * Type of all of the values in 'array'.
129 */
130 enum GNUNET_BLOCK_Type type;
131};
132
133
134/**
135 * Context for all functions in this plugin.
136 */
137struct Plugin
138{
139 /**
140 * Our execution environment.
141 */
142 struct GNUNET_DATASTORE_PluginEnvironment *env;
143
144 /**
145 * Mapping from keys to 'struct Value's.
146 */
147 struct GNUNET_CONTAINER_MultiHashMap *keyvalue;
148
149 /**
150 * Heap organized by minimum expiration time.
151 */
152 struct GNUNET_CONTAINER_Heap *by_expiration;
153
154 /**
155 * Heap organized by maximum replication value.
156 */
157 struct GNUNET_CONTAINER_Heap *by_replication;
158
159 /**
160 * Head of list of arrays containing zero-anonymity values by type.
161 */
162 struct ZeroAnonByType *zero_head;
163
164 /**
165 * Tail of list of arrays containing zero-anonymity values by type.
166 */
167 struct ZeroAnonByType *zero_tail;
168
169 /**
170 * Size of all values we're storing.
171 */
172 unsigned long long size;
173};
174
175
176/**
177 * Get an estimate of how much space the database is
178 * currently using.
179 *
180 * @param cls our "struct Plugin*"
181 * @return number of bytes used on disk
182 */
183static void
184heap_plugin_estimate_size (void *cls, unsigned long long *estimate)
185{
186 struct Plugin *plugin = cls;
187
188 if (NULL != estimate)
189 *estimate = plugin->size;
190}
191
192
193/**
194 * Closure for iterator for updating.
195 */
196struct UpdateContext
197{
198 /**
199 * Number of bytes in 'data'.
200 */
201 uint32_t size;
202
203 /**
204 * Pointer to the data.
205 */
206 const void *data;
207
208 /**
209 * Priority of the value.
210 */
211 uint32_t priority;
212
213 /**
214 * Replication level for the value.
215 */
216 uint32_t replication;
217
218 /**
219 * Expiration time for this value.
220 */
221 struct GNUNET_TIME_Absolute expiration;
222
223 /**
224 * True if the value was found and updated.
225 */
226 bool updated;
227};
228
229
230/**
231 * Update the matching value.
232 *
233 * @param cls the 'struct UpdateContext'
234 * @param key unused
235 * @param val the 'struct Value'
236 * @return GNUNET_YES (continue iteration), GNUNET_NO if value was found
237 */
238static int
239update_iterator (void *cls,
240 const struct GNUNET_HashCode *key,
241 void *val)
242{
243 struct UpdateContext *uc = cls;
244 struct Value *value = val;
245
246 if (value->size != uc->size)
247 return GNUNET_YES;
248 if (0 != memcmp (value->data, uc->data, uc->size))
249 return GNUNET_YES;
250 uc->expiration = GNUNET_TIME_absolute_max (value->expiration,
251 uc->expiration);
252 if (value->expiration.abs_value_us != uc->expiration.abs_value_us)
253 {
254 value->expiration = uc->expiration;
255 GNUNET_CONTAINER_heap_update_cost (value->expire_heap,
256 value->expiration.abs_value_us);
257 }
258 /* Saturating adds, don't overflow */
259 if (value->priority > UINT32_MAX - uc->priority)
260 value->priority = UINT32_MAX;
261 else
262 value->priority += uc->priority;
263 if (value->replication > UINT32_MAX - uc->replication)
264 value->replication = UINT32_MAX;
265 else
266 value->replication += uc->replication;
267 uc->updated = true;
268 return GNUNET_NO;
269}
270
271
272/**
273 * Store an item in the datastore.
274 *
275 * @param cls closure
276 * @param key key for the item
277 * @param absent true if the key was not found in the bloom filter
278 * @param size number of bytes in data
279 * @param data content stored
280 * @param type type of the content
281 * @param priority priority of the content
282 * @param anonymity anonymity-level for the content
283 * @param replication replication-level for the content
284 * @param expiration expiration time for the content
285 * @param cont continuation called with success or failure status
286 * @param cont_cls continuation closure
287 */
288static void
289heap_plugin_put (void *cls,
290 const struct GNUNET_HashCode *key,
291 bool absent,
292 uint32_t size,
293 const void *data,
294 enum GNUNET_BLOCK_Type type,
295 uint32_t priority,
296 uint32_t anonymity,
297 uint32_t replication,
298 struct GNUNET_TIME_Absolute expiration,
299 PluginPutCont cont,
300 void *cont_cls)
301{
302 struct Plugin *plugin = cls;
303 struct Value *value;
304
305 if (! absent)
306 {
307 struct UpdateContext uc;
308
309 uc.size = size;
310 uc.data = data;
311 uc.priority = priority;
312 uc.replication = replication;
313 uc.expiration = expiration;
314 uc.updated = false;
315 GNUNET_CONTAINER_multihashmap_get_multiple (plugin->keyvalue,
316 key,
317 &update_iterator,
318 &uc);
319 if (uc.updated)
320 {
321 cont (cont_cls, key, size, GNUNET_NO, NULL);
322 return;
323 }
324 }
325 value = GNUNET_malloc (sizeof(struct Value) + size);
326 value->key = *key;
327 value->data = &value[1];
328 value->expire_heap = GNUNET_CONTAINER_heap_insert (plugin->by_expiration,
329 value,
330 expiration.abs_value_us);
331 value->replication_heap = GNUNET_CONTAINER_heap_insert (
332 plugin->by_replication,
333 value,
334 replication);
335 value->expiration = expiration;
336 if (0 == anonymity)
337 {
338 struct ZeroAnonByType *zabt;
339
340 for (zabt = plugin->zero_head; NULL != zabt; zabt = zabt->next)
341 if (zabt->type == type)
342 break;
343 if (NULL == zabt)
344 {
345 zabt = GNUNET_new (struct ZeroAnonByType);
346 zabt->type = type;
347 GNUNET_CONTAINER_DLL_insert (plugin->zero_head,
348 plugin->zero_tail,
349 zabt);
350 }
351 if (zabt->array_size == zabt->array_pos)
352 {
353 GNUNET_array_grow (zabt->array,
354 zabt->array_size,
355 zabt->array_size * 2 + 4);
356 }
357 value->zero_anon_offset = zabt->array_pos;
358 zabt->array[zabt->array_pos++] = value;
359 }
360 value->size = size;
361 value->priority = priority;
362 value->anonymity = anonymity;
363 value->replication = replication;
364 value->type = type;
365 GNUNET_memcpy (&value[1], data, size);
366 GNUNET_CONTAINER_multihashmap_put (plugin->keyvalue,
367 &value->key,
368 value,
369 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
370 plugin->size += size;
371 cont (cont_cls, key, size, GNUNET_OK, NULL);
372}
373
374
375/**
376 * Delete the given value, removing it from the plugin's data
377 * structures.
378 *
379 * @param plugin the plugin
380 * @param value value to delete
381 */
382static void
383delete_value (struct Plugin *plugin,
384 struct Value *value)
385{
386 GNUNET_assert (GNUNET_YES ==
387 GNUNET_CONTAINER_multihashmap_remove (plugin->keyvalue,
388 &value->key,
389 value));
390 GNUNET_assert (value == GNUNET_CONTAINER_heap_remove_node (
391 value->expire_heap));
392 GNUNET_assert (value == GNUNET_CONTAINER_heap_remove_node (
393 value->replication_heap));
394 if (0 == value->anonymity)
395 {
396 struct ZeroAnonByType *zabt;
397
398 for (zabt = plugin->zero_head; NULL != zabt; zabt = zabt->next)
399 if (zabt->type == value->type)
400 break;
401 GNUNET_assert (NULL != zabt);
402 zabt->array[value->zero_anon_offset] = zabt->array[--zabt->array_pos];
403 zabt->array[value->zero_anon_offset]->zero_anon_offset =
404 value->zero_anon_offset;
405 if (0 == zabt->array_pos)
406 {
407 GNUNET_array_grow (zabt->array,
408 zabt->array_size,
409 0);
410 GNUNET_CONTAINER_DLL_remove (plugin->zero_head,
411 plugin->zero_tail,
412 zabt);
413 GNUNET_free (zabt);
414 }
415 }
416 plugin->size -= value->size;
417 GNUNET_free (value);
418}
419
420
421/**
422 * Closure for iterator called during 'get_key'.
423 */
424struct GetContext
425{
426 /**
427 * Lowest uid to consider.
428 */
429 uint64_t next_uid;
430
431 /**
432 * Value with lowest uid >= next_uid found so far.
433 */
434 struct Value *value;
435
436 /**
437 * Requested type.
438 */
439 enum GNUNET_BLOCK_Type type;
440
441 /**
442 * If true, return a random value
443 */
444 bool random;
445};
446
447
448/**
449 * Obtain the matching value with the lowest uid >= next_uid.
450 *
451 * @param cls the 'struct GetContext'
452 * @param key unused
453 * @param val the 'struct Value'
454 * @return GNUNET_YES (continue iteration), GNUNET_NO if result was found
455 */
456static int
457get_iterator (void *cls,
458 const struct GNUNET_HashCode *key,
459 void *val)
460{
461 struct GetContext *gc = cls;
462 struct Value *value = val;
463
464 if ((gc->type != GNUNET_BLOCK_TYPE_ANY) &&
465 (gc->type != value->type))
466 return GNUNET_OK;
467 if (gc->random)
468 {
469 gc->value = value;
470 return GNUNET_NO;
471 }
472 if ((uint64_t) (intptr_t) value < gc->next_uid)
473 return GNUNET_OK;
474 if ((NULL != gc->value) &&
475 (value > gc->value))
476 return GNUNET_OK;
477 gc->value = value;
478 return GNUNET_OK;
479}
480
481
482/**
483 * Get one of the results for a particular key in the datastore.
484 *
485 * @param cls closure
486 * @param next_uid return the result with lowest uid >= next_uid
487 * @param random if true, return a random result instead of using next_uid
488 * @param key maybe NULL (to match all entries)
489 * @param type entries of which type are relevant?
490 * Use 0 for any type.
491 * @param proc function to call on the matching value;
492 * will be called with NULL if nothing matches
493 * @param proc_cls closure for @a proc
494 */
495static void
496heap_plugin_get_key (void *cls,
497 uint64_t next_uid,
498 bool random,
499 const struct GNUNET_HashCode *key,
500 enum GNUNET_BLOCK_Type type,
501 PluginDatumProcessor proc,
502 void *proc_cls)
503{
504 struct Plugin *plugin = cls;
505 struct GetContext gc;
506
507 gc.value = NULL;
508 gc.next_uid = next_uid;
509 gc.random = random;
510 gc.type = type;
511 if (NULL == key)
512 {
513 GNUNET_CONTAINER_multihashmap_iterate (plugin->keyvalue,
514 &get_iterator,
515 &gc);
516 }
517 else
518 {
519 GNUNET_CONTAINER_multihashmap_get_multiple (plugin->keyvalue,
520 key,
521 &get_iterator,
522 &gc);
523 }
524 if (NULL == gc.value)
525 {
526 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
527 return;
528 }
529 GNUNET_assert (GNUNET_OK ==
530 proc (proc_cls,
531 &gc.value->key,
532 gc.value->size,
533 &gc.value[1],
534 gc.value->type,
535 gc.value->priority,
536 gc.value->anonymity,
537 gc.value->replication,
538 gc.value->expiration,
539 (uint64_t) (intptr_t) gc.value));
540}
541
542
543/**
544 * Get a random item for replication. Returns a single, not expired,
545 * random item from those with the highest replication counters. The
546 * item's replication counter is decremented by one IF it was positive
547 * before. Call 'proc' with all values ZERO or NULL if the datastore
548 * is empty.
549 *
550 * @param cls closure
551 * @param proc function to call the value (once only).
552 * @param proc_cls closure for proc
553 */
554static void
555heap_plugin_get_replication (void *cls,
556 PluginDatumProcessor proc,
557 void *proc_cls)
558{
559 struct Plugin *plugin = cls;
560 struct Value *value;
561
562 value = GNUNET_CONTAINER_heap_remove_root (plugin->by_replication);
563 if (NULL == value)
564 {
565 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
566 return;
567 }
568 if (value->replication > 0)
569 {
570 value->replication--;
571 value->replication_heap = GNUNET_CONTAINER_heap_insert (
572 plugin->by_replication,
573 value,
574 value->replication);
575 }
576 else
577 {
578 /* need a better way to pick a random item, replication level is always 0 */
579 value->replication_heap = GNUNET_CONTAINER_heap_insert (
580 plugin->by_replication,
581 value,
582 value->replication);
583 value = GNUNET_CONTAINER_heap_walk_get_next (plugin->by_replication);
584 }
585 GNUNET_assert (GNUNET_OK ==
586 proc (proc_cls,
587 &value->key,
588 value->size,
589 &value[1],
590 value->type,
591 value->priority,
592 value->anonymity,
593 value->replication,
594 value->expiration,
595 (uint64_t) (intptr_t) value));
596}
597
598
599/**
600 * Get a random item for expiration. Call 'proc' with all values ZERO
601 * or NULL if the datastore is empty.
602 *
603 * @param cls closure
604 * @param proc function to call the value (once only).
605 * @param proc_cls closure for proc
606 */
607static void
608heap_plugin_get_expiration (void *cls, PluginDatumProcessor proc,
609 void *proc_cls)
610{
611 struct Plugin *plugin = cls;
612 struct Value *value;
613
614 value = GNUNET_CONTAINER_heap_peek (plugin->by_expiration);
615 if (NULL == value)
616 {
617 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
618 return;
619 }
620 if (GNUNET_NO ==
621 proc (proc_cls,
622 &value->key,
623 value->size,
624 &value[1],
625 value->type,
626 value->priority,
627 value->anonymity,
628 value->replication,
629 value->expiration,
630 (uint64_t) (intptr_t) value))
631 delete_value (plugin, value);
632}
633
634
635/**
636 * Call the given processor on an item with zero anonymity.
637 *
638 * @param cls our "struct Plugin*"
639 * @param next_uid return the result with lowest uid >= next_uid
640 * @param type entries of which type should be considered?
641 * Must not be zero (ANY).
642 * @param proc function to call on each matching value;
643 * will be called with NULL if no value matches
644 * @param proc_cls closure for proc
645 */
646static void
647heap_plugin_get_zero_anonymity (void *cls, uint64_t next_uid,
648 enum GNUNET_BLOCK_Type type,
649 PluginDatumProcessor proc, void *proc_cls)
650{
651 struct Plugin *plugin = cls;
652 struct ZeroAnonByType *zabt;
653 struct Value *value = NULL;
654
655 for (zabt = plugin->zero_head; NULL != zabt; zabt = zabt->next)
656 {
657 if ((type != GNUNET_BLOCK_TYPE_ANY) &&
658 (type != zabt->type))
659 continue;
660 for (int i = 0; i < zabt->array_pos; ++i)
661 {
662 if ((uint64_t) (intptr_t) zabt->array[i] < next_uid)
663 continue;
664 if ((NULL != value) &&
665 (zabt->array[i] > value))
666 continue;
667 value = zabt->array[i];
668 }
669 }
670 if (NULL == value)
671 {
672 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
673 return;
674 }
675 GNUNET_assert (GNUNET_OK ==
676 proc (proc_cls,
677 &value->key,
678 value->size,
679 &value[1],
680 value->type,
681 value->priority,
682 value->anonymity,
683 value->replication,
684 value->expiration,
685 (uint64_t) (intptr_t) value));
686}
687
688
689/**
690 * Drop database.
691 */
692static void
693heap_plugin_drop (void *cls)
694{
695 /* nothing needs to be done */
696}
697
698
699/**
700 * Closure for the 'return_value' function.
701 */
702struct GetAllContext
703{
704 /**
705 * Function to call.
706 */
707 PluginKeyProcessor proc;
708
709 /**
710 * Closure for 'proc'.
711 */
712 void *proc_cls;
713};
714
715
716/**
717 * Callback invoked to call callback on each value.
718 *
719 * @param cls the plugin
720 * @param key unused
721 * @param val the value
722 * @return GNUNET_OK (continue to iterate)
723 */
724static int
725return_value (void *cls,
726 const struct GNUNET_HashCode *key,
727 void *val)
728{
729 struct GetAllContext *gac = cls;
730
731 gac->proc (gac->proc_cls,
732 key,
733 1);
734 return GNUNET_OK;
735}
736
737
738/**
739 * Get all of the keys in the datastore.
740 *
741 * @param cls closure
742 * @param proc function to call on each key
743 * @param proc_cls closure for proc
744 */
745static void
746heap_get_keys (void *cls,
747 PluginKeyProcessor proc,
748 void *proc_cls)
749{
750 struct Plugin *plugin = cls;
751 struct GetAllContext gac;
752
753 gac.proc = proc;
754 gac.proc_cls = proc_cls;
755 GNUNET_CONTAINER_multihashmap_iterate (plugin->keyvalue,
756 &return_value,
757 &gac);
758 proc (proc_cls, NULL, 0);
759}
760
761
762/**
763 * Closure for iterator called during 'remove_key'.
764 */
765struct RemoveContext
766{
767 /**
768 * Value found.
769 */
770 struct Value *value;
771
772 /**
773 * Size of data.
774 */
775 uint32_t size;
776
777 /**
778 * Data to remove.
779 */
780 const void *data;
781};
782
783
784/**
785 * Obtain the matching value with the lowest uid >= next_uid.
786 *
787 * @param cls the 'struct GetContext'
788 * @param key unused
789 * @param val the 'struct Value'
790 * @return GNUNET_YES (continue iteration), GNUNET_NO if result was found
791 */
792static int
793remove_iterator (void *cls,
794 const struct GNUNET_HashCode *key,
795 void *val)
796{
797 struct RemoveContext *rc = cls;
798 struct Value *value = val;
799
800 if (value->size != rc->size)
801 return GNUNET_YES;
802 if (0 != memcmp (value->data, rc->data, rc->size))
803 return GNUNET_YES;
804 rc->value = value;
805 return GNUNET_NO;
806}
807
808
809/**
810 * Remove a particular key in the datastore.
811 *
812 * @param cls closure
813 * @param key key for the content
814 * @param size number of bytes in data
815 * @param data content stored
816 * @param cont continuation called with success or failure status
817 * @param cont_cls continuation closure for @a cont
818 */
819static void
820heap_plugin_remove_key (void *cls,
821 const struct GNUNET_HashCode *key,
822 uint32_t size,
823 const void *data,
824 PluginRemoveCont cont,
825 void *cont_cls)
826{
827 struct Plugin *plugin = cls;
828 struct RemoveContext rc;
829
830 rc.value = NULL;
831 rc.size = size;
832 rc.data = data;
833 GNUNET_CONTAINER_multihashmap_get_multiple (plugin->keyvalue,
834 key,
835 &remove_iterator,
836 &rc);
837 if (NULL == rc.value)
838 {
839 cont (cont_cls,
840 key,
841 size,
842 GNUNET_NO,
843 NULL);
844 return;
845 }
846 delete_value (plugin,
847 rc.value);
848 cont (cont_cls,
849 key,
850 size,
851 GNUNET_OK,
852 NULL);
853}
854
855
856/**
857 * Entry point for the plugin.
858 *
859 * @param cls the "struct GNUNET_DATASTORE_PluginEnvironment*"
860 * @return our "struct Plugin*"
861 */
862void *
863libgnunet_plugin_datastore_heap_init (void *cls)
864{
865 struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
866 struct GNUNET_DATASTORE_PluginFunctions *api;
867 struct Plugin *plugin;
868 unsigned long long esize;
869
870 if (GNUNET_OK !=
871 GNUNET_CONFIGURATION_get_value_number (env->cfg,
872 "datastore-heap",
873 "HASHMAPSIZE",
874 &esize))
875 esize = 128 * 1024;
876 plugin = GNUNET_new (struct Plugin);
877 plugin->env = env;
878 plugin->keyvalue = GNUNET_CONTAINER_multihashmap_create (esize, GNUNET_YES);
879 plugin->by_expiration = GNUNET_CONTAINER_heap_create (
880 GNUNET_CONTAINER_HEAP_ORDER_MIN);
881 plugin->by_replication = GNUNET_CONTAINER_heap_create (
882 GNUNET_CONTAINER_HEAP_ORDER_MAX);
883 api = GNUNET_new (struct GNUNET_DATASTORE_PluginFunctions);
884 api->cls = plugin;
885 api->estimate_size = &heap_plugin_estimate_size;
886 api->put = &heap_plugin_put;
887 api->get_key = &heap_plugin_get_key;
888 api->get_replication = &heap_plugin_get_replication;
889 api->get_expiration = &heap_plugin_get_expiration;
890 api->get_zero_anonymity = &heap_plugin_get_zero_anonymity;
891 api->drop = &heap_plugin_drop;
892 api->get_keys = &heap_get_keys;
893 api->remove_key = &heap_plugin_remove_key;
894 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "heap",
895 _ ("Heap database running\n"));
896 return api;
897}
898
899
900/**
901 * Callback invoked to free all value.
902 *
903 * @param cls the plugin
904 * @param key unused
905 * @param val the value
906 * @return GNUNET_OK (continue to iterate)
907 */
908static int
909free_value (void *cls,
910 const struct GNUNET_HashCode *key,
911 void *val)
912{
913 struct Plugin *plugin = cls;
914 struct Value *value = val;
915
916 delete_value (plugin, value);
917 return GNUNET_OK;
918}
919
920
921/**
922 * Exit point from the plugin.
923 * @param cls our "struct Plugin*"
924 * @return always NULL
925 */
926void *
927libgnunet_plugin_datastore_heap_done (void *cls)
928{
929 struct GNUNET_DATASTORE_PluginFunctions *api = cls;
930 struct Plugin *plugin = api->cls;
931
932 GNUNET_CONTAINER_multihashmap_iterate (plugin->keyvalue,
933 &free_value,
934 plugin);
935 GNUNET_CONTAINER_multihashmap_destroy (plugin->keyvalue);
936 GNUNET_CONTAINER_heap_destroy (plugin->by_expiration);
937 GNUNET_CONTAINER_heap_destroy (plugin->by_replication);
938 GNUNET_free (plugin);
939 GNUNET_free (api);
940 return NULL;
941}
942
943
944/* end of plugin_datastore_heap.c */
diff --git a/src/plugin/datastore/plugin_datastore_postgres.c b/src/plugin/datastore/plugin_datastore_postgres.c
new file mode 100644
index 000000000..5fcacc17b
--- /dev/null
+++ b/src/plugin/datastore/plugin_datastore_postgres.c
@@ -0,0 +1,930 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2009-2017, 2022 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file datastore/plugin_datastore_postgres.c
23 * @brief postgres-based datastore backend
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_datastore_plugin.h"
28#include "gnunet_pq_lib.h"
29
30
31/**
32 * After how many ms "busy" should a DB operation fail for good?
33 * A low value makes sure that we are more responsive to requests
34 * (especially PUTs). A high value guarantees a higher success
35 * rate (SELECTs in iterate can take several seconds despite LIMIT=1).
36 *
37 * The default value of 1s should ensure that users do not experience
38 * huge latencies while at the same time allowing operations to succeed
39 * with reasonable probability.
40 */
41#define BUSY_TIMEOUT GNUNET_TIME_UNIT_SECONDS
42
43
44/**
45 * Context for all functions in this plugin.
46 */
47struct Plugin
48{
49 /**
50 * Our execution environment.
51 */
52 struct GNUNET_DATASTORE_PluginEnvironment *env;
53
54 /**
55 * Native Postgres database handle.
56 */
57 struct GNUNET_PQ_Context *dbh;
58};
59
60
61/**
62 * @brief Get a database handle
63 *
64 * @param plugin global context
65 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
66 */
67static enum GNUNET_GenericReturnValue
68init_connection (struct Plugin *plugin)
69{
70#define RESULT_COLUMNS "repl, type, prio, anonLevel, expire, hash, value, oid"
71 struct GNUNET_PQ_PreparedStatement ps[] = {
72 GNUNET_PQ_make_prepare ("get",
73 "SELECT " RESULT_COLUMNS
74 " FROM datastore.gn090"
75 " WHERE oid >= $1::bigint AND"
76 " (rvalue >= $2 OR 0 = $3::smallint) AND"
77 " (hash = $4 OR 0 = $5::smallint) AND"
78 " (type = $6 OR 0 = $7::smallint)"
79 " ORDER BY oid ASC LIMIT 1"),
80 GNUNET_PQ_make_prepare ("put",
81 "INSERT INTO datastore.gn090"
82 " (repl, type, prio, anonLevel, expire, rvalue, hash, vhash, value) "
83 "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"),
84 GNUNET_PQ_make_prepare ("update",
85 "UPDATE datastore.gn090"
86 " SET prio = prio + $1,"
87 " repl = repl + $2,"
88 " expire = GREATEST(expire, $3)"
89 " WHERE hash = $4 AND vhash = $5"),
90 GNUNET_PQ_make_prepare ("decrepl",
91 "UPDATE datastore.gn090"
92 " SET repl = GREATEST (repl - 1, 0)"
93 " WHERE oid = $1"),
94 GNUNET_PQ_make_prepare ("select_non_anonymous",
95 "SELECT " RESULT_COLUMNS
96 " FROM datastore.gn090"
97 " WHERE anonLevel = 0 AND type = $1 AND oid >= $2::bigint"
98 " ORDER BY oid ASC LIMIT 1"),
99 GNUNET_PQ_make_prepare ("select_expiration_order",
100 "(SELECT " RESULT_COLUMNS
101 " FROM datastore.gn090"
102 " WHERE expire < $1 ORDER BY prio ASC LIMIT 1) "
103 "UNION "
104 "(SELECT " RESULT_COLUMNS
105 " FROM datastore.gn090"
106 " ORDER BY prio ASC LIMIT 1)"
107 " ORDER BY expire ASC LIMIT 1"),
108 GNUNET_PQ_make_prepare ("select_replication_order",
109 "SELECT " RESULT_COLUMNS
110 " FROM datastore.gn090"
111 " ORDER BY repl DESC,RANDOM() LIMIT 1"),
112 GNUNET_PQ_make_prepare ("delrow",
113 "DELETE FROM datastore.gn090"
114 " WHERE oid=$1"),
115 GNUNET_PQ_make_prepare ("remove",
116 "DELETE FROM datastore.gn090"
117 " WHERE hash = $1 AND"
118 " value = $2"),
119 GNUNET_PQ_make_prepare ("get_keys",
120 "SELECT hash"
121 " FROM datastore.gn090"),
122 GNUNET_PQ_make_prepare ("estimate_size",
123 "SELECT CASE WHEN NOT EXISTS"
124 " (SELECT 1 FROM datastore.gn090)"
125 " THEN 0"
126 " ELSE (SELECT SUM(LENGTH(value))+256*COUNT(*)"
127 " FROM datastore.gn090)"
128 "END AS total"),
129 GNUNET_PQ_PREPARED_STATEMENT_END
130 };
131#undef RESULT_COLUMNS
132
133 plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->env->cfg,
134 "datastore-postgres",
135 "datastore-",
136 NULL,
137 ps);
138 if (NULL == plugin->dbh)
139 return GNUNET_SYSERR;
140 return GNUNET_OK;
141}
142
143
144/**
145 * Get an estimate of how much space the database is
146 * currently using.
147 *
148 * @param cls our `struct Plugin *`
149 * @return number of bytes used on disk
150 */
151static void
152postgres_plugin_estimate_size (void *cls,
153 unsigned long long *estimate)
154{
155 struct Plugin *plugin = cls;
156 uint64_t total;
157 struct GNUNET_PQ_QueryParam params[] = {
158 GNUNET_PQ_query_param_end
159 };
160 struct GNUNET_PQ_ResultSpec rs[] = {
161 GNUNET_PQ_result_spec_uint64 ("total",
162 &total),
163 GNUNET_PQ_result_spec_end
164 };
165 enum GNUNET_DB_QueryStatus ret;
166
167 if (NULL == estimate)
168 return;
169 ret = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
170 "estimate_size",
171 params,
172 rs);
173 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ret)
174 {
175 *estimate = 0LL;
176 return;
177 }
178 *estimate = total;
179}
180
181
182/**
183 * Store an item in the datastore.
184 *
185 * @param cls closure with the `struct Plugin`
186 * @param key key for the item
187 * @param absent true if the key was not found in the bloom filter
188 * @param size number of bytes in data
189 * @param data content stored
190 * @param type type of the content
191 * @param priority priority of the content
192 * @param anonymity anonymity-level for the content
193 * @param replication replication-level for the content
194 * @param expiration expiration time for the content
195 * @param cont continuation called with success or failure status
196 * @param cont_cls continuation closure
197 */
198static void
199postgres_plugin_put (void *cls,
200 const struct GNUNET_HashCode *key,
201 bool absent,
202 uint32_t size,
203 const void *data,
204 enum GNUNET_BLOCK_Type type,
205 uint32_t priority,
206 uint32_t anonymity,
207 uint32_t replication,
208 struct GNUNET_TIME_Absolute expiration,
209 PluginPutCont cont,
210 void *cont_cls)
211{
212 struct Plugin *plugin = cls;
213 struct GNUNET_HashCode vhash;
214 enum GNUNET_DB_QueryStatus ret;
215
216 GNUNET_CRYPTO_hash (data,
217 size,
218 &vhash);
219 if (! absent)
220 {
221 struct GNUNET_PQ_QueryParam params[] = {
222 GNUNET_PQ_query_param_uint32 (&priority),
223 GNUNET_PQ_query_param_uint32 (&replication),
224 GNUNET_PQ_query_param_absolute_time (&expiration),
225 GNUNET_PQ_query_param_auto_from_type (key),
226 GNUNET_PQ_query_param_auto_from_type (&vhash),
227 GNUNET_PQ_query_param_end
228 };
229 ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
230 "update",
231 params);
232 if (0 > ret)
233 {
234 cont (cont_cls,
235 key,
236 size,
237 GNUNET_SYSERR,
238 _ ("Postgresql exec failure"));
239 return;
240 }
241 bool affected = (0 != ret);
242 if (affected)
243 {
244 cont (cont_cls,
245 key,
246 size,
247 GNUNET_NO,
248 NULL);
249 return;
250 }
251 }
252
253 {
254 uint32_t utype = (uint32_t) type;
255 uint64_t rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
256 UINT64_MAX);
257 struct GNUNET_PQ_QueryParam params[] = {
258 GNUNET_PQ_query_param_uint32 (&replication),
259 GNUNET_PQ_query_param_uint32 (&utype),
260 GNUNET_PQ_query_param_uint32 (&priority),
261 GNUNET_PQ_query_param_uint32 (&anonymity),
262 GNUNET_PQ_query_param_absolute_time (&expiration),
263 GNUNET_PQ_query_param_uint64 (&rvalue),
264 GNUNET_PQ_query_param_auto_from_type (key),
265 GNUNET_PQ_query_param_auto_from_type (&vhash),
266 GNUNET_PQ_query_param_fixed_size (data, size),
267 GNUNET_PQ_query_param_end
268 };
269
270 ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
271 "put",
272 params);
273 if (0 > ret)
274 {
275 cont (cont_cls,
276 key,
277 size,
278 GNUNET_SYSERR,
279 "Postgresql exec failure");
280 return;
281 }
282 }
283 plugin->env->duc (plugin->env->cls,
284 size + GNUNET_DATASTORE_ENTRY_OVERHEAD);
285 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
286 "datastore-postgres",
287 "Stored %u bytes in database\n",
288 (unsigned int) size);
289 cont (cont_cls,
290 key,
291 size,
292 GNUNET_OK,
293 NULL);
294}
295
296
297/**
298 * Closure for #process_result.
299 */
300struct ProcessResultContext
301{
302 /**
303 * The plugin handle.
304 */
305 struct Plugin *plugin;
306
307 /**
308 * Function to call on each result.
309 */
310 PluginDatumProcessor proc;
311
312 /**
313 * Closure for @e proc.
314 */
315 void *proc_cls;
316};
317
318
319/**
320 * Function invoked to process the result and call the processor of @a
321 * cls.
322 *
323 * @param cls our `struct ProcessResultContext`
324 * @param res result from exec
325 * @param num_results number of results in @a res
326 */
327static void
328process_result (void *cls,
329 PGresult *res,
330 unsigned int num_results)
331{
332 struct ProcessResultContext *prc = cls;
333 struct Plugin *plugin = prc->plugin;
334
335 if (0 == num_results)
336 {
337 /* no result */
338 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
339 "datastore-postgres",
340 "Ending iteration (no more results)\n");
341 prc->proc (prc->proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
342 GNUNET_TIME_UNIT_ZERO_ABS, 0);
343 return;
344 }
345 if (1 != num_results)
346 {
347 GNUNET_break (0);
348 prc->proc (prc->proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
349 GNUNET_TIME_UNIT_ZERO_ABS, 0);
350 return;
351 }
352 /* Technically we don't need the loop here, but nicer in case
353 we ever relax the condition above. */
354 for (unsigned int i = 0; i < num_results; i++)
355 {
356 int iret;
357 uint64_t rowid;
358 uint32_t utype;
359 uint32_t anonymity;
360 uint32_t replication;
361 uint32_t priority;
362 size_t size;
363 void *data;
364 struct GNUNET_TIME_Absolute expiration_time;
365 struct GNUNET_HashCode key;
366 struct GNUNET_PQ_ResultSpec rs[] = {
367 GNUNET_PQ_result_spec_uint32 ("repl", &replication),
368 GNUNET_PQ_result_spec_uint32 ("type", &utype),
369 GNUNET_PQ_result_spec_uint32 ("prio", &priority),
370 GNUNET_PQ_result_spec_uint32 ("anonLevel", &anonymity),
371 GNUNET_PQ_result_spec_absolute_time ("expire", &expiration_time),
372 GNUNET_PQ_result_spec_auto_from_type ("hash", &key),
373 GNUNET_PQ_result_spec_variable_size ("value", &data, &size),
374 GNUNET_PQ_result_spec_uint64 ("oid", &rowid),
375 GNUNET_PQ_result_spec_end
376 };
377
378 if (GNUNET_OK !=
379 GNUNET_PQ_extract_result (res,
380 rs,
381 i))
382 {
383 GNUNET_break (0);
384 prc->proc (prc->proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
385 GNUNET_TIME_UNIT_ZERO_ABS, 0);
386 return;
387 }
388
389 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
390 "datastore-postgres",
391 "Found result of size %u bytes and type %u in database\n",
392 (unsigned int) size,
393 (unsigned int) utype);
394 iret = prc->proc (prc->proc_cls,
395 &key,
396 size,
397 data,
398 (enum GNUNET_BLOCK_Type) utype,
399 priority,
400 anonymity,
401 replication,
402 expiration_time,
403 rowid);
404 if (iret == GNUNET_NO)
405 {
406 struct GNUNET_PQ_QueryParam param[] = {
407 GNUNET_PQ_query_param_uint64 (&rowid),
408 GNUNET_PQ_query_param_end
409 };
410
411 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
412 "Processor asked for item %u to be removed.\n",
413 (unsigned int) rowid);
414 if (0 <
415 GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
416 "delrow",
417 param))
418 {
419 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
420 "datastore-postgres",
421 "Deleting %u bytes from database\n",
422 (unsigned int) size);
423 plugin->env->duc (plugin->env->cls,
424 -(size + GNUNET_DATASTORE_ENTRY_OVERHEAD));
425 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
426 "datastore-postgres",
427 "Deleted %u bytes from database\n",
428 (unsigned int) size);
429 }
430 }
431 GNUNET_PQ_cleanup_result (rs);
432 } /* for (i) */
433}
434
435
436/**
437 * Get one of the results for a particular key in the datastore.
438 *
439 * @param cls closure with the `struct Plugin`
440 * @param next_uid return the result with lowest uid >= next_uid
441 * @param random if true, return a random result instead of using next_uid
442 * @param key maybe NULL (to match all entries)
443 * @param type entries of which type are relevant?
444 * Use 0 for any type.
445 * @param proc function to call on the matching value;
446 * will be called with NULL if nothing matches
447 * @param proc_cls closure for @a proc
448 */
449static void
450postgres_plugin_get_key (void *cls,
451 uint64_t next_uid,
452 bool random,
453 const struct GNUNET_HashCode *key,
454 enum GNUNET_BLOCK_Type type,
455 PluginDatumProcessor proc,
456 void *proc_cls)
457{
458 struct Plugin *plugin = cls;
459 uint32_t utype = type;
460 uint16_t use_rvalue = random;
461 uint16_t use_key = NULL != key;
462 uint16_t use_type = GNUNET_BLOCK_TYPE_ANY != type;
463 uint64_t rvalue;
464 struct GNUNET_PQ_QueryParam params[] = {
465 GNUNET_PQ_query_param_uint64 (&next_uid),
466 GNUNET_PQ_query_param_uint64 (&rvalue),
467 GNUNET_PQ_query_param_uint16 (&use_rvalue),
468 GNUNET_PQ_query_param_auto_from_type (key),
469 GNUNET_PQ_query_param_uint16 (&use_key),
470 GNUNET_PQ_query_param_uint32 (&utype),
471 GNUNET_PQ_query_param_uint16 (&use_type),
472 GNUNET_PQ_query_param_end
473 };
474 struct ProcessResultContext prc;
475 enum GNUNET_DB_QueryStatus res;
476
477 if (random)
478 {
479 rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
480 UINT64_MAX);
481 next_uid = 0;
482 }
483 else
484 {
485 rvalue = 0;
486 }
487 prc.plugin = plugin;
488 prc.proc = proc;
489 prc.proc_cls = proc_cls;
490
491 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
492 "get",
493 params,
494 &process_result,
495 &prc);
496 if (0 > res)
497 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
498 GNUNET_TIME_UNIT_ZERO_ABS, 0);
499}
500
501
502/**
503 * Select a subset of the items in the datastore and call
504 * the given iterator for each of them.
505 *
506 * @param cls our `struct Plugin *`
507 * @param next_uid return the result with lowest uid >= next_uid
508 * @param type entries of which type should be considered?
509 * Must not be zero (ANY).
510 * @param proc function to call on the matching value;
511 * will be called with NULL if no value matches
512 * @param proc_cls closure for @a proc
513 */
514static void
515postgres_plugin_get_zero_anonymity (void *cls,
516 uint64_t next_uid,
517 enum GNUNET_BLOCK_Type type,
518 PluginDatumProcessor proc,
519 void *proc_cls)
520{
521 struct Plugin *plugin = cls;
522 uint32_t utype = type;
523 struct GNUNET_PQ_QueryParam params[] = {
524 GNUNET_PQ_query_param_uint32 (&utype),
525 GNUNET_PQ_query_param_uint64 (&next_uid),
526 GNUNET_PQ_query_param_end
527 };
528 struct ProcessResultContext prc;
529 enum GNUNET_DB_QueryStatus res;
530
531 prc.plugin = plugin;
532 prc.proc = proc;
533 prc.proc_cls = proc_cls;
534 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
535 "select_non_anonymous",
536 params,
537 &process_result,
538 &prc);
539 if (0 > res)
540 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
541 GNUNET_TIME_UNIT_ZERO_ABS, 0);
542}
543
544
545/**
546 * Context for #repl_iter() function.
547 */
548struct ReplCtx
549{
550 /**
551 * Plugin handle.
552 */
553 struct Plugin *plugin;
554
555 /**
556 * Function to call for the result (or the NULL).
557 */
558 PluginDatumProcessor proc;
559
560 /**
561 * Closure for @e proc.
562 */
563 void *proc_cls;
564};
565
566
567/**
568 * Wrapper for the iterator for 'sqlite_plugin_replication_get'.
569 * Decrements the replication counter and calls the original
570 * iterator.
571 *
572 * @param cls closure with the `struct ReplCtx *`
573 * @param key key for the content
574 * @param size number of bytes in @a data
575 * @param data content stored
576 * @param type type of the content
577 * @param priority priority of the content
578 * @param anonymity anonymity-level for the content
579 * @param replication replication-level for the content
580 * @param expiration expiration time for the content
581 * @param uid unique identifier for the datum;
582 * maybe 0 if no unique identifier is available
583 * @return #GNUNET_SYSERR to abort the iteration,
584 * #GNUNET_OK to continue
585 * (continue on call to "next", of course),
586 * #GNUNET_NO to delete the item and continue (if supported)
587 */
588static int
589repl_proc (void *cls,
590 const struct GNUNET_HashCode *key,
591 uint32_t size,
592 const void *data,
593 enum GNUNET_BLOCK_Type type,
594 uint32_t priority,
595 uint32_t anonymity,
596 uint32_t replication,
597 struct GNUNET_TIME_Absolute expiration,
598 uint64_t uid)
599{
600 struct ReplCtx *rc = cls;
601 struct Plugin *plugin = rc->plugin;
602 int ret;
603 struct GNUNET_PQ_QueryParam params[] = {
604 GNUNET_PQ_query_param_uint64 (&uid),
605 GNUNET_PQ_query_param_end
606 };
607 enum GNUNET_DB_QueryStatus qret;
608
609 ret = rc->proc (rc->proc_cls,
610 key,
611 size,
612 data,
613 type,
614 priority,
615 anonymity,
616 replication,
617 expiration,
618 uid);
619 if (NULL == key)
620 return ret;
621 qret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
622 "decrepl",
623 params);
624 if (0 > qret)
625 return GNUNET_SYSERR;
626 return ret;
627}
628
629
630/**
631 * Get a random item for replication. Returns a single, not expired,
632 * random item from those with the highest replication counters. The
633 * item's replication counter is decremented by one IF it was positive
634 * before. Call @a proc with all values ZERO or NULL if the datastore
635 * is empty.
636 *
637 * @param cls closure with the `struct Plugin`
638 * @param proc function to call the value (once only).
639 * @param proc_cls closure for @a proc
640 */
641static void
642postgres_plugin_get_replication (void *cls,
643 PluginDatumProcessor proc,
644 void *proc_cls)
645{
646 struct Plugin *plugin = cls;
647 struct GNUNET_PQ_QueryParam params[] = {
648 GNUNET_PQ_query_param_end
649 };
650 struct ReplCtx rc;
651 struct ProcessResultContext prc;
652 enum GNUNET_DB_QueryStatus res;
653
654 rc.plugin = plugin;
655 rc.proc = proc;
656 rc.proc_cls = proc_cls;
657 prc.plugin = plugin;
658 prc.proc = &repl_proc;
659 prc.proc_cls = &rc;
660 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
661 "select_replication_order",
662 params,
663 &process_result,
664 &prc);
665 if (0 > res)
666 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0,
667 GNUNET_TIME_UNIT_ZERO_ABS, 0);
668}
669
670
671/**
672 * Get a random item for expiration. Call @a proc with all values
673 * ZERO or NULL if the datastore is empty.
674 *
675 * @param cls closure with the `struct Plugin`
676 * @param proc function to call the value (once only).
677 * @param proc_cls closure for @a proc
678 */
679static void
680postgres_plugin_get_expiration (void *cls,
681 PluginDatumProcessor proc,
682 void *proc_cls)
683{
684 struct Plugin *plugin = cls;
685 struct GNUNET_TIME_Absolute now = { 0 };
686 struct GNUNET_PQ_QueryParam params[] = {
687 GNUNET_PQ_query_param_absolute_time (&now),
688 GNUNET_PQ_query_param_end
689 };
690 struct ProcessResultContext prc;
691
692 now = GNUNET_TIME_absolute_get ();
693 prc.plugin = plugin;
694 prc.proc = proc;
695 prc.proc_cls = proc_cls;
696 (void) GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
697 "select_expiration_order",
698 params,
699 &process_result,
700 &prc);
701}
702
703
704/**
705 * Closure for #process_keys.
706 */
707struct ProcessKeysContext
708{
709 /**
710 * Function to call for each key.
711 */
712 PluginKeyProcessor proc;
713
714 /**
715 * Closure for @e proc.
716 */
717 void *proc_cls;
718};
719
720
721/**
722 * Function to be called with the results of a SELECT statement
723 * that has returned @a num_results results.
724 *
725 * @param cls closure with a `struct ProcessKeysContext`
726 * @param result the postgres result
727 * @param num_results the number of results in @a result
728 */
729static void
730process_keys (void *cls,
731 PGresult *result,
732 unsigned int num_results)
733{
734 struct ProcessKeysContext *pkc = cls;
735
736 for (unsigned i = 0; i < num_results; i++)
737 {
738 struct GNUNET_HashCode key;
739 struct GNUNET_PQ_ResultSpec rs[] = {
740 GNUNET_PQ_result_spec_auto_from_type ("hash",
741 &key),
742 GNUNET_PQ_result_spec_end
743 };
744
745 if (GNUNET_OK !=
746 GNUNET_PQ_extract_result (result,
747 rs,
748 i))
749 {
750 GNUNET_break (0);
751 continue;
752 }
753 pkc->proc (pkc->proc_cls,
754 &key,
755 1);
756 GNUNET_PQ_cleanup_result (rs);
757 }
758}
759
760
761/**
762 * Get all of the keys in the datastore.
763 *
764 * @param cls closure with the `struct Plugin *`
765 * @param proc function to call on each key
766 * @param proc_cls closure for @a proc
767 */
768static void
769postgres_plugin_get_keys (void *cls,
770 PluginKeyProcessor proc,
771 void *proc_cls)
772{
773 struct Plugin *plugin = cls;
774 struct GNUNET_PQ_QueryParam params[] = {
775 GNUNET_PQ_query_param_end
776 };
777 struct ProcessKeysContext pkc;
778
779 pkc.proc = proc;
780 pkc.proc_cls = proc_cls;
781 (void) GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
782 "get_keys",
783 params,
784 &process_keys,
785 &pkc);
786 proc (proc_cls,
787 NULL,
788 0);
789}
790
791
792/**
793 * Drop database.
794 *
795 * @param cls closure with the `struct Plugin *`
796 */
797static void
798postgres_plugin_drop (void *cls)
799{
800 struct Plugin *plugin = cls;
801 struct GNUNET_PQ_ExecuteStatement es[] = {
802 GNUNET_PQ_make_execute ("DROP TABLE gn090"),
803 GNUNET_PQ_EXECUTE_STATEMENT_END
804 };
805
806 if (GNUNET_OK !=
807 GNUNET_PQ_exec_statements (plugin->dbh,
808 es))
809 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
810 "postgres",
811 _ ("Failed to drop table from database.\n"));
812}
813
814
815/**
816 * Remove a particular key in the datastore.
817 *
818 * @param cls closure
819 * @param key key for the content
820 * @param size number of bytes in data
821 * @param data content stored
822 * @param cont continuation called with success or failure status
823 * @param cont_cls continuation closure for @a cont
824 */
825static void
826postgres_plugin_remove_key (void *cls,
827 const struct GNUNET_HashCode *key,
828 uint32_t size,
829 const void *data,
830 PluginRemoveCont cont,
831 void *cont_cls)
832{
833 struct Plugin *plugin = cls;
834 enum GNUNET_DB_QueryStatus ret;
835 struct GNUNET_PQ_QueryParam params[] = {
836 GNUNET_PQ_query_param_auto_from_type (key),
837 GNUNET_PQ_query_param_fixed_size (data, size),
838 GNUNET_PQ_query_param_end
839 };
840
841 ret = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
842 "remove",
843 params);
844 if (0 > ret)
845 {
846 cont (cont_cls,
847 key,
848 size,
849 GNUNET_SYSERR,
850 _ ("Postgresql exec failure"));
851 return;
852 }
853 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == ret)
854 {
855 cont (cont_cls,
856 key,
857 size,
858 GNUNET_NO,
859 NULL);
860 return;
861 }
862 plugin->env->duc (plugin->env->cls,
863 -(size + GNUNET_DATASTORE_ENTRY_OVERHEAD));
864 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
865 "datastore-postgres",
866 "Deleted %u bytes from database\n",
867 (unsigned int) size);
868 cont (cont_cls,
869 key,
870 size,
871 GNUNET_OK,
872 NULL);
873}
874
875
876/**
877 * Entry point for the plugin.
878 *
879 * @param cls the `struct GNUNET_DATASTORE_PluginEnvironment*`
880 * @return our `struct Plugin *`
881 */
882void *
883libgnunet_plugin_datastore_postgres_init (void *cls)
884{
885 struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
886 struct GNUNET_DATASTORE_PluginFunctions *api;
887 struct Plugin *plugin;
888
889 plugin = GNUNET_new (struct Plugin);
890 plugin->env = env;
891 if (GNUNET_OK != init_connection (plugin))
892 {
893 GNUNET_free (plugin);
894 return NULL;
895 }
896 api = GNUNET_new (struct GNUNET_DATASTORE_PluginFunctions);
897 api->cls = plugin;
898 api->estimate_size = &postgres_plugin_estimate_size;
899 api->put = &postgres_plugin_put;
900 api->get_key = &postgres_plugin_get_key;
901 api->get_replication = &postgres_plugin_get_replication;
902 api->get_expiration = &postgres_plugin_get_expiration;
903 api->get_zero_anonymity = &postgres_plugin_get_zero_anonymity;
904 api->get_keys = &postgres_plugin_get_keys;
905 api->drop = &postgres_plugin_drop;
906 api->remove_key = &postgres_plugin_remove_key;
907 return api;
908}
909
910
911/**
912 * Exit point from the plugin.
913 *
914 * @param cls our `struct Plugin *`
915 * @return always NULL
916 */
917void *
918libgnunet_plugin_datastore_postgres_done (void *cls)
919{
920 struct GNUNET_DATASTORE_PluginFunctions *api = cls;
921 struct Plugin *plugin = api->cls;
922
923 GNUNET_PQ_disconnect (plugin->dbh);
924 GNUNET_free (plugin);
925 GNUNET_free (api);
926 return NULL;
927}
928
929
930/* end of plugin_datastore_postgres.c */
diff --git a/src/plugin/datastore/plugin_datastore_sqlite.c b/src/plugin/datastore/plugin_datastore_sqlite.c
new file mode 100644
index 000000000..5ea9da4cb
--- /dev/null
+++ b/src/plugin/datastore/plugin_datastore_sqlite.c
@@ -0,0 +1,1375 @@
1/*
2 * This file is part of GNUnet
3 * Copyright (C) 2009, 2011, 2017 GNUnet e.V.
4 *
5 * GNUnet is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License,
8 * or (at your option) any later version.
9 *
10 * GNUnet is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file datastore/plugin_datastore_sqlite.c
23 * @brief sqlite-based datastore backend
24 * @author Christian Grothoff
25 */
26
27#include "platform.h"
28#include "gnunet_datastore_plugin.h"
29#include "gnunet_sq_lib.h"
30#include <sqlite3.h>
31
32
33/**
34 * We allocate items on the stack at times. To prevent a stack
35 * overflow, we impose a limit on the maximum size for the data per
36 * item. 64k should be enough.
37 */
38#define MAX_ITEM_SIZE 65536
39
40/**
41 * After how many ms "busy" should a DB operation fail for good?
42 * A low value makes sure that we are more responsive to requests
43 * (especially PUTs). A high value guarantees a higher success
44 * rate (SELECTs in iterate can take several seconds despite LIMIT=1).
45 *
46 * The default value of 250ms should ensure that users do not experience
47 * huge latencies while at the same time allowing operations to succeed
48 * with reasonable probability.
49 */
50#define BUSY_TIMEOUT_MS 250
51
52
53/**
54 * Log an error message at log-level 'level' that indicates
55 * a failure of the command 'cmd' on file 'filename'
56 * with the message given by strerror(errno).
57 */
58#define LOG_SQLITE(db, level, cmd) \
59 do \
60 { \
61 GNUNET_log_from (level, \
62 "sqlite", \
63 _ ("`%s' failed at %s:%d with error: %s\n"), \
64 cmd, \
65 __FILE__, \
66 __LINE__, \
67 sqlite3_errmsg (db->dbh)); \
68 } while (0)
69
70
71/**
72 * Log an error message at log-level 'level' that indicates
73 * a failure of the command 'cmd' on file 'filename'
74 * with the message given by strerror(errno).
75 */
76#define LOG_SQLITE_MSG(db, msg, level, cmd) \
77 do \
78 { \
79 GNUNET_log_from (level, \
80 "sqlite", \
81 _ ("`%s' failed at %s:%d with error: %s\n"), \
82 cmd, \
83 __FILE__, \
84 __LINE__, \
85 sqlite3_errmsg (db->dbh)); \
86 GNUNET_asprintf (msg, \
87 _ ("`%s' failed at %s:%u with error: %s"), \
88 cmd, \
89 __FILE__, \
90 __LINE__, \
91 sqlite3_errmsg (db->dbh)); \
92 } while (0)
93
94
95/**
96 * Context for all functions in this plugin.
97 */
98struct Plugin
99{
100 /**
101 * Our execution environment.
102 */
103 struct GNUNET_DATASTORE_PluginEnvironment *env;
104
105 /**
106 * Database filename.
107 */
108 char *fn;
109
110 /**
111 * Native SQLite database handle.
112 */
113 sqlite3 *dbh;
114
115 /**
116 * Precompiled SQL for remove_key.
117 */
118 sqlite3_stmt *remove;
119
120 /**
121 * Precompiled SQL for deletion.
122 */
123 sqlite3_stmt *delRow;
124
125 /**
126 * Precompiled SQL for update.
127 */
128 sqlite3_stmt *update;
129
130 /**
131 * Get maximum repl value in database.
132 */
133 sqlite3_stmt *maxRepl;
134
135 /**
136 * Precompiled SQL for replication decrement.
137 */
138 sqlite3_stmt *updRepl;
139
140 /**
141 * Precompiled SQL for replication selection.
142 */
143 sqlite3_stmt *selRepl;
144
145 /**
146 * Precompiled SQL for expiration selection.
147 */
148 sqlite3_stmt *selExpi;
149
150 /**
151 * Precompiled SQL for expiration selection.
152 */
153 sqlite3_stmt *selZeroAnon;
154
155 /**
156 * Precompiled SQL for insertion.
157 */
158 sqlite3_stmt *insertContent;
159
160 /**
161 * Precompiled SQL for selection
162 */
163 sqlite3_stmt *get[8];
164
165 /**
166 * Should the database be dropped on shutdown?
167 */
168 int drop_on_shutdown;
169};
170
171
172/**
173 * @brief Prepare a SQL statement
174 *
175 * @param dbh handle to the database
176 * @param zSql SQL statement, UTF-8 encoded
177 * @param ppStmt set to the prepared statement
178 * @return 0 on success
179 */
180static int
181sq_prepare (sqlite3 *dbh, const char *zSql, sqlite3_stmt **ppStmt)
182{
183 char *dummy;
184 int result;
185
186 result = sqlite3_prepare_v2 (dbh,
187 zSql,
188 strlen (zSql),
189 ppStmt,
190 (const char **) &dummy);
191 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
192 "sqlite",
193 "Prepared `%s' / %p: %d\n",
194 zSql,
195 *ppStmt,
196 result);
197 return result;
198}
199
200
201/**
202 * Create our database indices.
203 *
204 * @param dbh handle to the database
205 */
206static void
207create_indices (sqlite3 *dbh)
208{
209 /* create indices */
210 if (
211 0 !=
212 (SQLITE_OK !=
213 sqlite3_exec (dbh,
214 "CREATE INDEX IF NOT EXISTS idx_hash ON gn091 (hash)",
215 NULL,
216 NULL,
217 NULL))
218 + (SQLITE_OK !=
219 sqlite3_exec (
220 dbh,
221 "CREATE INDEX IF NOT EXISTS idx_anon_type ON gn091 (anonLevel ASC,type)",
222 NULL,
223 NULL,
224 NULL))
225 + (SQLITE_OK !=
226 sqlite3_exec (dbh,
227 "CREATE INDEX IF NOT EXISTS idx_expire ON gn091 (expire ASC)",
228 NULL,
229 NULL,
230 NULL))
231 + (SQLITE_OK !=
232 sqlite3_exec (
233 dbh,
234 "CREATE INDEX IF NOT EXISTS idx_repl_rvalue ON gn091 (repl,rvalue)",
235 NULL,
236 NULL,
237 NULL)))
238 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
239 "sqlite",
240 "Failed to create indices: %s\n",
241 sqlite3_errmsg (dbh));
242}
243
244
245#if 0
246#define CHECK(a) GNUNET_break (a)
247#define ENULL NULL
248#else
249#define ENULL &e
250#define ENULL_DEFINED 1
251#define CHECK(a) \
252 if (! (a)) \
253 { \
254 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s\n", e); \
255 sqlite3_free (e); \
256 e = NULL; \
257 }
258#endif
259
260
261/**
262 * Initialize the database connections and associated
263 * data structures (create tables and indices
264 * as needed as well).
265 *
266 * @param cfg our configuration
267 * @param plugin the plugin context (state for this module)
268 * @return #GNUNET_OK on success
269 */
270static int
271database_setup (const struct GNUNET_CONFIGURATION_Handle *cfg,
272 struct Plugin *plugin)
273{
274 sqlite3_stmt *stmt;
275 char *afsdir;
276
277#if ENULL_DEFINED
278 char *e;
279#endif
280
281 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg,
282 "datastore-sqlite",
283 "FILENAME",
284 &afsdir))
285 {
286 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
287 "datastore-sqlite",
288 "FILENAME");
289 return GNUNET_SYSERR;
290 }
291 if (GNUNET_OK != GNUNET_DISK_file_test (afsdir))
292 {
293 if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (afsdir))
294 {
295 GNUNET_break (0);
296 GNUNET_free (afsdir);
297 return GNUNET_SYSERR;
298 }
299 /* database is new or got deleted, reset payload to zero! */
300 if (NULL != plugin->env->duc)
301 plugin->env->duc (plugin->env->cls, 0);
302 }
303 /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
304 plugin->fn = afsdir;
305
306 /* Open database and precompile statements */
307 if (SQLITE_OK != sqlite3_open (plugin->fn, &plugin->dbh))
308 {
309 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
310 "sqlite",
311 _ ("Unable to initialize SQLite: %s.\n"),
312 sqlite3_errmsg (plugin->dbh));
313 return GNUNET_SYSERR;
314 }
315 CHECK (
316 SQLITE_OK ==
317 sqlite3_exec (plugin->dbh, "PRAGMA temp_store=MEMORY", NULL, NULL, ENULL));
318 CHECK (
319 SQLITE_OK ==
320 sqlite3_exec (plugin->dbh, "PRAGMA synchronous=OFF", NULL, NULL, ENULL));
321 CHECK (SQLITE_OK == sqlite3_exec (plugin->dbh,
322 "PRAGMA legacy_file_format=OFF",
323 NULL,
324 NULL,
325 ENULL));
326 CHECK (SQLITE_OK == sqlite3_exec (plugin->dbh,
327 "PRAGMA auto_vacuum=INCREMENTAL",
328 NULL,
329 NULL,
330 ENULL));
331 CHECK (SQLITE_OK == sqlite3_exec (plugin->dbh,
332 "PRAGMA locking_mode=EXCLUSIVE",
333 NULL,
334 NULL,
335 ENULL));
336 CHECK (
337 SQLITE_OK ==
338 sqlite3_exec (plugin->dbh, "PRAGMA page_size=4096", NULL, NULL, ENULL));
339
340 CHECK (SQLITE_OK == sqlite3_busy_timeout (plugin->dbh, BUSY_TIMEOUT_MS));
341
342
343 /* We have to do it here, because otherwise precompiling SQL might fail */
344 CHECK (SQLITE_OK ==
345 sq_prepare (plugin->dbh,
346 "SELECT 1 FROM sqlite_master WHERE tbl_name = 'gn091'",
347 &stmt));
348
349 /* FIXME: SQLite does not have unsigned integers! This is ok for the type column because
350 * we only test equality on it and can cast it to/from uint32_t. For repl, prio, and anonLevel
351 * we do math or inequality tests, so we can't handle the entire range of uint32_t.
352 * This will also cause problems for expiration times after 294247-01-10-04:00:54 UTC.
353 */if ((SQLITE_DONE == sqlite3_step (stmt)) &&
354 (SQLITE_OK != sqlite3_exec (plugin->dbh,
355 "CREATE TABLE gn091 ("
356 " repl INT4 NOT NULL DEFAULT 0,"
357 " type INT4 NOT NULL DEFAULT 0,"
358 " prio INT4 NOT NULL DEFAULT 0,"
359 " anonLevel INT4 NOT NULL DEFAULT 0,"
360 " expire INT8 NOT NULL DEFAULT 0,"
361 " rvalue INT8 NOT NULL,"
362 " hash TEXT NOT NULL DEFAULT '',"
363 " vhash TEXT NOT NULL DEFAULT '',"
364 " value BLOB NOT NULL DEFAULT '')",
365 NULL,
366 NULL,
367 NULL)))
368 {
369 LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR, "sqlite3_exec");
370 sqlite3_finalize (stmt);
371 return GNUNET_SYSERR;
372 }
373 sqlite3_finalize (stmt);
374 create_indices (plugin->dbh);
375
376#define RESULT_COLUMNS \
377 "repl, type, prio, anonLevel, expire, hash, value, _ROWID_"
378 if (
379 (SQLITE_OK != sq_prepare (plugin->dbh,
380 "UPDATE gn091 "
381 "SET prio = prio + ?, "
382 "repl = repl + ?, "
383 "expire = MAX(expire, ?) "
384 "WHERE hash = ? AND vhash = ?",
385 &plugin->update)) ||
386 (SQLITE_OK != sq_prepare (plugin->dbh,
387 "UPDATE gn091 "
388 "SET repl = MAX (0, repl - 1) WHERE _ROWID_ = ?",
389 &plugin->updRepl)) ||
390 (SQLITE_OK != sq_prepare (plugin->dbh,
391 "SELECT " RESULT_COLUMNS " FROM gn091 "
392 "WHERE repl=?2 AND "
393 " (rvalue>=?1 OR "
394 " NOT EXISTS (SELECT 1 FROM gn091 "
395 "WHERE repl=?2 AND rvalue>=?1 LIMIT 1) ) "
396 "ORDER BY rvalue ASC LIMIT 1",
397 &plugin->selRepl)) ||
398 (SQLITE_OK != sq_prepare (plugin->dbh,
399 "SELECT MAX(repl) FROM gn091",
400 &plugin->maxRepl)) ||
401 (SQLITE_OK !=
402 sq_prepare (plugin->dbh,
403 "SELECT " RESULT_COLUMNS " FROM gn091 "
404 "WHERE NOT EXISTS (SELECT 1 FROM gn091 WHERE expire < ?1 LIMIT 1) OR (expire < ?1) "
405 "ORDER BY expire ASC LIMIT 1",
406 &plugin->selExpi)) ||
407 (SQLITE_OK != sq_prepare (plugin->dbh,
408 "SELECT " RESULT_COLUMNS " FROM gn091 "
409 "WHERE _ROWID_ >= ? AND "
410 "anonLevel = 0 AND "
411 "type = ? "
412 "ORDER BY _ROWID_ ASC LIMIT 1",
413 &plugin->selZeroAnon)) ||
414 (SQLITE_OK !=
415 sq_prepare (plugin->dbh,
416 "INSERT INTO gn091 (repl, type, prio, anonLevel, expire, rvalue, hash, vhash, value) "
417 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
418 &plugin->insertContent)) ||
419 (SQLITE_OK != sq_prepare (plugin->dbh,
420 "SELECT " RESULT_COLUMNS " FROM gn091 "
421 "WHERE _ROWID_ >= ?1 "
422 "ORDER BY _ROWID_ ASC LIMIT 1",
423 &plugin->get[0])) ||
424 (SQLITE_OK != sq_prepare (plugin->dbh,
425 "SELECT " RESULT_COLUMNS " FROM gn091 "
426 "WHERE _ROWID_ >= ?1 AND "
427 "type = ?4 "
428 "ORDER BY _ROWID_ ASC LIMIT 1",
429 &plugin->get[1])) ||
430 (SQLITE_OK != sq_prepare (plugin->dbh,
431 "SELECT " RESULT_COLUMNS " FROM gn091 "
432 "WHERE _ROWID_ >= ?1 AND "
433 "hash = ?3 "
434 "ORDER BY _ROWID_ ASC LIMIT 1",
435 &plugin->get[2])) ||
436 (SQLITE_OK != sq_prepare (plugin->dbh,
437 "SELECT " RESULT_COLUMNS " FROM gn091 "
438 "WHERE _ROWID_ >= ?1 AND "
439 "hash = ?3 AND "
440 "type = ?4 "
441 "ORDER BY _ROWID_ ASC LIMIT 1",
442 &plugin->get[3])) ||
443 (SQLITE_OK != sq_prepare (plugin->dbh,
444 "SELECT " RESULT_COLUMNS " FROM gn091 "
445 "WHERE _ROWID_ >= ?1 AND "
446 "rvalue >= ?2 "
447 "ORDER BY _ROWID_ ASC LIMIT 1",
448 &plugin->get[4])) ||
449 (SQLITE_OK != sq_prepare (plugin->dbh,
450 "SELECT " RESULT_COLUMNS " FROM gn091 "
451 "WHERE _ROWID_ >= ?1 AND "
452 "rvalue >= ?2 AND "
453 "type = ?4 "
454 "ORDER BY _ROWID_ ASC LIMIT 1",
455 &plugin->get[5])) ||
456 (SQLITE_OK != sq_prepare (plugin->dbh,
457 "SELECT " RESULT_COLUMNS " FROM gn091 "
458 "WHERE _ROWID_ >= ?1 AND "
459 "rvalue >= ?2 AND "
460 "hash = ?3 "
461 "ORDER BY _ROWID_ ASC LIMIT 1",
462 &plugin->get[6])) ||
463 (SQLITE_OK != sq_prepare (plugin->dbh,
464 "SELECT " RESULT_COLUMNS " FROM gn091 "
465 "WHERE _ROWID_ >= ?1 AND "
466 "rvalue >= ?2 AND "
467 "hash = ?3 AND "
468 "type = ?4 "
469 "ORDER BY _ROWID_ ASC LIMIT 1",
470 &plugin->get[7])) ||
471 (SQLITE_OK != sq_prepare (plugin->dbh,
472 "DELETE FROM gn091 WHERE _ROWID_ = ?",
473 &plugin->delRow)) ||
474 (SQLITE_OK != sq_prepare (plugin->dbh,
475 "DELETE FROM gn091 "
476 "WHERE hash = ? AND "
477 "value = ? ",
478 &plugin->remove)) ||
479 false)
480 {
481 LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR, "precompiling");
482 return GNUNET_SYSERR;
483 }
484 return GNUNET_OK;
485}
486
487
488/**
489 * Shutdown database connection and associate data
490 * structures.
491 *
492 * @param plugin the plugin context (state for this module)
493 */
494static void
495database_shutdown (struct Plugin *plugin)
496{
497 int result;
498
499#if SQLITE_VERSION_NUMBER >= 3007000
500 sqlite3_stmt *stmt;
501#endif
502
503 if (NULL != plugin->remove)
504 sqlite3_finalize (plugin->remove);
505 if (NULL != plugin->delRow)
506 sqlite3_finalize (plugin->delRow);
507 if (NULL != plugin->update)
508 sqlite3_finalize (plugin->update);
509 if (NULL != plugin->updRepl)
510 sqlite3_finalize (plugin->updRepl);
511 if (NULL != plugin->selRepl)
512 sqlite3_finalize (plugin->selRepl);
513 if (NULL != plugin->maxRepl)
514 sqlite3_finalize (plugin->maxRepl);
515 if (NULL != plugin->selExpi)
516 sqlite3_finalize (plugin->selExpi);
517 if (NULL != plugin->selZeroAnon)
518 sqlite3_finalize (plugin->selZeroAnon);
519 if (NULL != plugin->insertContent)
520 sqlite3_finalize (plugin->insertContent);
521 for (int i = 0; i < 8; ++i)
522 if (NULL != plugin->get[i])
523 sqlite3_finalize (plugin->get[i]);
524 result = sqlite3_close (plugin->dbh);
525#if SQLITE_VERSION_NUMBER >= 3007000
526 if (result == SQLITE_BUSY)
527 {
528 GNUNET_log_from (
529 GNUNET_ERROR_TYPE_WARNING,
530 "sqlite",
531 _ (
532 "Tried to close sqlite without finalizing all prepared statements.\n"));
533 stmt = sqlite3_next_stmt (plugin->dbh, NULL);
534 while (NULL != stmt)
535 {
536 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
537 "sqlite",
538 "Closing statement %p\n",
539 stmt);
540 result = sqlite3_finalize (stmt);
541 if (result != SQLITE_OK)
542 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
543 "sqlite",
544 "Failed to close statement %p: %d\n",
545 stmt,
546 result);
547 stmt = sqlite3_next_stmt (plugin->dbh, NULL);
548 }
549 result = sqlite3_close (plugin->dbh);
550 }
551#endif
552 if (SQLITE_OK != result)
553 LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR, "sqlite3_close");
554 GNUNET_free (plugin->fn);
555}
556
557
558/**
559 * Delete the database entry with the given
560 * row identifier.
561 *
562 * @param plugin the plugin context (state for this module)
563 * @param rid the ID of the row to delete
564 */
565static int
566delete_by_rowid (struct Plugin *plugin, uint64_t rid)
567{
568 struct GNUNET_SQ_QueryParam params[] = { GNUNET_SQ_query_param_uint64 (&rid),
569 GNUNET_SQ_query_param_end };
570
571 if (GNUNET_OK != GNUNET_SQ_bind (plugin->delRow, params))
572 return GNUNET_SYSERR;
573 if (SQLITE_DONE != sqlite3_step (plugin->delRow))
574 {
575 LOG_SQLITE (plugin,
576 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
577 "sqlite3_step");
578 GNUNET_SQ_reset (plugin->dbh, plugin->delRow);
579 return GNUNET_SYSERR;
580 }
581 GNUNET_SQ_reset (plugin->dbh, plugin->delRow);
582 return GNUNET_OK;
583}
584
585
586/**
587 * Store an item in the datastore.
588 *
589 * @param cls closure
590 * @param key key for the item
591 * @param absent true if the key was not found in the bloom filter
592 * @param size number of bytes in @a data
593 * @param data content stored
594 * @param type type of the content
595 * @param priority priority of the content
596 * @param anonymity anonymity-level for the content
597 * @param replication replication-level for the content
598 * @param expiration expiration time for the content
599 * @param cont continuation called with success or failure status
600 * @param cont_cls continuation closure
601 */
602static void
603sqlite_plugin_put (void *cls,
604 const struct GNUNET_HashCode *key,
605 bool absent,
606 uint32_t size,
607 const void *data,
608 enum GNUNET_BLOCK_Type type,
609 uint32_t priority,
610 uint32_t anonymity,
611 uint32_t replication,
612 struct GNUNET_TIME_Absolute expiration,
613 PluginPutCont cont,
614 void *cont_cls)
615{
616 struct Plugin *plugin = cls;
617 struct GNUNET_HashCode vhash;
618 char *msg = NULL;
619
620 GNUNET_CRYPTO_hash (data, size, &vhash);
621
622 if (! absent)
623 {
624 struct GNUNET_SQ_QueryParam params[] =
625 { GNUNET_SQ_query_param_uint32 (&priority),
626 GNUNET_SQ_query_param_uint32 (&replication),
627 GNUNET_SQ_query_param_absolute_time (&expiration),
628 GNUNET_SQ_query_param_auto_from_type (key),
629 GNUNET_SQ_query_param_auto_from_type (&vhash),
630 GNUNET_SQ_query_param_end };
631
632 if (GNUNET_OK != GNUNET_SQ_bind (plugin->update, params))
633 {
634 cont (cont_cls, key, size, GNUNET_SYSERR, _ ("sqlite bind failure"));
635 return;
636 }
637 if (SQLITE_DONE != sqlite3_step (plugin->update))
638 {
639 LOG_SQLITE_MSG (plugin,
640 &msg,
641 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
642 "sqlite3_step");
643 cont (cont_cls, key, size, GNUNET_SYSERR, msg);
644 GNUNET_free (msg);
645 return;
646 }
647 int changes = sqlite3_changes (plugin->dbh);
648 GNUNET_SQ_reset (plugin->dbh, plugin->update);
649 if (0 != changes)
650 {
651 cont (cont_cls, key, size, GNUNET_NO, NULL);
652 return;
653 }
654 }
655
656 uint64_t rvalue;
657 uint32_t type32 = (uint32_t) type;
658 struct GNUNET_SQ_QueryParam params[] =
659 { GNUNET_SQ_query_param_uint32 (&replication),
660 GNUNET_SQ_query_param_uint32 (&type32),
661 GNUNET_SQ_query_param_uint32 (&priority),
662 GNUNET_SQ_query_param_uint32 (&anonymity),
663 GNUNET_SQ_query_param_absolute_time (&expiration),
664 GNUNET_SQ_query_param_uint64 (&rvalue),
665 GNUNET_SQ_query_param_auto_from_type (key),
666 GNUNET_SQ_query_param_auto_from_type (&vhash),
667 GNUNET_SQ_query_param_fixed_size (data, size),
668 GNUNET_SQ_query_param_end };
669 int n;
670 int ret;
671 sqlite3_stmt *stmt;
672
673 if (size > MAX_ITEM_SIZE)
674 {
675 cont (cont_cls, key, size, GNUNET_SYSERR, _ ("Data too large"));
676 return;
677 }
678 GNUNET_log_from (
679 GNUNET_ERROR_TYPE_DEBUG,
680 "sqlite",
681 "Storing in database block with type %u/key `%s'/priority %u/expiration in %s (%s).\n",
682 type,
683 GNUNET_h2s (key),
684 priority,
685 GNUNET_STRINGS_relative_time_to_string (GNUNET_TIME_absolute_get_remaining (
686 expiration),
687 GNUNET_YES),
688 GNUNET_STRINGS_absolute_time_to_string (expiration));
689 stmt = plugin->insertContent;
690 rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
691 if (GNUNET_OK != GNUNET_SQ_bind (stmt, params))
692 {
693 cont (cont_cls, key, size, GNUNET_SYSERR, NULL);
694 return;
695 }
696 n = sqlite3_step (stmt);
697 switch (n)
698 {
699 case SQLITE_DONE:
700 if (NULL != plugin->env->duc)
701 plugin->env->duc (plugin->env->cls,
702 size + GNUNET_DATASTORE_ENTRY_OVERHEAD);
703 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
704 "sqlite",
705 "Stored new entry (%u bytes)\n",
706 size + GNUNET_DATASTORE_ENTRY_OVERHEAD);
707 ret = GNUNET_OK;
708 break;
709
710 case SQLITE_BUSY:
711 GNUNET_break (0);
712 LOG_SQLITE_MSG (plugin,
713 &msg,
714 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
715 "sqlite3_step");
716 ret = GNUNET_SYSERR;
717 break;
718
719 default:
720 LOG_SQLITE_MSG (plugin,
721 &msg,
722 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
723 "sqlite3_step");
724 GNUNET_SQ_reset (plugin->dbh, stmt);
725 database_shutdown (plugin);
726 database_setup (plugin->env->cfg, plugin);
727 cont (cont_cls, key, size, GNUNET_SYSERR, msg);
728 GNUNET_free (msg);
729 return;
730 }
731 GNUNET_SQ_reset (plugin->dbh, stmt);
732 cont (cont_cls, key, size, ret, msg);
733 GNUNET_free (msg);
734}
735
736
737/**
738 * Execute statement that gets a row and call the callback
739 * with the result. Resets the statement afterwards.
740 *
741 * @param plugin the plugin
742 * @param stmt the statement
743 * @param proc processor to call
744 * @param proc_cls closure for @a proc
745 */
746static void
747execute_get (struct Plugin *plugin,
748 sqlite3_stmt *stmt,
749 PluginDatumProcessor proc,
750 void *proc_cls)
751{
752 int n;
753 struct GNUNET_TIME_Absolute expiration;
754 uint32_t replication;
755 uint32_t type;
756 uint32_t priority;
757 uint32_t anonymity;
758 uint64_t rowid;
759 void *value;
760 size_t value_size;
761 struct GNUNET_HashCode key;
762 int ret;
763 struct GNUNET_SQ_ResultSpec rs[] =
764 { GNUNET_SQ_result_spec_uint32 (&replication),
765 GNUNET_SQ_result_spec_uint32 (&type),
766 GNUNET_SQ_result_spec_uint32 (&priority),
767 GNUNET_SQ_result_spec_uint32 (&anonymity),
768 GNUNET_SQ_result_spec_absolute_time (&expiration),
769 GNUNET_SQ_result_spec_auto_from_type (&key),
770 GNUNET_SQ_result_spec_variable_size (&value, &value_size),
771 GNUNET_SQ_result_spec_uint64 (&rowid),
772 GNUNET_SQ_result_spec_end };
773
774 n = sqlite3_step (stmt);
775 switch (n)
776 {
777 case SQLITE_ROW:
778 if (GNUNET_OK != GNUNET_SQ_extract_result (stmt, rs))
779 {
780 GNUNET_break (0);
781 break;
782 }
783 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
784 "sqlite",
785 "Found reply in database with expiration %s\n",
786 GNUNET_STRINGS_absolute_time_to_string (expiration));
787 ret = proc (proc_cls,
788 &key,
789 value_size,
790 value,
791 type,
792 priority,
793 anonymity,
794 replication,
795 expiration,
796 rowid);
797 GNUNET_SQ_cleanup_result (rs);
798 GNUNET_SQ_reset (plugin->dbh, stmt);
799 if ((GNUNET_NO == ret) && (GNUNET_OK == delete_by_rowid (plugin, rowid)) &&
800 (NULL != plugin->env->duc))
801 plugin->env->duc (plugin->env->cls,
802 -(value_size + GNUNET_DATASTORE_ENTRY_OVERHEAD));
803 return;
804
805 case SQLITE_DONE:
806 /* database must be empty */
807 break;
808
809 case SQLITE_BUSY:
810 case SQLITE_ERROR:
811 case SQLITE_MISUSE:
812 default:
813 LOG_SQLITE (plugin,
814 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
815 "sqlite3_step");
816 if (SQLITE_OK != sqlite3_reset (stmt))
817 LOG_SQLITE (plugin,
818 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
819 "sqlite3_reset");
820 GNUNET_break (0);
821 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
822 database_shutdown (plugin);
823 database_setup (plugin->env->cfg, plugin);
824 return;
825 }
826 GNUNET_SQ_reset (plugin->dbh, stmt);
827 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
828}
829
830
831/**
832 * Select a subset of the items in the datastore and call
833 * the given processor for the item.
834 *
835 * @param cls our plugin context
836 * @param next_uid return the result with lowest uid >= next_uid
837 * @param type entries of which type should be considered?
838 * Must not be zero (ANY).
839 * @param proc function to call on the matching value;
840 * will be called with NULL if no value matches
841 * @param proc_cls closure for @a proc
842 */
843static void
844sqlite_plugin_get_zero_anonymity (void *cls,
845 uint64_t next_uid,
846 enum GNUNET_BLOCK_Type type,
847 PluginDatumProcessor proc,
848 void *proc_cls)
849{
850 struct Plugin *plugin = cls;
851 uint32_t type32 = type;
852 struct GNUNET_SQ_QueryParam params[] = { GNUNET_SQ_query_param_uint64 (
853 &next_uid),
854 GNUNET_SQ_query_param_uint32 (
855 &type32),
856 GNUNET_SQ_query_param_end };
857
858 GNUNET_assert (type != GNUNET_BLOCK_TYPE_ANY);
859 if (GNUNET_OK != GNUNET_SQ_bind (plugin->selZeroAnon, params))
860 {
861 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
862 return;
863 }
864 execute_get (plugin, plugin->selZeroAnon, proc, proc_cls);
865}
866
867
868/**
869 * Get results for a particular key in the datastore.
870 *
871 * @param cls closure
872 * @param next_uid return the result with lowest uid >= next_uid
873 * @param random if true, return a random result instead of using next_uid
874 * @param key maybe NULL (to match all entries)
875 * @param type entries of which type are relevant?
876 * Use 0 for any type.
877 * @param proc function to call on the matching value;
878 * will be called with NULL if nothing matches
879 * @param proc_cls closure for @a proc
880 */
881static void
882sqlite_plugin_get_key (void *cls,
883 uint64_t next_uid,
884 bool random,
885 const struct GNUNET_HashCode *key,
886 enum GNUNET_BLOCK_Type type,
887 PluginDatumProcessor proc,
888 void *proc_cls)
889{
890 struct Plugin *plugin = cls;
891 uint64_t rvalue;
892 int use_rvalue = random;
893 uint32_t type32 = (uint32_t) type;
894 int use_type = GNUNET_BLOCK_TYPE_ANY != type;
895 int use_key = NULL != key;
896 sqlite3_stmt *stmt = plugin->get[use_rvalue * 4 + use_key * 2 + use_type];
897 struct GNUNET_SQ_QueryParam params[] =
898 { GNUNET_SQ_query_param_uint64 (&next_uid),
899 GNUNET_SQ_query_param_uint64 (&rvalue),
900 GNUNET_SQ_query_param_auto_from_type (key),
901 GNUNET_SQ_query_param_uint32 (&type32),
902 GNUNET_SQ_query_param_end };
903
904 /* SQLite doesn't like it when you try to bind a parameter greater than the
905 * last numbered parameter, but unused parameters in the middle are OK.
906 */
907 if (! use_type)
908 {
909 params[3] = (struct GNUNET_SQ_QueryParam) GNUNET_SQ_query_param_end;
910 if (! use_key)
911 {
912 params[2] = (struct GNUNET_SQ_QueryParam) GNUNET_SQ_query_param_end;
913 if (! use_rvalue)
914 params[1] = (struct GNUNET_SQ_QueryParam) GNUNET_SQ_query_param_end;
915 }
916 }
917 if (random)
918 {
919 rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
920 next_uid = 0;
921 }
922 else
923 rvalue = 0;
924
925 if (GNUNET_OK != GNUNET_SQ_bind (stmt, params))
926 {
927 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
928 return;
929 }
930 execute_get (plugin, stmt, proc, proc_cls);
931}
932
933
934/**
935 * Context for #repl_proc() function.
936 */
937struct ReplCtx
938{
939 /**
940 * Function to call for the result (or the NULL).
941 */
942 PluginDatumProcessor proc;
943
944 /**
945 * Closure for @e proc.
946 */
947 void *proc_cls;
948
949 /**
950 * UID to use.
951 */
952 uint64_t uid;
953
954 /**
955 * Yes if UID was set.
956 */
957 int have_uid;
958};
959
960
961/**
962 * Wrapper for the processor for #sqlite_plugin_get_replication().
963 * Decrements the replication counter and calls the original
964 * processor.
965 *
966 * @param cls closure
967 * @param key key for the content
968 * @param size number of bytes in @a data
969 * @param data content stored
970 * @param type type of the content
971 * @param priority priority of the content
972 * @param anonymity anonymity-level for the content
973 * @param replication replication-level for the content
974 * @param expiration expiration time for the content
975 * @param uid unique identifier for the datum;
976 * maybe 0 if no unique identifier is available
977 * @return #GNUNET_OK for normal return,
978 * #GNUNET_NO to delete the item
979 */
980static int
981repl_proc (void *cls,
982 const struct GNUNET_HashCode *key,
983 uint32_t size,
984 const void *data,
985 enum GNUNET_BLOCK_Type type,
986 uint32_t priority,
987 uint32_t anonymity,
988 uint32_t replication,
989 struct GNUNET_TIME_Absolute expiration,
990 uint64_t uid)
991{
992 struct ReplCtx *rc = cls;
993 int ret;
994
995 if (GNUNET_SYSERR == rc->have_uid)
996 rc->have_uid = GNUNET_NO;
997 ret = rc->proc (rc->proc_cls,
998 key,
999 size,
1000 data,
1001 type,
1002 priority,
1003 anonymity,
1004 replication,
1005 expiration,
1006 uid);
1007 if (NULL != key)
1008 {
1009 rc->uid = uid;
1010 rc->have_uid = GNUNET_YES;
1011 }
1012 return ret;
1013}
1014
1015
1016/**
1017 * Get a random item for replication. Returns a single random item
1018 * from those with the highest replication counters. The item's
1019 * replication counter is decremented by one IF it was positive before.
1020 * Call @a proc with all values ZERO or NULL if the datastore is empty.
1021 *
1022 * @param cls closure
1023 * @param proc function to call the value (once only).
1024 * @param proc_cls closure for @a proc
1025 */
1026static void
1027sqlite_plugin_get_replication (void *cls,
1028 PluginDatumProcessor proc,
1029 void *proc_cls)
1030{
1031 struct Plugin *plugin = cls;
1032 struct ReplCtx rc;
1033 uint64_t rvalue = 0;
1034 uint32_t repl;
1035 struct GNUNET_SQ_QueryParam params_sel_repl[] =
1036 { GNUNET_SQ_query_param_uint64 (&rvalue),
1037 GNUNET_SQ_query_param_uint32 (&repl),
1038 GNUNET_SQ_query_param_end };
1039 struct GNUNET_SQ_QueryParam params_upd_repl[] =
1040 { GNUNET_SQ_query_param_uint64 (&rc.uid), GNUNET_SQ_query_param_end };
1041
1042 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
1043 "datastore-sqlite",
1044 "Getting random block based on replication order.\n");
1045 if (SQLITE_ROW != sqlite3_step (plugin->maxRepl))
1046 {
1047 GNUNET_SQ_reset (plugin->dbh, plugin->maxRepl);
1048 /* DB empty */
1049 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
1050 return;
1051 }
1052 repl = sqlite3_column_int (plugin->maxRepl, 0);
1053 GNUNET_SQ_reset (plugin->dbh, plugin->maxRepl);
1054 rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
1055 if (GNUNET_OK != GNUNET_SQ_bind (plugin->selRepl, params_sel_repl))
1056 {
1057 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
1058 return;
1059 }
1060 rc.have_uid = GNUNET_SYSERR;
1061 rc.proc = proc;
1062 rc.proc_cls = proc_cls;
1063 execute_get (plugin, plugin->selRepl, &repl_proc, &rc);
1064 if (GNUNET_YES == rc.have_uid)
1065 {
1066 if (GNUNET_OK != GNUNET_SQ_bind (plugin->updRepl, params_upd_repl))
1067 {
1068 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
1069 return;
1070 }
1071 if (SQLITE_DONE != sqlite3_step (plugin->updRepl))
1072 LOG_SQLITE (plugin,
1073 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
1074 "sqlite3_step");
1075 GNUNET_SQ_reset (plugin->dbh, plugin->updRepl);
1076 }
1077 if (GNUNET_SYSERR == rc.have_uid)
1078 {
1079 /* proc was not called at all so far, do it now. */
1080 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
1081 }
1082}
1083
1084
1085/**
1086 * Get a random item that has expired or has low priority.
1087 * Call @a proc with all values ZERO or NULL if the datastore is empty.
1088 *
1089 * @param cls closure
1090 * @param proc function to call the value (once only).
1091 * @param proc_cls closure for @a proc
1092 */
1093static void
1094sqlite_plugin_get_expiration (void *cls,
1095 PluginDatumProcessor proc,
1096 void *proc_cls)
1097{
1098 struct Plugin *plugin = cls;
1099 sqlite3_stmt *stmt;
1100 struct GNUNET_TIME_Absolute now = { 0 };
1101 struct GNUNET_SQ_QueryParam params[] = { GNUNET_SQ_query_param_absolute_time (
1102 &now),
1103 GNUNET_SQ_query_param_end };
1104
1105 GNUNET_log_from (
1106 GNUNET_ERROR_TYPE_DEBUG,
1107 "sqlite",
1108 "Getting random block based on expiration and priority order.\n");
1109 now = GNUNET_TIME_absolute_get ();
1110 stmt = plugin->selExpi;
1111 if (GNUNET_OK != GNUNET_SQ_bind (stmt, params))
1112 {
1113 proc (proc_cls, NULL, 0, NULL, 0, 0, 0, 0, GNUNET_TIME_UNIT_ZERO_ABS, 0);
1114 return;
1115 }
1116 execute_get (plugin, stmt, proc, proc_cls);
1117}
1118
1119
1120/**
1121 * Get all of the keys in the datastore.
1122 *
1123 * @param cls closure
1124 * @param proc function to call on each key
1125 * @param proc_cls closure for @a proc
1126 */
1127static void
1128sqlite_plugin_get_keys (void *cls, PluginKeyProcessor proc, void *proc_cls)
1129{
1130 struct Plugin *plugin = cls;
1131 struct GNUNET_HashCode key;
1132 struct GNUNET_SQ_ResultSpec results[] =
1133 { GNUNET_SQ_result_spec_auto_from_type (&key), GNUNET_SQ_result_spec_end };
1134 sqlite3_stmt *stmt;
1135 int ret;
1136
1137 GNUNET_assert (NULL != proc);
1138 if (SQLITE_OK != sq_prepare (plugin->dbh, "SELECT hash FROM gn091", &stmt))
1139 {
1140 LOG_SQLITE (plugin,
1141 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
1142 "sqlite_prepare");
1143 proc (proc_cls, NULL, 0);
1144 return;
1145 }
1146 while (SQLITE_ROW == (ret = sqlite3_step (stmt)))
1147 {
1148 if (GNUNET_OK == GNUNET_SQ_extract_result (stmt, results))
1149 proc (proc_cls, &key, 1);
1150 else
1151 GNUNET_break (0);
1152 }
1153 if (SQLITE_DONE != ret)
1154 LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR, "sqlite_step");
1155 sqlite3_finalize (stmt);
1156 proc (proc_cls, NULL, 0);
1157}
1158
1159
1160/**
1161 * Drop database.
1162 *
1163 * @param cls our plugin context
1164 */
1165static void
1166sqlite_plugin_drop (void *cls)
1167{
1168 struct Plugin *plugin = cls;
1169
1170 plugin->drop_on_shutdown = GNUNET_YES;
1171}
1172
1173
1174/**
1175 * Remove a particular key in the datastore.
1176 *
1177 * @param cls closure
1178 * @param key key for the content
1179 * @param size number of bytes in data
1180 * @param data content stored
1181 * @param cont continuation called with success or failure status
1182 * @param cont_cls continuation closure for @a cont
1183 */
1184static void
1185sqlite_plugin_remove_key (void *cls,
1186 const struct GNUNET_HashCode *key,
1187 uint32_t size,
1188 const void *data,
1189 PluginRemoveCont cont,
1190 void *cont_cls)
1191{
1192 struct Plugin *plugin = cls;
1193 struct GNUNET_SQ_QueryParam params[] =
1194 { GNUNET_SQ_query_param_auto_from_type (key),
1195 GNUNET_SQ_query_param_fixed_size (data, size),
1196 GNUNET_SQ_query_param_end };
1197
1198 if (GNUNET_OK != GNUNET_SQ_bind (plugin->remove, params))
1199 {
1200 cont (cont_cls, key, size, GNUNET_SYSERR, "bind failed");
1201 return;
1202 }
1203 if (SQLITE_DONE != sqlite3_step (plugin->remove))
1204 {
1205 LOG_SQLITE (plugin,
1206 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
1207 "sqlite3_step");
1208 GNUNET_SQ_reset (plugin->dbh, plugin->remove);
1209 cont (cont_cls, key, size, GNUNET_SYSERR, "sqlite3_step failed");
1210 return;
1211 }
1212 int changes = sqlite3_changes (plugin->dbh);
1213 GNUNET_SQ_reset (plugin->dbh, plugin->remove);
1214 if (0 == changes)
1215 {
1216 cont (cont_cls, key, size, GNUNET_NO, NULL);
1217 return;
1218 }
1219 if (NULL != plugin->env->duc)
1220 plugin->env->duc (plugin->env->cls,
1221 -(size + GNUNET_DATASTORE_ENTRY_OVERHEAD));
1222 cont (cont_cls, key, size, GNUNET_OK, NULL);
1223}
1224
1225
1226/**
1227 * Get an estimate of how much space the database is
1228 * currently using.
1229 *
1230 * @param cls the `struct Plugin`
1231 * @return the size of the database on disk (estimate)
1232 */
1233static void
1234sqlite_plugin_estimate_size (void *cls, unsigned long long *estimate)
1235{
1236 struct Plugin *plugin = cls;
1237 sqlite3_stmt *stmt;
1238 uint64_t pages;
1239 uint64_t page_size;
1240
1241#if ENULL_DEFINED
1242 char *e;
1243#endif
1244
1245 if (NULL == estimate)
1246 return;
1247 if (SQLITE_VERSION_NUMBER < 3006000)
1248 {
1249 GNUNET_log_from (
1250 GNUNET_ERROR_TYPE_WARNING,
1251 "datastore-sqlite",
1252 _ ("sqlite version to old to determine size, assuming zero\n"));
1253 *estimate = 0;
1254 return;
1255 }
1256 CHECK (SQLITE_OK == sqlite3_exec (plugin->dbh, "VACUUM", NULL, NULL, ENULL));
1257 CHECK (SQLITE_OK == sqlite3_exec (plugin->dbh,
1258 "PRAGMA auto_vacuum=INCREMENTAL",
1259 NULL,
1260 NULL,
1261 ENULL));
1262 if (SQLITE_OK != sq_prepare (plugin->dbh, "PRAGMA page_count", &stmt))
1263 {
1264 GNUNET_log_from (
1265 GNUNET_ERROR_TYPE_WARNING,
1266 "datastore-sqlite",
1267 _("error preparing statement\n"));
1268 return;
1269 }
1270 if (SQLITE_ROW == sqlite3_step (stmt))
1271 pages = sqlite3_column_int64 (stmt, 0);
1272 else
1273 pages = 0;
1274 sqlite3_finalize (stmt);
1275 if (SQLITE_OK != sq_prepare (plugin->dbh, "PRAGMA page_size", &stmt))
1276 {
1277 GNUNET_log_from (
1278 GNUNET_ERROR_TYPE_WARNING,
1279 "datastore-sqlite",
1280 _("error preparing statement\n"));
1281 return;
1282 }
1283 if (SQLITE_ROW != sqlite3_step (stmt))
1284 {
1285 GNUNET_log_from (
1286 GNUNET_ERROR_TYPE_WARNING,
1287 "datastore-sqlite",
1288 _("error stepping\n"));
1289 return;
1290 }
1291 page_size = sqlite3_column_int64 (stmt, 0);
1292 sqlite3_finalize (stmt);
1293 GNUNET_log (
1294 GNUNET_ERROR_TYPE_INFO,
1295 _ (
1296 "Using sqlite page utilization to estimate payload (%llu pages of size %llu bytes)\n"),
1297 (unsigned long long) pages,
1298 (unsigned long long) page_size);
1299 *estimate = pages * page_size;
1300}
1301
1302
1303/**
1304 * Entry point for the plugin.
1305 *
1306 * @param cls the `struct GNUNET_DATASTORE_PluginEnvironment *`
1307 * @return NULL on error, othrewise the plugin context
1308 */
1309void *
1310libgnunet_plugin_datastore_sqlite_init (void *cls)
1311{
1312 static struct Plugin plugin;
1313 struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
1314 struct GNUNET_DATASTORE_PluginFunctions *api;
1315
1316 if (NULL != plugin.env)
1317 return NULL; /* can only initialize once! */
1318 memset (&plugin, 0, sizeof(struct Plugin));
1319 plugin.env = env;
1320 if (GNUNET_OK != database_setup (env->cfg, &plugin))
1321 {
1322 database_shutdown (&plugin);
1323 return NULL;
1324 }
1325 api = GNUNET_new (struct GNUNET_DATASTORE_PluginFunctions);
1326 api->cls = &plugin;
1327 api->estimate_size = &sqlite_plugin_estimate_size;
1328 api->put = &sqlite_plugin_put;
1329 api->get_key = &sqlite_plugin_get_key;
1330 api->get_replication = &sqlite_plugin_get_replication;
1331 api->get_expiration = &sqlite_plugin_get_expiration;
1332 api->get_zero_anonymity = &sqlite_plugin_get_zero_anonymity;
1333 api->get_keys = &sqlite_plugin_get_keys;
1334 api->drop = &sqlite_plugin_drop;
1335 api->remove_key = &sqlite_plugin_remove_key;
1336 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
1337 "sqlite",
1338 _ ("Sqlite database running\n"));
1339 return api;
1340}
1341
1342
1343/**
1344 * Exit point from the plugin.
1345 *
1346 * @param cls the plugin context (as returned by "init")
1347 * @return always NULL
1348 */
1349void *
1350libgnunet_plugin_datastore_sqlite_done (void *cls)
1351{
1352 char *fn;
1353 struct GNUNET_DATASTORE_PluginFunctions *api = cls;
1354 struct Plugin *plugin = api->cls;
1355
1356 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
1357 "sqlite",
1358 "sqlite plugin is done\n");
1359 fn = NULL;
1360 if (plugin->drop_on_shutdown)
1361 fn = GNUNET_strdup (plugin->fn);
1362 database_shutdown (plugin);
1363 plugin->env = NULL;
1364 GNUNET_free (api);
1365 if (NULL != fn)
1366 {
1367 if (0 != unlink (fn))
1368 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn);
1369 GNUNET_free (fn);
1370 }
1371 return NULL;
1372}
1373
1374
1375/* end of plugin_datastore_sqlite.c */
diff --git a/src/plugin/datastore/plugin_datastore_template.c b/src/plugin/datastore/plugin_datastore_template.c
new file mode 100644
index 000000000..2b455f8cb
--- /dev/null
+++ b/src/plugin/datastore/plugin_datastore_template.c
@@ -0,0 +1,274 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2009, 2011 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file datastore/plugin_datastore_template.c
23 * @brief template-based datastore backend
24 * @author Christian Grothoff
25 */
26
27#include "platform.h"
28#include "gnunet_datastore_plugin.h"
29
30
31/**
32 * Context for all functions in this plugin.
33 */
34struct Plugin
35{
36 /**
37 * Our execution environment.
38 */
39 struct GNUNET_DATASTORE_PluginEnvironment *env;
40};
41
42
43/**
44 * Get an estimate of how much space the database is
45 * currently using.
46 *
47 * @param cls our "struct Plugin*"
48 * @return number of bytes used on disk
49 */
50static void
51template_plugin_estimate_size (void *cls, unsigned long long *estimate)
52{
53 if (NULL == estimate)
54 return;
55 GNUNET_break (0);
56 *estimate = 0;
57}
58
59
60/**
61 * Store an item in the datastore.
62 *
63 * @param cls closure
64 * @param key key for the item
65 * @param absent true if the key was not found in the bloom filter
66 * @param size number of bytes in data
67 * @param data content stored
68 * @param type type of the content
69 * @param priority priority of the content
70 * @param anonymity anonymity-level for the content
71 * @param replication replication-level for the content
72 * @param expiration expiration time for the content
73 * @param cont continuation called with success or failure status
74 * @param cont_cls continuation closure
75 */
76static void
77template_plugin_put (void *cls,
78 const struct GNUNET_HashCode *key,
79 bool absent,
80 uint32_t size,
81 const void *data,
82 enum GNUNET_BLOCK_Type type,
83 uint32_t priority,
84 uint32_t anonymity,
85 uint32_t replication,
86 struct GNUNET_TIME_Absolute expiration,
87 PluginPutCont cont,
88 void *cont_cls)
89{
90 GNUNET_break (0);
91 cont (cont_cls, key, size, GNUNET_SYSERR, "not implemented");
92}
93
94
95/**
96 * Get one of the results for a particular key in the datastore.
97 *
98 * @param cls closure
99 * @param next_uid return the result with lowest uid >= next_uid
100 * @param random if true, return a random result instead of using next_uid
101 * @param key maybe NULL (to match all entries)
102 * @param type entries of which type are relevant?
103 * Use 0 for any type.
104 * @param proc function to call on each matching value;
105 * will be called with NULL if nothing matches
106 * @param proc_cls closure for proc
107 */
108static void
109template_plugin_get_key (void *cls,
110 uint64_t next_uid,
111 bool random,
112 const struct GNUNET_HashCode *key,
113 enum GNUNET_BLOCK_Type type,
114 PluginDatumProcessor proc,
115 void *proc_cls)
116{
117 GNUNET_break (0);
118}
119
120
121/**
122 * Get a random item for replication. Returns a single, not expired,
123 * random item from those with the highest replication counters. The
124 * item's replication counter is decremented by one IF it was positive
125 * before. Call 'proc' with all values ZERO or NULL if the datastore
126 * is empty.
127 *
128 * @param cls closure
129 * @param proc function to call the value (once only).
130 * @param proc_cls closure for proc
131 */
132static void
133template_plugin_get_replication (void *cls, PluginDatumProcessor proc,
134 void *proc_cls)
135{
136 GNUNET_break (0);
137}
138
139
140/**
141 * Get a random item for expiration. Call 'proc' with all values ZERO
142 * or NULL if the datastore is empty.
143 *
144 * @param cls closure
145 * @param proc function to call the value (once only).
146 * @param proc_cls closure for proc
147 */
148static void
149template_plugin_get_expiration (void *cls, PluginDatumProcessor proc,
150 void *proc_cls)
151{
152 GNUNET_break (0);
153}
154
155
156/**
157 * Call the given processor on an item with zero anonymity.
158 *
159 * @param cls our "struct Plugin*"
160 * @param next_uid return the result with lowest uid >= next_uid
161 * @param type entries of which type should be considered?
162 * Must not be zero (ANY).
163 * @param proc function to call on the matching value;
164 * will be called with NULL if no value matches
165 * @param proc_cls closure for proc
166 */
167static void
168template_plugin_get_zero_anonymity (void *cls, uint64_t next_uid,
169 enum GNUNET_BLOCK_Type type,
170 PluginDatumProcessor proc, void *proc_cls)
171{
172 GNUNET_break (0);
173}
174
175
176/**
177 * Drop database.
178 */
179static void
180template_plugin_drop (void *cls)
181{
182 GNUNET_break (0);
183}
184
185
186/**
187 * Get all of the keys in the datastore.
188 *
189 * @param cls closure
190 * @param proc function to call on each key
191 * @param proc_cls closure for proc
192 */
193static void
194template_get_keys (void *cls,
195 PluginKeyProcessor proc,
196 void *proc_cls)
197{
198 proc (proc_cls, NULL, 0);
199}
200
201
202/**
203 * Remove a particular key in the datastore.
204 *
205 * @param cls closure
206 * @param key key for the content
207 * @param size number of bytes in data
208 * @param data content stored
209 * @param cont continuation called with success or failure status
210 * @param cont_cls continuation closure for @a cont
211 */
212static void
213template_plugin_remove_key (void *cls,
214 const struct GNUNET_HashCode *key,
215 uint32_t size,
216 const void *data,
217 PluginRemoveCont cont,
218 void *cont_cls)
219{
220 GNUNET_break (0);
221 cont (cont_cls, key, size, GNUNET_SYSERR, "not implemented");
222}
223
224
225/**
226 * Entry point for the plugin.
227 *
228 * @param cls the "struct GNUNET_DATASTORE_PluginEnvironment*"
229 * @return our "struct Plugin*"
230 */
231void *
232libgnunet_plugin_datastore_template_init (void *cls)
233{
234 struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
235 struct GNUNET_DATASTORE_PluginFunctions *api;
236 struct Plugin *plugin;
237
238 plugin = GNUNET_new (struct Plugin);
239 plugin->env = env;
240 api = GNUNET_new (struct GNUNET_DATASTORE_PluginFunctions);
241 api->cls = plugin;
242 api->estimate_size = &template_plugin_estimate_size;
243 api->put = &template_plugin_put;
244 api->get_key = &template_plugin_get_key;
245 api->get_replication = &template_plugin_get_replication;
246 api->get_expiration = &template_plugin_get_expiration;
247 api->get_zero_anonymity = &template_plugin_get_zero_anonymity;
248 api->drop = &template_plugin_drop;
249 api->get_keys = &template_get_keys;
250 api->remove_key = &template_plugin_remove_key;
251 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "template",
252 _ ("Template database running\n"));
253 return api;
254}
255
256
257/**
258 * Exit point from the plugin.
259 * @param cls our "struct Plugin*"
260 * @return always NULL
261 */
262void *
263libgnunet_plugin_datastore_template_done (void *cls)
264{
265 struct GNUNET_DATASTORE_PluginFunctions *api = cls;
266 struct Plugin *plugin = api->cls;
267
268 GNUNET_free (plugin);
269 GNUNET_free (api);
270 return NULL;
271}
272
273
274/* end of plugin_datastore_template.c */
diff --git a/src/plugin/datastore/test_defaults.conf b/src/plugin/datastore/test_defaults.conf
new file mode 100644
index 000000000..1e6bbeeaf
--- /dev/null
+++ b/src/plugin/datastore/test_defaults.conf
@@ -0,0 +1,10 @@
1@inline@ ../../../contrib/conf/gnunet/no_autostart_above_core.conf
2@inline@ ../../../contrib/conf/gnunet/no_forcestart.conf
3
4[datastore]
5PORT = 22654
6QUOTA = 1 MB
7START_ON_DEMAND = YES
8
9[nse]
10WORKBITS = 1
diff --git a/src/plugin/datastore/test_plugin_datastore.c b/src/plugin/datastore/test_plugin_datastore.c
new file mode 100644
index 000000000..7de1acf2d
--- /dev/null
+++ b/src/plugin/datastore/test_plugin_datastore.c
@@ -0,0 +1,478 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2011 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/*
21 * @file test_plugin_datastore.c
22 * @brief Test database plugin directly, calling each API function once
23 * @author Christian Grothoff
24 */
25
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_protocols.h"
29#include "gnunet_datastore_plugin.h"
30#include "gnunet_testing_lib.h"
31
32/**
33 * Number of put operations to perform.
34 */
35#define PUT_10 10
36
37static unsigned long long stored_bytes;
38
39static unsigned long long stored_entries;
40
41static unsigned long long stored_ops;
42
43static const char *plugin_name;
44
45static int ok;
46
47enum RunPhase
48{
49 RP_ERROR = 0,
50 RP_PUT,
51 RP_GET,
52 RP_ITER_ZERO,
53 RP_REPL_GET,
54 RP_EXPI_GET,
55 RP_REMOVE,
56 RP_DROP
57};
58
59
60struct CpsRunContext
61{
62 const struct GNUNET_CONFIGURATION_Handle *cfg;
63 struct GNUNET_DATASTORE_PluginFunctions *api;
64 enum RunPhase phase;
65 unsigned int cnt;
66 unsigned int i;
67};
68
69
70/**
71 * Function called by plugins to notify us about a
72 * change in their disk utilization.
73 *
74 * @param cls closure (NULL)
75 * @param delta change in disk utilization,
76 * 0 for "reset to empty"
77 */
78static void
79disk_utilization_change_cb (void *cls, int delta)
80{
81 /* do nothing */
82}
83
84
85static void
86test (void *cls);
87
88
89/**
90 * Put continuation.
91 *
92 * @param cls closure
93 * @param key key for the item stored
94 * @param size size of the item stored
95 * @param status #GNUNET_OK or #GNUNET_SYSERROR
96 * @param msg error message on error
97 */
98static void
99put_continuation (void *cls,
100 const struct GNUNET_HashCode *key,
101 uint32_t size,
102 int status,
103 const char *msg)
104{
105 struct CpsRunContext *crc = cls;
106 static unsigned long long os;
107 unsigned long long cs;
108
109 if (GNUNET_OK != status)
110 {
111 fprintf (stderr,
112 "ERROR: `%s'\n",
113 msg);
114 }
115 else
116 {
117 crc->api->estimate_size (crc->api->cls,
118 &cs);
119 GNUNET_assert (os <= cs);
120 os = cs;
121 stored_bytes += size;
122 stored_ops++;
123 stored_entries++;
124 }
125 GNUNET_SCHEDULER_add_now (&test, crc);
126}
127
128
129static void
130gen_key (int i, struct GNUNET_HashCode *key)
131{
132 memset (key, 0, sizeof(struct GNUNET_HashCode));
133 key->bits[0] = (unsigned int) i;
134 GNUNET_CRYPTO_hash (key, sizeof(struct GNUNET_HashCode), key);
135}
136
137
138static void
139do_put (struct CpsRunContext *crc)
140{
141 char value[65536];
142 size_t size;
143 struct GNUNET_HashCode key;
144 unsigned int prio;
145 static int i;
146
147 if (PUT_10 == i)
148 {
149 i = 0;
150 crc->phase++;
151 GNUNET_SCHEDULER_add_now (&test, crc);
152 return;
153 }
154 /* most content is 32k */
155 size = 32 * 1024;
156
157 if ((0 != i) && (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 16) ==
158 0) ) /* but some of it is less! */
159 size = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 32 * 1024);
160 size = size - (size & 7); /* always multiple of 8 */
161
162 /* generate random key */
163 gen_key (i, &key);
164 memset (value, i, size);
165 if (i > 255)
166 memset (value, i - 255, size / 2);
167 value[0] = crc->i;
168 prio = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 100);
169 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
170 "putting type %u, anon %u under key %s\n", i + 1, i,
171 GNUNET_h2s (&key));
172 crc->api->put (crc->api->cls,
173 &key,
174 false /* absent */,
175 size,
176 value, i + 1 /* type */,
177 prio,
178 i /* anonymity */,
179 0 /* replication */,
180 GNUNET_TIME_relative_to_absolute
181 (GNUNET_TIME_relative_multiply
182 (GNUNET_TIME_UNIT_MILLISECONDS,
183 60 * 60 * 60 * 1000
184 + GNUNET_CRYPTO_random_u32
185 (GNUNET_CRYPTO_QUALITY_WEAK, 1000))),
186 put_continuation,
187 crc);
188 i++;
189}
190
191
192static uint64_t guid;
193
194
195static int
196iterate_one_shot (void *cls,
197 const struct GNUNET_HashCode *key,
198 uint32_t size,
199 const void *data,
200 enum GNUNET_BLOCK_Type type,
201 uint32_t priority,
202 uint32_t anonymity,
203 uint32_t replication,
204 struct GNUNET_TIME_Absolute expiration,
205 uint64_t uid)
206{
207 struct CpsRunContext *crc = cls;
208
209 GNUNET_assert (NULL != key);
210 guid = uid;
211 crc->phase++;
212 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
213 "Found result type=%u, priority=%u, size=%u, expire=%s, key %s\n",
214 (unsigned int) type,
215 (unsigned int) priority,
216 (unsigned int) size,
217 GNUNET_STRINGS_absolute_time_to_string (expiration),
218 GNUNET_h2s (key));
219 GNUNET_SCHEDULER_add_now (&test,
220 crc);
221 return GNUNET_OK;
222}
223
224
225static void
226remove_continuation (void *cls,
227 const struct GNUNET_HashCode *key,
228 uint32_t size,
229 int status,
230 const char *msg)
231{
232 struct CpsRunContext *crc = cls;
233
234 GNUNET_assert (NULL != key);
235 GNUNET_assert (32768 == size);
236 GNUNET_assert (GNUNET_OK == status);
237 GNUNET_assert (NULL == msg);
238 crc->phase++;
239 GNUNET_SCHEDULER_add_now (&test,
240 crc);
241}
242
243
244/**
245 * Function called when the service shuts
246 * down. Unloads our datastore plugin.
247 *
248 * @param api api to unload
249 * @param cfg configuration to use
250 */
251static void
252unload_plugin (struct GNUNET_DATASTORE_PluginFunctions *api,
253 const struct GNUNET_CONFIGURATION_Handle *cfg)
254{
255 char *name;
256 char *libname;
257
258 if (GNUNET_OK !=
259 GNUNET_CONFIGURATION_get_value_string (cfg,
260 "DATASTORE",
261 "DATABASE",
262 &name))
263 {
264 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
265 _ ("No `%s' specified for `%s' in configuration!\n"),
266 "DATABASE",
267 "DATASTORE");
268 return;
269 }
270 GNUNET_asprintf (&libname, "libgnunet_plugin_datastore_%s", name);
271 GNUNET_break (NULL == GNUNET_PLUGIN_unload (libname, api));
272 GNUNET_free (libname);
273 GNUNET_free (name);
274}
275
276
277/**
278 * Last task run during shutdown. Disconnects us from
279 * the transport and core.
280 */
281static void
282cleaning_task (void *cls)
283{
284 struct CpsRunContext *crc = cls;
285
286 unload_plugin (crc->api, crc->cfg);
287 GNUNET_free (crc);
288}
289
290
291static void
292test (void *cls)
293{
294 struct CpsRunContext *crc = cls;
295 struct GNUNET_HashCode key;
296
297 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
298 "In phase %d, iteration %u\n", crc->phase, crc->cnt);
299 switch (crc->phase)
300 {
301 case RP_ERROR:
302 ok = 1;
303 GNUNET_break (0);
304 crc->api->drop (crc->api->cls);
305 GNUNET_SCHEDULER_add_now (&cleaning_task, crc);
306 break;
307
308 case RP_PUT:
309 do_put (crc);
310 break;
311
312 case RP_GET:
313 if (crc->cnt == 1)
314 {
315 crc->cnt = 0;
316 crc->phase++;
317 GNUNET_SCHEDULER_add_now (&test, crc);
318 break;
319 }
320 gen_key (5, &key);
321 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
322 "Looking for %s\n",
323 GNUNET_h2s (&key));
324 crc->api->get_key (crc->api->cls,
325 0,
326 false,
327 &key,
328 GNUNET_BLOCK_TYPE_ANY,
329 &iterate_one_shot,
330 crc);
331 break;
332
333 case RP_ITER_ZERO:
334 if (crc->cnt == 1)
335 {
336 crc->cnt = 0;
337 crc->phase++;
338 GNUNET_SCHEDULER_add_now (&test, crc);
339 break;
340 }
341 crc->api->get_zero_anonymity (crc->api->cls, 0, 1, &iterate_one_shot, crc);
342 break;
343
344 case RP_REPL_GET:
345 crc->api->get_replication (crc->api->cls, &iterate_one_shot, crc);
346 break;
347
348 case RP_EXPI_GET:
349 crc->api->get_expiration (crc->api->cls, &iterate_one_shot, crc);
350 break;
351
352 case RP_REMOVE:
353 {
354 struct GNUNET_HashCode key;
355 uint32_t size = 32768;
356 char value[size];
357
358 gen_key (0, &key);
359 memset (value, 0, size);
360 value[0] = crc->i;
361 crc->api->remove_key (crc->api->cls,
362 &key,
363 size,
364 value,
365 &remove_continuation,
366 crc);
367 break;
368 }
369
370 case RP_DROP:
371 crc->api->drop (crc->api->cls);
372 GNUNET_SCHEDULER_add_now (&cleaning_task, crc);
373 break;
374 }
375}
376
377
378/**
379 * Load the datastore plugin.
380 */
381static struct GNUNET_DATASTORE_PluginFunctions *
382load_plugin (const struct GNUNET_CONFIGURATION_Handle *cfg)
383{
384 static struct GNUNET_DATASTORE_PluginEnvironment env;
385 struct GNUNET_DATASTORE_PluginFunctions *ret;
386 char *name;
387 char *libname;
388
389 if (GNUNET_OK !=
390 GNUNET_CONFIGURATION_get_value_string (cfg,
391 "DATASTORE",
392 "DATABASE",
393 &name))
394 {
395 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
396 _ ("No `%s' specified for `%s' in configuration!\n"),
397 "DATABASE",
398 "DATASTORE");
399 return NULL;
400 }
401 env.cfg = cfg;
402 env.duc = &disk_utilization_change_cb;
403 env.cls = NULL;
404 GNUNET_log (GNUNET_ERROR_TYPE_INFO, _ ("Loading `%s' datastore plugin\n"),
405 name);
406 GNUNET_asprintf (&libname, "libgnunet_plugin_datastore_%s", name);
407 if (NULL == (ret = GNUNET_PLUGIN_load (libname, &env)))
408 {
409 fprintf (stderr, "Failed to load plugin `%s'!\n", name);
410 GNUNET_free (libname);
411 GNUNET_free (name);
412 ok = 77; /* mark test as skipped */
413 return NULL;
414 }
415 GNUNET_free (libname);
416 GNUNET_free (name);
417 return ret;
418}
419
420
421static void
422run (void *cls, char *const *args, const char *cfgfile,
423 const struct GNUNET_CONFIGURATION_Handle *c)
424{
425 struct GNUNET_DATASTORE_PluginFunctions *api;
426 struct CpsRunContext *crc;
427
428 api = load_plugin (c);
429 if (api == NULL)
430 {
431 fprintf (stderr,
432 "%s",
433 "Could not initialize plugin, assuming database not configured. Test not run!\n");
434 return;
435 }
436 crc = GNUNET_new (struct CpsRunContext);
437 crc->api = api;
438 crc->cfg = c;
439 crc->phase = RP_PUT;
440 GNUNET_SCHEDULER_add_now (&test, crc);
441}
442
443
444int
445main (int argc, char *argv[])
446{
447 char dir_name[PATH_MAX];
448 char cfg_name[PATH_MAX];
449 char *const xargv[] = {
450 "test-plugin-datastore",
451 "-c",
452 cfg_name,
453 NULL
454 };
455 static struct GNUNET_GETOPT_CommandLineOption options[] = {
456 GNUNET_GETOPT_OPTION_END
457 };
458
459 /* determine name of plugin to use */
460 plugin_name = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
461 GNUNET_snprintf (dir_name, sizeof(dir_name),
462 "/tmp/test-gnunet-datastore-plugin-%s", plugin_name);
463 GNUNET_DISK_directory_remove (dir_name);
464 GNUNET_log_setup ("test-plugin-datastore",
465 "WARNING",
466 NULL);
467 GNUNET_snprintf (cfg_name, sizeof(cfg_name),
468 "test_plugin_datastore_data_%s.conf", plugin_name);
469 GNUNET_PROGRAM_run ((sizeof(xargv) / sizeof(char *)) - 1, xargv,
470 "test-plugin-datastore", "nohelp", options, &run, NULL);
471 if ((0 != ok) && (77 != ok))
472 fprintf (stderr, "Missed some testcases: %u\n", ok);
473 GNUNET_DISK_directory_remove (dir_name);
474 return ok;
475}
476
477
478/* end of test_plugin_datastore.c */
diff --git a/src/plugin/datastore/test_plugin_datastore_data_heap.conf b/src/plugin/datastore/test_plugin_datastore_data_heap.conf
new file mode 100644
index 000000000..b1ea8ff67
--- /dev/null
+++ b/src/plugin/datastore/test_plugin_datastore_data_heap.conf
@@ -0,0 +1,6 @@
1@INLINE@ test_defaults.conf
2[PATHS]
3GNUNET_TEST_HOME = $GNUNET_TMP/test-gnunet-datastore-plugin-heap/
4
5[datastore]
6DATABASE = heap
diff --git a/src/plugin/datastore/test_plugin_datastore_data_postgres.conf b/src/plugin/datastore/test_plugin_datastore_data_postgres.conf
new file mode 100644
index 000000000..d0e29437f
--- /dev/null
+++ b/src/plugin/datastore/test_plugin_datastore_data_postgres.conf
@@ -0,0 +1,10 @@
1@INLINE@ test_defaults.conf
2[PATHS]
3GNUNET_TEST_HOME = $GNUNET_TMP/test-gnunet-datastore-plugin-postgres/
4
5[datastore]
6DATABASE = postgres
7
8[datastore-postgres]
9CONFIG = dbname=gnunetcheck
10
diff --git a/src/plugin/datastore/test_plugin_datastore_data_sqlite.conf b/src/plugin/datastore/test_plugin_datastore_data_sqlite.conf
new file mode 100644
index 000000000..ca837c77a
--- /dev/null
+++ b/src/plugin/datastore/test_plugin_datastore_data_sqlite.conf
@@ -0,0 +1,4 @@
1@INLINE@ test_defaults.conf
2[PATHS]
3GNUNET_TEST_HOME = $GNUNET_TMP/test-gnunet-datastore-plugin-sqlite/
4
diff --git a/src/plugin/dht/Makefile.am b/src/plugin/dht/Makefile.am
new file mode 100644
index 000000000..4df810066
--- /dev/null
+++ b/src/plugin/dht/Makefile.am
@@ -0,0 +1,27 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4plugindir = $(libdir)/gnunet
5
6pkgcfgdir= $(pkgdatadir)/config.d/
7
8libexecdir= $(pkglibdir)/libexec/
9
10if USE_COVERAGE
11 AM_CFLAGS = --coverage -O0
12 XLIB = -lgcov
13endif
14
15plugin_LTLIBRARIES = \
16 libgnunet_plugin_block_dht.la
17
18libgnunet_plugin_block_dht_la_SOURCES = \
19 plugin_block_dht.c
20libgnunet_plugin_block_dht_la_LIBADD = \
21 $(top_builddir)/src/lib/hello/libgnunethello.la \
22 $(top_builddir)/src/lib/block/libgnunetblock.la \
23 $(top_builddir)/src/lib/block/libgnunetblockgroup.la \
24 $(top_builddir)/src/lib/util/libgnunetutil.la \
25 $(LTLIBINTL)
26libgnunet_plugin_block_dht_la_LDFLAGS = \
27 $(GN_PLUGIN_LDFLAGS)
diff --git a/src/plugin/dht/meson.build b/src/plugin/dht/meson.build
new file mode 100644
index 000000000..81b829dac
--- /dev/null
+++ b/src/plugin/dht/meson.build
@@ -0,0 +1,9 @@
1shared_module('gnunet_plugin_block_dht',
2 ['plugin_block_dht.c'],
3 dependencies: [libgnunetutil_dep,
4 libgnunethello_dep,
5 libgnunetblock_dep,
6 libgnunetblockgroup_dep],
7 include_directories: [incdir, configuration_inc],
8 install:true,
9 install_dir: get_option('libdir')/'gnunet')
diff --git a/src/plugin/dht/plugin_block_dht.c b/src/plugin/dht/plugin_block_dht.c
new file mode 100644
index 000000000..ede23ea57
--- /dev/null
+++ b/src/plugin/dht/plugin_block_dht.c
@@ -0,0 +1,308 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2010, 2017, 2022 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file dht/plugin_block_dht.c
23 * @brief block plugin for DHT internals (right now, find-peer requests only);
24 * other plugins should be used to store "useful" data in the
25 * DHT (see fs block plugin)
26 * @author Christian Grothoff
27 */
28#include "platform.h"
29#include "gnunet_constants.h"
30#include "gnunet_hello_uri_lib.h"
31#include "gnunet_block_plugin.h"
32#include "gnunet_block_group_lib.h"
33
34#define DEBUG_DHT GNUNET_EXTRA_LOGGING
35
36/**
37 * Number of bits we set per entry in the bloomfilter.
38 * Do not change!
39 */
40#define BLOOMFILTER_K 16
41
42
43/**
44 * Create a new block group.
45 *
46 * @param ctx block context in which the block group is created
47 * @param type type of the block for which we are creating the group
48 * @param raw_data optional serialized prior state of the group, NULL if unavailable/fresh
49 * @param raw_data_size number of bytes in @a raw_data, 0 if unavailable/fresh
50 * @param va variable arguments specific to @a type
51 * @return block group handle, NULL if block groups are not supported
52 * by this @a type of block (this is not an error)
53 */
54static struct GNUNET_BLOCK_Group *
55block_plugin_dht_create_group (void *cls,
56 enum GNUNET_BLOCK_Type type,
57 const void *raw_data,
58 size_t raw_data_size,
59 va_list va)
60{
61 unsigned int bf_size;
62 const char *guard;
63
64 guard = va_arg (va, const char *);
65 if (0 == strcmp (guard,
66 "seen-set-size"))
67 bf_size = GNUNET_BLOCK_GROUP_compute_bloomfilter_size (va_arg (va,
68 unsigned int),
69 BLOOMFILTER_K);
70 else if (0 == strcmp (guard,
71 "filter-size"))
72 bf_size = va_arg (va, unsigned int);
73 else
74 {
75 GNUNET_break (0);
76 bf_size = 8;
77 }
78 GNUNET_break (NULL == va_arg (va, const char *));
79 return GNUNET_BLOCK_GROUP_bf_create (cls,
80 bf_size,
81 BLOOMFILTER_K,
82 type,
83 raw_data,
84 raw_data_size);
85}
86
87
88/**
89 * Function called to validate a query.
90 *
91 * @param cls closure
92 * @param type block type
93 * @param query original query (hash)
94 * @param xquery extrended query data (can be NULL, depending on type)
95 * @param xquery_size number of bytes in @a xquery
96 * @return #GNUNET_OK if the query is fine, #GNUNET_NO if not
97 */
98static enum GNUNET_GenericReturnValue
99block_plugin_dht_check_query (void *cls,
100 enum GNUNET_BLOCK_Type type,
101 const struct GNUNET_HashCode *query,
102 const void *xquery,
103 size_t xquery_size)
104{
105 switch (type)
106 {
107 case GNUNET_BLOCK_TYPE_DHT_HELLO:
108 if (0 != xquery_size)
109 {
110 GNUNET_break_op (0);
111 return GNUNET_NO;
112 }
113 return GNUNET_OK;
114 default:
115 GNUNET_break (0);
116 return GNUNET_SYSERR;
117 }
118}
119
120
121/**
122 * Function called to validate a block for storage.
123 *
124 * @param cls closure
125 * @param type block type
126 * @param block block data to validate
127 * @param block_size number of bytes in @a block
128 * @return #GNUNET_OK if the block is fine, #GNUNET_NO if not
129 */
130static enum GNUNET_GenericReturnValue
131block_plugin_dht_check_block (void *cls,
132 enum GNUNET_BLOCK_Type type,
133 const void *block,
134 size_t block_size)
135{
136 switch (type)
137 {
138 case GNUNET_BLOCK_TYPE_DHT_HELLO:
139 {
140 struct GNUNET_HELLO_Builder *b;
141 const struct GNUNET_PeerIdentity *pid;
142 struct GNUNET_HashCode h_pid;
143
144 b = GNUNET_HELLO_builder_from_block (block,
145 block_size);
146 if (NULL == b)
147 {
148 GNUNET_break (0);
149 return GNUNET_NO;
150 }
151 pid = GNUNET_HELLO_builder_iterate (b,
152 NULL, NULL);
153 GNUNET_CRYPTO_hash (pid,
154 sizeof (*pid),
155 &h_pid);
156 GNUNET_HELLO_builder_free (b);
157 return GNUNET_OK;
158 }
159 default:
160 GNUNET_break (0);
161 return GNUNET_SYSERR;
162 }
163}
164
165
166/**
167 * Function called to validate a reply to a request. Note that it is assumed
168 * that the reply has already been matched to the key (and signatures checked)
169 * as it would be done with the GetKeyFunction and the
170 * BlockEvaluationFunction.
171 *
172 * @param cls closure
173 * @param type block type
174 * @param group which block group to use for evaluation
175 * @param query original query (hash)
176 * @param xquery extrended query data (can be NULL, depending on type)
177 * @param xquery_size number of bytes in @a xquery
178 * @param reply_block response to validate
179 * @param reply_block_size number of bytes in @a reply_block
180 * @return characterization of result
181 */
182static enum GNUNET_BLOCK_ReplyEvaluationResult
183block_plugin_dht_check_reply (
184 void *cls,
185 enum GNUNET_BLOCK_Type type,
186 struct GNUNET_BLOCK_Group *group,
187 const struct GNUNET_HashCode *query,
188 const void *xquery,
189 size_t xquery_size,
190 const void *reply_block,
191 size_t reply_block_size)
192{
193 switch (type)
194 {
195 case GNUNET_BLOCK_TYPE_DHT_HELLO:
196 {
197 struct GNUNET_HELLO_Builder *b;
198 const struct GNUNET_PeerIdentity *pid;
199 struct GNUNET_HashCode h_pid;
200
201 b = GNUNET_HELLO_builder_from_block (reply_block,
202 reply_block_size);
203 GNUNET_assert (NULL != b);
204 pid = GNUNET_HELLO_builder_iterate (b,
205 NULL, NULL);
206 GNUNET_CRYPTO_hash (pid,
207 sizeof (*pid),
208 &h_pid);
209 GNUNET_HELLO_builder_free (b);
210 if (GNUNET_YES ==
211 GNUNET_BLOCK_GROUP_bf_test_and_set (group,
212 &h_pid))
213 return GNUNET_BLOCK_REPLY_OK_DUPLICATE;
214 return GNUNET_BLOCK_REPLY_OK_MORE;
215 }
216 default:
217 return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
218 }
219}
220
221
222/**
223 * Function called to obtain the key for a block.
224 *
225 * @param cls closure
226 * @param type block type
227 * @param block block to get the key for
228 * @param block_size number of bytes @a block
229 * @param[out] key set to the key (query) for the given block
230 * @return #GNUNET_OK on success, #GNUNET_SYSERR if type not supported
231 * (or if extracting a key from a block of this type does not work)
232 */
233static enum GNUNET_GenericReturnValue
234block_plugin_dht_get_key (void *cls,
235 enum GNUNET_BLOCK_Type type,
236 const void *block,
237 size_t block_size,
238 struct GNUNET_HashCode *key)
239{
240 switch (type)
241 {
242 case GNUNET_BLOCK_TYPE_DHT_HELLO:
243 {
244 struct GNUNET_HELLO_Builder *b;
245 const struct GNUNET_PeerIdentity *pid;
246
247 b = GNUNET_HELLO_builder_from_block (block,
248 block_size);
249 if (NULL == b)
250 {
251 GNUNET_break (0);
252 memset (key,
253 0,
254 sizeof (*key));
255 return GNUNET_OK;
256 }
257 pid = GNUNET_HELLO_builder_iterate (b,
258 NULL, NULL);
259 GNUNET_CRYPTO_hash (pid,
260 sizeof (*pid),
261 key);
262 GNUNET_HELLO_builder_free (b);
263 return GNUNET_OK;
264 }
265 default:
266 GNUNET_break (0);
267 return GNUNET_SYSERR;
268 }
269}
270
271
272/**
273 * Entry point for the plugin.
274 */
275void *
276libgnunet_plugin_block_dht_init (void *cls)
277{
278 static enum GNUNET_BLOCK_Type types[] = {
279 GNUNET_BLOCK_TYPE_DHT_HELLO,
280 GNUNET_BLOCK_TYPE_ANY /* end of list */
281 };
282 struct GNUNET_BLOCK_PluginFunctions *api;
283
284 api = GNUNET_new (struct GNUNET_BLOCK_PluginFunctions);
285 api->get_key = &block_plugin_dht_get_key;
286 api->check_query = &block_plugin_dht_check_query;
287 api->check_block = &block_plugin_dht_check_block;
288 api->check_reply = &block_plugin_dht_check_reply;
289 api->create_group = &block_plugin_dht_create_group;
290 api->types = types;
291 return api;
292}
293
294
295/**
296 * Exit point from the plugin.
297 */
298void *
299libgnunet_plugin_block_dht_done (void *cls)
300{
301 struct GNUNET_BLOCK_PluginFunctions *api = cls;
302
303 GNUNET_free (api);
304 return NULL;
305}
306
307
308/* end of plugin_block_dht.c */
diff --git a/src/plugin/dns/Makefile.am b/src/plugin/dns/Makefile.am
new file mode 100644
index 000000000..8b5843159
--- /dev/null
+++ b/src/plugin/dns/Makefile.am
@@ -0,0 +1,25 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4if USE_COVERAGE
5 AM_CFLAGS = --coverage -O0
6endif
7
8pkgcfgdir= $(pkgdatadir)/config.d/
9
10libexecdir= $(pkglibdir)/libexec/
11
12plugindir = $(libdir)/gnunet
13
14plugin_LTLIBRARIES = \
15 libgnunet_plugin_block_dns.la
16
17libgnunet_plugin_block_dns_la_SOURCES = \
18 plugin_block_dns.c
19libgnunet_plugin_block_dns_la_LIBADD = \
20 $(top_builddir)/src/lib/block/libgnunetblockgroup.la \
21 $(top_builddir)/src/lib/block/libgnunetblock.la \
22 $(top_builddir)/src/lib/util/libgnunetutil.la
23libgnunet_plugin_block_dns_la_LDFLAGS = \
24 $(GN_LIBINTL) \
25 $(top_builddir)/src/block/$(GN_PLUGIN_LDFLAGS)
diff --git a/src/plugin/dns/meson.build b/src/plugin/dns/meson.build
new file mode 100644
index 000000000..974b2c38d
--- /dev/null
+++ b/src/plugin/dns/meson.build
@@ -0,0 +1,6 @@
1shared_module('gnunet_plugin_block_dns',
2 ['plugin_block_dns.c'],
3 dependencies: [libgnunetutil_dep, libgnunetblockgroup_dep],
4 include_directories: [incdir, configuration_inc],
5 install: true,
6 install_dir: get_option('libdir')/'gnunet')
diff --git a/src/plugin/dns/plugin_block_dns.c b/src/plugin/dns/plugin_block_dns.c
new file mode 100644
index 000000000..1bbd7f750
--- /dev/null
+++ b/src/plugin/dns/plugin_block_dns.c
@@ -0,0 +1,290 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013, 2017 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file dns/plugin_block_dns.c
23 * @brief block plugin for advertising a DNS exit service
24 * @author Christian Grothoff
25 *
26 * Note that this plugin might more belong with EXIT and PT
27 * as those two are using this type of block. Still, this
28 * might be a natural enough place for people to find the code...
29 */
30#include "platform.h"
31#include "gnunet_block_plugin.h"
32#include "block_dns.h"
33#include "gnunet_signatures.h"
34#include "gnunet_block_group_lib.h"
35
36
37/**
38 * Number of bits we set per entry in the bloomfilter.
39 * Do not change!
40 */
41#define BLOOMFILTER_K 16
42
43
44/**
45 * Create a new block group.
46 *
47 * @param ctx block context in which the block group is created
48 * @param type type of the block for which we are creating the group
49 * @param raw_data optional serialized prior state of the group, NULL if unavailable/fresh
50 * @param raw_data_size number of bytes in @a raw_data, 0 if unavailable/fresh
51 * @param va variable arguments specific to @a type
52 * @return block group handle, NULL if block groups are not supported
53 * by this @a type of block (this is not an error)
54 */
55static struct GNUNET_BLOCK_Group *
56block_plugin_dns_create_group (void *cls,
57 enum GNUNET_BLOCK_Type type,
58 const void *raw_data,
59 size_t raw_data_size,
60 va_list va)
61{
62 unsigned int bf_size;
63 const char *guard;
64
65 guard = va_arg (va, const char *);
66 if (0 == strcmp (guard,
67 "seen-set-size"))
68 bf_size = GNUNET_BLOCK_GROUP_compute_bloomfilter_size (va_arg (va, unsigned
69 int),
70 BLOOMFILTER_K);
71 else if (0 == strcmp (guard,
72 "filter-size"))
73 bf_size = va_arg (va, unsigned int);
74 else
75 {
76 GNUNET_break (0);
77 bf_size = 8;
78 }
79 GNUNET_break (NULL == va_arg (va, const char *));
80 return GNUNET_BLOCK_GROUP_bf_create (cls,
81 bf_size,
82 BLOOMFILTER_K,
83 type,
84 raw_data,
85 raw_data_size);
86}
87
88
89/**
90 * Function called to validate a query.
91 *
92 * @param cls closure
93 * @param type block type
94 * @param query original query (hash)
95 * @param xquery extrended query data (can be NULL, depending on type)
96 * @param xquery_size number of bytes in @a xquery
97 * @return #GNUNET_OK if the query is fine, #GNUNET_NO if not
98 */
99static enum GNUNET_GenericReturnValue
100block_plugin_dns_check_query (void *cls,
101 enum GNUNET_BLOCK_Type type,
102 const struct GNUNET_HashCode *query,
103 const void *xquery,
104 size_t xquery_size)
105{
106 switch (type)
107 {
108 case GNUNET_BLOCK_TYPE_DNS:
109 if (0 != xquery_size)
110 {
111 GNUNET_break_op (0);
112 return GNUNET_NO;
113 }
114 return GNUNET_OK;
115 default:
116 GNUNET_break (0);
117 return GNUNET_SYSERR;
118 }
119}
120
121
122/**
123 * Function called to validate a block for storage.
124 *
125 * @param cls closure
126 * @param type block type
127 * @param block block data to validate
128 * @param block_size number of bytes in @a block
129 * @return #GNUNET_OK if the block is fine, #GNUNET_NO if not
130 */
131static enum GNUNET_GenericReturnValue
132block_plugin_dns_check_block (void *cls,
133 enum GNUNET_BLOCK_Type type,
134 const void *block,
135 size_t block_size)
136{
137 const struct GNUNET_DNS_Advertisement *ad;
138
139 switch (type)
140 {
141 case GNUNET_BLOCK_TYPE_DNS:
142 if (sizeof(struct GNUNET_DNS_Advertisement) != block_size)
143 {
144 GNUNET_break_op (0);
145 return GNUNET_NO;
146 }
147 ad = block;
148
149 if (ntohl (ad->purpose.size) !=
150 sizeof(struct GNUNET_DNS_Advertisement)
151 - sizeof(struct GNUNET_CRYPTO_EddsaSignature))
152 {
153 GNUNET_break_op (0);
154 return GNUNET_NO;
155 }
156 if (GNUNET_TIME_absolute_is_past (
157 GNUNET_TIME_absolute_ntoh (ad->expiration_time)))
158 {
159 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
160 "DNS advertisement has expired\n");
161 return GNUNET_NO;
162 }
163 if (GNUNET_OK !=
164 GNUNET_CRYPTO_eddsa_verify_ (GNUNET_SIGNATURE_PURPOSE_DNS_RECORD,
165 &ad->purpose,
166 &ad->signature,
167 &ad->peer.public_key))
168 {
169 GNUNET_break_op (0);
170 return GNUNET_NO;
171 }
172 return GNUNET_OK;
173 default:
174 GNUNET_break (0);
175 return GNUNET_SYSERR;
176 }
177}
178
179
180/**
181 * Function called to validate a reply to a request. Note that it is assumed
182 * that the reply has already been matched to the key (and signatures checked)
183 * as it would be done with the GetKeyFunction and the
184 * BlockEvaluationFunction.
185 *
186 * @param cls closure
187 * @param type block type
188 * @param group which block group to use for evaluation
189 * @param query original query (hash)
190 * @param xquery extrended query data (can be NULL, depending on type)
191 * @param xquery_size number of bytes in @a xquery
192 * @param reply_block response to validate
193 * @param reply_block_size number of bytes in @a reply_block
194 * @return characterization of result
195 */
196static enum GNUNET_BLOCK_ReplyEvaluationResult
197block_plugin_dns_check_reply (
198 void *cls,
199 enum GNUNET_BLOCK_Type type,
200 struct GNUNET_BLOCK_Group *group,
201 const struct GNUNET_HashCode *query,
202 const void *xquery,
203 size_t xquery_size,
204 const void *reply_block,
205 size_t reply_block_size)
206{
207 struct GNUNET_HashCode phash;
208
209 switch (type)
210 {
211 case GNUNET_BLOCK_TYPE_DNS:
212 GNUNET_CRYPTO_hash (reply_block,
213 reply_block_size,
214 &phash);
215 if (GNUNET_YES ==
216 GNUNET_BLOCK_GROUP_bf_test_and_set (group,
217 &phash))
218 return GNUNET_BLOCK_REPLY_OK_DUPLICATE;
219 return GNUNET_BLOCK_REPLY_OK_MORE;
220 default:
221 GNUNET_break (0);
222 return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
223 }
224}
225
226
227/**
228 * Function called to obtain the key for a block.
229 *
230 * @param cls closure
231 * @param type block type
232 * @param block block to get the key for
233 * @param block_size number of bytes in @a block
234 * @param key set to the key (query) for the given block
235 * @return #GNUNET_OK on success, #GNUNET_SYSERR if type not supported
236 * (or if extracting a key from a block of this type does not work)
237 */
238static enum GNUNET_GenericReturnValue
239block_plugin_dns_get_key (void *cls,
240 enum GNUNET_BLOCK_Type type,
241 const void *block,
242 size_t block_size,
243 struct GNUNET_HashCode *key)
244{
245 if (GNUNET_BLOCK_TYPE_DNS != type)
246 {
247 GNUNET_break (0);
248 return GNUNET_SYSERR;
249 }
250 return GNUNET_NO;
251}
252
253
254/**
255 * Entry point for the plugin.
256 */
257void *
258libgnunet_plugin_block_dns_init (void *cls)
259{
260 static enum GNUNET_BLOCK_Type types[] = {
261 GNUNET_BLOCK_TYPE_DNS,
262 GNUNET_BLOCK_TYPE_ANY /* end of list */
263 };
264 struct GNUNET_BLOCK_PluginFunctions *api;
265
266 api = GNUNET_new (struct GNUNET_BLOCK_PluginFunctions);
267 api->get_key = &block_plugin_dns_get_key;
268 api->check_query = &block_plugin_dns_check_query;
269 api->check_block = &block_plugin_dns_check_block;
270 api->check_reply = &block_plugin_dns_check_reply;
271 api->create_group = &block_plugin_dns_create_group;
272 api->types = types;
273 return api;
274}
275
276
277/**
278 * Exit point from the plugin.
279 */
280void *
281libgnunet_plugin_block_dns_done (void *cls)
282{
283 struct GNUNET_BLOCK_PluginFunctions *api = cls;
284
285 GNUNET_free (api);
286 return NULL;
287}
288
289
290/* end of plugin_block_dns.c */
diff --git a/src/plugin/fs/Makefile.am b/src/plugin/fs/Makefile.am
new file mode 100644
index 000000000..7c0e55cba
--- /dev/null
+++ b/src/plugin/fs/Makefile.am
@@ -0,0 +1,35 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4if USE_COVERAGE
5 AM_CFLAGS = --coverage -O0
6 XLIB = -lgcov
7endif
8
9pkgcfgdir= $(pkgdatadir)/config.d/
10
11libexecdir= $(pkglibdir)/libexec/
12
13plugindir = $(libdir)/gnunet
14
15plugin_LTLIBRARIES = \
16 libgnunet_plugin_block_fs.la
17
18libgnunet_plugin_block_fs_la_SOURCES = \
19 plugin_block_fs.c
20libgnunet_plugin_block_fs_la_LIBADD = \
21 $(top_builddir)/src/lib/block/libgnunetblockgroup.la \
22 $(top_builddir)/src/lib/block/libgnunetblock.la \
23 $(top_builddir)/src/lib/util/libgnunetutil.la \
24 $(LTLIBINTL)
25libgnunet_plugin_block_fs_la_LDFLAGS = \
26 $(GN_PLUGIN_LDFLAGS)
27
28check_PROGRAMS = \
29 test_plugin_block_fs
30
31test_plugin_block_fs_SOURCES = \
32 test_plugin_block_fs.c
33test_plugin_block_fs_LDADD = \
34 $(top_builddir)/src/lib/block/libgnunetblock.la \
35 $(top_builddir)/src/lib/util/libgnunetutil.la
diff --git a/src/plugin/fs/meson.build b/src/plugin/fs/meson.build
new file mode 100644
index 000000000..7978070f9
--- /dev/null
+++ b/src/plugin/fs/meson.build
@@ -0,0 +1,8 @@
1shared_module('gnunet_plugin_block_fs',
2 ['plugin_block_fs.c'],
3 dependencies: [libgnunetutil_dep,
4 libgnunetblockgroup_dep],
5 include_directories: [incdir, configuration_inc],
6 install:true,
7 install_dir: get_option('libdir')/'gnunet')
8
diff --git a/src/plugin/fs/plugin_block_fs.c b/src/plugin/fs/plugin_block_fs.c
new file mode 100644
index 000000000..bbd0ff57b
--- /dev/null
+++ b/src/plugin/fs/plugin_block_fs.c
@@ -0,0 +1,337 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2010, 2013, 2021 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file fs/plugin_block_fs.c
23 * @brief blocks used for file-sharing
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_block_plugin.h"
28
29#include "gnunet_fs_service.h"
30#include "block_fs.h"
31#include "gnunet_signatures.h"
32#include "gnunet_block_group_lib.h"
33
34
35/**
36 * Number of bits we set per entry in the bloomfilter.
37 * Do not change!
38 */
39#define BLOOMFILTER_K 16
40
41
42/**
43 * Create a new block group.
44 *
45 * @param ctx block context in which the block group is created
46 * @param type type of the block for which we are creating the group
47 * @param raw_data optional serialized prior state of the group, NULL if unavailable/fresh
48 * @param raw_data_size number of bytes in @a raw_data, 0 if unavailable/fresh
49 * @param va variable arguments specific to @a type
50 * @return block group handle, NULL if block groups are not supported
51 * by this @a type of block (this is not an error)
52 */
53static struct GNUNET_BLOCK_Group *
54block_plugin_fs_create_group (void *cls,
55 enum GNUNET_BLOCK_Type type,
56 const void *raw_data,
57 size_t raw_data_size,
58 va_list va)
59{
60 unsigned int size;
61 const char *guard;
62
63 switch (type)
64 {
65 case GNUNET_BLOCK_TYPE_FS_DBLOCK:
66 GNUNET_break (NULL == va_arg (va, const char *));
67 return NULL;
68 case GNUNET_BLOCK_TYPE_FS_IBLOCK:
69 GNUNET_break (NULL == va_arg (va, const char *));
70 return NULL;
71 case GNUNET_BLOCK_TYPE_FS_UBLOCK:
72 guard = va_arg (va, const char *);
73 if (0 == strcmp (guard,
74 "seen-set-size"))
75 {
76 size = GNUNET_BLOCK_GROUP_compute_bloomfilter_size (va_arg (va, unsigned
77 int),
78 BLOOMFILTER_K);
79 }
80 else if (0 == strcmp (guard,
81 "filter-size"))
82 {
83 size = va_arg (va, unsigned int);
84 }
85 else
86 {
87 /* va-args invalid! bad bug, complain! */
88 GNUNET_break (0);
89 size = 8;
90 }
91 if (0 == size)
92 size = raw_data_size; /* not for us to determine, use what we got! */
93 GNUNET_break (NULL == va_arg (va, const char *));
94 return GNUNET_BLOCK_GROUP_bf_create (cls,
95 size,
96 BLOOMFILTER_K,
97 type,
98 raw_data,
99 raw_data_size);
100
101 default:
102 GNUNET_break (NULL == va_arg (va, const char *));
103 GNUNET_break (0);
104 return NULL;
105 }
106}
107
108
109/**
110 * Function called to obtain the key for a block.
111 *
112 * @param cls closure
113 * @param type block type
114 * @param block block to get the key for
115 * @param block_size number of bytes in @a block
116 * @param key set to the key (query) for the given block
117 * @return #GNUNET_OK on success, #GNUNET_SYSERR if type not supported
118 * (or if extracting a key from a block of this type does not work)
119 */
120static enum GNUNET_GenericReturnValue
121block_plugin_fs_get_key (void *cls,
122 enum GNUNET_BLOCK_Type type,
123 const void *block,
124 size_t block_size,
125 struct GNUNET_HashCode *key)
126{
127 const struct UBlock *ub;
128
129 switch (type)
130 {
131 case GNUNET_BLOCK_TYPE_FS_DBLOCK:
132 case GNUNET_BLOCK_TYPE_FS_IBLOCK:
133 GNUNET_CRYPTO_hash (block,
134 block_size,
135 key);
136 return GNUNET_OK;
137 case GNUNET_BLOCK_TYPE_FS_UBLOCK:
138 if (block_size < sizeof(struct UBlock))
139 {
140 GNUNET_break_op (0);
141 memset (key,
142 0,
143 sizeof (*key));
144 return GNUNET_OK;
145 }
146 ub = block;
147 GNUNET_CRYPTO_hash (&ub->verification_key,
148 sizeof(ub->verification_key),
149 key);
150 return GNUNET_OK;
151 default:
152 GNUNET_break (0);
153 return GNUNET_SYSERR;
154 }
155}
156
157
158/**
159 * Function called to validate a query.
160 *
161 * @param cls closure
162 * @param type block type
163 * @param query original query (hash)
164 * @param xquery extended query data (can be NULL, depending on type)
165 * @param xquery_size number of bytes in @a xquery
166 * @return #GNUNET_OK if the query is fine, #GNUNET_NO if not
167 */
168static enum GNUNET_GenericReturnValue
169block_plugin_fs_check_query (void *cls,
170 enum GNUNET_BLOCK_Type type,
171 const struct GNUNET_HashCode *query,
172 const void *xquery,
173 size_t xquery_size)
174{
175 switch (type)
176 {
177 case GNUNET_BLOCK_TYPE_FS_DBLOCK:
178 case GNUNET_BLOCK_TYPE_FS_IBLOCK:
179 case GNUNET_BLOCK_TYPE_FS_UBLOCK:
180 if (0 != xquery_size)
181 {
182 GNUNET_break_op (0);
183 return GNUNET_NO;
184 }
185 return GNUNET_OK;
186 default:
187 GNUNET_break (0);
188 return GNUNET_SYSERR;
189 }
190}
191
192
193/**
194 * Function called to validate a block for storage.
195 *
196 * @param cls closure
197 * @param type block type
198 * @param block block data to validate
199 * @param block_size number of bytes in @a block
200 * @return #GNUNET_OK if the block is fine, #GNUNET_NO if not
201 */
202static enum GNUNET_GenericReturnValue
203block_plugin_fs_check_block (void *cls,
204 enum GNUNET_BLOCK_Type type,
205 const void *block,
206 size_t block_size)
207{
208 switch (type)
209 {
210 case GNUNET_BLOCK_TYPE_FS_DBLOCK:
211 case GNUNET_BLOCK_TYPE_FS_IBLOCK:
212 return GNUNET_OK;
213 case GNUNET_BLOCK_TYPE_FS_UBLOCK:
214 {
215 const struct UBlock *ub;
216
217 if (block_size < sizeof(struct UBlock))
218 {
219 GNUNET_break_op (0);
220 return GNUNET_NO;
221 }
222 ub = block;
223 if (block_size !=
224 ntohl (ub->purpose.size)
225 + sizeof (struct GNUNET_CRYPTO_EcdsaSignature))
226 {
227 GNUNET_break_op (0);
228 return GNUNET_NO;
229 }
230 if (GNUNET_OK !=
231 GNUNET_CRYPTO_ecdsa_verify_ (GNUNET_SIGNATURE_PURPOSE_FS_UBLOCK,
232 &ub->purpose,
233 &ub->signature,
234 &ub->verification_key))
235 {
236 GNUNET_break_op (0);
237 return GNUNET_NO;
238 }
239 return GNUNET_OK;
240 }
241 default:
242 GNUNET_break (0);
243 return GNUNET_SYSERR;
244 }
245}
246
247
248/**
249 * Function called to validate a reply to a request. Note that it is assumed
250 * that the reply has already been matched to the key (and signatures checked)
251 * as it would be done with the GetKeyFunction and the
252 * BlockEvaluationFunction.
253 *
254 * @param cls closure
255 * @param type block type
256 * @param group which block group to use for evaluation
257 * @param query original query (hash)
258 * @param xquery extrended query data (can be NULL, depending on type)
259 * @param xquery_size number of bytes in @a xquery
260 * @param reply_block response to validate
261 * @param reply_block_size number of bytes in @a reply_block
262 * @return characterization of result
263 */
264static enum GNUNET_BLOCK_ReplyEvaluationResult
265block_plugin_fs_check_reply (void *cls,
266 enum GNUNET_BLOCK_Type type,
267 struct GNUNET_BLOCK_Group *group,
268 const struct GNUNET_HashCode *query,
269 const void *xquery,
270 size_t xquery_size,
271 const void *reply_block,
272 size_t reply_block_size)
273{
274 switch (type)
275 {
276 case GNUNET_BLOCK_TYPE_FS_DBLOCK:
277 case GNUNET_BLOCK_TYPE_FS_IBLOCK:
278 return GNUNET_BLOCK_REPLY_OK_LAST;
279 case GNUNET_BLOCK_TYPE_FS_UBLOCK:
280 {
281 struct GNUNET_HashCode chash;
282
283 GNUNET_CRYPTO_hash (reply_block,
284 reply_block_size,
285 &chash);
286 if (GNUNET_YES ==
287 GNUNET_BLOCK_GROUP_bf_test_and_set (group,
288 &chash))
289 return GNUNET_BLOCK_REPLY_OK_DUPLICATE;
290 return GNUNET_BLOCK_REPLY_OK_MORE;
291 }
292 default:
293 GNUNET_break (0);
294 return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
295 }
296}
297
298
299/**
300 * Entry point for the plugin.
301 */
302void *
303libgnunet_plugin_block_fs_init (void *cls)
304{
305 static const enum GNUNET_BLOCK_Type types[] = {
306 GNUNET_BLOCK_TYPE_FS_DBLOCK,
307 GNUNET_BLOCK_TYPE_FS_IBLOCK,
308 GNUNET_BLOCK_TYPE_FS_UBLOCK,
309 GNUNET_BLOCK_TYPE_ANY /* end of list */
310 };
311 struct GNUNET_BLOCK_PluginFunctions *api;
312
313 api = GNUNET_new (struct GNUNET_BLOCK_PluginFunctions);
314 api->get_key = &block_plugin_fs_get_key;
315 api->create_group = &block_plugin_fs_create_group;
316 api->check_query = &block_plugin_fs_check_query;
317 api->check_block = &block_plugin_fs_check_block;
318 api->check_reply = &block_plugin_fs_check_reply;
319 api->types = types;
320 return api;
321}
322
323
324/**
325 * Exit point from the plugin.
326 */
327void *
328libgnunet_plugin_block_fs_done (void *cls)
329{
330 struct GNUNET_BLOCK_PluginFunctions *api = cls;
331
332 GNUNET_free (api);
333 return NULL;
334}
335
336
337/* end of plugin_block_fs.c */
diff --git a/src/plugin/fs/test_plugin_block_fs.c b/src/plugin/fs/test_plugin_block_fs.c
new file mode 100644
index 000000000..f15d10b17
--- /dev/null
+++ b/src/plugin/fs/test_plugin_block_fs.c
@@ -0,0 +1,86 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2010, 2012 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @file fs/test_plugin_block_fs.c
22 * @brief test for plugin_block_fs.c
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_block_lib.h"
27
28
29static int
30test_fs (struct GNUNET_BLOCK_Context *ctx)
31{
32 struct GNUNET_HashCode key;
33 char block[4];
34
35 memset (block, 1, sizeof(block));
36 if (GNUNET_OK !=
37 GNUNET_BLOCK_get_key (ctx,
38 GNUNET_BLOCK_TYPE_FS_DBLOCK,
39 block,
40 sizeof(block),
41 &key))
42 return 1;
43 if (GNUNET_OK !=
44 GNUNET_BLOCK_check_block (ctx,
45 GNUNET_BLOCK_TYPE_FS_DBLOCK,
46 block,
47 sizeof(block)))
48 return 2;
49 if (GNUNET_OK !=
50 GNUNET_BLOCK_check_query (ctx,
51 GNUNET_BLOCK_TYPE_FS_DBLOCK,
52 &key,
53 NULL, 0))
54 return 4;
55 GNUNET_log_skip (1, GNUNET_NO);
56 if (GNUNET_NO !=
57 GNUNET_BLOCK_check_query (ctx,
58 GNUNET_BLOCK_TYPE_FS_DBLOCK,
59 &key,
60 "bogus", 5))
61 return 8;
62 GNUNET_log_skip (0, GNUNET_YES);
63 return 0;
64}
65
66
67int
68main (int argc, char *argv[])
69{
70 int ret;
71 struct GNUNET_BLOCK_Context *ctx;
72 struct GNUNET_CONFIGURATION_Handle *cfg;
73
74 GNUNET_log_setup ("test-block", "WARNING", NULL);
75 cfg = GNUNET_CONFIGURATION_create ();
76 ctx = GNUNET_BLOCK_context_create (cfg);
77 ret = test_fs (ctx);
78 GNUNET_BLOCK_context_destroy (ctx);
79 GNUNET_CONFIGURATION_destroy (cfg);
80 if (ret != 0)
81 fprintf (stderr, "Tests failed: %d\n", ret);
82 return ret;
83}
84
85
86/* end of test_plugin_block_fs.c */
diff --git a/src/plugin/gns/Makefile.am b/src/plugin/gns/Makefile.am
new file mode 100644
index 000000000..8dc9ef2b2
--- /dev/null
+++ b/src/plugin/gns/Makefile.am
@@ -0,0 +1,59 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4if HAVE_LIBIDN
5 LIBIDN= -lidn
6else
7 LIBIDN=
8endif
9
10if HAVE_LIBIDN2
11 LIBIDN2= -lidn2
12else
13 LIBIDN2=
14endif
15
16USE_VPN = $(top_builddir)/src/service/vpn/libgnunetvpn.la
17
18if USE_COVERAGE
19 AM_CFLAGS = --coverage -O0
20endif
21
22pkgcfgdir = $(pkgdatadir)/config.d/
23
24libexecdir= $(pkglibdir)/libexec/
25
26plugindir = $(libdir)/gnunet
27
28plugin_LTLIBRARIES = \
29 libgnunet_plugin_block_gns.la \
30 libgnunet_plugin_gnsrecord_gns.la
31
32
33libgnunet_plugin_gnsrecord_gns_la_SOURCES = \
34 plugin_gnsrecord_gns.c
35libgnunet_plugin_gnsrecord_gns_la_LIBADD = \
36 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
37 $(top_builddir)/src/lib/util/libgnunetutil.la \
38 $(LTLIBINTL)
39libgnunet_plugin_gnsrecord_gns_la_LDFLAGS = \
40 $(GN_PLUGIN_LDFLAGS)
41
42
43libgnunet_plugin_block_gns_la_SOURCES = \
44 plugin_block_gns.c
45libgnunet_plugin_block_gns_la_LIBADD = \
46 $(top_builddir)/src/lib/util/libgnunetutil.la \
47 $(top_builddir)/src/lib/block/libgnunetblock.la \
48 $(top_builddir)/src/lib/block/libgnunetblockgroup.la \
49 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la
50libgnunet_plugin_block_gns_la_LDFLAGS = \
51 $(GN_LIBINTL) \
52 $(GN_PLUGIN_LDFLAGS)
53
54if ENABLE_TEST_RUN
55if HAVE_SQLITE
56 AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
57 TESTS = $(check_SCRIPTS)
58endif
59endif
diff --git a/src/plugin/gns/meson.build b/src/plugin/gns/meson.build
new file mode 100644
index 000000000..2cdf770f8
--- /dev/null
+++ b/src/plugin/gns/meson.build
@@ -0,0 +1,17 @@
1shared_module('gnunet_plugin_gnsrecord_gns',
2 ['plugin_gnsrecord_gns.c'],
3 dependencies: [libgnunetutil_dep,
4 libgnunetgnsrecord_dep,
5 ],
6 include_directories: [incdir, configuration_inc],
7 install: true,
8 install_dir: get_option('libdir')/'gnunet')
9shared_module('gnunet_plugin_block_gns',
10 ['plugin_block_gns.c'],
11 dependencies: [libgnunetutil_dep,
12 libgnunetgnsrecord_dep,
13 libgnunetblockgroup_dep],
14 include_directories: [incdir, configuration_inc],
15 install:true,
16 install_dir: get_option('libdir')/'gnunet')
17
diff --git a/src/plugin/gns/plugin_block_gns.c b/src/plugin/gns/plugin_block_gns.c
new file mode 100644
index 000000000..75e182092
--- /dev/null
+++ b/src/plugin/gns/plugin_block_gns.c
@@ -0,0 +1,289 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2010-2013, 2021, 2022 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file gns/plugin_block_gns.c
23 * @brief blocks used for GNS records
24 * @author Martin Schanzenbach
25 * @author Christian Grothoff
26 */
27
28#include "platform.h"
29#include "gnunet_block_group_lib.h"
30#include "gnunet_block_plugin.h"
31#include "gnunet_namestore_service.h"
32#include "gnunet_signatures.h"
33
34/**
35 * Number of bits we set per entry in the bloomfilter.
36 * Do not change! -from fs
37 */
38#define BLOOMFILTER_K 16
39
40/**
41 * How big is the BF we use for GNS blocks?
42 */
43#define GNS_BF_SIZE 8
44
45
46/**
47 * Create a new block group.
48 *
49 * @param ctx block context in which the block group is created
50 * @param type type of the block for which we are creating the group
51 * @param raw_data optional serialized prior state of the group, NULL if unavailable/fresh
52 * @param raw_data_size number of bytes in @a raw_data, 0 if unavailable/fresh
53 * @param va variable arguments specific to @a type
54 * @return block group handle, NULL if block groups are not supported
55 * by this @a type of block (this is not an error)
56 */
57static struct GNUNET_BLOCK_Group *
58block_plugin_gns_create_group (void *cls,
59 enum GNUNET_BLOCK_Type type,
60 const void *raw_data,
61 size_t raw_data_size,
62 va_list va)
63{
64 unsigned int bf_size;
65 const char *guard;
66
67 guard = va_arg (va, const char *);
68 if (0 == strcmp (guard,
69 "seen-set-size"))
70 bf_size = GNUNET_BLOCK_GROUP_compute_bloomfilter_size (va_arg (va, unsigned
71 int),
72 BLOOMFILTER_K);
73 else if (0 == strcmp (guard,
74 "filter-size"))
75 bf_size = va_arg (va, unsigned int);
76 else
77 {
78 GNUNET_break (0);
79 bf_size = GNS_BF_SIZE;
80 }
81 GNUNET_break (NULL == va_arg (va, const char *));
82 return GNUNET_BLOCK_GROUP_bf_create (cls,
83 bf_size,
84 BLOOMFILTER_K,
85 type,
86 raw_data,
87 raw_data_size);
88}
89
90
91/**
92 * Function called to obtain the key for a block.
93 * If the @a block is malformed, the function should
94 * zero-out @a key and return #GNUNET_OK.
95 *
96 * @param cls closure
97 * @param type block type
98 * @param reply_block block to get the key for
99 * @param reply_block_size number of bytes in @a reply_block
100 * @param key set to the key (query) for the given block
101 * @return #GNUNET_OK on success, #GNUNET_SYSERR if type not supported, #GNUNET_NO if extracting a key from a block of this type does not work
102 */
103static enum GNUNET_GenericReturnValue
104block_plugin_gns_get_key (void *cls,
105 enum GNUNET_BLOCK_Type type,
106 const void *reply_block,
107 size_t reply_block_size,
108 struct GNUNET_HashCode *key)
109{
110 const struct GNUNET_GNSRECORD_Block *block;
111
112 if (GNUNET_BLOCK_TYPE_GNS_NAMERECORD != type)
113 {
114 GNUNET_break (0);
115 return GNUNET_SYSERR;
116 }
117 if (reply_block_size < sizeof(struct GNUNET_GNSRECORD_Block))
118 {
119 GNUNET_break_op (0);
120 memset (key,
121 0,
122 sizeof (*key));
123 return GNUNET_OK;
124 }
125 block = reply_block;
126 GNUNET_GNSRECORD_query_from_block (block,
127 key);
128 return GNUNET_OK;
129}
130
131
132/**
133 * Function called to validate a query.
134 *
135 * @param cls closure
136 * @param type block type
137 * @param query original query (hash)
138 * @param xquery extrended query data (can be NULL, depending on type)
139 * @param xquery_size number of bytes in @a xquery
140 * @return #GNUNET_OK if the query is fine, #GNUNET_NO if not
141 */
142static enum GNUNET_GenericReturnValue
143block_plugin_gns_check_query (void *cls,
144 enum GNUNET_BLOCK_Type type,
145 const struct GNUNET_HashCode *query,
146 const void *xquery,
147 size_t xquery_size)
148{
149 if (GNUNET_BLOCK_TYPE_GNS_NAMERECORD != type)
150 {
151 GNUNET_break (0);
152 return GNUNET_SYSERR;
153 }
154 if (0 != xquery_size)
155 {
156 GNUNET_break_op (0);
157 return GNUNET_NO;
158 }
159 return GNUNET_OK;
160}
161
162
163/**
164 * Function called to validate a block for storage.
165 *
166 * @param cls closure
167 * @param type block type
168 * @param block block data to validate
169 * @param block_size number of bytes in @a block
170 * @return #GNUNET_OK if the block is fine, #GNUNET_NO if not
171 */
172static enum GNUNET_GenericReturnValue
173block_plugin_gns_check_block (void *cls,
174 enum GNUNET_BLOCK_Type type,
175 const void *block,
176 size_t block_size)
177{
178 const struct GNUNET_GNSRECORD_Block *gblock;
179
180 if (GNUNET_BLOCK_TYPE_GNS_NAMERECORD != type)
181 {
182 GNUNET_break (0);
183 return GNUNET_SYSERR;
184 }
185 if (block_size < sizeof(struct GNUNET_GNSRECORD_Block))
186 {
187 GNUNET_break_op (0);
188 return GNUNET_NO;
189 }
190 gblock = block;
191 if (GNUNET_GNSRECORD_block_get_size (gblock) > block_size)
192 {
193 GNUNET_break_op (0);
194 return GNUNET_NO;
195 }
196 if (GNUNET_OK !=
197 GNUNET_GNSRECORD_block_verify (gblock))
198 {
199 GNUNET_break_op (0);
200 return GNUNET_NO;
201 }
202 return GNUNET_OK;
203}
204
205
206/**
207 * Function called to validate a reply to a request. Note that it is assumed
208 * that the reply has already been matched to the key (and signatures checked)
209 * as it would be done with the GetKeyFunction and the
210 * BlockEvaluationFunction.
211 *
212 * @param cls closure
213 * @param type block type
214 * @param group which block group to use for evaluation
215 * @param query original query (hash)
216 * @param xquery extrended query data (can be NULL, depending on type)
217 * @param xquery_size number of bytes in @a xquery
218 * @param reply_block response to validate
219 * @param reply_block_size number of bytes in @a reply_block
220 * @return characterization of result
221 */
222static enum GNUNET_BLOCK_ReplyEvaluationResult
223block_plugin_gns_check_reply (void *cls,
224 enum GNUNET_BLOCK_Type type,
225 struct GNUNET_BLOCK_Group *group,
226 const struct GNUNET_HashCode *query,
227 const void *xquery,
228 size_t xquery_size,
229 const void *reply_block,
230 size_t reply_block_size)
231{
232 const struct GNUNET_GNSRECORD_Block *block = reply_block;
233 struct GNUNET_HashCode chash;
234
235 if (GNUNET_BLOCK_TYPE_GNS_NAMERECORD != type)
236 {
237 GNUNET_break (0);
238 return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
239 }
240 GNUNET_assert (reply_block_size >= sizeof(struct GNUNET_GNSRECORD_Block));
241 GNUNET_assert (reply_block_size >= GNUNET_GNSRECORD_block_get_size (block));
242 GNUNET_CRYPTO_hash (reply_block,
243 reply_block_size,
244 &chash);
245 if (GNUNET_YES ==
246 GNUNET_BLOCK_GROUP_bf_test_and_set (group,
247 &chash))
248 return GNUNET_BLOCK_REPLY_OK_DUPLICATE;
249 return GNUNET_BLOCK_REPLY_OK_MORE;
250}
251
252
253/**
254 * Entry point for the plugin.
255 */
256void *
257libgnunet_plugin_block_gns_init (void *cls)
258{
259 static const enum GNUNET_BLOCK_Type types[] = {
260 GNUNET_BLOCK_TYPE_GNS_NAMERECORD,
261 GNUNET_BLOCK_TYPE_ANY /* end of list */
262 };
263 struct GNUNET_BLOCK_PluginFunctions *api;
264
265 api = GNUNET_new (struct GNUNET_BLOCK_PluginFunctions);
266 api->get_key = &block_plugin_gns_get_key;
267 api->create_group = &block_plugin_gns_create_group;
268 api->check_query = &block_plugin_gns_check_query;
269 api->check_block = &block_plugin_gns_check_block;
270 api->check_reply = &block_plugin_gns_check_reply;
271 api->types = types;
272 return api;
273}
274
275
276/**
277 * Exit point from the plugin.
278 */
279void *
280libgnunet_plugin_block_gns_done (void *cls)
281{
282 struct GNUNET_BLOCK_PluginFunctions *api = cls;
283
284 GNUNET_free (api);
285 return NULL;
286}
287
288
289/* end of plugin_block_gns.c */
diff --git a/src/plugin/gns/plugin_gnsrecord_gns.c b/src/plugin/gns/plugin_gnsrecord_gns.c
new file mode 100644
index 000000000..36e0c9fde
--- /dev/null
+++ b/src/plugin/gns/plugin_gnsrecord_gns.c
@@ -0,0 +1,515 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013, 2014, 2016 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file gns/plugin_gnsrecord_gns.c
23 * @brief gnsrecord plugin to provide the API for fundamental GNS records
24 * This includes the VPN record because GNS resolution
25 * is expected to understand VPN records and (if needed)
26 * map the result to A/AAAA.
27 * @author Christian Grothoff
28 */
29#include "platform.h"
30#include "gnunet_util_lib.h"
31#include "gnunet_gnsrecord_lib.h"
32#include "gnunet_gnsrecord_plugin.h"
33#include <inttypes.h>
34
35
36/**
37 * Convert the 'value' of a record to a string.
38 *
39 * @param cls closure, unused
40 * @param type type of the record
41 * @param data value in binary encoding
42 * @param data_size number of bytes in @a data
43 * @return NULL on error, otherwise human-readable representation of the value
44 */
45static char *
46gns_value_to_string (void *cls,
47 uint32_t type,
48 const void *data,
49 size_t data_size)
50{
51 const char *cdata;
52 struct GNUNET_CRYPTO_PublicKey pk;
53
54 switch (type)
55 {
56 case GNUNET_GNSRECORD_TYPE_PKEY:
57 case GNUNET_GNSRECORD_TYPE_EDKEY:
58 if (GNUNET_OK !=
59 GNUNET_GNSRECORD_identity_from_data (data,
60 data_size,
61 type,
62 &pk))
63 return NULL;
64 return GNUNET_CRYPTO_public_key_to_string (&pk);
65
66 case GNUNET_GNSRECORD_TYPE_NICK:
67 case GNUNET_GNSRECORD_TYPE_REDIRECT:
68 case GNUNET_GNSRECORD_TYPE_LEHO:
69 return GNUNET_strndup (data, data_size);
70
71 case GNUNET_GNSRECORD_TYPE_GNS2DNS: {
72 char *ns;
73 char *ip;
74 size_t off;
75 char *nstr;
76
77 off = 0;
78 ns = GNUNET_DNSPARSER_parse_name (data, data_size, &off);
79 if (NULL == ns)
80 {
81 GNUNET_break_op (0);
82 GNUNET_free (ns);
83 return NULL;
84 }
85 /* DNS server IP/name must be UTF-8 */
86 ip = GNUNET_strdup (&((const char*) data)[off]);
87 GNUNET_asprintf (&nstr, "%s@%s", ns, ip);
88 GNUNET_free (ns);
89 GNUNET_free (ip);
90 return nstr;
91 }
92
93 case GNUNET_GNSRECORD_TYPE_VPN: {
94 struct GNUNET_TUN_GnsVpnRecord vpn;
95 char *vpn_str;
96
97 cdata = data;
98 if ((data_size <= sizeof(vpn)) || ('\0' != cdata[data_size - 1]))
99 return NULL; /* malformed */
100 /* need to memcpy for alignment */
101 GNUNET_memcpy (&vpn, data, sizeof(vpn));
102 GNUNET_asprintf (&vpn_str,
103 "%u %s %s",
104 (unsigned int) ntohs (vpn.proto),
105 (const char *) GNUNET_i2s_full (&vpn.peer),
106 (const char *) &cdata[sizeof(vpn)]);
107 return vpn_str;
108 }
109
110 case GNUNET_GNSRECORD_TYPE_BOX: {
111 struct GNUNET_GNSRECORD_BoxRecord box;
112 uint32_t rt;
113 char *box_str;
114 char *ival;
115
116 cdata = data;
117 if (data_size < sizeof(struct GNUNET_GNSRECORD_BoxRecord))
118 return NULL; /* malformed */
119 GNUNET_memcpy (&box, data, sizeof(box));
120 rt = ntohl (box.record_type);
121 ival = GNUNET_GNSRECORD_value_to_string (rt,
122 &cdata[sizeof(box)],
123 data_size - sizeof(box));
124 if (NULL == ival)
125 return NULL; /* malformed */
126 GNUNET_asprintf (&box_str,
127 "%u %u %u %s",
128 (unsigned int) ntohs (box.protocol),
129 (unsigned int) ntohs (box.service),
130 (unsigned int) rt,
131 ival);
132 GNUNET_free (ival);
133 return box_str;
134 }
135 case GNUNET_GNSRECORD_TYPE_SBOX: {
136 struct GNUNET_GNSRECORD_SBoxRecord box;
137 uint32_t rt;
138 char *box_str;
139 char *ival;
140 char *prefix;
141
142 cdata = data;
143 if (data_size < sizeof(struct GNUNET_GNSRECORD_SBoxRecord))
144 return NULL; /* malformed */
145 GNUNET_memcpy (&box, data, sizeof(box));
146 rt = ntohl (box.record_type);
147
148 prefix = GNUNET_strdup (&cdata[sizeof(box)]);
149 ival = GNUNET_GNSRECORD_value_to_string (rt, &cdata[sizeof(box)
150 + strlen (prefix)
151 + 1],
152 data_size - sizeof(box)
153 - strlen (prefix) - 1);
154 if (NULL == ival)
155 return NULL; /* malformed */
156 GNUNET_asprintf (&box_str,
157 "%s %u %s",
158 prefix,
159 (unsigned int) rt,
160 ival);
161 GNUNET_free (prefix);
162 GNUNET_free (ival);
163 return box_str;
164 }
165 case GNUNET_GNSRECORD_TYPE_TOMBSTONE: {
166 return GNUNET_strdup (_ (
167 "This is a memento of an older block for internal maintenance."));
168 }
169 default:
170 return NULL;
171 }
172}
173
174
175/**
176 * Convert human-readable version of a 'value' of a record to the binary
177 * representation.
178 *
179 * @param cls closure, unused
180 * @param type type of the record
181 * @param s human-readable string
182 * @param data set to value in binary encoding (will be allocated)
183 * @param data_size set to number of bytes in @a data
184 * @return #GNUNET_OK on success
185 */
186static int
187gns_string_to_value (void *cls,
188 uint32_t type,
189 const char *s,
190 void **data,
191 size_t *data_size)
192{
193 struct GNUNET_CRYPTO_PublicKey pk;
194 uint32_t record_type;
195
196 if (NULL == s)
197 return GNUNET_SYSERR;
198 switch (type)
199 {
200 case GNUNET_GNSRECORD_TYPE_PKEY:
201 case GNUNET_GNSRECORD_TYPE_EDKEY:
202 if (GNUNET_OK !=
203 GNUNET_CRYPTO_public_key_from_string (s, &pk))
204 {
205 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
206 _ ("Unable to parse zone key record `%s'\n"),
207 s);
208 return GNUNET_SYSERR;
209 }
210 *data_size = GNUNET_CRYPTO_public_key_get_length (&pk);
211 if (GNUNET_OK !=
212 GNUNET_GNSRECORD_data_from_identity (&pk,
213 (char **) data,
214 data_size,
215 &record_type))
216 return GNUNET_SYSERR;
217 if (record_type != type)
218 {
219 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
220 _ ("Record type does not match parsed record type\n"));
221 return GNUNET_SYSERR;
222 }
223 return GNUNET_OK;
224
225 case GNUNET_GNSRECORD_TYPE_NICK:
226 case GNUNET_GNSRECORD_TYPE_REDIRECT:
227 case GNUNET_GNSRECORD_TYPE_LEHO:
228 *data = GNUNET_strdup (s);
229 *data_size = strlen (s);
230 return GNUNET_OK;
231
232 case GNUNET_GNSRECORD_TYPE_GNS2DNS: {
233 char nsbuf[514];
234 char *cpy;
235 char *at;
236 size_t off;
237
238 cpy = GNUNET_strdup (s);
239 at = strchr (cpy, '@');
240 if (NULL == at)
241 {
242 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
243 _ ("Unable to parse GNS2DNS record `%s'\n"),
244 s);
245 GNUNET_free (cpy);
246 return GNUNET_SYSERR;
247 }
248 *at = '\0';
249 at++;
250
251 off = 0;
252 if (GNUNET_OK !=
253 GNUNET_DNSPARSER_builder_add_name (nsbuf,
254 sizeof(nsbuf),
255 &off,
256 cpy))
257 {
258 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
259 _ (
260 "Failed to serialize GNS2DNS record with value `%s': Not a DNS name.\n"),
261 s);
262 GNUNET_free (cpy);
263 return GNUNET_SYSERR;
264 }
265 /* The DNS server location/name is in UTF-8 */
266 GNUNET_memcpy (&nsbuf[off], at, strlen (at) + 1);
267 off += strlen (at) + 1;
268 GNUNET_free (cpy);
269 *data_size = off;
270 *data = GNUNET_malloc (off);
271 GNUNET_memcpy (*data, nsbuf, off);
272 return GNUNET_OK;
273 }
274
275 case GNUNET_GNSRECORD_TYPE_VPN: {
276 struct GNUNET_TUN_GnsVpnRecord *vpn;
277 char s_peer[103 + 1];
278 char s_serv[253 + 1];
279 unsigned int proto;
280
281 if (3 != sscanf (s, "%u %103s %253s", &proto, s_peer, s_serv))
282 {
283 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
284 _ ("Unable to parse VPN record string `%s'\n"),
285 s);
286 return GNUNET_SYSERR;
287 }
288 *data_size = sizeof(struct GNUNET_TUN_GnsVpnRecord) + strlen (s_serv) + 1;
289 *data = vpn = GNUNET_malloc (*data_size);
290 if (GNUNET_OK !=
291 GNUNET_CRYPTO_eddsa_public_key_from_string ((char *) s_peer,
292 strlen (s_peer),
293 &vpn->peer.public_key))
294 {
295 GNUNET_free (vpn);
296 *data_size = 0;
297 return GNUNET_SYSERR;
298 }
299 vpn->proto = htons ((uint16_t) proto);
300 strcpy ((char *) &vpn[1], s_serv);
301 return GNUNET_OK;
302 }
303
304 case GNUNET_GNSRECORD_TYPE_BOX: {
305 struct GNUNET_GNSRECORD_BoxRecord *box;
306 size_t rest;
307 unsigned int protocol;
308 unsigned int service;
309 unsigned int record_type;
310 void *bval;
311 size_t bval_size;
312
313 if (3 != sscanf (s, "%u %u %u ", &protocol, &service, &record_type))
314 {
315 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
316 _ ("Unable to parse BOX record string `%s'\n"),
317 s);
318 return GNUNET_SYSERR;
319 }
320 rest = snprintf (NULL, 0, "%u %u %u ", protocol, service, record_type);
321 if (GNUNET_OK != GNUNET_GNSRECORD_string_to_value (record_type,
322 &s[rest],
323 &bval,
324 &bval_size))
325 return GNUNET_SYSERR;
326 *data_size = sizeof(struct GNUNET_GNSRECORD_BoxRecord) + bval_size;
327 *data = box = GNUNET_malloc (*data_size);
328 box->protocol = htons (protocol);
329 box->service = htons (service);
330 box->record_type = htonl (record_type);
331 GNUNET_memcpy (&box[1], bval, bval_size);
332 GNUNET_free (bval);
333 return GNUNET_OK;
334 }
335 case GNUNET_GNSRECORD_TYPE_SBOX: {
336 struct GNUNET_GNSRECORD_SBoxRecord *box;
337 size_t rest;
338 char *prefix;
339 char *underscore_prefix;
340 unsigned int record_type;
341 void *bval;
342 size_t bval_size;
343 prefix = GNUNET_malloc (strlen (s));
344 size_t prefix_size;
345 if (2 != sscanf (s, "%s %u ", prefix, &record_type))
346 {
347 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
348 _ ("Unable to parse SBOX record string `%s'\n"),
349 s);
350 return GNUNET_SYSERR;
351 }
352 underscore_prefix = strrchr (prefix, '.');
353 if (underscore_prefix == NULL)
354 {
355 underscore_prefix = prefix;
356 }
357 else
358 {
359 underscore_prefix++;
360 }
361 if ('_' != underscore_prefix[0])
362 {
363 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
364 _ (
365 "Unable to parse SBOX record string `%s', the rightmost label `%s' does not start with an underscore\n"),
366 prefix, underscore_prefix);
367 return GNUNET_SYSERR;
368 }
369 rest = snprintf (NULL, 0, "%s %u ", prefix, record_type);
370 if (GNUNET_OK != GNUNET_GNSRECORD_string_to_value (record_type,
371 &s[rest],
372 &bval,
373 &bval_size))
374 return GNUNET_SYSERR;
375 prefix_size = strlen (prefix) + 1;
376 *data_size = sizeof(struct GNUNET_GNSRECORD_SBoxRecord) + prefix_size
377 + bval_size;
378 void *p = *data = box = GNUNET_malloc (*data_size);
379 box->record_type = htonl (record_type);
380 p += sizeof(struct GNUNET_GNSRECORD_SBoxRecord);
381 GNUNET_memcpy (p, prefix, prefix_size);
382 p += prefix_size;
383 GNUNET_memcpy (p, bval, bval_size);
384 GNUNET_free (bval);
385 GNUNET_free (prefix);
386 return GNUNET_OK;
387 }
388 case GNUNET_GNSRECORD_TYPE_TOMBSTONE: {
389 *data_size = 0;
390 *data = NULL;
391 return GNUNET_OK;
392 }
393
394 default:
395 return GNUNET_SYSERR;
396 }
397}
398
399
400/**
401 * Mapping of record type numbers to human-readable
402 * record type names.
403 */
404static struct
405{
406 const char *name;
407 uint32_t number;
408} gns_name_map[] = {
409 { "PKEY", GNUNET_GNSRECORD_TYPE_PKEY },
410 { "EDKEY", GNUNET_GNSRECORD_TYPE_EDKEY },
411 { "NICK", GNUNET_GNSRECORD_TYPE_NICK },
412 { "LEHO", GNUNET_GNSRECORD_TYPE_LEHO },
413 { "VPN", GNUNET_GNSRECORD_TYPE_VPN },
414 { "GNS2DNS", GNUNET_GNSRECORD_TYPE_GNS2DNS },
415 { "BOX", GNUNET_GNSRECORD_TYPE_BOX },
416 { "SBOX", GNUNET_GNSRECORD_TYPE_SBOX },
417 { "REDIRECT", GNUNET_GNSRECORD_TYPE_REDIRECT },
418 /* Tombstones should never be added manually
419 * so this makes sense, kind of */
420 { "\u271E", GNUNET_GNSRECORD_TYPE_TOMBSTONE },
421 { NULL, UINT32_MAX }
422};
423
424
425/**
426 * Convert a type name (e.g. "AAAA") to the corresponding number.
427 *
428 * @param cls closure, unused
429 * @param gns_typename name to convert
430 * @return corresponding number, UINT32_MAX on error
431 */
432static uint32_t
433gns_typename_to_number (void *cls,
434 const char *gns_typename)
435{
436 unsigned int i;
437
438 i = 0;
439 while ((NULL != gns_name_map[i].name) &&
440 (0 != strcasecmp (gns_typename, gns_name_map[i].name)))
441 i++;
442 return gns_name_map[i].number;
443}
444
445
446/**
447 * Convert a type number to the corresponding type string (e.g. 1 to "A")
448 *
449 * @param cls closure, unused
450 * @param type number of a type to convert
451 * @return corresponding typestring, NULL on error
452 */
453static const char *
454gns_number_to_typename (void *cls,
455 uint32_t type)
456{
457 unsigned int i;
458
459 i = 0;
460 while ( (NULL != gns_name_map[i].name) &&
461 (type != gns_name_map[i].number) )
462 i++;
463 return gns_name_map[i].name;
464}
465
466
467static enum GNUNET_GenericReturnValue
468gns_is_critical (void *cls, uint32_t type)
469{
470 return ((type == GNUNET_GNSRECORD_TYPE_PKEY) ||
471 (type == GNUNET_GNSRECORD_TYPE_EDKEY) ||
472 (type == GNUNET_GNSRECORD_TYPE_GNS2DNS) ||
473 (type == GNUNET_GNSRECORD_TYPE_REDIRECT) ?
474 GNUNET_YES : GNUNET_NO);
475}
476
477
478/**
479 * Entry point for the plugin.
480 *
481 * @param cls NULL
482 * @return the exported block API
483 */
484void *
485libgnunet_plugin_gnsrecord_gns_init (void *cls)
486{
487 struct GNUNET_GNSRECORD_PluginFunctions *api;
488
489 api = GNUNET_new (struct GNUNET_GNSRECORD_PluginFunctions);
490 api->value_to_string = &gns_value_to_string;
491 api->string_to_value = &gns_string_to_value;
492 api->typename_to_number = &gns_typename_to_number;
493 api->number_to_typename = &gns_number_to_typename;
494 api->is_critical = &gns_is_critical;
495 return api;
496}
497
498
499/**
500 * Exit point from the plugin.
501 *
502 * @param cls the return value from #libgnunet_plugin_block_test_init()
503 * @return NULL
504 */
505void *
506libgnunet_plugin_gnsrecord_gns_done (void *cls)
507{
508 struct GNUNET_GNSRECORD_PluginFunctions *api = cls;
509
510 GNUNET_free (api);
511 return NULL;
512}
513
514
515/* end of plugin_gnsrecord_gns.c */
diff --git a/src/plugin/gnsrecord/Makefile.am b/src/plugin/gnsrecord/Makefile.am
new file mode 100644
index 000000000..1b008cf62
--- /dev/null
+++ b/src/plugin/gnsrecord/Makefile.am
@@ -0,0 +1,25 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include ${MHD_CFLAGS}
3
4plugindir = $(libdir)/gnunet
5
6pkgcfgdir= $(pkgdatadir)/config.d/
7
8libexecdir= $(pkglibdir)/libexec/
9
10if USE_COVERAGE
11 AM_CFLAGS = --coverage -O0
12 XLIBS = -lgcov
13endif
14
15plugin_LTLIBRARIES = \
16 libgnunet_plugin_gnsrecord_dns.la
17
18
19libgnunet_plugin_gnsrecord_dns_la_SOURCES = \
20 plugin_gnsrecord_dns.c
21libgnunet_plugin_gnsrecord_dns_la_LIBADD = \
22 $(top_builddir)/src/lib/util/libgnunetutil.la \
23 $(LTLIBINTL)
24libgnunet_plugin_gnsrecord_dns_la_LDFLAGS = \
25 $(GN_PLUGIN_LDFLAGS)
diff --git a/src/plugin/gnsrecord/meson.build b/src/plugin/gnsrecord/meson.build
new file mode 100644
index 000000000..0778fb233
--- /dev/null
+++ b/src/plugin/gnsrecord/meson.build
@@ -0,0 +1,6 @@
1shared_module('gnunet_plugin_gnsrecord_dns',
2 ['plugin_gnsrecord_dns.c'],
3 dependencies: [libgnunetutil_dep, libgnunetgnsrecord_dep],
4 include_directories: [incdir, configuration_inc],
5 install: true,
6 install_dir: get_option('libdir')/'gnunet')
diff --git a/src/plugin/gnsrecord/plugin_gnsrecord_dns.c b/src/plugin/gnsrecord/plugin_gnsrecord_dns.c
new file mode 100644
index 000000000..94b2686a1
--- /dev/null
+++ b/src/plugin/gnsrecord/plugin_gnsrecord_dns.c
@@ -0,0 +1,893 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013, 2014 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file gnsrecord/plugin_gnsrecord_dns.c
23 * @brief gnsrecord plugin to provide the API for basic DNS records
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "gnunet_gnsrecord_plugin.h"
29
30
31/**
32 * Convert the 'value' of a record to a string.
33 *
34 * @param cls closure, unused
35 * @param type type of the record
36 * @param data value in binary encoding
37 * @param data_size number of bytes in @a data
38 * @return NULL on error, otherwise human-readable representation of the value
39 */
40static char *
41dns_value_to_string (void *cls,
42 uint32_t type,
43 const void *data,
44 size_t data_size)
45{
46 char *result;
47 char tmp[INET6_ADDRSTRLEN];
48
49 switch (type)
50 {
51 case GNUNET_DNSPARSER_TYPE_A:
52 if (data_size != sizeof(struct in_addr))
53 return NULL;
54 if (NULL == inet_ntop (AF_INET, data, tmp, sizeof(tmp)))
55 return NULL;
56 return GNUNET_strdup (tmp);
57
58 case GNUNET_DNSPARSER_TYPE_NS: {
59 char *ns;
60 size_t off;
61
62 off = 0;
63 ns = GNUNET_DNSPARSER_parse_name (data, data_size, &off);
64 if ((NULL == ns) || (off != data_size))
65 {
66 GNUNET_break_op (0);
67 GNUNET_free (ns);
68 return NULL;
69 }
70 return ns;
71 }
72
73 case GNUNET_DNSPARSER_TYPE_CNAME: {
74 char *cname;
75 size_t off;
76
77 off = 0;
78 cname = GNUNET_DNSPARSER_parse_name (data, data_size, &off);
79 if ((NULL == cname) || (off != data_size))
80 {
81 GNUNET_break_op (0);
82 GNUNET_free (cname);
83 return NULL;
84 }
85 return cname;
86 }
87
88 case GNUNET_DNSPARSER_TYPE_SOA: {
89 struct GNUNET_DNSPARSER_SoaRecord *soa;
90 size_t off;
91
92 off = 0;
93 soa = GNUNET_DNSPARSER_parse_soa (data, data_size, &off);
94 if ((NULL == soa) || (off != data_size))
95 {
96 GNUNET_break_op (0);
97 if (NULL != soa)
98 GNUNET_DNSPARSER_free_soa (soa);
99 return NULL;
100 }
101 GNUNET_asprintf (&result,
102 "%s %s ( %u %u %u %u %u )",
103 soa->rname,
104 soa->mname,
105 soa->serial,
106 soa->refresh,
107 soa->retry,
108 soa->expire,
109 soa->minimum_ttl);
110 GNUNET_DNSPARSER_free_soa (soa);
111 return result;
112 }
113
114 case GNUNET_DNSPARSER_TYPE_PTR: {
115 char *ptr;
116 size_t off;
117
118 off = 0;
119 ptr = GNUNET_DNSPARSER_parse_name (data, data_size, &off);
120 if ((NULL == ptr) || (off != data_size))
121 {
122 GNUNET_break_op (0);
123 GNUNET_free (ptr);
124 return NULL;
125 }
126 return ptr;
127 }
128
129 case GNUNET_DNSPARSER_TYPE_CERT: {
130 struct GNUNET_DNSPARSER_CertRecord *cert;
131 size_t off;
132 char *base64;
133 int len;
134
135 off = 0;
136 cert = GNUNET_DNSPARSER_parse_cert (data, data_size, &off);
137 if ((NULL == cert) || (off != data_size))
138 {
139 GNUNET_break_op (0);
140 GNUNET_DNSPARSER_free_cert (cert);
141 return NULL;
142 }
143 len = GNUNET_STRINGS_base64_encode (cert->certificate_data,
144 cert->certificate_size,
145 &base64);
146 GNUNET_asprintf (&result,
147 "%u %u %u %.*s",
148 cert->cert_type,
149 cert->cert_tag,
150 cert->algorithm,
151 len,
152 base64);
153 GNUNET_free (base64);
154 GNUNET_DNSPARSER_free_cert (cert);
155 return result;
156 }
157
158 case GNUNET_DNSPARSER_TYPE_MX: {
159 struct GNUNET_DNSPARSER_MxRecord *mx;
160 size_t off;
161
162 off = 0;
163 mx = GNUNET_DNSPARSER_parse_mx (data, data_size, &off);
164 if ((NULL == mx) || (off != data_size))
165 {
166 GNUNET_break_op (0);
167 GNUNET_DNSPARSER_free_mx (mx);
168 return NULL;
169 }
170 GNUNET_asprintf (&result,
171 "%u %s",
172 (unsigned int) mx->preference,
173 mx->mxhost);
174 GNUNET_DNSPARSER_free_mx (mx);
175 return result;
176 }
177
178 case GNUNET_DNSPARSER_TYPE_TXT:
179 return GNUNET_strndup (data, data_size);
180
181 case GNUNET_DNSPARSER_TYPE_AAAA:
182 if (data_size != sizeof(struct in6_addr))
183 return NULL;
184 if (NULL == inet_ntop (AF_INET6, data, tmp, sizeof(tmp)))
185 return NULL;
186 return GNUNET_strdup (tmp);
187
188 case GNUNET_DNSPARSER_TYPE_SRV: {
189 struct GNUNET_DNSPARSER_SrvRecord *srv;
190 size_t off;
191
192 off = 0;
193 srv = GNUNET_DNSPARSER_parse_srv (data, data_size, &off);
194 if ((NULL == srv) || (off != data_size))
195 {
196 GNUNET_break_op (0);
197 if (NULL != srv)
198 GNUNET_DNSPARSER_free_srv (srv);
199 return NULL;
200 }
201 GNUNET_asprintf (&result,
202 "%d %d %d %s",
203 srv->priority,
204 srv->weight,
205 srv->port,
206 srv->target);
207 GNUNET_DNSPARSER_free_srv (srv);
208 return result;
209 }
210
211 case GNUNET_DNSPARSER_TYPE_URI: { // RFC7553
212 struct GNUNET_DNSPARSER_UriRecord *uri;
213 size_t off;
214
215 off = 0;
216 uri = GNUNET_DNSPARSER_parse_uri (data, data_size, &off);
217 if ((NULL == uri) || (off != data_size))
218 {
219 GNUNET_break_op (0);
220 if (NULL != uri)
221 GNUNET_DNSPARSER_free_uri (uri);
222 return NULL;
223 }
224 GNUNET_asprintf (&result,
225 "%d %d \"%s\"",
226 uri->priority,
227 uri->weight,
228 uri->target);
229 GNUNET_DNSPARSER_free_uri (uri);
230 return result;
231 }
232
233 case GNUNET_DNSPARSER_TYPE_SMIMEA:
234 case GNUNET_DNSPARSER_TYPE_TLSA: {
235 const struct GNUNET_TUN_DnsTlsaRecord *tlsa;
236 char *tlsa_str;
237 char *hex;
238
239 if (data_size < sizeof(struct GNUNET_TUN_DnsTlsaRecord))
240 return NULL; /* malformed */
241 tlsa = data;
242 hex =
243 GNUNET_DNSPARSER_bin_to_hex (&tlsa[1],
244 data_size
245 - sizeof(struct GNUNET_TUN_DnsTlsaRecord));
246 if (0 == GNUNET_asprintf (&tlsa_str,
247 "%u %u %u %s",
248 (unsigned int) tlsa->usage,
249 (unsigned int) tlsa->selector,
250 (unsigned int) tlsa->matching_type,
251 hex))
252 {
253 GNUNET_free (hex);
254 GNUNET_free (tlsa_str);
255 return NULL;
256 }
257 GNUNET_free (hex);
258 return tlsa_str;
259 }
260
261 case GNUNET_DNSPARSER_TYPE_CAA: { // RFC6844
262 const struct GNUNET_DNSPARSER_CaaRecord *caa;
263 char tag[15]; // between 1 and 15 bytes
264 char value[data_size];
265 char *caa_str;
266 if (data_size < sizeof(struct GNUNET_DNSPARSER_CaaRecord))
267 return NULL; /* malformed */
268 caa = data;
269 if ((1 > caa->tag_len) || (15 < caa->tag_len))
270 return NULL; /* malformed */
271 memset (tag, 0, sizeof(tag));
272 memset (value, 0, data_size);
273 memcpy (tag, &caa[1], caa->tag_len);
274 memcpy (value,
275 (char *) &caa[1] + caa->tag_len,
276 data_size - caa->tag_len - 2);
277 if (0 == GNUNET_asprintf (&caa_str,
278 "%u %s %s",
279 (unsigned int) caa->flags,
280 tag,
281 value))
282 {
283 GNUNET_free (caa_str);
284 return NULL;
285 }
286 return caa_str;
287 }
288
289 default:
290 return NULL;
291 }
292}
293
294
295/**
296 * Convert RFC 4394 Mnemonics to the corresponding integer values.
297 *
298 * @param mnemonic string to look up
299 * @return the value, 0 if not found
300 */
301static unsigned int
302rfc4398_mnemonic_to_value (const char *mnemonic)
303{
304 static struct
305 {
306 const char *mnemonic;
307 unsigned int val;
308 } table[] = { { "PKIX", 1 },
309 { "SPKI", 2 },
310 { "PGP", 3 },
311 { "IPKIX", 4 },
312 { "ISPKI", 5 },
313 { "IPGP", 6 },
314 { "ACPKIX", 7 },
315 { "IACPKIX", 8 },
316 { "URI", 253 },
317 { "OID", 254 },
318 { NULL, 0 } };
319 unsigned int i;
320
321 for (i = 0; NULL != table[i].mnemonic; i++)
322 if (0 == strcasecmp (mnemonic, table[i].mnemonic))
323 return table[i].val;
324 return 0;
325}
326
327
328/**
329 * Convert RFC 4034 algorithm types to the corresponding integer values.
330 *
331 * @param mnemonic string to look up
332 * @return the value, 0 if not found
333 */
334static unsigned int
335rfc4034_mnemonic_to_value (const char *mnemonic)
336{
337 static struct
338 {
339 const char *mnemonic;
340 unsigned int val;
341 } table[] = { { "RSAMD5", 1 },
342 { "DH", 2 },
343 { "DSA", 3 },
344 { "ECC", 4 },
345 { "RSASHA1", 5 },
346 { "INDIRECT", 252 },
347 { "PRIVATEDNS", 253 },
348 { "PRIVATEOID", 254 },
349 { NULL, 0 } };
350 unsigned int i;
351
352 for (i = 0; NULL != table[i].mnemonic; i++)
353 if (0 == strcasecmp (mnemonic, table[i].mnemonic))
354 return table[i].val;
355 return 0;
356}
357
358
359/**
360 * Convert human-readable version of a 'value' of a record to the binary
361 * representation.
362 *
363 * @param cls closure, unused
364 * @param type type of the record
365 * @param s human-readable string
366 * @param data set to value in binary encoding (will be allocated)
367 * @param data_size set to number of bytes in @a data
368 * @return #GNUNET_OK on success
369 */
370static int
371dns_string_to_value (void *cls,
372 uint32_t type,
373 const char *s,
374 void **data,
375 size_t *data_size)
376{
377 struct in_addr value_a;
378 struct in6_addr value_aaaa;
379 struct GNUNET_TUN_DnsTlsaRecord *tlsa;
380
381 if (NULL == s)
382 return GNUNET_SYSERR;
383 switch (type)
384 {
385 case GNUNET_DNSPARSER_TYPE_A:
386 if (1 != inet_pton (AF_INET, s, &value_a))
387 {
388 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
389 _ ("Unable to parse IPv4 address `%s'\n"),
390 s);
391 return GNUNET_SYSERR;
392 }
393 *data = GNUNET_new (struct in_addr);
394 GNUNET_memcpy (*data, &value_a, sizeof(value_a));
395 *data_size = sizeof(value_a);
396 return GNUNET_OK;
397
398 case GNUNET_DNSPARSER_TYPE_NS: {
399 char nsbuf[256];
400 size_t off;
401
402 off = 0;
403 if (GNUNET_OK !=
404 GNUNET_DNSPARSER_builder_add_name (nsbuf, sizeof(nsbuf), &off, s))
405 {
406 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
407 _ ("Failed to serialize NS record with value `%s'\n"),
408 s);
409 return GNUNET_SYSERR;
410 }
411 *data_size = off;
412 *data = GNUNET_malloc (off);
413 GNUNET_memcpy (*data, nsbuf, off);
414 return GNUNET_OK;
415 }
416
417 case GNUNET_DNSPARSER_TYPE_CNAME: {
418 char cnamebuf[256];
419 size_t off;
420
421 off = 0;
422 if (GNUNET_OK != GNUNET_DNSPARSER_builder_add_name (cnamebuf,
423 sizeof(cnamebuf),
424 &off,
425 s))
426 {
427 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
428 _ ("Failed to serialize CNAME record with value `%s'\n"),
429 s);
430 return GNUNET_SYSERR;
431 }
432 *data_size = off;
433 *data = GNUNET_malloc (off);
434 GNUNET_memcpy (*data, cnamebuf, off);
435 return GNUNET_OK;
436 }
437
438 case GNUNET_DNSPARSER_TYPE_CERT: {
439 char *sdup;
440 const char *typep;
441 const char *keyp;
442 const char *algp;
443 const char *certp;
444 unsigned int type;
445 unsigned int key;
446 unsigned int alg;
447 size_t cert_size;
448 char *cert_data;
449 struct GNUNET_DNSPARSER_CertRecord cert;
450
451 sdup = GNUNET_strdup (s);
452 typep = strtok (sdup, " ");
453 if ((NULL == typep) ||
454 ((0 == (type = rfc4398_mnemonic_to_value (typep))) &&
455 ((1 != sscanf (typep, "%u", &type)) || (type > UINT16_MAX))))
456 {
457 GNUNET_free (sdup);
458 return GNUNET_SYSERR;
459 }
460 keyp = strtok (NULL, " ");
461 if ((NULL == keyp) || (1 != sscanf (keyp, "%u", &key)) ||
462 (key > UINT16_MAX))
463 {
464 GNUNET_free (sdup);
465 return GNUNET_SYSERR;
466 }
467 alg = 0;
468 algp = strtok (NULL, " ");
469 if ((NULL == algp) ||
470 ((0 == (type = rfc4034_mnemonic_to_value (typep))) &&
471 ((1 != sscanf (algp, "%u", &alg)) || (alg > UINT8_MAX))))
472 {
473 GNUNET_free (sdup);
474 return GNUNET_SYSERR;
475 }
476 certp = strtok (NULL, " ");
477 if ((NULL == certp) || (0 == strlen (certp)))
478 {
479 GNUNET_free (sdup);
480 return GNUNET_SYSERR;
481 }
482 cert_size = GNUNET_STRINGS_base64_decode (certp,
483 strlen (certp),
484 (void **) &cert_data);
485 GNUNET_free (sdup);
486 cert.cert_type = type;
487 cert.cert_tag = key;
488 cert.algorithm = alg;
489 cert.certificate_size = cert_size;
490 cert.certificate_data = cert_data;
491 {
492 char certbuf[cert_size + sizeof(struct GNUNET_TUN_DnsCertRecord)];
493 size_t off;
494
495 off = 0;
496 if (GNUNET_OK != GNUNET_DNSPARSER_builder_add_cert (certbuf,
497 sizeof(certbuf),
498 &off,
499 &cert))
500 {
501 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
502 _ ("Failed to serialize CERT record with %u bytes\n"),
503 (unsigned int) cert_size);
504 GNUNET_free (cert_data);
505 return GNUNET_SYSERR;
506 }
507 *data_size = off;
508 *data = GNUNET_malloc (off);
509 GNUNET_memcpy (*data, certbuf, off);
510 }
511 GNUNET_free (cert_data);
512 return GNUNET_OK;
513 }
514
515 case GNUNET_DNSPARSER_TYPE_SOA: {
516 struct GNUNET_DNSPARSER_SoaRecord soa;
517 char soabuf[540];
518 char soa_rname[253 + 1];
519 char soa_mname[253 + 1];
520 unsigned int soa_serial;
521 unsigned int soa_refresh;
522 unsigned int soa_retry;
523 unsigned int soa_expire;
524 unsigned int soa_min;
525 size_t off;
526
527 if (7 != sscanf (s,
528 "%253s %253s ( %u %u %u %u %u )",
529 soa_rname,
530 soa_mname,
531 &soa_serial,
532 &soa_refresh,
533 &soa_retry,
534 &soa_expire,
535 &soa_min))
536 {
537 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
538 _ ("Unable to parse SOA record `%s'\n"),
539 s);
540 return GNUNET_SYSERR;
541 }
542 soa.mname = soa_mname;
543 soa.rname = soa_rname;
544 soa.serial = (uint32_t) soa_serial;
545 soa.refresh = (uint32_t) soa_refresh;
546 soa.retry = (uint32_t) soa_retry;
547 soa.expire = (uint32_t) soa_expire;
548 soa.minimum_ttl = (uint32_t) soa_min;
549 off = 0;
550 if (GNUNET_OK !=
551 GNUNET_DNSPARSER_builder_add_soa (soabuf, sizeof(soabuf), &off, &soa))
552 {
553 GNUNET_log (
554 GNUNET_ERROR_TYPE_ERROR,
555 _ ("Failed to serialize SOA record with mname `%s' and rname `%s'\n"),
556 soa_mname,
557 soa_rname);
558 return GNUNET_SYSERR;
559 }
560 *data_size = off;
561 *data = GNUNET_malloc (off);
562 GNUNET_memcpy (*data, soabuf, off);
563 return GNUNET_OK;
564 }
565
566 case GNUNET_DNSPARSER_TYPE_PTR: {
567 char ptrbuf[256];
568 size_t off;
569
570 off = 0;
571 if (GNUNET_OK !=
572 GNUNET_DNSPARSER_builder_add_name (ptrbuf, sizeof(ptrbuf), &off, s))
573 {
574 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
575 _ ("Failed to serialize PTR record with value `%s'\n"),
576 s);
577 return GNUNET_SYSERR;
578 }
579 *data_size = off;
580 *data = GNUNET_malloc (off);
581 GNUNET_memcpy (*data, ptrbuf, off);
582 return GNUNET_OK;
583 }
584
585 case GNUNET_DNSPARSER_TYPE_MX: {
586 struct GNUNET_DNSPARSER_MxRecord mx;
587 char mxbuf[258];
588 char mxhost[253 + 1];
589 unsigned int mx_pref;
590 size_t off;
591
592 if (2 != sscanf (s, "%u %253s", &mx_pref, mxhost))
593 {
594 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
595 _ ("Unable to parse MX record `%s'\n"),
596 s);
597 return GNUNET_SYSERR;
598 }
599 mx.preference = (uint16_t) mx_pref;
600 mx.mxhost = mxhost;
601 off = 0;
602
603 if (GNUNET_OK !=
604 GNUNET_DNSPARSER_builder_add_mx (mxbuf, sizeof(mxbuf), &off, &mx))
605 {
606 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
607 _ ("Failed to serialize MX record with hostname `%s'\n"),
608 mxhost);
609 return GNUNET_SYSERR;
610 }
611 *data_size = off;
612 *data = GNUNET_malloc (off);
613 GNUNET_memcpy (*data, mxbuf, off);
614 return GNUNET_OK;
615 }
616
617 case GNUNET_DNSPARSER_TYPE_SRV: {
618 struct GNUNET_DNSPARSER_SrvRecord srv;
619 char srvbuf[270];
620 char srvtarget[253 + 1];
621 unsigned int priority;
622 unsigned int weight;
623 unsigned int port;
624 size_t off;
625
626 if (4 != sscanf (s, "%u %u %u %253s", &priority, &weight, &port,
627 srvtarget))
628 {
629 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
630 _ ("Unable to parse SRV record `%s'\n"),
631 s);
632 return GNUNET_SYSERR;
633 }
634 srv.priority = (uint16_t) priority;
635 srv.weight = (uint16_t) weight;
636 srv.port = (uint16_t) port;
637 srv.target = srvtarget;
638 off = 0;
639 if (GNUNET_OK !=
640 GNUNET_DNSPARSER_builder_add_srv (srvbuf, sizeof(srvbuf), &off, &srv))
641 {
642 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
643 _ ("Failed to serialize SRV record with target `%s'\n"),
644 srvtarget);
645 return GNUNET_SYSERR;
646 }
647 *data_size = off;
648 *data = GNUNET_malloc (off);
649 GNUNET_memcpy (*data, srvbuf, off);
650 return GNUNET_OK;
651 }
652
653 case GNUNET_DNSPARSER_TYPE_URI: {
654 struct GNUNET_DNSPARSER_UriRecord uri;
655 char target[strlen (s)];
656 unsigned int priority;
657 unsigned int weight;
658 size_t off;
659
660 if (3 != sscanf (s, "%u %u \"%s", &priority, &weight, &target[0])) // only \" befor %s because %s will consume the ending " of the presentation of the URI record
661 {
662 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
663 _ ("Unable to parse URI record `%s'\n"),
664 s);
665 return GNUNET_SYSERR;
666 }
667 target[strlen (target) - 1] = '\0'; // Removing the last " of the presentation of the URI record
668
669 uri.priority = (uint16_t) priority;
670 uri.weight = (uint16_t) weight;
671 uri.target = target;
672 off = 0;
673
674 // TODO add more precise uri checking (RFC3986)
675 if (strstr (target, ":") == NULL ||
676 target[0] == 58 ||
677 target[strlen (target) - 1] == 58)
678 {
679 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
680 _ ("Failed to serialize URI record with target `%s'\n"),
681 target);
682 return GNUNET_SYSERR;
683 }
684
685 char uribuf[sizeof(struct GNUNET_TUN_DnsUriRecord) + strlen (target) + 1];
686
687 if (GNUNET_OK !=
688 GNUNET_DNSPARSER_builder_add_uri (uribuf, sizeof(uribuf), &off, &uri))
689 {
690 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
691 _ ("Failed to serialize URI record with target `%s'\n"),
692 target);
693 return GNUNET_SYSERR;
694 }
695 *data_size = off;
696 *data = GNUNET_malloc (off);
697 GNUNET_memcpy (*data, uribuf, off);
698 return GNUNET_OK;
699 }
700
701 case GNUNET_DNSPARSER_TYPE_TXT:
702 *data = GNUNET_strdup (s);
703 *data_size = strlen (s);
704 return GNUNET_OK;
705
706 case GNUNET_DNSPARSER_TYPE_AAAA:
707 if (1 != inet_pton (AF_INET6, s, &value_aaaa))
708 {
709 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
710 _ ("Unable to parse IPv6 address `%s'\n"),
711 s);
712 return GNUNET_SYSERR;
713 }
714 *data = GNUNET_new (struct in6_addr);
715 *data_size = sizeof(struct in6_addr);
716 GNUNET_memcpy (*data, &value_aaaa, sizeof(value_aaaa));
717 return GNUNET_OK;
718
719 case GNUNET_DNSPARSER_TYPE_SMIMEA:
720 case GNUNET_DNSPARSER_TYPE_TLSA: {
721 unsigned int usage;
722 unsigned int selector;
723 unsigned int matching_type;
724 size_t slen = strlen (s) + 1;
725 char hex[slen];
726
727 if (4 != sscanf (s, "%u %u %u %s", &usage, &selector, &matching_type,
728 hex))
729 {
730 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
731 _ ("Unable to parse TLSA/SMIMEA record string `%s'\n"),
732 s);
733 *data_size = 0;
734 return GNUNET_SYSERR;
735 }
736
737 *data_size = sizeof(struct GNUNET_TUN_DnsTlsaRecord) + strlen (hex) / 2;
738 *data = tlsa = GNUNET_malloc (*data_size);
739 tlsa->usage = (uint8_t) usage;
740 tlsa->selector = (uint8_t) selector;
741 tlsa->matching_type = (uint8_t) matching_type;
742 if (strlen (hex) / 2 != GNUNET_DNSPARSER_hex_to_bin (hex, &tlsa[1]))
743 {
744 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
745 _ ("Unable to parse TLSA/SMIMEA record string `%s'\n"),
746 s);
747 GNUNET_free (*data);
748 *data = NULL;
749 *data_size = 0;
750 return GNUNET_SYSERR;
751 }
752 return GNUNET_OK;
753 }
754
755 case GNUNET_DNSPARSER_TYPE_CAA: { // RFC6844
756 struct GNUNET_DNSPARSER_CaaRecord *caa;
757 unsigned int flags;
758 char tag[15]; // Max tag length 15
759 char value[strlen (s) + 1]; // Should be more than enough
760
761 if (3 != sscanf (s, "%u %s %[^\n]", &flags, tag, value))
762 {
763 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
764 _ ("Unable to parse CAA record string `%s'\n"),
765 s);
766 *data_size = 0;
767 return GNUNET_SYSERR;
768 }
769 *data_size = sizeof(struct GNUNET_DNSPARSER_CaaRecord) + strlen (tag)
770 + strlen (value);
771 *data = caa = GNUNET_malloc (*data_size);
772 caa->flags = flags;
773 memcpy (&caa[1], tag, strlen (tag));
774 caa->tag_len = strlen (tag);
775 memcpy ((char *) &caa[1] + caa->tag_len, value, strlen (value));
776 return GNUNET_OK;
777 }
778
779 default:
780 return GNUNET_SYSERR;
781 }
782}
783
784
785/**
786 * Mapping of record type numbers to human-readable
787 * record type names.
788 */
789static struct
790{
791 const char *name;
792 uint32_t number;
793} name_map[] = { { "A", GNUNET_DNSPARSER_TYPE_A },
794 { "NS", GNUNET_DNSPARSER_TYPE_NS },
795 { "CNAME", GNUNET_DNSPARSER_TYPE_CNAME },
796 { "SOA", GNUNET_DNSPARSER_TYPE_SOA },
797 { "PTR", GNUNET_DNSPARSER_TYPE_PTR },
798 { "MX", GNUNET_DNSPARSER_TYPE_MX },
799 { "TXT", GNUNET_DNSPARSER_TYPE_TXT },
800 { "AAAA", GNUNET_DNSPARSER_TYPE_AAAA },
801 { "SRV", GNUNET_DNSPARSER_TYPE_SRV },
802 { "URI", GNUNET_DNSPARSER_TYPE_URI },
803 { "TLSA", GNUNET_DNSPARSER_TYPE_TLSA },
804 { "SMIMEA", GNUNET_DNSPARSER_TYPE_SMIMEA },
805 { "CERT", GNUNET_DNSPARSER_TYPE_CERT },
806 { "CAA", GNUNET_DNSPARSER_TYPE_CAA },
807 { NULL, UINT32_MAX } };
808
809
810/**
811 * Convert a type name (e.g. "AAAA") to the corresponding number.
812 *
813 * @param cls closure, unused
814 * @param dns_typename name to convert
815 * @return corresponding number, UINT32_MAX on error
816 */
817static uint32_t
818dns_typename_to_number (void *cls, const char *dns_typename)
819{
820 unsigned int i;
821
822 i = 0;
823 while ((NULL != name_map[i].name) &&
824 (0 != strcasecmp (dns_typename, name_map[i].name)))
825 i++;
826 return name_map[i].number;
827}
828
829
830/**
831 * Convert a type number to the corresponding type string (e.g. 1 to "A")
832 *
833 * @param cls closure, unused
834 * @param type number of a type to convert
835 * @return corresponding typestring, NULL on error
836 */
837static const char *
838dns_number_to_typename (void *cls, uint32_t type)
839{
840 unsigned int i;
841
842 i = 0;
843 while ((NULL != name_map[i].name) && (type != name_map[i].number))
844 i++;
845 return name_map[i].name;
846}
847
848
849static enum GNUNET_GenericReturnValue
850dns_is_critical (void *cls, uint32_t type)
851{
852 return GNUNET_NO;
853}
854
855
856/**
857 * Entry point for the plugin.
858 *
859 * @param cls NULL
860 * @return the exported block API
861 */
862void *
863libgnunet_plugin_gnsrecord_dns_init (void *cls)
864{
865 struct GNUNET_GNSRECORD_PluginFunctions *api;
866
867 api = GNUNET_new (struct GNUNET_GNSRECORD_PluginFunctions);
868 api->value_to_string = &dns_value_to_string;
869 api->string_to_value = &dns_string_to_value;
870 api->typename_to_number = &dns_typename_to_number;
871 api->number_to_typename = &dns_number_to_typename;
872 api->is_critical = &dns_is_critical;
873 return api;
874}
875
876
877/**
878 * Exit point from the plugin.
879 *
880 * @param cls the return value from #libgnunet_plugin_block_test_init
881 * @return NULL
882 */
883void *
884libgnunet_plugin_gnsrecord_dns_done (void *cls)
885{
886 struct GNUNET_GNSRECORD_PluginFunctions *api = cls;
887
888 GNUNET_free (api);
889 return NULL;
890}
891
892
893/* end of plugin_gnsrecord_dns.c */
diff --git a/src/plugin/meson.build b/src/plugin/meson.build
new file mode 100644
index 000000000..d4f797c54
--- /dev/null
+++ b/src/plugin/meson.build
@@ -0,0 +1,17 @@
1subdir('block')
2subdir('dns')
3subdir('gnsrecord')
4subdir('datacache')
5subdir('datastore')
6subdir('peerstore')
7subdir('namecache')
8subdir('namestore')
9subdir('dht')
10subdir('seti')
11subdir('setu')
12subdir('regex')
13subdir('revocation')
14subdir('gns')
15subdir('fs')
16subdir('reclaim')
17subdir('messenger')
diff --git a/src/plugin/messenger/Makefile.am b/src/plugin/messenger/Makefile.am
new file mode 100644
index 000000000..e3c69ea33
--- /dev/null
+++ b/src/plugin/messenger/Makefile.am
@@ -0,0 +1,26 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4if USE_COVERAGE
5 AM_CFLAGS = --coverage -O0
6 XLIB = -lgcov
7endif
8
9pkgcfgdir= $(pkgdatadir)/config.d/
10
11libexecdir= $(pkglibdir)/libexec/
12
13plugin_LTLIBRARIES = \
14 libgnunet_plugin_gnsrecord_messenger.la
15
16
17libgnunet_plugin_gnsrecord_messenger_la_SOURCES = \
18 plugin_gnsrecord_messenger.c
19libgnunet_plugin_gnsrecord_messenger_la_LIBADD = \
20 $(top_builddir)/src/lib/util/libgnunetutil.la \
21 $(LTLIBINTL)
22libgnunet_plugin_gnsrecord_messenger_la_LDFLAGS = \
23 $(GN_PLUGIN_LDFLAGS)
24
25
26plugindir = $(libdir)/gnunet
diff --git a/src/plugin/messenger/meson.build b/src/plugin/messenger/meson.build
new file mode 100644
index 000000000..17dd9bd32
--- /dev/null
+++ b/src/plugin/messenger/meson.build
@@ -0,0 +1,7 @@
1shared_module('gnunet_plugin_gnsrecord_messenger',
2 ['plugin_gnsrecord_messenger.c'],
3 dependencies: [libgnunetutil_dep, libgnunetgnsrecord_dep,
4 ],
5 include_directories: [incdir, configuration_inc],
6 install: true,
7 install_dir: get_option('libdir')/'gnunet')
diff --git a/src/plugin/messenger/plugin_gnsrecord_messenger.c b/src/plugin/messenger/plugin_gnsrecord_messenger.c
new file mode 100644
index 000000000..81763e3b5
--- /dev/null
+++ b/src/plugin/messenger/plugin_gnsrecord_messenger.c
@@ -0,0 +1,305 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2021--2024 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @author Tobias Frisch
22 * @file src/messenger/plugin_gnsrecord_messenger.c
23 * @brief Plugin to provide the API for useful GNS records to improve
24 * the usability of the messenger service.
25 */
26
27#include "platform.h"
28#include "gnunet_util_lib.h"
29#include "gnunet_gnsrecord_lib.h"
30#include "gnunet_messenger_service.h"
31#include "gnunet_gnsrecord_plugin.h"
32
33
34/**
35 * Convert the 'value' of a record to a string.
36 *
37 * @param cls closure, unused
38 * @param type type of the record
39 * @param data value in binary encoding
40 * @param data_size number of bytes in @a data
41 * @return NULL on error, otherwise human-readable representation of the value
42 */
43static char *
44messenger_value_to_string (void *cls,
45 uint32_t type,
46 const void *data,
47 size_t data_size)
48{
49 (void) cls;
50 switch (type)
51 {
52 case GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_ENTRY:
53 {
54 if (data_size != sizeof(struct GNUNET_MESSENGER_RoomEntryRecord))
55 {
56 GNUNET_break_op (0);
57 return NULL;
58 }
59
60 const struct GNUNET_MESSENGER_RoomEntryRecord *record = data;
61
62 char *door = GNUNET_CRYPTO_eddsa_public_key_to_string (
63 &(record->door.public_key));
64 char *key = GNUNET_STRINGS_data_to_string_alloc (&(record->key),
65 sizeof(struct
66 GNUNET_HashCode));
67
68 char *ret;
69 GNUNET_asprintf (&ret, "%s-%s", key, door);
70 GNUNET_free (key);
71 GNUNET_free (door);
72 return ret;
73 }
74 case GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_DETAILS:
75 {
76 if (data_size != sizeof(struct GNUNET_MESSENGER_RoomDetailsRecord))
77 {
78 GNUNET_break_op (0);
79 return NULL;
80 }
81
82 const struct GNUNET_MESSENGER_RoomDetailsRecord *record = data;
83
84 char *name = GNUNET_strndup (record->name, 256);
85 char *flags = GNUNET_STRINGS_data_to_string_alloc (&(record->flags),
86 sizeof(uint32_t));
87
88 char *ret;
89 GNUNET_asprintf (&ret, "%s-%s", flags, name);
90 GNUNET_free (flags);
91 GNUNET_free (name);
92 return ret;
93 }
94 default:
95 return NULL;
96 }
97}
98
99
100/**
101 * Convert human-readable version of a 'value' of a record to the binary
102 * representation.
103 *
104 * @param cls closure, unused
105 * @param type type of the record
106 * @param s human-readable string
107 * @param data set to value in binary encoding (will be allocated)
108 * @param data_size set to number of bytes in @a data
109 * @return #GNUNET_OK on success
110 */
111static int
112messenger_string_to_value (void *cls,
113 uint32_t type,
114 const char *s,
115 void **data,
116 size_t *data_size)
117{
118 (void) cls;
119 if (NULL == s)
120 {
121 GNUNET_break (0);
122 return GNUNET_SYSERR;
123 }
124
125 switch (type)
126 {
127 case GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_ENTRY:
128 {
129 char key[104];
130 const char *dash;
131 struct GNUNET_PeerIdentity door;
132
133 if ((NULL == (dash = strchr (s, '-'))) ||
134 (1 != sscanf (s, "%103s-", key)) ||
135 (GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (dash + 1,
136 strlen (
137 dash + 1),
138 &(door.
139 public_key))))
140 {
141 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
142 _ ("Unable to parse MESSENGER_ROOM_ENTRY record `%s'\n"),
143 s);
144 return GNUNET_SYSERR;
145 }
146
147 struct GNUNET_MESSENGER_RoomEntryRecord *record = GNUNET_new (
148 struct GNUNET_MESSENGER_RoomEntryRecord
149 );
150
151 if (GNUNET_OK != GNUNET_STRINGS_string_to_data (key,
152 strlen (key),
153 &(record->key),
154 sizeof(struct
155 GNUNET_HashCode)))
156 {
157 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
158 _ ("Unable to parse MESSENGER_ROOM_ENTRY record `%s'\n"),
159 s);
160 GNUNET_free (record);
161 return GNUNET_SYSERR;
162 }
163
164 record->door = door;
165 *data = record;
166 *data_size = sizeof(struct GNUNET_MESSENGER_RoomEntryRecord);
167 return GNUNET_OK;
168 }
169 case GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_DETAILS:
170 {
171 char flags[8];
172 const char *dash;
173
174 if ((NULL == (dash = strchr (s, '-'))) ||
175 (1 != sscanf (s, "%7s-", flags)) ||
176 (strlen (dash + 1) > 256))
177 {
178 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
179 _ ("Unable to parse MESSENGER_ROOM_DETAILS record `%s'\n"),
180 s);
181 return GNUNET_SYSERR;
182 }
183
184 struct GNUNET_MESSENGER_RoomDetailsRecord *record = GNUNET_new (
185 struct GNUNET_MESSENGER_RoomDetailsRecord
186 );
187
188 if (GNUNET_OK != GNUNET_STRINGS_string_to_data (flags,
189 strlen (flags),
190 &(record->flags),
191 sizeof(uint32_t)))
192 {
193 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
194 _ ("Unable to parse MESSENGER_ROOM_DETAILS record `%s'\n"),
195 s);
196 GNUNET_free (record);
197 return GNUNET_SYSERR;
198 }
199
200 GNUNET_memcpy (record->name, dash + 1, strlen (dash + 1));
201
202 *data = record;
203 *data_size = sizeof(struct GNUNET_MESSENGER_RoomDetailsRecord);
204 return GNUNET_OK;
205 }
206 default:
207 return GNUNET_SYSERR;
208 }
209}
210
211
212/**
213 * Mapping of record type numbers to human-readable
214 * record type names.
215 */
216static struct
217{
218 const char *name;
219 uint32_t number;
220} name_map[] = {
221 { "MESSENGER_ROOM_ENTRY", GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_ENTRY },
222 { "MESSENGER_ROOM_DETAILS", GNUNET_GNSRECORD_TYPE_MESSENGER_ROOM_DETAILS },
223 { NULL, UINT32_MAX }
224};
225
226
227/**
228 * Convert a type name (e.g. "AAAA") to the corresponding number.
229 *
230 * @param cls closure, unused
231 * @param gns_typename name to convert
232 * @return corresponding number, UINT32_MAX on error
233 */
234static uint32_t
235messenger_typename_to_number (void *cls,
236 const char *gns_typename)
237{
238 unsigned int i;
239
240 (void) cls;
241 i = 0;
242 while ((name_map[i].name != NULL) &&
243 (0 != strcasecmp (gns_typename, name_map[i].name)))
244 i++;
245 return name_map[i].number;
246}
247
248
249/**
250 * Convert a type number to the corresponding type string (e.g. 1 to "A")
251 *
252 * @param cls closure, unused
253 * @param type number of a type to convert
254 * @return corresponding typestring, NULL on error
255 */
256static const char *
257messenger_number_to_typename (void *cls,
258 uint32_t type)
259{
260 unsigned int i;
261
262 (void) cls;
263 i = 0;
264 while ((name_map[i].name != NULL) &&
265 (type != name_map[i].number))
266 i++;
267 return name_map[i].name;
268}
269
270
271/**
272 * Entry point for the plugin.
273 *
274 * @param cls NULL
275 * @return the exported block API
276 */
277void *
278libgnunet_plugin_gnsrecord_messenger_init (void *cls)
279{
280 struct GNUNET_GNSRECORD_PluginFunctions *api;
281
282 (void) cls;
283 api = GNUNET_new (struct GNUNET_GNSRECORD_PluginFunctions);
284 api->value_to_string = &messenger_value_to_string;
285 api->string_to_value = &messenger_string_to_value;
286 api->typename_to_number = &messenger_typename_to_number;
287 api->number_to_typename = &messenger_number_to_typename;
288 return api;
289}
290
291
292/**
293 * Exit point from the plugin.
294 *
295 * @param cls the return value from #libgnunet_plugin_block_test_init
296 * @return NULL
297 */
298void *
299libgnunet_plugin_gnsrecord_messenger_done (void *cls)
300{
301 struct GNUNET_GNSRECORD_PluginFunctions *api = cls;
302
303 GNUNET_free (api);
304 return NULL;
305}
diff --git a/src/plugin/namecache/Makefile.am b/src/plugin/namecache/Makefile.am
new file mode 100644
index 000000000..bf8fc9b8d
--- /dev/null
+++ b/src/plugin/namecache/Makefile.am
@@ -0,0 +1,108 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include $(POSTGRESQL_CPPFLAGS)
3
4plugindir = $(libdir)/gnunet
5
6pkgcfgdir= $(pkgdatadir)/config.d/
7
8libexecdir= $(pkglibdir)/libexec/
9
10sqldir = $(prefix)/share/gnunet/sql/
11
12sql_DATA = \
13 namecache-0001.sql \
14 namecache-drop.sql
15
16
17if USE_COVERAGE
18 AM_CFLAGS = --coverage -O0
19 XLIBS = -lgcov
20endif
21
22if HAVE_EXPERIMENTAL
23FLAT_PLUGIN = libgnunet_plugin_namecache_flat.la
24FLAT_TESTS = test_plugin_namecache_flat
25endif
26
27if HAVE_SQLITE
28SQLITE_PLUGIN = libgnunet_plugin_namecache_sqlite.la
29SQLITE_TESTS = test_plugin_namecache_sqlite
30endif
31
32if HAVE_POSTGRESQL
33POSTGRES_PLUGIN = libgnunet_plugin_namecache_postgres.la
34POSTGRES_TESTS = test_plugin_namecache_postgres
35endif
36
37if HAVE_SQLITE
38check_PROGRAMS = \
39 $(SQLITE_TESTS) \
40 $(POSTGRES_TESTS) \
41 $(FLAT_TESTS) \
42 $(TESTING_TESTS)
43endif
44
45if ENABLE_TEST_RUN
46AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
47TESTS = \
48 $(check_PROGRAMS)
49endif
50
51plugin_LTLIBRARIES = \
52 $(SQLITE_PLUGIN) \
53 $(FLAT_PLUGIN) \
54 $(POSTGRES_PLUGIN)
55
56libgnunet_plugin_namecache_flat_la_SOURCES = \
57 plugin_namecache_flat.c
58libgnunet_plugin_namecache_flat_la_LIBADD = \
59 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
60 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) \
61 $(LTLIBINTL)
62libgnunet_plugin_namecache_flat_la_LDFLAGS = \
63 $(GN_PLUGIN_LDFLAGS)
64
65libgnunet_plugin_namecache_sqlite_la_SOURCES = \
66 plugin_namecache_sqlite.c
67libgnunet_plugin_namecache_sqlite_la_LIBADD = \
68 $(top_builddir)/src/lib/sq/libgnunetsq.la \
69 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
70 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) -lsqlite3 \
71 $(LTLIBINTL)
72libgnunet_plugin_namecache_sqlite_la_LDFLAGS = \
73 $(GN_PLUGIN_LDFLAGS)
74
75
76libgnunet_plugin_namecache_postgres_la_SOURCES = \
77 plugin_namecache_postgres.c
78libgnunet_plugin_namecache_postgres_la_LIBADD = \
79 $(top_builddir)/src/lib/pq/libgnunetpq.la \
80 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
81 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) -lpq \
82 $(LTLIBINTL)
83libgnunet_plugin_namecache_postgres_la_LDFLAGS = \
84 $(GN_PLUGIN_LDFLAGS) $(POSTGRESQL_LDFLAGS)
85
86test_plugin_namecache_flat_SOURCES = \
87 test_plugin_namecache.c
88test_plugin_namecache_flat_LDADD = \
89 $(top_builddir)/src/service/testing/libgnunettesting.la \
90 $(top_builddir)/src/lib/util/libgnunetutil.la
91
92test_plugin_namecache_sqlite_SOURCES = \
93 test_plugin_namecache.c
94test_plugin_namecache_sqlite_LDADD = \
95 $(top_builddir)/src/service/testing/libgnunettesting.la \
96 $(top_builddir)/src/lib/util/libgnunetutil.la
97
98test_plugin_namecache_postgres_SOURCES = \
99 test_plugin_namecache.c
100test_plugin_namecache_postgres_LDADD = \
101 $(top_builddir)/src/service/testing/libgnunettesting.la \
102 $(top_builddir)/src/lib/util/libgnunetutil.la
103
104EXTRA_DIST = \
105 test_plugin_namecache_sqlite.conf \
106 test_plugin_namecache_postgres.conf \
107 test_plugin_namecache_flat.conf \
108 $(sql_DATA)
diff --git a/src/plugin/namecache/meson.build b/src/plugin/namecache/meson.build
new file mode 100644
index 000000000..51504314d
--- /dev/null
+++ b/src/plugin/namecache/meson.build
@@ -0,0 +1,22 @@
1shared_module('gnunet_plugin_namecache_sqlite',
2 ['plugin_namecache_sqlite.c'],
3 dependencies: [libgnunetutil_dep,
4 libgnunetgnsrecord_dep,
5 sqlite_dep,
6 libgnunetsq_dep],
7 include_directories: [incdir, configuration_inc],
8 install: true,
9 install_dir: get_option('libdir')/'gnunet')
10
11if pq_dep.found()
12 shared_module('gnunet_plugin_namecache_postgres',
13 ['plugin_namecache_postgres.c'],
14 dependencies: [libgnunetutil_dep,
15 libgnunetgnsrecord_dep,
16 pq_dep,
17 libgnunetpq_dep],
18 include_directories: [incdir, configuration_inc],
19 install: true,
20 install_dir: get_option('libdir')/'gnunet')
21endif
22
diff --git a/src/plugin/namecache/namecache-0001.sql b/src/plugin/namecache/namecache-0001.sql
new file mode 100644
index 000000000..8509b078f
--- /dev/null
+++ b/src/plugin/namecache/namecache-0001.sql
@@ -0,0 +1,42 @@
1--
2-- This file is part of GNUnet
3-- Copyright (C) 2014--2022 GNUnet e.V.
4--
5-- GNUnet is free software; you can redistribute it and/or modify it under the
6-- terms of the GNU General Public License as published by the Free Software
7-- Foundation; either version 3, or (at your option) any later version.
8--
9-- GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY
10-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12--
13-- You should have received a copy of the GNU General Public License along with
14-- GNUnet; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15--
16
17-- Everything in one big transaction
18BEGIN;
19
20-- Check patch versioning is in place.
21SELECT _v.register_patch('namecache-0001', NULL, NULL);
22
23-------------------- Schema ----------------------------
24
25CREATE SCHEMA datacache;
26COMMENT ON SCHEMA datacache IS 'gnunet-datacache data';
27
28SET search_path TO datacache;
29
30CREATE TABLE IF NOT EXISTS ns096blocks (
31 query BYTEA NOT NULL DEFAULT '',
32 block BYTEA NOT NULL DEFAULT '',
33 expiration_time BIGINT NOT NULL DEFAULT 0);
34
35CREATE INDEX ir_query_hash
36 ON ns096blocks (query,expiration_time);
37
38CREATE INDEX ir_block_expiration
39 ON ns096blocks (expiration_time);
40
41
42COMMIT;
diff --git a/src/plugin/namecache/namecache-drop.sql b/src/plugin/namecache/namecache-drop.sql
new file mode 100644
index 000000000..197ee78c1
--- /dev/null
+++ b/src/plugin/namecache/namecache-drop.sql
@@ -0,0 +1,25 @@
1--
2-- This file is part of GNUnet
3-- Copyright (C) 2014--2022 GNUnet e.V.
4--
5-- GNUnet is free software; you can redistribute it and/or modify it under the
6-- terms of the GNU General Public License as published by the Free Software
7-- Foundation; either version 3, or (at your option) any later version.
8--
9-- GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY
10-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12--
13-- You should have received a copy of the GNU General Public License along with
14-- GNUnet; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15--
16
17-- Everything in one big transaction
18BEGIN;
19
20
21SELECT _v.unregister_patch('namecache-0001');
22
23DROP SCHEMA namecache CASCADE;
24
25COMMIT;
diff --git a/src/plugin/namecache/plugin_namecache_flat.c b/src/plugin/namecache/plugin_namecache_flat.c
new file mode 100644
index 000000000..ba118bf02
--- /dev/null
+++ b/src/plugin/namecache/plugin_namecache_flat.c
@@ -0,0 +1,429 @@
1/*
2 * This file is part of GNUnet
3 * Copyright (C) 2009-2015 GNUnet e.V.
4 *
5 * GNUnet is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License,
8 * or (at your option) any later version.
9 *
10 * GNUnet is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file namecache/plugin_namecache_flat.c
23 * @brief flat file-based namecache backend
24 * @author Martin Schanzenbach
25 */
26
27#include "platform.h"
28#include "gnunet_namecache_plugin.h"
29#include "gnunet_namecache_service.h"
30#include "gnunet_gnsrecord_lib.h"
31
32/**
33 * Context for all functions in this plugin.
34 */
35struct Plugin
36{
37 const struct GNUNET_CONFIGURATION_Handle *cfg;
38
39 /**
40 * Database filename.
41 */
42 char *fn;
43
44 /**
45 * HashMap
46 */
47 struct GNUNET_CONTAINER_MultiHashMap *hm;
48};
49
50struct FlatFileEntry
51{
52 /**
53 * Block
54 */
55 struct GNUNET_GNSRECORD_Block *block;
56
57 /**
58 * query
59 */
60 struct GNUNET_HashCode query;
61};
62
63/**
64 * Initialize the database connections and associated
65 * data structures (create tables and indices
66 * as needed as well).
67 *
68 * @param plugin the plugin context (state for this module)
69 * @return #GNUNET_OK on success
70 */
71static int
72database_setup (struct Plugin *plugin)
73{
74 char *afsdir;
75 char*block_buffer;
76 char*buffer;
77 char*line;
78 char*query;
79 char*block;
80 uint64_t size;
81 struct FlatFileEntry *entry;
82 struct GNUNET_DISK_FileHandle *fh;
83
84 if (GNUNET_OK !=
85 GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
86 "namecache-flat",
87 "FILENAME",
88 &afsdir))
89 {
90 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
91 "namecache-flat", "FILENAME");
92 return GNUNET_SYSERR;
93 }
94 if (GNUNET_OK != GNUNET_DISK_file_test (afsdir))
95 {
96 if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (afsdir))
97 {
98 GNUNET_break (0);
99 GNUNET_free (afsdir);
100 return GNUNET_SYSERR;
101 }
102 }
103 /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
104 plugin->fn = afsdir;
105
106 /* Load data from file into hashmap */
107 plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
108 GNUNET_NO);
109 fh = GNUNET_DISK_file_open (afsdir,
110 GNUNET_DISK_OPEN_CREATE
111 | GNUNET_DISK_OPEN_READWRITE,
112 GNUNET_DISK_PERM_USER_WRITE
113 | GNUNET_DISK_PERM_USER_READ);
114 if (NULL == fh)
115 {
116 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
117 _ ("Unable to initialize file: %s.\n"),
118 afsdir);
119 return GNUNET_SYSERR;
120 }
121
122 if (GNUNET_SYSERR == GNUNET_DISK_file_size (afsdir,
123 &size,
124 GNUNET_YES,
125 GNUNET_YES))
126 {
127 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
128 _ ("Unable to get filesize: %s.\n"),
129 afsdir);
130 GNUNET_DISK_file_close (fh);
131 return GNUNET_SYSERR;
132 }
133
134 if (0 == size)
135 {
136 GNUNET_DISK_file_close (fh);
137 return GNUNET_OK;
138 }
139
140 buffer = GNUNET_malloc (size + 1);
141
142 if (GNUNET_SYSERR == GNUNET_DISK_file_read (fh,
143 buffer,
144 size))
145 {
146 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
147 _ ("Unable to read file: %s.\n"),
148 afsdir);
149 GNUNET_free (buffer);
150 GNUNET_DISK_file_close (fh);
151 return GNUNET_SYSERR;
152 }
153 buffer[size] = '\0';
154
155 GNUNET_DISK_file_close (fh);
156 if (0 < size)
157 {
158 line = strtok (buffer, "\n");
159 while (line != NULL)
160 {
161 query = strtok (line, ",");
162 if (NULL == query)
163 break;
164 block = strtok (NULL, ",");
165 if (NULL == block)
166 break;
167 line = strtok (NULL, "\n");
168 entry = GNUNET_malloc (sizeof(struct FlatFileEntry));
169 GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_hash_from_string (query,
170 &entry->query));
171 GNUNET_STRINGS_base64_decode (block,
172 strlen (block),
173 (void **) &block_buffer);
174 entry->block = (struct GNUNET_GNSRECORD_Block *) block_buffer;
175 if (GNUNET_OK !=
176 GNUNET_CONTAINER_multihashmap_put (plugin->hm,
177 &entry->query,
178 entry,
179 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
180 {
181 GNUNET_free (entry);
182 GNUNET_break (0);
183 }
184 }
185 }
186 GNUNET_free (buffer);
187 return GNUNET_OK;
188}
189
190
191/**
192 * Store values in hashmap in file and free data
193 *
194 * @param plugin the plugin context
195 */
196static int
197store_and_free_entries (void *cls,
198 const struct GNUNET_HashCode *key,
199 void *value)
200{
201 struct GNUNET_DISK_FileHandle *fh = cls;
202 struct FlatFileEntry *entry = value;
203
204 char *line;
205 char *block_b64;
206 struct GNUNET_CRYPTO_HashAsciiEncoded query;
207 size_t block_size;
208
209 block_size = GNUNET_GNSRECORD_block_get_size (entry->block);
210 GNUNET_STRINGS_base64_encode ((char *) entry->block,
211 block_size,
212 &block_b64);
213 GNUNET_CRYPTO_hash_to_enc (&entry->query,
214 &query);
215 GNUNET_asprintf (&line,
216 "%s,%s\n",
217 (char *) &query,
218 block_b64);
219
220 GNUNET_free (block_b64);
221
222 GNUNET_DISK_file_write (fh,
223 line,
224 strlen (line));
225
226 GNUNET_free (entry->block);
227 GNUNET_free (entry);
228 GNUNET_free (line);
229 return GNUNET_YES;
230}
231
232
233/**
234 * Shutdown database connection and associate data
235 * structures.
236 * @param plugin the plugin context (state for this module)
237 */
238static void
239database_shutdown (struct Plugin *plugin)
240{
241 struct GNUNET_DISK_FileHandle *fh;
242
243 fh = GNUNET_DISK_file_open (plugin->fn,
244 GNUNET_DISK_OPEN_CREATE
245 | GNUNET_DISK_OPEN_TRUNCATE
246 | GNUNET_DISK_OPEN_READWRITE,
247 GNUNET_DISK_PERM_USER_WRITE
248 | GNUNET_DISK_PERM_USER_READ);
249 if (NULL == fh)
250 {
251 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
252 _ ("Unable to initialize file: %s.\n"),
253 plugin->fn);
254 return;
255 }
256
257 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
258 &store_and_free_entries,
259 fh);
260 GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
261 GNUNET_DISK_file_close (fh);
262}
263
264
265static int
266expire_blocks (void *cls,
267 const struct GNUNET_HashCode *key,
268 void *value)
269{
270 struct Plugin *plugin = cls;
271 struct FlatFileEntry *entry = value;
272 struct GNUNET_TIME_Absolute now;
273 struct GNUNET_TIME_Absolute expiration;
274
275 now = GNUNET_TIME_absolute_get ();
276 expiration = GNUNET_GNSRECORD_block_get_expiration (entry->block);
277
278 if (0 == GNUNET_TIME_absolute_get_difference (now,
279 expiration).rel_value_us)
280 {
281 GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, key);
282 }
283 return GNUNET_YES;
284}
285
286
287/**
288 * Removes any expired block.
289 *
290 * @param plugin the plugin
291 */
292static void
293namecache_expire_blocks (struct Plugin *plugin)
294{
295 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
296 &expire_blocks,
297 plugin);
298}
299
300
301/**
302 * Cache a block in the datastore.
303 *
304 * @param cls closure (internal context for the plugin)
305 * @param block block to cache
306 * @return #GNUNET_OK on success, else #GNUNET_SYSERR
307 */
308static int
309namecache_cache_block (void *cls,
310 const struct GNUNET_GNSRECORD_Block *block)
311{
312 struct Plugin *plugin = cls;
313 struct GNUNET_HashCode query;
314 struct FlatFileEntry *entry;
315 size_t block_size;
316
317 namecache_expire_blocks (plugin);
318 GNUNET_GNSRECORD_query_from_block (block,
319 &query);
320 block_size = GNUNET_GNSRECORD_block_get_size (block);
321 if (block_size > 64 * 65536)
322 {
323 GNUNET_break (0);
324 return GNUNET_SYSERR;
325 }
326 entry = GNUNET_malloc (sizeof(struct FlatFileEntry));
327 entry->block = GNUNET_malloc (block_size);
328 GNUNET_memcpy (entry->block, block, block_size);
329 GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, &query);
330 if (GNUNET_OK !=
331 GNUNET_CONTAINER_multihashmap_put (plugin->hm,
332 &query,
333 entry,
334 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
335 {
336 GNUNET_free (entry);
337 GNUNET_break (0);
338 return GNUNET_SYSERR;
339 }
340 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
341 "Caching block under derived key `%s'\n",
342 GNUNET_h2s_full (&query));
343 return GNUNET_OK;
344}
345
346
347/**
348 * Get the block for a particular zone and label in the
349 * datastore. Will return at most one result to the iterator.
350 *
351 * @param cls closure (internal context for the plugin)
352 * @param query hash of public key derived from the zone and the label
353 * @param iter function to call with the result
354 * @param iter_cls closure for @a iter
355 * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
356 */
357static int
358namecache_lookup_block (void *cls,
359 const struct GNUNET_HashCode *query,
360 GNUNET_NAMECACHE_BlockCallback iter, void *iter_cls)
361{
362 struct Plugin *plugin = cls;
363 const struct GNUNET_GNSRECORD_Block *block;
364
365 block = GNUNET_CONTAINER_multihashmap_get (plugin->hm, query);
366 if (NULL == block)
367 return GNUNET_NO;
368 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
369 "Found block under derived key `%s'\n",
370 GNUNET_h2s_full (query));
371 iter (iter_cls, block);
372 return GNUNET_YES;
373}
374
375
376/**
377 * Entry point for the plugin.
378 *
379 * @param cls the "struct GNUNET_NAMECACHE_PluginEnvironment*"
380 * @return NULL on error, otherwise the plugin context
381 */
382void *
383libgnunet_plugin_namecache_flat_init (void *cls)
384{
385 static struct Plugin plugin;
386 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
387 struct GNUNET_NAMECACHE_PluginFunctions *api;
388
389 if (NULL != plugin.cfg)
390 return NULL; /* can only initialize once! */
391 memset (&plugin, 0, sizeof(struct Plugin));
392 plugin.cfg = cfg;
393 if (GNUNET_OK != database_setup (&plugin))
394 {
395 database_shutdown (&plugin);
396 return NULL;
397 }
398 api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
399 api->cls = &plugin;
400 api->cache_block = &namecache_cache_block;
401 api->lookup_block = &namecache_lookup_block;
402 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
403 _ ("flat plugin running\n"));
404 return api;
405}
406
407
408/**
409 * Exit point from the plugin.
410 *
411 * @param cls the plugin context (as returned by "init")
412 * @return always NULL
413 */
414void *
415libgnunet_plugin_namecache_flat_done (void *cls)
416{
417 struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
418 struct Plugin *plugin = api->cls;
419
420 database_shutdown (plugin);
421 plugin->cfg = NULL;
422 GNUNET_free (api);
423 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
424 "flat plugin is finished\n");
425 return NULL;
426}
427
428
429/* end of plugin_namecache_sqlite.c */
diff --git a/src/plugin/namecache/plugin_namecache_postgres.c b/src/plugin/namecache/plugin_namecache_postgres.c
new file mode 100644
index 000000000..7e2925d1a
--- /dev/null
+++ b/src/plugin/namecache/plugin_namecache_postgres.c
@@ -0,0 +1,315 @@
1/*
2 * This file is part of GNUnet
3 * Copyright (C) 2009-2013, 2016, 2017, 2022 GNUnet e.V.
4 *
5 * GNUnet is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License,
8 * or (at your option) any later version.
9 *
10 * GNUnet is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file namecache/plugin_namecache_postgres.c
23 * @brief postgres-based namecache backend
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_namecache_plugin.h"
28#include "gnunet_namecache_service.h"
29#include "gnunet_gnsrecord_lib.h"
30#include "gnunet_pq_lib.h"
31
32
33#define LOG(kind, ...) GNUNET_log_from (kind, "namecache-postgres", __VA_ARGS__)
34
35
36/**
37 * Context for all functions in this plugin.
38 */
39struct Plugin
40{
41 const struct GNUNET_CONFIGURATION_Handle *cfg;
42
43 /**
44 * Postgres database handle.
45 */
46 struct GNUNET_PQ_Context *dbh;
47};
48
49
50/**
51 * Initialize the database connections and associated
52 * data structures (create tables and indices
53 * as needed as well).
54 *
55 * @param plugin the plugin context (state for this module)
56 * @return #GNUNET_OK on success
57 */
58static enum GNUNET_GenericReturnValue
59database_setup (struct Plugin *plugin)
60{
61 struct GNUNET_PQ_PreparedStatement ps[] = {
62 GNUNET_PQ_make_prepare ("cache_block",
63 "INSERT INTO namecache.ns096blocks"
64 " (query, block, expiration_time)"
65 " VALUES"
66 " ($1, $2, $3)"),
67 GNUNET_PQ_make_prepare ("expire_blocks",
68 "DELETE FROM namecache.ns096blocks"
69 " WHERE expiration_time<$1"),
70 GNUNET_PQ_make_prepare ("delete_block",
71 "DELETE FROM namecache.ns096blocks"
72 " WHERE query=$1 AND expiration_time<=$2"),
73 GNUNET_PQ_make_prepare ("lookup_block",
74 "SELECT block"
75 " FROM namecache.ns096blocks"
76 " WHERE query=$1"
77 " ORDER BY expiration_time DESC LIMIT 1"),
78 GNUNET_PQ_PREPARED_STATEMENT_END
79 };
80
81 plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
82 "namecache-postgres",
83 "namecache-",
84 NULL,
85 ps);
86 if (NULL == plugin->dbh)
87 return GNUNET_SYSERR;
88 return GNUNET_OK;
89}
90
91
92/**
93 * Removes any expired block.
94 *
95 * @param plugin the plugin
96 */
97static void
98namecache_postgres_expire_blocks (struct Plugin *plugin)
99{
100 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
101 struct GNUNET_PQ_QueryParam params[] = {
102 GNUNET_PQ_query_param_absolute_time (&now),
103 GNUNET_PQ_query_param_end
104 };
105 enum GNUNET_DB_QueryStatus res;
106
107 res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
108 "expire_blocks",
109 params);
110 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != res);
111}
112
113
114/**
115 * Delete older block in the datastore.
116 *
117 * @param plugin the plugin
118 * @param query query for the block
119 * @param expiration_time how old does the block have to be for deletion
120 */
121static void
122delete_old_block (struct Plugin *plugin,
123 const struct GNUNET_HashCode *query,
124 struct GNUNET_TIME_Absolute expiration_time)
125{
126 struct GNUNET_PQ_QueryParam params[] = {
127 GNUNET_PQ_query_param_auto_from_type (query),
128 GNUNET_PQ_query_param_absolute_time (&expiration_time),
129 GNUNET_PQ_query_param_end
130 };
131 enum GNUNET_DB_QueryStatus res;
132
133 res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
134 "delete_block",
135 params);
136 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != res);
137}
138
139
140/**
141 * Cache a block in the datastore.
142 *
143 * @param cls closure (internal context for the plugin)
144 * @param block block to cache
145 * @return #GNUNET_OK on success, else #GNUNET_SYSERR
146 */
147static enum GNUNET_GenericReturnValue
148namecache_postgres_cache_block (void *cls,
149 const struct GNUNET_GNSRECORD_Block *block)
150{
151 struct Plugin *plugin = cls;
152 struct GNUNET_HashCode query;
153 size_t block_size = GNUNET_GNSRECORD_block_get_size (block);
154 struct GNUNET_TIME_Absolute exp;
155 exp = GNUNET_GNSRECORD_block_get_expiration (block);
156 struct GNUNET_PQ_QueryParam params[] = {
157 GNUNET_PQ_query_param_auto_from_type (&query),
158 GNUNET_PQ_query_param_fixed_size (block, block_size),
159 GNUNET_PQ_query_param_absolute_time (&exp),
160 GNUNET_PQ_query_param_end
161 };
162 enum GNUNET_DB_QueryStatus res;
163
164 namecache_postgres_expire_blocks (plugin);
165 GNUNET_GNSRECORD_query_from_block (block,
166 &query);
167 if (block_size > 64 * 65536)
168 {
169 GNUNET_break (0);
170 return GNUNET_SYSERR;
171 }
172 delete_old_block (plugin,
173 &query,
174 exp);
175
176 res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
177 "cache_block",
178 params);
179 if (0 > res)
180 return GNUNET_SYSERR;
181 return GNUNET_OK;
182}
183
184
185/**
186 * Get the block for a particular zone and label in the
187 * datastore. Will return at most one result to the iterator.
188 *
189 * @param cls closure (internal context for the plugin)
190 * @param query hash of public key derived from the zone and the label
191 * @param iter function to call with the result
192 * @param iter_cls closure for @a iter
193 * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
194 */
195static enum GNUNET_GenericReturnValue
196namecache_postgres_lookup_block (void *cls,
197 const struct GNUNET_HashCode *query,
198 GNUNET_NAMECACHE_BlockCallback iter,
199 void *iter_cls)
200{
201 struct Plugin *plugin = cls;
202 size_t bsize;
203 struct GNUNET_GNSRECORD_Block *block;
204 struct GNUNET_PQ_QueryParam params[] = {
205 GNUNET_PQ_query_param_auto_from_type (query),
206 GNUNET_PQ_query_param_end
207 };
208 struct GNUNET_PQ_ResultSpec rs[] = {
209 GNUNET_PQ_result_spec_variable_size ("block",
210 (void **) &block,
211 &bsize),
212 GNUNET_PQ_result_spec_end
213 };
214 enum GNUNET_DB_QueryStatus res;
215
216 res = GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh,
217 "lookup_block",
218 params,
219 rs);
220 if (0 > res)
221 {
222 LOG (GNUNET_ERROR_TYPE_WARNING,
223 "Failing lookup block in namecache (postgres error)\n");
224 return GNUNET_SYSERR;
225 }
226 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
227 {
228 /* no result */
229 LOG (GNUNET_ERROR_TYPE_DEBUG,
230 "Ending iteration (no more results)\n");
231 return GNUNET_NO;
232 }
233 if ((bsize < sizeof(*block)))
234 {
235 GNUNET_break (0);
236 LOG (GNUNET_ERROR_TYPE_DEBUG,
237 "Failing lookup (corrupt block)\n");
238 GNUNET_PQ_cleanup_result (rs);
239 return GNUNET_SYSERR;
240 }
241 iter (iter_cls,
242 block);
243 GNUNET_PQ_cleanup_result (rs);
244 return GNUNET_OK;
245}
246
247
248/**
249 * Shutdown database connection and associate data
250 * structures.
251 *
252 * @param plugin the plugin context (state for this module)
253 */
254static void
255database_shutdown (struct Plugin *plugin)
256{
257 GNUNET_PQ_disconnect (plugin->dbh);
258 plugin->dbh = NULL;
259}
260
261
262/**
263 * Entry point for the plugin.
264 *
265 * @param cls the `struct GNUNET_NAMECACHE_PluginEnvironment *`
266 * @return NULL on error, otherwise the plugin context
267 */
268void *
269libgnunet_plugin_namecache_postgres_init (void *cls)
270{
271 static struct Plugin plugin;
272 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
273 struct GNUNET_NAMECACHE_PluginFunctions *api;
274
275 if (NULL != plugin.cfg)
276 return NULL; /* can only initialize once! */
277 memset (&plugin, 0, sizeof(struct Plugin));
278 plugin.cfg = cfg;
279 if (GNUNET_OK != database_setup (&plugin))
280 {
281 database_shutdown (&plugin);
282 return NULL;
283 }
284 api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
285 api->cls = &plugin;
286 api->cache_block = &namecache_postgres_cache_block;
287 api->lookup_block = &namecache_postgres_lookup_block;
288 LOG (GNUNET_ERROR_TYPE_INFO,
289 "Postgres namecache plugin running\n");
290 return api;
291}
292
293
294/**
295 * Exit point from the plugin.
296 *
297 * @param cls the plugin context (as returned by "init")
298 * @return always NULL
299 */
300void *
301libgnunet_plugin_namecache_postgres_done (void *cls)
302{
303 struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
304 struct Plugin *plugin = api->cls;
305
306 database_shutdown (plugin);
307 plugin->cfg = NULL;
308 GNUNET_free (api);
309 LOG (GNUNET_ERROR_TYPE_DEBUG,
310 "Postgres namecache plugin is finished\n");
311 return NULL;
312}
313
314
315/* end of plugin_namecache_postgres.c */
diff --git a/src/plugin/namecache/plugin_namecache_sqlite.c b/src/plugin/namecache/plugin_namecache_sqlite.c
new file mode 100644
index 000000000..d8b485381
--- /dev/null
+++ b/src/plugin/namecache/plugin_namecache_sqlite.c
@@ -0,0 +1,589 @@
1/*
2 * This file is part of GNUnet
3 * Copyright (C) 2009-2013 GNUnet e.V.
4 *
5 * GNUnet is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License,
8 * or (at your option) any later version.
9 *
10 * GNUnet is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file namecache/plugin_namecache_sqlite.c
23 * @brief sqlite-based namecache backend
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_sq_lib.h"
28#include "gnunet_namecache_plugin.h"
29#include "gnunet_namecache_service.h"
30#include "gnunet_gnsrecord_lib.h"
31#include <sqlite3.h>
32
33/**
34 * After how many ms "busy" should a DB operation fail for good? A
35 * low value makes sure that we are more responsive to requests
36 * (especially PUTs). A high value guarantees a higher success rate
37 * (SELECTs in iterate can take several seconds despite LIMIT=1).
38 *
39 * The default value of 1s should ensure that users do not experience
40 * huge latencies while at the same time allowing operations to
41 * succeed with reasonable probability.
42 */
43#define BUSY_TIMEOUT_MS 1000
44
45
46/**
47 * Log an error message at log-level 'level' that indicates
48 * a failure of the command 'cmd' on file 'filename'
49 * with the message given by strerror(errno).
50 */
51#define LOG_SQLITE(db, level, cmd) do { GNUNET_log_from (level, \
52 "namecache-sqlite", _ ( \
53 "`%s' failed at %s:%d with error: %s\n"), \
54 cmd, \
55 __FILE__, __LINE__, \
56 sqlite3_errmsg ( \
57 db->dbh)); \
58} while (0)
59
60#define LOG(kind, ...) GNUNET_log_from (kind, "namecache-sqlite", __VA_ARGS__)
61
62
63/**
64 * Context for all functions in this plugin.
65 */
66struct Plugin
67{
68 const struct GNUNET_CONFIGURATION_Handle *cfg;
69
70 /**
71 * Database filename.
72 */
73 char *fn;
74
75 /**
76 * Native SQLite database handle.
77 */
78 sqlite3 *dbh;
79
80 /**
81 * Precompiled SQL for caching a block
82 */
83 sqlite3_stmt *cache_block;
84
85 /**
86 * Precompiled SQL for deleting an older block
87 */
88 sqlite3_stmt *delete_block;
89
90 /**
91 * Precompiled SQL for looking up a block
92 */
93 sqlite3_stmt *lookup_block;
94
95 /**
96 * Precompiled SQL for removing expired blocks
97 */
98 sqlite3_stmt *expire_blocks;
99};
100
101
102/**
103 * Initialize the database connections and associated
104 * data structures (create tables and indices
105 * as needed as well).
106 *
107 * @param plugin the plugin context (state for this module)
108 * @return #GNUNET_OK on success
109 */
110static int
111database_setup (struct Plugin *plugin)
112{
113 struct GNUNET_SQ_ExecuteStatement es[] = {
114 GNUNET_SQ_make_try_execute ("PRAGMA temp_store=MEMORY"),
115 GNUNET_SQ_make_try_execute ("PRAGMA synchronous=NORMAL"),
116 GNUNET_SQ_make_try_execute ("PRAGMA legacy_file_format=OFF"),
117 GNUNET_SQ_make_try_execute ("PRAGMA auto_vacuum=INCREMENTAL"),
118 GNUNET_SQ_make_try_execute ("PRAGMA encoding=\"UTF-8\""),
119 GNUNET_SQ_make_try_execute ("PRAGMA locking_mode=EXCLUSIVE"),
120 GNUNET_SQ_make_try_execute ("PRAGMA page_size=4092"),
121 GNUNET_SQ_make_try_execute ("PRAGMA journal_mode=WAL"),
122 GNUNET_SQ_make_execute ("CREATE TABLE IF NOT EXISTS ns096blocks ("
123 " query BLOB NOT NULL,"
124 " block BLOB NOT NULL,"
125 " expiration_time INT8 NOT NULL"
126 ")"),
127 GNUNET_SQ_make_execute ("CREATE INDEX IF NOT EXISTS ir_query_hash "
128 "ON ns096blocks (query,expiration_time)"),
129 GNUNET_SQ_make_execute ("CREATE INDEX IF NOT EXISTS ir_block_expiration "
130 "ON ns096blocks (expiration_time)"),
131 GNUNET_SQ_EXECUTE_STATEMENT_END
132 };
133 struct GNUNET_SQ_PrepareStatement ps[] = {
134 GNUNET_SQ_make_prepare (
135 "INSERT INTO ns096blocks (query,block,expiration_time) VALUES (?, ?, ?)",
136 &plugin->cache_block),
137 GNUNET_SQ_make_prepare ("DELETE FROM ns096blocks WHERE expiration_time<?",
138 &plugin->expire_blocks),
139 GNUNET_SQ_make_prepare (
140 "DELETE FROM ns096blocks WHERE query=? AND expiration_time<=?",
141 &plugin->delete_block),
142 GNUNET_SQ_make_prepare ("SELECT block FROM ns096blocks WHERE query=? "
143 "ORDER BY expiration_time DESC LIMIT 1",
144 &plugin->lookup_block),
145 GNUNET_SQ_PREPARE_END
146 };
147 char *afsdir;
148
149 if (GNUNET_OK !=
150 GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
151 "namecache-sqlite",
152 "FILENAME",
153 &afsdir))
154 {
155 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
156 "namecache-sqlite",
157 "FILENAME");
158 return GNUNET_SYSERR;
159 }
160 if (GNUNET_OK !=
161 GNUNET_DISK_file_test (afsdir))
162 {
163 if (GNUNET_OK !=
164 GNUNET_DISK_directory_create_for_file (afsdir))
165 {
166 GNUNET_break (0);
167 GNUNET_free (afsdir);
168 return GNUNET_SYSERR;
169 }
170 }
171 /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
172 plugin->fn = afsdir;
173
174 /* Open database and precompile statements */
175 if (SQLITE_OK !=
176 sqlite3_open (plugin->fn, &plugin->dbh))
177 {
178 LOG (GNUNET_ERROR_TYPE_ERROR,
179 _ ("Unable to initialize SQLite: %s.\n"),
180 sqlite3_errmsg (plugin->dbh));
181 return GNUNET_SYSERR;
182 }
183 if (GNUNET_OK !=
184 GNUNET_SQ_exec_statements (plugin->dbh,
185 es))
186 {
187 GNUNET_break (0);
188 LOG (GNUNET_ERROR_TYPE_ERROR,
189 _ ("Failed to setup database at `%s'\n"),
190 plugin->fn);
191 return GNUNET_SYSERR;
192 }
193 GNUNET_break (SQLITE_OK ==
194 sqlite3_busy_timeout (plugin->dbh,
195 BUSY_TIMEOUT_MS));
196
197 if (GNUNET_OK !=
198 GNUNET_SQ_prepare (plugin->dbh,
199 ps))
200 {
201 GNUNET_break (0);
202 LOG (GNUNET_ERROR_TYPE_ERROR,
203 _ ("Failed to setup database at `%s'\n"),
204 plugin->fn);
205 return GNUNET_SYSERR;
206 }
207
208 return GNUNET_OK;
209}
210
211
212/**
213 * Shutdown database connection and associate data
214 * structures.
215 * @param plugin the plugin context (state for this module)
216 */
217static void
218database_shutdown (struct Plugin *plugin)
219{
220 int result;
221 sqlite3_stmt *stmt;
222
223 if (NULL != plugin->cache_block)
224 sqlite3_finalize (plugin->cache_block);
225 if (NULL != plugin->lookup_block)
226 sqlite3_finalize (plugin->lookup_block);
227 if (NULL != plugin->expire_blocks)
228 sqlite3_finalize (plugin->expire_blocks);
229 if (NULL != plugin->delete_block)
230 sqlite3_finalize (plugin->delete_block);
231 result = sqlite3_close (plugin->dbh);
232 if (result == SQLITE_BUSY)
233 {
234 LOG (GNUNET_ERROR_TYPE_WARNING,
235 _ (
236 "Tried to close sqlite without finalizing all prepared statements.\n"));
237 stmt = sqlite3_next_stmt (plugin->dbh,
238 NULL);
239 while (stmt != NULL)
240 {
241 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
242 "sqlite",
243 "Closing statement %p\n",
244 stmt);
245 result = sqlite3_finalize (stmt);
246 if (result != SQLITE_OK)
247 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
248 "sqlite",
249 "Failed to close statement %p: %d\n",
250 stmt,
251 result);
252 stmt = sqlite3_next_stmt (plugin->dbh,
253 NULL);
254 }
255 result = sqlite3_close (plugin->dbh);
256 }
257 if (SQLITE_OK != result)
258 LOG_SQLITE (plugin,
259 GNUNET_ERROR_TYPE_ERROR,
260 "sqlite3_close");
261
262 GNUNET_free (plugin->fn);
263}
264
265
266/**
267 * Removes any expired block.
268 *
269 * @param plugin the plugin
270 */
271static void
272namecache_sqlite_expire_blocks (struct Plugin *plugin)
273{
274 struct GNUNET_TIME_Absolute now;
275 now = GNUNET_TIME_absolute_get ();
276 struct GNUNET_SQ_QueryParam params[] = {
277 GNUNET_SQ_query_param_absolute_time (&now),
278 GNUNET_SQ_query_param_end
279 };
280 int n;
281
282 if (GNUNET_OK !=
283 GNUNET_SQ_bind (plugin->expire_blocks,
284 params))
285 {
286 LOG_SQLITE (plugin,
287 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
288 "sqlite3_bind_XXXX");
289 GNUNET_SQ_reset (plugin->dbh,
290 plugin->expire_blocks);
291 return;
292 }
293 n = sqlite3_step (plugin->expire_blocks);
294 GNUNET_SQ_reset (plugin->dbh,
295 plugin->expire_blocks);
296 switch (n)
297 {
298 case SQLITE_DONE:
299 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
300 "sqlite",
301 "Records expired\n");
302 return;
303
304 case SQLITE_BUSY:
305 LOG_SQLITE (plugin,
306 GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
307 "sqlite3_step");
308 return;
309
310 default:
311 LOG_SQLITE (plugin,
312 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
313 "sqlite3_step");
314 return;
315 }
316}
317
318
319/**
320 * Cache a block in the datastore.
321 *
322 * @param cls closure (internal context for the plugin)
323 * @param block block to cache
324 * @return #GNUNET_OK on success, else #GNUNET_SYSERR
325 */
326static int
327namecache_sqlite_cache_block (void *cls,
328 const struct GNUNET_GNSRECORD_Block *block)
329{
330 static struct GNUNET_TIME_Absolute last_expire;
331 struct Plugin *plugin = cls;
332 struct GNUNET_HashCode query;
333 struct GNUNET_TIME_Absolute expiration;
334 size_t block_size = GNUNET_GNSRECORD_block_get_size (block);
335 struct GNUNET_SQ_QueryParam del_params[] = {
336 GNUNET_SQ_query_param_auto_from_type (&query),
337 GNUNET_SQ_query_param_absolute_time (&expiration),
338 GNUNET_SQ_query_param_end
339 };
340 struct GNUNET_SQ_QueryParam ins_params[] = {
341 GNUNET_SQ_query_param_auto_from_type (&query),
342 GNUNET_SQ_query_param_fixed_size (block,
343 block_size),
344 GNUNET_SQ_query_param_absolute_time (&expiration),
345 GNUNET_SQ_query_param_end
346 };
347 int n;
348
349 /* run expiration of old cache entries once per hour */
350 if (GNUNET_TIME_absolute_get_duration (last_expire).rel_value_us >
351 GNUNET_TIME_UNIT_HOURS.rel_value_us)
352 {
353 last_expire = GNUNET_TIME_absolute_get ();
354 namecache_sqlite_expire_blocks (plugin);
355 }
356 GNUNET_assert (GNUNET_OK ==
357 GNUNET_GNSRECORD_query_from_block (block, &query));
358 expiration = GNUNET_GNSRECORD_block_get_expiration (block);
359 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
360 "Caching new version of block %s (expires %s)\n",
361 GNUNET_h2s (&query),
362 GNUNET_STRINGS_absolute_time_to_string (expiration));
363 if (block_size > 64 * 65536)
364 {
365 GNUNET_break (0);
366 return GNUNET_SYSERR;
367 }
368
369 /* delete old version of the block */
370 if (GNUNET_OK !=
371 GNUNET_SQ_bind (plugin->delete_block,
372 del_params))
373 {
374 LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
375 "sqlite3_bind_XXXX");
376 GNUNET_SQ_reset (plugin->dbh,
377 plugin->delete_block);
378 return GNUNET_SYSERR;
379 }
380 n = sqlite3_step (plugin->delete_block);
381 switch (n)
382 {
383 case SQLITE_DONE:
384 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
385 "sqlite",
386 "Old block deleted\n");
387 break;
388
389 case SQLITE_BUSY:
390 LOG_SQLITE (plugin,
391 GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
392 "sqlite3_step");
393 break;
394
395 default:
396 LOG_SQLITE (plugin,
397 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
398 "sqlite3_step");
399 break;
400 }
401 GNUNET_SQ_reset (plugin->dbh,
402 plugin->delete_block);
403
404 /* insert new version of the block */
405 if (GNUNET_OK !=
406 GNUNET_SQ_bind (plugin->cache_block,
407 ins_params))
408 {
409 LOG_SQLITE (plugin,
410 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
411 "sqlite3_bind_XXXX");
412 GNUNET_SQ_reset (plugin->dbh,
413 plugin->cache_block);
414 return GNUNET_SYSERR;
415 }
416 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
417 "Caching block under derived key `%s'\n",
418 GNUNET_h2s_full (&query));
419 n = sqlite3_step (plugin->cache_block);
420 GNUNET_SQ_reset (plugin->dbh,
421 plugin->cache_block);
422 switch (n)
423 {
424 case SQLITE_DONE:
425 LOG (GNUNET_ERROR_TYPE_DEBUG,
426 "Record stored\n");
427 return GNUNET_OK;
428
429 case SQLITE_BUSY:
430 LOG_SQLITE (plugin,
431 GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
432 "sqlite3_step");
433 return GNUNET_NO;
434
435 default:
436 LOG_SQLITE (plugin,
437 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
438 "sqlite3_step");
439 return GNUNET_SYSERR;
440 }
441}
442
443
444/**
445 * Get the block for a particular zone and label in the
446 * datastore. Will return at most one result to the iterator.
447 *
448 * @param cls closure (internal context for the plugin)
449 * @param query hash of public key derived from the zone and the label
450 * @param iter function to call with the result
451 * @param iter_cls closure for @a iter
452 * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
453 */
454static int
455namecache_sqlite_lookup_block (void *cls,
456 const struct GNUNET_HashCode *query,
457 GNUNET_NAMECACHE_BlockCallback iter,
458 void *iter_cls)
459{
460 struct Plugin *plugin = cls;
461 int ret;
462 int sret;
463 size_t block_size;
464 const struct GNUNET_GNSRECORD_Block *block;
465 struct GNUNET_SQ_QueryParam params[] = {
466 GNUNET_SQ_query_param_auto_from_type (query),
467 GNUNET_SQ_query_param_end
468 };
469 struct GNUNET_SQ_ResultSpec rs[] = {
470 GNUNET_SQ_result_spec_variable_size ((void **) &block,
471 &block_size),
472 GNUNET_SQ_result_spec_end
473 };
474
475 if (GNUNET_OK !=
476 GNUNET_SQ_bind (plugin->lookup_block,
477 params))
478 {
479 LOG_SQLITE (plugin,
480 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
481 "sqlite3_bind_XXXX");
482 GNUNET_SQ_reset (plugin->dbh,
483 plugin->lookup_block);
484 return GNUNET_SYSERR;
485 }
486 ret = GNUNET_NO;
487 if (SQLITE_ROW ==
488 (sret = sqlite3_step (plugin->lookup_block)))
489 {
490 if (GNUNET_OK !=
491 GNUNET_SQ_extract_result (plugin->lookup_block,
492 rs))
493 {
494 GNUNET_break (0);
495 ret = GNUNET_SYSERR;
496 }
497 else if ((block_size < sizeof(struct GNUNET_GNSRECORD_Block)))
498 {
499 GNUNET_break (0);
500 GNUNET_SQ_cleanup_result (rs);
501 ret = GNUNET_SYSERR;
502 }
503 else
504 {
505 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
506 "Found block under derived key `%s'\n",
507 GNUNET_h2s_full (query));
508 iter (iter_cls,
509 block);
510 GNUNET_SQ_cleanup_result (rs);
511 ret = GNUNET_YES;
512 }
513 }
514 else
515 {
516 if (SQLITE_DONE != sret)
517 {
518 LOG_SQLITE (plugin,
519 GNUNET_ERROR_TYPE_ERROR,
520 "sqlite_step");
521 ret = GNUNET_SYSERR;
522 }
523 else
524 {
525 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
526 "No block found under derived key `%s'\n",
527 GNUNET_h2s_full (query));
528 }
529 }
530 GNUNET_SQ_reset (plugin->dbh,
531 plugin->lookup_block);
532 return ret;
533}
534
535
536/**
537 * Entry point for the plugin.
538 *
539 * @param cls the "struct GNUNET_NAMECACHE_PluginEnvironment*"
540 * @return NULL on error, otherwise the plugin context
541 */
542void *
543libgnunet_plugin_namecache_sqlite_init (void *cls)
544{
545 static struct Plugin plugin;
546 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
547 struct GNUNET_NAMECACHE_PluginFunctions *api;
548
549 if (NULL != plugin.cfg)
550 return NULL; /* can only initialize once! */
551 memset (&plugin, 0, sizeof(struct Plugin));
552 plugin.cfg = cfg;
553 if (GNUNET_OK != database_setup (&plugin))
554 {
555 database_shutdown (&plugin);
556 return NULL;
557 }
558 api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
559 api->cls = &plugin;
560 api->cache_block = &namecache_sqlite_cache_block;
561 api->lookup_block = &namecache_sqlite_lookup_block;
562 LOG (GNUNET_ERROR_TYPE_INFO,
563 _ ("Sqlite database running\n"));
564 return api;
565}
566
567
568/**
569 * Exit point from the plugin.
570 *
571 * @param cls the plugin context (as returned by "init")
572 * @return always NULL
573 */
574void *
575libgnunet_plugin_namecache_sqlite_done (void *cls)
576{
577 struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
578 struct Plugin *plugin = api->cls;
579
580 database_shutdown (plugin);
581 plugin->cfg = NULL;
582 GNUNET_free (api);
583 LOG (GNUNET_ERROR_TYPE_DEBUG,
584 "sqlite plugin is finished\n");
585 return NULL;
586}
587
588
589/* end of plugin_namecache_sqlite.c */
diff --git a/src/plugin/namecache/test_plugin_namecache.c b/src/plugin/namecache/test_plugin_namecache.c
new file mode 100644
index 000000000..141698a23
--- /dev/null
+++ b/src/plugin/namecache/test_plugin_namecache.c
@@ -0,0 +1,135 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2012 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/*
21 * @file namecache/test_plugin_namecache.c
22 * @brief Test for the namecache plugins
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_util_lib.h"
27#include "gnunet_namecache_plugin.h"
28#include "gnunet_testing_lib.h"
29
30
31static int ok;
32
33/**
34 * Name of plugin under test.
35 */
36static const char *plugin_name;
37
38
39/**
40 * Function called when the service shuts down. Unloads our namecache
41 * plugin.
42 *
43 * @param api api to unload
44 */
45static void
46unload_plugin (struct GNUNET_NAMECACHE_PluginFunctions *api)
47{
48 char *libname;
49
50 GNUNET_asprintf (&libname, "libgnunet_plugin_namecache_%s", plugin_name);
51 GNUNET_break (NULL == GNUNET_PLUGIN_unload (libname, api));
52 GNUNET_free (libname);
53}
54
55
56/**
57 * Load the namecache plugin.
58 *
59 * @param cfg configuration to pass
60 * @return NULL on error
61 */
62static struct GNUNET_NAMECACHE_PluginFunctions *
63load_plugin (const struct GNUNET_CONFIGURATION_Handle *cfg)
64{
65 struct GNUNET_NAMECACHE_PluginFunctions *ret;
66 char *libname;
67
68 GNUNET_log (GNUNET_ERROR_TYPE_INFO, _ ("Loading `%s' namecache plugin\n"),
69 plugin_name);
70 GNUNET_asprintf (&libname, "libgnunet_plugin_namecache_%s", plugin_name);
71 if (NULL == (ret = GNUNET_PLUGIN_load (libname, (void *) cfg)))
72 {
73 fprintf (stderr, "Failed to load plugin `%s'!\n", plugin_name);
74 GNUNET_free (libname);
75 return NULL;
76 }
77 GNUNET_free (libname);
78 return ret;
79}
80
81
82static void
83run (void *cls, char *const *args, const char *cfgfile,
84 const struct GNUNET_CONFIGURATION_Handle *cfg)
85{
86 struct GNUNET_NAMECACHE_PluginFunctions *nsp;
87
88 ok = 0;
89 nsp = load_plugin (cfg);
90 if (NULL == nsp)
91 {
92 fprintf (stderr,
93 "%s",
94 "Failed to initialize namecache. Database likely not setup, skipping test.\n");
95 return;
96 }
97
98 unload_plugin (nsp);
99}
100
101
102int
103main (int argc, char *argv[])
104{
105 char cfg_name[PATH_MAX];
106 char *const xargv[] = {
107 "test-plugin-namecache",
108 "-c",
109 cfg_name,
110 NULL
111 };
112 struct GNUNET_GETOPT_CommandLineOption options[] = {
113 GNUNET_GETOPT_OPTION_END
114 };
115
116 plugin_name = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
117 GNUNET_snprintf (cfg_name, sizeof(cfg_name), "test_plugin_namecache_%s.conf",
118 plugin_name);
119
120 GNUNET_DISK_purge_cfg_dir (cfg_name, "GNUNET_TEST_HOME");
121
122 GNUNET_log_setup ("test-plugin-namecache",
123 "WARNING",
124 NULL);
125 GNUNET_PROGRAM_run ((sizeof(xargv) / sizeof(char *)) - 1, xargv,
126 "test-plugin-namecache", "nohelp", options, &run, NULL);
127
128 if (ok != 0)
129 fprintf (stderr, "Missed some testcases: %d\n", ok);
130 GNUNET_DISK_purge_cfg_dir (cfg_name, "GNUNET_TEST_HOME");
131 return ok;
132}
133
134
135/* end of test_plugin_namecache.c */
diff --git a/src/plugin/namecache/test_plugin_namecache_flat.conf b/src/plugin/namecache/test_plugin_namecache_flat.conf
new file mode 100644
index 000000000..efe77e37a
--- /dev/null
+++ b/src/plugin/namecache/test_plugin_namecache_flat.conf
@@ -0,0 +1,2 @@
1[namecache-flat]
2FILENAME = $GNUNET_TMP/gnunet-test-plugin-namecache-flat/flatdb
diff --git a/src/plugin/namecache/test_plugin_namecache_postgres.conf b/src/plugin/namecache/test_plugin_namecache_postgres.conf
new file mode 100644
index 000000000..8473857d5
--- /dev/null
+++ b/src/plugin/namecache/test_plugin_namecache_postgres.conf
@@ -0,0 +1,3 @@
1[namestore-postgres]
2CONFIG = connect_timeout=10; dbname=gnunetcheck
3TEMPORARY_TABLE = YES
diff --git a/src/plugin/namecache/test_plugin_namecache_sqlite.conf b/src/plugin/namecache/test_plugin_namecache_sqlite.conf
new file mode 100644
index 000000000..24eecd286
--- /dev/null
+++ b/src/plugin/namecache/test_plugin_namecache_sqlite.conf
@@ -0,0 +1,2 @@
1[namestore-sqlite]
2FILENAME = $GNUNET_TMP/gnunet-test-plugin-namestore-sqlite/sqlite.db
diff --git a/src/plugin/namestore/Makefile.am b/src/plugin/namestore/Makefile.am
new file mode 100644
index 000000000..3db174225
--- /dev/null
+++ b/src/plugin/namestore/Makefile.am
@@ -0,0 +1,79 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include $(POSTGRESQL_CPPFLAGS)
3
4plugindir = $(libdir)/gnunet
5
6pkgcfgdir= $(pkgdatadir)/config.d/
7
8libexecdir= $(pkglibdir)/libexec/
9
10sqldir = $(prefix)/share/gnunet/sql/
11
12sql_DATA = \
13 namestore-0001.sql \
14 namestore-drop.sql
15
16if USE_COVERAGE
17 AM_CFLAGS = --coverage -O0
18 XLIBS = -lgcov
19endif
20
21
22
23
24plugin_LTLIBRARIES = \
25 $(SQLITE_PLUGIN) \
26 $(POSTGRES_PLUGIN)
27
28
29libgnunet_plugin_namestore_sqlite_la_SOURCES = \
30 plugin_namestore_sqlite.c
31libgnunet_plugin_namestore_sqlite_la_LIBADD = \
32 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
33 $(top_builddir)/src/lib/sq/libgnunetsq.la \
34 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) -lsqlite3 \
35 $(LTLIBINTL)
36libgnunet_plugin_namestore_sqlite_la_LDFLAGS = \
37 $(GN_PLUGIN_LDFLAGS)
38
39libgnunet_plugin_namestore_postgres_la_SOURCES = \
40 plugin_namestore_postgres.c
41libgnunet_plugin_namestore_postgres_la_LIBADD = \
42 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
43 $(top_builddir)/src/lib/pq/libgnunetpq.la \
44 $(top_builddir)/src/lib/util/libgnunetutil.la $(XLIBS) -lpq \
45 $(LTLIBINTL)
46libgnunet_plugin_namestore_postgres_la_LDFLAGS = \
47 $(GN_PLUGIN_LDFLAGS) $(POSTGRESQL_LDFLAGS)
48
49if HAVE_SQLITE
50SQLITE_PLUGIN = libgnunet_plugin_namestore_sqlite.la
51SQLITE_TESTS = test_plugin_namestore_sqlite
52endif
53
54
55if HAVE_POSTGRESQL
56POSTGRES_PLUGIN = libgnunet_plugin_namestore_postgres.la
57POSTGRES_TESTS = test_plugin_namestore_postgres
58endif
59if HAVE_SQLITE
60check_PROGRAMS = \
61 $(SQLITE_TESTS) \
62 $(POSTGRES_TESTS)
63endif
64
65test_plugin_namestore_sqlite_SOURCES = \
66 test_plugin_namestore.c
67test_plugin_namestore_sqlite_LDADD = \
68 $(top_builddir)/src/lib/util/libgnunetutil.la
69
70test_plugin_namestore_postgres_SOURCES = \
71 test_plugin_namestore.c
72test_plugin_namestore_postgres_LDADD = \
73 $(top_builddir)/src/lib/util/libgnunetutil.la
74
75EXTRA_DIST = \
76 test_plugin_namestore_sqlite.conf \
77 test_plugin_namestore_postgres.conf \
78 $(sql_DATA)
79
diff --git a/src/plugin/namestore/meson.build b/src/plugin/namestore/meson.build
new file mode 100644
index 000000000..86eeb37c5
--- /dev/null
+++ b/src/plugin/namestore/meson.build
@@ -0,0 +1,63 @@
1libgnunetpluginnamestore_sqlite_src = ['plugin_namestore_sqlite.c']
2
3shared_module('gnunet_plugin_namestore_sqlite',
4 libgnunetpluginnamestore_sqlite_src,
5 dependencies: [libgnunetutil_dep,
6 libgnunetgnsrecord_dep,
7 libgnunetsq_dep,
8 sqlite_dep],
9 include_directories: [incdir, configuration_inc],
10 install: true,
11 install_dir: get_option('libdir')/'gnunet')
12
13configure_file(copy: true,
14 input: 'test_plugin_namestore_sqlite.conf',
15 output: 'test_plugin_namestore_sqlite.conf')
16
17configure_file(input : 'namestore-0001.sql',
18 output : 'namestore-0001.sql',
19 configuration : cdata,
20 install: true,
21 install_dir: get_option('datadir')/'gnunet'/'sql')
22
23configure_file(input : 'namestore-drop.sql',
24 output : 'namestore-drop.sql',
25 configuration : cdata,
26 install: true,
27 install_dir: get_option('datadir')/'gnunet'/'sql')
28
29if pq_dep.found()
30 shared_module('gnunet_plugin_namestore_postgres',
31 ['plugin_namestore_postgres.c'],
32 dependencies: [libgnunetutil_dep,
33 libgnunetgnsrecord_dep,
34 libgnunetpq_dep,
35 pq_dep],
36 include_directories: [incdir, configuration_inc],
37 install: true,
38 install_dir: get_option('libdir')/'gnunet')
39configure_file(copy: true,
40 input: 'test_plugin_namestore_postgres.conf',
41 output: 'test_plugin_namestore_postgres.conf')
42
43 testpluginnamestore_pq = executable ('test_plugin_namestore_postgres',
44 ['test_plugin_namestore.c'],
45 dependencies: [
46 libgnunetutil_dep
47 ],
48 include_directories: [incdir, configuration_inc],
49 install: false)
50 test('test_plugin_namestore_postgres', testpluginnamestore_pq, workdir: meson.current_build_dir(),
51 suite: 'namestore')
52endif
53
54testpluginnamestore_sq = executable ('test_plugin_namestore_sqlite',
55 ['test_plugin_namestore.c'],
56 dependencies: [
57 libgnunetutil_dep
58 ],
59 include_directories: [incdir, configuration_inc],
60 install: false)
61test('test_plugin_namestore_sqlite', testpluginnamestore_sq, workdir: meson.current_build_dir(),
62 suite: 'namestore')
63
diff --git a/src/plugin/namestore/namestore-0001.sql b/src/plugin/namestore/namestore-0001.sql
new file mode 100644
index 000000000..bd323e365
--- /dev/null
+++ b/src/plugin/namestore/namestore-0001.sql
@@ -0,0 +1,49 @@
1--
2-- This file is part of GNUnet
3-- Copyright (C) 2014--2022 GNUnet e.V.
4--
5-- GNUnet is free software; you can redistribute it and/or modify it under the
6-- terms of the GNU General Public License as published by the Free Software
7-- Foundation; either version 3, or (at your option) any later version.
8--
9-- GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY
10-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12--
13-- You should have received a copy of the GNU General Public License along with
14-- GNUnet; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15--
16
17-- Everything in one big transaction
18BEGIN;
19
20-- Check patch versioning is in place.
21SELECT _v.register_patch('namestore-0001', NULL, NULL);
22
23-------------------- Schema ----------------------------
24
25CREATE SCHEMA namestore;
26COMMENT ON SCHEMA namestore IS 'gnunet-namestore data';
27
28SET search_path TO namestore;
29
30CREATE TABLE ns098records (
31 seq BIGSERIAL PRIMARY KEY,
32 zone_private_key BYTEA NOT NULL DEFAULT '',
33 pkey BYTEA DEFAULT '',
34 rvalue BYTEA NOT NULL DEFAULT '',
35 record_count INTEGER NOT NULL DEFAULT 0,
36 record_data BYTEA NOT NULL DEFAULT '',
37 label TEXT NOT NULL DEFAULT '',
38 editor_hint TEXT NOT NULL DEFAULT '',
39 CONSTRAINT zl UNIQUE (zone_private_key,label));
40
41CREATE INDEX IF NOT EXISTS ir_pkey_reverse
42 ON ns098records (zone_private_key,pkey);
43CREATE INDEX IF NOT EXISTS ir_pkey_iter
44 ON ns098records (zone_private_key,seq);
45CREATE INDEX IF NOT EXISTS ir_label
46 ON ns098records (label);
47
48
49COMMIT;
diff --git a/src/plugin/namestore/namestore-drop.sql b/src/plugin/namestore/namestore-drop.sql
new file mode 100644
index 000000000..231417af8
--- /dev/null
+++ b/src/plugin/namestore/namestore-drop.sql
@@ -0,0 +1,25 @@
1--
2-- This file is part of GNUnet
3-- Copyright (C) 2014--2022 GNUnet e.V.
4--
5-- GNUnet is free software; you can redistribute it and/or modify it under the
6-- terms of the GNU General Public License as published by the Free Software
7-- Foundation; either version 3, or (at your option) any later version.
8--
9-- GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY
10-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12--
13-- You should have received a copy of the GNU General Public License along with
14-- GNUnet; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15--
16
17-- Everything in one big transaction
18BEGIN;
19
20
21SELECT _v.unregister_patch('namestore-0001');
22
23DROP SCHEMA namestore CASCADE;
24
25COMMIT;
diff --git a/src/plugin/namestore/plugin_namestore_flat.c b/src/plugin/namestore/plugin_namestore_flat.c
new file mode 100644
index 000000000..20019e1db
--- /dev/null
+++ b/src/plugin/namestore/plugin_namestore_flat.c
@@ -0,0 +1,816 @@
1/*
2 * This file is part of GNUnet
3 * Copyright (C) 2009-2015, 2018, 2019 GNUnet e.V.
4 *
5 * GNUnet is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License,
8 * or (at your option) any later version.
9 *
10 * GNUnet is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @file namestore/plugin_namestore_flat.c
22 * @brief file-based namestore backend
23 * @author Martin Schanzenbach
24 * @author Christian Grothoff
25 */
26
27#include "platform.h"
28#include "gnunet_namestore_plugin.h"
29#include "gnunet_gnsrecord_lib.h"
30
31/**
32 * Context for all functions in this plugin.
33 */
34struct Plugin
35{
36 const struct GNUNET_CONFIGURATION_Handle *cfg;
37
38 /**
39 * Database filename.
40 */
41 char *fn;
42
43 /**
44 * HashMap
45 */
46 struct GNUNET_CONTAINER_MultiHashMap *hm;
47};
48
49
50struct FlatFileEntry
51{
52 /**
53 * Entry zone
54 */
55 struct GNUNET_CRYPTO_PrivateKey private_key;
56
57 /**
58 * Record count.
59 */
60 uint32_t record_count;
61
62 /**
63 * Rvalue
64 */
65 uint64_t rvalue;
66
67 /**
68 * Record data
69 */
70 struct GNUNET_GNSRECORD_Data *record_data;
71
72 /**
73 * Label
74 */
75 char *label;
76};
77
78
79/**
80 * Hash contactenation of @a pkey and @a label into @a h
81 *
82 * @param pkey a key
83 * @param label a label
84 * @param[out] h initialized hash
85 */
86static void
87hash_pkey_and_label (const struct GNUNET_CRYPTO_PrivateKey *pkey,
88 const char *label,
89 struct GNUNET_HashCode *h)
90{
91 char *key;
92 size_t label_len;
93 size_t key_len;
94
95 label_len = strlen (label);
96 key_len = label_len + sizeof(struct GNUNET_CRYPTO_PrivateKey);
97 key = GNUNET_malloc (key_len);
98 GNUNET_memcpy (key,
99 label,
100 label_len);
101 GNUNET_memcpy (key + label_len,
102 pkey,
103 sizeof(struct GNUNET_CRYPTO_PrivateKey));
104 GNUNET_CRYPTO_hash (key,
105 key_len,
106 h);
107 GNUNET_free (key);
108}
109
110
111/**
112 * Initialize the database connections and associated
113 * data structures (create tables and indices
114 * as needed as well).
115 *
116 * @param plugin the plugin context (state for this module)
117 * @return #GNUNET_OK on success
118 */
119static int
120database_setup (struct Plugin *plugin)
121{
122 char *flatdbfile;
123 char *record_data;
124 char *zone_private_key;
125 char *record_data_b64;
126 char *buffer;
127 char *line;
128 char *label;
129 char *rvalue;
130 char *record_count;
131 size_t record_data_size;
132 uint64_t size;
133 struct GNUNET_HashCode hkey;
134 struct GNUNET_DISK_FileHandle *fh;
135 struct FlatFileEntry *entry;
136 struct GNUNET_DISK_MapHandle *mh;
137
138 if (GNUNET_OK !=
139 GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
140 "namestore-flat",
141 "FILENAME",
142 &flatdbfile))
143 {
144 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
145 "namestore-flat",
146 "FILENAME");
147 return GNUNET_SYSERR;
148 }
149 if (GNUNET_OK !=
150 GNUNET_DISK_file_test (flatdbfile))
151 {
152 if (GNUNET_OK !=
153 GNUNET_DISK_directory_create_for_file (flatdbfile))
154 {
155 GNUNET_break (0);
156 GNUNET_free (flatdbfile);
157 return GNUNET_SYSERR;
158 }
159 }
160 /* flatdbfile should be UTF-8-encoded. If it isn't, it's a bug */
161 plugin->fn = flatdbfile;
162
163 /* Load data from file into hashmap */
164 plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
165 GNUNET_NO);
166 fh = GNUNET_DISK_file_open (flatdbfile,
167 GNUNET_DISK_OPEN_CREATE
168 | GNUNET_DISK_OPEN_READWRITE,
169 GNUNET_DISK_PERM_USER_WRITE
170 | GNUNET_DISK_PERM_USER_READ);
171 if (NULL == fh)
172 {
173 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
174 _ ("Unable to initialize file: %s.\n"),
175 flatdbfile);
176 return GNUNET_SYSERR;
177 }
178 if (GNUNET_SYSERR ==
179 GNUNET_DISK_file_size (flatdbfile,
180 &size,
181 GNUNET_YES,
182 GNUNET_YES))
183 {
184 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
185 _ ("Unable to get filesize: %s.\n"),
186 flatdbfile);
187 GNUNET_DISK_file_close (fh);
188 return GNUNET_SYSERR;
189 }
190 if (size > SIZE_MAX)
191 {
192 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
193 _ ("File too big to map: %llu bytes.\n"),
194 (unsigned long long) size);
195 GNUNET_DISK_file_close (fh);
196 return GNUNET_SYSERR;
197 }
198 if (0 == size)
199 {
200 GNUNET_DISK_file_close (fh);
201 return GNUNET_OK;
202 }
203 buffer = GNUNET_DISK_file_map (fh,
204 &mh,
205 GNUNET_DISK_MAP_TYPE_READ,
206 size);
207 if (NULL == buffer)
208 {
209 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
210 "mmap");
211 GNUNET_DISK_file_close (fh);
212 return GNUNET_SYSERR;
213 }
214 if ('\0' != buffer[size - 1])
215 {
216 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
217 _ ("Namestore database file `%s' malformed\n"),
218 flatdbfile);
219 GNUNET_DISK_file_unmap (mh);
220 GNUNET_DISK_file_close (fh);
221 return GNUNET_SYSERR;
222 }
223
224 line = strtok (buffer, "\n");
225 while (NULL != line)
226 {
227 zone_private_key = strtok (line, ",");
228 if (NULL == zone_private_key)
229 break;
230 rvalue = strtok (NULL, ",");
231 if (NULL == rvalue)
232 break;
233 record_count = strtok (NULL, ",");
234 if (NULL == record_count)
235 break;
236 record_data_b64 = strtok (NULL, ",");
237 if (NULL == record_data_b64)
238 break;
239 label = strtok (NULL, ",");
240 if (NULL == label)
241 break;
242 line = strtok (NULL, "\n");
243 entry = GNUNET_new (struct FlatFileEntry);
244 {
245 unsigned long long ll;
246
247 if (1 != sscanf (rvalue,
248 "%llu",
249 &ll))
250 {
251 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
252 "Error parsing entry\n");
253 GNUNET_free (entry);
254 break;
255 }
256 entry->rvalue = (uint64_t) ll;
257 }
258 {
259 unsigned int ui;
260
261 if (1 != sscanf (record_count,
262 "%u",
263 &ui))
264 {
265 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
266 "Error parsing entry\n");
267 GNUNET_free (entry);
268 break;
269 }
270 entry->record_count = (uint32_t) ui;
271 }
272 entry->label = GNUNET_strdup (label);
273 record_data_size
274 = GNUNET_STRINGS_base64_decode (record_data_b64,
275 strlen (record_data_b64),
276 (void **) &record_data);
277 entry->record_data =
278 GNUNET_new_array (entry->record_count,
279 struct GNUNET_GNSRECORD_Data);
280 if (GNUNET_OK !=
281 GNUNET_GNSRECORD_records_deserialize (record_data_size,
282 record_data,
283 entry->record_count,
284 entry->record_data))
285 {
286 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
287 "Unable to deserialize record %s\n",
288 label);
289 GNUNET_free (entry->label);
290 GNUNET_free (entry);
291 GNUNET_free (record_data);
292 break;
293 }
294 GNUNET_free (record_data);
295
296 {
297 struct GNUNET_CRYPTO_PrivateKey *private_key;
298
299 GNUNET_STRINGS_base64_decode (zone_private_key,
300 strlen (zone_private_key),
301 (void **) &private_key);
302 entry->private_key = *private_key;
303 GNUNET_free (private_key);
304 }
305
306 hash_pkey_and_label (&entry->private_key,
307 label,
308 &hkey);
309 if (GNUNET_OK !=
310 GNUNET_CONTAINER_multihashmap_put (plugin->hm,
311 &hkey,
312 entry,
313 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
314 {
315 GNUNET_free (entry);
316 GNUNET_break (0);
317 }
318 }
319 GNUNET_DISK_file_unmap (mh);
320 GNUNET_DISK_file_close (fh);
321 return GNUNET_OK;
322}
323
324
325/**
326 * Store values in hashmap in file and free data
327 *
328 * @param plugin the plugin context
329 * @param key key in the map
330 * @param value a `struct FlatFileEntry`
331 */
332static int
333store_and_free_entries (void *cls,
334 const struct GNUNET_HashCode *key,
335 void *value)
336{
337 struct GNUNET_DISK_FileHandle *fh = cls;
338 struct FlatFileEntry *entry = value;
339 char *line;
340 char *zone_private_key;
341 char *record_data_b64;
342 ssize_t data_size;
343
344 (void) key;
345 GNUNET_STRINGS_base64_encode (&entry->private_key,
346 sizeof(struct GNUNET_CRYPTO_PrivateKey),
347 &zone_private_key);
348 data_size = GNUNET_GNSRECORD_records_get_size (entry->record_count,
349 entry->record_data);
350 if (data_size < 0)
351 {
352 GNUNET_break (0);
353 GNUNET_free (zone_private_key);
354 return GNUNET_SYSERR;
355 }
356 if (data_size >= UINT16_MAX)
357 {
358 GNUNET_break (0);
359 GNUNET_free (zone_private_key);
360 return GNUNET_SYSERR;
361 }
362 {
363 char data[data_size];
364 ssize_t ret;
365
366 ret = GNUNET_GNSRECORD_records_serialize (entry->record_count,
367 entry->record_data,
368 data_size,
369 data);
370 if ((ret < 0) ||
371 (data_size != ret))
372 {
373 GNUNET_break (0);
374 GNUNET_free (zone_private_key);
375 return GNUNET_SYSERR;
376 }
377 GNUNET_STRINGS_base64_encode (data,
378 data_size,
379 &record_data_b64);
380 }
381 GNUNET_asprintf (&line,
382 "%s,%llu,%u,%s,%s\n",
383 zone_private_key,
384 (unsigned long long) entry->rvalue,
385 (unsigned int) entry->record_count,
386 record_data_b64,
387 entry->label);
388 GNUNET_free (record_data_b64);
389 GNUNET_free (zone_private_key);
390
391 GNUNET_DISK_file_write (fh,
392 line,
393 strlen (line));
394
395 GNUNET_free (line);
396 GNUNET_free (entry->label);
397 GNUNET_free (entry->record_data);
398 GNUNET_free (entry);
399 return GNUNET_YES;
400}
401
402
403/**
404 * Shutdown database connection and associate data
405 * structures.
406 * @param plugin the plugin context (state for this module)
407 */
408static void
409database_shutdown (struct Plugin *plugin)
410{
411 struct GNUNET_DISK_FileHandle *fh;
412
413 fh = GNUNET_DISK_file_open (plugin->fn,
414 GNUNET_DISK_OPEN_CREATE
415 | GNUNET_DISK_OPEN_TRUNCATE
416 | GNUNET_DISK_OPEN_READWRITE,
417 GNUNET_DISK_PERM_USER_WRITE
418 | GNUNET_DISK_PERM_USER_READ);
419 if (NULL == fh)
420 {
421 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
422 _ ("Unable to initialize file: %s.\n"),
423 plugin->fn);
424 return;
425 }
426
427 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
428 &store_and_free_entries,
429 fh);
430 GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
431 /* append 0-terminator */
432 GNUNET_DISK_file_write (fh,
433 "",
434 1);
435 GNUNET_DISK_file_close (fh);
436}
437
438
439/**
440 * Store a record in the datastore. Removes any existing record in the
441 * same zone with the same name.
442 *
443 * @param cls closure (internal context for the plugin)
444 * @param zone_key private key of the zone
445 * @param label name that is being mapped (at most 255 characters long)
446 * @param rd_count number of entries in @a rd array
447 * @param rd array of records with data to store
448 * @return #GNUNET_OK on success, else #GNUNET_SYSERR
449 */
450static int
451namestore_flat_store_records (void *cls,
452 const struct
453 GNUNET_CRYPTO_PrivateKey *zone_key,
454 const char *label,
455 unsigned int rd_count,
456 const struct GNUNET_GNSRECORD_Data *rd)
457{
458 struct Plugin *plugin = cls;
459 uint64_t rvalue;
460 struct GNUNET_HashCode hkey;
461 struct FlatFileEntry *entry;
462
463 rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
464 UINT64_MAX);
465 hash_pkey_and_label (zone_key,
466 label,
467 &hkey);
468 GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm,
469 &hkey);
470 if (0 == rd_count)
471 {
472 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
473 "sqlite",
474 "Record deleted\n");
475 return GNUNET_OK;
476 }
477 entry = GNUNET_new (struct FlatFileEntry);
478 GNUNET_asprintf (&entry->label,
479 label,
480 strlen (label));
481 GNUNET_memcpy (&entry->private_key,
482 zone_key,
483 sizeof(struct GNUNET_CRYPTO_PrivateKey));
484 entry->rvalue = rvalue;
485 entry->record_count = rd_count;
486 entry->record_data = GNUNET_new_array (rd_count,
487 struct GNUNET_GNSRECORD_Data);
488 for (unsigned int i = 0; i < rd_count; i++)
489 {
490 entry->record_data[i].expiration_time = rd[i].expiration_time;
491 entry->record_data[i].record_type = rd[i].record_type;
492 entry->record_data[i].flags = rd[i].flags;
493 entry->record_data[i].data_size = rd[i].data_size;
494 entry->record_data[i].data = GNUNET_malloc (rd[i].data_size);
495 GNUNET_memcpy ((char *) entry->record_data[i].data,
496 rd[i].data,
497 rd[i].data_size);
498 }
499 return GNUNET_CONTAINER_multihashmap_put (plugin->hm,
500 &hkey,
501 entry,
502 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
503}
504
505
506/**
507 * Lookup records in the datastore for which we are the authority.
508 *
509 * @param cls closure (internal context for the plugin)
510 * @param zone private key of the zone
511 * @param label name of the record in the zone
512 * @param iter function to call with the result
513 * @param iter_cls closure for @a iter
514 * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
515 */
516static int
517namestore_flat_lookup_records (void *cls,
518 const struct GNUNET_CRYPTO_PrivateKey *zone,
519 const char *label,
520 GNUNET_NAMESTORE_RecordIterator iter,
521 void *iter_cls)
522{
523 struct Plugin *plugin = cls;
524 struct FlatFileEntry *entry;
525 struct GNUNET_HashCode hkey;
526
527 if (NULL == zone)
528 {
529 GNUNET_break (0);
530 return GNUNET_SYSERR;
531 }
532 hash_pkey_and_label (zone,
533 label,
534 &hkey);
535 entry = GNUNET_CONTAINER_multihashmap_get (plugin->hm,
536 &hkey);
537
538 if (NULL == entry)
539 return GNUNET_NO;
540 if (NULL != iter)
541 iter (iter_cls,
542 1, /* zero is illegal */
543 &entry->private_key,
544 entry->label,
545 entry->record_count,
546 entry->record_data);
547 return GNUNET_YES;
548}
549
550
551/**
552 * Closure for #iterate_zones.
553 */
554struct IterateContext
555{
556 /**
557 * How many more records should we skip before returning results?
558 */
559 uint64_t offset;
560
561 /**
562 * How many more records should we return?
563 */
564 uint64_t limit;
565
566 /**
567 * What is the position of the current entry, counting
568 * starts from 1.
569 */
570 uint64_t pos;
571
572 /**
573 * Target zone.
574 */
575 const struct GNUNET_CRYPTO_PrivateKey *zone;
576
577 /**
578 * Function to call on each record.
579 */
580 GNUNET_NAMESTORE_RecordIterator iter;
581
582 /**
583 * Closure for @e iter.
584 */
585 void *iter_cls;
586};
587
588
589/**
590 * Helper function for #namestore_flat_iterate_records().
591 *
592 * @param cls a `struct IterateContext`
593 * @param key unused
594 * @param value a `struct FlatFileEntry`
595 * @return #GNUNET_YES to continue the iteration
596 */
597static int
598iterate_zones (void *cls,
599 const struct GNUNET_HashCode *key,
600 void *value)
601{
602 struct IterateContext *ic = cls;
603 struct FlatFileEntry *entry = value;
604
605 (void) key;
606 if (0 == ic->limit)
607 return GNUNET_NO;
608 if ((NULL != ic->zone) &&
609 (0 != GNUNET_memcmp (&entry->private_key,
610 ic->zone)))
611 return GNUNET_YES;
612 ic->pos++;
613 if (ic->offset > 0)
614 {
615 ic->offset--;
616 return GNUNET_YES;
617 }
618 ic->iter (ic->iter_cls,
619 ic->pos,
620 (NULL == ic->zone)
621 ? &entry->private_key
622 : ic->zone,
623 entry->label,
624 entry->record_count,
625 entry->record_data);
626 ic->limit--;
627 if (0 == ic->limit)
628 return GNUNET_NO;
629 return GNUNET_YES;
630}
631
632
633/**
634 * Iterate over the results for a particular key and zone in the
635 * datastore. Will return at most one result to the iterator.
636 *
637 * @param cls closure (internal context for the plugin)
638 * @param zone hash of public key of the zone, NULL to iterate over all zones
639 * @param serial serial number to exclude in the list of all matching records
640 * @param limit maximum number of results to return to @a iter
641 * @param iter function to call with the result
642 * @param iter_cls closure for @a iter
643 * @return #GNUNET_OK on success, #GNUNET_NO if there were no more results, #GNUNET_SYSERR on error
644 */
645static int
646namestore_flat_iterate_records (void *cls,
647 const struct
648 GNUNET_CRYPTO_PrivateKey *zone,
649 uint64_t serial,
650 uint64_t limit,
651 GNUNET_NAMESTORE_RecordIterator iter,
652 void *iter_cls)
653{
654 struct Plugin *plugin = cls;
655 struct IterateContext ic;
656
657 ic.offset = serial;
658 ic.pos = 0;
659 ic.limit = limit;
660 ic.iter = iter;
661 ic.iter_cls = iter_cls;
662 ic.zone = zone;
663 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
664 &iterate_zones,
665 &ic);
666 return (0 == ic.limit) ? GNUNET_OK : GNUNET_NO;
667}
668
669
670/**
671 * Closure for #zone_to_name.
672 */
673struct ZoneToNameContext
674{
675 const struct GNUNET_CRYPTO_PrivateKey *zone;
676 const struct GNUNET_CRYPTO_PublicKey *value_zone;
677 GNUNET_NAMESTORE_RecordIterator iter;
678 void *iter_cls;
679
680 int result_found;
681};
682
683
684static int
685zone_to_name (void *cls,
686 const struct GNUNET_HashCode *key,
687 void *value)
688{
689 struct ZoneToNameContext *ztn = cls;
690 struct FlatFileEntry *entry = value;
691
692 (void) key;
693 if (0 != GNUNET_memcmp (&entry->private_key,
694 ztn->zone))
695 return GNUNET_YES;
696
697 for (unsigned int i = 0; i < entry->record_count; i++)
698 {
699 if (GNUNET_NO ==
700 GNUNET_GNSRECORD_is_zonekey_type (entry->record_data[i].record_type))
701 continue;
702 if (ztn->value_zone->type != entry->record_data[i].record_type)
703 continue;
704 if (0 == memcmp (ztn->value_zone,
705 entry->record_data[i].data,
706 entry->record_data[i].data_size))
707 {
708 ztn->iter (ztn->iter_cls,
709 i + 1, /* zero is illegal! */
710 &entry->private_key,
711 entry->label,
712 entry->record_count,
713 entry->record_data);
714 ztn->result_found = GNUNET_YES;
715 }
716 }
717 return GNUNET_YES;
718}
719
720
721/**
722 * Look for an existing PKEY delegation record for a given public key.
723 * Returns at most one result to the iterator.
724 *
725 * @param cls closure (internal context for the plugin)
726 * @param zone private key of the zone to look up in, never NULL
727 * @param value_zone public key of the target zone (value), never NULL
728 * @param iter function to call with the result
729 * @param iter_cls closure for @a iter
730 * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
731 */
732static int
733namestore_flat_zone_to_name (void *cls,
734 const struct GNUNET_CRYPTO_PrivateKey *zone,
735 const struct
736 GNUNET_CRYPTO_PublicKey *value_zone,
737 GNUNET_NAMESTORE_RecordIterator iter,
738 void *iter_cls)
739{
740 struct Plugin *plugin = cls;
741 struct ZoneToNameContext ztn = {
742 .iter = iter,
743 .iter_cls = iter_cls,
744 .zone = zone,
745 .value_zone = value_zone,
746 .result_found = GNUNET_NO
747 };
748
749 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
750 "Performing reverse lookup for `%s'\n",
751 GNUNET_GNSRECORD_z2s (value_zone));
752 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
753 &zone_to_name,
754 &ztn);
755 return ztn.result_found;
756}
757
758
759/**
760 * Entry point for the plugin.
761 *
762 * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
763 * @return NULL on error, otherwise the plugin context
764 */
765void *
766libgnunet_plugin_namestore_flat_init (void *cls)
767{
768 static struct Plugin plugin;
769 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
770 struct GNUNET_NAMESTORE_PluginFunctions *api;
771
772 if (NULL != plugin.cfg)
773 return NULL; /* can only initialize once! */
774 memset (&plugin,
775 0,
776 sizeof(struct Plugin));
777 plugin.cfg = cfg;
778 if (GNUNET_OK != database_setup (&plugin))
779 {
780 database_shutdown (&plugin);
781 return NULL;
782 }
783 api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
784 api->cls = &plugin;
785 api->store_records = &namestore_flat_store_records;
786 api->iterate_records = &namestore_flat_iterate_records;
787 api->zone_to_name = &namestore_flat_zone_to_name;
788 api->lookup_records = &namestore_flat_lookup_records;
789 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
790 _ ("Flat file database running\n"));
791 return api;
792}
793
794
795/**
796 * Exit point from the plugin.
797 *
798 * @param cls the plugin context (as returned by "init")
799 * @return always NULL
800 */
801void *
802libgnunet_plugin_namestore_flat_done (void *cls)
803{
804 struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
805 struct Plugin *plugin = api->cls;
806
807 database_shutdown (plugin);
808 plugin->cfg = NULL;
809 GNUNET_free (api);
810 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
811 "Flat file plugin is finished\n");
812 return NULL;
813}
814
815
816/* end of plugin_namestore_flat.c */
diff --git a/src/plugin/namestore/plugin_namestore_postgres.c b/src/plugin/namestore/plugin_namestore_postgres.c
new file mode 100644
index 000000000..ccf84dfa6
--- /dev/null
+++ b/src/plugin/namestore/plugin_namestore_postgres.c
@@ -0,0 +1,786 @@
1/*
2 * This file is part of GNUnet
3 * Copyright (C) 2009-2013, 2016-2018 GNUnet e.V.
4 *
5 * GNUnet is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License,
8 * or (at your option) any later version.
9 *
10 * GNUnet is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file namestore/plugin_namestore_postgres.c
23 * @brief postgres-based namestore backend
24 * @author Christian Grothoff
25 */
26#include "gnunet_db_lib.h"
27#include "gnunet_namestore_plugin.h"
28#include "gnunet_gnsrecord_lib.h"
29#include "gnunet_pq_lib.h"
30
31
32#define LOG(kind, ...) GNUNET_log_from (kind, "namestore-postgres", __VA_ARGS__)
33
34
35/**
36 * Context for all functions in this plugin.
37 */
38struct Plugin
39{
40 /**
41 * Our configuration.
42 */
43 const struct GNUNET_CONFIGURATION_Handle *cfg;
44
45 /**
46 * Postgres database handle.
47 */
48 struct GNUNET_PQ_Context *dbh;
49
50 /**
51 * Database is prepared and ready
52 */
53 bool ready;
54};
55
56
57/**
58 * Initialize the database connections and associated data structures (create
59 * tables and indices as needed as well).
60 *
61 * @param cls the plugin context (state for this module)
62 * @return #GNUNET_OK on success
63 */
64static enum GNUNET_GenericReturnValue
65namestore_postgres_create_tables (void *cls)
66{
67 struct Plugin *plugin = cls;
68 struct GNUNET_PQ_Context *dbh;
69
70 dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
71 "namestore-postgres",
72 "namestore-",
73 NULL,
74 NULL);
75 if (NULL == dbh)
76 return GNUNET_SYSERR;
77 GNUNET_PQ_disconnect (dbh);
78 return GNUNET_OK;
79}
80
81
82/**
83 * Drop existing namestore tables.
84 *
85 * @param cls the plugin context (state for this module)
86 * @return #GNUNET_OK on success
87 */
88static enum GNUNET_GenericReturnValue
89namestore_postgres_drop_tables (void *cls)
90{
91 struct Plugin *plugin = cls;
92 struct GNUNET_PQ_Context *dbh;
93 enum GNUNET_GenericReturnValue ret;
94
95 dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
96 "namestore-postgres",
97 NULL,
98 NULL,
99 NULL);
100 if (NULL == dbh)
101 {
102 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
103 "Failed to connect to database\n");
104 return GNUNET_SYSERR;
105 }
106 ret = GNUNET_PQ_exec_sql (dbh,
107 "namestore-drop");
108 GNUNET_PQ_disconnect (dbh);
109 return ret;
110}
111
112
113static enum GNUNET_GenericReturnValue
114database_prepare (struct Plugin *plugin)
115{
116 enum GNUNET_GenericReturnValue ret;
117
118 if (plugin->ready)
119 return GNUNET_OK;
120 {
121 struct GNUNET_PQ_PreparedStatement ps[] = {
122 GNUNET_PQ_make_prepare ("store_records",
123 "INSERT INTO namestore.ns098records"
124 " (zone_private_key, pkey, rvalue, record_count, record_data, label, editor_hint)"
125 " VALUES ($1, $2, $3, $4, $5, $6, '')"
126 " ON CONFLICT ON CONSTRAINT zl"
127 " DO UPDATE"
128 " SET pkey=$2,rvalue=$3,record_count=$4,record_data=$5"
129 " WHERE ns098records.zone_private_key = $1"
130 " AND ns098records.label = $6"),
131 GNUNET_PQ_make_prepare ("delete_records",
132 "DELETE FROM namestore.ns098records "
133 "WHERE zone_private_key=$1 AND label=$2"),
134 GNUNET_PQ_make_prepare ("zone_to_name",
135 "SELECT seq,record_count,record_data,label,editor_hint FROM namestore.ns098records"
136 " WHERE zone_private_key=$1 AND pkey=$2"),
137 GNUNET_PQ_make_prepare ("iterate_zone",
138 "SELECT seq,record_count,record_data,label,editor_hint FROM namestore.ns098records "
139 "WHERE zone_private_key=$1 AND seq > $2 ORDER BY seq ASC LIMIT $3"),
140 GNUNET_PQ_make_prepare ("iterate_all_zones",
141 "SELECT seq,record_count,record_data,label,editor_hint,zone_private_key"
142 " FROM namestore.ns098records WHERE seq > $1 ORDER BY seq ASC LIMIT $2"),
143 GNUNET_PQ_make_prepare ("lookup_label",
144 "SELECT seq,record_count,record_data,label,editor_hint "
145 "FROM namestore.ns098records WHERE zone_private_key=$1 AND label=$2"),
146 GNUNET_PQ_make_prepare ("edit_set",
147 "UPDATE namestore.ns098records"
148 " SET editor_hint=$3"
149 " FROM namestore.ns098records AS old_ns098records"
150 " WHERE ns098records.zone_private_key=$1 AND ns098records.label=$2"
151 " RETURNING ns098records.seq,ns098records.record_count,ns098records.record_data,ns098records.label,old_ns098records.editor_hint "),
152 GNUNET_PQ_make_prepare ("clear_editor_hint",
153 "UPDATE namestore.ns098records"
154 " SET editor_hint=$4"
155 " WHERE zone_private_key=$1 AND label=$2 AND editor_hint=$3"),
156 GNUNET_PQ_PREPARED_STATEMENT_END
157 };
158
159 ret = GNUNET_PQ_prepare_statements (plugin->dbh,
160 ps);
161 }
162 if (GNUNET_OK != ret)
163 return ret;
164 plugin->ready = true;
165 return GNUNET_OK;
166}
167
168
169/**
170 * Initialize the database connections and associated
171 * data structures (create tables and indices
172 * as needed as well).
173 *
174 * @param plugin the plugin context (state for this module)
175 * @return #GNUNET_OK on success
176 */
177static enum GNUNET_GenericReturnValue
178database_connect (struct Plugin *plugin)
179{
180 struct GNUNET_PQ_ExecuteStatement ess[] = {
181 GNUNET_PQ_make_try_execute ("SET synchronous_commit TO off"),
182 GNUNET_PQ_EXECUTE_STATEMENT_END
183 };
184 struct GNUNET_PQ_ExecuteStatement *es;
185
186 if (GNUNET_YES ==
187 GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
188 "namestore-postgres",
189 "ASYNC_COMMIT"))
190 es = &ess[0];
191 else
192 es = &ess[1];
193
194 if (GNUNET_YES ==
195 GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
196 "namestore-postgres",
197 "INIT_ON_CONNECT"))
198 {
199 if (GNUNET_OK !=
200 namestore_postgres_create_tables (plugin))
201 {
202 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
203 "Failed to create tables\n");
204 return GNUNET_SYSERR;
205 }
206 }
207 plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
208 "namestore-postgres",
209 NULL,
210 es,
211 NULL);
212 if (NULL == plugin->dbh)
213 return GNUNET_SYSERR;
214 return GNUNET_OK;
215}
216
217
218/**
219 * Store a record in the datastore. Removes any existing record in the
220 * same zone with the same name.
221 *
222 * @param cls closure (internal context for the plugin)
223 * @param zone_key private key of the zone
224 * @param label name that is being mapped (at most 255 characters long)
225 * @param rd_count number of entries in @a rd array
226 * @param rd array of records with data to store
227 * @return #GNUNET_OK on success, else #GNUNET_SYSERR
228 */
229static enum GNUNET_GenericReturnValue
230namestore_postgres_store_records (void *cls,
231 const struct
232 GNUNET_CRYPTO_PrivateKey *zone_key,
233 const char *label,
234 unsigned int rd_count,
235 const struct GNUNET_GNSRECORD_Data *rd)
236{
237 struct Plugin *plugin = cls;
238 struct GNUNET_CRYPTO_PublicKey pkey;
239 uint64_t rvalue;
240 uint32_t rd_count32 = (uint32_t) rd_count;
241 ssize_t data_size;
242
243 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
244 memset (&pkey,
245 0,
246 sizeof(pkey));
247 for (unsigned int i = 0; i < rd_count; i++)
248 if (GNUNET_YES ==
249 GNUNET_GNSRECORD_is_zonekey_type (rd[i].record_type))
250 {
251 GNUNET_break (GNUNET_OK ==
252 GNUNET_GNSRECORD_identity_from_data (rd[i].data,
253 rd[i].data_size,
254 rd[i].record_type,
255 &pkey));
256 break;
257 }
258 rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
259 UINT64_MAX);
260 data_size = GNUNET_GNSRECORD_records_get_size (rd_count,
261 rd);
262 if (data_size < 0)
263 {
264 GNUNET_break (0);
265 return GNUNET_SYSERR;
266 }
267 if (data_size >= UINT16_MAX)
268 {
269 GNUNET_break (0);
270 return GNUNET_SYSERR;
271 }
272 /* if record set is empty, delete existing records */
273 if (0 == rd_count)
274 {
275 struct GNUNET_PQ_QueryParam params[] = {
276 GNUNET_PQ_query_param_auto_from_type (zone_key),
277 GNUNET_PQ_query_param_string (label),
278 GNUNET_PQ_query_param_end
279 };
280 enum GNUNET_DB_QueryStatus res;
281
282 res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
283 "delete_records",
284 params);
285 if ((GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != res) &&
286 (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != res))
287 {
288 GNUNET_break (0);
289 return GNUNET_SYSERR;
290 }
291 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
292 "postgres",
293 "Record deleted\n");
294 return GNUNET_OK;
295 }
296 /* otherwise, UPSERT (i.e. UPDATE if exists, otherwise INSERT) */
297 {
298 char data[data_size];
299 struct GNUNET_PQ_QueryParam params[] = {
300 GNUNET_PQ_query_param_auto_from_type (zone_key),
301 GNUNET_PQ_query_param_auto_from_type (&pkey),
302 GNUNET_PQ_query_param_uint64 (&rvalue),
303 GNUNET_PQ_query_param_uint32 (&rd_count32),
304 GNUNET_PQ_query_param_fixed_size (data, data_size),
305 GNUNET_PQ_query_param_string (label),
306 GNUNET_PQ_query_param_end
307 };
308 enum GNUNET_DB_QueryStatus res;
309 ssize_t ret;
310
311 ret = GNUNET_GNSRECORD_records_serialize (rd_count,
312 rd,
313 data_size,
314 data);
315 if ((ret < 0) ||
316 (data_size != ret))
317 {
318 GNUNET_break (0);
319 return GNUNET_SYSERR;
320 }
321
322 res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
323 "store_records",
324 params);
325 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != res)
326 return GNUNET_SYSERR;
327 }
328 return GNUNET_OK;
329}
330
331
332/**
333 * Closure for #parse_result_call_iterator.
334 */
335struct ParserContext
336{
337 /**
338 * Function to call for each result.
339 */
340 GNUNET_NAMESTORE_RecordIterator iter;
341
342 /**
343 * Closure for @e iter.
344 */
345 void *iter_cls;
346
347 /**
348 * Zone key, NULL if part of record.
349 */
350 const struct GNUNET_CRYPTO_PrivateKey *zone_key;
351
352 /**
353 * Number of results still to return (counted down by
354 * number of results given to iterator).
355 */
356 uint64_t limit;
357};
358
359
360/**
361 * A statement has been run. We should evaluate the result, and if possible
362 * call the @a iter in @a cls with the result.
363 *
364 * @param cls closure of type `struct ParserContext *`
365 * @param res the postgres result
366 * @param num_results the number of results in @a result
367 */
368static void
369parse_result_call_iterator (void *cls,
370 PGresult *res,
371 unsigned int num_results)
372{
373 struct ParserContext *pc = cls;
374
375 if (NULL == pc->iter)
376 return; /* no need to do more work */
377 LOG (GNUNET_ERROR_TYPE_DEBUG,
378 "Got %d results from PQ.\n", num_results);
379 for (unsigned int i = 0; i < num_results; i++)
380 {
381 uint64_t serial;
382 void *data;
383 size_t data_size;
384 uint32_t record_count;
385 char *label;
386 char *editor_hint;
387 struct GNUNET_CRYPTO_PrivateKey zk;
388 struct GNUNET_PQ_ResultSpec rs_with_zone[] = {
389 GNUNET_PQ_result_spec_uint64 ("seq", &serial),
390 GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
391 GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
392 GNUNET_PQ_result_spec_string ("label", &label),
393 GNUNET_PQ_result_spec_string ("editor_hint", &editor_hint),
394 GNUNET_PQ_result_spec_auto_from_type ("zone_private_key", &zk),
395 GNUNET_PQ_result_spec_end
396 };
397 struct GNUNET_PQ_ResultSpec rs_without_zone[] = {
398 GNUNET_PQ_result_spec_uint64 ("seq", &serial),
399 GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
400 GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
401 GNUNET_PQ_result_spec_string ("label", &label),
402 GNUNET_PQ_result_spec_string ("editor_hint", &editor_hint),
403 GNUNET_PQ_result_spec_end
404 };
405 struct GNUNET_PQ_ResultSpec *rs;
406
407 rs = (NULL == pc->zone_key) ? rs_with_zone : rs_without_zone;
408 if (GNUNET_YES !=
409 GNUNET_PQ_extract_result (res,
410 rs,
411 i))
412 {
413 GNUNET_break (0);
414 return;
415 }
416
417 if (record_count > 64 * 1024)
418 {
419 /* sanity check, don't stack allocate far too much just
420 because database might contain a large value here */
421 GNUNET_break (0);
422 GNUNET_PQ_cleanup_result (rs);
423 return;
424 }
425
426 {
427 struct GNUNET_GNSRECORD_Data rd[GNUNET_NZL (record_count)];
428
429 GNUNET_assert (0 != serial);
430 if (GNUNET_OK !=
431 GNUNET_GNSRECORD_records_deserialize (data_size,
432 data,
433 record_count,
434 rd))
435 {
436 GNUNET_break (0);
437 GNUNET_PQ_cleanup_result (rs);
438 return;
439 }
440 pc->iter (pc->iter_cls,
441 serial,
442 editor_hint,
443 (NULL == pc->zone_key) ? &zk : pc->zone_key,
444 label,
445 record_count,
446 rd);
447 }
448 GNUNET_PQ_cleanup_result (rs);
449 }
450 pc->limit -= num_results;
451}
452
453
454/**
455 * Lookup records in the datastore for which we are the authority.
456 *
457 * @param cls closure (internal context for the plugin)
458 * @param zone private key of the zone
459 * @param label name of the record in the zone
460 * @param iter function to call with the result
461 * @param iter_cls closure for @a iter
462 * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
463 */
464static enum GNUNET_GenericReturnValue
465namestore_postgres_lookup_records (void *cls,
466 const struct
467 GNUNET_CRYPTO_PrivateKey *zone,
468 const char *label,
469 GNUNET_NAMESTORE_RecordIterator iter,
470 void *iter_cls)
471{
472 struct Plugin *plugin = cls;
473 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
474 struct GNUNET_PQ_QueryParam params[] = {
475 GNUNET_PQ_query_param_auto_from_type (zone),
476 GNUNET_PQ_query_param_string (label),
477 GNUNET_PQ_query_param_end
478 };
479 struct ParserContext pc;
480 enum GNUNET_DB_QueryStatus res;
481
482 if (NULL == zone)
483 {
484 GNUNET_break (0);
485 return GNUNET_SYSERR;
486 }
487 pc.iter = iter;
488 pc.iter_cls = iter_cls;
489 pc.zone_key = zone;
490 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
491 "lookup_label",
492 params,
493 &parse_result_call_iterator,
494 &pc);
495 if (res < 0)
496 return GNUNET_SYSERR;
497 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
498 return GNUNET_NO;
499 return GNUNET_OK;
500}
501
502
503/**
504 *
505 * @param cls closure (internal context for the plugin)
506 * @param zone private key of the zone
507 * @param label name of the record in the zone
508 * @param iter function to call with the result
509 * @param iter_cls closure for @a iter
510 * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
511 */
512static int
513namestore_postgres_clear_editor_hint (void *cls,
514 const char *editor_hint,
515 const char *editor_hint_replacement,
516 const struct
517 GNUNET_CRYPTO_PrivateKey *zone,
518 const char *label)
519{
520
521 struct Plugin *plugin = cls;
522 struct GNUNET_CRYPTO_PublicKey pkey;
523
524 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
525 memset (&pkey,
526 0,
527 sizeof(pkey));
528 {
529 struct GNUNET_PQ_QueryParam params[] = {
530 GNUNET_PQ_query_param_auto_from_type (zone),
531 GNUNET_PQ_query_param_string (label),
532 GNUNET_PQ_query_param_string (editor_hint),
533 GNUNET_PQ_query_param_string (editor_hint_replacement),
534 GNUNET_PQ_query_param_end
535 };
536 enum GNUNET_DB_QueryStatus res;
537
538 res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
539 "clear_editor_hint",
540 params);
541 if ((GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != res) &&
542 (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != res))
543 return GNUNET_SYSERR;
544 }
545 return GNUNET_OK;
546}
547
548
549/**
550 * Edit records in the datastore for which we are the authority.
551 *
552 * @param cls closure (internal context for the plugin)
553 * @param zone private key of the zone
554 * @param label name of the record in the zone
555 * @param iter function to call with the result
556 * @param iter_cls closure for @a iter
557 * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
558 */
559static int
560namestore_postgres_edit_records (void *cls,
561 const char *editor_hint,
562 const struct
563 GNUNET_CRYPTO_PrivateKey *zone,
564 const char *label,
565 GNUNET_NAMESTORE_RecordIterator iter,
566 void *iter_cls)
567{
568 struct Plugin *plugin = cls;
569 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
570 struct GNUNET_PQ_QueryParam params[] = {
571 GNUNET_PQ_query_param_auto_from_type (zone),
572 GNUNET_PQ_query_param_string (label),
573 GNUNET_PQ_query_param_string (editor_hint),
574 GNUNET_PQ_query_param_end
575 };
576 struct ParserContext pc;
577 enum GNUNET_DB_QueryStatus res;
578
579 if (NULL == zone)
580 {
581 GNUNET_break (0);
582 return GNUNET_SYSERR;
583 }
584 pc.iter = iter;
585 pc.iter_cls = iter_cls;
586 pc.zone_key = zone;
587 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
588 "edit_set",
589 params,
590 &parse_result_call_iterator,
591 &pc);
592 if (res < 0)
593 return GNUNET_SYSERR;
594 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
595 return GNUNET_NO;
596 return GNUNET_OK;
597}
598
599
600/**
601 * Iterate over the results for a particular key and zone in the
602 * datastore. Will return at most one result to the iterator.
603 *
604 * @param cls closure (internal context for the plugin)
605 * @param zone hash of public key of the zone, NULL to iterate over all zones
606 * @param serial serial number to exclude in the list of all matching records
607 * @param limit maximum number of results to fetch
608 * @param iter function to call with the result
609 * @param iter_cls closure for @a iter
610 * @return #GNUNET_OK on success, #GNUNET_NO if there were no more results, #GNUNET_SYSERR on error
611 */
612static enum GNUNET_GenericReturnValue
613namestore_postgres_iterate_records (void *cls,
614 const struct
615 GNUNET_CRYPTO_PrivateKey *zone,
616 uint64_t serial,
617 uint64_t limit,
618 GNUNET_NAMESTORE_RecordIterator iter,
619 void *iter_cls)
620{
621 struct Plugin *plugin = cls;
622 enum GNUNET_DB_QueryStatus res;
623 struct ParserContext pc;
624
625 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
626 pc.iter = iter;
627 pc.iter_cls = iter_cls;
628 pc.zone_key = zone;
629 pc.limit = limit;
630 if (NULL == zone)
631 {
632 struct GNUNET_PQ_QueryParam params_without_zone[] = {
633 GNUNET_PQ_query_param_uint64 (&serial),
634 GNUNET_PQ_query_param_uint64 (&limit),
635 GNUNET_PQ_query_param_end
636 };
637
638 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
639 "iterate_all_zones",
640 params_without_zone,
641 &parse_result_call_iterator,
642 &pc);
643 }
644 else
645 {
646 struct GNUNET_PQ_QueryParam params_with_zone[] = {
647 GNUNET_PQ_query_param_auto_from_type (zone),
648 GNUNET_PQ_query_param_uint64 (&serial),
649 GNUNET_PQ_query_param_uint64 (&limit),
650 GNUNET_PQ_query_param_end
651 };
652
653 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
654 "iterate_zone",
655 params_with_zone,
656 &parse_result_call_iterator,
657 &pc);
658 }
659 if (res < 0)
660 return GNUNET_SYSERR;
661
662 if ((GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res) ||
663 (pc.limit > 0))
664 return GNUNET_NO;
665 return GNUNET_OK;
666}
667
668
669/**
670 * Look for an existing PKEY delegation record for a given public key.
671 * Returns at most one result to the iterator.
672 *
673 * @param cls closure (internal context for the plugin)
674 * @param zone private key of the zone to look up in, never NULL
675 * @param value_zone public key of the target zone (value), never NULL
676 * @param iter function to call with the result
677 * @param iter_cls closure for @a iter
678 * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
679 */
680static enum GNUNET_GenericReturnValue
681namestore_postgres_zone_to_name (void *cls,
682 const struct
683 GNUNET_CRYPTO_PrivateKey *zone,
684 const struct
685 GNUNET_CRYPTO_PublicKey *value_zone,
686 GNUNET_NAMESTORE_RecordIterator iter,
687 void *iter_cls)
688{
689 struct Plugin *plugin = cls;
690 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
691 struct GNUNET_PQ_QueryParam params[] = {
692 GNUNET_PQ_query_param_auto_from_type (zone),
693 GNUNET_PQ_query_param_auto_from_type (value_zone),
694 GNUNET_PQ_query_param_end
695 };
696 enum GNUNET_DB_QueryStatus res;
697 struct ParserContext pc;
698
699 pc.iter = iter;
700 pc.iter_cls = iter_cls;
701 pc.zone_key = zone;
702 res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
703 "zone_to_name",
704 params,
705 &parse_result_call_iterator,
706 &pc);
707 if (res < 0)
708 return GNUNET_SYSERR;
709 return GNUNET_OK;
710}
711
712
713/**
714 * Shutdown database connection and associate data
715 * structures.
716 *
717 * @param plugin the plugin context (state for this module)
718 */
719static void
720database_shutdown (struct Plugin *plugin)
721{
722 GNUNET_PQ_disconnect (plugin->dbh);
723 plugin->dbh = NULL;
724}
725
726
727/**
728 * Entry point for the plugin.
729 *
730 * @param cls the `struct GNUNET_NAMESTORE_PluginEnvironment*`
731 * @return NULL on error, othrewise the plugin context
732 */
733void *
734libgnunet_plugin_namestore_postgres_init (void *cls)
735{
736 struct Plugin *plugin;
737 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
738 struct GNUNET_NAMESTORE_PluginFunctions *api;
739
740 plugin = GNUNET_new (struct Plugin);
741 plugin->cfg = cfg;
742 if (GNUNET_OK != database_connect (plugin))
743 {
744 database_shutdown (plugin);
745 GNUNET_free (plugin);
746 return NULL;
747 }
748 api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
749 api->cls = plugin;
750 api->create_tables = &namestore_postgres_create_tables;
751 api->drop_tables = &namestore_postgres_drop_tables;
752 api->store_records = &namestore_postgres_store_records;
753 api->iterate_records = &namestore_postgres_iterate_records;
754 api->zone_to_name = &namestore_postgres_zone_to_name;
755 api->lookup_records = &namestore_postgres_lookup_records;
756 api->edit_records = &namestore_postgres_edit_records;
757 api->clear_editor_hint = &namestore_postgres_clear_editor_hint;
758 LOG (GNUNET_ERROR_TYPE_INFO,
759 "Postgres namestore plugin running\n");
760 return api;
761}
762
763
764/**
765 * Exit point from the plugin.
766 *
767 * @param cls the plugin context (as returned by "init")
768 * @return always NULL
769 */
770void *
771libgnunet_plugin_namestore_postgres_done (void *cls)
772{
773 struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
774 struct Plugin *plugin = api->cls;
775
776 database_shutdown (plugin);
777 plugin->cfg = NULL;
778 GNUNET_free (plugin);
779 GNUNET_free (api);
780 LOG (GNUNET_ERROR_TYPE_DEBUG,
781 "Postgres namestore plugin is finished\n");
782 return NULL;
783}
784
785
786/* end of plugin_namestore_postgres.c */
diff --git a/src/plugin/namestore/plugin_namestore_sqlite.c b/src/plugin/namestore/plugin_namestore_sqlite.c
new file mode 100644
index 000000000..50c2f5728
--- /dev/null
+++ b/src/plugin/namestore/plugin_namestore_sqlite.c
@@ -0,0 +1,1030 @@
1/*
2 * This file is part of GNUnet
3 * Copyright (C) 2009-2017 GNUnet e.V.
4 *
5 * GNUnet is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License,
8 * or (at your option) any later version.
9 *
10 * GNUnet is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file namestore/plugin_namestore_sqlite.c
23 * @brief sqlite-based namestore backend
24 * @author Christian Grothoff
25 */
26
27#include "platform.h"
28#include "gnunet_namestore_plugin.h"
29#include "gnunet_gnsrecord_lib.h"
30#include "gnunet_sq_lib.h"
31#include <sqlite3.h>
32
33/**
34 * After how many ms "busy" should a DB operation fail for good? A
35 * low value makes sure that we are more responsive to requests
36 * (especially PUTs). A high value guarantees a higher success rate
37 * (SELECTs in iterate can take several seconds despite LIMIT=1).
38 *
39 * The default value of 1s should ensure that users do not experience
40 * huge latencies while at the same time allowing operations to
41 * succeed with reasonable probability.
42 */
43#define BUSY_TIMEOUT_MS 1000
44
45
46/**
47 * Log an error message at log-level 'level' that indicates
48 * a failure of the command 'cmd' on file 'filename'
49 * with the message given by strerror(errno).
50 */
51#define LOG_SQLITE(db, level, cmd) do { \
52 GNUNET_log_from (level, \
53 "namestore-sqlite", _ ( \
54 "`%s' failed at %s:%d with error: %s\n"), \
55 cmd, \
56 __FILE__, __LINE__, \
57 sqlite3_errmsg ( \
58 db->dbh)); \
59} while (0)
60
61#define LOG(kind, ...) GNUNET_log_from (kind, "namestore-sqlite", __VA_ARGS__)
62
63
64/**
65 * Context for all functions in this plugin.
66 */
67struct Plugin
68{
69 const struct GNUNET_CONFIGURATION_Handle *cfg;
70
71 /**
72 * Database filename.
73 */
74 char *fn;
75
76 /**
77 * Statements prepared, we are ready to go if true.
78 */
79 bool ready;
80
81 /**
82 * Native SQLite database handle.
83 */
84 sqlite3 *dbh;
85
86 /**
87 * Precompiled SQL to store records.
88 */
89 sqlite3_stmt *store_records;
90
91 /**
92 * Precompiled SQL to deltete existing records.
93 */
94 sqlite3_stmt *delete_records;
95
96 /**
97 * Precompiled SQL for iterate records within a zone.
98 */
99 sqlite3_stmt *iterate_zone;
100
101 /**
102 * Precompiled SQL for iterate all records within all zones.
103 */
104 sqlite3_stmt *iterate_all_zones;
105
106 /**
107 * Precompiled SQL to for reverse lookup based on PKEY.
108 */
109 sqlite3_stmt *zone_to_name;
110
111 /**
112 * Precompiled SQL to lookup records based on label.
113 */
114 sqlite3_stmt *lookup_label;
115
116 /**
117 * Precompiled SQL to clear editor hint.
118 */
119 sqlite3_stmt *editor_hint_clear;
120};
121
122
123/**
124 * Initialize the database connections and associated
125 * data structures (create tables and indices
126 * as needed as well).
127 *
128 * @param plugin the plugin context (state for this module)
129 * @return #GNUNET_OK on success
130 */
131static enum GNUNET_GenericReturnValue
132database_prepare (struct Plugin *plugin)
133{
134 if (plugin->ready)
135 return GNUNET_OK;
136 struct GNUNET_SQ_ExecuteStatement es[] = {
137 GNUNET_SQ_make_try_execute ("PRAGMA temp_store=MEMORY"),
138 GNUNET_SQ_make_try_execute ("PRAGMA synchronous=NORMAL"),
139 GNUNET_SQ_make_try_execute ("PRAGMA legacy_file_format=OFF"),
140 GNUNET_SQ_make_try_execute ("PRAGMA auto_vacuum=INCREMENTAL"),
141 GNUNET_SQ_make_try_execute ("PRAGMA encoding=\"UTF-8\""),
142 GNUNET_SQ_make_try_execute ("PRAGMA locking_mode=NORMAL"),
143 GNUNET_SQ_make_try_execute ("PRAGMA journal_mode=WAL"),
144 GNUNET_SQ_make_try_execute ("PRAGMA page_size=4092"),
145 GNUNET_SQ_EXECUTE_STATEMENT_END
146 };
147 struct GNUNET_SQ_PrepareStatement ps[] = {
148 GNUNET_SQ_make_prepare ("INSERT INTO ns098records "
149 "(zone_private_key,pkey,rvalue,record_count,record_data,"
150 "label,editor_hint)"
151 " VALUES (?, ?, ?, ?, ?, ?, '')",
152 &plugin->store_records),
153 GNUNET_SQ_make_prepare ("DELETE FROM ns098records "
154 "WHERE zone_private_key=? AND label=?",
155 &plugin->delete_records),
156 GNUNET_SQ_make_prepare (
157 "SELECT uid,record_count,record_data,label,editor_hint"
158 " FROM ns098records"
159 " WHERE zone_private_key=? AND pkey=?",
160 &plugin->zone_to_name),
161 GNUNET_SQ_make_prepare (
162 "SELECT uid,record_count,record_data,label,editor_hint"
163 " FROM ns098records"
164 " WHERE zone_private_key=? AND uid > ?"
165 " ORDER BY uid ASC"
166 " LIMIT ?",
167 &plugin->iterate_zone),
168 GNUNET_SQ_make_prepare (
169 "SELECT uid,record_count,record_data,label,editor_hint,zone_private_key"
170 " FROM ns098records"
171 " WHERE uid > ?"
172 " ORDER BY uid ASC"
173 " LIMIT ?",
174 &plugin->iterate_all_zones),
175 GNUNET_SQ_make_prepare ("UPDATE ns098records"
176 " SET editor_hint=?"
177 " FROM ns098records AS old_ns098records"
178 " WHERE ns098records.zone_private_key=? AND ns098records.label=?"
179 " RETURNING ns098records.uid,ns098records.record_count,ns098records.record_data,ns098records.label,editor_hint ",
180 &plugin->lookup_label),
181 GNUNET_SQ_make_prepare ("UPDATE ns098records"
182 " SET editor_hint=?"
183 " FROM ns098records AS old_ns098records"
184 " WHERE ns098records.zone_private_key=? AND ns098records.label=? AND ns098records.editor_hint=?",
185 &plugin->editor_hint_clear),
186 GNUNET_SQ_PREPARE_END
187 };
188
189 if (GNUNET_OK !=
190 GNUNET_SQ_exec_statements (plugin->dbh,
191 es))
192 {
193 LOG (GNUNET_ERROR_TYPE_ERROR,
194 _ ("Failed to setup database with: `%s'\n"),
195 sqlite3_errmsg (plugin->dbh));
196 return GNUNET_SYSERR;
197 }
198 if (GNUNET_OK !=
199 GNUNET_SQ_prepare (plugin->dbh,
200 ps))
201 {
202 GNUNET_break (0);
203 LOG (GNUNET_ERROR_TYPE_ERROR,
204 _ ("Failed to setup database with: `%s'\n"),
205 sqlite3_errmsg (plugin->dbh));
206 return GNUNET_SYSERR;
207 }
208 plugin->ready = GNUNET_YES;
209 return GNUNET_OK;
210}
211
212
213/**
214 * Shutdown database connection and associate data
215 * structures.
216 * @param plugin the plugin context (state for this module)
217 */
218static void
219database_shutdown (struct Plugin *plugin)
220{
221 int result;
222 sqlite3_stmt *stmt;
223
224 if (NULL != plugin->store_records)
225 sqlite3_finalize (plugin->store_records);
226 if (NULL != plugin->delete_records)
227 sqlite3_finalize (plugin->delete_records);
228 if (NULL != plugin->iterate_zone)
229 sqlite3_finalize (plugin->iterate_zone);
230 if (NULL != plugin->iterate_all_zones)
231 sqlite3_finalize (plugin->iterate_all_zones);
232 if (NULL != plugin->zone_to_name)
233 sqlite3_finalize (plugin->zone_to_name);
234 if (NULL != plugin->lookup_label)
235 sqlite3_finalize (plugin->lookup_label);
236 if (NULL != plugin->editor_hint_clear)
237 sqlite3_finalize (plugin->editor_hint_clear);
238 result = sqlite3_close (plugin->dbh);
239 if (result == SQLITE_BUSY)
240 {
241 LOG (GNUNET_ERROR_TYPE_WARNING,
242 _ (
243 "Tried to close sqlite without finalizing all prepared statements.\n"));
244 stmt = sqlite3_next_stmt (plugin->dbh,
245 NULL);
246 while (NULL != stmt)
247 {
248 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
249 "sqlite",
250 "Closing statement %p\n",
251 stmt);
252 result = sqlite3_finalize (stmt);
253 if (result != SQLITE_OK)
254 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
255 "sqlite",
256 "Failed to close statement %p: %d\n",
257 stmt,
258 result);
259 stmt = sqlite3_next_stmt (plugin->dbh,
260 NULL);
261 }
262 result = sqlite3_close (plugin->dbh);
263 }
264 if (SQLITE_OK != result)
265 LOG_SQLITE (plugin,
266 GNUNET_ERROR_TYPE_ERROR,
267 "sqlite3_close");
268
269}
270
271
272/**
273 * Store a record in the datastore. Removes any existing record in the
274 * same zone with the same name.
275 *
276 * @param cls closure (internal context for the plugin)
277 * @param zone_key private key of the zone
278 * @param label name that is being mapped (at most 255 characters long)
279 * @param rd_count number of entries in @a rd array
280 * @param rd array of records with data to store
281 * @return #GNUNET_OK on success, else #GNUNET_SYSERR
282 */
283static enum GNUNET_GenericReturnValue
284namestore_sqlite_store_records (void *cls,
285 const struct
286 GNUNET_CRYPTO_PrivateKey *zone_key,
287 const char *label,
288 unsigned int rd_count,
289 const struct GNUNET_GNSRECORD_Data *rd)
290{
291 struct Plugin *plugin = cls;
292 int n;
293 struct GNUNET_CRYPTO_PublicKey pkey;
294 uint64_t rvalue;
295 ssize_t data_size;
296
297 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
298 memset (&pkey,
299 0,
300 sizeof(pkey));
301 for (unsigned int i = 0; i < rd_count; i++)
302 {
303 LOG (GNUNET_ERROR_TYPE_DEBUG,
304 "Checking if `%d' is zonekey type\n",
305 rd[i].record_type);
306
307 if (GNUNET_YES == GNUNET_GNSRECORD_is_zonekey_type (rd[i].record_type))
308 {
309 GNUNET_break (GNUNET_YES ==
310 GNUNET_GNSRECORD_identity_from_data (rd[i].data,
311 rd[i].data_size,
312 rd[i].record_type,
313 &pkey));
314 LOG (GNUNET_ERROR_TYPE_DEBUG,
315 "Storing delegation zone record value `%s'\n",
316 GNUNET_GNSRECORD_z2s (&pkey));
317
318 break;
319 }
320 }
321 rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
322 UINT64_MAX);
323 data_size = GNUNET_GNSRECORD_records_get_size (rd_count,
324 rd);
325 if (data_size < 0)
326 {
327 GNUNET_break (0);
328 return GNUNET_SYSERR;
329 }
330 if (data_size > 64 * 65536)
331 {
332 GNUNET_break (0);
333 return GNUNET_SYSERR;
334 }
335 {
336 /* First delete 'old' records */
337 char data[data_size];
338 struct GNUNET_SQ_QueryParam dparams[] = {
339 GNUNET_SQ_query_param_auto_from_type (zone_key),
340 GNUNET_SQ_query_param_string (label),
341 GNUNET_SQ_query_param_end
342 };
343 ssize_t ret;
344
345 ret = GNUNET_GNSRECORD_records_serialize (rd_count,
346 rd,
347 data_size,
348 data);
349 if ((ret < 0) ||
350 (data_size != ret))
351 {
352 GNUNET_break (0);
353 return GNUNET_SYSERR;
354 }
355 if (GNUNET_OK !=
356 GNUNET_SQ_bind (plugin->delete_records,
357 dparams))
358 {
359 LOG_SQLITE (plugin,
360 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
361 "sqlite3_bind_XXXX");
362 GNUNET_SQ_reset (plugin->dbh,
363 plugin->delete_records);
364 return GNUNET_SYSERR;
365 }
366 n = sqlite3_step (plugin->delete_records);
367 GNUNET_SQ_reset (plugin->dbh,
368 plugin->delete_records);
369
370 if (0 != rd_count)
371 {
372 uint32_t rd_count32 = (uint32_t) rd_count;
373 struct GNUNET_SQ_QueryParam sparams[] = {
374 GNUNET_SQ_query_param_auto_from_type (zone_key),
375 GNUNET_SQ_query_param_auto_from_type (&pkey),
376 GNUNET_SQ_query_param_uint64 (&rvalue),
377 GNUNET_SQ_query_param_uint32 (&rd_count32),
378 GNUNET_SQ_query_param_fixed_size (data, data_size),
379 GNUNET_SQ_query_param_string (label),
380 GNUNET_SQ_query_param_end
381 };
382
383 if (GNUNET_OK !=
384 GNUNET_SQ_bind (plugin->store_records,
385 sparams))
386 {
387 LOG_SQLITE (plugin,
388 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
389 "sqlite3_bind_XXXX");
390 GNUNET_SQ_reset (plugin->dbh,
391 plugin->store_records);
392 return GNUNET_SYSERR;
393 }
394 n = sqlite3_step (plugin->store_records);
395 GNUNET_SQ_reset (plugin->dbh,
396 plugin->store_records);
397 }
398 }
399 switch (n)
400 {
401 case SQLITE_DONE:
402 if (0 != rd_count)
403 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
404 "sqlite",
405 "Record stored\n");
406 else
407 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
408 "sqlite",
409 "Record deleted\n");
410 return GNUNET_OK;
411
412 case SQLITE_BUSY:
413 LOG_SQLITE (plugin,
414 GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
415 "sqlite3_step");
416 return GNUNET_NO;
417
418 default:
419 LOG_SQLITE (plugin,
420 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
421 "sqlite3_step");
422 return GNUNET_SYSERR;
423 }
424}
425
426
427/**
428 * The given 'sqlite' statement has been prepared to be run.
429 * It will return a record which should be given to the iterator.
430 * Runs the statement and parses the returned record.
431 *
432 * @param plugin plugin context
433 * @param stmt to run (and then clean up)
434 * @param zone_key private key of the zone
435 * @param limit maximum number of results to fetch
436 * @param iter iterator to call with the result
437 * @param iter_cls closure for @a iter
438 * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
439 */
440static enum GNUNET_GenericReturnValue
441get_records_and_call_iterator (struct Plugin *plugin,
442 sqlite3_stmt *stmt,
443 const struct
444 GNUNET_CRYPTO_PrivateKey *zone_key,
445 uint64_t limit,
446 GNUNET_NAMESTORE_RecordIterator iter,
447 void *iter_cls)
448{
449 int ret;
450 int sret;
451
452 ret = GNUNET_OK;
453 for (uint64_t i = 0; i < limit; i++)
454 {
455 sret = sqlite3_step (stmt);
456
457 if (SQLITE_DONE == sret)
458 {
459 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
460 "Iteration done (no results)\n");
461 ret = GNUNET_NO;
462 break;
463 }
464 if (SQLITE_ROW != sret)
465 {
466 LOG_SQLITE (plugin,
467 GNUNET_ERROR_TYPE_ERROR,
468 "sqlite_step");
469 ret = GNUNET_SYSERR;
470 break;
471 }
472
473 {
474 uint64_t seq;
475 uint32_t record_count;
476 size_t data_size;
477 void *data;
478 char *label;
479 char *editor_hint;
480 struct GNUNET_CRYPTO_PrivateKey zk;
481 struct GNUNET_SQ_ResultSpec rs[] = {
482 GNUNET_SQ_result_spec_uint64 (&seq),
483 GNUNET_SQ_result_spec_uint32 (&record_count),
484 GNUNET_SQ_result_spec_variable_size (&data,
485 &data_size),
486 GNUNET_SQ_result_spec_string (&label),
487 GNUNET_SQ_result_spec_string (&editor_hint),
488 GNUNET_SQ_result_spec_end
489 };
490 struct GNUNET_SQ_ResultSpec rsx[] = {
491 GNUNET_SQ_result_spec_uint64 (&seq),
492 GNUNET_SQ_result_spec_uint32 (&record_count),
493 GNUNET_SQ_result_spec_variable_size (&data,
494 &data_size),
495 GNUNET_SQ_result_spec_string (&label),
496 GNUNET_SQ_result_spec_string (&editor_hint),
497 GNUNET_SQ_result_spec_auto_from_type (&zk),
498 GNUNET_SQ_result_spec_end
499 };
500
501 ret = GNUNET_SQ_extract_result (stmt,
502 (NULL == zone_key)
503 ? rsx
504 : rs);
505 if ((GNUNET_OK != ret) ||
506 (record_count > 64 * 1024))
507 {
508 /* sanity check, don't stack allocate far too much just
509 because database might contain a large value here */
510 GNUNET_break (0);
511 ret = GNUNET_SYSERR;
512 break;
513 }
514 else
515 {
516 struct GNUNET_GNSRECORD_Data rd[record_count];
517
518 GNUNET_assert (0 != seq);
519 if (GNUNET_OK !=
520 GNUNET_GNSRECORD_records_deserialize (data_size,
521 data,
522 record_count,
523 rd))
524 {
525 GNUNET_break (0);
526 ret = GNUNET_SYSERR;
527 break;
528 }
529 else
530 {
531 if (NULL != zone_key)
532 zk = *zone_key;
533 if (NULL != iter)
534 iter (iter_cls,
535 seq,
536 editor_hint,
537 &zk,
538 label,
539 record_count,
540 rd);
541 }
542 }
543 GNUNET_SQ_cleanup_result (rs);
544 }
545 }
546 GNUNET_SQ_reset (plugin->dbh,
547 stmt);
548 return ret;
549}
550
551
552/**
553 * Lookup records in the datastore for which we are the authority.
554 *
555 * @param cls closure (internal context for the plugin)
556 * @param zone private key of the zone
557 * @param label name of the record in the zone
558 * @param iter function to call with the result
559 * @param iter_cls closure for @a iter
560 * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
561 */
562static enum GNUNET_GenericReturnValue
563lookup_records (void *cls,
564 const struct
565 GNUNET_CRYPTO_PrivateKey *zone,
566 const char *label,
567 GNUNET_NAMESTORE_RecordIterator iter,
568 void *iter_cls,
569 const char *editor_hint)
570{
571 struct Plugin *plugin = cls;
572 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
573 struct GNUNET_SQ_QueryParam params[] = {
574 GNUNET_SQ_query_param_string (editor_hint),
575 GNUNET_SQ_query_param_auto_from_type (zone),
576 GNUNET_SQ_query_param_string (label),
577 GNUNET_SQ_query_param_end
578 };
579
580 if (NULL == zone)
581 {
582 GNUNET_break (0);
583 return GNUNET_SYSERR;
584 }
585 if (GNUNET_OK !=
586 GNUNET_SQ_bind (plugin->lookup_label,
587 params))
588 {
589 LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
590 "sqlite3_bind_XXXX");
591 GNUNET_SQ_reset (plugin->dbh,
592 plugin->lookup_label);
593 return GNUNET_SYSERR;
594 }
595 return get_records_and_call_iterator (plugin,
596 plugin->lookup_label,
597 zone,
598 1,
599 iter,
600 iter_cls);
601}
602
603
604/**
605 * Lookup records in the datastore for which we are the authority.
606 *
607 * @param cls closure (internal context for the plugin)
608 * @param zone private key of the zone
609 * @param label name of the record in the zone
610 * @param iter function to call with the result
611 * @param iter_cls closure for @a iter
612 * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
613 */
614static enum GNUNET_GenericReturnValue
615namestore_sqlite_lookup_records (void *cls,
616 const struct
617 GNUNET_CRYPTO_PrivateKey *zone,
618 const char *label,
619 GNUNET_NAMESTORE_RecordIterator iter,
620 void *iter_cls)
621{
622 return lookup_records (cls, zone, label, iter, iter_cls, "");
623}
624
625
626/**
627 * Clear editor hint.
628 *
629 * @param cls closure (internal context for the plugin)
630 * @param zone private key of the zone
631 * @param label name of the record in the zone
632 * @param editor_hint editor hint to clear
633 * @param editor_hint_repl editor hint to replace the old with (optional)
634 * @param iter function to call with the result
635 * @param iter_cls closure for @a iter
636 * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
637 */
638static enum GNUNET_GenericReturnValue
639namestore_sqlite_editor_hint_clear (void *cls,
640 const char *editor_hint,
641 const char *editor_hint_replacement,
642 const struct
643 GNUNET_CRYPTO_PrivateKey *zone,
644 const char *label)
645{
646 struct Plugin *plugin = cls;
647 int n;
648 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
649 struct GNUNET_SQ_QueryParam params[] = {
650 GNUNET_SQ_query_param_string ((NULL == editor_hint_replacement) ? "":
651 editor_hint_replacement),
652 GNUNET_SQ_query_param_auto_from_type (zone),
653 GNUNET_SQ_query_param_string (label),
654 GNUNET_SQ_query_param_string (editor_hint),
655 GNUNET_SQ_query_param_end
656 };
657
658 if (NULL == zone)
659 {
660 GNUNET_break (0);
661 return GNUNET_SYSERR;
662 }
663 if (GNUNET_OK !=
664 GNUNET_SQ_bind (plugin->editor_hint_clear,
665 params))
666 {
667 LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
668 "sqlite3_bind_XXXX");
669 GNUNET_SQ_reset (plugin->dbh,
670 plugin->editor_hint_clear);
671 return GNUNET_SYSERR;
672 }
673 n = sqlite3_step (plugin->editor_hint_clear);
674 GNUNET_SQ_reset (plugin->dbh,
675 plugin->store_records);
676 switch (n)
677 {
678 case SQLITE_DONE:
679 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
680 "sqlite",
681 "Editor hint cleared\n");
682 return GNUNET_OK;
683
684 case SQLITE_BUSY:
685 LOG_SQLITE (plugin,
686 GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
687 "sqlite3_step");
688 return GNUNET_NO;
689
690 default:
691 LOG_SQLITE (plugin,
692 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
693 "sqlite3_step");
694 return GNUNET_SYSERR;
695 }
696}
697
698
699/**
700 * Lookup records in the datastore for which we are the authority.
701 *
702 * @param cls closure (internal context for the plugin)
703 * @param zone private key of the zone
704 * @param label name of the record in the zone
705 * @param iter function to call with the result
706 * @param iter_cls closure for @a iter
707 * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
708 */
709static enum GNUNET_GenericReturnValue
710namestore_sqlite_edit_records (void *cls,
711 const char *editor_hint,
712 const struct
713 GNUNET_CRYPTO_PrivateKey *zone,
714 const char *label,
715 GNUNET_NAMESTORE_RecordIterator iter,
716 void *iter_cls)
717{
718 return lookup_records (cls, zone, label, iter, iter_cls, editor_hint);
719}
720
721
722/**
723 * Iterate over the results for a particular key and zone in the
724 * datastore. Will return at most one result to the iterator.
725 *
726 * @param cls closure (internal context for the plugin)
727 * @param zone hash of public key of the zone, NULL to iterate over all zones
728 * @param serial serial number to exclude in the list of all matching records
729 * @param limit maximum number of results to return
730 * @param iter function to call with the result
731 * @param iter_cls closure for @a iter
732 * @return #GNUNET_OK on success, #GNUNET_NO if there were no more results, #GNUNET_SYSERR on error
733 */
734static enum GNUNET_GenericReturnValue
735namestore_sqlite_iterate_records (void *cls,
736 const struct
737 GNUNET_CRYPTO_PrivateKey *zone,
738 uint64_t serial,
739 uint64_t limit,
740 GNUNET_NAMESTORE_RecordIterator iter,
741 void *iter_cls)
742{
743 struct Plugin *plugin = cls;
744 sqlite3_stmt *stmt;
745 int err;
746
747 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
748 if (NULL == zone)
749 {
750 struct GNUNET_SQ_QueryParam params[] = {
751 GNUNET_SQ_query_param_uint64 (&serial),
752 GNUNET_SQ_query_param_uint64 (&limit),
753 GNUNET_SQ_query_param_end
754 };
755
756 stmt = plugin->iterate_all_zones;
757 err = GNUNET_SQ_bind (stmt,
758 params);
759 }
760 else
761 {
762 struct GNUNET_SQ_QueryParam params[] = {
763 GNUNET_SQ_query_param_auto_from_type (zone),
764 GNUNET_SQ_query_param_uint64 (&serial),
765 GNUNET_SQ_query_param_uint64 (&limit),
766 GNUNET_SQ_query_param_end
767 };
768
769 stmt = plugin->iterate_zone;
770 err = GNUNET_SQ_bind (stmt,
771 params);
772 }
773 if (GNUNET_OK != err)
774 {
775 LOG_SQLITE (plugin,
776 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
777 "sqlite3_bind_XXXX");
778 GNUNET_SQ_reset (plugin->dbh,
779 stmt);
780 return GNUNET_SYSERR;
781 }
782 return get_records_and_call_iterator (plugin,
783 stmt,
784 zone,
785 limit,
786 iter,
787 iter_cls);
788}
789
790
791/**
792 * Look for an existing PKEY delegation record for a given public key.
793 * Returns at most one result to the iterator.
794 *
795 * @param cls closure (internal context for the plugin)
796 * @param zone private key of the zone to look up in, never NULL
797 * @param value_zone public key of the target zone (value), never NULL
798 * @param iter function to call with the result
799 * @param iter_cls closure for @a iter
800 * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
801 */
802static enum GNUNET_GenericReturnValue
803namestore_sqlite_zone_to_name (void *cls,
804 const struct GNUNET_CRYPTO_PrivateKey *zone,
805 const struct
806 GNUNET_CRYPTO_PublicKey *value_zone,
807 GNUNET_NAMESTORE_RecordIterator iter,
808 void *iter_cls)
809{
810 struct Plugin *plugin = cls;
811 GNUNET_assert (GNUNET_OK == database_prepare (plugin));
812 struct GNUNET_SQ_QueryParam params[] = {
813 GNUNET_SQ_query_param_auto_from_type (zone),
814 GNUNET_SQ_query_param_auto_from_type (value_zone),
815 GNUNET_SQ_query_param_end
816 };
817
818 if (GNUNET_OK !=
819 GNUNET_SQ_bind (plugin->zone_to_name,
820 params))
821 {
822 LOG_SQLITE (plugin,
823 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
824 "sqlite3_bind_XXXX");
825 GNUNET_SQ_reset (plugin->dbh,
826 plugin->zone_to_name);
827 return GNUNET_SYSERR;
828 }
829 LOG (GNUNET_ERROR_TYPE_DEBUG,
830 "Performing reverse lookup for `%s'\n",
831 GNUNET_GNSRECORD_z2s (value_zone));
832 return get_records_and_call_iterator (plugin,
833 plugin->zone_to_name,
834 zone,
835 1,
836 iter,
837 iter_cls);
838}
839
840
841static enum GNUNET_GenericReturnValue
842namestore_sqlite_create_tables (void *cls)
843{
844 struct Plugin *plugin = cls;
845 struct GNUNET_SQ_ExecuteStatement es[] = {
846 GNUNET_SQ_make_try_execute ("PRAGMA temp_store=MEMORY"),
847 GNUNET_SQ_make_try_execute ("PRAGMA synchronous=NORMAL"),
848 GNUNET_SQ_make_try_execute ("PRAGMA legacy_file_format=OFF"),
849 GNUNET_SQ_make_try_execute ("PRAGMA auto_vacuum=INCREMENTAL"),
850 GNUNET_SQ_make_try_execute ("PRAGMA encoding=\"UTF-8\""),
851 GNUNET_SQ_make_try_execute ("PRAGMA locking_mode=NORMAL"),
852 GNUNET_SQ_make_try_execute ("PRAGMA journal_mode=WAL"),
853 GNUNET_SQ_make_try_execute ("PRAGMA page_size=4092"),
854 GNUNET_SQ_make_execute ("CREATE TABLE IF NOT EXISTS ns098records ("
855 " uid INTEGER PRIMARY KEY,"
856 " zone_private_key BLOB NOT NULL,"
857 " pkey BLOB,"
858 " rvalue INT8 NOT NULL,"
859 " record_count INT NOT NULL,"
860 " record_data BLOB NOT NULL,"
861 " label TEXT NOT NULL,"
862 " editor_hint TEXT NOT NULL"
863 ")"),
864 GNUNET_SQ_make_try_execute ("CREATE INDEX ir_pkey_reverse "
865 "ON ns098records (zone_private_key,pkey)"),
866 GNUNET_SQ_make_try_execute ("CREATE INDEX ir_pkey_iter "
867 "ON ns098records (zone_private_key,uid)"),
868 GNUNET_SQ_EXECUTE_STATEMENT_END
869 };
870
871 if (GNUNET_OK !=
872 GNUNET_SQ_exec_statements (plugin->dbh,
873 es))
874 {
875 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
876 "Failed to setup database with: `%s'\n",
877 sqlite3_errmsg (plugin->dbh));
878 return GNUNET_SYSERR;
879 }
880 return GNUNET_OK;
881}
882
883
884static enum GNUNET_GenericReturnValue
885namestore_sqlite_drop_tables (void *cls)
886{
887 struct Plugin *plugin = cls;
888 struct GNUNET_SQ_ExecuteStatement es_drop[] = {
889 GNUNET_SQ_make_execute ("DROP TABLE IF EXISTS ns098records"),
890 GNUNET_SQ_EXECUTE_STATEMENT_END
891 };
892
893 if (GNUNET_OK !=
894 GNUNET_SQ_exec_statements (plugin->dbh,
895 es_drop))
896 {
897 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
898 "Failed to drop database with: `%s'\n",
899 sqlite3_errmsg (plugin->dbh));
900 return GNUNET_SYSERR;
901 }
902 return GNUNET_OK;
903}
904
905
906/**
907 * Initialize the database connections and associated
908 * data structures (create tables and indices
909 * as needed as well).
910 *
911 * @param plugin the plugin context (state for this module)
912 * @return #GNUNET_OK on success
913 */
914static enum GNUNET_GenericReturnValue
915database_connect (struct Plugin *plugin)
916{
917 char *sqlite_filename;
918
919 if (GNUNET_OK !=
920 GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
921 "namestore-sqlite",
922 "FILENAME",
923 &sqlite_filename))
924 {
925 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
926 "namestore-sqlite",
927 "FILENAME");
928 return GNUNET_SYSERR;
929 }
930 if (GNUNET_OK !=
931 GNUNET_DISK_file_test (sqlite_filename))
932 {
933 if (GNUNET_OK !=
934 GNUNET_DISK_directory_create_for_file (sqlite_filename))
935 {
936 GNUNET_break (0);
937 GNUNET_free (sqlite_filename);
938 return GNUNET_SYSERR;
939 }
940 }
941
942 /* Open database and precompile statements */
943 if ((NULL == plugin->dbh) &&
944 (SQLITE_OK != sqlite3_open (sqlite_filename,
945 &plugin->dbh)))
946 {
947 LOG (GNUNET_ERROR_TYPE_ERROR,
948 _ ("Unable to initialize SQLite: %s.\n"),
949 sqlite3_errmsg (plugin->dbh));
950 GNUNET_free (sqlite_filename);
951 return GNUNET_SYSERR;
952 }
953 GNUNET_free (sqlite_filename);
954 GNUNET_break (SQLITE_OK ==
955 sqlite3_busy_timeout (plugin->dbh,
956 BUSY_TIMEOUT_MS));
957 if (GNUNET_YES ==
958 GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
959 "namestore-sqlite",
960 "INIT_ON_CONNECT"))
961 {
962 if (GNUNET_OK !=
963 namestore_sqlite_create_tables (plugin))
964 return GNUNET_SYSERR;
965 }
966 return GNUNET_OK;
967}
968
969
970/**
971 * Entry point for the plugin.
972 *
973 * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
974 * @return NULL on error, otherwise the plugin context
975 */
976void *
977libgnunet_plugin_namestore_sqlite_init (void *cls)
978{
979 struct Plugin *plugin;
980 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
981 struct GNUNET_NAMESTORE_PluginFunctions *api;
982
983 plugin = GNUNET_new (struct Plugin);
984 plugin->cfg = cfg;
985 if (GNUNET_OK != database_connect (plugin))
986 {
987 LOG (GNUNET_ERROR_TYPE_ERROR,
988 "Database could not be connected to.\n");
989 GNUNET_free (plugin);
990 return NULL;
991 }
992 api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
993 api->cls = plugin;
994 api->store_records = &namestore_sqlite_store_records;
995 api->iterate_records = &namestore_sqlite_iterate_records;
996 api->zone_to_name = &namestore_sqlite_zone_to_name;
997 api->lookup_records = &namestore_sqlite_lookup_records;
998 api->create_tables = &namestore_sqlite_create_tables;
999 api->drop_tables = &namestore_sqlite_drop_tables;
1000 api->edit_records = &namestore_sqlite_edit_records;
1001 api->clear_editor_hint = &namestore_sqlite_editor_hint_clear;
1002 LOG (GNUNET_ERROR_TYPE_DEBUG,
1003 _ ("SQlite database running\n"));
1004 return api;
1005}
1006
1007
1008/**
1009 * Exit point from the plugin.
1010 *
1011 * @param cls the plugin context (as returned by "init")
1012 * @return always NULL
1013 */
1014void *
1015libgnunet_plugin_namestore_sqlite_done (void *cls)
1016{
1017 struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
1018 struct Plugin *plugin = api->cls;
1019
1020 database_shutdown (plugin);
1021 plugin->cfg = NULL;
1022 GNUNET_free (plugin);
1023 GNUNET_free (api);
1024 LOG (GNUNET_ERROR_TYPE_DEBUG,
1025 "SQlite plugin is finished\n");
1026 return NULL;
1027}
1028
1029
1030/* end of plugin_namestore_sqlite.c */
diff --git a/src/plugin/namestore/test_plugin_namestore.c b/src/plugin/namestore/test_plugin_namestore.c
new file mode 100644
index 000000000..db943ab9a
--- /dev/null
+++ b/src/plugin/namestore/test_plugin_namestore.c
@@ -0,0 +1,217 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2012 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/*
21 * @file namestore/test_plugin_namestore.c
22 * @brief Test for the namestore plugins
23 * @author Christian Grothoff
24 */
25#include "gnunet_util_lib.h"
26#include "gnunet_namestore_plugin.h"
27
28#define TEST_RECORD_TYPE GNUNET_DNSPARSER_TYPE_TXT
29
30static int ok;
31
32/**
33 * Name of plugin under test.
34 */
35static const char *plugin_name;
36
37#ifndef DARWIN // #5582
38/**
39 * Function called when the service shuts down. Unloads our namestore
40 * plugin.
41 *
42 * @param api api to unload
43 */
44static void
45unload_plugin (struct GNUNET_NAMESTORE_PluginFunctions *api)
46{
47 char *libname;
48
49 GNUNET_asprintf (&libname, "libgnunet_plugin_namestore_%s", plugin_name);
50 GNUNET_break (NULL == GNUNET_PLUGIN_unload (libname, api));
51 GNUNET_free (libname);
52}
53#endif
54
55/**
56 * Load the namestore plugin.
57 *
58 * @param cfg configuration to pass
59 * @return NULL on error
60 */
61static struct GNUNET_NAMESTORE_PluginFunctions *
62load_plugin (const struct GNUNET_CONFIGURATION_Handle *cfg)
63{
64 struct GNUNET_NAMESTORE_PluginFunctions *ret;
65 char *libname;
66
67 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
68 "Loading `%s' namestore plugin\n",
69 plugin_name);
70 GNUNET_asprintf (&libname,
71 "libgnunet_plugin_namestore_%s",
72 plugin_name);
73 if (NULL == (ret = GNUNET_PLUGIN_load (libname, (void *) cfg)))
74 {
75 fprintf (stderr,
76 "Failed to load plugin `%s'!\n",
77 plugin_name);
78 GNUNET_free (libname);
79 return NULL;
80 }
81 GNUNET_free (libname);
82 if (GNUNET_OK != ret->drop_tables (ret->cls))
83 {
84 GNUNET_break (0);
85 return NULL;
86 }
87 if (GNUNET_OK != ret->create_tables (ret->cls))
88 {
89 GNUNET_break (0);
90 return NULL;
91 }
92 return ret;
93}
94
95
96static void
97test_record (void *cls,
98 uint64_t seq,
99 const char *editor_hint,
100 const struct GNUNET_CRYPTO_PrivateKey *private_key,
101 const char *label,
102 unsigned int rd_count,
103 const struct GNUNET_GNSRECORD_Data *rd)
104{
105 int *idp = cls;
106 int id = *idp;
107 struct GNUNET_CRYPTO_PrivateKey tzone_private_key;
108 char tname[64];
109 unsigned int trd_count = 1 + (id % 1024);
110
111 GNUNET_snprintf (tname, sizeof(tname), "a%u", (unsigned int) id);
112 GNUNET_assert (trd_count == rd_count);
113 for (unsigned int i = 0; i < trd_count; i++)
114 {
115 GNUNET_assert (rd[i].data_size == id % 10);
116 GNUNET_assert (0 == memcmp ("Hello World", rd[i].data, id % 10));
117 GNUNET_assert (rd[i].record_type == TEST_RECORD_TYPE);
118 GNUNET_assert (rd[i].flags == 0);
119 }
120 memset (&tzone_private_key, (id % 241), sizeof(tzone_private_key));
121 GNUNET_assert (0 == strcmp (label, tname));
122 GNUNET_assert (0 == GNUNET_memcmp (&tzone_private_key, private_key));
123}
124
125
126static void
127get_record (struct GNUNET_NAMESTORE_PluginFunctions *nsp, int id)
128{
129 GNUNET_assert (
130 GNUNET_OK ==
131 nsp->iterate_records (nsp->cls, NULL, 0, 1, &test_record, &id));
132}
133
134
135static void
136put_record (struct GNUNET_NAMESTORE_PluginFunctions *nsp, int id)
137{
138 struct GNUNET_CRYPTO_PrivateKey zone_private_key;
139 char label[64];
140 unsigned int rd_count = 1 + (id % 1024);
141 struct GNUNET_GNSRECORD_Data rd[GNUNET_NZL (rd_count)];
142 struct GNUNET_CRYPTO_EcdsaSignature signature;
143
144 GNUNET_snprintf (label, sizeof(label), "a%u", (unsigned int) id);
145 for (unsigned int i = 0; i < rd_count; i++)
146 {
147 rd[i].data = "Hello World";
148 rd[i].data_size = id % 10;
149 rd[i].expiration_time =
150 GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES).abs_value_us;
151 rd[i].record_type = TEST_RECORD_TYPE;
152 rd[i].flags = 0;
153 }
154 memset (&zone_private_key, (id % 241), sizeof(zone_private_key));
155 memset (&signature, (id % 243), sizeof(signature));
156 GNUNET_assert (
157 GNUNET_OK ==
158 nsp->store_records (nsp->cls, &zone_private_key, label, rd_count, rd));
159}
160
161
162static void
163run (void *cls,
164 char *const *args,
165 const char *cfgfile,
166 const struct GNUNET_CONFIGURATION_Handle *cfg)
167{
168 struct GNUNET_NAMESTORE_PluginFunctions *nsp;
169
170 ok = 0;
171 nsp = load_plugin (cfg);
172 if (NULL == nsp)
173 {
174 fprintf (
175 stderr,
176 "%s",
177 "Failed to initialize namestore. Database likely not setup, skipping test.\n");
178 return;
179 }
180 put_record (nsp, 1);
181 get_record (nsp, 1);
182#ifndef DARWIN // #5582
183 unload_plugin (nsp);
184#endif
185}
186
187
188int
189main (int argc, char *argv[])
190{
191 char cfg_name[PATH_MAX];
192 char *const xargv[] = { "test-plugin-namestore", "-c", cfg_name, NULL };
193 struct GNUNET_GETOPT_CommandLineOption options[] =
194 { GNUNET_GETOPT_OPTION_END };
195
196 GNUNET_log_setup ("test-plugin-namestore", "WARNING", NULL);
197 plugin_name = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
198 GNUNET_snprintf (cfg_name,
199 sizeof(cfg_name),
200 "test_plugin_namestore_%s.conf",
201 plugin_name);
202 GNUNET_DISK_purge_cfg_dir (cfg_name, "GNUNET_TMP");
203 GNUNET_PROGRAM_run ((sizeof(xargv) / sizeof(char *)) - 1,
204 xargv,
205 "test-plugin-namestore",
206 "nohelp",
207 options,
208 &run,
209 NULL);
210 GNUNET_DISK_purge_cfg_dir (cfg_name, "GNUNET_TMP");
211 if (ok != 0)
212 fprintf (stderr, "Missed some testcases: %d\n", ok);
213 return ok;
214}
215
216
217/* end of test_plugin_namestore.c */
diff --git a/src/plugin/namestore/test_plugin_namestore_postgres.conf b/src/plugin/namestore/test_plugin_namestore_postgres.conf
new file mode 100644
index 000000000..140d54623
--- /dev/null
+++ b/src/plugin/namestore/test_plugin_namestore_postgres.conf
@@ -0,0 +1,4 @@
1[namestore-postgres]
2CONFIG = connect_timeout=10 dbname=gnunetcheck
3INIT_ON_CONNECT = YES
4#TEMPORARY_TABLE = YES
diff --git a/src/plugin/namestore/test_plugin_namestore_sqlite.conf b/src/plugin/namestore/test_plugin_namestore_sqlite.conf
new file mode 100644
index 000000000..365198db2
--- /dev/null
+++ b/src/plugin/namestore/test_plugin_namestore_sqlite.conf
@@ -0,0 +1,3 @@
1[namestore-sqlite]
2FILENAME = $GNUNET_TMP/gnunet-test-plugin-namestore-sqlite/sqlite.db
3INIT_ON_CONNECT = YES
diff --git a/src/plugin/namestore/test_plugin_rest_namestore.sh b/src/plugin/namestore/test_plugin_rest_namestore.sh
new file mode 100755
index 000000000..4f117db8b
--- /dev/null
+++ b/src/plugin/namestore/test_plugin_rest_namestore.sh
@@ -0,0 +1,131 @@
1#!/bin/sh
2trap "gnunet-arm -e -c test_gns_lookup.conf" SIGINT
3
4LOCATION=$(which gnunet-config)
5if [ -z $LOCATION ]
6then
7 LOCATION="gnunet-config"
8fi
9$LOCATION --version 1> /dev/null
10if test $? != 0
11then
12 echo "GNUnet command line tools cannot be found, check environmental variables PATH and GNUNET_PREFIX"
13 exit 77
14fi
15
16rm -rf `gnunet-config -c test_namestore_api.conf -f -s paths -o GNUNET_TEST_HOME`
17
18namestore_link="http://localhost:7776/namestore"
19wrong_link="http://localhost:7776/namestoreandmore"
20
21curl_get () {
22 #$1 is link
23 #$2 is grep
24 resp=$(curl -v "$1" 2>&1)
25 cache="$(echo $resp | grep "$2")"
26 #echo $cache
27 if [ "" = "$cache" ]
28 then
29 echo "Error in get response: $resp, expected $2"
30 gnunet-arm -e -c test_namestore_api.conf
31 exit 1
32 fi
33}
34
35curl_post () {
36 #$1 is link
37 #$2 is data
38 #$3 is grep
39 resp=$(curl -v -X "POST" "$1" --data "$2" 2>&1)
40 cache="$(echo $resp | grep "$3")"
41 #echo $cache
42 if [ "" = "$cache" ]
43 then
44 echo "Error in post response: $resp ($2), expected $3"
45 gnunet-arm -e -c test_namestore_api.conf
46 exit 1
47 fi
48}
49
50curl_delete () {
51 #$1 is link
52 #$2 is grep
53 resp=$(curl -v -X "DELETE" "$1" 2>&1)
54 cache="$(echo $resp | grep "$2")"
55 #echo $cache
56 if [ "" = "$cache" ]
57 then
58 echo "Error in delete response: $resp, expected $2"
59 gnunet-arm -e -c test_namestore_api.conf
60 exit 1
61 fi
62}
63
64# curl_put () {
65# #$1 is link
66# #$2 is data
67# #$3 is grep
68# cache="$(curl -v -X "PUT" "$1" --data "$2" 2>&1 | grep "$3")"
69# #echo $cache
70# if [ "" == "$cache" ]
71# then
72# exit 1
73# fi
74# }
75
76#Test subsystem default identity
77
78TEST_ID="test"
79gnunet-arm -s -c test_namestore_api.conf
80#Test GET
81gnunet-identity -C $TEST_ID -c test_namestore_api.conf
82test="$(gnunet-namestore -D -z $TEST_ID -c test_namestore_api.conf)"
83name=$TEST_ID
84public="$(gnunet-identity -d -c test_namestore_api.conf | grep $TEST_ID | awk 'NR==1{print $3}')"
85echo "$name $public"
86gnunet-namestore -z $name -p -a -n "test_entry" -e "1d" -V "000G006WVZ8HQ5YTVFNX09HK0VJVVQ9ZCBYDSCH3ERT04N5ZRBKEB82EP8" -t "PKEY" -c test_namestore_api.conf
87sleep 1
88gnunet-arm -i rest -c test_namestore_api.conf
89sleep 1
90curl_get "${namestore_link}/$name" "HTTP/1.1 200 OK"
91curl_get "${namestore_link}/$public" "error"
92gnunet-namestore -z $name -d -n "test_entry" -c test_namestore_api.conf
93
94#Test POST with NAME
95curl_post "${namestore_link}/$name" '{"data": [{"value":"000G006WVZ8HQ5YTVFNX09HK0VJVVQ9ZCBYDSCH3ERT04N5ZRBKEB82EP8", "record_type":"PKEY", "relative_expiration": 86400000000, "is_relative_expiration": false, "is_supplemental": false, "is_shadow": false, "is_private": false}],"record_name":"test_entry"}' "HTTP/1.1 204 No Content"
96gnunet-namestore -z $name -d -n "test_entry" -c test_namestore_api.conf > /dev/null 2>&1
97
98# invalid values
99curl_post "${namestore_link}/$name" '{"data": [{"value":"HVX38H2CB7WJM0WCPWT9CFX6GASMYJVR65RN75SJSSKAYVYXHMRGxxx", "record_type":"PKEY", "relative_expiration": 86400000000, "is_relative_expiration": false, "is_supplemental": false, "is_shadow": false, "is_private": false}],"record_name":"test_entry"}' "error"
100gnunet-namestore -z $name -d -n "test_entry" -c test_namestore_api.conf > /dev/null 2>&1
101
102
103curl_post "${namestore_link}/$name" '{"data": [{"value":"", "record_type":"PKEY", "relative_expiration": 86400000000,"flag":0,"record_name"}]:"test_entry"}' "error"
104gnunet-namestore -z $name -d -n "test_entry" -c test_namestore_api.conf > /dev/null 2>&1
105
106curl_post "${namestore_link}/$name" '{"data": [{"record_type":"PKEY", "relative_expiration": 86400000000,"flag":0}],"record_name":"test_entry"}' "error"
107gnunet-namestore -z $name -d -n "test_entry" -c test_namestore_api.conf > /dev/null 2>&1
108
109#expirations
110curl_post "${namestore_link}/$name" '{"data": [{"value":"000G006WVZ8HQ5YTVFNX09HK0VJVVQ9ZCBYDSCH3ERT04N5ZRBKEB82EP8", "record_type":"PKEY", "relative_expiration":0, "is_relative_expiration": true, "is_supplemental": false, "is_shadow": false, "is_private": false}],"record_name":"test_entry"}' "HTTP/1.1 204"
111gnunet-namestore -z $name -d -n "test_entry" -c test_namestore_api.conf > /dev/null 2>&1
112
113curl_post "${namestore_link}/$name" '{"data": [{"value":"000G006WVZ8HQ5YTVFNX09HK0VJVVQ9ZCBYDSCH3ERT04N5ZRBKEB82EP8", "record_type":"PKEY", "relative_expiration":864000000000000, "is_relative_expiration": true, "is_supplemental": false, "is_shadow": false, "is_private": false}],"record_name":"test_entry"}' "HTTP/1.1 204"
114gnunet-namestore -z $name -d -n "test_entry" -c test_namestore_api.conf > /dev/null 2>&1
115
116curl_post "${namestore_link}/$name" '{"data": [{"value":"000G006WVZ8HQ5YTVFNX09HK0VJVVQ9ZCBYDSCH3ERT04N5ZRBKEB82EP8", "record_type":"PKEY", "expiration_time_missing":"1d", "is_relative_expiration": false, "is_supplemental": false, "is_shadow": false, "is_private": false}],"record_name":"test_entry"}' "error"
117gnunet-namestore -z $name -d -n "test_entry" -c test_namestore_api.conf > /dev/null 2>&1
118
119#record_name
120curl_post "${namestore_link}/$name" '{"data": [{"value":"000G006WVZ8HQ5YTVFNX09HK0VJVVQ9ZCBYDSCH3ERT04N5ZRBKEB82EP8", "record_type":"PKEY", "relative_expiration":86400000000, "is_relative_expiration": false, "is_supplemental": false, "is_shadow": false, "is_private": false}],"record_name":""}' "error"
121gnunet-namestore -z $name -d -n "test_entry" -c test_namestore_api.conf > /dev/null 2>&1
122curl_post "${namestore_link}/$name" '{"data": [{"value":"000G006WVZ8HQ5YTVFNX09HK0VJVVQ9ZCBYDSCH3ERT04N5ZRBKEB82EP8", "record_type":"PKEY", "relative_expiration":"1d", "is_relative_expiration": false, "is_supplemental": false, "is_shadow": false, "is_private": false}],"record_name_missing":"test_entry"}' "error"
123gnunet-namestore -z $name -d -n "test_entry" -c test_namestore_api.conf > /dev/null 2>&1
124
125#Test DELETE
126gnunet-namestore -z $name -p -a -n "test_entry" -e "1d" -V "000G006WVZ8HQ5YTVFNX09HK0VJVVQ9ZCBYDSCH3ERT04N5ZRBKEB82EP8" -t "PKEY" -c test_namestore_api.conf
127curl_delete "${namestore_link}/$name/test_entry" "HTTP/1.1 204"
128
129gnunet-arm -e -c test_namestore_api.conf
130exit 0;
131
diff --git a/src/plugin/peerstore/Makefile.am b/src/plugin/peerstore/Makefile.am
new file mode 100644
index 000000000..7202aef84
--- /dev/null
+++ b/src/plugin/peerstore/Makefile.am
@@ -0,0 +1,46 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4plugindir = $(libdir)/gnunet
5
6pkgcfgdir= $(pkgdatadir)/config.d/
7
8libexecdir= $(pkglibdir)/libexec/
9
10if USE_COVERAGE
11 AM_CFLAGS = -fprofile-arcs -ftest-coverage
12endif
13
14if HAVE_SQLITE
15SQLITE_PLUGIN = libgnunet_plugin_peerstore_sqlite.la
16SQLITE_TESTS = test_plugin_peerstore_sqlite
17libgnunet_plugin_peerstore_sqlite_la_SOURCES = \
18 plugin_peerstore_sqlite.c
19libgnunet_plugin_peerstore_sqlite_la_LIBADD = \
20 $(top_builddir)/src/lib/sq/libgnunetsq.la \
21 $(top_builddir)/src/lib/util/libgnunetutil.la \
22 $(XLIBS) -lsqlite3 \
23 $(LTLIBINTL)
24libgnunet_plugin_peerstore_sqlite_la_LDFLAGS = \
25 $(GN_PLUGIN_LDFLAGS)
26endif
27
28plugin_LTLIBRARIES = \
29 $(SQLITE_PLUGIN)
30
31test_plugin_peerstore_sqlite_SOURCES = \
32 test_plugin_peerstore.c
33test_plugin_peerstore_sqlite_LDADD = \
34 $(top_builddir)/src/service/testing/libgnunettesting.la \
35 $(top_builddir)/src/lib/util/libgnunetutil.la
36
37check_PROGRAMS = \
38 $(SQLITE_TESTS)
39
40EXTRA_DIST = \
41 test_plugin_peerstore_sqlite.conf
42
43if ENABLE_TEST_RUN
44AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
45TESTS = $(check_PROGRAMS)
46endif
diff --git a/src/plugin/peerstore/meson.build b/src/plugin/peerstore/meson.build
new file mode 100644
index 000000000..1d22ca798
--- /dev/null
+++ b/src/plugin/peerstore/meson.build
@@ -0,0 +1,9 @@
1shared_module('gnunet_plugin_peerstore_sqlite',
2 ['plugin_peerstore_sqlite.c'],
3 dependencies: [libgnunetutil_dep,
4 libgnunetsq_dep,
5 sqlite_dep],
6 include_directories: [incdir,
7 configuration_inc],
8 install: true,
9 install_dir: get_option('libdir')/'gnunet')
diff --git a/src/plugin/peerstore/plugin_peerstore_sqlite.c b/src/plugin/peerstore/plugin_peerstore_sqlite.c
new file mode 100644
index 000000000..c7132bd85
--- /dev/null
+++ b/src/plugin/peerstore/plugin_peerstore_sqlite.c
@@ -0,0 +1,743 @@
1/*
2 * This file is part of GNUnet
3 * Copyright (C) 2013, 2017 GNUnet e.V.
4 *
5 * GNUnet is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License,
8 * or (at your option) any later version.
9 *
10 * GNUnet is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file peerstore/plugin_peerstore_sqlite.c
23 * @brief sqlite-based peerstore backend
24 * @author Omar Tarabai
25 * @author Christian Grothoff
26 */
27
28#include "platform.h"
29#include "gnunet_peerstore_plugin.h"
30#include "gnunet_peerstore_service.h"
31#include "gnunet_sq_lib.h"
32#include "../../service/peerstore/peerstore.h"
33#include <sqlite3.h>
34
35/**
36 * After how many ms "busy" should a DB operation fail for good? A
37 * low value makes sure that we are more responsive to requests
38 * (especially PUTs). A high value guarantees a higher success rate
39 * (SELECTs in iterate can take several seconds despite LIMIT=1).
40 *
41 * The default value of 1s should ensure that users do not experience
42 * huge latencies while at the same time allowing operations to
43 * succeed with reasonable probability.
44 */
45#define BUSY_TIMEOUT_MS 1000
46
47/**
48 * Log an error message at log-level 'level' that indicates
49 * a failure of the command 'cmd' on file 'filename'
50 * with the message given by strerror(errno).
51 */
52#define LOG_SQLITE(db, level, cmd) do { GNUNET_log_from (level, \
53 "peerstore-sqlite", _ ( \
54 "`%s' failed at %s:%d with error: %s\n"), \
55 cmd, \
56 __FILE__, __LINE__, \
57 sqlite3_errmsg ( \
58 db->dbh)); \
59} while (0)
60
61#define LOG(kind, ...) GNUNET_log_from (kind, "peerstore-sqlite", __VA_ARGS__)
62
63/**
64 * Context for all functions in this plugin.
65 */
66struct Plugin
67{
68 /**
69 * Configuration handle
70 */
71 const struct GNUNET_CONFIGURATION_Handle *cfg;
72
73 /**
74 * Database filename.
75 */
76 char *fn;
77
78 /**
79 * Native SQLite database handle.
80 */
81 sqlite3 *dbh;
82
83 /**
84 * Precompiled SQL for inserting into peerstoredata
85 */
86 sqlite3_stmt *insert_peerstoredata;
87
88 /**
89 * Precompiled SQL for selecting from peerstoredata
90 */
91 sqlite3_stmt *select_peerstoredata;
92
93 /**
94 * Precompiled SQL for selecting from peerstoredata
95 */
96 sqlite3_stmt *select_peerstoredata_by_pid;
97
98 /**
99 * Precompiled SQL for selecting from peerstoredata
100 */
101 sqlite3_stmt *select_peerstoredata_by_key;
102
103 /**
104 * Precompiled SQL for selecting from peerstoredata
105 */
106 sqlite3_stmt *select_peerstoredata_by_all;
107
108 /**
109 * Precompiled SQL for deleting expired
110 * records from peerstoredata
111 */
112 sqlite3_stmt *expire_peerstoredata;
113
114 /**
115 * Precompiled SQL for deleting records
116 * with given key
117 */
118 sqlite3_stmt *delete_peerstoredata;
119};
120
121
122/**
123 * Delete records with the given key
124 *
125 * @param cls closure (internal context for the plugin)
126 * @param sub_system name of sub system
127 * @param peer Peer identity (can be NULL)
128 * @param key entry key string (can be NULL)
129 * @return number of deleted records, #GNUNE_SYSERR on error
130 */
131static int
132peerstore_sqlite_delete_records (void *cls,
133 const char *sub_system,
134 const struct GNUNET_PeerIdentity *peer,
135 const char *key)
136{
137 struct Plugin *plugin = cls;
138 sqlite3_stmt *stmt = plugin->delete_peerstoredata;
139 struct GNUNET_SQ_QueryParam params[] = {
140 GNUNET_SQ_query_param_string (sub_system),
141 GNUNET_SQ_query_param_auto_from_type (peer),
142 GNUNET_SQ_query_param_string (key),
143 GNUNET_SQ_query_param_end
144 };
145 int ret;
146
147 if (GNUNET_OK !=
148 GNUNET_SQ_bind (stmt,
149 params))
150 {
151 LOG_SQLITE (plugin,
152 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
153 "sqlite3_bind");
154 GNUNET_SQ_reset (plugin->dbh,
155 stmt);
156 return GNUNET_SYSERR;
157 }
158 if (SQLITE_DONE !=
159 sqlite3_step (stmt))
160 {
161 LOG_SQLITE (plugin,
162 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
163 "sqlite3_step");
164 ret = GNUNET_SYSERR;
165 }
166 else
167 {
168 ret = sqlite3_changes (plugin->dbh);
169 }
170 GNUNET_SQ_reset (plugin->dbh,
171 stmt);
172 return ret;
173}
174
175
176/**
177 * Delete expired records (expiry < now)
178 *
179 * @param cls closure (internal context for the plugin)
180 * @param now time to use as reference
181 * @param cont continuation called with the number of records expired
182 * @param cont_cls continuation closure
183 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and cont is not
184 * called
185 */
186static int
187peerstore_sqlite_expire_records (void *cls, struct GNUNET_TIME_Absolute now,
188 GNUNET_PEERSTORE_Continuation cont,
189 void *cont_cls)
190{
191 struct Plugin *plugin = cls;
192 sqlite3_stmt *stmt = plugin->expire_peerstoredata;
193 struct GNUNET_SQ_QueryParam params[] = {
194 GNUNET_SQ_query_param_absolute_time (&now),
195 GNUNET_SQ_query_param_end
196 };
197
198 if (GNUNET_OK !=
199 GNUNET_SQ_bind (stmt,
200 params))
201 {
202 LOG_SQLITE (plugin,
203 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
204 "sqlite3_bind");
205 GNUNET_SQ_reset (plugin->dbh,
206 stmt);
207 return GNUNET_SYSERR;
208 }
209 if (SQLITE_DONE != sqlite3_step (stmt))
210 {
211 LOG_SQLITE (plugin,
212 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
213 "sqlite3_step");
214 GNUNET_SQ_reset (plugin->dbh,
215 stmt);
216 return GNUNET_SYSERR;
217 }
218 if (NULL != cont)
219 cont (cont_cls,
220 sqlite3_changes (plugin->dbh));
221 GNUNET_SQ_reset (plugin->dbh,
222 stmt);
223 return GNUNET_OK;
224}
225
226
227/**
228 * Iterate over the records given an optional peer id
229 * and/or key.
230 *
231 * @param cls closure (internal context for the plugin)
232 * @param sub_system name of sub system
233 * @param peer Peer identity (can be NULL)
234 * @param key entry key string (can be NULL)
235 * @param iter function to call asynchronously with the results, terminated
236 * by a NULL result
237 * @param iter_cls closure for @a iter
238 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and iter is not
239 * called
240 */
241static int
242peerstore_sqlite_iterate_records (void *cls,
243 const char *sub_system,
244 const struct GNUNET_PeerIdentity *peer,
245 const char *key,
246 uint64_t serial,
247 uint64_t limit,
248 GNUNET_PEERSTORE_PluginProcessor iter,
249 void *iter_cls)
250{
251 struct Plugin *plugin = cls;
252 sqlite3_stmt *stmt;
253 int err = 0;
254 int sret;
255 int ret;
256 uint64_t seq;
257 struct GNUNET_PEERSTORE_Record rec;
258
259 LOG (GNUNET_ERROR_TYPE_DEBUG,
260 "Executing iterate request on sqlite db.\n");
261 if (NULL == peer)
262 {
263 if (NULL == key)
264 {
265 struct GNUNET_SQ_QueryParam params[] = {
266 GNUNET_SQ_query_param_string (sub_system),
267 GNUNET_SQ_query_param_uint64 (&serial),
268 GNUNET_SQ_query_param_uint64 (&limit),
269 GNUNET_SQ_query_param_end
270 };
271
272 stmt = plugin->select_peerstoredata;
273 err = GNUNET_SQ_bind (stmt,
274 params);
275 }
276 else
277 {
278 struct GNUNET_SQ_QueryParam params[] = {
279 GNUNET_SQ_query_param_string (sub_system),
280 GNUNET_SQ_query_param_string (key),
281 GNUNET_SQ_query_param_uint64 (&serial),
282 GNUNET_SQ_query_param_uint64 (&limit),
283 GNUNET_SQ_query_param_end
284 };
285
286 stmt = plugin->select_peerstoredata_by_key;
287 err = GNUNET_SQ_bind (stmt,
288 params);
289 }
290 }
291 else
292 {
293 if (NULL == key)
294 {
295 struct GNUNET_SQ_QueryParam params[] = {
296 GNUNET_SQ_query_param_string (sub_system),
297 GNUNET_SQ_query_param_auto_from_type (peer),
298 GNUNET_SQ_query_param_uint64 (&serial),
299 GNUNET_SQ_query_param_uint64 (&limit),
300 GNUNET_SQ_query_param_end
301 };
302
303 stmt = plugin->select_peerstoredata_by_pid;
304 err = GNUNET_SQ_bind (stmt,
305 params);
306 }
307 else
308 {
309 struct GNUNET_SQ_QueryParam params[] = {
310 GNUNET_SQ_query_param_string (sub_system),
311 GNUNET_SQ_query_param_auto_from_type (peer),
312 GNUNET_SQ_query_param_string (key),
313 GNUNET_SQ_query_param_uint64 (&serial),
314 GNUNET_SQ_query_param_uint64 (&limit),
315 GNUNET_SQ_query_param_end
316 };
317
318 stmt = plugin->select_peerstoredata_by_all;
319 err = GNUNET_SQ_bind (stmt,
320 params);
321 }
322 }
323
324 if (GNUNET_OK != err)
325 {
326 LOG_SQLITE (plugin,
327 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
328 "sqlite3_bind_XXXX");
329 GNUNET_SQ_reset (plugin->dbh,
330 stmt);
331 return GNUNET_SYSERR;
332 }
333
334 err = 0;
335 ret = GNUNET_OK;
336 for (uint64_t i = 0; i < limit; i++)
337 {
338 sret = sqlite3_step (stmt);
339 if (SQLITE_DONE == sret)
340 {
341 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
342 "Iteration done (no results)\n");
343 ret = GNUNET_NO;
344 break;
345 }
346 if (SQLITE_ROW != sret)
347 {
348 LOG_SQLITE (plugin,
349 GNUNET_ERROR_TYPE_ERROR,
350 "sqlite_step");
351 ret = GNUNET_SYSERR;
352 break;
353 }
354 {
355 LOG (GNUNET_ERROR_TYPE_DEBUG,
356 "Returning a matched record.\n");
357 struct GNUNET_SQ_ResultSpec rs[] = {
358 GNUNET_SQ_result_spec_uint64 (&seq),
359 GNUNET_SQ_result_spec_string (&rec.sub_system),
360 GNUNET_SQ_result_spec_auto_from_type (&rec.peer),
361 GNUNET_SQ_result_spec_string (&rec.key),
362 GNUNET_SQ_result_spec_variable_size (&rec.value, &rec.value_size),
363 GNUNET_SQ_result_spec_absolute_time (&rec.expiry),
364 GNUNET_SQ_result_spec_end
365 };
366
367 if (GNUNET_OK !=
368 GNUNET_SQ_extract_result (stmt,
369 rs))
370 {
371 GNUNET_break (0);
372 break;
373 }
374 if (NULL != iter)
375 iter (iter_cls,
376 seq,
377 &rec,
378 NULL);
379 GNUNET_SQ_cleanup_result (rs);
380 }
381 }
382 GNUNET_SQ_reset (plugin->dbh,
383 stmt);
384 return ret;
385}
386
387
388/**
389 * Store a record in the peerstore.
390 * Key is the combination of sub system and peer identity.
391 * One key can store multiple values.
392 *
393 * @param cls closure (internal context for the plugin)
394 * @param sub_system name of the GNUnet sub system responsible
395 * @param peer peer identity
396 * @param key record key string
397 * @param value value to be stored
398 * @param size size of value to be stored
399 * @param expiry absolute time after which the record is (possibly) deleted
400 * @param options options related to the store operation
401 * @param cont continuation called when record is stored
402 * @param cont_cls continuation closure
403 * @return #GNUNET_OK on success, else #GNUNET_SYSERR and cont is not called
404 */
405static int
406peerstore_sqlite_store_record (void *cls,
407 const char *sub_system,
408 const struct GNUNET_PeerIdentity *peer,
409 const char *key,
410 const void *value,
411 size_t size,
412 struct GNUNET_TIME_Absolute expiry,
413 enum GNUNET_PEERSTORE_StoreOption options,
414 GNUNET_PEERSTORE_Continuation cont,
415 void *cont_cls)
416{
417 struct Plugin *plugin = cls;
418 sqlite3_stmt *stmt;
419 struct GNUNET_SQ_QueryParam params[] = {
420 GNUNET_SQ_query_param_string (sub_system),
421 GNUNET_SQ_query_param_auto_from_type (peer),
422 GNUNET_SQ_query_param_string (key),
423 GNUNET_SQ_query_param_fixed_size (value, size),
424 GNUNET_SQ_query_param_absolute_time (&expiry),
425 GNUNET_SQ_query_param_end
426 };
427
428 if (GNUNET_PEERSTORE_STOREOPTION_REPLACE == options)
429 {
430 peerstore_sqlite_delete_records (cls,
431 sub_system,
432 peer,
433 key);
434 }
435
436 stmt = plugin->insert_peerstoredata;
437 if (GNUNET_OK !=
438 GNUNET_SQ_bind (stmt,
439 params))
440 {
441 LOG_SQLITE (plugin,
442 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
443 "sqlite3_bind");
444 GNUNET_assert (0);
445 }
446 if (SQLITE_DONE != sqlite3_step (stmt))
447 {
448 LOG_SQLITE (plugin,
449 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
450 "sqlite3_step");
451 }
452 GNUNET_SQ_reset (plugin->dbh,
453 stmt);
454 if (NULL != cont)
455 cont (cont_cls,
456 GNUNET_OK);
457 return GNUNET_OK;
458}
459
460
461/**
462 * @brief Prepare a SQL statement
463 *
464 * @param dbh handle to the database
465 * @param sql SQL statement, UTF-8 encoded
466 * @return 0 on success
467 */
468static int
469sql_exec (sqlite3 *dbh,
470 const char *sql)
471{
472 int result;
473
474 result = sqlite3_exec (dbh,
475 sql,
476 NULL,
477 NULL,
478 NULL);
479 LOG (GNUNET_ERROR_TYPE_DEBUG,
480 "Executed `%s' / %d\n",
481 sql,
482 result);
483 if (SQLITE_OK != result)
484 LOG (GNUNET_ERROR_TYPE_ERROR,
485 _ ("Error executing SQL query: %s\n %s\n"),
486 sqlite3_errmsg (dbh),
487 sql);
488 return result;
489}
490
491
492/**
493 * @brief Prepare a SQL statement
494 *
495 * @param dbh handle to the database
496 * @param sql SQL statement, UTF-8 encoded
497 * @param stmt set to the prepared statement
498 * @return 0 on success
499 */
500static int
501sql_prepare (sqlite3 *dbh,
502 const char *sql,
503 sqlite3_stmt **stmt)
504{
505 char *tail;
506 int result;
507
508 result = sqlite3_prepare_v2 (dbh,
509 sql,
510 strlen (sql),
511 stmt,
512 (const char **) &tail);
513 LOG (GNUNET_ERROR_TYPE_DEBUG,
514 "Prepared `%s' / %p: %d\n",
515 sql,
516 *stmt,
517 result);
518 if (SQLITE_OK != result)
519 LOG (GNUNET_ERROR_TYPE_ERROR,
520 _ ("Error preparing SQL query: %s\n %s\n"),
521 sqlite3_errmsg (dbh),
522 sql);
523 return result;
524}
525
526
527/**
528 * Initialize the database connections and associated
529 * data structures (create tables and indices
530 * as needed as well).
531 *
532 * @param plugin the plugin context (state for this module)
533 * @return #GNUNET_OK on success
534 */
535static int
536database_setup (struct Plugin *plugin)
537{
538 char *filename;
539
540 if (GNUNET_OK !=
541 GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
542 "peerstore-sqlite",
543 "FILENAME",
544 &filename))
545 {
546 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
547 "peerstore-sqlite",
548 "FILENAME");
549 return GNUNET_SYSERR;
550 }
551 if (GNUNET_OK != GNUNET_DISK_file_test (filename))
552 {
553 if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (filename))
554 {
555 GNUNET_break (0);
556 GNUNET_free (filename);
557 return GNUNET_SYSERR;
558 }
559 }
560 /* filename should be UTF-8-encoded. If it isn't, it's a bug */
561 plugin->fn = filename;
562 /* Open database and precompile statements */
563 if (SQLITE_OK != sqlite3_open (plugin->fn,
564 &plugin->dbh))
565 {
566 LOG (GNUNET_ERROR_TYPE_ERROR,
567 _ ("Unable to initialize SQLite: %s.\n"),
568 sqlite3_errmsg (plugin->dbh));
569 return GNUNET_SYSERR;
570 }
571 sql_exec (plugin->dbh,
572 "PRAGMA temp_store=MEMORY");
573 sql_exec (plugin->dbh,
574 "PRAGMA synchronous=OFF");
575 sql_exec (plugin->dbh,
576 "PRAGMA legacy_file_format=OFF");
577 sql_exec (plugin->dbh,
578 "PRAGMA auto_vacuum=INCREMENTAL");
579 sql_exec (plugin->dbh,
580 "PRAGMA encoding=\"UTF-8\"");
581 sql_exec (plugin->dbh,
582 "PRAGMA page_size=4096");
583 sqlite3_busy_timeout (plugin->dbh,
584 BUSY_TIMEOUT_MS);
585 /* Create tables */
586 sql_exec (plugin->dbh,
587 "CREATE TABLE IF NOT EXISTS peerstoredata (\n"
588 " uid INTEGER PRIMARY KEY,"
589 " sub_system TEXT NOT NULL,\n"
590 " peer_id BLOB NOT NULL,\n"
591 " key TEXT NOT NULL,\n"
592 " value BLOB NULL,\n"
593 " expiry INT8 NOT NULL" ");");
594 /* Create Indices */
595 if (SQLITE_OK !=
596 sqlite3_exec (plugin->dbh,
597 "CREATE INDEX IF NOT EXISTS peerstoredata_key_index ON peerstoredata (sub_system, peer_id, key, uid)",
598 NULL,
599 NULL,
600 NULL))
601 {
602 LOG (GNUNET_ERROR_TYPE_ERROR,
603 _ ("Unable to create indices: %s.\n"),
604 sqlite3_errmsg (plugin->dbh));
605 return GNUNET_SYSERR;
606 }
607 /* Prepare statements */
608
609 sql_prepare (plugin->dbh,
610 "INSERT INTO peerstoredata (sub_system, peer_id, key, value, expiry)"
611 " VALUES (?,?,?,?,?);",
612 &plugin->insert_peerstoredata);
613 sql_prepare (plugin->dbh,
614 "SELECT uid,sub_system,peer_id,key,value,expiry FROM peerstoredata"
615 " WHERE sub_system = ?"
616 " AND uid > ?"
617 " ORDER BY uid ASC"
618 " LIMIT ?",
619 &plugin->select_peerstoredata);
620 sql_prepare (plugin->dbh,
621 "SELECT uid,sub_system,peer_id,key,value,expiry FROM peerstoredata"
622 " WHERE sub_system = ?"
623 " AND peer_id = ?"
624 " AND uid > ?"
625 " ORDER BY uid ASC"
626 " LIMIT ?",
627 &plugin->select_peerstoredata_by_pid);
628 sql_prepare (plugin->dbh,
629 "SELECT uid,sub_system,peer_id,key,value,expiry FROM peerstoredata"
630 " WHERE sub_system = ?"
631 " AND key = ?"
632 " AND uid > ?"
633 " ORDER BY uid ASC"
634 " LIMIT ?",
635 &plugin->select_peerstoredata_by_key);
636 sql_prepare (plugin->dbh,
637 "SELECT uid,sub_system,peer_id,key,value,expiry FROM peerstoredata"
638 " WHERE sub_system = ?"
639 " AND peer_id = ?"
640 " AND key = ?"
641 " AND uid > ?"
642 " ORDER BY uid ASC"
643 " LIMIT ?",
644 &plugin->select_peerstoredata_by_all);
645 sql_prepare (plugin->dbh,
646 "DELETE FROM peerstoredata"
647 " WHERE expiry < ?",
648 &plugin->expire_peerstoredata);
649 sql_prepare (plugin->dbh,
650 "DELETE FROM peerstoredata"
651 " WHERE sub_system = ?"
652 " AND peer_id = ?" " AND key = ?",
653 &plugin->delete_peerstoredata);
654 return GNUNET_OK;
655}
656
657
658/**
659 * Shutdown database connection and associate data
660 * structures.
661 * @param plugin the plugin context (state for this module)
662 */
663static void
664database_shutdown (struct Plugin *plugin)
665{
666 int result;
667 sqlite3_stmt *stmt;
668
669 while (NULL != (stmt = sqlite3_next_stmt (plugin->dbh,
670 NULL)))
671 {
672 result = sqlite3_finalize (stmt);
673 if (SQLITE_OK != result)
674 LOG (GNUNET_ERROR_TYPE_WARNING,
675 "Failed to close statement %p: %d\n",
676 stmt,
677 result);
678 }
679 if (SQLITE_OK != sqlite3_close (plugin->dbh))
680 LOG_SQLITE (plugin,
681 GNUNET_ERROR_TYPE_ERROR,
682 "sqlite3_close");
683 GNUNET_free (plugin->fn);
684}
685
686
687/**
688 * Entry point for the plugin.
689 *
690 * @param cls The struct GNUNET_CONFIGURATION_Handle.
691 * @return NULL on error, otherwise the plugin context
692 */
693void *
694libgnunet_plugin_peerstore_sqlite_init (void *cls)
695{
696 static struct Plugin plugin;
697 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
698 struct GNUNET_PEERSTORE_PluginFunctions *api;
699
700 if (NULL != plugin.cfg)
701 return NULL; /* can only initialize once! */
702 memset (&plugin,
703 0,
704 sizeof(struct Plugin));
705 plugin.cfg = cfg;
706 if (GNUNET_OK != database_setup (&plugin))
707 {
708 database_shutdown (&plugin);
709 return NULL;
710 }
711 api = GNUNET_new (struct GNUNET_PEERSTORE_PluginFunctions);
712 api->cls = &plugin;
713 api->store_record = &peerstore_sqlite_store_record;
714 api->iterate_records = &peerstore_sqlite_iterate_records;
715 api->expire_records = &peerstore_sqlite_expire_records;
716 LOG (GNUNET_ERROR_TYPE_DEBUG,
717 "Sqlite plugin is running\n");
718 return api;
719}
720
721
722/**
723 * Exit point from the plugin.
724 *
725 * @param cls The plugin context (as returned by "init")
726 * @return Always NULL
727 */
728void *
729libgnunet_plugin_peerstore_sqlite_done (void *cls)
730{
731 struct GNUNET_PEERSTORE_PluginFunctions *api = cls;
732 struct Plugin *plugin = api->cls;
733
734 database_shutdown (plugin);
735 plugin->cfg = NULL;
736 GNUNET_free (api);
737 LOG (GNUNET_ERROR_TYPE_DEBUG,
738 "Sqlite plugin is finished\n");
739 return NULL;
740}
741
742
743/* end of plugin_peerstore_sqlite.c */
diff --git a/src/plugin/peerstore/test_plugin_peerstore.c b/src/plugin/peerstore/test_plugin_peerstore.c
new file mode 100644
index 000000000..1377845aa
--- /dev/null
+++ b/src/plugin/peerstore/test_plugin_peerstore.c
@@ -0,0 +1,226 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2015 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/*
21 * @file namestore/test_plugin_namestore.c
22 * @brief Test for the namestore plugins
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_util_lib.h"
27#include "gnunet_peerstore_plugin.h"
28#include "gnunet_testing_lib.h"
29
30
31static int ok;
32
33/**
34 * Name of plugin under test.
35 */
36static const char *plugin_name;
37
38
39static struct GNUNET_PEERSTORE_PluginFunctions *psp;
40
41static struct GNUNET_PeerIdentity p1;
42
43
44/**
45 * Function called when the service shuts down. Unloads our namestore
46 * plugin.
47 *
48 * @param api api to unload
49 */
50static void
51unload_plugin (struct GNUNET_PEERSTORE_PluginFunctions *api)
52{
53 char *libname;
54
55 GNUNET_asprintf (&libname,
56 "libgnunet_plugin_peer_%s",
57 plugin_name);
58 GNUNET_break (NULL ==
59 GNUNET_PLUGIN_unload (libname,
60 api));
61 GNUNET_free (libname);
62}
63
64
65/**
66 * Load the namestore plugin.
67 *
68 * @param cfg configuration to pass
69 * @return NULL on error
70 */
71static struct GNUNET_PEERSTORE_PluginFunctions *
72load_plugin (const struct GNUNET_CONFIGURATION_Handle *cfg)
73{
74 struct GNUNET_PEERSTORE_PluginFunctions *ret;
75 char *libname;
76
77 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
78 _ ("Loading `%s' peer plugin\n"),
79 plugin_name);
80 GNUNET_asprintf (&libname,
81 "libgnunet_plugin_peerstore_%s",
82 plugin_name);
83 if (NULL == (ret = GNUNET_PLUGIN_load (libname,
84 (void *) cfg)))
85 {
86 fprintf (stderr,
87 "Failed to load plugin `%s'!\n",
88 plugin_name);
89 GNUNET_free (libname);
90 return NULL;
91 }
92 GNUNET_free (libname);
93 return ret;
94}
95
96
97static void
98test_record (void *cls,
99 uint64_t seq,
100 const struct GNUNET_PEERSTORE_Record *record,
101 const char *error)
102{
103 const struct GNUNET_PeerIdentity *id = cls;
104 const char*testval = "test_val";
105
106 if (NULL == record)
107 {
108 unload_plugin (psp);
109 return;
110 }
111 GNUNET_assert (0 == memcmp (&record->peer,
112 id,
113 sizeof(struct GNUNET_PeerIdentity)));
114 GNUNET_assert (0 == strcmp ("subsys",
115 record->sub_system));
116 GNUNET_assert (0 == strcmp ("key",
117 record->key));
118 GNUNET_assert (0 == memcmp (testval,
119 record->value,
120 strlen (testval)));
121 ok = 0;
122}
123
124
125static void
126get_record (struct GNUNET_PEERSTORE_PluginFunctions *psp,
127 const struct GNUNET_PeerIdentity *identity)
128{
129 GNUNET_assert (GNUNET_OK ==
130 psp->iterate_records (psp->cls,
131 "subsys",
132 identity,
133 "key",
134 0, 1,
135 &test_record,
136 (void *) identity));
137}
138
139
140static void
141store_cont (void *cls,
142 int status)
143{
144 GNUNET_assert (GNUNET_OK == status);
145 get_record (psp,
146 &p1);
147}
148
149
150static void
151put_record (struct GNUNET_PEERSTORE_PluginFunctions *psp,
152 const struct GNUNET_PeerIdentity *identity)
153{
154 GNUNET_assert (GNUNET_OK ==
155 psp->store_record (psp->cls,
156 "subsys",
157 identity,
158 "key",
159 "test_value",
160 strlen ("test_value"),
161 GNUNET_TIME_absolute_get (),
162 GNUNET_PEERSTORE_STOREOPTION_REPLACE,
163 &store_cont,
164 NULL));
165}
166
167
168static void
169run (void *cls,
170 char *const *args,
171 const char *cfgfile,
172 const struct GNUNET_CONFIGURATION_Handle *cfg)
173{
174 ok = 1;
175 psp = load_plugin (cfg);
176 if (NULL == psp)
177 {
178 fprintf (stderr,
179 "%s",
180 "Failed to initialize peerstore. Database likely not setup, skipping test.\n");
181 return;
182 }
183 memset (&p1, 1, sizeof(p1));
184 put_record (psp,
185 &p1);
186}
187
188
189int
190main (int argc, char *argv[])
191{
192 char cfg_name[PATH_MAX];
193 char *const xargv[] = {
194 "test-plugin-peerstore",
195 "-c",
196 cfg_name,
197 NULL
198 };
199 struct GNUNET_GETOPT_CommandLineOption options[] = {
200 GNUNET_GETOPT_OPTION_END
201 };
202
203 GNUNET_log_setup ("test-plugin-peerstore",
204 "WARNING",
205 NULL);
206 plugin_name = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
207 GNUNET_snprintf (cfg_name,
208 sizeof(cfg_name),
209 "test_plugin_peerstore_%s.conf",
210 plugin_name);
211 GNUNET_PROGRAM_run ((sizeof(xargv) / sizeof(char *)) - 1,
212 xargv,
213 "test-plugin-peerstore",
214 "nohelp",
215 options,
216 &run,
217 NULL);
218 if (ok != 0)
219 fprintf (stderr,
220 "Missed some testcases: %d\n",
221 ok);
222 return ok;
223}
224
225
226/* end of test_plugin_peerstore.c */
diff --git a/src/plugin/peerstore/test_plugin_peerstore_sqlite.conf b/src/plugin/peerstore/test_plugin_peerstore_sqlite.conf
new file mode 100644
index 000000000..154491ff6
--- /dev/null
+++ b/src/plugin/peerstore/test_plugin_peerstore_sqlite.conf
@@ -0,0 +1,2 @@
1[peerstore-sqlite]
2FILENAME = $GNUNET_TMP/gnunet-test-plugin-peerstore-sqlite/sqlite-ng.db
diff --git a/src/plugin/reclaim/Makefile.am b/src/plugin/reclaim/Makefile.am
new file mode 100644
index 000000000..e8ac2f94d
--- /dev/null
+++ b/src/plugin/reclaim/Makefile.am
@@ -0,0 +1,70 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4 plugindir = $(libdir)/gnunet
5
6if USE_COVERAGE
7 AM_CFLAGS = --coverage -O0
8 XLIB = -lgcov
9endif
10
11
12CREDENTIAL_PLUGIN = \
13 libgnunet_plugin_reclaim_credential_jwt.la
14
15if HAVE_PABC
16 CREDENTIAL_PLUGIN += libgnunet_plugin_reclaim_credential_pabc.la
17endif
18
19plugin_LTLIBRARIES = \
20 libgnunet_plugin_gnsrecord_reclaim.la \
21 libgnunet_plugin_reclaim_attribute_basic.la \
22 $(CREDENTIAL_PLUGIN)
23
24pkgcfgdir= $(pkgdatadir)/config.d/
25
26libexecdir= $(pkglibdir)/libexec/
27
28libgnunet_plugin_gnsrecord_reclaim_la_SOURCES = \
29 plugin_gnsrecord_reclaim.c
30libgnunet_plugin_gnsrecord_reclaim_la_LIBADD = \
31 $(top_builddir)/src/lib/util/libgnunetutil.la \
32 $(LTLIBINTL)
33libgnunet_plugin_gnsrecord_reclaim_la_LDFLAGS = \
34 $(GN_PLUGIN_LDFLAGS)
35
36
37libgnunet_plugin_reclaim_attribute_basic_la_SOURCES = \
38 plugin_reclaim_attribute_basic.c
39libgnunet_plugin_reclaim_attribute_basic_la_LIBADD = \
40 $(top_builddir)/src/lib/util/libgnunetutil.la \
41 $(LTLIBINTL)
42libgnunet_plugin_reclaim_attribute_basic_la_LDFLAGS = \
43 $(GN_PLUGIN_LDFLAGS)
44
45if HAVE_PABC
46libgnunet_plugin_reclaim_credential_pabc_la_SOURCES = \
47 plugin_reclaim_credential_pabc.c \
48 pabc_helper.c \
49 pabc_helper.h
50libgnunet_plugin_reclaim_credential_pabc_la_LIBADD = \
51 $(top_builddir)/src/lib/util/libgnunetutil.la \
52 $(top_builddir)/src/service/reclaim/libgnunetreclaim.la \
53 -ljansson\
54 -lpabc \
55 $(LTLIBINTL)
56libgnunet_plugin_reclaim_credential_pabc_la_LDFLAGS = \
57 $(GN_PLUGIN_LDFLAGS)
58endif
59
60
61libgnunet_plugin_reclaim_credential_jwt_la_SOURCES = \
62 plugin_reclaim_credential_jwt.c \
63 $(top_builddir)/src/service/reclaim/reclaim_attribute.c \
64 $(top_builddir)/src/service/reclaim/reclaim_credential.c
65libgnunet_plugin_reclaim_credential_jwt_la_LIBADD = \
66 $(top_builddir)/src/lib/util/libgnunetutil.la \
67 -ljansson\
68 $(LTLIBINTL)
69libgnunet_plugin_reclaim_credential_jwt_la_LDFLAGS = \
70 $(GN_PLUGIN_LDFLAGS)
diff --git a/src/plugin/reclaim/meson.build b/src/plugin/reclaim/meson.build
new file mode 100644
index 000000000..cde9fc726
--- /dev/null
+++ b/src/plugin/reclaim/meson.build
@@ -0,0 +1,28 @@
1shared_module('gnunet_plugin_gnsrecord_reclaim',
2 ['plugin_gnsrecord_reclaim.c'],
3 dependencies: [libgnunetutil_dep, libgnunetgnsrecord_dep],
4 include_directories: [incdir, configuration_inc],
5 install: true,
6 install_dir: get_option('libdir')/'gnunet')
7
8shared_module('gnunet_plugin_reclaim_attribute_basic',
9 ['plugin_reclaim_attribute_basic.c'],
10 dependencies: [
11 libgnunetjson_dep,
12 libgnunetutil_dep,
13 json_dep],
14 include_directories: [incdir, configuration_inc],
15 install: true,
16 install_dir: get_option('libdir') / 'gnunet')
17shared_module('gnunet_plugin_reclaim_credential_jwt',
18 ['plugin_reclaim_credential_jwt.c',
19 '../../service/reclaim/reclaim_attribute.c',
20 '../../service/reclaim/reclaim_credential.c'],
21 dependencies: [
22 libgnunetjson_dep,
23 libgnunetutil_dep,
24 json_dep],
25 include_directories: [incdir, configuration_inc],
26 install: true,
27 install_dir: get_option('libdir') / 'gnunet')
28
diff --git a/src/plugin/reclaim/pabc_helper.c b/src/plugin/reclaim/pabc_helper.c
new file mode 100644
index 000000000..d7688e9e1
--- /dev/null
+++ b/src/plugin/reclaim/pabc_helper.c
@@ -0,0 +1,374 @@
1// maximilian.kaul@aisec.fraunhofer.de
2
3// WIP implementation of
4// https://github.com/ontio/ontology-crypto/wiki/Anonymous-Credential
5// using the relic library https://github.com/relic-toolkit/relic/
6
7#include "platform.h"
8#include "pabc_helper.h"
9#include <pwd.h>
10#include <stdlib.h>
11#include <unistd.h>
12
13static char pabc_dir[PATH_MAX + 1];
14
15static const char *
16get_homedir ()
17{
18 const char *homedir;
19 if ((homedir = getenv ("HOME")) == NULL)
20 {
21 homedir = getpwuid (getuid ())->pw_dir;
22 }
23 return homedir;
24}
25
26
27static enum GNUNET_GenericReturnValue
28write_file (char const *const filename, const char *buffer)
29{
30 struct GNUNET_DISK_FileHandle *fh;
31 fh = GNUNET_DISK_file_open (filename,
32 GNUNET_DISK_OPEN_WRITE
33 | GNUNET_DISK_OPEN_TRUNCATE
34 | GNUNET_DISK_OPEN_CREATE,
35 GNUNET_DISK_PERM_USER_WRITE
36 | GNUNET_DISK_PERM_USER_READ);
37 if (fh == NULL)
38 return GNUNET_SYSERR;
39 if (GNUNET_SYSERR == GNUNET_DISK_file_write (fh,
40 buffer, strlen (buffer) + 1))
41 goto fail;
42 GNUNET_DISK_file_close (fh);
43 return GNUNET_OK;
44
45fail:
46 GNUNET_DISK_file_close (fh);
47 return GNUNET_SYSERR;
48}
49
50
51static enum GNUNET_GenericReturnValue
52init_pabc_dir ()
53{
54 size_t filename_size = strlen (get_homedir ()) + 1 + strlen (".local") + 1
55 + strlen ("pabc-reclaim") + 1;
56 snprintf (pabc_dir, filename_size, "%s/%s/%s",
57 get_homedir (), ".local", "pabc-reclaim");
58 return GNUNET_DISK_directory_create (pabc_dir);
59}
60
61
62static const char *
63get_pabcdir ()
64{
65 init_pabc_dir ();
66 return pabc_dir;
67}
68
69
70enum GNUNET_GenericReturnValue
71read_file (char const *const filename, char **buffer)
72{
73 struct GNUNET_DISK_FileHandle *fh;
74 if (GNUNET_YES != GNUNET_DISK_file_test (filename))
75 return GNUNET_SYSERR;
76
77 fh = GNUNET_DISK_file_open (filename,
78 GNUNET_DISK_OPEN_READ,
79 GNUNET_DISK_PERM_USER_READ);
80 if (fh == NULL)
81 return GNUNET_SYSERR;
82 long lSize = GNUNET_DISK_file_seek (fh, 0, GNUNET_DISK_SEEK_END);
83 if (lSize < 0)
84 goto fail;
85 GNUNET_DISK_file_seek (fh, 0, GNUNET_DISK_SEEK_SET);
86 *buffer = calloc ((size_t) lSize + 1, sizeof(char));
87 if (*buffer == NULL)
88 goto fail;
89
90 // copy the file into the buffer:
91 size_t r = GNUNET_DISK_file_read (fh, *buffer, (size_t) lSize);
92 if (r != (size_t) lSize)
93 goto fail;
94
95 GNUNET_DISK_file_close (fh);
96 return GNUNET_OK;
97
98fail:
99 GNUNET_DISK_file_close (fh);
100 GNUNET_free (*buffer);
101 return GNUNET_SYSERR;
102}
103
104
105struct pabc_public_parameters *
106PABC_read_issuer_ppfile (const char *f, struct pabc_context *const ctx)
107{
108 if (NULL == ctx)
109 {
110 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No global context provided\n");
111 return NULL;
112 }
113 struct pabc_public_parameters *pp;
114 char *buffer;
115 int r;
116 r = read_file (f, &buffer);
117 if (GNUNET_OK != r)
118 {
119 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error reading file\n");
120 return NULL;
121 }
122 if (PABC_OK != pabc_decode_and_new_public_parameters (ctx, &pp, buffer))
123 {
124 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
125 "Failed to decode public parameters\n");
126 PABC_FREE_NULL (buffer);
127 return NULL;
128 }
129 PABC_FREE_NULL (buffer);
130 return pp;
131}
132
133
134enum GNUNET_GenericReturnValue
135PABC_load_public_parameters (struct pabc_context *const ctx,
136 char const *const pp_name,
137 struct pabc_public_parameters **pp)
138{
139 char fname[PATH_MAX];
140 char *pp_filename;
141 const char *pdir = get_pabcdir ();
142
143 if (ctx == NULL)
144 return GNUNET_SYSERR;
145 if (pp_name == NULL)
146 return GNUNET_SYSERR;
147
148 GNUNET_STRINGS_urlencode (strlen (pp_name),
149 pp_name,
150 &pp_filename);
151 if (GNUNET_YES != GNUNET_DISK_directory_test (pdir, GNUNET_YES))
152 {
153 GNUNET_free (pp_filename);
154 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error reading %s\n", pdir);
155 return GNUNET_SYSERR;
156 }
157 snprintf (fname, PATH_MAX, "%s/%s%s", pdir, pp_filename, PABC_PP_EXT);
158 if (GNUNET_YES != GNUNET_DISK_file_test (fname))
159 {
160 GNUNET_free (pp_filename);
161 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error testing %s\n", fname);
162 return GNUNET_SYSERR;
163 }
164 *pp = PABC_read_issuer_ppfile (fname, ctx);
165 if (*pp)
166 return GNUNET_OK;
167 else
168 return GNUNET_SYSERR;
169}
170
171
172enum GNUNET_GenericReturnValue
173PABC_write_public_parameters (char const *const pp_name,
174 struct pabc_public_parameters *const pp)
175{
176 char *json;
177 char *filename;
178 char *pp_filename;
179 enum pabc_status status;
180 struct pabc_context *ctx = NULL;
181
182 GNUNET_STRINGS_urlencode (strlen (pp_name),
183 pp_name,
184 &pp_filename);
185 PABC_ASSERT (pabc_new_ctx (&ctx));
186 // store in json file
187 status = pabc_encode_public_parameters (ctx, pp, &json);
188 if (status != PABC_OK)
189 {
190 GNUNET_free (pp_filename);
191 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
192 "Failed to encode public parameters.\n");
193 pabc_free_ctx (&ctx);
194 return GNUNET_SYSERR;
195 }
196
197 size_t filename_size =
198 strlen (get_pabcdir ()) + 1 + strlen (pp_filename) + strlen (PABC_PP_EXT)
199 + 1;
200 filename = GNUNET_malloc (filename_size);
201 if (! filename)
202 {
203 GNUNET_free (pp_filename);
204 PABC_FREE_NULL (json);
205 pabc_free_ctx (&ctx);
206 return GNUNET_SYSERR;
207 }
208 snprintf (filename, filename_size, "%s/%s%s", get_pabcdir (), pp_filename,
209 PABC_PP_EXT);
210
211 GNUNET_free (pp_filename);
212 if (GNUNET_OK != write_file (filename, json))
213 {
214 PABC_FREE_NULL (filename);
215 PABC_FREE_NULL (json);
216 pabc_free_ctx (&ctx);
217 return GNUNET_SYSERR;
218 }
219 PABC_FREE_NULL (filename);
220 PABC_FREE_NULL (json);
221 pabc_free_ctx (&ctx);
222 return GNUNET_OK;
223}
224
225
226enum GNUNET_GenericReturnValue
227PABC_write_usr_ctx (char const *const usr_name,
228 char const *const pp_name,
229 struct pabc_context const *const ctx,
230 struct pabc_public_parameters const *const pp,
231 struct pabc_user_context *const usr_ctx)
232{
233
234 char *pp_filename;
235 char *json = NULL;
236 enum pabc_status status;
237 char *fname = NULL;
238
239 if (NULL == usr_name)
240 {
241 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No issuer given.\n");
242 return GNUNET_SYSERR;
243 }
244 if (NULL == pp_name)
245 {
246 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No user given.\n");
247 return GNUNET_SYSERR;
248 }
249 if (NULL == ctx)
250 {
251 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No context given.\n");
252 return GNUNET_SYSERR;
253 }
254 if (NULL == pp)
255 {
256 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No public parameters given.\n");
257 return GNUNET_SYSERR;
258 }
259 if (NULL == usr_ctx)
260 {
261 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No user context given.\n");
262 return GNUNET_SYSERR;
263 }
264
265 GNUNET_STRINGS_urlencode (strlen (pp_name),
266 pp_name,
267 &pp_filename);
268 status = pabc_encode_user_ctx (ctx, pp, usr_ctx, &json);
269 if (PABC_OK != status)
270 {
271 GNUNET_free (pp_filename);
272 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to encode user context.\n");
273 return status;
274 }
275
276 size_t fname_size = strlen (get_pabcdir ()) + 1 + strlen (usr_name) + 1
277 + strlen (pp_filename) + strlen (PABC_USR_EXT) + 1;
278 fname = GNUNET_malloc (fname_size);
279
280 snprintf (fname, fname_size, "%s/%s_%s%s", get_pabcdir (), usr_name,
281 pp_filename,
282 PABC_USR_EXT);
283
284 GNUNET_free (pp_filename);
285 if (GNUNET_OK == write_file (fname, json))
286 {
287 GNUNET_free (fname);
288 GNUNET_free (json);
289 return GNUNET_OK;
290 }
291 else
292 {
293 GNUNET_free (fname);
294 GNUNET_free (json);
295 return GNUNET_SYSERR;
296 }
297}
298
299
300enum GNUNET_GenericReturnValue
301PABC_read_usr_ctx (char const *const usr_name,
302 char const *const pp_name,
303 struct pabc_context const *const ctx,
304 struct pabc_public_parameters const *const pp,
305 struct pabc_user_context **usr_ctx)
306{
307 char *json = NULL;
308 char *pp_filename;
309 enum pabc_status status;
310
311 char *fname = NULL;
312
313 if (NULL == usr_name)
314 {
315 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No issuer given.\n");
316 return GNUNET_SYSERR;
317 }
318 if (NULL == pp_name)
319 {
320 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No user given.\n");
321 return GNUNET_SYSERR;
322 }
323 if (NULL == ctx)
324 {
325 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No context given.\n");
326 return GNUNET_SYSERR;
327 }
328 if (NULL == pp)
329 {
330 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No public parameters given.\n");
331 return GNUNET_SYSERR;
332 }
333 if (NULL == usr_ctx)
334 {
335 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No user context given.\n");
336 return GNUNET_SYSERR;
337 }
338 GNUNET_STRINGS_urlencode (strlen (pp_name),
339 pp_name,
340 &pp_filename);
341
342 size_t fname_size = strlen (get_pabcdir ()) + 1 + strlen (usr_name) + 1
343 + strlen (pp_filename) + strlen (PABC_USR_EXT) + 1;
344 fname = GNUNET_malloc (fname_size);
345 snprintf (fname, fname_size, "%s/%s_%s%s", get_pabcdir (), usr_name,
346 pp_filename,
347 PABC_USR_EXT);
348 GNUNET_free (pp_filename);
349 if (GNUNET_OK != read_file (fname, &json))
350 {
351 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
352 "Failed to read `%s'\n", fname);
353 PABC_FREE_NULL (fname);
354 return GNUNET_SYSERR;
355 }
356 GNUNET_free (fname);
357
358 status = pabc_new_user_context (ctx, pp, usr_ctx);
359 if (PABC_OK != status)
360 {
361 GNUNET_free (json);
362 return GNUNET_SYSERR;
363 }
364 status = pabc_decode_user_ctx (ctx, pp, *usr_ctx, json);
365 GNUNET_free (json);
366 if (PABC_OK != status)
367 {
368 pabc_free_user_context (ctx, pp, usr_ctx);
369 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to encode user context.\n");
370 return GNUNET_SYSERR;
371 }
372
373 return GNUNET_OK;
374}
diff --git a/src/plugin/reclaim/pabc_helper.h b/src/plugin/reclaim/pabc_helper.h
new file mode 100644
index 000000000..045ad5dda
--- /dev/null
+++ b/src/plugin/reclaim/pabc_helper.h
@@ -0,0 +1,41 @@
1#include "platform.h"
2#include "gnunet_util_lib.h"
3#include <pabc/pabc.h>
4
5#ifndef PATH_MAX
6#define PATH_MAX 4096
7#endif
8
9#define PABC_ISK_EXT ".isk"
10
11#define PABC_PP_EXT ".pp"
12
13#define PABC_USR_EXT ".usr"
14
15#define PABC_ATTR_DELIM "="
16
17enum GNUNET_GenericReturnValue
18PABC_write_public_parameters (char const *const pp_name,
19 struct pabc_public_parameters *const pp);
20
21
22enum GNUNET_GenericReturnValue
23PABC_load_public_parameters (struct pabc_context *const ctx,
24 char const *const pp_name,
25 struct pabc_public_parameters **pp);
26
27enum GNUNET_GenericReturnValue
28PABC_write_usr_ctx (char const *const user_name,
29 char const *const pp_name,
30 struct pabc_context const *const ctx,
31 struct pabc_public_parameters const *const
32 pp,
33 struct pabc_user_context *const usr_ctx);
34
35enum GNUNET_GenericReturnValue
36PABC_read_usr_ctx (char const *const user_name,
37 char const *const pp_name,
38 struct pabc_context const *const ctx,
39 struct pabc_public_parameters const *const
40 pp,
41 struct pabc_user_context **usr_ctx);
diff --git a/src/plugin/reclaim/plugin_gnsrecord_reclaim.c b/src/plugin/reclaim/plugin_gnsrecord_reclaim.c
new file mode 100644
index 000000000..baf2fc37a
--- /dev/null
+++ b/src/plugin/reclaim/plugin_gnsrecord_reclaim.c
@@ -0,0 +1,195 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013, 2014 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file reclaim/plugin_gnsrecord_reclaim.c
23 * @brief gnsrecord plugin to provide the API for identity records
24 * @author Martin Schanzenbach
25 */
26#include "platform.h"
27
28#include "gnunet_util_lib.h"
29
30#include "gnunet_gnsrecord_lib.h"
31#include "gnunet_gnsrecord_plugin.h"
32
33/**
34 * Convert the 'value' of a record to a string.
35 *
36 * @param cls closure, unused
37 * @param type type of the record
38 * @param data value in binary encoding
39 * @param data_size number of bytes in @a data
40 * @return NULL on error, otherwise human-readable representation of the value
41 */
42static char *
43value_to_string (void *cls, uint32_t type, const void *data, size_t data_size)
44{
45 switch (type)
46 {
47 case GNUNET_GNSRECORD_TYPE_RECLAIM_TICKET:
48 case GNUNET_GNSRECORD_TYPE_RECLAIM_OIDC_REDIRECT:
49 case GNUNET_GNSRECORD_TYPE_RECLAIM_OIDC_CLIENT:
50 return GNUNET_strndup (data, data_size);
51 case GNUNET_GNSRECORD_TYPE_RECLAIM_ATTRIBUTE:
52 case GNUNET_GNSRECORD_TYPE_RECLAIM_ATTRIBUTE_REF:
53 case GNUNET_GNSRECORD_TYPE_RECLAIM_CREDENTIAL:
54 case GNUNET_GNSRECORD_TYPE_RECLAIM_PRESENTATION:
55 return GNUNET_STRINGS_data_to_string_alloc (data, data_size);
56
57 default:
58 return NULL;
59 }
60}
61
62
63/**
64 * Convert human-readable version of a 'value' of a record to the binary
65 * representation.
66 *
67 * @param cls closure, unused
68 * @param type type of the record
69 * @param s human-readable string
70 * @param data set to value in binary encoding (will be allocated)
71 * @param data_size set to number of bytes in @a data
72 * @return #GNUNET_OK on success
73 */
74static int
75string_to_value (void *cls, uint32_t type, const char *s, void **data,
76 size_t *data_size)
77{
78 if (NULL == s)
79 return GNUNET_SYSERR;
80 switch (type)
81 {
82 case GNUNET_GNSRECORD_TYPE_RECLAIM_TICKET:
83 case GNUNET_GNSRECORD_TYPE_RECLAIM_OIDC_REDIRECT:
84 case GNUNET_GNSRECORD_TYPE_RECLAIM_OIDC_CLIENT:
85 *data = GNUNET_strdup (s);
86 *data_size = strlen (s);
87 return GNUNET_OK;
88 case GNUNET_GNSRECORD_TYPE_RECLAIM_ATTRIBUTE:
89 case GNUNET_GNSRECORD_TYPE_RECLAIM_ATTRIBUTE_REF:
90 case GNUNET_GNSRECORD_TYPE_RECLAIM_CREDENTIAL:
91 case GNUNET_GNSRECORD_TYPE_RECLAIM_PRESENTATION:
92 return GNUNET_STRINGS_string_to_data (s, strlen (s), *data, *data_size);
93
94 default:
95 return GNUNET_SYSERR;
96 }
97}
98
99
100/**
101 * Mapping of record type numbers to human-readable
102 * record type names.
103 */
104static struct
105{
106 const char *name;
107 uint32_t number;
108} name_map[] = {
109 { "RECLAIM_ATTRIBUTE", GNUNET_GNSRECORD_TYPE_RECLAIM_ATTRIBUTE },
110 { "RECLAIM_ATTRIBUTE_REF", GNUNET_GNSRECORD_TYPE_RECLAIM_ATTRIBUTE_REF },
111 { "RECLAIM_CREDENTIAL", GNUNET_GNSRECORD_TYPE_RECLAIM_CREDENTIAL },
112 { "RECLAIM_PRESENTATION", GNUNET_GNSRECORD_TYPE_RECLAIM_PRESENTATION },
113 { "RECLAIM_OIDC_CLIENT", GNUNET_GNSRECORD_TYPE_RECLAIM_OIDC_CLIENT },
114 { "RECLAIM_OIDC_REDIRECT", GNUNET_GNSRECORD_TYPE_RECLAIM_OIDC_REDIRECT },
115 { "RECLAIM_TICKET", GNUNET_GNSRECORD_TYPE_RECLAIM_TICKET },
116 { NULL, UINT32_MAX }
117};
118
119
120/**
121 * Convert a type name (e.g. "AAAA") to the corresponding number.
122 *
123 * @param cls closure, unused
124 * @param dns_typename name to convert
125 * @return corresponding number, UINT32_MAX on error
126 */
127static uint32_t
128typename_to_number (void *cls, const char *dns_typename)
129{
130 unsigned int i;
131
132 i = 0;
133 while ((NULL != name_map[i].name) &&
134 (0 != strcasecmp (dns_typename, name_map[i].name)))
135 i++;
136 return name_map[i].number;
137}
138
139
140/**
141 * Convert a type number to the corresponding type string (e.g. 1 to "A")
142 *
143 * @param cls closure, unused
144 * @param type number of a type to convert
145 * @return corresponding typestring, NULL on error
146 */
147static const char *
148number_to_typename (void *cls, uint32_t type)
149{
150 unsigned int i;
151
152 i = 0;
153 while ((NULL != name_map[i].name) && (type != name_map[i].number))
154 i++;
155 return name_map[i].name;
156}
157
158
159/**
160 * Entry point for the plugin.
161 *
162 * @param cls NULL
163 * @return the exported block API
164 */
165void *
166libgnunet_plugin_gnsrecord_reclaim_init (void *cls)
167{
168 struct GNUNET_GNSRECORD_PluginFunctions *api;
169
170 api = GNUNET_new (struct GNUNET_GNSRECORD_PluginFunctions);
171 api->value_to_string = &value_to_string;
172 api->string_to_value = &string_to_value;
173 api->typename_to_number = &typename_to_number;
174 api->number_to_typename = &number_to_typename;
175 return api;
176}
177
178
179/**
180 * Exit point from the plugin.
181 *
182 * @param cls the return value from #libgnunet_plugin_block_test_init
183 * @return NULL
184 */
185void *
186libgnunet_plugin_gnsrecord_reclaim_done (void *cls)
187{
188 struct GNUNET_GNSRECORD_PluginFunctions *api = cls;
189
190 GNUNET_free (api);
191 return NULL;
192}
193
194
195/* end of plugin_gnsrecord_dns.c */
diff --git a/src/plugin/reclaim/plugin_reclaim_attribute_basic.c b/src/plugin/reclaim/plugin_reclaim_attribute_basic.c
new file mode 100644
index 000000000..66f59998a
--- /dev/null
+++ b/src/plugin/reclaim/plugin_reclaim_attribute_basic.c
@@ -0,0 +1,181 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013, 2014, 2016 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file reclaim-attribute/plugin_reclaim_attribute_gnuid.c
23 * @brief reclaim-attribute-plugin-gnuid attribute plugin to provide the API for
24 * fundamental
25 * attribute types.
26 *
27 * @author Martin Schanzenbach
28 */
29#include "platform.h"
30#include "gnunet_util_lib.h"
31#include "gnunet_reclaim_plugin.h"
32#include <inttypes.h>
33
34
35/**
36 * Convert the 'value' of an attribute to a string.
37 *
38 * @param cls closure, unused
39 * @param type type of the attribute
40 * @param data value in binary encoding
41 * @param data_size number of bytes in @a data
42 * @return NULL on error, otherwise human-readable representation of the value
43 */
44static char *
45basic_value_to_string (void *cls,
46 uint32_t type,
47 const void *data,
48 size_t data_size)
49{
50 switch (type)
51 {
52 case GNUNET_RECLAIM_ATTRIBUTE_TYPE_STRING:
53 return GNUNET_strndup (data, data_size);
54
55 default:
56 return NULL;
57 }
58}
59
60
61/**
62 * Convert human-readable version of a 'value' of an attribute to the binary
63 * representation.
64 *
65 * @param cls closure, unused
66 * @param type type of the attribute
67 * @param s human-readable string
68 * @param data set to value in binary encoding (will be allocated)
69 * @param data_size set to number of bytes in @a data
70 * @return #GNUNET_OK on success
71 */
72static int
73basic_string_to_value (void *cls,
74 uint32_t type,
75 const char *s,
76 void **data,
77 size_t *data_size)
78{
79 if (NULL == s)
80 return GNUNET_SYSERR;
81 switch (type)
82 {
83 case GNUNET_RECLAIM_ATTRIBUTE_TYPE_STRING:
84 *data = GNUNET_strdup (s);
85 *data_size = strlen (s) + 1;
86 return GNUNET_OK;
87
88 default:
89 return GNUNET_SYSERR;
90 }
91}
92
93
94/**
95 * Mapping of attribute type numbers to human-readable
96 * attribute type names.
97 */
98static struct
99{
100 const char *name;
101 uint32_t number;
102} basic_name_map[] = { { "STRING", GNUNET_RECLAIM_ATTRIBUTE_TYPE_STRING },
103 { NULL, UINT32_MAX } };
104
105
106/**
107 * Convert a type name to the corresponding number.
108 *
109 * @param cls closure, unused
110 * @param basic_typename name to convert
111 * @return corresponding number, UINT32_MAX on error
112 */
113static uint32_t
114basic_typename_to_number (void *cls, const char *basic_typename)
115{
116 unsigned int i;
117
118 i = 0;
119 while ((NULL != basic_name_map[i].name) &&
120 (0 != strcasecmp (basic_typename, basic_name_map[i].name)))
121 i++;
122 return basic_name_map[i].number;
123}
124
125
126/**
127 * Convert a type number to the corresponding type string (e.g. 1 to "A")
128 *
129 * @param cls closure, unused
130 * @param type number of a type to convert
131 * @return corresponding typestring, NULL on error
132 */
133static const char *
134basic_number_to_typename (void *cls, uint32_t type)
135{
136 unsigned int i;
137
138 i = 0;
139 while ((NULL != basic_name_map[i].name) && (type != basic_name_map[i].number))
140 i++;
141 return basic_name_map[i].name;
142}
143
144
145/**
146 * Entry point for the plugin.
147 *
148 * @param cls NULL
149 * @return the exported block API
150 */
151void *
152libgnunet_plugin_reclaim_attribute_basic_init (void *cls)
153{
154 struct GNUNET_RECLAIM_AttributePluginFunctions *api;
155
156 api = GNUNET_new (struct GNUNET_RECLAIM_AttributePluginFunctions);
157 api->value_to_string = &basic_value_to_string;
158 api->string_to_value = &basic_string_to_value;
159 api->typename_to_number = &basic_typename_to_number;
160 api->number_to_typename = &basic_number_to_typename;
161 return api;
162}
163
164
165/**
166 * Exit point from the plugin.
167 *
168 * @param cls the return value from #libgnunet_plugin_block_test_init()
169 * @return NULL
170 */
171void *
172libgnunet_plugin_reclaim_attribute_basic_done (void *cls)
173{
174 struct GNUNET_RECLAIM_AttributePluginFunctions *api = cls;
175
176 GNUNET_free (api);
177 return NULL;
178}
179
180
181/* end of plugin_reclaim_attribute_type_gnuid.c */
diff --git a/src/plugin/reclaim/plugin_reclaim_credential_jwt.c b/src/plugin/reclaim/plugin_reclaim_credential_jwt.c
new file mode 100644
index 000000000..3eb4bfebf
--- /dev/null
+++ b/src/plugin/reclaim/plugin_reclaim_credential_jwt.c
@@ -0,0 +1,512 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013, 2014, 2016 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file reclaim/plugin_reclaim_credential_jwt.c
23 * @brief reclaim-credential-plugin-jwt attribute plugin to provide the API for
24 * JWT credentials.
25 *
26 * @author Martin Schanzenbach
27 */
28#include "platform.h"
29#include "gnunet_util_lib.h"
30#include "gnunet_reclaim_plugin.h"
31#include <inttypes.h>
32#include <jansson.h>
33
34/**
35 * Convert the 'value' of an credential to a string.
36 *
37 * @param cls closure, unused
38 * @param type type of the credential
39 * @param data value in binary encoding
40 * @param data_size number of bytes in @a data
41 * @return NULL on error, otherwise human-readable representation of the value
42 */
43static char *
44jwt_value_to_string (void *cls,
45 uint32_t type,
46 const void *data,
47 size_t data_size)
48{
49 switch (type)
50 {
51 case GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT:
52 return GNUNET_strndup (data, data_size);
53
54 default:
55 return NULL;
56 }
57}
58
59
60/**
61 * Convert human-readable version of a 'value' of an credential to the binary
62 * representation.
63 *
64 * @param cls closure, unused
65 * @param type type of the credential
66 * @param s human-readable string
67 * @param data set to value in binary encoding (will be allocated)
68 * @param data_size set to number of bytes in @a data
69 * @return #GNUNET_OK on success
70 */
71static int
72jwt_string_to_value (void *cls,
73 uint32_t type,
74 const char *s,
75 void **data,
76 size_t *data_size)
77{
78 if (NULL == s)
79 return GNUNET_SYSERR;
80 switch (type)
81 {
82 case GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT:
83 *data = GNUNET_strdup (s);
84 *data_size = strlen (s) + 1;
85 return GNUNET_OK;
86
87 default:
88 return GNUNET_SYSERR;
89 }
90}
91
92
93/**
94 * Mapping of credential type numbers to human-readable
95 * credential type names.
96 */
97static struct
98{
99 const char *name;
100 uint32_t number;
101} jwt_cred_name_map[] = { { "JWT", GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT },
102 { NULL, UINT32_MAX } };
103
104/**
105 * Convert a type name to the corresponding number.
106 *
107 * @param cls closure, unused
108 * @param jwt_typename name to convert
109 * @return corresponding number, UINT32_MAX on error
110 */
111static uint32_t
112jwt_typename_to_number (void *cls, const char *jwt_typename)
113{
114 unsigned int i;
115
116 i = 0;
117 while ((NULL != jwt_cred_name_map[i].name) &&
118 (0 != strcasecmp (jwt_typename, jwt_cred_name_map[i].name)))
119 i++;
120 return jwt_cred_name_map[i].number;
121}
122
123
124/**
125 * Convert a type number to the corresponding type string (e.g. 1 to "A")
126 *
127 * @param cls closure, unused
128 * @param type number of a type to convert
129 * @return corresponding typestring, NULL on error
130 */
131static const char *
132jwt_number_to_typename (void *cls, uint32_t type)
133{
134 unsigned int i;
135
136 i = 0;
137 while ((NULL != jwt_cred_name_map[i].name) && (type !=
138 jwt_cred_name_map[i].
139 number))
140 i++;
141 return jwt_cred_name_map[i].name;
142}
143
144
145/**
146 * Parse a JWT and return the respective claim value as Attribute
147 *
148 * @param cls the plugin
149 * @param cred the jwt credential
150 * @return a GNUNET_RECLAIM_Attribute, containing the new value
151 */
152struct GNUNET_RECLAIM_AttributeList *
153jwt_parse_attributes (void *cls,
154 const char *data,
155 size_t data_size)
156{
157 char *jwt_string;
158 struct GNUNET_RECLAIM_AttributeList *attrs;
159 char delim[] = ".";
160 char *val_str = NULL;
161 char *decoded_jwt;
162 char *tmp;
163 json_t *json_val;
164 json_error_t json_err;
165
166 attrs = GNUNET_new (struct GNUNET_RECLAIM_AttributeList);
167
168 jwt_string = GNUNET_strndup (data, data_size);
169 const char *jwt_body = strtok (jwt_string, delim);
170 if (NULL == jwt_body)
171 {
172 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
173 "Failed to parse JSON %s\n", jwt_string);
174 return attrs;
175 }
176 jwt_body = strtok (NULL, delim);
177 if (NULL == jwt_body)
178 {
179 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
180 "Failed to parse JSON %s\n", jwt_string);
181 GNUNET_free (jwt_string);
182 return attrs;
183 }
184 GNUNET_STRINGS_base64url_decode (jwt_body, strlen (jwt_body),
185 (void **) &decoded_jwt);
186 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Decoded JWT: %s\n", decoded_jwt);
187 GNUNET_assert (NULL != decoded_jwt);
188 json_val = json_loads (decoded_jwt, JSON_DECODE_ANY, &json_err);
189 GNUNET_free (decoded_jwt);
190 const char *key;
191 const char *addr_key;
192 json_t *value;
193 json_t *addr_value;
194
195 json_object_foreach (json_val, key, value) {
196 if (0 == strcmp ("iss", key))
197 continue;
198 if (0 == strcmp ("jti", key))
199 continue;
200 if (0 == strcmp ("exp", key))
201 continue;
202 if (0 == strcmp ("iat", key))
203 continue;
204 if (0 == strcmp ("nbf", key))
205 continue;
206 if (0 == strcmp ("aud", key))
207 continue;
208 if (0 == strcmp ("address", key))
209 {
210 if (! json_is_object (value))
211 {
212 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
213 "address claim in wrong format!");
214 continue;
215 }
216 json_object_foreach (value, addr_key, addr_value) {
217 val_str = json_dumps (addr_value, JSON_ENCODE_ANY);
218 tmp = val_str;
219 // Remove leading " from jasson conversion
220 if (tmp[0] == '"')
221 tmp++;
222 // Remove trailing " from jansson conversion
223 if (tmp[strlen (tmp) - 1] == '"')
224 tmp[strlen (tmp) - 1] = '\0';
225 GNUNET_RECLAIM_attribute_list_add (attrs,
226 addr_key,
227 NULL,
228 GNUNET_RECLAIM_ATTRIBUTE_TYPE_STRING,
229 tmp,
230 strlen (val_str));
231 GNUNET_free (val_str);
232 }
233 continue;
234 }
235 val_str = json_dumps (value, JSON_ENCODE_ANY);
236 tmp = val_str;
237 // Remove leading " from jasson conversion
238 if (tmp[0] == '"')
239 tmp++;
240 // Remove trailing " from jansson conversion
241 if (tmp[strlen (tmp) - 1] == '"')
242 tmp[strlen (tmp) - 1] = '\0';
243 GNUNET_RECLAIM_attribute_list_add (attrs,
244 key,
245 NULL,
246 GNUNET_RECLAIM_ATTRIBUTE_TYPE_STRING,// FIXME
247 tmp,
248 strlen (val_str));
249 GNUNET_free (val_str);
250 }
251 json_decref (json_val);
252 GNUNET_free (jwt_string);
253 return attrs;
254}
255
256
257/**
258 * Parse a JWT and return the respective claim value as Attribute
259 *
260 * @param cls the plugin
261 * @param cred the jwt credential
262 * @return a GNUNET_RECLAIM_Attribute, containing the new value
263 */
264struct GNUNET_RECLAIM_AttributeList *
265jwt_parse_attributes_c (void *cls,
266 const struct GNUNET_RECLAIM_Credential *cred)
267{
268 if (cred->type != GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT)
269 return NULL;
270 return jwt_parse_attributes (cls, cred->data, cred->data_size);
271}
272
273
274/**
275 * Parse a JWT and return the respective claim value as Attribute
276 *
277 * @param cls the plugin
278 * @param cred the jwt credential
279 * @return a GNUNET_RECLAIM_Attribute, containing the new value
280 */
281struct GNUNET_RECLAIM_AttributeList *
282jwt_parse_attributes_p (void *cls,
283 const struct GNUNET_RECLAIM_Presentation *cred)
284{
285 if (cred->type != GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT)
286 return NULL;
287 return jwt_parse_attributes (cls, cred->data, cred->data_size);
288}
289
290
291/**
292 * Parse a JWT and return the issuer
293 *
294 * @param cls the plugin
295 * @param cred the jwt credential
296 * @return a string, containing the isser
297 */
298char *
299jwt_get_issuer (void *cls,
300 const char *data,
301 size_t data_size)
302{
303 const char *jwt_body;
304 char *jwt_string;
305 char delim[] = ".";
306 char *issuer = NULL;
307 char *decoded_jwt;
308 json_t *issuer_json;
309 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Parsing JWT attributes.\n");
310 json_t *json_val;
311 json_error_t json_err;
312
313 jwt_string = GNUNET_strndup (data, data_size);
314 jwt_body = strtok (jwt_string, delim);
315 jwt_body = strtok (NULL, delim);
316 GNUNET_STRINGS_base64url_decode (jwt_body, strlen (jwt_body),
317 (void **) &decoded_jwt);
318 json_val = json_loads (decoded_jwt, JSON_DECODE_ANY, &json_err);
319 GNUNET_free (decoded_jwt);
320 GNUNET_free (jwt_string);
321 if (NULL == json_val)
322 return NULL;
323 issuer_json = json_object_get (json_val, "iss");
324 if ((NULL == issuer_json) || (! json_is_string (issuer_json)))
325 {
326 json_decref (json_val);
327 return NULL;
328 }
329 issuer = GNUNET_strdup (json_string_value (issuer_json));
330 json_decref (json_val);
331 return issuer;
332}
333
334
335/**
336 * Parse a JWT and return the issuer
337 *
338 * @param cls the plugin
339 * @param cred the jwt credential
340 * @return a string, containing the isser
341 */
342char *
343jwt_get_issuer_c (void *cls,
344 const struct GNUNET_RECLAIM_Credential *cred)
345{
346 if (GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT != cred->type)
347 return NULL;
348 return jwt_get_issuer (cls, cred->data, cred->data_size);
349}
350
351
352/**
353 * Parse a JWT and return the issuer
354 *
355 * @param cls the plugin
356 * @param cred the jwt credential
357 * @return a string, containing the isser
358 */
359char *
360jwt_get_issuer_p (void *cls,
361 const struct GNUNET_RECLAIM_Presentation *cred)
362{
363 if (GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT != cred->type)
364 return NULL;
365 return jwt_get_issuer (cls, cred->data, cred->data_size);
366}
367
368
369/**
370 * Parse a JWT and return the expiration
371 *
372 * @param cls the plugin
373 * @param cred the jwt credential
374 * @return a string, containing the isser
375 */
376enum GNUNET_GenericReturnValue
377jwt_get_expiration (void *cls,
378 const char *data,
379 size_t data_size,
380 struct GNUNET_TIME_Absolute *exp)
381{
382 const char *jwt_body;
383 char *jwt_string;
384 char delim[] = ".";
385 char *decoded_jwt;
386 json_t *exp_json;
387 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Parsing JWT attributes.\n");
388 json_t *json_val;
389 json_error_t json_err;
390
391 jwt_string = GNUNET_strndup (data, data_size);
392 jwt_body = strtok (jwt_string, delim);
393 jwt_body = strtok (NULL, delim);
394 GNUNET_STRINGS_base64url_decode (jwt_body, strlen (jwt_body),
395 (void **) &decoded_jwt);
396 json_val = json_loads (decoded_jwt, JSON_DECODE_ANY, &json_err);
397 GNUNET_free (decoded_jwt);
398 GNUNET_free (jwt_string);
399 if (NULL == json_val)
400 return GNUNET_SYSERR;
401 exp_json = json_object_get (json_val, "exp");
402 if ((NULL == exp_json) || (! json_is_integer (exp_json)))
403 {
404 json_decref (json_val);
405 return GNUNET_SYSERR;
406 }
407 exp->abs_value_us = json_integer_value (exp_json) * 1000 * 1000;
408 json_decref (json_val);
409 return GNUNET_OK;
410}
411
412
413/**
414 * Parse a JWT and return the expiration
415 *
416 * @param cls the plugin
417 * @param cred the jwt credential
418 * @return the expirati
419 */
420enum GNUNET_GenericReturnValue
421jwt_get_expiration_c (void *cls,
422 const struct GNUNET_RECLAIM_Credential *cred,
423 struct GNUNET_TIME_Absolute *exp)
424{
425 if (GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT != cred->type)
426 return GNUNET_NO;
427 return jwt_get_expiration (cls, cred->data, cred->data_size, exp);
428}
429
430
431/**
432 * Parse a JWT and return the expiration
433 *
434 * @param cls the plugin
435 * @param cred the jwt credential
436 * @return a string, containing the isser
437 */
438enum GNUNET_GenericReturnValue
439jwt_get_expiration_p (void *cls,
440 const struct GNUNET_RECLAIM_Presentation *cred,
441 struct GNUNET_TIME_Absolute *exp)
442{
443 if (GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT != cred->type)
444 return GNUNET_NO;
445 return jwt_get_expiration (cls, cred->data, cred->data_size, exp);
446}
447
448
449enum GNUNET_GenericReturnValue
450jwt_create_presentation (void *cls,
451 const struct GNUNET_RECLAIM_Credential *cred,
452 const struct GNUNET_RECLAIM_AttributeList *attrs,
453 struct GNUNET_RECLAIM_Presentation **presentation)
454{
455 if (GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT != cred->type)
456 return GNUNET_NO;
457 *presentation = GNUNET_RECLAIM_presentation_new (
458 GNUNET_RECLAIM_CREDENTIAL_TYPE_JWT,
459 cred->data,
460 cred->data_size);
461 return GNUNET_OK;
462}
463
464
465/**
466 * Entry point for the plugin.
467 *
468 * @param cls NULL
469 * @return the exported block API
470 */
471void *
472libgnunet_plugin_reclaim_credential_jwt_init (void *cls)
473{
474 struct GNUNET_RECLAIM_CredentialPluginFunctions *api;
475
476 api = GNUNET_new (struct GNUNET_RECLAIM_CredentialPluginFunctions);
477 api->value_to_string = &jwt_value_to_string;
478 api->string_to_value = &jwt_string_to_value;
479 api->typename_to_number = &jwt_typename_to_number;
480 api->number_to_typename = &jwt_number_to_typename;
481 api->get_attributes = &jwt_parse_attributes_c;
482 api->get_issuer = &jwt_get_issuer_c;
483 api->get_expiration = &jwt_get_expiration_c;
484 api->value_to_string_p = &jwt_value_to_string;
485 api->string_to_value_p = &jwt_string_to_value;
486 api->typename_to_number_p = &jwt_typename_to_number;
487 api->number_to_typename_p = &jwt_number_to_typename;
488 api->get_attributes_p = &jwt_parse_attributes_p;
489 api->get_issuer_p = &jwt_get_issuer_p;
490 api->get_expiration_p = &jwt_get_expiration_p;
491 api->create_presentation = &jwt_create_presentation;
492 return api;
493}
494
495
496/**
497 * Exit point from the plugin.
498 *
499 * @param cls the return value from #libgnunet_plugin_block_test_init()
500 * @return NULL
501 */
502void *
503libgnunet_plugin_reclaim_credential_jwt_done (void *cls)
504{
505 struct GNUNET_RECLAIM_CredentialPluginFunctions *api = cls;
506
507 GNUNET_free (api);
508 return NULL;
509}
510
511
512/* end of plugin_reclaim_credential_type_jwt.c */
diff --git a/src/plugin/reclaim/plugin_reclaim_credential_pabc.c b/src/plugin/reclaim/plugin_reclaim_credential_pabc.c
new file mode 100644
index 000000000..a906805fb
--- /dev/null
+++ b/src/plugin/reclaim/plugin_reclaim_credential_pabc.c
@@ -0,0 +1,572 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013, 2014, 2016 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file reclaim/plugin_reclaim_credential_pabc.c
23 * @brief reclaim-credential-plugin-pabc attribute plugin to provide the API for
24 * pabc credentials.
25 *
26 * @author Martin Schanzenbach
27 */
28#include "platform.h"
29#include "gnunet_util_lib.h"
30#include "gnunet_reclaim_plugin.h"
31#include <inttypes.h>
32#include <jansson.h>
33#include <pabc/pabc.h>
34#include "pabc_helper.h"
35
36/**
37 * Convert the 'value' of an credential to a string.
38 *
39 * @param cls closure, unused
40 * @param type type of the credential
41 * @param data value in binary encoding
42 * @param data_size number of bytes in @a data
43 * @return NULL on error, otherwise human-readable representation of the value
44 */
45static char *
46pabc_value_to_string (void *cls,
47 uint32_t type,
48 const void *data,
49 size_t data_size)
50{
51 switch (type)
52 {
53 case GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC:
54 return GNUNET_strndup (data, data_size);
55
56 default:
57 return NULL;
58 }
59}
60
61
62/**
63 * Convert human-readable version of a 'value' of an credential to the binary
64 * representation.
65 *
66 * @param cls closure, unused
67 * @param type type of the credential
68 * @param s human-readable string
69 * @param data set to value in binary encoding (will be allocated)
70 * @param data_size set to number of bytes in @a data
71 * @return #GNUNET_OK on success
72 */
73static int
74pabc_string_to_value (void *cls,
75 uint32_t type,
76 const char *s,
77 void **data,
78 size_t *data_size)
79{
80 if (NULL == s)
81 return GNUNET_SYSERR;
82 switch (type)
83 {
84 case GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC:
85 *data = GNUNET_strdup (s);
86 *data_size = strlen (s) + 1;
87 return GNUNET_OK;
88
89 default:
90 return GNUNET_SYSERR;
91 }
92}
93
94
95/**
96 * Mapping of credential type numbers to human-readable
97 * credential type names.
98 */
99static struct
100{
101 const char *name;
102 uint32_t number;
103} pabc_cred_name_map[] = { { "PABC", GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC },
104 { NULL, UINT32_MAX } };
105
106/**
107 * Convert a type name to the corresponding number.
108 *
109 * @param cls closure, unused
110 * @param pabc_typename name to convert
111 * @return corresponding number, UINT32_MAX on error
112 */
113static uint32_t
114pabc_typename_to_number (void *cls, const char *pabc_typename)
115{
116 unsigned int i;
117
118 i = 0;
119 while ((NULL != pabc_cred_name_map[i].name) &&
120 (0 != strcasecmp (pabc_typename, pabc_cred_name_map[i].name)))
121 i++;
122 return pabc_cred_name_map[i].number;
123}
124
125
126/**
127 * Convert a type number (i.e. 1) to the corresponding type string
128 *
129 * @param cls closure, unused
130 * @param type number of a type to convert
131 * @return corresponding typestring, NULL on error
132 */
133static const char *
134pabc_number_to_typename (void *cls, uint32_t type)
135{
136 unsigned int i;
137
138 i = 0;
139 while ((NULL != pabc_cred_name_map[i].name) && (type !=
140 pabc_cred_name_map[i].
141 number))
142 i++;
143 return pabc_cred_name_map[i].name;
144}
145
146
147static void
148inspect_attrs (char const *const key,
149 char const *const value,
150 void *ctx)
151{
152 struct GNUNET_RECLAIM_AttributeList *attrs = ctx;
153
154 if (NULL == value)
155 return;
156 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
157 "Found attribute in PABC credential: `%s': `%s'\n",
158 key, value);
159 if (0 == strcmp (key, "expiration"))
160 return;
161 if (0 == strcmp (key, "issuer"))
162 return;
163 if (0 == strcmp (key, "subject"))
164 return;
165 GNUNET_RECLAIM_attribute_list_add (attrs,
166 key,
167 NULL,
168 GNUNET_RECLAIM_ATTRIBUTE_TYPE_STRING,
169 value,
170 strlen (value));
171}
172
173
174/**
175 * Parse a pabc and return the respective claim value as Attribute
176 *
177 * @param cls the plugin
178 * @param cred the pabc credential
179 * @return a GNUNET_RECLAIM_Attribute, containing the new value
180 */
181struct GNUNET_RECLAIM_AttributeList *
182pabc_parse_attributes (void *cls,
183 const char *data,
184 size_t data_size)
185{
186 struct GNUNET_RECLAIM_AttributeList *attrs;
187
188 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
189 "Collecting PABC attributes...\n");
190 attrs = GNUNET_new (struct GNUNET_RECLAIM_AttributeList);
191 GNUNET_assert (PABC_OK ==
192 pabc_cred_inspect_credential (data,
193 &inspect_attrs, attrs));
194 return attrs;
195}
196
197
198/**
199 * Parse a pabc and return the respective claim value as Attribute
200 *
201 * @param cls the plugin
202 * @param cred the pabc credential
203 * @return a GNUNET_RECLAIM_Attribute, containing the new value
204 */
205struct GNUNET_RECLAIM_AttributeList *
206pabc_parse_attributes_c (void *cls,
207 const struct GNUNET_RECLAIM_Credential *cred)
208{
209 if (cred->type != GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC)
210 return NULL;
211 return pabc_parse_attributes (cls, cred->data, cred->data_size);
212}
213
214
215/**
216 * Parse a pabc and return the respective claim value as Attribute
217 *
218 * @param cls the plugin
219 * @param cred the pabc credential
220 * @return a GNUNET_RECLAIM_Attribute, containing the new value
221 */
222struct GNUNET_RECLAIM_AttributeList *
223pabc_parse_attributes_p (void *cls,
224 const struct GNUNET_RECLAIM_Presentation *cred)
225{
226 if (cred->type != GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC)
227 return NULL;
228 return pabc_parse_attributes (cls, cred->data, cred->data_size);
229}
230
231
232/**
233 * Parse a pabc and return the issuer
234 *
235 * @param cls the plugin
236 * @param cred the pabc credential
237 * @return a string, containing the isser
238 */
239char*
240pabc_get_issuer (void *cls,
241 const char *data,
242 size_t data_size)
243{
244 char *res;
245 if (PABC_OK != pabc_cred_get_attr_by_name_from_cred (data,
246 "issuer",
247 &res))
248 return NULL;
249 return res;
250}
251
252
253/**
254 * Parse a pabc and return the issuer
255 *
256 * @param cls the plugin
257 * @param cred the pabc credential
258 * @return a string, containing the isser
259 */
260char *
261pabc_get_issuer_c (void *cls,
262 const struct GNUNET_RECLAIM_Credential *cred)
263{
264 if (GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC != cred->type)
265 return NULL;
266 return pabc_get_issuer (cls, cred->data, cred->data_size);
267}
268
269
270/**
271 * Parse a pabc and return the issuer
272 *
273 * @param cls the plugin
274 * @param cred the pabc credential
275 * @return a string, containing the isser
276 */
277char *
278pabc_get_issuer_p (void *cls,
279 const struct GNUNET_RECLAIM_Presentation *cred)
280{
281 if (GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC != cred->type)
282 return NULL;
283 return pabc_get_issuer (cls, cred->data, cred->data_size);
284}
285
286
287/**
288 * Parse a pabc and return the expiration
289 *
290 * @param cls the plugin
291 * @param cred the pabc credential
292 * @return a string, containing the isser
293 */
294enum GNUNET_GenericReturnValue
295pabc_get_expiration (void *cls,
296 const char *data,
297 size_t data_size,
298 struct GNUNET_TIME_Absolute *exp)
299{
300 char *exp_str;
301 uint64_t exp_i;
302
303 if (PABC_OK != pabc_cred_get_attr_by_name_from_cred (data,
304 "expiration",
305 &exp_str))
306 return GNUNET_SYSERR;
307
308 if (1 != sscanf (exp_str, "%llu", &exp_i))
309 {
310 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
311 "Invalid expiration `%s'\n", exp_str);
312 GNUNET_free (exp_str);
313 return GNUNET_SYSERR;
314 }
315 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
316 "Converted expiration string `%s' to %llu",
317 exp_str, exp_i);
318
319 GNUNET_free (exp_str);
320 exp->abs_value_us = exp_i * 1000 * 1000;
321 return GNUNET_OK;
322}
323
324
325/**
326 * Parse a pabc and return the expiration
327 *
328 * @param cls the plugin
329 * @param cred the pabc credential
330 * @return a string, containing the isser
331 */
332enum GNUNET_GenericReturnValue
333pabc_get_expiration_c (void *cls,
334 const struct GNUNET_RECLAIM_Credential *cred,
335 struct GNUNET_TIME_Absolute *exp)
336{
337 if (cred->type != GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC)
338 return GNUNET_NO;
339 return pabc_get_expiration (cls, cred->data, cred->data_size, exp);
340}
341
342
343/**
344 * Parse a pabc and return the expiration
345 *
346 * @param cls the plugin
347 * @param cred the pabc credential
348 * @return a string, containing the isser
349 */
350enum GNUNET_GenericReturnValue
351pabc_get_expiration_p (void *cls,
352 const struct GNUNET_RECLAIM_Presentation *cred,
353 struct GNUNET_TIME_Absolute *exp)
354{
355 if (cred->type != GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC)
356 return GNUNET_NO;
357 return pabc_get_expiration (cls, cred->data, cred->data_size, exp);
358}
359
360
361int
362pabc_create_presentation (void *cls,
363 const struct GNUNET_RECLAIM_Credential *credential,
364 const struct GNUNET_RECLAIM_AttributeList *attrs,
365 struct GNUNET_RECLAIM_Presentation **presentation)
366{
367 struct pabc_context *ctx = NULL;
368 struct pabc_user_context *usr_ctx = NULL;
369 struct pabc_public_parameters *pp = NULL;
370 struct pabc_credential *cred = NULL;
371 struct pabc_blinded_proof *proof = NULL;
372 struct GNUNET_RECLAIM_AttributeListEntry *ale;
373 char *issuer;
374 char *subject;
375 enum pabc_status status;
376
377 if (GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC != credential->type)
378 return GNUNET_NO;
379
380
381 PABC_ASSERT (pabc_new_ctx (&ctx));
382 issuer = pabc_get_issuer_c (cls, credential);
383 if (NULL == issuer)
384 {
385 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
386 "No issuer found in credential\n");
387 pabc_free_ctx (&ctx);
388 return GNUNET_SYSERR;
389 }
390 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
391 "Got issuer for credential: %s\n", issuer);
392 status = PABC_load_public_parameters (ctx, issuer, &pp);
393 if (status != PABC_OK)
394 {
395 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
396 "Failed to read public parameters.\n");
397 pabc_free_ctx (&ctx);
398 GNUNET_free (issuer);
399 return GNUNET_SYSERR;
400 }
401 if (PABC_OK != pabc_cred_get_attr_by_name_from_cred (credential->data,
402 "subject",
403 &subject))
404 {
405 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
406 "Failed to get subject.\n");
407 pabc_free_ctx (&ctx);
408 GNUNET_free (issuer);
409 return GNUNET_SYSERR;
410 }
411 status = PABC_read_usr_ctx (subject, issuer, ctx, pp, &usr_ctx);
412 GNUNET_free (issuer);
413 GNUNET_free (subject);
414 if (PABC_OK != status)
415 {
416 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
417 "Failed to read user context.\n");
418 pabc_free_public_parameters (ctx, &pp);
419 return GNUNET_SYSERR;
420 }
421
422 status = pabc_new_credential (ctx, pp, &cred);
423 if (status != PABC_OK)
424 {
425 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
426 "Failed to allocate credential.\n");
427 pabc_free_user_context (ctx, pp, &usr_ctx);
428 pabc_free_public_parameters (ctx, &pp);
429 return GNUNET_SYSERR;
430 }
431
432 status = pabc_decode_credential (ctx, pp, cred, credential->data);
433 if (status != PABC_OK)
434 {
435 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
436 "Failed to decode credential.\n");
437 pabc_free_credential (ctx, pp, &cred);
438 pabc_free_user_context (ctx, pp, &usr_ctx);
439 pabc_free_public_parameters (ctx, &pp);
440 return GNUNET_SYSERR;
441 }
442
443 status = pabc_new_proof (ctx, pp, &proof);
444 if (status != PABC_OK)
445 {
446 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
447 "Failed to allocate proof.\n");
448 pabc_free_credential (ctx, pp, &cred);
449 pabc_free_user_context (ctx, pp, &usr_ctx);
450 pabc_free_public_parameters (ctx, &pp);
451 return GNUNET_SYSERR;
452 }
453
454 // now we can parse the attributes to disclose and configure the proof
455 for (ale = attrs->list_head; NULL != ale; ale = ale->next)
456 {
457 status = pabc_set_disclosure_by_attribute_name (ctx, pp, proof,
458 ale->attribute->name,
459 PABC_DISCLOSED, cred);
460 if (status != PABC_OK)
461 {
462 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
463 "Failed to configure proof.\n");
464 pabc_free_credential (ctx, pp, &cred);
465 pabc_free_user_context (ctx, pp, &usr_ctx);
466 pabc_free_public_parameters (ctx, &pp);
467 return GNUNET_SYSERR;
468 }
469 }
470
471 // and finally -> sign the proof
472 status = pabc_gen_proof (ctx, usr_ctx, pp, proof, cred);
473 if (status != PABC_OK)
474 {
475 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
476 "Failed to sign proof.\n");
477 pabc_free_proof (ctx, pp, &proof);
478 pabc_free_credential (ctx, pp, &cred);
479 pabc_free_user_context (ctx, pp, &usr_ctx);
480 pabc_free_public_parameters (ctx, &pp);
481 return GNUNET_SYSERR;
482 }
483 // print the result
484 char *json = NULL;
485 char *ppid = NULL;
486 char *userid = NULL;
487 GNUNET_assert (PABC_OK == pabc_cred_get_userid_from_cred (credential->data,
488 &userid));
489 GNUNET_assert (PABC_OK == pabc_cred_get_ppid_from_cred (credential->data,
490 &ppid));
491 pabc_cred_encode_proof (ctx, pp, proof, userid, ppid, &json);
492 GNUNET_free (ppid);
493 GNUNET_free (userid);
494 if (PABC_OK != status)
495 {
496 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
497 "Failed to serialize proof.\n");
498 pabc_free_proof (ctx, pp, &proof);
499 pabc_free_credential (ctx, pp, &cred);
500 pabc_free_user_context (ctx, pp, &usr_ctx);
501 pabc_free_public_parameters (ctx, &pp);
502 return GNUNET_SYSERR;
503 }
504 char *json_enc;
505 GNUNET_STRINGS_base64_encode (json,
506 strlen (json) + 1,
507 &json_enc);
508 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
509 "Presentation: %s\n", json_enc);
510 // clean up
511 *presentation = GNUNET_RECLAIM_presentation_new (
512 GNUNET_RECLAIM_CREDENTIAL_TYPE_PABC,
513 json_enc,
514 strlen (json_enc) + 1);
515 GNUNET_free (json_enc);
516 PABC_FREE_NULL (json);
517 pabc_free_proof (ctx, pp, &proof);
518 pabc_free_credential (ctx, pp, &cred);
519 pabc_free_user_context (ctx, pp, &usr_ctx);
520 pabc_free_public_parameters (ctx, &pp);
521 return GNUNET_OK;
522}
523
524
525/**
526 * Entry point for the plugin.
527 *
528 * @param cls NULL
529 * @return the exported block API
530 */
531void *
532libgnunet_plugin_reclaim_credential_pabc_init (void *cls)
533{
534 struct GNUNET_RECLAIM_CredentialPluginFunctions *api;
535
536 api = GNUNET_new (struct GNUNET_RECLAIM_CredentialPluginFunctions);
537 api->value_to_string = &pabc_value_to_string;
538 api->string_to_value = &pabc_string_to_value;
539 api->typename_to_number = &pabc_typename_to_number;
540 api->number_to_typename = &pabc_number_to_typename;
541 api->get_attributes = &pabc_parse_attributes_c;
542 api->get_issuer = &pabc_get_issuer_c;
543 api->get_expiration = &pabc_get_expiration_c;
544 api->value_to_string_p = &pabc_value_to_string;
545 api->string_to_value_p = &pabc_string_to_value;
546 api->typename_to_number_p = &pabc_typename_to_number;
547 api->number_to_typename_p = &pabc_number_to_typename;
548 api->get_attributes_p = &pabc_parse_attributes_p;
549 api->get_issuer_p = &pabc_get_issuer_p;
550 api->get_expiration_p = &pabc_get_expiration_p;
551 api->create_presentation = &pabc_create_presentation;
552 return api;
553}
554
555
556/**
557 * Exit point from the plugin.
558 *
559 * @param cls the return value from #libgnunet_plugin_block_test_init()
560 * @return NULL
561 */
562void *
563libgnunet_plugin_reclaim_credential_pabc_done (void *cls)
564{
565 struct GNUNET_RECLAIM_CredentialPluginFunctions *api = cls;
566
567 GNUNET_free (api);
568 return NULL;
569}
570
571
572/* end of plugin_reclaim_credential_type_pabc.c */
diff --git a/src/plugin/regex/Makefile.am b/src/plugin/regex/Makefile.am
new file mode 100644
index 000000000..a39cfc927
--- /dev/null
+++ b/src/plugin/regex/Makefile.am
@@ -0,0 +1,40 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4if USE_COVERAGE
5 AM_CFLAGS = --coverage
6endif
7
8pkgcfgdir= $(pkgdatadir)/config.d/
9
10libexecdir= $(pkglibdir)/libexec/
11
12plugindir = $(libdir)/gnunet
13
14lib_LTLIBRARIES = \
15 libgnunetregexblock.la
16
17libgnunetregexblock_la_SOURCES = \
18 regex_block_lib.c regex_block_lib.h
19libgnunetregexblock_la_LIBADD = \
20 $(top_builddir)/src/lib/util/libgnunetutil.la \
21 $(XLIB) \
22 $(LTLIBINTL)
23libgnunetregexblock_la_LDFLAGS = \
24 $(GN_LIB_LDFLAGS) \
25 -version-info 1:0:0
26
27
28plugin_LTLIBRARIES = \
29 libgnunet_plugin_block_regex.la
30
31libgnunet_plugin_block_regex_la_SOURCES = \
32 plugin_block_regex.c
33libgnunet_plugin_block_regex_la_LIBADD = \
34 libgnunetregexblock.la \
35 $(top_builddir)/src/lib/block/libgnunetblock.la \
36 $(top_builddir)/src/lib/block/libgnunetblockgroup.la \
37 $(top_builddir)/src/lib/util/libgnunetutil.la
38libgnunet_plugin_block_regex_la_LDFLAGS = \
39 $(GN_LIBINTL) \
40 $(GN_PLUGIN_LDFLAGS)
diff --git a/src/plugin/regex/meson.build b/src/plugin/regex/meson.build
new file mode 100644
index 000000000..0e1b4b08d
--- /dev/null
+++ b/src/plugin/regex/meson.build
@@ -0,0 +1,23 @@
1libgnunetregexblock_src = ['regex_block_lib.c']
2
3libgnunetregexblock = library('gnunetregexblock',
4 libgnunetregexblock_src,
5 soversion: '1',
6 version: '1.0.0',
7 dependencies: libgnunetutil_dep,
8 include_directories: [incdir, configuration_inc],
9 install: true,
10 install_dir: get_option('libdir'))
11libgnunetregexblock_dep = declare_dependency(link_with : libgnunetregexblock)
12
13shared_module('gnunet_plugin_block_regex',
14 ['plugin_block_regex.c'],
15 dependencies: [libgnunetutil_dep,
16 libgnunetregexblock_dep,
17 libgnunetblock_dep,
18 libgnunetblockgroup_dep],
19 include_directories: [incdir, configuration_inc],
20 install:true,
21 install_dir: get_option('libdir')/'gnunet')
22
23
diff --git a/src/plugin/regex/plugin_block_regex.c b/src/plugin/regex/plugin_block_regex.c
new file mode 100644
index 000000000..5f23a32df
--- /dev/null
+++ b/src/plugin/regex/plugin_block_regex.c
@@ -0,0 +1,380 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2013 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file regex/plugin_block_regex.c
23 * @brief blocks used for regex storage and search
24 * @author Bartlomiej Polot
25 */
26#include "platform.h"
27#include "gnunet_block_plugin.h"
28#include "gnunet_block_group_lib.h"
29#include "block_regex.h"
30#include "regex_block_lib.h"
31#include "gnunet_signatures.h"
32
33
34/**
35 * Number of bits we set per entry in the bloomfilter.
36 * Do not change!
37 */
38#define BLOOMFILTER_K 16
39
40
41/**
42 * How big is the BF we use for REGEX blocks?
43 */
44#define REGEX_BF_SIZE 8
45
46
47/**
48 * Create a new block group.
49 *
50 * @param ctx block context in which the block group is created
51 * @param type type of the block for which we are creating the group
52 * @param raw_data optional serialized prior state of the group, NULL if unavailable/fresh
53 * @param raw_data_size number of bytes in @a raw_data, 0 if unavailable/fresh
54 * @param va variable arguments specific to @a type
55 * @return block group handle, NULL if block groups are not supported
56 * by this @a type of block (this is not an error)
57 */
58static struct GNUNET_BLOCK_Group *
59block_plugin_regex_create_group (void *cls,
60 enum GNUNET_BLOCK_Type type,
61 const void *raw_data,
62 size_t raw_data_size,
63 va_list va)
64{
65 unsigned int bf_size;
66 const char *guard;
67
68 guard = va_arg (va, const char *);
69 if (0 == strcmp (guard,
70 "seen-set-size"))
71 bf_size = GNUNET_BLOCK_GROUP_compute_bloomfilter_size (va_arg (va, unsigned
72 int),
73 BLOOMFILTER_K);
74 else if (0 == strcmp (guard,
75 "filter-size"))
76 bf_size = va_arg (va, unsigned int);
77 else
78 {
79 GNUNET_break (0);
80 bf_size = REGEX_BF_SIZE;
81 }
82 GNUNET_break (NULL == va_arg (va, const char *));
83 return GNUNET_BLOCK_GROUP_bf_create (cls,
84 bf_size,
85 BLOOMFILTER_K,
86 type,
87 raw_data,
88 raw_data_size);
89}
90
91
92/**
93 * Function called to validate a query.
94 *
95 * @param cls closure
96 * @param ctx block context
97 * @param type block type
98 * @param query original query (hash)
99 * @param xquery extrended query data (can be NULL, depending on type)
100 * @param xquery_size number of bytes in @a xquery
101 * @return #GNUNET_OK if the query is fine, #GNUNET_NO if not
102 */
103static enum GNUNET_GenericReturnValue
104block_plugin_regex_check_query (void *cls,
105 enum GNUNET_BLOCK_Type type,
106 const struct GNUNET_HashCode *query,
107 const void *xquery,
108 size_t xquery_size)
109{
110 switch (type)
111 {
112 case GNUNET_BLOCK_TYPE_REGEX:
113 if (0 != xquery_size)
114 {
115 const char *s;
116
117 s = (const char *) xquery;
118 if ('\0' != s[xquery_size - 1]) /* must be valid 0-terminated string */
119 {
120 GNUNET_break_op (0);
121 return GNUNET_NO;
122 }
123 }
124 return GNUNET_OK;
125 case GNUNET_BLOCK_TYPE_REGEX_ACCEPT:
126 if (0 != xquery_size)
127 {
128 GNUNET_break_op (0);
129 return GNUNET_NO;
130 }
131 return GNUNET_OK;
132 default:
133 GNUNET_break (0);
134 return GNUNET_SYSERR;
135 }
136}
137
138
139/**
140 * Function called to validate a block for storage.
141 *
142 * @param cls closure
143 * @param type block type
144 * @param block block data to validate
145 * @param block_size number of bytes in @a block
146 * @return #GNUNET_OK if the block is fine, #GNUNET_NO if not
147 */
148static enum GNUNET_GenericReturnValue
149block_plugin_regex_check_block (void *cls,
150 enum GNUNET_BLOCK_Type type,
151 const void *block,
152 size_t block_size)
153{
154 switch (type)
155 {
156 case GNUNET_BLOCK_TYPE_REGEX:
157 if (GNUNET_SYSERR ==
158 REGEX_BLOCK_check (block,
159 block_size,
160 NULL,
161 NULL))
162 return GNUNET_NO;
163 return GNUNET_OK;
164 case GNUNET_BLOCK_TYPE_REGEX_ACCEPT:
165 {
166 const struct RegexAcceptBlock *rba;
167
168 if (sizeof(struct RegexAcceptBlock) != block_size)
169 {
170 GNUNET_break_op (0);
171 return GNUNET_NO;
172 }
173 rba = block;
174 if (ntohl (rba->purpose.size) !=
175 sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose)
176 + sizeof(struct GNUNET_TIME_AbsoluteNBO)
177 + sizeof(struct GNUNET_HashCode))
178 {
179 GNUNET_break_op (0);
180 return GNUNET_NO;
181 }
182 if (GNUNET_TIME_absolute_is_past (GNUNET_TIME_absolute_ntoh (
183 rba->expiration_time)))
184 {
185 return GNUNET_NO;
186 }
187 if (GNUNET_OK !=
188 GNUNET_CRYPTO_eddsa_verify_ (GNUNET_SIGNATURE_PURPOSE_REGEX_ACCEPT,
189 &rba->purpose,
190 &rba->signature,
191 &rba->peer.public_key))
192 {
193 GNUNET_break_op (0);
194 return GNUNET_NO;
195 }
196 return GNUNET_OK;
197 }
198 default:
199 GNUNET_break (0);
200 return GNUNET_SYSERR;
201 }
202}
203
204
205/**
206 * Function called to validate a reply to a request. Note that it is assumed
207 * that the reply has already been matched to the key (and signatures checked)
208 * as it would be done with the GetKeyFunction and the
209 * BlockEvaluationFunction.
210 *
211 * @param cls closure
212 * @param type block type
213 * @param group which block group to use for evaluation
214 * @param query original query (hash)
215 * @param xquery extrended query data (can be NULL, depending on type)
216 * @param xquery_size number of bytes in @a xquery
217 * @param reply_block response to validate
218 * @param reply_block_size number of bytes in @a reply_block
219 * @return characterization of result
220 */
221static enum GNUNET_BLOCK_ReplyEvaluationResult
222block_plugin_regex_check_reply (
223 void *cls,
224 enum GNUNET_BLOCK_Type type,
225 struct GNUNET_BLOCK_Group *group,
226 const struct GNUNET_HashCode *query,
227 const void *xquery,
228 size_t xquery_size,
229 const void *reply_block,
230 size_t reply_block_size)
231{
232 struct GNUNET_HashCode chash;
233
234 switch (type)
235 {
236 case GNUNET_BLOCK_TYPE_REGEX:
237 if (0 != xquery_size)
238 {
239 const char *s;
240
241 s = (const char *) xquery;
242 GNUNET_assert ('\0' == s[xquery_size - 1]);
243 }
244 switch (REGEX_BLOCK_check (reply_block,
245 reply_block_size,
246 query,
247 xquery))
248 {
249 case GNUNET_SYSERR:
250 GNUNET_assert (0);
251 case GNUNET_NO:
252 /* xquery mismatch, can happen */
253 return GNUNET_BLOCK_REPLY_IRRELEVANT;
254 default:
255 break;
256 }
257 GNUNET_CRYPTO_hash (reply_block,
258 reply_block_size,
259 &chash);
260 if (GNUNET_YES ==
261 GNUNET_BLOCK_GROUP_bf_test_and_set (group,
262 &chash))
263 return GNUNET_BLOCK_REPLY_OK_DUPLICATE;
264 return GNUNET_BLOCK_REPLY_OK_MORE;
265 case GNUNET_BLOCK_TYPE_REGEX_ACCEPT:
266 {
267 const struct RegexAcceptBlock *rba;
268
269 GNUNET_assert (sizeof(struct RegexAcceptBlock) == reply_block_size);
270 rba = reply_block;
271 GNUNET_assert (ntohl (rba->purpose.size) ==
272 sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose)
273 + sizeof(struct GNUNET_TIME_AbsoluteNBO)
274 + sizeof(struct GNUNET_HashCode));
275 GNUNET_CRYPTO_hash (reply_block,
276 reply_block_size,
277 &chash);
278 if (GNUNET_YES ==
279 GNUNET_BLOCK_GROUP_bf_test_and_set (group,
280 &chash))
281 return GNUNET_BLOCK_REPLY_OK_DUPLICATE;
282 return GNUNET_BLOCK_REPLY_OK_MORE;
283 }
284 default:
285 GNUNET_break (0);
286 return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
287 }
288 return GNUNET_BLOCK_REPLY_OK_MORE;
289}
290
291
292/**
293 * Function called to obtain the key for a block.
294 *
295 * @param cls closure
296 * @param type block type
297 * @param block block to get the key for
298 * @param block_size number of bytes in @a block
299 * @param key set to the key (query) for the given block
300 * @return #GNUNET_OK on success, #GNUNET_SYSERR if type not supported,
301 * #GNUNET_NO if extracting a key from a block of this type does not work
302 */
303static enum GNUNET_GenericReturnValue
304block_plugin_regex_get_key (void *cls,
305 enum GNUNET_BLOCK_Type type,
306 const void *block,
307 size_t block_size,
308 struct GNUNET_HashCode *key)
309{
310 switch (type)
311 {
312 case GNUNET_BLOCK_TYPE_REGEX:
313 if (GNUNET_OK !=
314 REGEX_BLOCK_get_key (block,
315 block_size,
316 key))
317 {
318 GNUNET_break_op (0);
319 memset (key,
320 0,
321 sizeof (*key));
322 return GNUNET_OK;
323 }
324 return GNUNET_OK;
325 case GNUNET_BLOCK_TYPE_REGEX_ACCEPT:
326 if (sizeof(struct RegexAcceptBlock) != block_size)
327 {
328 GNUNET_break_op (0);
329 memset (key,
330 0,
331 sizeof (*key));
332 return GNUNET_OK;
333 }
334 *key = ((struct RegexAcceptBlock *) block)->key;
335 return GNUNET_OK;
336 default:
337 GNUNET_break (0);
338 return GNUNET_SYSERR;
339 }
340}
341
342
343/**
344 * Entry point for the plugin.
345 */
346void *
347libgnunet_plugin_block_regex_init (void *cls)
348{
349 static const enum GNUNET_BLOCK_Type types[] = {
350 GNUNET_BLOCK_TYPE_REGEX,
351 GNUNET_BLOCK_TYPE_REGEX_ACCEPT,
352 GNUNET_BLOCK_TYPE_ANY /* end of list */
353 };
354 struct GNUNET_BLOCK_PluginFunctions *api;
355
356 api = GNUNET_new (struct GNUNET_BLOCK_PluginFunctions);
357 api->get_key = &block_plugin_regex_get_key;
358 api->check_query = &block_plugin_regex_check_query;
359 api->check_block = &block_plugin_regex_check_block;
360 api->check_reply = &block_plugin_regex_check_reply;
361 api->create_group = &block_plugin_regex_create_group;
362 api->types = types;
363 return api;
364}
365
366
367/**
368 * Exit point from the plugin.
369 */
370void *
371libgnunet_plugin_block_regex_done (void *cls)
372{
373 struct GNUNET_BLOCK_PluginFunctions *api = cls;
374
375 GNUNET_free (api);
376 return NULL;
377}
378
379
380/* end of plugin_block_regex.c */
diff --git a/src/plugin/regex/regex_block_lib.c b/src/plugin/regex/regex_block_lib.c
new file mode 100644
index 000000000..048d6d743
--- /dev/null
+++ b/src/plugin/regex/regex_block_lib.c
@@ -0,0 +1,431 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2012,2013 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @author Bartlomiej Polot
22 * @file regex/regex_block_lib.c
23 * @brief functions for manipulating non-accept blocks stored for
24 * regex in the DHT
25 */
26#include "platform.h"
27#include "regex_block_lib.h"
28#include "gnunet_constants.h"
29
30#define LOG(kind, ...) GNUNET_log_from (kind, "regex-bck", __VA_ARGS__)
31
32GNUNET_NETWORK_STRUCT_BEGIN
33
34/**
35 * Information for each edge.
36 */
37struct EdgeInfo
38{
39 /**
40 * Index of the destination of this edge in the
41 * unique destinations array.
42 */
43 uint16_t destination_index GNUNET_PACKED;
44
45 /**
46 * Number of bytes the token for this edge takes in the
47 * token area.
48 */
49 uint16_t token_length GNUNET_PACKED;
50};
51
52
53/**
54 * @brief Block to announce a regex state.
55 */
56struct RegexBlock
57{
58 /**
59 * Length of the proof regex string.
60 */
61 uint16_t proof_len GNUNET_PACKED;
62
63 /**
64 * Is this state an accepting state?
65 */
66 int16_t is_accepting GNUNET_PACKED;
67
68 /**
69 * Number of edges parting from this state.
70 */
71 uint16_t num_edges GNUNET_PACKED;
72
73 /**
74 * Number of unique destinations reachable from this state.
75 */
76 uint16_t num_destinations GNUNET_PACKED;
77
78 /* followed by 'struct GNUNET_HashCode[num_destinations]' */
79
80 /* followed by 'struct EdgeInfo[edge_destination_indices]' */
81
82 /* followed by 'char proof[n_proof]', NOT 0-terminated */
83
84 /* followed by 'char tokens[num_edges][edge_info[k].token_length]';
85 essentially all of the tokens one after the other in the
86 order of the edges; tokens are NOT 0-terminated */
87};
88
89
90GNUNET_NETWORK_STRUCT_END
91
92
93/**
94 * Test if this block is marked as being an accept state.
95 *
96 * @param block block to test
97 * @param size number of bytes in block
98 * @return #GNUNET_YES if the block is accepting, #GNUNET_NO if not
99 */
100int
101GNUNET_BLOCK_is_accepting (const struct RegexBlock *block,
102 size_t size)
103{
104 if (size < sizeof(struct RegexBlock))
105 {
106 GNUNET_break_op (0);
107 return GNUNET_SYSERR;
108 }
109 return ntohs (block->is_accepting);
110}
111
112
113int
114REGEX_BLOCK_check_proof (const char *proof,
115 size_t proof_len,
116 const struct GNUNET_HashCode *key)
117{
118 struct GNUNET_HashCode key_check;
119
120 if ((NULL == proof) || (NULL == key))
121 {
122 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Proof check failed, was NULL.\n");
123 return GNUNET_NO;
124 }
125 GNUNET_CRYPTO_hash (proof, proof_len, &key_check);
126 return (0 ==
127 GNUNET_CRYPTO_hash_cmp (key, &key_check)) ? GNUNET_OK : GNUNET_NO;
128}
129
130
131/**
132 * Struct to keep track of the xquery while iterating all the edges in a block.
133 */
134struct CheckEdgeContext
135{
136 /**
137 * Xquery: string we are looking for.
138 */
139 const char *xquery;
140
141 /**
142 * Has any edge matched the xquery so far? (GNUNET_OK / GNUNET_NO)
143 */
144 int found;
145};
146
147
148/**
149 * Iterator over all edges in a block, checking for a presence of a given query.
150 *
151 * @param cls Closure, (xquery context).
152 * @param token Token that follows to next state.
153 * @param len Length of token.
154 * @param key Hash of next state.
155 *
156 * @return #GNUNET_YES, to keep iterating
157 */
158static int
159check_edge (void *cls,
160 const char *token,
161 size_t len,
162 const struct GNUNET_HashCode *key)
163{
164 struct CheckEdgeContext *ctx = cls;
165
166 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
167 "edge %.*s [%u]: %s\n",
168 (int) len,
169 token,
170 (unsigned int) len,
171 GNUNET_h2s (key));
172 if (NULL == ctx->xquery)
173 return GNUNET_YES;
174 if (strlen (ctx->xquery) < len)
175 return GNUNET_YES; /* too long */
176 if (0 == strncmp (ctx->xquery, token, len))
177 ctx->found = GNUNET_OK;
178 return GNUNET_YES; /* keep checking for malformed data! */
179}
180
181
182int
183REGEX_BLOCK_check (const struct RegexBlock *block,
184 size_t size,
185 const struct GNUNET_HashCode *query,
186 const char *xquery)
187{
188 struct GNUNET_HashCode key;
189 struct CheckEdgeContext ctx;
190 int res;
191
192 LOG (GNUNET_ERROR_TYPE_DEBUG,
193 "Block check\n");
194 if (GNUNET_OK !=
195 REGEX_BLOCK_get_key (block, size,
196 &key))
197 {
198 GNUNET_break_op (0);
199 return GNUNET_SYSERR;
200 }
201 if ((NULL != query) &&
202 (0 != GNUNET_memcmp (&key,
203 query)) )
204 {
205 GNUNET_break_op (0);
206 return GNUNET_SYSERR;
207 }
208 if ((GNUNET_YES == ntohs (block->is_accepting)) &&
209 ((NULL == xquery) || ('\0' == xquery[0])))
210 {
211 LOG (GNUNET_ERROR_TYPE_DEBUG,
212 " out! Is accepting: %u, xquery %p\n",
213 ntohs (block->is_accepting),
214 xquery);
215 return GNUNET_OK;
216 }
217 ctx.xquery = xquery;
218 ctx.found = GNUNET_NO;
219 res = REGEX_BLOCK_iterate (block, size, &check_edge, &ctx);
220 if (GNUNET_SYSERR == res)
221 return GNUNET_SYSERR;
222 if (NULL == xquery)
223 return GNUNET_YES;
224 LOG (GNUNET_ERROR_TYPE_DEBUG, "Result %d\n", ctx.found);
225 return ctx.found;
226}
227
228
229int
230REGEX_BLOCK_get_key (const struct RegexBlock *block,
231 size_t block_len,
232 struct GNUNET_HashCode *key)
233{
234 uint16_t len;
235 const struct GNUNET_HashCode *destinations;
236 const struct EdgeInfo *edges;
237 uint16_t num_destinations;
238 uint16_t num_edges;
239 size_t total;
240
241 if (block_len < sizeof(struct RegexBlock))
242 {
243 GNUNET_break_op (0);
244 return GNUNET_SYSERR;
245 }
246 num_destinations = ntohs (block->num_destinations);
247 num_edges = ntohs (block->num_edges);
248 len = ntohs (block->proof_len);
249 destinations = (const struct GNUNET_HashCode *) &block[1];
250 edges = (const struct EdgeInfo *) &destinations[num_destinations];
251 total = sizeof(struct RegexBlock) + num_destinations * sizeof(struct
252 GNUNET_HashCode)
253 + num_edges * sizeof(struct EdgeInfo) + len;
254 if (block_len < total)
255 {
256 GNUNET_break_op (0);
257 return GNUNET_SYSERR;
258 }
259 GNUNET_CRYPTO_hash (&edges[num_edges], len, key);
260 return GNUNET_OK;
261}
262
263
264int
265REGEX_BLOCK_iterate (const struct RegexBlock *block,
266 size_t size,
267 REGEX_INTERNAL_EgdeIterator iterator,
268 void *iter_cls)
269{
270 uint16_t len;
271 const struct GNUNET_HashCode *destinations;
272 const struct EdgeInfo *edges;
273 const char *aux;
274 uint16_t num_destinations;
275 uint16_t num_edges;
276 size_t total;
277 unsigned int n;
278 size_t off;
279
280 LOG (GNUNET_ERROR_TYPE_DEBUG, "Block iterate\n");
281 if (size < sizeof(struct RegexBlock))
282 {
283 GNUNET_break_op (0);
284 return GNUNET_SYSERR;
285 }
286 num_destinations = ntohs (block->num_destinations);
287 num_edges = ntohs (block->num_edges);
288 len = ntohs (block->proof_len);
289 destinations = (const struct GNUNET_HashCode *) &block[1];
290 edges = (const struct EdgeInfo *) &destinations[num_destinations];
291 aux = (const char *) &edges[num_edges];
292 total = sizeof(struct RegexBlock) + num_destinations * sizeof(struct
293 GNUNET_HashCode)
294 + num_edges * sizeof(struct EdgeInfo) + len;
295 if (size < total)
296 {
297 GNUNET_break_op (0);
298 return GNUNET_SYSERR;
299 }
300 for (n = 0; n < num_edges; n++)
301 total += ntohs (edges[n].token_length);
302 if (size != total)
303 {
304 fprintf (stderr, "Expected %u, got %u\n",
305 (unsigned int) size,
306 (unsigned int) total);
307 GNUNET_break_op (0);
308 return GNUNET_SYSERR;
309 }
310 off = len;
311 LOG (GNUNET_ERROR_TYPE_DEBUG,
312 "Start iterating block of size %lu, proof %u, off %lu edges %u\n",
313 (unsigned long) size, len, (unsigned long) off, n);
314 /* &aux[off] always points to our token */
315 for (n = 0; n < num_edges; n++)
316 {
317 LOG (GNUNET_ERROR_TYPE_DEBUG,
318 "Edge %u/%u, off %lu tokenlen %u (%.*s)\n",
319 n + 1, num_edges, (unsigned long) off,
320 ntohs (edges[n].token_length), ntohs (edges[n].token_length),
321 &aux[off]);
322 if (NULL != iterator)
323 if (GNUNET_NO == iterator (iter_cls,
324 &aux[off],
325 ntohs (edges[n].token_length),
326 &destinations[ntohs (
327 edges[n].destination_index)]))
328 return GNUNET_OK;
329 off += ntohs (edges[n].token_length);
330 }
331 return GNUNET_OK;
332}
333
334
335/**
336 * Construct a regex block to be stored in the DHT.
337 *
338 * @param proof proof string for the block
339 * @param num_edges number of edges in the block
340 * @param edges the edges of the block
341 * @param accepting is this an accepting state
342 * @param rsize set to the size of the returned block (OUT-only)
343 * @return the regex block, NULL on error
344 */
345struct RegexBlock *
346REGEX_BLOCK_create (const char *proof,
347 unsigned int num_edges,
348 const struct REGEX_BLOCK_Edge *edges,
349 int accepting,
350 size_t *rsize)
351{
352 struct RegexBlock *block;
353 struct GNUNET_HashCode destinations[1024]; /* 1024 = 64k/64 bytes/key == absolute MAX */
354 uint16_t destination_indices[num_edges];
355 struct GNUNET_HashCode *dests;
356 struct EdgeInfo *edgeinfos;
357 size_t off;
358 size_t len;
359 size_t total;
360 size_t slen;
361 unsigned int unique_destinations;
362 unsigned int j;
363 unsigned int i;
364 char *aux;
365
366 len = strlen (proof);
367 if (len > UINT16_MAX)
368 {
369 GNUNET_break (0);
370 return NULL;
371 }
372 unique_destinations = 0;
373 total = sizeof(struct RegexBlock) + len;
374 for (i = 0; i < num_edges; i++)
375 {
376 slen = strlen (edges[i].label);
377 if (slen > UINT16_MAX)
378 {
379 GNUNET_break (0);
380 return NULL;
381 }
382 total += slen;
383 for (j = 0; j < unique_destinations; j++)
384 if (0 == memcmp (&destinations[j],
385 &edges[i].destination,
386 sizeof(struct GNUNET_HashCode)))
387 break;
388 if (j >= 1024)
389 {
390 GNUNET_break (0);
391 return NULL;
392 }
393 destination_indices[i] = j;
394 if (j == unique_destinations)
395 destinations[unique_destinations++] = edges[i].destination;
396 }
397 total += num_edges * sizeof(struct EdgeInfo) + unique_destinations
398 * sizeof(struct GNUNET_HashCode);
399 if (total >= GNUNET_CONSTANTS_MAX_BLOCK_SIZE)
400 {
401 GNUNET_break (0);
402 return NULL;
403 }
404 block = GNUNET_malloc (total);
405 block->proof_len = htons (len);
406 block->is_accepting = htons (accepting);
407 block->num_edges = htons (num_edges);
408 block->num_destinations = htons (unique_destinations);
409 dests = (struct GNUNET_HashCode *) &block[1];
410 GNUNET_memcpy (dests, destinations, sizeof(struct GNUNET_HashCode)
411 * unique_destinations);
412 edgeinfos = (struct EdgeInfo *) &dests[unique_destinations];
413 aux = (char *) &edgeinfos[num_edges];
414 off = len;
415 GNUNET_memcpy (aux, proof, len);
416 for (i = 0; i < num_edges; i++)
417 {
418 slen = strlen (edges[i].label);
419 edgeinfos[i].token_length = htons ((uint16_t) slen);
420 edgeinfos[i].destination_index = htons (destination_indices[i]);
421 GNUNET_memcpy (&aux[off],
422 edges[i].label,
423 slen);
424 off += slen;
425 }
426 *rsize = total;
427 return block;
428}
429
430
431/* end of regex_block_lib.c */
diff --git a/src/plugin/regex/regex_block_lib.h b/src/plugin/regex/regex_block_lib.h
new file mode 100644
index 000000000..11029b9af
--- /dev/null
+++ b/src/plugin/regex/regex_block_lib.h
@@ -0,0 +1,193 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2012,2013 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @author Bartlomiej Polot
23 * @file regex/regex_block_lib.h
24 * @brief common function to manipulate blocks stored by regex in the DHT
25 */
26
27#ifndef REGEX_BLOCK_LIB_H_
28#define REGEX_BLOCK_LIB_H_
29
30#ifdef __cplusplus
31extern "C"
32{
33#if 0
34/* keep Emacsens' auto-indent happy */
35}
36#endif
37#endif
38
39#include "platform.h"
40#include "block_regex.h"
41
42
43/**
44 * Representation of a Regex node (and edges) in the DHT.
45 */
46struct RegexBlock;
47
48
49/**
50 * Edge representation.
51 */
52struct REGEX_BLOCK_Edge
53{
54 /**
55 * Label of the edge. FIXME: might want to not consume exactly
56 * multiples of 8 bits, need length!
57 */
58 const char *label;
59
60 /**
61 * Destination of the edge.
62 */
63 struct GNUNET_HashCode destination;
64};
65
66
67/**
68 * Check if the given 'proof' matches the given 'key'.
69 *
70 * @param proof partial regex of a state
71 * @param proof_len number of bytes in @a proof
72 * @param key hash of a state.
73 * @return #GNUNET_OK if the proof is valid for the given key.
74 */
75int
76REGEX_BLOCK_check_proof (const char *proof,
77 size_t proof_len,
78 const struct GNUNET_HashCode *key);
79
80
81/**
82 * Check if the regex block is well formed, including all edges.
83 *
84 * @param block The start of the block.
85 * @param size The size of the @a block.
86 * @param query the query for the @a block
87 * @param xquery String describing the edge we are looking for.
88 * Can be NULL in case this is a put block.
89 * @return #GNUNET_OK in case it's fine.
90 * #GNUNET_NO in case the xquery exists and is not found (IRRELEVANT).
91 * #GNUNET_SYSERR if the block is invalid.
92 */
93int
94REGEX_BLOCK_check (const struct RegexBlock *block,
95 size_t size,
96 const struct GNUNET_HashCode *query,
97 const char *xquery);
98
99
100/* FIXME: might want to use 'struct REGEX_BLOCK_Edge' here instead of 3 arguments! */
101
102/**
103 * Iterator over edges in a block.
104 *
105 * @param cls Closure.
106 * @param token Token that follows to next state.
107 * @param len Length of token.
108 * @param key Hash of next state.
109 * @return #GNUNET_YES if should keep iterating, #GNUNET_NO otherwise.
110 */
111typedef int
112(*REGEX_INTERNAL_EgdeIterator)(void *cls,
113 const char *token,
114 size_t len,
115 const struct GNUNET_HashCode *key);
116
117
118/**
119 * Iterate over all edges of a block of a regex state.
120 *
121 * @param block Block to iterate over.
122 * @param size Size of block.
123 * @param iterator Function to call on each edge in the block.
124 * @param iter_cls Closure for the @a iterator.
125 * @return #GNUNET_SYSERR if an error has been encountered.
126 * #GNUNET_OK if no error has been encountered.
127 * Note that if the iterator stops the iteration by returning
128 * #GNUNET_NO, the block will no longer be checked for further errors.
129 * The return value will be #GNUNET_OK meaning that no errors were
130 * found until the edge last notified to the iterator, but there might
131 * be errors in further edges.
132 */
133int
134REGEX_BLOCK_iterate (const struct RegexBlock *block,
135 size_t size,
136 REGEX_INTERNAL_EgdeIterator iterator,
137 void *iter_cls);
138
139
140/**
141 * Obtain the key that a particular block is to be stored under.
142 *
143 * @param block block to get the key from
144 * @param block_len number of bytes in @a block
145 * @param key where to store the key
146 * @return #GNUNET_OK on success, #GNUNET_SYSERR if the block is malformed
147 */
148int
149REGEX_BLOCK_get_key (const struct RegexBlock *block,
150 size_t block_len,
151 struct GNUNET_HashCode *key);
152
153
154/**
155 * Test if this block is marked as being an accept state.
156 *
157 * @param block block to test
158 * @param size number of bytes in block
159 * @return #GNUNET_YES if the block is accepting, #GNUNET_NO if not
160 */
161int
162GNUNET_BLOCK_is_accepting (const struct RegexBlock *block,
163 size_t block_len);
164
165
166/**
167 * Construct a regex block to be stored in the DHT.
168 *
169 * @param proof proof string for the block
170 * @param num_edges number of edges in the block
171 * @param edges the edges of the block
172 * @param accepting is this an accepting state
173 * @param rsize set to the size of the returned block (OUT-only)
174 * @return the regex block, NULL on error
175 */
176struct RegexBlock *
177REGEX_BLOCK_create (const char *proof,
178 unsigned int num_edges,
179 const struct REGEX_BLOCK_Edge *edges,
180 int accepting,
181 size_t *rsize);
182
183
184#if 0 /* keep Emacsens' auto-indent happy */
185{
186#endif
187#ifdef __cplusplus
188}
189#endif
190
191/* ifndef REGEX_BLOCK_LIB_H */
192#endif
193/* end of regex_block_lib.h */
diff --git a/src/plugin/revocation/Makefile.am b/src/plugin/revocation/Makefile.am
new file mode 100644
index 000000000..99373f8d7
--- /dev/null
+++ b/src/plugin/revocation/Makefile.am
@@ -0,0 +1,27 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4plugindir = $(libdir)/gnunet
5
6if USE_COVERAGE
7 AM_CFLAGS = --coverage -O0
8 XLIB = -lgcov
9endif
10
11pkgcfgdir= $(pkgdatadir)/config.d/
12
13libexecdir= $(pkglibdir)/libexec/
14
15plugin_LTLIBRARIES = \
16 libgnunet_plugin_block_revocation.la
17
18libgnunet_plugin_block_revocation_la_SOURCES = \
19 plugin_block_revocation.c
20libgnunet_plugin_block_revocation_la_LIBADD = \
21 $(top_builddir)/src/lib/gnsrecord/libgnunetgnsrecord.la \
22 $(top_builddir)/src/lib/block/libgnunetblockgroup.la \
23 $(top_builddir)/src/lib/block/libgnunetblock.la \
24 $(top_builddir)/src/lib/util/libgnunetutil.la \
25 $(LTLIBINTL)
26libgnunet_plugin_block_revocation_la_LDFLAGS = \
27 $(GN_PLUGIN_LDFLAGS)
diff --git a/src/plugin/revocation/meson.build b/src/plugin/revocation/meson.build
new file mode 100644
index 000000000..0ac8df28d
--- /dev/null
+++ b/src/plugin/revocation/meson.build
@@ -0,0 +1,9 @@
1shared_module('gnunet_plugin_block_revocation',
2 ['plugin_block_revocation.c'],
3 dependencies: [libgnunetutil_dep,
4 libgnunetgnsrecord_dep,
5 libgnunetblock_dep],
6 include_directories: [incdir, configuration_inc],
7 install: true,
8 install_dir: get_option('libdir')/'gnunet')
9
diff --git a/src/plugin/revocation/plugin_block_revocation.c b/src/plugin/revocation/plugin_block_revocation.c
new file mode 100644
index 000000000..c4332184e
--- /dev/null
+++ b/src/plugin/revocation/plugin_block_revocation.c
@@ -0,0 +1,309 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file block/plugin_block_revocation.c
23 * @brief revocation for a block plugin
24 * @author Christian Grothoff
25 */
26
27#include "platform.h"
28#include "gnunet_signatures.h"
29#include "gnunet_block_plugin.h"
30#include "gnunet_gnsrecord_lib.h"
31#include "gnunet_block_group_lib.h"
32#include "../../service/revocation/revocation.h"
33#include "gnunet_revocation_service.h"
34
35#define DEBUG_REVOCATION GNUNET_EXTRA_LOGGING
36
37/**
38 * Context used inside the plugin.
39 */
40struct InternalContext
41{
42 unsigned int matching_bits;
43 struct GNUNET_TIME_Relative epoch_duration;
44};
45
46
47/**
48 * Function called to validate a query.
49 *
50 * @param cls closure
51 * @param ctx block context
52 * @param type block type
53 * @param query original query (hash)
54 * @param xquery extrended query data (can be NULL, depending on type)
55 * @param xquery_size number of bytes in @a xquery
56 * @return #GNUNET_OK if the query is fine, #GNUNET_NO if not
57 */
58static enum GNUNET_GenericReturnValue
59block_plugin_revocation_check_query (void *cls,
60 enum GNUNET_BLOCK_Type type,
61 const struct GNUNET_HashCode *query,
62 const void *xquery,
63 size_t xquery_size)
64{
65 (void) cls;
66 (void) query;
67 (void) xquery;
68 if (GNUNET_BLOCK_TYPE_REVOCATION != type)
69 {
70 GNUNET_break (0);
71 return GNUNET_SYSERR;
72 }
73 if (0 != xquery_size)
74 return GNUNET_NO;
75 return GNUNET_OK;
76}
77
78
79/**
80 * Function called to validate a block for storage.
81 *
82 * @param cls closure
83 * @param type block type
84 * @param block block data to validate
85 * @param block_size number of bytes in @a block
86 * @return #GNUNET_OK if the block is fine, #GNUNET_NO if not
87 */
88static enum GNUNET_GenericReturnValue
89block_plugin_revocation_check_block (void *cls,
90 enum GNUNET_BLOCK_Type type,
91 const void *block,
92 size_t block_size)
93{
94 struct InternalContext *ic = cls;
95 const struct RevokeMessage *rm = block;
96 const struct GNUNET_GNSRECORD_PowP *pow
97 = (const struct GNUNET_GNSRECORD_PowP *) &rm[1];
98 struct GNUNET_CRYPTO_PublicKey pk;
99 size_t pklen;
100 size_t left;
101
102 if (GNUNET_BLOCK_TYPE_REVOCATION != type)
103 {
104 GNUNET_break (0);
105 return GNUNET_SYSERR;
106 }
107 if (block_size < sizeof(*rm) + sizeof(*pow))
108 {
109 GNUNET_break_op (0);
110 return GNUNET_NO;
111 }
112 if (block_size != sizeof(*rm) + ntohl (rm->pow_size))
113 {
114 GNUNET_break_op (0);
115 return GNUNET_NO;
116 }
117 left = block_size - sizeof (*rm) - sizeof (*pow);
118 if (GNUNET_SYSERR ==
119 GNUNET_CRYPTO_read_public_key_from_buffer (&pow[1],
120 left,
121 &pk,
122 &pklen))
123 {
124 GNUNET_break_op (0);
125 return GNUNET_NO;
126 }
127 if (0 == pklen)
128 {
129 GNUNET_break_op (0);
130 return GNUNET_NO;
131 }
132 if (GNUNET_YES !=
133 GNUNET_GNSRECORD_check_pow (pow,
134 ic->matching_bits,
135 ic->epoch_duration))
136 {
137 GNUNET_break_op (0);
138 return GNUNET_NO;
139 }
140 return GNUNET_OK;
141}
142
143
144/**
145 * Function called to validate a reply to a request. Note that it is assumed
146 * that the reply has already been matched to the key (and signatures checked)
147 * as it would be done with the GetKeyFunction and the
148 * BlockEvaluationFunction.
149 *
150 * @param cls closure
151 * @param type block type
152 * @param group which block group to use for evaluation
153 * @param query original query (hash)
154 * @param xquery extrended query data (can be NULL, depending on type)
155 * @param xquery_size number of bytes in @a xquery
156 * @param reply_block response to validate
157 * @param reply_block_size number of bytes in @a reply_block
158 * @return characterization of result
159 */
160static enum GNUNET_BLOCK_ReplyEvaluationResult
161block_plugin_revocation_check_reply (
162 void *cls,
163 enum GNUNET_BLOCK_Type type,
164 struct GNUNET_BLOCK_Group *group,
165 const struct GNUNET_HashCode *query,
166 const void *xquery,
167 size_t xquery_size,
168 const void *reply_block,
169 size_t reply_block_size)
170{
171 (void) cls;
172 (void) group;
173 (void) query;
174 (void) xquery;
175 (void) xquery_size;
176 (void) reply_block;
177 (void) reply_block_size;
178 if (GNUNET_BLOCK_TYPE_REVOCATION != type)
179 {
180 GNUNET_break (0);
181 return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
182 }
183 return GNUNET_BLOCK_REPLY_OK_LAST;
184}
185
186
187/**
188 * Function called to obtain the key for a block.
189 *
190 * @param cls closure
191 * @param type block type
192 * @param block block to get the key for
193 * @param block_size number of bytes in block
194 * @param key set to the key (query) for the given block
195 * @return #GNUNET_OK on success, #GNUNET_SYSERR if type not supported
196 * (or if extracting a key from a block of this type does not work)
197 */
198static enum GNUNET_GenericReturnValue
199block_plugin_revocation_get_key (void *cls,
200 enum GNUNET_BLOCK_Type type,
201 const void *block,
202 size_t block_size,
203 struct GNUNET_HashCode *key)
204{
205 const struct RevokeMessage *rm = block;
206 const struct GNUNET_GNSRECORD_PowP *pow
207 = (const struct GNUNET_GNSRECORD_PowP *) &rm[1];
208 struct GNUNET_CRYPTO_PublicKey pk;
209 size_t pklen;
210 size_t left;
211
212 if (GNUNET_BLOCK_TYPE_REVOCATION != type)
213 {
214 GNUNET_break (0);
215 return GNUNET_SYSERR;
216 }
217 if (block_size < sizeof(*rm) + sizeof(*pow))
218 {
219 GNUNET_break_op (0);
220 return GNUNET_NO;
221 }
222 if (block_size != sizeof(*rm) + ntohl (rm->pow_size))
223 {
224 GNUNET_break_op (0);
225 return GNUNET_NO;
226 }
227 left = block_size - sizeof (*rm) - sizeof (*pow);
228 if (GNUNET_SYSERR == GNUNET_CRYPTO_read_public_key_from_buffer (&pow[1],
229 left,
230 &pk,
231 &pklen))
232 {
233 GNUNET_break_op (0);
234 return GNUNET_NO;
235 }
236 if (0 == pklen)
237 {
238 GNUNET_break_op (0);
239 return GNUNET_NO;
240 }
241 GNUNET_CRYPTO_hash (&pow[1],
242 pklen,
243 key);
244 return GNUNET_OK;
245}
246
247
248/**
249 * Entry point for the plugin.
250 *
251 * @param cls the configuration to use
252 */
253void *
254libgnunet_plugin_block_revocation_init (void *cls)
255{
256 static const enum GNUNET_BLOCK_Type types[] = {
257 GNUNET_BLOCK_TYPE_REVOCATION,
258 GNUNET_BLOCK_TYPE_ANY /* end of list */
259 };
260 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
261 struct GNUNET_BLOCK_PluginFunctions *api;
262 struct InternalContext *ic;
263 unsigned long long matching_bits;
264 struct GNUNET_TIME_Relative epoch_duration;
265
266 if (GNUNET_OK !=
267 GNUNET_CONFIGURATION_get_value_number (cfg,
268 "REVOCATION",
269 "WORKBITS",
270 &matching_bits))
271 return NULL;
272 if (GNUNET_OK !=
273 GNUNET_CONFIGURATION_get_value_time (cfg,
274 "REVOCATION",
275 "EPOCH_DURATION",
276 &epoch_duration))
277 return NULL;
278
279 api = GNUNET_new (struct GNUNET_BLOCK_PluginFunctions);
280 api->get_key = &block_plugin_revocation_get_key;
281 api->check_query = &block_plugin_revocation_check_query;
282 api->check_block = &block_plugin_revocation_check_block;
283 api->check_reply = &block_plugin_revocation_check_reply;
284 api->create_group = NULL;
285 api->types = types;
286 ic = GNUNET_new (struct InternalContext);
287 ic->matching_bits = (unsigned int) matching_bits;
288 ic->epoch_duration = epoch_duration;
289 api->cls = ic;
290 return api;
291}
292
293
294/**
295 * Exit point from the plugin.
296 */
297void *
298libgnunet_plugin_block_revocation_done (void *cls)
299{
300 struct GNUNET_BLOCK_PluginFunctions *api = cls;
301 struct InternalContext *ic = api->cls;
302
303 GNUNET_free (ic);
304 GNUNET_free (api);
305 return NULL;
306}
307
308
309/* end of plugin_block_revocation.c */
diff --git a/src/plugin/seti/Makefile.am b/src/plugin/seti/Makefile.am
new file mode 100644
index 000000000..e6f579a65
--- /dev/null
+++ b/src/plugin/seti/Makefile.am
@@ -0,0 +1,25 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4pkgcfgdir= $(pkgdatadir)/config.d/
5
6libexecdir= $(pkglibdir)/libexec/
7
8plugindir = $(libdir)/gnunet
9
10if USE_COVERAGE
11 AM_CFLAGS = -fprofile-arcs -ftest-coverage
12endif
13
14plugin_LTLIBRARIES = \
15 libgnunet_plugin_block_seti_test.la
16
17libgnunet_plugin_block_seti_test_la_SOURCES = \
18 plugin_block_seti_test.c
19libgnunet_plugin_block_seti_test_la_LIBADD = \
20 $(top_builddir)/src/lib/block/libgnunetblock.la \
21 $(top_builddir)/src/lib/block/libgnunetblockgroup.la \
22 $(top_builddir)/src/lib/util/libgnunetutil.la \
23 $(LTLIBINTL)
24libgnunet_plugin_block_seti_test_la_LDFLAGS = \
25 $(GN_PLUGIN_LDFLAGS)
diff --git a/src/plugin/seti/meson.build b/src/plugin/seti/meson.build
new file mode 100644
index 000000000..880936164
--- /dev/null
+++ b/src/plugin/seti/meson.build
@@ -0,0 +1,7 @@
1shared_module('gnunet_plugin_block_seti_test',
2 ['plugin_block_seti_test.c'],
3 dependencies: libgnunetutil_dep,
4 include_directories: [incdir, configuration_inc],
5 install:true,
6 install_dir: get_option('libdir')/'gnunet')
7
diff --git a/src/plugin/seti/plugin_block_seti_test.c b/src/plugin/seti/plugin_block_seti_test.c
new file mode 100644
index 000000000..5b9196cef
--- /dev/null
+++ b/src/plugin/seti/plugin_block_seti_test.c
@@ -0,0 +1,197 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file seti/plugin_block_seti_test.c
23 * @brief set test block, recognizes elements with non-zero first byte as invalid
24 * @author Christian Grothoff
25 */
26
27#include "platform.h"
28#include "gnunet_block_plugin.h"
29#include "gnunet_block_group_lib.h"
30
31
32/**
33 * Function called to validate a query.
34 *
35 * @param cls closure
36 * @param ctx block context
37 * @param type block type
38 * @param query original query (hash)
39 * @param xquery extrended query data (can be NULL, depending on type)
40 * @param xquery_size number of bytes in @a xquery
41 * @return #GNUNET_OK if the query is fine, #GNUNET_NO if not
42 */
43static enum GNUNET_GenericReturnValue
44block_plugin_seti_test_check_query (void *cls,
45 enum GNUNET_BLOCK_Type type,
46 const struct GNUNET_HashCode *query,
47 const void *xquery,
48 size_t xquery_size)
49{
50 if (GNUNET_BLOCK_TYPE_SETI_TEST != type)
51 {
52 GNUNET_break (0);
53 return GNUNET_SYSERR;
54 }
55 if (0 != xquery_size)
56 {
57 GNUNET_break_op (0);
58 return GNUNET_NO;
59 }
60 return GNUNET_OK;
61}
62
63
64/**
65 * Function called to validate a block for storage.
66 *
67 * @param cls closure
68 * @param type block type
69 * @param block block data to validate
70 * @param block_size number of bytes in @a block
71 * @return #GNUNET_OK if the block is fine, #GNUNET_NO if not
72 */
73static enum GNUNET_GenericReturnValue
74block_plugin_seti_test_check_block (void *cls,
75 enum GNUNET_BLOCK_Type type,
76 const void *block,
77 size_t block_size)
78{
79 (void) cls;
80 if (GNUNET_BLOCK_TYPE_SETI_TEST != type)
81 {
82 GNUNET_break (0);
83 return GNUNET_SYSERR;
84 }
85 if ((NULL == block) ||
86 (0 == block_size) ||
87 (0 != ((char *) block)[0]))
88 return GNUNET_SYSERR;
89 return GNUNET_OK;
90}
91
92
93/**
94 * Function called to validate a reply to a request. Note that it is assumed
95 * that the reply has already been matched to the key (and signatures checked)
96 * as it would be done with the GetKeyFunction and the
97 * BlockEvaluationFunction.
98 *
99 * @param cls closure
100 * @param type block type
101 * @param group which block group to use for evaluation
102 * @param query original query (hash)
103 * @param xquery extrended query data (can be NULL, depending on type)
104 * @param xquery_size number of bytes in @a xquery
105 * @param reply_block response to validate
106 * @param reply_block_size number of bytes in @a reply_block
107 * @return characterization of result
108 */
109static enum GNUNET_BLOCK_ReplyEvaluationResult
110block_plugin_seti_test_check_reply (void *cls,
111 enum GNUNET_BLOCK_Type type,
112 struct GNUNET_BLOCK_Group *group,
113 const struct GNUNET_HashCode *query,
114 const void *xquery,
115 size_t xquery_size,
116 const void *reply_block,
117 size_t reply_block_size)
118{
119 (void) cls;
120 (void) xquery;
121 (void) xquery_size;
122 if (GNUNET_BLOCK_TYPE_SETI_TEST != type)
123 {
124 GNUNET_break (0);
125 return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
126 }
127 if ( (NULL == reply_block) ||
128 (0 == reply_block_size) ||
129 (0 != ((char *) reply_block)[0]) )
130 GNUNET_assert (0);
131 return GNUNET_BLOCK_REPLY_OK_MORE;
132}
133
134
135/**
136 * Function called to obtain the key for a block.
137 *
138 * @param cls closure
139 * @param type block type
140 * @param block block to get the key for
141 * @param block_size number of bytes in block
142 * @param key set to the key (query) for the given block
143 * @return #GNUNET_OK on success, #GNUNET_SYSERR if type not supported
144 * (or if extracting a key from a block of this type does not work)
145 */
146static enum GNUNET_GenericReturnValue
147block_plugin_seti_test_get_key (void *cls,
148 enum GNUNET_BLOCK_Type type,
149 const void *block,
150 size_t block_size,
151 struct GNUNET_HashCode *key)
152{
153 if (GNUNET_BLOCK_TYPE_SETI_TEST != type)
154 {
155 GNUNET_break (0);
156 return GNUNET_SYSERR;
157 }
158 return GNUNET_NO;
159}
160
161
162/**
163 * Entry point for the plugin.
164 */
165void *
166libgnunet_plugin_block_seti_test_init (void *cls)
167{
168 static enum GNUNET_BLOCK_Type types[] = {
169 GNUNET_BLOCK_TYPE_SETI_TEST,
170 GNUNET_BLOCK_TYPE_ANY /* end of list */
171 };
172 struct GNUNET_BLOCK_PluginFunctions *api;
173
174 api = GNUNET_new (struct GNUNET_BLOCK_PluginFunctions);
175 api->get_key = &block_plugin_seti_test_get_key;
176 api->check_query = &block_plugin_seti_test_check_query;
177 api->check_block = &block_plugin_seti_test_check_block;
178 api->check_reply = &block_plugin_seti_test_check_reply;
179 api->types = types;
180 return api;
181}
182
183
184/**
185 * Exit point from the plugin.
186 */
187void *
188libgnunet_plugin_block_seti_test_done (void *cls)
189{
190 struct GNUNET_BLOCK_PluginFunctions *api = cls;
191
192 GNUNET_free (api);
193 return NULL;
194}
195
196
197/* end of plugin_block_seti_test.c */
diff --git a/src/plugin/setu/Makefile.am b/src/plugin/setu/Makefile.am
new file mode 100644
index 000000000..90a14cce0
--- /dev/null
+++ b/src/plugin/setu/Makefile.am
@@ -0,0 +1,21 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4pkgcfgdir= $(pkgdatadir)/config.d/
5
6libexecdir= $(pkglibdir)/libexec/
7
8plugindir = $(libdir)/gnunet
9
10plugin_LTLIBRARIES = \
11 libgnunet_plugin_block_setu_test.la
12
13libgnunet_plugin_block_setu_test_la_SOURCES = \
14 plugin_block_setu_test.c
15libgnunet_plugin_block_setu_test_la_LIBADD = \
16 $(top_builddir)/src/lib/block/libgnunetblock.la \
17 $(top_builddir)/src/lib/block/libgnunetblockgroup.la \
18 $(top_builddir)/src/lib/util/libgnunetutil.la \
19 $(LTLIBINTL)
20libgnunet_plugin_block_setu_test_la_LDFLAGS = \
21 $(GN_PLUGIN_LDFLAGS)
diff --git a/src/plugin/setu/meson.build b/src/plugin/setu/meson.build
new file mode 100644
index 000000000..e22738ab6
--- /dev/null
+++ b/src/plugin/setu/meson.build
@@ -0,0 +1,7 @@
1shared_module('gnunet_plugin_block_setu_test',
2 ['plugin_block_setu_test.c'],
3 dependencies: libgnunetutil_dep,
4 include_directories: [incdir, configuration_inc],
5 install:true,
6 install_dir: get_option('libdir')/'gnunet')
7
diff --git a/src/plugin/setu/plugin_block_setu_test.c b/src/plugin/setu/plugin_block_setu_test.c
new file mode 100644
index 000000000..178ad3314
--- /dev/null
+++ b/src/plugin/setu/plugin_block_setu_test.c
@@ -0,0 +1,195 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file setu/plugin_block_setu_test.c
23 * @brief set test block, recognizes elements with non-zero first byte as invalid
24 * @author Christian Grothoff
25 */
26#include "platform.h"
27#include "gnunet_block_plugin.h"
28#include "gnunet_block_group_lib.h"
29
30
31/**
32 * Function called to validate a query.
33 *
34 * @param cls closure
35 * @param ctx block context
36 * @param type block type
37 * @param query original query (hash)
38 * @param xquery extrended query data (can be NULL, depending on type)
39 * @param xquery_size number of bytes in @a xquery
40 * @return #GNUNET_OK if the query is fine, #GNUNET_NO if not
41 */
42static enum GNUNET_GenericReturnValue
43block_plugin_setu_test_check_query (void *cls,
44 enum GNUNET_BLOCK_Type type,
45 const struct GNUNET_HashCode *query,
46 const void *xquery,
47 size_t xquery_size)
48{
49 if (GNUNET_BLOCK_TYPE_SETU_TEST != type)
50 {
51 GNUNET_break (0);
52 return GNUNET_SYSERR;
53 }
54 if (0 != xquery_size)
55 {
56 GNUNET_break_op (0);
57 return GNUNET_NO;
58 }
59 return GNUNET_OK;
60}
61
62
63/**
64 * Function called to validate a block for storage.
65 *
66 * @param cls closure
67 * @param type block type
68 * @param block block data to validate
69 * @param block_size number of bytes in @a block
70 * @return #GNUNET_OK if the block is fine, #GNUNET_NO if not
71 */
72static enum GNUNET_GenericReturnValue
73block_plugin_setu_test_check_block (void *cls,
74 enum GNUNET_BLOCK_Type type,
75 const void *block,
76 size_t block_size)
77{
78 if (GNUNET_BLOCK_TYPE_SETU_TEST != type)
79 {
80 GNUNET_break (0);
81 return GNUNET_SYSERR;
82 }
83 if ( (NULL == block) ||
84 (0 == block_size) ||
85 (0 != ((char *) block)[0]) )
86 return GNUNET_NO;
87 return GNUNET_OK;
88}
89
90
91/**
92 * Function called to validate a reply to a request. Note that it is assumed
93 * that the reply has already been matched to the key (and signatures checked)
94 * as it would be done with the GetKeyFunction and the
95 * BlockEvaluationFunction.
96 *
97 * @param cls closure
98 * @param type block type
99 * @param group which block group to use for evaluation
100 * @param query original query (hash)
101 * @param xquery extrended query data (can be NULL, depending on type)
102 * @param xquery_size number of bytes in @a xquery
103 * @param reply_block response to validate
104 * @param reply_block_size number of bytes in @a reply_block
105 * @return characterization of result
106 */
107static enum GNUNET_BLOCK_ReplyEvaluationResult
108block_plugin_setu_test_check_reply (void *cls,
109 enum GNUNET_BLOCK_Type type,
110 struct GNUNET_BLOCK_Group *group,
111 const struct GNUNET_HashCode *query,
112 const void *xquery,
113 size_t xquery_size,
114 const void *reply_block,
115 size_t reply_block_size)
116{
117 (void) cls;
118 (void) xquery;
119 (void) xquery_size;
120 if (GNUNET_BLOCK_TYPE_SETU_TEST != type)
121 {
122 GNUNET_break (0);
123 return GNUNET_BLOCK_REPLY_TYPE_NOT_SUPPORTED;
124 }
125 if ( (NULL == reply_block) ||
126 (0 == reply_block_size) ||
127 (0 != ((char *) reply_block)[0]) )
128 GNUNET_assert (0);
129 return GNUNET_BLOCK_REPLY_OK_MORE;
130}
131
132
133/**
134 * Function called to obtain the key for a block.
135 *
136 * @param cls closure
137 * @param type block type
138 * @param block block to get the key for
139 * @param block_size number of bytes in block
140 * @param key set to the key (query) for the given block
141 * @return #GNUNET_OK on success, #GNUNET_SYSERR if type not supported
142 * (or if extracting a key from a block of this type does not work)
143 */
144static enum GNUNET_GenericReturnValue
145block_plugin_setu_test_get_key (void *cls,
146 enum GNUNET_BLOCK_Type type,
147 const void *block,
148 size_t block_size,
149 struct GNUNET_HashCode *key)
150{
151 if (GNUNET_BLOCK_TYPE_SETU_TEST != type)
152 {
153 GNUNET_break (0);
154 return GNUNET_SYSERR;
155 }
156 return GNUNET_NO;
157}
158
159
160/**
161 * Entry point for the plugin.
162 */
163void *
164libgnunet_plugin_block_setu_test_init (void *cls)
165{
166 static enum GNUNET_BLOCK_Type types[] = {
167 GNUNET_BLOCK_TYPE_SETU_TEST,
168 GNUNET_BLOCK_TYPE_ANY /* end of list */
169 };
170 struct GNUNET_BLOCK_PluginFunctions *api;
171
172 api = GNUNET_new (struct GNUNET_BLOCK_PluginFunctions);
173 api->get_key = &block_plugin_setu_test_get_key;
174 api->check_query = &block_plugin_setu_test_check_query;
175 api->check_block = &block_plugin_setu_test_check_block;
176 api->check_reply = &block_plugin_setu_test_check_reply;
177 api->types = types;
178 return api;
179}
180
181
182/**
183 * Exit point from the plugin.
184 */
185void *
186libgnunet_plugin_block_setu_test_done (void *cls)
187{
188 struct GNUNET_BLOCK_PluginFunctions *api = cls;
189
190 GNUNET_free (api);
191 return NULL;
192}
193
194
195/* end of plugin_block_setu_test.c */