finish backchannel message delivery
[oweals/gnunet.git] / src / namecache / plugin_namecache_flat.c
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 #include "namecache.h"
32
33 /**
34  * Context for all functions in this plugin.
35  */
36 struct Plugin
37 {
38
39   const struct GNUNET_CONFIGURATION_Handle *cfg;
40
41   /**
42    * Database filename.
43    */
44   char *fn;
45
46   /**
47    * HashMap
48    */
49   struct GNUNET_CONTAINER_MultiHashMap *hm;
50
51 };
52
53 struct FlatFileEntry
54 {
55   /**
56    * Block
57    */
58   struct GNUNET_GNSRECORD_Block *block;
59
60   /**
61    * query
62    */
63   struct GNUNET_HashCode query;
64
65 };
66
67 /**
68  * Initialize the database connections and associated
69  * data structures (create tables and indices
70  * as needed as well).
71  *
72  * @param plugin the plugin context (state for this module)
73  * @return #GNUNET_OK on success
74  */
75 static int
76 database_setup (struct Plugin *plugin)
77 {
78   char *afsdir;
79   char* block_buffer;
80   char* buffer;
81   char* line;
82   char* query;
83   char* block;
84   size_t size;
85   struct FlatFileEntry *entry;
86   struct GNUNET_DISK_FileHandle *fh;
87
88   if (GNUNET_OK !=
89       GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
90                                                "namecache-flat",
91                                                "FILENAME",
92                                                &afsdir))
93   {
94     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
95                                "namecache-flat", "FILENAME");
96     return GNUNET_SYSERR;
97   }
98   if (GNUNET_OK != GNUNET_DISK_file_test (afsdir))
99   {
100     if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (afsdir))
101     {
102       GNUNET_break (0);
103       GNUNET_free (afsdir);
104       return GNUNET_SYSERR;
105     }
106   }
107   /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
108   plugin->fn = afsdir;
109
110   /* Load data from file into hashmap */
111   plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
112                                                      GNUNET_NO);
113   fh = GNUNET_DISK_file_open (afsdir,
114                               GNUNET_DISK_OPEN_CREATE |
115                               GNUNET_DISK_OPEN_READWRITE,
116                               GNUNET_DISK_PERM_USER_WRITE |
117                               GNUNET_DISK_PERM_USER_READ);
118   if (NULL == fh)
119   {
120     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
121                 _("Unable to initialize file: %s.\n"),
122                 afsdir);
123     return GNUNET_SYSERR;
124   }
125
126   if (GNUNET_SYSERR == GNUNET_DISK_file_size (afsdir,
127                                               &size,
128                                               GNUNET_YES,
129                                               GNUNET_YES))
130   {
131     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
132                 _("Unable to get filesize: %s.\n"),
133                 afsdir);
134     GNUNET_DISK_file_close (fh);
135     return GNUNET_SYSERR;
136   }
137
138   if (0 == size)
139   {
140     GNUNET_DISK_file_close (fh);
141     return GNUNET_OK;
142   }
143
144   buffer = GNUNET_malloc (size + 1);
145
146   if (GNUNET_SYSERR == GNUNET_DISK_file_read (fh,
147                                               buffer,
148                                               size))
149   {
150     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
151                 _("Unable to read file: %s.\n"),
152                 afsdir);
153     GNUNET_free (buffer);
154     GNUNET_DISK_file_close (fh);
155     return GNUNET_SYSERR;
156   }
157   buffer[size] = '\0';
158
159   GNUNET_DISK_file_close (fh);
160   if (0 < size)
161   {
162     line = strtok (buffer, "\n");
163     while (line != NULL) {
164       query = strtok (line, ",");
165       if (NULL == query)
166         break;
167       block = strtok (NULL, ",");
168       if (NULL == block)
169         break;
170       line = strtok (NULL, "\n");
171       entry = GNUNET_malloc (sizeof (struct FlatFileEntry));
172       GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_hash_from_string (query,
173                                         &entry->query));
174       GNUNET_STRINGS_base64_decode (block,
175                                     strlen (block),
176                                     (void**)&block_buffer);
177       entry->block = (struct GNUNET_GNSRECORD_Block *) block_buffer;
178       if (GNUNET_OK !=
179           GNUNET_CONTAINER_multihashmap_put (plugin->hm,
180                                              &entry->query,
181                                              entry,
182                                              GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
183       {
184         GNUNET_free (entry);
185         GNUNET_break (0);
186       }
187     }
188   }
189   GNUNET_free (buffer);
190   return GNUNET_OK;
191 }
192
193 /**
194  * Store values in hashmap in file and free data
195  *
196  * @param plugin the plugin context
197  */
198 static int
199 store_and_free_entries (void *cls,
200                         const struct GNUNET_HashCode *key,
201                         void *value)
202 {
203   struct GNUNET_DISK_FileHandle *fh = cls;
204   struct FlatFileEntry *entry = value;
205
206   char *line;
207   char *block_b64;
208   struct GNUNET_CRYPTO_HashAsciiEncoded query;
209   size_t block_size;
210
211   block_size = ntohl (entry->block->purpose.size) +
212     sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
213     sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
214
215   GNUNET_STRINGS_base64_encode ((char*)entry->block,
216                                 block_size,
217                                 &block_b64);
218   GNUNET_CRYPTO_hash_to_enc (&entry->query,
219                              &query);
220   GNUNET_asprintf (&line,
221                    "%s,%s\n",
222                    (char*)&query,
223                    block_b64);
224
225   GNUNET_free (block_b64);
226
227   GNUNET_DISK_file_write (fh,
228                           line,
229                           strlen (line));
230
231   GNUNET_free (entry->block);
232   GNUNET_free (entry);
233   GNUNET_free (line);
234   return GNUNET_YES;
235 }
236
237 /**
238  * Shutdown database connection and associate data
239  * structures.
240  * @param plugin the plugin context (state for this module)
241  */
242 static void
243 database_shutdown (struct Plugin *plugin)
244 {
245   struct GNUNET_DISK_FileHandle *fh;
246   fh = GNUNET_DISK_file_open (plugin->fn,
247                               GNUNET_DISK_OPEN_CREATE |
248                               GNUNET_DISK_OPEN_TRUNCATE |
249                               GNUNET_DISK_OPEN_READWRITE,
250                               GNUNET_DISK_PERM_USER_WRITE |
251                               GNUNET_DISK_PERM_USER_READ);
252   if (NULL == fh)
253   {
254     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
255                 _("Unable to initialize file: %s.\n"),
256                 plugin->fn);
257     return;
258   }
259
260   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
261                                          &store_and_free_entries,
262                                          fh);
263   GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
264   GNUNET_DISK_file_close (fh);
265
266 }
267
268 static int
269 expire_blocks (void *cls,
270                const struct GNUNET_HashCode *key,
271                void *value)
272 {
273   struct Plugin *plugin = cls;
274   struct FlatFileEntry *entry = value;
275   struct GNUNET_TIME_Absolute now;
276   struct GNUNET_TIME_Absolute expiration;
277
278   now = GNUNET_TIME_absolute_get ();
279   expiration = GNUNET_TIME_absolute_ntoh (entry->block->expiration_time);
280
281   if (0 == GNUNET_TIME_absolute_get_difference (now,
282                                                 expiration).rel_value_us)
283   {
284     GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, key);
285   }
286   return GNUNET_YES;
287 }
288
289
290
291 /**
292  * Removes any expired block.
293  *
294  * @param plugin the plugin
295  */
296 static void
297 namecache_expire_blocks (struct Plugin *plugin)
298 {
299   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
300                                          &expire_blocks,
301                                          plugin);
302 }
303
304
305 /**
306  * Cache a block in the datastore.
307  *
308  * @param cls closure (internal context for the plugin)
309  * @param block block to cache
310  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
311  */
312 static int
313 namecache_cache_block (void *cls,
314                        const struct GNUNET_GNSRECORD_Block *block)
315 {
316   struct Plugin *plugin = cls;
317   struct GNUNET_HashCode query;
318   struct FlatFileEntry *entry;
319   size_t block_size;
320
321   namecache_expire_blocks (plugin);
322   GNUNET_CRYPTO_hash (&block->derived_key,
323                       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
324                       &query);
325   block_size = ntohl (block->purpose.size) +
326     sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
327     sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
328   if (block_size > 64 * 65536)
329   {
330     GNUNET_break (0);
331     return GNUNET_SYSERR;
332   }
333   entry = GNUNET_malloc (sizeof (struct FlatFileEntry));
334   entry->block = GNUNET_malloc (block_size);
335   GNUNET_memcpy (entry->block, block, block_size);
336   GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, &query);
337   if (GNUNET_OK !=
338       GNUNET_CONTAINER_multihashmap_put (plugin->hm,
339                                          &query,
340                                          entry,
341                                          GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
342   {
343     GNUNET_free (entry);
344     GNUNET_break (0);
345     return GNUNET_SYSERR;
346   }
347   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
348               "Caching block under derived key `%s'\n",
349               GNUNET_h2s_full (&query));
350   return GNUNET_OK;
351 }
352
353
354 /**
355  * Get the block for a particular zone and label in the
356  * datastore.  Will return at most one result to the iterator.
357  *
358  * @param cls closure (internal context for the plugin)
359  * @param query hash of public key derived from the zone and the label
360  * @param iter function to call with the result
361  * @param iter_cls closure for @a iter
362  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
363  */
364 static int
365 namecache_lookup_block (void *cls,
366                         const struct GNUNET_HashCode *query,
367                         GNUNET_NAMECACHE_BlockCallback iter, void *iter_cls)
368 {
369   struct Plugin *plugin = cls;
370   const struct GNUNET_GNSRECORD_Block *block;
371
372   block = GNUNET_CONTAINER_multihashmap_get (plugin->hm, query);
373   if (NULL == block)
374     return GNUNET_NO;
375   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
376               "Found block under derived key `%s'\n",
377               GNUNET_h2s_full (query));
378   iter (iter_cls, block);
379   return GNUNET_YES;
380 }
381
382
383 /**
384  * Entry point for the plugin.
385  *
386  * @param cls the "struct GNUNET_NAMECACHE_PluginEnvironment*"
387  * @return NULL on error, otherwise the plugin context
388  */
389 void *
390 libgnunet_plugin_namecache_flat_init (void *cls)
391 {
392   static struct Plugin plugin;
393   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
394   struct GNUNET_NAMECACHE_PluginFunctions *api;
395
396   if (NULL != plugin.cfg)
397     return NULL;                /* can only initialize once! */
398   memset (&plugin, 0, sizeof (struct Plugin));
399   plugin.cfg = cfg;
400   if (GNUNET_OK != database_setup (&plugin))
401   {
402     database_shutdown (&plugin);
403     return NULL;
404   }
405   api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
406   api->cls = &plugin;
407   api->cache_block = &namecache_cache_block;
408   api->lookup_block = &namecache_lookup_block;
409   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
410               _("flat plugin running\n"));
411   return api;
412 }
413
414
415 /**
416  * Exit point from the plugin.
417  *
418  * @param cls the plugin context (as returned by "init")
419  * @return always NULL
420  */
421 void *
422 libgnunet_plugin_namecache_flat_done (void *cls)
423 {
424   struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
425   struct Plugin *plugin = api->cls;
426
427   database_shutdown (plugin);
428   plugin->cfg = NULL;
429   GNUNET_free (api);
430   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
431               "flat plugin is finished\n");
432   return NULL;
433 }
434
435 /* end of plugin_namecache_sqlite.c */